mirror of
https://github.com/Monadical-SAS/cubbi.git
synced 2025-12-21 04:39:07 +00:00
feat(mcp): ensure inner mcp environemnt variables are passed
This commit is contained in:
30
README.md
30
README.md
@@ -5,6 +5,14 @@ containers that run AI tools and development environments. It works with both
|
|||||||
local Docker and a dedicated remote web service that manages containers in a
|
local Docker and a dedicated remote web service that manages containers in a
|
||||||
Docker-in-Docker (DinD) environment. MC also supports connecting to MCP (Model Control Protocol) servers to extend AI tools with additional capabilities.
|
Docker-in-Docker (DinD) environment. MC also supports connecting to MCP (Model Control Protocol) servers to extend AI tools with additional capabilities.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
- `mc session create` - Create a new session
|
||||||
|
- `mcx` - Shortcut for `mc session create` (mount directories or clone repos)
|
||||||
|
- `mcx .` - Mount the current directory
|
||||||
|
- `mcx /path/to/dir` - Mount a specific directory
|
||||||
|
- `mcx https://github.com/user/repo` - Clone a repository
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- [uv](https://docs.astral.sh/uv/)
|
- [uv](https://docs.astral.sh/uv/)
|
||||||
@@ -27,10 +35,12 @@ mc --help
|
|||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create a new session with the default driver
|
# Show help message (displays available commands)
|
||||||
# mc create session -- is the full command
|
|
||||||
mc
|
mc
|
||||||
|
|
||||||
|
# Create a new session with the default driver
|
||||||
|
mc session create
|
||||||
|
|
||||||
# List all active sessions
|
# List all active sessions
|
||||||
mc session list
|
mc session list
|
||||||
|
|
||||||
@@ -50,17 +60,27 @@ mc session create -e VAR1=value1 -e VAR2=value2
|
|||||||
mc session create -v /local/path:/container/path
|
mc session create -v /local/path:/container/path
|
||||||
mc session create -v ~/data:/data -v ./configs:/etc/app/config
|
mc session create -v ~/data:/data -v ./configs:/etc/app/config
|
||||||
|
|
||||||
|
# Mount a local directory (current directory or specific path)
|
||||||
|
mc session create .
|
||||||
|
mc session create /path/to/project
|
||||||
|
|
||||||
# Connect to external Docker networks
|
# Connect to external Docker networks
|
||||||
mc session create --network teamnet --network dbnet
|
mc session create --network teamnet --network dbnet
|
||||||
|
|
||||||
# Connect to MCP servers for extended capabilities
|
# Connect to MCP servers for extended capabilities
|
||||||
mc session create --mcp github --mcp jira
|
mc session create --mcp github --mcp jira
|
||||||
|
|
||||||
# Shorthand for creating a session with a project repository
|
# Clone a Git repository
|
||||||
mc github.com/username/repo
|
mc session create https://github.com/username/repo
|
||||||
|
|
||||||
|
# Using the mcx shortcut (equivalent to mc session create)
|
||||||
|
mcx # Creates a session without mounting anything
|
||||||
|
mcx . # Mounts the current directory
|
||||||
|
mcx /path/to/project # Mounts the specified directory
|
||||||
|
mcx https://github.com/username/repo # Clones the repository
|
||||||
|
|
||||||
# Shorthand with MCP servers
|
# Shorthand with MCP servers
|
||||||
mc github.com/username/repo --mcp github
|
mcx https://github.com/username/repo --mcp github
|
||||||
```
|
```
|
||||||
|
|
||||||
## Driver Management
|
## Driver Management
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ CLI for Monadical Container Tool.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
import typer
|
import typer
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
@@ -15,11 +16,18 @@ from .user_config import UserConfigManager
|
|||||||
from .session import SessionManager
|
from .session import SessionManager
|
||||||
from .mcp import MCPManager
|
from .mcp import MCPManager
|
||||||
|
|
||||||
app = typer.Typer(help="Monadical Container Tool")
|
# Configure logging - will only show logs if --verbose flag is used
|
||||||
session_app = typer.Typer(help="Manage MC sessions")
|
logging.basicConfig(
|
||||||
|
level=logging.WARNING, # Default to WARNING level
|
||||||
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||||
|
handlers=[logging.StreamHandler()],
|
||||||
|
)
|
||||||
|
|
||||||
|
app = typer.Typer(help="Monadical Container Tool", no_args_is_help=True)
|
||||||
|
session_app = typer.Typer(help="Manage MC sessions", no_args_is_help=True)
|
||||||
driver_app = typer.Typer(help="Manage MC drivers", no_args_is_help=True)
|
driver_app = typer.Typer(help="Manage MC drivers", no_args_is_help=True)
|
||||||
config_app = typer.Typer(help="Manage MC configuration")
|
config_app = typer.Typer(help="Manage MC configuration", no_args_is_help=True)
|
||||||
mcp_app = typer.Typer(help="Manage MCP servers")
|
mcp_app = typer.Typer(help="Manage MCP servers", no_args_is_help=True)
|
||||||
app.add_typer(session_app, name="session", no_args_is_help=True)
|
app.add_typer(session_app, name="session", no_args_is_help=True)
|
||||||
app.add_typer(driver_app, name="driver", no_args_is_help=True)
|
app.add_typer(driver_app, name="driver", no_args_is_help=True)
|
||||||
app.add_typer(config_app, name="config", no_args_is_help=True)
|
app.add_typer(config_app, name="config", no_args_is_help=True)
|
||||||
@@ -33,21 +41,21 @@ container_manager = ContainerManager(config_manager, session_manager, user_confi
|
|||||||
mcp_manager = MCPManager(config_manager=user_config)
|
mcp_manager = MCPManager(config_manager=user_config)
|
||||||
|
|
||||||
|
|
||||||
@app.callback(invoke_without_command=True)
|
@app.callback()
|
||||||
def main(ctx: typer.Context) -> None:
|
def main(
|
||||||
"""Monadical Container Tool"""
|
ctx: typer.Context,
|
||||||
# If no command is specified, create a session
|
verbose: bool = typer.Option(
|
||||||
if ctx.invoked_subcommand is None:
|
False, "--verbose", "-v", help="Enable verbose logging"
|
||||||
create_session(
|
),
|
||||||
driver=None,
|
) -> None:
|
||||||
project=None,
|
"""Monadical Container Tool
|
||||||
env=[],
|
|
||||||
volume=[],
|
Run 'mc session create' to create a new session.
|
||||||
network=[],
|
Use 'mcx' as a shortcut for 'mc session create'.
|
||||||
name=None,
|
"""
|
||||||
no_connect=False,
|
# Set log level based on verbose flag
|
||||||
no_mount=False,
|
if verbose:
|
||||||
)
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
@@ -120,8 +128,10 @@ def list_sessions() -> None:
|
|||||||
@session_app.command("create")
|
@session_app.command("create")
|
||||||
def create_session(
|
def create_session(
|
||||||
driver: Optional[str] = typer.Option(None, "--driver", "-d", help="Driver to use"),
|
driver: Optional[str] = typer.Option(None, "--driver", "-d", help="Driver to use"),
|
||||||
project: Optional[str] = typer.Option(
|
project: Optional[str] = typer.Argument(
|
||||||
None, "--project", "-p", help="Project repository URL"
|
None,
|
||||||
|
help="Local directory path to mount or repository URL to clone",
|
||||||
|
show_default=False,
|
||||||
),
|
),
|
||||||
env: List[str] = typer.Option(
|
env: List[str] = typer.Option(
|
||||||
[], "--env", "-e", help="Environment variables (KEY=VALUE)"
|
[], "--env", "-e", help="Environment variables (KEY=VALUE)"
|
||||||
@@ -136,11 +146,6 @@ def create_session(
|
|||||||
no_connect: bool = typer.Option(
|
no_connect: bool = typer.Option(
|
||||||
False, "--no-connect", help="Don't automatically connect to the session"
|
False, "--no-connect", help="Don't automatically connect to the session"
|
||||||
),
|
),
|
||||||
no_mount: bool = typer.Option(
|
|
||||||
False,
|
|
||||||
"--no-mount",
|
|
||||||
help="Don't mount local directory to /app (ignored if --project is used)",
|
|
||||||
),
|
|
||||||
mcp: List[str] = typer.Option(
|
mcp: List[str] = typer.Option(
|
||||||
[],
|
[],
|
||||||
"--mcp",
|
"--mcp",
|
||||||
@@ -148,7 +153,12 @@ def create_session(
|
|||||||
help="Attach MCP servers to the session (can be specified multiple times)",
|
help="Attach MCP servers to the session (can be specified multiple times)",
|
||||||
),
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create a new MC session"""
|
"""Create a new MC session
|
||||||
|
|
||||||
|
If a local directory path is provided, it will be mounted at /app in the container.
|
||||||
|
If a repository URL is provided, it will be cloned into /app during initialization.
|
||||||
|
If no path or URL is provided, no local volume will be mounted.
|
||||||
|
"""
|
||||||
# Use default driver from user configuration
|
# Use default driver from user configuration
|
||||||
if not driver:
|
if not driver:
|
||||||
driver = user_config.get(
|
driver = user_config.get(
|
||||||
@@ -209,7 +219,7 @@ def create_session(
|
|||||||
if not all_mcps:
|
if not all_mcps:
|
||||||
default_mcps = user_config.get("defaults.mcps", [])
|
default_mcps = user_config.get("defaults.mcps", [])
|
||||||
all_mcps = default_mcps
|
all_mcps = default_mcps
|
||||||
|
|
||||||
if default_mcps:
|
if default_mcps:
|
||||||
console.print(f"Using default MCP servers: {', '.join(default_mcps)}")
|
console.print(f"Using default MCP servers: {', '.join(default_mcps)}")
|
||||||
|
|
||||||
@@ -223,12 +233,18 @@ def create_session(
|
|||||||
console.print(f" {host_path} -> {mount_info['bind']}")
|
console.print(f" {host_path} -> {mount_info['bind']}")
|
||||||
|
|
||||||
with console.status(f"Creating session with driver '{driver}'..."):
|
with console.status(f"Creating session with driver '{driver}'..."):
|
||||||
|
# If project is a local directory, we should mount it
|
||||||
|
# If it's a Git URL or doesn't exist, handle accordingly
|
||||||
|
mount_local = False
|
||||||
|
if project and os.path.isdir(os.path.expanduser(project)):
|
||||||
|
mount_local = True
|
||||||
|
|
||||||
session = container_manager.create_session(
|
session = container_manager.create_session(
|
||||||
driver_name=driver,
|
driver_name=driver,
|
||||||
project=project,
|
project=project,
|
||||||
environment=environment,
|
environment=environment,
|
||||||
session_name=name,
|
session_name=name,
|
||||||
mount_local=not no_mount and user_config.get("defaults.mount_local", True),
|
mount_local=mount_local,
|
||||||
volumes=volume_mounts,
|
volumes=volume_mounts,
|
||||||
networks=all_networks,
|
networks=all_networks,
|
||||||
mcp=all_mcps,
|
mcp=all_mcps,
|
||||||
@@ -362,61 +378,6 @@ def stop() -> None:
|
|||||||
os.system("kill 1") # Send SIGTERM to PID 1 (container's init process)
|
os.system("kill 1") # Send SIGTERM to PID 1 (container's init process)
|
||||||
|
|
||||||
|
|
||||||
# Main CLI entry point that handles project repository URLs
|
|
||||||
@app.command(name="")
|
|
||||||
def quick_create(
|
|
||||||
project: Optional[str] = typer.Argument(..., help="Project repository URL"),
|
|
||||||
driver: Optional[str] = typer.Option(None, "--driver", "-d", help="Driver to use"),
|
|
||||||
env: List[str] = typer.Option(
|
|
||||||
[], "--env", "-e", help="Environment variables (KEY=VALUE)"
|
|
||||||
),
|
|
||||||
volume: List[str] = typer.Option(
|
|
||||||
[], "--volume", "-v", help="Mount volumes (LOCAL_PATH:CONTAINER_PATH)"
|
|
||||||
),
|
|
||||||
network: List[str] = typer.Option(
|
|
||||||
[], "--network", "-N", help="Connect to additional Docker networks"
|
|
||||||
),
|
|
||||||
name: Optional[str] = typer.Option(None, "--name", "-n", help="Session name"),
|
|
||||||
no_connect: bool = typer.Option(
|
|
||||||
False, "--no-connect", help="Don't automatically connect to the session"
|
|
||||||
),
|
|
||||||
no_mount: bool = typer.Option(
|
|
||||||
False,
|
|
||||||
"--no-mount",
|
|
||||||
help="Don't mount local directory to /app (ignored if a project is specified)",
|
|
||||||
),
|
|
||||||
mcp: List[str] = typer.Option(
|
|
||||||
[],
|
|
||||||
"--mcp",
|
|
||||||
"-m",
|
|
||||||
help="Attach MCP servers to the session (can be specified multiple times)",
|
|
||||||
),
|
|
||||||
) -> None:
|
|
||||||
"""Create a new MC session with a project repository"""
|
|
||||||
# Use user config for defaults if not specified
|
|
||||||
if not 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(
|
|
||||||
driver=driver,
|
|
||||||
project=project,
|
|
||||||
env=env,
|
|
||||||
volume=volume,
|
|
||||||
network=network,
|
|
||||||
name=name,
|
|
||||||
no_connect=no_connect,
|
|
||||||
no_mount=no_mount,
|
|
||||||
mcp=all_mcps,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@driver_app.command("list")
|
@driver_app.command("list")
|
||||||
def list_drivers() -> None:
|
def list_drivers() -> None:
|
||||||
"""List available MC drivers"""
|
"""List available MC drivers"""
|
||||||
@@ -537,6 +498,7 @@ config_app.add_typer(volume_app, name="volume", no_args_is_help=True)
|
|||||||
config_mcp_app = typer.Typer(help="Manage default MCP servers")
|
config_mcp_app = typer.Typer(help="Manage default MCP servers")
|
||||||
config_app.add_typer(config_mcp_app, name="mcp", no_args_is_help=True)
|
config_app.add_typer(config_mcp_app, name="mcp", no_args_is_help=True)
|
||||||
|
|
||||||
|
|
||||||
# MCP configuration commands
|
# MCP configuration commands
|
||||||
@config_mcp_app.command("list")
|
@config_mcp_app.command("list")
|
||||||
def list_default_mcps() -> None:
|
def list_default_mcps() -> None:
|
||||||
@@ -555,6 +517,7 @@ def list_default_mcps() -> None:
|
|||||||
|
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
@config_mcp_app.command("add")
|
@config_mcp_app.command("add")
|
||||||
def add_default_mcp(
|
def add_default_mcp(
|
||||||
name: str = typer.Argument(..., help="MCP server name to add to defaults"),
|
name: str = typer.Argument(..., help="MCP server name to add to defaults"),
|
||||||
@@ -576,6 +539,7 @@ def add_default_mcp(
|
|||||||
user_config.set("defaults.mcps", default_mcps)
|
user_config.set("defaults.mcps", default_mcps)
|
||||||
console.print(f"[green]Added MCP server '{name}' to defaults[/green]")
|
console.print(f"[green]Added MCP server '{name}' to defaults[/green]")
|
||||||
|
|
||||||
|
|
||||||
@config_mcp_app.command("remove")
|
@config_mcp_app.command("remove")
|
||||||
def remove_default_mcp(
|
def remove_default_mcp(
|
||||||
name: str = typer.Argument(..., help="MCP server name to remove from defaults"),
|
name: str = typer.Argument(..., help="MCP server name to remove from defaults"),
|
||||||
@@ -1017,8 +981,15 @@ def mcp_status(name: str = typer.Argument(..., help="MCP server name")) -> None:
|
|||||||
def start_mcp(
|
def start_mcp(
|
||||||
name: Optional[str] = typer.Argument(None, help="MCP server name"),
|
name: Optional[str] = typer.Argument(None, help="MCP server name"),
|
||||||
all_servers: bool = typer.Option(False, "--all", help="Start all MCP servers"),
|
all_servers: bool = typer.Option(False, "--all", help="Start all MCP servers"),
|
||||||
|
verbose: bool = typer.Option(
|
||||||
|
False, "--verbose", "-v", help="Enable verbose logging"
|
||||||
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Start an MCP server or all servers"""
|
"""Start an MCP server or all servers"""
|
||||||
|
# Set log level based on verbose flag
|
||||||
|
if verbose:
|
||||||
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
||||||
# Check if we need to start all servers
|
# Check if we need to start all servers
|
||||||
if all_servers:
|
if all_servers:
|
||||||
# Get all configured MCP servers
|
# Get all configured MCP servers
|
||||||
@@ -1281,6 +1252,26 @@ def mcp_logs(
|
|||||||
def remove_mcp(name: str = typer.Argument(..., help="MCP server name")) -> None:
|
def remove_mcp(name: str = typer.Argument(..., help="MCP server name")) -> None:
|
||||||
"""Remove an MCP server configuration"""
|
"""Remove an MCP server configuration"""
|
||||||
try:
|
try:
|
||||||
|
# Check if any active sessions might be using this MCP
|
||||||
|
active_sessions = container_manager.list_sessions()
|
||||||
|
affected_sessions = []
|
||||||
|
|
||||||
|
for session in active_sessions:
|
||||||
|
if session.mcps and name in session.mcps:
|
||||||
|
affected_sessions.append(session)
|
||||||
|
|
||||||
|
# Just warn users about affected sessions
|
||||||
|
if affected_sessions:
|
||||||
|
console.print(
|
||||||
|
f"[yellow]Warning: Found {len(affected_sessions)} active sessions using MCP '{name}'[/yellow]"
|
||||||
|
)
|
||||||
|
console.print(
|
||||||
|
"[yellow]You may need to restart these sessions for changes to take effect:[/yellow]"
|
||||||
|
)
|
||||||
|
for session in affected_sessions:
|
||||||
|
console.print(f" - Session: {session.id} ({session.name})")
|
||||||
|
|
||||||
|
# Remove the MCP from configuration
|
||||||
with console.status(f"Removing MCP server '{name}'..."):
|
with console.status(f"Removing MCP server '{name}'..."):
|
||||||
result = mcp_manager.remove_mcp(name)
|
result = mcp_manager.remove_mcp(name)
|
||||||
|
|
||||||
@@ -1365,7 +1356,7 @@ 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:
|
if not no_default:
|
||||||
console.print(f"MCP server '{name}' added to defaults")
|
console.print(f"MCP server '{name}' added to defaults")
|
||||||
else:
|
else:
|
||||||
@@ -1400,10 +1391,12 @@ 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, add_as_default=not no_default)
|
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:
|
if not no_default:
|
||||||
console.print(f"MCP server '{name}' added to defaults")
|
console.print(f"MCP server '{name}' added to defaults")
|
||||||
else:
|
else:
|
||||||
@@ -1861,5 +1854,27 @@ exec npm start
|
|||||||
console.print("[green]MCP Inspector stopped[/green]")
|
console.print("[green]MCP Inspector stopped[/green]")
|
||||||
|
|
||||||
|
|
||||||
|
def session_create_entry_point():
|
||||||
|
"""Entry point that directly invokes 'mc session create'.
|
||||||
|
|
||||||
|
This provides a convenient shortcut:
|
||||||
|
- 'mcx' runs as if you typed 'mc session create'
|
||||||
|
- 'mcx .' mounts the current directory
|
||||||
|
- 'mcx /path/to/project' mounts the specified directory
|
||||||
|
- 'mcx repo-url' clones the repository
|
||||||
|
|
||||||
|
All command-line options are passed through to 'session create'.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Save the program name (e.g., 'mcx')
|
||||||
|
prog_name = sys.argv[0]
|
||||||
|
# Insert 'session' and 'create' commands before any other arguments
|
||||||
|
sys.argv.insert(1, "session")
|
||||||
|
sys.argv.insert(2, "create")
|
||||||
|
# Run the app with the modified arguments
|
||||||
|
app(prog_name=prog_name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app()
|
app()
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class ContainerManager:
|
|||||||
project: Optional[str] = None,
|
project: Optional[str] = None,
|
||||||
environment: Optional[Dict[str, str]] = None,
|
environment: Optional[Dict[str, str]] = None,
|
||||||
session_name: Optional[str] = None,
|
session_name: Optional[str] = None,
|
||||||
mount_local: bool = True,
|
mount_local: bool = False,
|
||||||
volumes: Optional[Dict[str, Dict[str, str]]] = None,
|
volumes: Optional[Dict[str, Dict[str, str]]] = None,
|
||||||
networks: Optional[List[str]] = None,
|
networks: Optional[List[str]] = None,
|
||||||
mcp: Optional[List[str]] = None,
|
mcp: Optional[List[str]] = None,
|
||||||
@@ -150,10 +150,10 @@ class ContainerManager:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
driver_name: The name of the driver to use
|
driver_name: The name of the driver to use
|
||||||
project: Optional project repository URL
|
project: Optional project repository URL or local directory path
|
||||||
environment: Optional environment variables
|
environment: Optional environment variables
|
||||||
session_name: Optional session name
|
session_name: Optional session name
|
||||||
mount_local: Whether to mount the current directory to /app
|
mount_local: Whether to mount the specified local directory to /app (ignored if project is None)
|
||||||
volumes: Optional additional volumes to mount (dict of {host_path: {"bind": container_path, "mode": mode}})
|
volumes: Optional additional volumes to mount (dict of {host_path: {"bind": container_path, "mode": mode}})
|
||||||
networks: Optional list of additional Docker networks to connect to
|
networks: Optional list of additional Docker networks to connect to
|
||||||
mcp: Optional list of MCP server names to attach to the session
|
mcp: Optional list of MCP server names to attach to the session
|
||||||
@@ -203,16 +203,29 @@ class ContainerManager:
|
|||||||
# Set up volume mounts
|
# Set up volume mounts
|
||||||
session_volumes = {}
|
session_volumes = {}
|
||||||
|
|
||||||
# If project URL is provided, don't mount local directory (will clone into /app)
|
# Determine if project is a local directory or a Git repository
|
||||||
# If no project URL and mount_local is True, mount local directory to /app
|
is_local_directory = False
|
||||||
if not project and mount_local:
|
is_git_repo = False
|
||||||
# Mount current directory to /app in the container
|
|
||||||
current_dir = os.getcwd()
|
if project:
|
||||||
session_volumes[current_dir] = {"bind": "/app", "mode": "rw"}
|
# Check if project is a local directory
|
||||||
print(f"Mounting local directory {current_dir} to /app")
|
if os.path.isdir(os.path.expanduser(project)):
|
||||||
elif project:
|
is_local_directory = True
|
||||||
|
else:
|
||||||
|
# If not a local directory, assume it's a Git repo URL
|
||||||
|
is_git_repo = True
|
||||||
|
|
||||||
|
# Handle mounting based on project type
|
||||||
|
if is_local_directory and mount_local:
|
||||||
|
# Mount the specified local directory to /app in the container
|
||||||
|
local_dir = os.path.abspath(os.path.expanduser(project))
|
||||||
|
session_volumes[local_dir] = {"bind": "/app", "mode": "rw"}
|
||||||
|
print(f"Mounting local directory {local_dir} to /app")
|
||||||
|
# Clear project for container environment since we're mounting
|
||||||
|
project = None
|
||||||
|
elif is_git_repo:
|
||||||
print(
|
print(
|
||||||
f"Project URL provided - container will clone {project} into /app during initialization"
|
f"Git repository URL provided - container will clone {project} into /app during initialization"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add user-specified volumes
|
# Add user-specified volumes
|
||||||
@@ -220,13 +233,13 @@ class ContainerManager:
|
|||||||
for host_path, mount_spec in volumes.items():
|
for host_path, mount_spec in volumes.items():
|
||||||
container_path = mount_spec["bind"]
|
container_path = mount_spec["bind"]
|
||||||
# Check for conflicts with /app mount
|
# Check for conflicts with /app mount
|
||||||
if container_path == "/app" and not project and mount_local:
|
if container_path == "/app" and is_local_directory and mount_local:
|
||||||
print(
|
print(
|
||||||
"[yellow]Warning: Volume mount to /app conflicts with automatic local directory mount. User-specified mount takes precedence.[/yellow]"
|
"[yellow]Warning: Volume mount to /app conflicts with local directory mount. User-specified mount takes precedence.[/yellow]"
|
||||||
)
|
)
|
||||||
# Remove the automatic mount if there's a conflict
|
# Remove the local directory mount if there's a conflict
|
||||||
if current_dir in session_volumes:
|
if local_dir in session_volumes:
|
||||||
del session_volumes[current_dir]
|
del session_volumes[local_dir]
|
||||||
|
|
||||||
# Add the volume
|
# Add the volume
|
||||||
session_volumes[host_path] = mount_spec
|
session_volumes[host_path] = mount_spec
|
||||||
@@ -309,9 +322,11 @@ class ContainerManager:
|
|||||||
try:
|
try:
|
||||||
print(f"Ensuring MCP server '{mcp_name}' is running...")
|
print(f"Ensuring MCP server '{mcp_name}' is running...")
|
||||||
self.mcp_manager.start_mcp(mcp_name)
|
self.mcp_manager.start_mcp(mcp_name)
|
||||||
|
|
||||||
# Store container name for later network connection
|
# Store container name for later network connection
|
||||||
container_name = self.mcp_manager.get_mcp_container_name(mcp_name)
|
container_name = self.mcp_manager.get_mcp_container_name(
|
||||||
|
mcp_name
|
||||||
|
)
|
||||||
mcp_container_names.append(container_name)
|
mcp_container_names.append(container_name)
|
||||||
|
|
||||||
# Get MCP status to extract endpoint information
|
# Get MCP status to extract endpoint information
|
||||||
@@ -438,25 +453,29 @@ class ContainerManager:
|
|||||||
)
|
)
|
||||||
except DockerException as e:
|
except DockerException as e:
|
||||||
print(f"Error connecting to network {network_name}: {e}")
|
print(f"Error connecting to network {network_name}: {e}")
|
||||||
|
|
||||||
# Reload the container to get updated network information
|
# Reload the container to get updated network information
|
||||||
container.reload()
|
container.reload()
|
||||||
|
|
||||||
# Connect directly to each MCP's dedicated network
|
# Connect directly to each MCP's dedicated network
|
||||||
for mcp_name in mcp_names:
|
for mcp_name in mcp_names:
|
||||||
try:
|
try:
|
||||||
# Get the dedicated network for this MCP
|
# Get the dedicated network for this MCP
|
||||||
dedicated_network_name = f"mc-mcp-{mcp_name}-network"
|
dedicated_network_name = f"mc-mcp-{mcp_name}-network"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
network = self.client.networks.get(dedicated_network_name)
|
network = self.client.networks.get(dedicated_network_name)
|
||||||
|
|
||||||
# Connect the session container to the MCP's dedicated network
|
# Connect the session container to the MCP's dedicated network
|
||||||
network.connect(container, aliases=[session_name])
|
network.connect(container, aliases=[session_name])
|
||||||
print(f"Connected session to MCP '{mcp_name}' via dedicated network: {dedicated_network_name}")
|
print(
|
||||||
|
f"Connected session to MCP '{mcp_name}' via dedicated network: {dedicated_network_name}"
|
||||||
|
)
|
||||||
except DockerException as e:
|
except DockerException as e:
|
||||||
print(f"Error connecting to MCP dedicated network '{dedicated_network_name}': {e}")
|
print(
|
||||||
|
f"Error connecting to MCP dedicated network '{dedicated_network_name}': {e}"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error connecting session to MCP '{mcp_name}': {e}")
|
print(f"Error connecting session to MCP '{mcp_name}': {e}")
|
||||||
|
|
||||||
@@ -464,7 +483,13 @@ class ContainerManager:
|
|||||||
if networks:
|
if networks:
|
||||||
for network_name in networks:
|
for network_name in networks:
|
||||||
# Check if already connected to this network
|
# Check if already connected to this network
|
||||||
if network_name not in [net.name for net in container.attrs.get("NetworkSettings", {}).get("Networks", {}).values()]:
|
# NetworkSettings.Networks contains a dict where keys are network names
|
||||||
|
existing_networks = (
|
||||||
|
container.attrs.get("NetworkSettings", {})
|
||||||
|
.get("Networks", {})
|
||||||
|
.keys()
|
||||||
|
)
|
||||||
|
if network_name not in existing_networks:
|
||||||
try:
|
try:
|
||||||
# Get or create the network
|
# Get or create the network
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ COPY update-goose-config.sh /usr/local/bin/update-goose-config.sh
|
|||||||
# Extend env via bashrc
|
# Extend env via bashrc
|
||||||
|
|
||||||
# Make scripts executable
|
# Make scripts executable
|
||||||
RUN chmod +x /mc-init.sh /entrypoint.sh /init-status.sh /usr/local/bin/update-goose-config.sh
|
RUN chmod +x /mc-init.sh /entrypoint.sh /init-status.sh \
|
||||||
|
/usr/local/bin/update-goose-config.sh
|
||||||
|
|
||||||
# Set up initialization status check on login
|
# Set up initialization status check on login
|
||||||
RUN echo 'export PATH=/root/.local/bin:$PATH' >> /etc/bash.bashrc
|
RUN echo 'export PATH=/root/.local/bin:$PATH' >> /etc/bash.bashrc
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ class MCPManager:
|
|||||||
if not networks:
|
if not networks:
|
||||||
self.client.networks.create(network_name, driver="bridge")
|
self.client.networks.create(network_name, driver="bridge")
|
||||||
return network_name
|
return network_name
|
||||||
|
|
||||||
def _get_mcp_dedicated_network(self, mcp_name: str) -> str:
|
def _get_mcp_dedicated_network(self, mcp_name: str) -> str:
|
||||||
"""Get or create a dedicated network for direct session-to-MCP connections.
|
"""Get or create a dedicated network for direct session-to-MCP connections.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mcp_name: The name of the MCP server
|
mcp_name: The name of the MCP server
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The name of the dedicated network
|
The name of the dedicated network
|
||||||
"""
|
"""
|
||||||
@@ -74,16 +74,20 @@ class MCPManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def add_remote_mcp(
|
def add_remote_mcp(
|
||||||
self, name: str, url: str, headers: Dict[str, str] = None, add_as_default: bool = True
|
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:
|
Args:
|
||||||
name: Name of the MCP server
|
name: Name of the MCP server
|
||||||
url: URL of the remote MCP server
|
url: URL of the remote MCP server
|
||||||
headers: HTTP headers to use when connecting
|
headers: HTTP headers to use when connecting
|
||||||
add_as_default: Whether to add this MCP to the default MCPs list
|
add_as_default: Whether to add this MCP to the default MCPs list
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The MCP configuration dictionary
|
The MCP configuration dictionary
|
||||||
"""
|
"""
|
||||||
@@ -106,7 +110,7 @@ class MCPManager:
|
|||||||
|
|
||||||
# Save the configuration
|
# Save the configuration
|
||||||
self.config_manager.set("mcps", mcps)
|
self.config_manager.set("mcps", mcps)
|
||||||
|
|
||||||
# Add to default MCPs if requested
|
# Add to default MCPs if requested
|
||||||
if add_as_default:
|
if add_as_default:
|
||||||
default_mcps = self.config_manager.get("defaults.mcps", [])
|
default_mcps = self.config_manager.get("defaults.mcps", [])
|
||||||
@@ -117,17 +121,22 @@ class MCPManager:
|
|||||||
return mcp_config
|
return mcp_config
|
||||||
|
|
||||||
def add_docker_mcp(
|
def add_docker_mcp(
|
||||||
self, name: str, image: str, command: str, env: Dict[str, str] = None, add_as_default: bool = True
|
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:
|
Args:
|
||||||
name: Name of the MCP server
|
name: Name of the MCP server
|
||||||
image: Docker image for the MCP server
|
image: Docker image for the MCP server
|
||||||
command: Command to run in the container
|
command: Command to run in the container
|
||||||
env: Environment variables to set in the container
|
env: Environment variables to set in the container
|
||||||
add_as_default: Whether to add this MCP to the default MCPs list
|
add_as_default: Whether to add this MCP to the default MCPs list
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The MCP configuration dictionary
|
The MCP configuration dictionary
|
||||||
"""
|
"""
|
||||||
@@ -151,7 +160,7 @@ class MCPManager:
|
|||||||
|
|
||||||
# Save the configuration
|
# Save the configuration
|
||||||
self.config_manager.set("mcps", mcps)
|
self.config_manager.set("mcps", mcps)
|
||||||
|
|
||||||
# Add to default MCPs if requested
|
# Add to default MCPs if requested
|
||||||
if add_as_default:
|
if add_as_default:
|
||||||
default_mcps = self.config_manager.get("defaults.mcps", [])
|
default_mcps = self.config_manager.get("defaults.mcps", [])
|
||||||
@@ -173,7 +182,7 @@ class MCPManager:
|
|||||||
add_as_default: bool = True,
|
add_as_default: bool = True,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Add a proxy-based MCP server.
|
"""Add a proxy-based MCP server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Name of the MCP server
|
name: Name of the MCP server
|
||||||
base_image: Base Docker image running the actual MCP server
|
base_image: Base Docker image running the actual MCP server
|
||||||
@@ -183,7 +192,7 @@ class MCPManager:
|
|||||||
env: Environment variables to set in the container
|
env: Environment variables to set in the container
|
||||||
host_port: Host port to bind the MCP server to (auto-assigned if not specified)
|
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
|
add_as_default: Whether to add this MCP to the default MCPs list
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The MCP configuration dictionary
|
The MCP configuration dictionary
|
||||||
"""
|
"""
|
||||||
@@ -228,7 +237,7 @@ class MCPManager:
|
|||||||
|
|
||||||
# Save the configuration
|
# Save the configuration
|
||||||
self.config_manager.set("mcps", mcps)
|
self.config_manager.set("mcps", mcps)
|
||||||
|
|
||||||
# Add to default MCPs if requested
|
# Add to default MCPs if requested
|
||||||
if add_as_default:
|
if add_as_default:
|
||||||
default_mcps = self.config_manager.get("defaults.mcps", [])
|
default_mcps = self.config_manager.get("defaults.mcps", [])
|
||||||
@@ -240,10 +249,10 @@ class MCPManager:
|
|||||||
|
|
||||||
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:
|
Args:
|
||||||
name: Name of the MCP server to remove
|
name: Name of the MCP server to remove
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if the MCP was successfully removed, False otherwise
|
True if the MCP was successfully removed, False otherwise
|
||||||
"""
|
"""
|
||||||
@@ -258,7 +267,7 @@ 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
|
# Also remove from default MCPs if it's there
|
||||||
default_mcps = self.config_manager.get("defaults.mcps", [])
|
default_mcps = self.config_manager.get("defaults.mcps", [])
|
||||||
if name in default_mcps:
|
if name in default_mcps:
|
||||||
@@ -370,20 +379,22 @@ class MCPManager:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Connect to the inspector network
|
# Connect to the inspector network
|
||||||
network = self.client.networks.get(network_name)
|
network = self.client.networks.get(network_name)
|
||||||
network.connect(container, aliases=[name])
|
network.connect(container, aliases=[name])
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Connected MCP server '{name}' to inspector network {network_name} with alias '{name}'"
|
f"Connected MCP server '{name}' to inspector network {network_name} with alias '{name}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create and connect to a dedicated network for session connections
|
# Create and connect to a dedicated network for session connections
|
||||||
dedicated_network_name = self._get_mcp_dedicated_network(name)
|
dedicated_network_name = self._get_mcp_dedicated_network(name)
|
||||||
try:
|
try:
|
||||||
dedicated_network = self.client.networks.get(dedicated_network_name)
|
dedicated_network = self.client.networks.get(dedicated_network_name)
|
||||||
except DockerException:
|
except DockerException:
|
||||||
dedicated_network = self.client.networks.create(dedicated_network_name, driver="bridge")
|
dedicated_network = self.client.networks.create(
|
||||||
|
dedicated_network_name, driver="bridge"
|
||||||
|
)
|
||||||
|
|
||||||
dedicated_network.connect(container, aliases=[name])
|
dedicated_network.connect(container, aliases=[name])
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Connected MCP server '{name}' to dedicated network {dedicated_network_name} with alias '{name}'"
|
f"Connected MCP server '{name}' to dedicated network {dedicated_network_name} with alias '{name}'"
|
||||||
@@ -400,6 +411,7 @@ class MCPManager:
|
|||||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
# Create entrypoint script for mcp-proxy that runs the base MCP image
|
# Create entrypoint script for mcp-proxy that runs the base MCP image
|
||||||
entrypoint_script = """#!/bin/sh
|
entrypoint_script = """#!/bin/sh
|
||||||
|
set -x
|
||||||
echo "Starting MCP proxy with base image $MCP_BASE_IMAGE (command: $MCP_COMMAND) on port $SSE_PORT"
|
echo "Starting MCP proxy with base image $MCP_BASE_IMAGE (command: $MCP_COMMAND) on port $SSE_PORT"
|
||||||
|
|
||||||
# Verify if Docker socket is available
|
# Verify if Docker socket is available
|
||||||
@@ -453,19 +465,44 @@ echo "Running MCP server from image $MCP_BASE_IMAGE with command: $CMD"
|
|||||||
|
|
||||||
# Run the actual MCP server in the base image and pipe its I/O to mcp-proxy
|
# Run the actual MCP server in the base image and pipe its I/O to mcp-proxy
|
||||||
# Using docker run without -d to keep stdio connected
|
# Using docker run without -d to keep stdio connected
|
||||||
|
|
||||||
|
# Build env vars string to pass through to the inner container
|
||||||
|
ENV_ARGS=""
|
||||||
|
|
||||||
|
# Check if the environment variable names file exists
|
||||||
|
if [ -f "/mcp-envs.txt" ]; then
|
||||||
|
# Read env var names from file and pass them to docker
|
||||||
|
while read -r var_name; do
|
||||||
|
# Skip empty lines
|
||||||
|
if [ -n "$var_name" ]; then
|
||||||
|
# Simply add the env var - Docker will only pass it if it exists
|
||||||
|
ENV_ARGS="$ENV_ARGS -e $var_name"
|
||||||
|
fi
|
||||||
|
done < "/mcp-envs.txt"
|
||||||
|
|
||||||
|
echo "Passing environment variables from mcp-envs.txt: $ENV_ARGS"
|
||||||
|
fi
|
||||||
|
|
||||||
exec mcp-proxy \
|
exec mcp-proxy \
|
||||||
--sse-port "$SSE_PORT" \
|
--sse-port "$SSE_PORT" \
|
||||||
--sse-host "$SSE_HOST" \
|
--sse-host "$SSE_HOST" \
|
||||||
--allow-origin "$ALLOW_ORIGIN" \
|
--allow-origin "$ALLOW_ORIGIN" \
|
||||||
--pass-environment \
|
--pass-environment \
|
||||||
-- \
|
-- \
|
||||||
docker run --rm -i "$MCP_BASE_IMAGE" $CMD
|
docker run --rm -i $ENV_ARGS "$MCP_BASE_IMAGE" $CMD
|
||||||
"""
|
"""
|
||||||
# Write the entrypoint script
|
# Write the entrypoint script
|
||||||
entrypoint_path = os.path.join(tmp_dir, "entrypoint.sh")
|
entrypoint_path = os.path.join(tmp_dir, "entrypoint.sh")
|
||||||
with open(entrypoint_path, "w") as f:
|
with open(entrypoint_path, "w") as f:
|
||||||
f.write(entrypoint_script)
|
f.write(entrypoint_script)
|
||||||
|
|
||||||
|
# Create a file with environment variable names (no values)
|
||||||
|
env_names_path = os.path.join(tmp_dir, "mcp-envs.txt")
|
||||||
|
with open(env_names_path, "w") as f:
|
||||||
|
# Write one env var name per line
|
||||||
|
for env_name in mcp_config.get("env", {}).keys():
|
||||||
|
f.write(f"{env_name}\n")
|
||||||
|
|
||||||
# Create a Dockerfile for the proxy
|
# Create a Dockerfile for the proxy
|
||||||
dockerfile_content = f"""
|
dockerfile_content = f"""
|
||||||
FROM {mcp_config["proxy_image"]}
|
FROM {mcp_config["proxy_image"]}
|
||||||
@@ -489,7 +526,8 @@ ENV DEBUG=1
|
|||||||
# Add environment variables from the configuration
|
# Add environment variables from the configuration
|
||||||
{chr(10).join([f'ENV {k}="{v}"' for k, v in mcp_config.get("env", {}).items()])}
|
{chr(10).join([f'ENV {k}="{v}"' for k, v in mcp_config.get("env", {}).items()])}
|
||||||
|
|
||||||
# Add entrypoint script
|
# Add env names file and entrypoint script
|
||||||
|
COPY mcp-envs.txt /mcp-envs.txt
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
@@ -545,20 +583,22 @@ ENTRYPOINT ["/entrypoint.sh"]
|
|||||||
ports=port_bindings, # Bind the SSE port to the host if configured
|
ports=port_bindings, # Bind the SSE port to the host if configured
|
||||||
)
|
)
|
||||||
|
|
||||||
# Connect to the inspector network
|
# Connect to the inspector network
|
||||||
network = self.client.networks.get(network_name)
|
network = self.client.networks.get(network_name)
|
||||||
network.connect(container, aliases=[name])
|
network.connect(container, aliases=[name])
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Connected MCP server '{name}' to inspector network {network_name} with alias '{name}'"
|
f"Connected MCP server '{name}' to inspector network {network_name} with alias '{name}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create and connect to a dedicated network for session connections
|
# Create and connect to a dedicated network for session connections
|
||||||
dedicated_network_name = self._get_mcp_dedicated_network(name)
|
dedicated_network_name = self._get_mcp_dedicated_network(name)
|
||||||
try:
|
try:
|
||||||
dedicated_network = self.client.networks.get(dedicated_network_name)
|
dedicated_network = self.client.networks.get(dedicated_network_name)
|
||||||
except DockerException:
|
except DockerException:
|
||||||
dedicated_network = self.client.networks.create(dedicated_network_name, driver="bridge")
|
dedicated_network = self.client.networks.create(
|
||||||
|
dedicated_network_name, driver="bridge"
|
||||||
|
)
|
||||||
|
|
||||||
dedicated_network.connect(container, aliases=[name])
|
dedicated_network.connect(container, aliases=[name])
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Connected MCP server '{name}' to dedicated network {dedicated_network_name} with alias '{name}'"
|
f"Connected MCP server '{name}' to dedicated network {dedicated_network_name} with alias '{name}'"
|
||||||
@@ -574,14 +614,25 @@ ENTRYPOINT ["/entrypoint.sh"]
|
|||||||
raise ValueError(f"Unsupported MCP type: {mcp_type}")
|
raise ValueError(f"Unsupported MCP type: {mcp_type}")
|
||||||
|
|
||||||
def stop_mcp(self, name: str) -> bool:
|
def stop_mcp(self, name: str) -> bool:
|
||||||
"""Stop an MCP server container."""
|
"""Stop an MCP server container.
|
||||||
if not self.client:
|
|
||||||
raise Exception("Docker client is not available")
|
|
||||||
|
|
||||||
# Get the MCP configuration
|
Args:
|
||||||
|
name: The name of the MCP server to stop
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the operation was successful (including cases where the container doesn't exist)
|
||||||
|
"""
|
||||||
|
if not self.client:
|
||||||
|
logger.warning("Docker client is not available")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get the MCP configuration - don't raise an exception if not found
|
||||||
mcp_config = self.get_mcp(name)
|
mcp_config = self.get_mcp(name)
|
||||||
if not mcp_config:
|
if not mcp_config:
|
||||||
raise ValueError(f"MCP server '{name}' not found")
|
logger.warning(
|
||||||
|
f"MCP server '{name}' not found, but continuing with removal"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
# Remote MCPs don't have containers to stop
|
# Remote MCPs don't have containers to stop
|
||||||
if mcp_config.get("type") == "remote":
|
if mcp_config.get("type") == "remote":
|
||||||
@@ -605,12 +656,13 @@ ENTRYPOINT ["/entrypoint.sh"]
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
except NotFound:
|
except NotFound:
|
||||||
# Container doesn't exist
|
# Container doesn't exist - this is fine when removing
|
||||||
logger.info(f"MCP container '{name}' not found, nothing to stop or remove")
|
logger.info(f"MCP container '{name}' not found, nothing to stop or remove")
|
||||||
return False
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# Log the error but don't fail the removal operation
|
||||||
logger.error(f"Error stopping/removing MCP container: {e}")
|
logger.error(f"Error stopping/removing MCP container: {e}")
|
||||||
return False
|
return True # Return true anyway to continue with removal
|
||||||
|
|
||||||
def restart_mcp(self, name: str) -> Dict[str, Any]:
|
def restart_mcp(self, name: str) -> Dict[str, Any]:
|
||||||
"""Restart an MCP server container."""
|
"""Restart an MCP server container."""
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ dev = [
|
|||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
mc = "mcontainer.cli:app"
|
mc = "mcontainer.cli:app"
|
||||||
|
mcx = "mcontainer.cli:session_create_entry_point"
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
|
|||||||
Reference in New Issue
Block a user