feat(mcp): improve inspector reliability over re-run

This commit is contained in:
2025-03-25 22:14:33 +01:00
parent d098f268cd
commit 3ee8ce6338
4 changed files with 141 additions and 37 deletions

View File

@@ -225,11 +225,31 @@ mc mcp list
# View detailed status of an MCP server
mc mcp status github
# Start/stop/restart an MCP server
# Start/stop/restart individual MCP servers
mc mcp start github
mc mcp stop github
mc mcp restart github
# Start all MCP servers at once
mc mcp start --all
# Stop and remove all MCP servers at once
mc mcp stop --all
# Run the MCP Inspector to visualize and interact with MCP servers
# It automatically joins all MCP networks for seamless DNS resolution
# Uses two ports: frontend UI (default: 5173) and backend API (default: 3000)
mc mcp inspector
# Run the MCP Inspector with custom ports
mc mcp inspector --client-port 6173 --server-port 6174
# Run the MCP Inspector in detached mode
mc mcp inspector --detach
# Stop the MCP Inspector
mc mcp inspector --stop
# View MCP server logs
mc mcp logs github

View File

@@ -54,11 +54,17 @@ mcps:
```
mc mcp list # List all configured MCP servers and their status
mc mcp status <name> # Show detailed status of a specific MCP server
mc mcp start <name> # Start an MCP server container
mc mcp stop <name> # Stop an MCP server container
mc mcp restart <name> # Restart an MCP server container
mc mcp logs <name> # Show logs for an MCP server container
mc mcp status <name> # Show detailed status of a specific MCP server
mc mcp start <name> # Start an MCP server container
mc mcp stop <name> # Stop and remove an MCP server container
mc mcp restart <name> # Restart an MCP server container
mc mcp start --all # Start all MCP server containers
mc mcp stop --all # Stop and remove all MCP server containers
mc mcp inspector # Run the MCP Inspector UI with network connectivity to all MCP servers
mc mcp inspector --client-port <cp> --server-port <sp> # Run with custom client port (default: 5173) and server port (default: 3000)
mc mcp inspector --detach # Run the inspector in detached mode
mc mcp inspector --stop # Stop the running inspector
mc mcp logs <name> # Show logs for an MCP server container
```
### MCP Configuration
@@ -84,10 +90,28 @@ mc session create [--mcp <name>] # Create a session with an MCP server attached
### MCP Container Management
1. MCP containers will have their own dedicated Docker network
1. MCP containers will have their own dedicated Docker network (`mc-mcp-network`)
2. Session containers will be attached to both their session network and the MCP network when using an MCP
3. MCP containers will be persistent across sessions unless explicitly stopped
4. MCP containers will be named with a prefix to identify them (`mc_mcp_<name>`)
5. Each MCP container will have a network alias matching its name without the prefix (e.g., `mc_mcp_github` will have the alias `github`)
6. Network aliases enable DNS-based service discovery between containers
### MCP Inspector
The MCP Inspector is a web-based UI tool that allows you to:
1. Visualize and interact with multiple MCP servers
2. Debug MCP server messages and interactions
3. Test MCP server capabilities directly
The MCP Inspector implementation includes:
1. A container based on the `mcp/inspector` image
2. Automatic joining of all MCP server networks for seamless DNS resolution
3. A modified Express server that binds to all interfaces (0.0.0.0)
4. Port mapping for both the frontend (default: 5173) and backend API (default: 3000)
5. Network connectivity to all MCP servers using their simple names as DNS hostnames
### Proxy-based MCP Servers (Default)

View File

@@ -1047,7 +1047,7 @@ def stop_mcp(
not_running_count = 0
failed_count = 0
console.print(f"Stopping {len(mcps)} MCP servers...")
console.print(f"Stopping and removing {len(mcps)} MCP servers...")
for mcp in mcps:
mcp_name = mcp.get("name")
@@ -1055,25 +1055,31 @@ def stop_mcp(
continue
try:
with console.status(f"Stopping MCP server '{mcp_name}'..."):
with console.status(
f"Stopping and removing MCP server '{mcp_name}'..."
):
result = mcp_manager.stop_mcp(mcp_name)
if result:
console.print(f"[green]Stopped MCP server '{mcp_name}'[/green]")
console.print(
f"[green]Stopped and removed MCP server '{mcp_name}'[/green]"
)
stopped_count += 1
else:
console.print(
f"[yellow]MCP server '{mcp_name}' was not running[/yellow]"
f"[yellow]MCP server '{mcp_name}' was not running or doesn't exist[/yellow]"
)
not_running_count += 1
except Exception as e:
console.print(f"[red]Error stopping MCP server '{mcp_name}': {e}[/red]")
console.print(
f"[red]Error stopping/removing MCP server '{mcp_name}': {e}[/red]"
)
failed_count += 1
# Show a summary
if stopped_count > 0:
console.print(
f"[green]Successfully stopped {stopped_count} MCP servers[/green]"
f"[green]Successfully stopped and removed {stopped_count} MCP servers[/green]"
)
if not_running_count > 0:
console.print(
@@ -1085,16 +1091,18 @@ def stop_mcp(
# Otherwise stop a specific server
elif name:
try:
with console.status(f"Stopping MCP server '{name}'..."):
with console.status(f"Stopping and removing MCP server '{name}'..."):
result = mcp_manager.stop_mcp(name)
if result:
console.print(f"[green]Stopped MCP server '{name}'[/green]")
console.print(f"[green]Stopped and removed MCP server '{name}'[/green]")
else:
console.print(f"[yellow]MCP server '{name}' was not running[/yellow]")
console.print(
f"[yellow]MCP server '{name}' was not running or doesn't exist[/yellow]"
)
except Exception as e:
console.print(f"[red]Error stopping MCP server: {e}[/red]")
console.print(f"[red]Error stopping/removing MCP server: {e}[/red]")
else:
console.print(
"[red]Error: Please provide a server name or use --all to stop all servers[/red]"
@@ -1321,8 +1329,17 @@ def add_remote_mcp(
@mcp_app.command("inspector")
def run_mcp_inspector(
host_port: int = typer.Option(
5173, "--port", "-p", help="Host port for the MCP Inspector"
client_port: int = typer.Option(
5173,
"--client-port",
"-c",
help="Port for the MCP Inspector frontend (default: 5173)",
),
server_port: int = typer.Option(
3000,
"--server-port",
"-s",
help="Port for the MCP Inspector backend API (default: 3000)",
),
detach: bool = typer.Option(False, "--detach", "-d", help="Run in detached mode"),
stop: bool = typer.Option(False, "--stop", help="Stop running MCP Inspector(s)"),
@@ -1374,19 +1391,33 @@ def run_mcp_inspector(
f"[yellow]Warning: Could not remove existing inspector: {e}[/yellow]"
)
# Check if the specified port is already in use
# Check if the specified ports are already in use
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Check client port
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind(("0.0.0.0", host_port))
s.close()
client_socket.bind(("0.0.0.0", client_port))
client_socket.close()
except socket.error:
console.print(
f"[red]Error: Port {host_port} is already in use by another process.[/red]"
f"[red]Error: Client port {client_port} is already in use by another process.[/red]"
)
console.print("Please stop any web servers or other processes using this port.")
console.print("You can try a different port with --port option")
console.print("You can try a different client port with --client-port option")
return
# Check server port
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
server_socket.bind(("0.0.0.0", server_port))
server_socket.close()
except socket.error:
console.print(
f"[red]Error: Server port {server_port} is already in use by another process.[/red]"
)
console.print("Please stop any web servers or other processes using this port.")
console.print("You can try a different server port with --server-port option")
return
# Container name with timestamp to avoid conflicts
@@ -1601,11 +1632,16 @@ exec npm start
detach=True,
network=initial_network,
ports={
"5173/tcp": host_port, # Map container port 5173 to host port (frontend)
"3000/tcp": 3000, # Map container port 3000 to host port 3000 (backend)
f"{client_port}/tcp": client_port, # Map container port to host port (frontend)
f"{server_port}/tcp": server_port, # Map container port to host port (backend)
},
environment={
"SERVER_PORT": "3000", # Tell the server to use port 3000 (default)
"CLIENT_PORT": str(
client_port
), # Tell the client to use the client_port
"SERVER_PORT": str(
server_port
), # Tell the server to use the server_port
},
volumes={
script_path: {
@@ -1693,13 +1729,32 @@ exec npm start
return
console.print("[bold]MCP Inspector is available at:[/bold]")
console.print(f"- Frontend: http://localhost:{host_port}")
console.print("- Backend API: http://localhost:3000")
console.print(f"- Frontend: http://localhost:{client_port}")
console.print(f"- Backend API: http://localhost:{server_port}")
if len(mcp_servers) > 0:
console.print(
f"[green]Auto-connected to {len(mcp_servers)} MCP servers[/green]"
)
# Print MCP server URLs for access within the Inspector
console.print("[bold]MCP Server URLs (for use within Inspector):[/bold]")
for mcp in all_mcps:
mcp_name = mcp.get("name")
mcp_type = mcp.get("type")
if mcp_type in ["docker", "proxy"]:
# For container-based MCPs, use the container name as hostname
# Default SSE port is 8080 unless specified in proxy_options
sse_port = "8080"
if mcp_type == "proxy" and "proxy_options" in mcp:
sse_port = str(mcp.get("proxy_options", {}).get("sse_port", "8080"))
console.print(f"- {mcp_name}: http://{mcp_name}:{sse_port}/sse")
elif mcp_type == "remote":
# For remote MCPs, use the configured URL
mcp_url = mcp.get("url")
if mcp_url:
console.print(f"- {mcp_name}: {mcp_url}")
else:
console.print(
"[yellow]Warning: No MCP servers found or started. The Inspector will run but won't have any servers to connect to.[/yellow]"

View File

@@ -475,21 +475,26 @@ ENTRYPOINT ["/entrypoint.sh"]
# Get the container name
container_name = self.get_mcp_container_name(name)
# Try to get and stop the container
# Try to get, stop, and remove the container
try:
container = self.client.containers.get(container_name)
# Only stop if it's running
# Stop the container if it's running
if container.status == "running":
logger.info(f"Stopping MCP container '{name}'...")
container.stop(timeout=10)
return True
else:
# Container exists but is not running
return False
# Remove the container regardless of its status
logger.info(f"Removing MCP container '{name}'...")
container.remove(force=True)
return True
except NotFound:
# Container doesn't exist
logger.info(f"MCP container '{name}' not found, nothing to stop or remove")
return False
except Exception as e:
logger.error(f"Error stopping MCP container: {e}")
logger.error(f"Error stopping/removing MCP container: {e}")
return False
def restart_mcp(self, name: str) -> Dict[str, Any]: