mirror of
https://github.com/Monadical-SAS/cubbi.git
synced 2025-12-20 20:29:06 +00:00
feat(mcp): add the possibility to have default mcp to connect to
This commit is contained in:
27
README.md
27
README.md
@@ -165,6 +165,31 @@ mc config volume remove /local/path
|
|||||||
|
|
||||||
Default volumes will be combined with any volumes specified using the `-v` flag when creating a session.
|
Default volumes will be combined with any volumes specified using the `-v` flag when creating a session.
|
||||||
|
|
||||||
|
### Default MCP Servers Configuration
|
||||||
|
|
||||||
|
You can configure default MCP servers that sessions will automatically connect to:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List default MCP servers
|
||||||
|
mc config mcp list
|
||||||
|
|
||||||
|
# Add an MCP server to defaults
|
||||||
|
mc config mcp add github
|
||||||
|
|
||||||
|
# Remove an MCP server from defaults
|
||||||
|
mc config mcp remove github
|
||||||
|
```
|
||||||
|
|
||||||
|
When adding new MCP servers, they are added to defaults by default. Use the `--no-default` flag to prevent this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add an MCP server without adding it to defaults
|
||||||
|
mc mcp add github ghcr.io/mcp/github:latest --no-default
|
||||||
|
mc mcp add-remote jira https://jira-mcp.example.com/sse --no-default
|
||||||
|
```
|
||||||
|
|
||||||
|
When creating sessions, if no MCP server is specified with `--mcp`, the default MCP servers will be used automatically.
|
||||||
|
|
||||||
### External Network Connectivity
|
### External Network Connectivity
|
||||||
|
|
||||||
MC containers can connect to external Docker networks, allowing them to communicate with other services in those networks:
|
MC containers can connect to external Docker networks, allowing them to communicate with other services in those networks:
|
||||||
@@ -269,7 +294,7 @@ mc mcp remote add github http://my-mcp-server.example.com/sse --header "Authoriz
|
|||||||
mc mcp docker add github mcp/github:latest --command "github-mcp" --env GITHUB_TOKEN=ghp_123456
|
mc mcp docker add github mcp/github:latest --command "github-mcp" --env GITHUB_TOKEN=ghp_123456
|
||||||
|
|
||||||
# Add a proxy-based MCP server (for stdio-to-SSE conversion)
|
# Add a proxy-based MCP server (for stdio-to-SSE conversion)
|
||||||
mc mcp proxy add github ghcr.io/mcp/github:latest --proxy-image ghcr.io/sparfenyuk/mcp-proxy:latest --command "github-mcp" --sse-port 8080
|
mc mcp add github ghcr.io/mcp/github:latest --proxy-image ghcr.io/sparfenyuk/mcp-proxy:latest --command "github-mcp" --sse-port 8080 --no-default
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using MCP Servers with Sessions
|
### Using MCP Servers with Sessions
|
||||||
|
|||||||
@@ -204,6 +204,15 @@ def create_session(
|
|||||||
# Combine default networks with user-specified networks, removing duplicates
|
# Combine default networks with user-specified networks, removing duplicates
|
||||||
all_networks = list(set(default_networks + network))
|
all_networks = list(set(default_networks + network))
|
||||||
|
|
||||||
|
# Get default MCPs from user config if none specified
|
||||||
|
all_mcps = mcp if isinstance(mcp, list) else []
|
||||||
|
if not all_mcps:
|
||||||
|
default_mcps = user_config.get("defaults.mcps", [])
|
||||||
|
all_mcps = default_mcps
|
||||||
|
|
||||||
|
if default_mcps:
|
||||||
|
console.print(f"Using default MCP servers: {', '.join(default_mcps)}")
|
||||||
|
|
||||||
if all_networks:
|
if all_networks:
|
||||||
console.print(f"Networks: {', '.join(all_networks)}")
|
console.print(f"Networks: {', '.join(all_networks)}")
|
||||||
|
|
||||||
@@ -222,7 +231,7 @@ def create_session(
|
|||||||
mount_local=not no_mount and user_config.get("defaults.mount_local", True),
|
mount_local=not no_mount and user_config.get("defaults.mount_local", True),
|
||||||
volumes=volume_mounts,
|
volumes=volume_mounts,
|
||||||
networks=all_networks,
|
networks=all_networks,
|
||||||
mcp=mcp,
|
mcp=all_mcps,
|
||||||
)
|
)
|
||||||
|
|
||||||
if session:
|
if session:
|
||||||
@@ -230,11 +239,6 @@ def create_session(
|
|||||||
console.print(f"Session ID: {session.id}")
|
console.print(f"Session ID: {session.id}")
|
||||||
console.print(f"Driver: {session.driver}")
|
console.print(f"Driver: {session.driver}")
|
||||||
|
|
||||||
if session.mcps:
|
|
||||||
console.print("MCP Servers:")
|
|
||||||
for mcp in session.mcps:
|
|
||||||
console.print(f" - {mcp}")
|
|
||||||
|
|
||||||
if session.ports:
|
if session.ports:
|
||||||
console.print("Ports:")
|
console.print("Ports:")
|
||||||
for container_port, host_port in session.ports.items():
|
for container_port, host_port in session.ports.items():
|
||||||
@@ -392,6 +396,13 @@ def quick_create(
|
|||||||
# Use user config for defaults if not specified
|
# Use user config for defaults if not specified
|
||||||
if not driver:
|
if not driver:
|
||||||
driver = user_config.get("defaults.driver")
|
driver = user_config.get("defaults.driver")
|
||||||
|
|
||||||
|
# Get default MCPs if none specified
|
||||||
|
all_mcps = mcp if isinstance(mcp, list) else []
|
||||||
|
if not all_mcps:
|
||||||
|
default_mcps = user_config.get("defaults.mcps", [])
|
||||||
|
if default_mcps:
|
||||||
|
all_mcps = default_mcps
|
||||||
|
|
||||||
create_session(
|
create_session(
|
||||||
driver=driver,
|
driver=driver,
|
||||||
@@ -402,7 +413,7 @@ def quick_create(
|
|||||||
name=name,
|
name=name,
|
||||||
no_connect=no_connect,
|
no_connect=no_connect,
|
||||||
no_mount=no_mount,
|
no_mount=no_mount,
|
||||||
mcp=mcp,
|
mcp=all_mcps,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -522,6 +533,64 @@ config_app.add_typer(network_app, name="network", no_args_is_help=True)
|
|||||||
volume_app = typer.Typer(help="Manage default volumes")
|
volume_app = typer.Typer(help="Manage default volumes")
|
||||||
config_app.add_typer(volume_app, name="volume", no_args_is_help=True)
|
config_app.add_typer(volume_app, name="volume", no_args_is_help=True)
|
||||||
|
|
||||||
|
# Create an MCP subcommand for config
|
||||||
|
config_mcp_app = typer.Typer(help="Manage default MCP servers")
|
||||||
|
config_app.add_typer(config_mcp_app, name="mcp", no_args_is_help=True)
|
||||||
|
|
||||||
|
# MCP configuration commands
|
||||||
|
@config_mcp_app.command("list")
|
||||||
|
def list_default_mcps() -> None:
|
||||||
|
"""List all default MCP servers"""
|
||||||
|
default_mcps = user_config.get("defaults.mcps", [])
|
||||||
|
|
||||||
|
if not default_mcps:
|
||||||
|
console.print("No default MCP servers configured")
|
||||||
|
return
|
||||||
|
|
||||||
|
table = Table(show_header=True, header_style="bold")
|
||||||
|
table.add_column("MCP Server")
|
||||||
|
|
||||||
|
for mcp in default_mcps:
|
||||||
|
table.add_row(mcp)
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
@config_mcp_app.command("add")
|
||||||
|
def add_default_mcp(
|
||||||
|
name: str = typer.Argument(..., help="MCP server name to add to defaults"),
|
||||||
|
) -> None:
|
||||||
|
"""Add an MCP server to default MCPs"""
|
||||||
|
# First check if the MCP server exists
|
||||||
|
mcp = mcp_manager.get_mcp(name)
|
||||||
|
if not mcp:
|
||||||
|
console.print(f"[red]MCP server '{name}' not found[/red]")
|
||||||
|
return
|
||||||
|
|
||||||
|
default_mcps = user_config.get("defaults.mcps", [])
|
||||||
|
|
||||||
|
if name in default_mcps:
|
||||||
|
console.print(f"MCP server '{name}' is already in defaults")
|
||||||
|
return
|
||||||
|
|
||||||
|
default_mcps.append(name)
|
||||||
|
user_config.set("defaults.mcps", default_mcps)
|
||||||
|
console.print(f"[green]Added MCP server '{name}' to defaults[/green]")
|
||||||
|
|
||||||
|
@config_mcp_app.command("remove")
|
||||||
|
def remove_default_mcp(
|
||||||
|
name: str = typer.Argument(..., help="MCP server name to remove from defaults"),
|
||||||
|
) -> None:
|
||||||
|
"""Remove an MCP server from default MCPs"""
|
||||||
|
default_mcps = user_config.get("defaults.mcps", [])
|
||||||
|
|
||||||
|
if name not in default_mcps:
|
||||||
|
console.print(f"MCP server '{name}' is not in defaults")
|
||||||
|
return
|
||||||
|
|
||||||
|
default_mcps.remove(name)
|
||||||
|
user_config.set("defaults.mcps", default_mcps)
|
||||||
|
console.print(f"[green]Removed MCP server '{name}' from defaults[/green]")
|
||||||
|
|
||||||
|
|
||||||
# Configuration commands
|
# Configuration commands
|
||||||
@config_app.command("list")
|
@config_app.command("list")
|
||||||
@@ -1252,6 +1321,9 @@ def add_mcp(
|
|||||||
env: List[str] = typer.Option(
|
env: List[str] = typer.Option(
|
||||||
[], "--env", "-e", help="Environment variables (format: KEY=VALUE)"
|
[], "--env", "-e", help="Environment variables (format: KEY=VALUE)"
|
||||||
),
|
),
|
||||||
|
no_default: bool = typer.Option(
|
||||||
|
False, "--no-default", help="Don't add MCP server to defaults"
|
||||||
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add a proxy-based MCP server (default type)"""
|
"""Add a proxy-based MCP server (default type)"""
|
||||||
# Parse environment variables
|
# Parse environment variables
|
||||||
@@ -1282,6 +1354,7 @@ def add_mcp(
|
|||||||
proxy_options,
|
proxy_options,
|
||||||
environment,
|
environment,
|
||||||
host_port,
|
host_port,
|
||||||
|
add_as_default=not no_default,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the assigned port
|
# Get the assigned port
|
||||||
@@ -1292,6 +1365,11 @@ def add_mcp(
|
|||||||
console.print(
|
console.print(
|
||||||
f"Container port {sse_port} will be bound to host port {assigned_port}"
|
f"Container port {sse_port} will be bound to host port {assigned_port}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not no_default:
|
||||||
|
console.print(f"MCP server '{name}' added to defaults")
|
||||||
|
else:
|
||||||
|
console.print(f"MCP server '{name}' not added to defaults")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[red]Error adding MCP server: {e}[/red]")
|
console.print(f"[red]Error adding MCP server: {e}[/red]")
|
||||||
@@ -1304,6 +1382,9 @@ def add_remote_mcp(
|
|||||||
header: List[str] = typer.Option(
|
header: List[str] = typer.Option(
|
||||||
[], "--header", "-H", help="HTTP headers (format: KEY=VALUE)"
|
[], "--header", "-H", help="HTTP headers (format: KEY=VALUE)"
|
||||||
),
|
),
|
||||||
|
no_default: bool = typer.Option(
|
||||||
|
False, "--no-default", help="Don't add MCP server to defaults"
|
||||||
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add a remote MCP server"""
|
"""Add a remote MCP server"""
|
||||||
# Parse headers
|
# Parse headers
|
||||||
@@ -1319,9 +1400,14 @@ def add_remote_mcp(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with console.status(f"Adding remote MCP server '{name}'..."):
|
with console.status(f"Adding remote MCP server '{name}'..."):
|
||||||
mcp_manager.add_remote_mcp(name, url, headers)
|
mcp_manager.add_remote_mcp(name, url, headers, add_as_default=not no_default)
|
||||||
|
|
||||||
console.print(f"[green]Added remote MCP server '{name}'[/green]")
|
console.print(f"[green]Added remote MCP server '{name}'[/green]")
|
||||||
|
|
||||||
|
if not no_default:
|
||||||
|
console.print(f"MCP server '{name}' added to defaults")
|
||||||
|
else:
|
||||||
|
console.print(f"MCP server '{name}' not added to defaults")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[red]Error adding remote MCP server: {e}[/red]")
|
console.print(f"[red]Error adding remote MCP server: {e}[/red]")
|
||||||
|
|||||||
@@ -74,9 +74,19 @@ class MCPManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def add_remote_mcp(
|
def add_remote_mcp(
|
||||||
self, name: str, url: str, headers: Dict[str, str] = None
|
self, name: str, url: str, headers: Dict[str, str] = None, add_as_default: bool = True
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Add a remote MCP server."""
|
"""Add a remote MCP server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the MCP server
|
||||||
|
url: URL of the remote MCP server
|
||||||
|
headers: HTTP headers to use when connecting
|
||||||
|
add_as_default: Whether to add this MCP to the default MCPs list
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The MCP configuration dictionary
|
||||||
|
"""
|
||||||
# Create the remote MCP configuration
|
# Create the remote MCP configuration
|
||||||
remote_mcp = RemoteMCP(
|
remote_mcp = RemoteMCP(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -91,17 +101,36 @@ class MCPManager:
|
|||||||
mcps = [mcp for mcp in mcps if mcp.get("name") != name]
|
mcps = [mcp for mcp in mcps if mcp.get("name") != name]
|
||||||
|
|
||||||
# Add the new MCP
|
# Add the new MCP
|
||||||
mcps.append(remote_mcp.model_dump())
|
mcp_config = remote_mcp.model_dump()
|
||||||
|
mcps.append(mcp_config)
|
||||||
|
|
||||||
# Save the configuration
|
# Save the configuration
|
||||||
self.config_manager.set("mcps", mcps)
|
self.config_manager.set("mcps", mcps)
|
||||||
|
|
||||||
|
# Add to default MCPs if requested
|
||||||
|
if add_as_default:
|
||||||
|
default_mcps = self.config_manager.get("defaults.mcps", [])
|
||||||
|
if name not in default_mcps:
|
||||||
|
default_mcps.append(name)
|
||||||
|
self.config_manager.set("defaults.mcps", default_mcps)
|
||||||
|
|
||||||
return remote_mcp.model_dump()
|
return mcp_config
|
||||||
|
|
||||||
def add_docker_mcp(
|
def add_docker_mcp(
|
||||||
self, name: str, image: str, command: str, env: Dict[str, str] = None
|
self, name: str, image: str, command: str, env: Dict[str, str] = None, add_as_default: bool = True
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Add a Docker-based MCP server."""
|
"""Add a Docker-based MCP server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the MCP server
|
||||||
|
image: Docker image for the MCP server
|
||||||
|
command: Command to run in the container
|
||||||
|
env: Environment variables to set in the container
|
||||||
|
add_as_default: Whether to add this MCP to the default MCPs list
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The MCP configuration dictionary
|
||||||
|
"""
|
||||||
# Create the Docker MCP configuration
|
# Create the Docker MCP configuration
|
||||||
docker_mcp = DockerMCP(
|
docker_mcp = DockerMCP(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -117,12 +146,20 @@ class MCPManager:
|
|||||||
mcps = [mcp for mcp in mcps if mcp.get("name") != name]
|
mcps = [mcp for mcp in mcps if mcp.get("name") != name]
|
||||||
|
|
||||||
# Add the new MCP
|
# Add the new MCP
|
||||||
mcps.append(docker_mcp.model_dump())
|
mcp_config = docker_mcp.model_dump()
|
||||||
|
mcps.append(mcp_config)
|
||||||
|
|
||||||
# Save the configuration
|
# Save the configuration
|
||||||
self.config_manager.set("mcps", mcps)
|
self.config_manager.set("mcps", mcps)
|
||||||
|
|
||||||
|
# Add to default MCPs if requested
|
||||||
|
if add_as_default:
|
||||||
|
default_mcps = self.config_manager.get("defaults.mcps", [])
|
||||||
|
if name not in default_mcps:
|
||||||
|
default_mcps.append(name)
|
||||||
|
self.config_manager.set("defaults.mcps", default_mcps)
|
||||||
|
|
||||||
return docker_mcp.model_dump()
|
return mcp_config
|
||||||
|
|
||||||
def add_proxy_mcp(
|
def add_proxy_mcp(
|
||||||
self,
|
self,
|
||||||
@@ -133,8 +170,23 @@ class MCPManager:
|
|||||||
proxy_options: Dict[str, Any] = None,
|
proxy_options: Dict[str, Any] = None,
|
||||||
env: Dict[str, str] = None,
|
env: Dict[str, str] = None,
|
||||||
host_port: Optional[int] = None,
|
host_port: Optional[int] = None,
|
||||||
|
add_as_default: bool = True,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Add a proxy-based MCP server."""
|
"""Add a proxy-based MCP server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the MCP server
|
||||||
|
base_image: Base Docker image running the actual MCP server
|
||||||
|
proxy_image: Docker image for the MCP proxy
|
||||||
|
command: Command to run in the container
|
||||||
|
proxy_options: Options for the MCP proxy
|
||||||
|
env: Environment variables to set in the container
|
||||||
|
host_port: Host port to bind the MCP server to (auto-assigned if not specified)
|
||||||
|
add_as_default: Whether to add this MCP to the default MCPs list
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The MCP configuration dictionary
|
||||||
|
"""
|
||||||
# If no host port specified, find the next available port starting from 5101
|
# If no host port specified, find the next available port starting from 5101
|
||||||
if host_port is None:
|
if host_port is None:
|
||||||
# Get current MCPs and find highest assigned port
|
# Get current MCPs and find highest assigned port
|
||||||
@@ -171,15 +223,30 @@ class MCPManager:
|
|||||||
mcps = [mcp for mcp in mcps if mcp.get("name") != name]
|
mcps = [mcp for mcp in mcps if mcp.get("name") != name]
|
||||||
|
|
||||||
# Add the new MCP
|
# Add the new MCP
|
||||||
mcps.append(proxy_mcp.model_dump())
|
mcp_config = proxy_mcp.model_dump()
|
||||||
|
mcps.append(mcp_config)
|
||||||
|
|
||||||
# Save the configuration
|
# Save the configuration
|
||||||
self.config_manager.set("mcps", mcps)
|
self.config_manager.set("mcps", mcps)
|
||||||
|
|
||||||
|
# Add to default MCPs if requested
|
||||||
|
if add_as_default:
|
||||||
|
default_mcps = self.config_manager.get("defaults.mcps", [])
|
||||||
|
if name not in default_mcps:
|
||||||
|
default_mcps.append(name)
|
||||||
|
self.config_manager.set("defaults.mcps", default_mcps)
|
||||||
|
|
||||||
return proxy_mcp.model_dump()
|
return mcp_config
|
||||||
|
|
||||||
def remove_mcp(self, name: str) -> bool:
|
def remove_mcp(self, name: str) -> bool:
|
||||||
"""Remove an MCP server configuration."""
|
"""Remove an MCP server configuration.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the MCP server to remove
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the MCP was successfully removed, False otherwise
|
||||||
|
"""
|
||||||
mcps = self.list_mcps()
|
mcps = self.list_mcps()
|
||||||
|
|
||||||
# Filter out the MCP with the specified name
|
# Filter out the MCP with the specified name
|
||||||
@@ -191,6 +258,12 @@ class MCPManager:
|
|||||||
|
|
||||||
# Save the updated configuration
|
# Save the updated configuration
|
||||||
self.config_manager.set("mcps", updated_mcps)
|
self.config_manager.set("mcps", updated_mcps)
|
||||||
|
|
||||||
|
# Also remove from default MCPs if it's there
|
||||||
|
default_mcps = self.config_manager.get("defaults.mcps", [])
|
||||||
|
if name in default_mcps:
|
||||||
|
default_mcps.remove(name)
|
||||||
|
self.config_manager.set("defaults.mcps", default_mcps)
|
||||||
|
|
||||||
# Stop and remove the container if it exists
|
# Stop and remove the container if it exists
|
||||||
self.stop_mcp(name)
|
self.stop_mcp(name)
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class UserConfigManager:
|
|||||||
"mount_local": True,
|
"mount_local": True,
|
||||||
"networks": [], # Default networks to connect to (besides mc-network)
|
"networks": [], # Default networks to connect to (besides mc-network)
|
||||||
"volumes": [], # Default volumes to mount, format: "source:dest"
|
"volumes": [], # Default volumes to mount, format: "source:dest"
|
||||||
|
"mcps": [], # Default MCP servers to connect to
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"langfuse": {},
|
"langfuse": {},
|
||||||
|
|||||||
Reference in New Issue
Block a user