mirror of
https://github.com/Monadical-SAS/cubbi.git
synced 2025-12-21 04:39:07 +00:00
feat(mcp): improve inspector reliability over re-run
This commit is contained in:
22
README.md
22
README.md
@@ -225,11 +225,31 @@ mc mcp list
|
|||||||
# View detailed status of an MCP server
|
# View detailed status of an MCP server
|
||||||
mc mcp status github
|
mc mcp status github
|
||||||
|
|
||||||
# Start/stop/restart an MCP server
|
# Start/stop/restart individual MCP servers
|
||||||
mc mcp start github
|
mc mcp start github
|
||||||
mc mcp stop github
|
mc mcp stop github
|
||||||
mc mcp restart 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
|
# View MCP server logs
|
||||||
mc mcp logs github
|
mc mcp logs github
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,14 @@ mcps:
|
|||||||
mc mcp list # List all configured MCP servers and their status
|
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 status <name> # Show detailed status of a specific MCP server
|
||||||
mc mcp start <name> # Start an MCP server container
|
mc mcp start <name> # Start an MCP server container
|
||||||
mc mcp stop <name> # Stop 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 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
|
mc mcp logs <name> # Show logs for an MCP server container
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -84,10 +90,28 @@ mc session create [--mcp <name>] # Create a session with an MCP server attached
|
|||||||
|
|
||||||
### MCP Container Management
|
### 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
|
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
|
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>`)
|
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)
|
### Proxy-based MCP Servers (Default)
|
||||||
|
|
||||||
|
|||||||
@@ -1047,7 +1047,7 @@ def stop_mcp(
|
|||||||
not_running_count = 0
|
not_running_count = 0
|
||||||
failed_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:
|
for mcp in mcps:
|
||||||
mcp_name = mcp.get("name")
|
mcp_name = mcp.get("name")
|
||||||
@@ -1055,25 +1055,31 @@ def stop_mcp(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
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)
|
result = mcp_manager.stop_mcp(mcp_name)
|
||||||
|
|
||||||
if result:
|
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
|
stopped_count += 1
|
||||||
else:
|
else:
|
||||||
console.print(
|
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
|
not_running_count += 1
|
||||||
except Exception as e:
|
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
|
failed_count += 1
|
||||||
|
|
||||||
# Show a summary
|
# Show a summary
|
||||||
if stopped_count > 0:
|
if stopped_count > 0:
|
||||||
console.print(
|
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:
|
if not_running_count > 0:
|
||||||
console.print(
|
console.print(
|
||||||
@@ -1085,16 +1091,18 @@ def stop_mcp(
|
|||||||
# Otherwise stop a specific server
|
# Otherwise stop a specific server
|
||||||
elif name:
|
elif name:
|
||||||
try:
|
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)
|
result = mcp_manager.stop_mcp(name)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
console.print(f"[green]Stopped MCP server '{name}'[/green]")
|
console.print(f"[green]Stopped and removed MCP server '{name}'[/green]")
|
||||||
else:
|
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:
|
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:
|
else:
|
||||||
console.print(
|
console.print(
|
||||||
"[red]Error: Please provide a server name or use --all to stop all servers[/red]"
|
"[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")
|
@mcp_app.command("inspector")
|
||||||
def run_mcp_inspector(
|
def run_mcp_inspector(
|
||||||
host_port: int = typer.Option(
|
client_port: int = typer.Option(
|
||||||
5173, "--port", "-p", help="Host port for the MCP Inspector"
|
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"),
|
detach: bool = typer.Option(False, "--detach", "-d", help="Run in detached mode"),
|
||||||
stop: bool = typer.Option(False, "--stop", help="Stop running MCP Inspector(s)"),
|
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]"
|
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
|
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:
|
try:
|
||||||
s.bind(("0.0.0.0", host_port))
|
client_socket.bind(("0.0.0.0", client_port))
|
||||||
s.close()
|
client_socket.close()
|
||||||
except socket.error:
|
except socket.error:
|
||||||
console.print(
|
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("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
|
return
|
||||||
|
|
||||||
# Container name with timestamp to avoid conflicts
|
# Container name with timestamp to avoid conflicts
|
||||||
@@ -1601,11 +1632,16 @@ exec npm start
|
|||||||
detach=True,
|
detach=True,
|
||||||
network=initial_network,
|
network=initial_network,
|
||||||
ports={
|
ports={
|
||||||
"5173/tcp": host_port, # Map container port 5173 to host port (frontend)
|
f"{client_port}/tcp": client_port, # Map container port to host port (frontend)
|
||||||
"3000/tcp": 3000, # Map container port 3000 to host port 3000 (backend)
|
f"{server_port}/tcp": server_port, # Map container port to host port (backend)
|
||||||
},
|
},
|
||||||
environment={
|
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={
|
volumes={
|
||||||
script_path: {
|
script_path: {
|
||||||
@@ -1693,13 +1729,32 @@ exec npm start
|
|||||||
return
|
return
|
||||||
|
|
||||||
console.print("[bold]MCP Inspector is available at:[/bold]")
|
console.print("[bold]MCP Inspector is available at:[/bold]")
|
||||||
console.print(f"- Frontend: http://localhost:{host_port}")
|
console.print(f"- Frontend: http://localhost:{client_port}")
|
||||||
console.print("- Backend API: http://localhost:3000")
|
console.print(f"- Backend API: http://localhost:{server_port}")
|
||||||
|
|
||||||
if len(mcp_servers) > 0:
|
if len(mcp_servers) > 0:
|
||||||
console.print(
|
console.print(
|
||||||
f"[green]Auto-connected to {len(mcp_servers)} MCP servers[/green]"
|
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:
|
else:
|
||||||
console.print(
|
console.print(
|
||||||
"[yellow]Warning: No MCP servers found or started. The Inspector will run but won't have any servers to connect to.[/yellow]"
|
"[yellow]Warning: No MCP servers found or started. The Inspector will run but won't have any servers to connect to.[/yellow]"
|
||||||
|
|||||||
@@ -475,21 +475,26 @@ ENTRYPOINT ["/entrypoint.sh"]
|
|||||||
# Get the container name
|
# Get the container name
|
||||||
container_name = self.get_mcp_container_name(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:
|
try:
|
||||||
container = self.client.containers.get(container_name)
|
container = self.client.containers.get(container_name)
|
||||||
# Only stop if it's running
|
|
||||||
|
# Stop the container if it's running
|
||||||
if container.status == "running":
|
if container.status == "running":
|
||||||
|
logger.info(f"Stopping MCP container '{name}'...")
|
||||||
container.stop(timeout=10)
|
container.stop(timeout=10)
|
||||||
|
|
||||||
|
# Remove the container regardless of its status
|
||||||
|
logger.info(f"Removing MCP container '{name}'...")
|
||||||
|
container.remove(force=True)
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
# Container exists but is not running
|
|
||||||
return False
|
|
||||||
except NotFound:
|
except NotFound:
|
||||||
# Container doesn't exist
|
# Container doesn't exist
|
||||||
|
logger.info(f"MCP container '{name}' not found, nothing to stop or remove")
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping MCP container: {e}")
|
logger.error(f"Error stopping/removing MCP container: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def restart_mcp(self, name: str) -> Dict[str, Any]:
|
def restart_mcp(self, name: str) -> Dict[str, Any]:
|
||||||
|
|||||||
Reference in New Issue
Block a user