refactor: rename project to cubbi

This commit is contained in:
2025-04-17 20:33:20 -06:00
parent 51fb79baa3
commit 12d77d0128
33 changed files with 525 additions and 517 deletions

View File

@@ -1,12 +1,12 @@
# Monadical Container Development Guide # Cubbi Container Development Guide
## Build Commands ## Build Commands
```bash ```bash
# Install dependencies using uv (Astral) # Install dependencies using uv (Astral)
uv sync uv sync
# Run MC CLI # Run Cubbi CLI
uv run -m mcontainer.cli uv run -m cubbi.cli
``` ```
## Lint/Test Commands ## Lint/Test Commands

188
README.md
View File

@@ -1,17 +1,17 @@
# MC - Monadical Container Tool # Cubbi - Container Tool
MC (Monadical Container) is a command-line tool for managing ephemeral Cubbi is a command-line tool for managing ephemeral
containers that run AI tools and development environments. It works with both 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. Cubbi also supports connecting to MCP (Model Control Protocol) servers to extend AI tools with additional capabilities.
## Quick Reference ## Quick Reference
- `mc session create` - Create a new session - `cubbi session create` - Create a new session
- `mcx` - Shortcut for `mc session create` - `cubbix` - Shortcut for `cubbi session create`
- `mcx .` - Mount the current directory - `cubbix .` - Mount the current directory
- `mcx /path/to/dir` - Mount a specific directory - `cubbix /path/to/dir` - Mount a specific directory
- `mcx https://github.com/user/repo` - Clone a repository - `cubbix https://github.com/user/repo` - Clone a repository
## Requirements ## Requirements
@@ -21,104 +21,110 @@ Docker-in-Docker (DinD) environment. MC also supports connecting to MCP (Model C
```bash ```bash
# Clone the repository # Clone the repository
git clone https://github.com/monadical/mcontainer.git git clone https://github.com/monadical/cubbi.git
# Install the tool locally # Install the tool locally
# (with editable, so you can update the code and work with it) # (with editable, so you can update the code and work with it)
cd mcontainer cd cubbi
uv tool install --with-editable . . uv tool install --with-editable . .
# Then you could use the tool as `mc` # Then you could use the tool as `cubbi`
mc --help cubbi --help
```
Important: compile your first image
```bash
cubbi image build goose
``` ```
## Basic Usage ## Basic Usage
```bash ```bash
# Show help message (displays available commands) # Show help message (displays available commands)
mc cubbi
# Create a new session with the default image (using mcx alias) # Create a new session with the default image (using cubbix alias)
mcx cubbix
# Create a session and run an initial command before the shell starts # Create a session and run an initial command before the shell starts
mcx --run "echo 'Setup complete'; ls -l" cubbix --run "echo 'Setup complete'; ls -l"
# List all active sessions # List all active sessions
mc session list cubbi session list
# Connect to a specific session # Connect to a specific session
mc session connect SESSION_ID cubbi session connect SESSION_ID
# Close a session when done # Close a session when done
mc session close SESSION_ID cubbi session close SESSION_ID
# Create a session with a specific image # Create a session with a specific image
mcx --image goose cubbix --image goose
# Create a session with environment variables # Create a session with environment variables
mcx -e VAR1=value1 -e VAR2=value2 cubbix -e VAR1=value1 -e VAR2=value2
# Mount custom volumes (similar to Docker's -v flag) # Mount custom volumes (similar to Docker's -v flag)
mcx -v /local/path:/container/path cubbix -v /local/path:/container/path
mcx -v ~/data:/data -v ./configs:/etc/app/config cubbix -v ~/data:/data -v ./configs:/etc/app/config
# Mount a local directory (current directory or specific path) # Mount a local directory (current directory or specific path)
mcx . cubbix .
mcx /path/to/project cubbix /path/to/project
# Connect to external Docker networks # Connect to external Docker networks
mcx --network teamnet --network dbnet cubbix --network teamnet --network dbnet
# Connect to MCP servers for extended capabilities # Connect to MCP servers for extended capabilities
mcx --mcp github --mcp jira cubbix --mcp github --mcp jira
# Clone a Git repository # Clone a Git repository
mcx https://github.com/username/repo cubbix https://github.com/username/repo
# Using the mcx shortcut (equivalent to mc session create) # Using the cubbix shortcut (equivalent to cubbi session create)
mcx # Creates a session without mounting anything cubbix # Creates a session without mounting anything
mcx . # Mounts the current directory cubbix . # Mounts the current directory
mcx /path/to/project # Mounts the specified directory cubbix /path/to/project # Mounts the specified directory
mcx https://github.com/username/repo # Clones the repository cubbix https://github.com/username/repo # Clones the repository
# Shorthand with MCP servers # Shorthand with MCP servers
mcx https://github.com/username/repo --mcp github cubbix https://github.com/username/repo --mcp github
# Shorthand with an initial command # Shorthand with an initial command
mcx . --run "apt-get update && apt-get install -y my-package" cubbix . --run "apt-get update && apt-get install -y my-package"
# Enable SSH server in the container # Enable SSH server in the container
mcx --ssh cubbix --ssh
``` ```
## Image Management ## Image Management
MC includes a image management system that allows you to build, manage, and use Docker images for different AI tools: Cubbi includes an image management system that allows you to build, manage, and use Docker images for different AI tools:
```bash ```bash
# List available images # List available images
mc image list cubbi image list
# Get detailed information about an image # Get detailed information about an image
mc image info goose cubbi image info goose
# Build an image # Build an image
mc image build goose cubbi image build goose
# Build and push an image # Build and push an image
mc image build goose --push cubbi image build goose --push
``` ```
Images are defined in the `mcontainer/images/` directory, with each subdirectory containing: Images are defined in the `cubbi/images/` directory, with each subdirectory containing:
- `Dockerfile`: Docker image definition - `Dockerfile`: Docker image definition
- `entrypoint.sh`: Container entrypoint script - `entrypoint.sh`: Container entrypoint script
- `mc-init.sh`: Standardized initialization script - `cubbi-init.sh`: Standardized initialization script
- `mc-image.yaml`: Image metadata and configuration - `cubbi-image.yaml`: Image metadata and configuration
- `README.md`: Image documentation - `README.md`: Image documentation
MC automatically discovers and loads image definitions from the YAML files. Cubbi automatically discovers and loads image definitions from the YAML files.
``` ```
## Development ## Development
@@ -139,28 +145,28 @@ uvx ruff format .
## Configuration ## Configuration
MC supports user-specific configuration via a YAML file located at `~/.config/mc/config.yaml`. This allows you to set default values and configure service credentials. Cubbi supports user-specific configuration via a YAML file located at `~/.config/cubbi/config.yaml`. This allows you to set default values and configure service credentials.
### Managing Configuration ### Managing Configuration
```bash ```bash
# View all configuration # View all configuration
mc config list cubbi config list
# Get a specific configuration value # Get a specific configuration value
mc config get langfuse.url cubbi config get langfuse.url
# Set configuration values # Set configuration values
mc config set langfuse.url "https://cloud.langfuse.com" cubbi config set langfuse.url "https://cloud.langfuse.com"
mc config set langfuse.public_key "pk-lf-..." cubbi config set langfuse.public_key "pk-lf-..."
mc config set langfuse.secret_key "sk-lf-..." cubbi config set langfuse.secret_key "sk-lf-..."
# Set API keys for various services # Set API keys for various services
mc config set openai.api_key "sk-..." cubbi config set openai.api_key "sk-..."
mc config set anthropic.api_key "sk-ant-..." cubbi config set anthropic.api_key "sk-ant-..."
# Reset configuration to defaults # Reset configuration to defaults
mc config reset cubbi config reset
``` ```
### Default Networks Configuration ### Default Networks Configuration
@@ -169,13 +175,13 @@ You can configure default networks that will be applied to every new session:
```bash ```bash
# List default networks # List default networks
mc config network list cubbi config network list
# Add a network to defaults # Add a network to defaults
mc config network add teamnet cubbi config network add teamnet
# Remove a network from defaults # Remove a network from defaults
mc config network remove teamnet cubbi config network remove teamnet
``` ```
### Default Volumes Configuration ### Default Volumes Configuration
@@ -184,13 +190,13 @@ You can configure default volumes that will be automatically mounted in every ne
```bash ```bash
# List default volumes # List default volumes
mc config volume list cubbi config volume list
# Add a volume to defaults # Add a volume to defaults
mc config volume add /local/path:/container/path cubbi config volume add /local/path:/container/path
# Remove a volume from defaults (will prompt if multiple matches found) # Remove a volume from defaults (will prompt if multiple matches found)
mc config volume remove /local/path cubbi 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.
@@ -201,35 +207,35 @@ You can configure default MCP servers that sessions will automatically connect t
```bash ```bash
# List default MCP servers # List default MCP servers
mc config mcp list cubbi config mcp list
# Add an MCP server to defaults # Add an MCP server to defaults
mc config mcp add github cubbi config mcp add github
# Remove an MCP server from defaults # Remove an MCP server from defaults
mc config mcp remove github cubbi config mcp remove github
``` ```
When adding new MCP servers, they are added to defaults by default. Use the `--no-default` flag to prevent this: When adding new MCP servers, they are added to defaults by default. Use the `--no-default` flag to prevent this:
```bash ```bash
# Add an MCP server without adding it to defaults # Add an MCP server without adding it to defaults
mc mcp add github ghcr.io/mcp/github:latest --no-default cubbi mcp add github ghcr.io/mcp/github:latest --no-default
mc mcp add-remote jira https://jira-mcp.example.com/sse --no-default cubbi 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. 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: Cubbi containers can connect to external Docker networks, allowing them to communicate with other services in those networks:
```bash ```bash
# Create a session connected to external networks # Create a session connected to external networks
mc session create --network teamnet --network dbnet cubbi session create --network teamnet --network dbnet
``` ```
**Important**: Networks must be "attachable" to be joined by MC containers. Here's how to create attachable networks: **Important**: Networks must be "attachable" to be joined by Cubbi containers. Here's how to create attachable networks:
```bash ```bash
# Create an attachable network with Docker # Create an attachable network with Docker
@@ -247,12 +253,12 @@ services:
networks: networks:
teamnet: teamnet:
driver: bridge driver: bridge
attachable: true # This is required for MC containers to connect attachable: true # This is required for Cubbi containers to connect
``` ```
### Service Credentials ### Service Credentials
Service credentials like API keys configured in `~/.config/mc/config.yaml` are automatically passed to containers as environment variables: Service credentials like API keys configured in `~/.config/cubbi/config.yaml` are automatically passed to containers as environment variables:
| Config Setting | Environment Variable | | Config Setting | Environment Variable |
|----------------|---------------------| |----------------|---------------------|
@@ -266,7 +272,7 @@ Service credentials like API keys configured in `~/.config/mc/config.yaml` are a
## MCP Server Management ## MCP Server Management
MCP (Model Control Protocol) servers provide tool-calling capabilities to AI models, enhancing their ability to interact with external services, databases, and systems. MC supports multiple types of MCP servers: MCP (Model Control Protocol) servers provide tool-calling capabilities to AI models, enhancing their ability to interact with external services, databases, and systems. Cubbi supports multiple types of MCP servers:
1. **Remote HTTP SSE servers** - External MCP servers accessed over HTTP 1. **Remote HTTP SSE servers** - External MCP servers accessed over HTTP
2. **Docker-based MCP servers** - Local MCP servers running in Docker containers 2. **Docker-based MCP servers** - Local MCP servers running in Docker containers
@@ -276,56 +282,56 @@ MCP (Model Control Protocol) servers provide tool-calling capabilities to AI mod
```bash ```bash
# List all configured MCP servers and their status # List all configured MCP servers and their status
mc mcp list cubbi mcp list
# View detailed status of an MCP server # View detailed status of an MCP server
mc mcp status github cubbi mcp status github
# Start/stop/restart individual MCP servers # Start/stop/restart individual MCP servers
mc mcp start github cubbi mcp start github
mc mcp stop github cubbi mcp stop github
mc mcp restart github cubbi mcp restart github
# Start all MCP servers at once # Start all MCP servers at once
mc mcp start --all cubbi mcp start --all
# Stop and remove all MCP servers at once # Stop and remove all MCP servers at once
mc mcp stop --all cubbi mcp stop --all
# Run the MCP Inspector to visualize and interact with MCP servers # Run the MCP Inspector to visualize and interact with MCP servers
# It automatically joins all MCP networks for seamless DNS resolution # It automatically joins all MCP networks for seamless DNS resolution
# Uses two ports: frontend UI (default: 5173) and backend API (default: 3000) # Uses two ports: frontend UI (default: 5173) and backend API (default: 3000)
mc mcp inspector cubbi mcp inspector
# Run the MCP Inspector with custom ports # Run the MCP Inspector with custom ports
mc mcp inspector --client-port 6173 --server-port 6174 cubbi mcp inspector --client-port 6173 --server-port 6174
# Run the MCP Inspector in detached mode # Run the MCP Inspector in detached mode
mc mcp inspector --detach cubbi mcp inspector --detach
# Stop the MCP Inspector # Stop the MCP Inspector
mc mcp inspector --stop cubbi mcp inspector --stop
# View MCP server logs # View MCP server logs
mc mcp logs github cubbi mcp logs github
# Remove an MCP server configuration # Remove an MCP server configuration
mc mcp remove github cubbi mcp remove github
``` ```
### Adding MCP Servers ### Adding MCP Servers
MC supports different types of MCP servers: Cubbi supports different types of MCP servers:
```bash ```bash
# Add a remote HTTP SSE MCP server # Add a remote HTTP SSE MCP server
mc mcp remote add github http://my-mcp-server.example.com/sse --header "Authorization=Bearer token123" cubbi mcp remote add github http://my-mcp-server.example.com/sse --header "Authorization=Bearer token123"
# Add a Docker-based MCP server # Add a Docker-based MCP server
mc mcp docker add github mcp/github:latest --command "github-mcp" --env GITHUB_TOKEN=ghp_123456 cubbi 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 add github ghcr.io/mcp/github:latest --proxy-image ghcr.io/sparfenyuk/mcp-proxy:latest --command "github-mcp" --sse-port 8080 --no-default cubbi 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
@@ -334,13 +340,13 @@ MCP servers can be attached to sessions when they are created:
```bash ```bash
# Create a session with a single MCP server # Create a session with a single MCP server
mc session create --mcp github cubbi session create --mcp github
# Create a session with multiple MCP servers # Create a session with multiple MCP servers
mc session create --mcp github --mcp jira cubbi session create --mcp github --mcp jira
# Using MCP with a project repository # Using MCP with a project repository
mc github.com/username/repo --mcp github cubbi github.com/username/repo --mcp github
``` ```
MCP servers are persistent and can be shared between sessions. They continue running even when sessions are closed, allowing for efficient reuse across multiple sessions. MCP servers are persistent and can be shared between sessions. They continue running even when sessions are closed, allowing for efficient reuse across multiple sessions.

View File

@@ -1,5 +1,5 @@
""" """
MC - Monadical Container Tool Cubbi - Cubbi Container Tool
""" """
__version__ = "0.1.0" __version__ = "0.1.0"

View File

@@ -1,5 +1,5 @@
""" """
CLI for Monadical Container Tool. CLI for Cubbi Container Tool.
""" """
import logging import logging
@@ -24,10 +24,10 @@ logging.basicConfig(
handlers=[logging.StreamHandler()], handlers=[logging.StreamHandler()],
) )
app = typer.Typer(help="Monadical Container Tool", no_args_is_help=True) app = typer.Typer(help="Cubbi Container Tool", no_args_is_help=True)
session_app = typer.Typer(help="Manage MC sessions", no_args_is_help=True) session_app = typer.Typer(help="Manage Cubbi sessions", no_args_is_help=True)
image_app = typer.Typer(help="Manage MC images", no_args_is_help=True) image_app = typer.Typer(help="Manage Cubbi images", no_args_is_help=True)
config_app = typer.Typer(help="Manage MC configuration", no_args_is_help=True) config_app = typer.Typer(help="Manage Cubbi configuration", no_args_is_help=True)
mcp_app = typer.Typer(help="Manage MCP servers", no_args_is_help=True) 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(image_app, name="image", no_args_is_help=True) app.add_typer(image_app, name="image", no_args_is_help=True)
@@ -49,10 +49,10 @@ def main(
False, "--verbose", "-v", help="Enable verbose logging" False, "--verbose", "-v", help="Enable verbose logging"
), ),
) -> None: ) -> None:
"""Monadical Container Tool """Cubbi Container Tool
Run 'mc session create' to create a new session. Run 'cubbi session create' to create a new session.
Use 'mcx' as a shortcut for 'mc session create'. Use 'cubbix' as a shortcut for 'cubbi session create'.
""" """
# Set log level based on verbose flag # Set log level based on verbose flag
if verbose: if verbose:
@@ -61,19 +61,19 @@ def main(
@app.command() @app.command()
def version() -> None: def version() -> None:
"""Show MC version information""" """Show Cubbi version information"""
from importlib.metadata import version as get_version from importlib.metadata import version as get_version
try: try:
version_str = get_version("mcontainer") version_str = get_version("cubbi")
console.print(f"MC - Monadical Container Tool v{version_str}") console.print(f"Cubbi - Cubbi Container Tool v{version_str}")
except Exception: except Exception:
console.print("MC - Monadical Container Tool (development version)") console.print("Cubbi - Cubbi Container Tool (development version)")
@session_app.command("list") @session_app.command("list")
def list_sessions() -> None: def list_sessions() -> None:
"""List active MC sessions""" """List active Cubbi sessions"""
sessions = container_manager.list_sessions() sessions = container_manager.list_sessions()
if not sessions: if not sessions:
@@ -168,7 +168,7 @@ def create_session(
), ),
ssh: bool = typer.Option(False, "--ssh", help="Start SSH server in the container"), ssh: bool = typer.Option(False, "--ssh", help="Start SSH server in the container"),
) -> None: ) -> None:
"""Create a new MC session """Create a new Cubbi session
If a local directory path is provided, it will be mounted at /app in the container. 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 a repository URL is provided, it will be cloned into /app during initialization.
@@ -304,11 +304,11 @@ def create_session(
if no_connect: if no_connect:
console.print("\nConnection skipped due to --no-connect.") console.print("\nConnection skipped due to --no-connect.")
console.print( console.print(
f"Connect manually with:\n mc session connect {session.id}" f"Connect manually with:\n cubbi session connect {session.id}"
) )
elif not auto_connect: elif not auto_connect:
console.print( console.print(
f"\nAuto-connect disabled. Connect with:\n mc session connect {session.id}" f"\nAuto-connect disabled. Connect with:\n cubbi session connect {session.id}"
) )
else: else:
console.print("[red]Failed to create session[/red]") console.print("[red]Failed to create session[/red]")
@@ -319,7 +319,7 @@ def close_session(
session_id: Optional[str] = typer.Argument(None, help="Session ID to close"), session_id: Optional[str] = typer.Argument(None, help="Session ID to close"),
all_sessions: bool = typer.Option(False, "--all", help="Close all active sessions"), all_sessions: bool = typer.Option(False, "--all", help="Close all active sessions"),
) -> None: ) -> None:
"""Close a MC session or all sessions""" """Close a Cubbi session or all sessions"""
if all_sessions: if all_sessions:
# Get sessions first to display them # Get sessions first to display them
sessions = container_manager.list_sessions() sessions = container_manager.list_sessions()
@@ -364,7 +364,7 @@ def close_session(
def connect_session( def connect_session(
session_id: str = typer.Argument(..., help="Session ID to connect to"), session_id: str = typer.Argument(..., help="Session ID to connect to"),
) -> None: ) -> None:
"""Connect to a MC session""" """Connect to a Cubbi session"""
console.print(f"Connecting to session {session_id}...") console.print(f"Connecting to session {session_id}...")
success = container_manager.connect_session(session_id) success = container_manager.connect_session(session_id)
@@ -380,7 +380,7 @@ def session_logs(
False, "--init", "-i", help="Show initialization logs instead of container logs" False, "--init", "-i", help="Show initialization logs instead of container logs"
), ),
) -> None: ) -> None:
"""Stream logs from a MC session""" """Stream logs from a Cubbi session"""
if init: if init:
# Show initialization logs # Show initialization logs
if follow: if follow:
@@ -407,7 +407,7 @@ def session_logs(
@image_app.command("list") @image_app.command("list")
def list_images() -> None: def list_images() -> None:
"""List available MC images""" """List available Cubbi images"""
images = config_manager.list_images() images = config_manager.list_images()
if not images: if not images:
@@ -455,7 +455,7 @@ def build_image(
return return
# Build image name # Build image name
docker_image_name = f"monadical/mc-{image_name}:{tag}" docker_image_name = f"monadical/cubbi-{image_name}:{tag}"
# Build the image # Build the image
with console.status(f"Building image {docker_image_name}..."): with console.status(f"Building image {docker_image_name}..."):
@@ -1472,7 +1472,7 @@ def run_mcp_inspector(
# If stop flag is set, stop all running MCP Inspectors # If stop flag is set, stop all running MCP Inspectors
if stop: if stop:
containers = client.containers.list( containers = client.containers.list(
all=True, filters={"label": "mc.mcp.inspector=true"} all=True, filters={"label": "cubbi.mcp.inspector=true"}
) )
if not containers: if not containers:
console.print("[yellow]No running MCP Inspector instances found[/yellow]") console.print("[yellow]No running MCP Inspector instances found[/yellow]")
@@ -1491,7 +1491,7 @@ def run_mcp_inspector(
# Check if inspector is already running # Check if inspector is already running
all_inspectors = client.containers.list( all_inspectors = client.containers.list(
all=True, filters={"label": "mc.mcp.inspector=true"} all=True, filters={"label": "cubbi.mcp.inspector=true"}
) )
# Stop any existing inspectors first # Stop any existing inspectors first
@@ -1535,7 +1535,7 @@ def run_mcp_inspector(
return return
# Container name with timestamp to avoid conflicts # Container name with timestamp to avoid conflicts
container_name = f"mc_mcp_inspector_{int(time.time())}" container_name = f"cubbi_mcp_inspector_{int(time.time())}"
with console.status("Starting MCP Inspector..."): with console.status("Starting MCP Inspector..."):
# Get MCP servers from configuration # Get MCP servers from configuration
@@ -1568,7 +1568,7 @@ def run_mcp_inspector(
mcp_name = mcp.get("name") mcp_name = mcp.get("name")
try: try:
# Get the container name for this MCP # Get the container name for this MCP
container_name = f"mc_mcp_{mcp_name}" container_name = f"cubbi_mcp_{mcp_name}"
container = None container = None
# Try to find the container # Try to find the container
@@ -1619,7 +1619,7 @@ def run_mcp_inspector(
# Make sure we have at least one network to connect to # Make sure we have at least one network to connect to
if not mcp_networks_to_connect: if not mcp_networks_to_connect:
# Create an MCP-specific network if none exists # Create an MCP-specific network if none exists
network_name = "mc-mcp-network" network_name = "cubbi-mcp-network"
console.print("No MCP networks found, creating a default one") console.print("No MCP networks found, creating a default one")
try: try:
networks = client.networks.list(names=[network_name]) networks = client.networks.list(names=[network_name])
@@ -1679,7 +1679,8 @@ exec npm start
# Write the script to a temp file # Write the script to a temp file
script_path = os.path.join( script_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "mc_inspector_entrypoint.sh" os.path.dirname(os.path.abspath(__file__)),
"cubbi_inspector_entrypoint.sh",
) )
with open(script_path, "w") as f: with open(script_path, "w") as f:
f.write(script_content) f.write(script_content)
@@ -1696,7 +1697,7 @@ exec npm start
# Check if existing container with the same name exists, and remove it # Check if existing container with the same name exists, and remove it
try: try:
existing = client.containers.get("mc_mcp_inspector") existing = client.containers.get("cubbi_mcp_inspector")
if existing.status == "running": if existing.status == "running":
existing.stop(timeout=1) existing.stop(timeout=1)
existing.remove(force=True) existing.remove(force=True)
@@ -1722,7 +1723,7 @@ exec npm start
for mcp in all_mcps: for mcp in all_mcps:
if mcp.get("type") in ["docker", "proxy"]: if mcp.get("type") in ["docker", "proxy"]:
mcp_name = mcp.get("name") mcp_name = mcp.get("name")
container_name = f"mc_mcp_{mcp_name}" container_name = f"cubbi_mcp_{mcp_name}"
try: try:
# Check if this container exists # Check if this container exists
@@ -1742,7 +1743,7 @@ exec npm start
container = client.containers.run( container = client.containers.run(
image="mcp/inspector", image="mcp/inspector",
name="mc_mcp_inspector", # Use a fixed name name="cubbi_mcp_inspector", # Use a fixed name
detach=True, detach=True,
network=initial_network, network=initial_network,
ports={ ports={
@@ -1765,8 +1766,8 @@ exec npm start
}, },
entrypoint="/entrypoint.sh", entrypoint="/entrypoint.sh",
labels={ labels={
"mc.mcp.inspector": "true", "cubbi.mcp.inspector": "true",
"mc.managed": "true", "cubbi.managed": "true",
}, },
network_mode=None, # Don't use network_mode as we're using network with aliases network_mode=None, # Don't use network_mode as we're using network with aliases
networking_config=client.api.create_networking_config(network_config), networking_config=client.api.create_networking_config(network_config),
@@ -1802,7 +1803,7 @@ exec npm start
for mcp in all_mcps: for mcp in all_mcps:
if mcp.get("type") in ["docker", "proxy"]: if mcp.get("type") in ["docker", "proxy"]:
mcp_name = mcp.get("name") mcp_name = mcp.get("name")
container_name = f"mc_mcp_{mcp_name}" container_name = f"cubbi_mcp_{mcp_name}"
try: try:
# Check if this container exists # Check if this container exists
@@ -1874,7 +1875,7 @@ exec npm start
"[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]"
) )
console.print( console.print(
"Start MCP servers using 'mc mcp start --all' and then restart the Inspector." "Start MCP servers using 'cubbi mcp start --all' and then restart the Inspector."
) )
if not detach: if not detach:
@@ -1890,19 +1891,19 @@ exec npm start
def session_create_entry_point(): def session_create_entry_point():
"""Entry point that directly invokes 'mc session create'. """Entry point that directly invokes 'cubbi session create'.
This provides a convenient shortcut: This provides a convenient shortcut:
- 'mcx' runs as if you typed 'mc session create' - 'cubbix' runs as if you typed 'cubbi session create'
- 'mcx .' mounts the current directory - 'cubbix .' mounts the current directory
- 'mcx /path/to/project' mounts the specified directory - 'cubbix /path/to/project' mounts the specified directory
- 'mcx repo-url' clones the repository - 'cubbix repo-url' clones the repository
All command-line options are passed through to 'session create'. All command-line options are passed through to 'session create'.
""" """
import sys import sys
# Save the program name (e.g., 'mcx') # Save the program name (e.g., 'cubbix')
prog_name = sys.argv[0] prog_name = sys.argv[0]
# Insert 'session' and 'create' commands before any other arguments # Insert 'session' and 'create' commands before any other arguments
sys.argv.insert(1, "session") sys.argv.insert(1, "session")

View File

@@ -5,11 +5,11 @@ import yaml
from .models import Config, Image from .models import Config, Image
DEFAULT_CONFIG_DIR = Path.home() / ".config" / "mc" DEFAULT_CONFIG_DIR = Path.home() / ".config" / "cubbi"
DEFAULT_CONFIG_FILE = DEFAULT_CONFIG_DIR / "config.yaml" DEFAULT_CONFIG_FILE = DEFAULT_CONFIG_DIR / "config.yaml"
DEFAULT_IMAGES_DIR = Path.home() / ".config" / "mc" / "images" DEFAULT_IMAGES_DIR = Path.home() / ".config" / "cubbi" / "images"
PROJECT_ROOT = Path(__file__).parent.parent PROJECT_ROOT = Path(__file__).parent.parent
BUILTIN_IMAGES_DIR = Path(__file__).parent / "images" # mcontainer/images BUILTIN_IMAGES_DIR = Path(__file__).parent / "images"
# Dynamically loaded from images directory at runtime # Dynamically loaded from images directory at runtime
DEFAULT_IMAGES = {} DEFAULT_IMAGES = {}
@@ -60,7 +60,7 @@ class ConfigManager:
config = Config( config = Config(
docker={ docker={
"socket": "/var/run/docker.sock", "socket": "/var/run/docker.sock",
"network": "mc-network", "network": "cubbi-network",
}, },
defaults={ defaults={
"image": "goose", "image": "goose",
@@ -108,7 +108,7 @@ class ConfigManager:
def load_image_from_dir(self, image_dir: Path) -> Optional[Image]: def load_image_from_dir(self, image_dir: Path) -> Optional[Image]:
"""Load an image configuration from a directory""" """Load an image configuration from a directory"""
# Check for image config file # Check for image config file
yaml_path = image_dir / "mc-image.yaml" yaml_path = image_dir / "cubbi-image.yaml"
if not yaml_path.exists(): if not yaml_path.exists():
return None return None
@@ -129,7 +129,7 @@ class ConfigManager:
try: try:
# Ensure image field is set if not in YAML # Ensure image field is set if not in YAML
if "image" not in image_data: if "image" not in image_data:
image_data["image"] = f"monadical/mc-{image_data['name']}:latest" image_data["image"] = f"monadical/cubbi-{image_data['name']}:latest"
image = Image.model_validate(image_data) image = Image.model_validate(image_data)
return image return image
@@ -144,13 +144,13 @@ class ConfigManager:
return None return None
def _load_package_images(self) -> Dict[str, Image]: def _load_package_images(self) -> Dict[str, Image]:
"""Load all package images from the mcontainer/images directory""" """Load all package images from the cubbi/images directory"""
images = {} images = {}
if not BUILTIN_IMAGES_DIR.exists(): if not BUILTIN_IMAGES_DIR.exists():
return images return images
# Search for mc-image.yaml files in each subdirectory # Search for cubbi-image.yaml files in each subdirectory
for image_dir in BUILTIN_IMAGES_DIR.iterdir(): for image_dir in BUILTIN_IMAGES_DIR.iterdir():
if image_dir.is_dir(): if image_dir.is_dir():
image = self.load_image_from_dir(image_dir) image = self.load_image_from_dir(image_dir)

View File

@@ -42,8 +42,8 @@ class ContainerManager:
sys.exit(1) sys.exit(1)
def _ensure_network(self) -> None: def _ensure_network(self) -> None:
"""Ensure the MC network exists""" """Ensure the Cubbi network exists"""
network_name = self.config_manager.config.docker.get("network", "mc-network") network_name = self.config_manager.config.docker.get("network", "cubbi-network")
networks = self.client.networks.list(names=[network_name]) networks = self.client.networks.list(names=[network_name])
if not networks: if not networks:
self.client.networks.create(network_name, driver="bridge") self.client.networks.create(network_name, driver="bridge")
@@ -64,8 +64,8 @@ class ContainerManager:
Returns: Returns:
Path to the project configuration directory, or None if no project_name is provided Path to the project configuration directory, or None if no project_name is provided
""" """
# Get home directory for the MC config # Get home directory for the Cubbi config
mc_home = pathlib.Path.home() / ".mc" cubbi_home = pathlib.Path.home() / ".cubbi"
# Only use project_name if explicitly provided # Only use project_name if explicitly provided
if project_name: if project_name:
@@ -73,7 +73,7 @@ class ContainerManager:
project_hash = hashlib.md5(project_name.encode()).hexdigest() project_hash = hashlib.md5(project_name.encode()).hexdigest()
# Create the project config directory path # Create the project config directory path
config_path = mc_home / "projects" / project_hash / "config" config_path = cubbi_home / "projects" / project_hash / "config"
# Create the directory if it doesn't exist # Create the directory if it doesn't exist
config_path.parent.mkdir(parents=True, exist_ok=True) config_path.parent.mkdir(parents=True, exist_ok=True)
@@ -82,22 +82,22 @@ class ContainerManager:
return config_path return config_path
else: else:
# If no project_name is provided, don't create any config directory # If no project_name is provided, don't create any config directory
# This ensures we don't mount the /mc-config volume for project-less sessions # This ensures we don't mount the /cubbi-config volume for project-less sessions
return None return None
def list_sessions(self) -> List[Session]: def list_sessions(self) -> List[Session]:
"""List all active MC sessions""" """List all active Cubbi sessions"""
sessions = [] sessions = []
try: try:
containers = self.client.containers.list( containers = self.client.containers.list(
all=True, filters={"label": "mc.session"} all=True, filters={"label": "cubbi.session"}
) )
for container in containers: for container in containers:
container_id = container.id container_id = container.id
labels = container.labels labels = container.labels
session_id = labels.get("mc.session.id") session_id = labels.get("cubbi.session.id")
if not session_id: if not session_id:
continue continue
@@ -109,8 +109,8 @@ class ContainerManager:
session = Session( session = Session(
id=session_id, id=session_id,
name=labels.get("mc.session.name", f"mc-{session_id}"), name=labels.get("cubbi.session.name", f"cubbi-{session_id}"),
image=labels.get("mc.image", "unknown"), image=labels.get("cubbi.image", "unknown"),
status=status, status=status,
container_id=container_id, container_id=container_id,
) )
@@ -153,7 +153,7 @@ class ContainerManager:
provider: Optional[str] = None, provider: Optional[str] = None,
ssh: bool = False, ssh: bool = False,
) -> Optional[Session]: ) -> Optional[Session]:
"""Create a new MC session """Create a new Cubbi session
Args: Args:
image_name: The name of the image to use image_name: The name of the image to use
@@ -180,7 +180,7 @@ class ContainerManager:
# Generate session ID and name # Generate session ID and name
session_id = self._generate_session_id() session_id = self._generate_session_id()
if not session_name: if not session_name:
session_name = f"mc-{session_id}" session_name = f"cubbi-{session_id}"
# Ensure network exists # Ensure network exists
self._ensure_network() self._ensure_network()
@@ -188,12 +188,12 @@ class ContainerManager:
# Prepare environment variables # Prepare environment variables
env_vars = environment or {} env_vars = environment or {}
# Add MC_USER_ID and MC_GROUP_ID for entrypoint script # Add CUBBI_USER_ID and CUBBI_GROUP_ID for entrypoint script
env_vars["MC_USER_ID"] = str(uid) if uid is not None else "1000" env_vars["CUBBI_USER_ID"] = str(uid) if uid is not None else "1000"
env_vars["MC_GROUP_ID"] = str(gid) if gid is not None else "1000" env_vars["CUBBI_GROUP_ID"] = str(gid) if gid is not None else "1000"
# Set SSH environment variable # Set SSH environment variable
env_vars["MC_SSH_ENABLED"] = "true" if ssh else "false" env_vars["CUBBI_SSH_ENABLED"] = "true" if ssh else "false"
# Pass API keys from host environment to container for local development # Pass API keys from host environment to container for local development
api_keys = [ api_keys = [
@@ -240,7 +240,7 @@ class ContainerManager:
# Clear project for container environment since we're mounting # Clear project for container environment since we're mounting
project = None project = None
elif is_git_repo: elif is_git_repo:
env_vars["MC_PROJECT_URL"] = project env_vars["CUBBI_PROJECT_URL"] = project
print( print(
f"Git repository URL provided - container will clone {project} into /app during initialization" f"Git repository URL provided - container will clone {project} into /app during initialization"
) )
@@ -269,13 +269,13 @@ class ContainerManager:
# Mount the project configuration directory # Mount the project configuration directory
session_volumes[str(project_config_path)] = { session_volumes[str(project_config_path)] = {
"bind": "/mc-config", "bind": "/cubbi-config",
"mode": "rw", "mode": "rw",
} }
# Add environment variables for config path # Add environment variables for config path
env_vars["MC_CONFIG_DIR"] = "/mc-config" env_vars["CUBBI_CONFIG_DIR"] = "/cubbi-config"
env_vars["MC_IMAGE_CONFIG_DIR"] = f"/mc-config/{image_name}" env_vars["CUBBI_IMAGE_CONFIG_DIR"] = f"/cubbi-config/{image_name}"
# Create image-specific config directories and set up direct volume mounts # Create image-specific config directories and set up direct volume mounts
if image.persistent_configs: if image.persistent_configs:
@@ -284,7 +284,7 @@ class ContainerManager:
for config in image.persistent_configs: for config in image.persistent_configs:
# Get target directory path on host # Get target directory path on host
target_dir = project_config_path / config.target.removeprefix( target_dir = project_config_path / config.target.removeprefix(
"/mc-config/" "/cubbi-config/"
) )
# Create directory if it's a directory type config # Create directory if it's a directory type config
@@ -299,7 +299,7 @@ class ContainerManager:
# File will be created by the container if needed # File will be created by the container if needed
# Store the source and target paths for the init script # Store the source and target paths for the init script
# Note: config.target is the path *within* /mc-config # Note: config.target is the path *within* /cubbi-config
persistent_links_data.append(f"{config.source}:{config.target}") persistent_links_data.append(f"{config.source}:{config.target}")
print( print(
@@ -308,20 +308,20 @@ class ContainerManager:
# Set up persistent links # Set up persistent links
if persistent_links_data: if persistent_links_data:
env_vars["MC_PERSISTENT_LINKS"] = ";".join( env_vars["CUBBI_PERSISTENT_LINKS"] = ";".join(
persistent_links_data persistent_links_data
) )
print( print(
f"Setting MC_PERSISTENT_LINKS={env_vars['MC_PERSISTENT_LINKS']}" f"Setting CUBBI_PERSISTENT_LINKS={env_vars['CUBBI_PERSISTENT_LINKS']}"
) )
else: else:
print( print(
"No project_name provided - skipping configuration directory setup." "No project_name provided - skipping configuration directory setup."
) )
# Default MC network # Default Cubbi network
default_network = self.config_manager.config.docker.get( default_network = self.config_manager.config.docker.get(
"network", "mc-network" "network", "cubbi-network"
) )
# Get network list # Get network list
@@ -440,9 +440,9 @@ class ContainerManager:
env_vars["MCP_NAMES"] = json.dumps(mcp_names) env_vars["MCP_NAMES"] = json.dumps(mcp_names)
# Add user-specified networks # Add user-specified networks
# Default MC network # Default Cubbi network
default_network = self.config_manager.config.docker.get( default_network = self.config_manager.config.docker.get(
"network", "mc-network" "network", "cubbi-network"
) )
# Get network list, ensuring default is first and no duplicates # Get network list, ensuring default is first and no duplicates
@@ -468,12 +468,12 @@ class ContainerManager:
target_shell = "/bin/bash" target_shell = "/bin/bash"
if run_command: if run_command:
# Set environment variable for mc-init.sh to pick up # Set environment variable for cubbi-init.sh to pick up
env_vars["MC_RUN_COMMAND"] = run_command env_vars["CUBBI_RUN_COMMAND"] = run_command
# Set the container's command to be the final shell # Set the container's command to be the final shell
container_command = [target_shell] container_command = [target_shell]
logger.info( logger.info(
f"Setting MC_RUN_COMMAND and targeting shell {target_shell}" f"Setting CUBBI_RUN_COMMAND and targeting shell {target_shell}"
) )
else: else:
# Use default behavior (often defined by image's ENTRYPOINT/CMD) # Use default behavior (often defined by image's ENTRYPOINT/CMD)
@@ -486,10 +486,10 @@ class ContainerManager:
) )
# Set default model/provider from user config if not explicitly provided # Set default model/provider from user config if not explicitly provided
env_vars["MC_MODEL"] = model or self.user_config_manager.get( env_vars["CUBBI_MODEL"] = model or self.user_config_manager.get(
"defaults.model", "" "defaults.model", ""
) )
env_vars["MC_PROVIDER"] = provider or self.user_config_manager.get( env_vars["CUBBI_PROVIDER"] = provider or self.user_config_manager.get(
"defaults.provider", "" "defaults.provider", ""
) )
@@ -504,13 +504,13 @@ class ContainerManager:
environment=env_vars, environment=env_vars,
volumes=session_volumes, volumes=session_volumes,
labels={ labels={
"mc.session": "true", "cubbi.session": "true",
"mc.session.id": session_id, "cubbi.session.id": session_id,
"mc.session.name": session_name, "cubbi.session.name": session_name,
"mc.image": image_name, "cubbi.image": image_name,
"mc.project": project or "", "cubbi.project": project or "",
"mc.project_name": project_name or "", "cubbi.project_name": project_name or "",
"mc.mcps": ",".join(mcp_names) if mcp_names else "", "cubbi.mcps": ",".join(mcp_names) if mcp_names else "",
}, },
network=network_list[0], # Connect to the first network initially network=network_list[0], # Connect to the first network initially
command=container_command, # Set the command command=container_command, # Set the command
@@ -549,7 +549,7 @@ class ContainerManager:
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"cubbi-mcp-{mcp_name}-network"
try: try:
network = self.client.networks.get(dedicated_network_name) network = self.client.networks.get(dedicated_network_name)
@@ -633,7 +633,7 @@ class ContainerManager:
return None return None
def close_session(self, session_id: str) -> bool: def close_session(self, session_id: str) -> bool:
"""Close a MC session""" """Close a Cubbi session"""
try: try:
sessions = self.list_sessions() sessions = self.list_sessions()
for session in sessions: for session in sessions:
@@ -648,7 +648,7 @@ class ContainerManager:
return False return False
def connect_session(self, session_id: str) -> bool: def connect_session(self, session_id: str) -> bool:
"""Connect to a running MC session""" """Connect to a running Cubbi session"""
# Retrieve full session data which should include uid/gid # Retrieve full session data which should include uid/gid
session_data = self.session_manager.get_session(session_id) session_data = self.session_manager.get_session(session_id)
@@ -738,7 +738,7 @@ class ContainerManager:
return False return False
def close_all_sessions(self, progress_callback=None) -> Tuple[int, bool]: def close_all_sessions(self, progress_callback=None) -> Tuple[int, bool]:
"""Close all MC sessions with parallel processing and progress reporting """Close all Cubbi sessions with parallel processing and progress reporting
Args: Args:
progress_callback: Optional callback function to report progress progress_callback: Optional callback function to report progress
@@ -811,7 +811,7 @@ class ContainerManager:
return 0, False return 0, False
def get_session_logs(self, session_id: str, follow: bool = False) -> Optional[str]: def get_session_logs(self, session_id: str, follow: bool = False) -> Optional[str]:
"""Get logs from a MC session""" """Get logs from a Cubbi session"""
try: try:
sessions = self.list_sessions() sessions = self.list_sessions()
for session in sessions: for session in sessions:
@@ -832,7 +832,7 @@ class ContainerManager:
return None return None
def get_init_logs(self, session_id: str, follow: bool = False) -> Optional[str]: def get_init_logs(self, session_id: str, follow: bool = False) -> Optional[str]:
"""Get initialization logs from a MC session """Get initialization logs from a Cubbi session
Args: Args:
session_id: The session ID session_id: The session ID

View File

@@ -1,7 +1,7 @@
FROM python:3.12-slim FROM python:3.12-slim
LABEL maintainer="team@monadical.com" LABEL maintainer="team@monadical.com"
LABEL description="Goose with MCP servers" LABEL description="Goose with MCP servers for Cubbi"
# Install system dependencies including gosu for user switching and shadow for useradd/groupadd # Install system dependencies including gosu for user switching and shadow for useradd/groupadd
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -41,16 +41,16 @@ RUN curl -fsSL https://github.com/block/goose/releases/download/stable/download_
WORKDIR /app WORKDIR /app
# Copy initialization scripts # Copy initialization scripts
COPY mc-init.sh /mc-init.sh COPY cubbi-init.sh /cubbi-init.sh
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh
COPY mc-image.yaml /mc-image.yaml COPY cubbi-image.yaml /cubbi-image.yaml
COPY init-status.sh /init-status.sh COPY init-status.sh /init-status.sh
COPY update-goose-config.py /usr/local/bin/update-goose-config.py COPY update-goose-config.py /usr/local/bin/update-goose-config.py
# 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 \ RUN chmod +x /cubbi-init.sh /entrypoint.sh /init-status.sh \
/usr/local/bin/update-goose-config.py /usr/local/bin/update-goose-config.py
# Set up initialization status check on login # Set up initialization status check on login
@@ -59,7 +59,7 @@ RUN echo '[ -x /init-status.sh ] && /init-status.sh' >> /etc/bash.bashrc
# Set up environment # Set up environment
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONDONTWRITEBYTECODE=1
# Set WORKDIR to /app, common practice and expected by mc-init.sh # Set WORKDIR to /app, common practice and expected by cubbi-init.sh
WORKDIR /app WORKDIR /app
# Expose ports # Expose ports

View File

@@ -17,9 +17,9 @@ This image provides a containerized environment for running [Goose](https://goos
| `LANGFUSE_INIT_PROJECT_PUBLIC_KEY` | Langfuse public key | No | | `LANGFUSE_INIT_PROJECT_PUBLIC_KEY` | Langfuse public key | No |
| `LANGFUSE_INIT_PROJECT_SECRET_KEY` | Langfuse secret key | No | | `LANGFUSE_INIT_PROJECT_SECRET_KEY` | Langfuse secret key | No |
| `LANGFUSE_URL` | Langfuse API URL | No | | `LANGFUSE_URL` | Langfuse API URL | No |
| `MC_PROJECT_URL` | Project repository URL | No | | `CUBBI_PROJECT_URL` | Project repository URL | No |
| `MC_GIT_SSH_KEY` | SSH key for Git authentication | No | | `CUBBI_GIT_SSH_KEY` | SSH key for Git authentication | No |
| `MC_GIT_TOKEN` | Token for Git authentication | No | | `CUBBI_GIT_TOKEN` | Token for Git authentication | No |
## Build ## Build
@@ -27,15 +27,15 @@ To build this image:
```bash ```bash
cd drivers/goose cd drivers/goose
docker build -t monadical/mc-goose:latest . docker build -t monadical/cubbi-goose:latest .
``` ```
## Usage ## Usage
```bash ```bash
# Create a new session with this image # Create a new session with this image
mc session create --driver goose cubbi session create --driver goose
# Create with project repository # Create with project repository
mc session create --driver goose --project github.com/username/repo cubbi session create --driver goose --project github.com/username/repo
``` ```

View File

@@ -2,10 +2,10 @@ name: goose
description: Goose AI environment description: Goose AI environment
version: 1.0.0 version: 1.0.0
maintainer: team@monadical.com maintainer: team@monadical.com
image: monadical/mc-goose:latest image: monadical/cubbi-goose:latest
init: init:
pre_command: /mc-init.sh pre_command: /cubbi-init.sh
command: /entrypoint.sh command: /entrypoint.sh
environment: environment:
@@ -25,21 +25,21 @@ environment:
default: https://cloud.langfuse.com default: https://cloud.langfuse.com
# Project environment variables # Project environment variables
- name: MC_PROJECT_URL - name: CUBBI_PROJECT_URL
description: Project repository URL description: Project repository URL
required: false required: false
- name: MC_PROJECT_TYPE - name: CUBBI_PROJECT_TYPE
description: Project repository type (git, svn, etc.) description: Project repository type (git, svn, etc.)
required: false required: false
default: git default: git
- name: MC_GIT_SSH_KEY - name: CUBBI_GIT_SSH_KEY
description: SSH key for Git authentication description: SSH key for Git authentication
required: false required: false
sensitive: true sensitive: true
- name: MC_GIT_TOKEN - name: CUBBI_GIT_TOKEN
description: Token for Git authentication description: Token for Git authentication
required: false required: false
sensitive: true sensitive: true
@@ -54,10 +54,10 @@ volumes:
persistent_configs: persistent_configs:
- source: "/app/.goose" - source: "/app/.goose"
target: "/mc-config/goose-app" target: "/cubbi-config/goose-app"
type: "directory" type: "directory"
description: "Goose memory" description: "Goose memory"
- source: "/home/mcuser/.config/goose" - source: "/home/cubbi/.config/goose"
target: "/mc-config/goose-config" target: "/cubbi-config/goose-config"
type: "directory" type: "directory"
description: "Goose configuration" description: "Goose configuration"

View File

@@ -1,59 +1,59 @@
#!/bin/bash #!/bin/bash
# Standardized initialization script for MC images # Standardized initialization script for Cubbi images
# Redirect all output to both stdout and the log file # Redirect all output to both stdout and the log file
exec > >(tee -a /init.log) 2>&1 exec > >(tee -a /init.log) 2>&1
# Mark initialization as started # Mark initialization as started
echo "=== MC Initialization started at $(date) ===" echo "=== Cubbi Initialization started at $(date) ==="
# --- START INSERTED BLOCK --- # --- START INSERTED BLOCK ---
# Default UID/GID if not provided (should be passed by mc tool) # Default UID/GID if not provided (should be passed by cubbi tool)
MC_USER_ID=${MC_USER_ID:-1000} CUBBI_USER_ID=${CUBBI_USER_ID:-1000}
MC_GROUP_ID=${MC_GROUP_ID:-1000} CUBBI_GROUP_ID=${CUBBI_GROUP_ID:-1000}
echo "Using UID: $MC_USER_ID, GID: $MC_GROUP_ID" echo "Using UID: $CUBBI_USER_ID, GID: $CUBBI_GROUP_ID"
# Create group if it doesn't exist # Create group if it doesn't exist
if ! getent group mcuser > /dev/null; then if ! getent group cubbi > /dev/null; then
groupadd -g $MC_GROUP_ID mcuser groupadd -g $CUBBI_GROUP_ID cubbi
else else
# If group exists but has different GID, modify it # If group exists but has different GID, modify it
EXISTING_GID=$(getent group mcuser | cut -d: -f3) EXISTING_GID=$(getent group cubbi | cut -d: -f3)
if [ "$EXISTING_GID" != "$MC_GROUP_ID" ]; then if [ "$EXISTING_GID" != "$CUBBI_GROUP_ID" ]; then
groupmod -g $MC_GROUP_ID mcuser groupmod -g $CUBBI_GROUP_ID cubbi
fi fi
fi fi
# Create user if it doesn't exist # Create user if it doesn't exist
if ! getent passwd mcuser > /dev/null; then if ! getent passwd cubbi > /dev/null; then
useradd --shell /bin/bash --uid $MC_USER_ID --gid $MC_GROUP_ID --no-create-home mcuser useradd --shell /bin/bash --uid $CUBBI_USER_ID --gid $CUBBI_GROUP_ID --no-create-home cubbi
else else
# If user exists but has different UID/GID, modify it # If user exists but has different UID/GID, modify it
EXISTING_UID=$(getent passwd mcuser | cut -d: -f3) EXISTING_UID=$(getent passwd cubbi | cut -d: -f3)
EXISTING_GID=$(getent passwd mcuser | cut -d: -f4) EXISTING_GID=$(getent passwd cubbi | cut -d: -f4)
if [ "$EXISTING_UID" != "$MC_USER_ID" ] || [ "$EXISTING_GID" != "$MC_GROUP_ID" ]; then if [ "$EXISTING_UID" != "$CUBBI_USER_ID" ] || [ "$EXISTING_GID" != "$CUBBI_GROUP_ID" ]; then
usermod --uid $MC_USER_ID --gid $MC_GROUP_ID mcuser usermod --uid $CUBBI_USER_ID --gid $CUBBI_GROUP_ID cubbi
fi fi
fi fi
# Create home directory and set permissions # Create home directory and set permissions
mkdir -p /home/mcuser mkdir -p /home/cubbi
chown $MC_USER_ID:$MC_GROUP_ID /home/mcuser chown $CUBBI_USER_ID:$CUBBI_GROUP_ID /home/cubbi
mkdir -p /app mkdir -p /app
chown $MC_USER_ID:$MC_GROUP_ID /app chown $CUBBI_USER_ID:$CUBBI_GROUP_ID /app
# Copy /root/.local/bin to the user's home directory # Copy /root/.local/bin to the user's home directory
if [ -d /root/.local/bin ]; then if [ -d /root/.local/bin ]; then
echo "Copying /root/.local/bin to /home/mcuser/.local/bin..." echo "Copying /root/.local/bin to /home/cubbi/.local/bin..."
mkdir -p /home/mcuser/.local/bin mkdir -p /home/cubbi/.local/bin
cp -r /root/.local/bin/* /home/mcuser/.local/bin/ cp -r /root/.local/bin/* /home/cubbi/.local/bin/
chown -R $MC_USER_ID:$MC_GROUP_ID /home/mcuser/.local chown -R $CUBBI_USER_ID:$CUBBI_GROUP_ID /home/cubbi/.local
fi fi
# Start SSH server only if explicitly enabled # Start SSH server only if explicitly enabled
if [ "$MC_SSH_ENABLED" = "true" ]; then if [ "$CUBBI_SSH_ENABLED" = "true" ]; then
echo "Starting SSH server..." echo "Starting SSH server..."
/usr/sbin/sshd /usr/sbin/sshd
else else
@@ -65,13 +65,13 @@ fi
echo "INIT_COMPLETE=false" > /init.status echo "INIT_COMPLETE=false" > /init.status
# Project initialization # Project initialization
if [ -n "$MC_PROJECT_URL" ]; then if [ -n "$CUBBI_PROJECT_URL" ]; then
echo "Initializing project: $MC_PROJECT_URL" echo "Initializing project: $CUBBI_PROJECT_URL"
# Set up SSH key if provided # Set up SSH key if provided
if [ -n "$MC_GIT_SSH_KEY" ]; then if [ -n "$CUBBI_GIT_SSH_KEY" ]; then
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "$MC_GIT_SSH_KEY" > ~/.ssh/id_ed25519 echo "$CUBBI_GIT_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
ssh-keyscan gitlab.com >> ~/.ssh/known_hosts 2>/dev/null ssh-keyscan gitlab.com >> ~/.ssh/known_hosts 2>/dev/null
@@ -79,23 +79,23 @@ if [ -n "$MC_PROJECT_URL" ]; then
fi fi
# Set up token if provided # Set up token if provided
if [ -n "$MC_GIT_TOKEN" ]; then if [ -n "$CUBBI_GIT_TOKEN" ]; then
git config --global credential.helper store git config --global credential.helper store
echo "https://$MC_GIT_TOKEN:x-oauth-basic@github.com" > ~/.git-credentials echo "https://$CUBBI_GIT_TOKEN:x-oauth-basic@github.com" > ~/.git-credentials
fi fi
# Clone repository # Clone repository
git clone $MC_PROJECT_URL /app git clone $CUBBI_PROJECT_URL /app
cd /app cd /app
# Run project-specific initialization if present # Run project-specific initialization if present
if [ -f "/app/.mc/init.sh" ]; then if [ -f "/app/.cubbi/init.sh" ]; then
bash /app/.mc/init.sh bash /app/.cubbi/init.sh
fi fi
# Persistent configs are now directly mounted as volumes # Persistent configs are now directly mounted as volumes
# No need to create symlinks anymore # No need to create symlinks anymore
if [ -n "$MC_CONFIG_DIR" ] && [ -d "$MC_CONFIG_DIR" ]; then if [ -n "$CUBBI_CONFIG_DIR" ] && [ -d "$CUBBI_CONFIG_DIR" ]; then
echo "Using persistent configuration volumes (direct mounts)" echo "Using persistent configuration volumes (direct mounts)"
fi fi
fi fi
@@ -110,18 +110,18 @@ if [ -n "$LANGFUSE_INIT_PROJECT_SECRET_KEY" ] && [ -n "$LANGFUSE_INIT_PROJECT_PU
export LANGFUSE_URL="${LANGFUSE_URL:-https://cloud.langfuse.com}" export LANGFUSE_URL="${LANGFUSE_URL:-https://cloud.langfuse.com}"
fi fi
# Ensure /mc-config directory exists (required for symlinks) # Ensure /cubbi-config directory exists (required for symlinks)
if [ ! -d "/mc-config" ]; then if [ ! -d "/cubbi-config" ]; then
echo "Creating /mc-config directory since it doesn't exist" echo "Creating /cubbi-config directory since it doesn't exist"
mkdir -p /mc-config mkdir -p /cubbi-config
chown $MC_USER_ID:$MC_GROUP_ID /mc-config chown $CUBBI_USER_ID:$CUBBI_GROUP_ID /cubbi-config
fi fi
# Create symlinks for persistent configurations defined in the image # Create symlinks for persistent configurations defined in the image
if [ -n "$MC_PERSISTENT_LINKS" ]; then if [ -n "$CUBBI_PERSISTENT_LINKS" ]; then
echo "Creating persistent configuration symlinks..." echo "Creating persistent configuration symlinks..."
# Split by semicolon # Split by semicolon
IFS=';' read -ra LINKS <<< "$MC_PERSISTENT_LINKS" IFS=';' read -ra LINKS <<< "$CUBBI_PERSISTENT_LINKS"
for link_pair in "${LINKS[@]}"; do for link_pair in "${LINKS[@]}"; do
# Split by colon # Split by colon
IFS=':' read -r source_path target_path <<< "$link_pair" IFS=':' read -r source_path target_path <<< "$link_pair"
@@ -134,47 +134,46 @@ if [ -n "$MC_PERSISTENT_LINKS" ]; then
echo "Processing link: $source_path -> $target_path" echo "Processing link: $source_path -> $target_path"
parent_dir=$(dirname "$source_path") parent_dir=$(dirname "$source_path")
# Ensure parent directory of the link source exists and is owned by mcuser # Ensure parent directory of the link source exists and is owned by cubbi
if [ ! -d "$parent_dir" ]; then if [ ! -d "$parent_dir" ]; then
echo "Creating parent directory: $parent_dir" echo "Creating parent directory: $parent_dir"
mkdir -p "$parent_dir" mkdir -p "$parent_dir"
echo "Changing ownership of parent $parent_dir to $MC_USER_ID:$MC_GROUP_ID" echo "Changing ownership of parent $parent_dir to $CUBBI_USER_ID:$CUBBI_GROUP_ID"
chown "$MC_USER_ID:$MC_GROUP_ID" "$parent_dir" || echo "Warning: Could not chown parent $parent_dir" chown "$CUBBI_USER_ID:$CUBBI_GROUP_ID" "$parent_dir" || echo "Warning: Could not chown parent $parent_dir"
fi fi
# Create the symlink (force, no-dereference) # Create the symlink (force, no-dereference)
echo "Creating symlink: ln -sfn $target_path $source_path" echo "Creating symlink: ln -sfn $target_path $source_path"
ln -sfn "$target_path" "$source_path" ln -sfn "$target_path" "$source_path"
# Optionally, change ownership of the symlink itself # Optionally, change ownership of the symlink itself
echo "Changing ownership of symlink $source_path to $MC_USER_ID:$MC_GROUP_ID" echo "Changing ownership of symlink $source_path to $CUBBI_USER_ID:$CUBBI_GROUP_ID"
chown -h "$MC_USER_ID:$MC_GROUP_ID" "$source_path" || echo "Warning: Could not chown symlink $source_path" chown -h "$CUBBI_USER_ID:$CUBBI_GROUP_ID" "$source_path" || echo "Warning: Could not chown symlink $source_path"
done done
echo "Persistent configuration symlinks created." echo "Persistent configuration symlinks created."
fi fi
# Update Goose configuration with available MCP servers (run as mcuser after symlinks are created) # Update Goose configuration with available MCP servers (run as cubbi after symlinks are created)
if [ -f "/usr/local/bin/update-goose-config.py" ]; then if [ -f "/usr/local/bin/update-goose-config.py" ]; then
echo "Updating Goose configuration with MCP servers as mcuser..." echo "Updating Goose configuration with MCP servers as cubbi..."
gosu mcuser /usr/local/bin/update-goose-config.py gosu cubbi /usr/local/bin/update-goose-config.py
elif [ -f "$(dirname "$0")/update-goose-config.py" ]; then elif [ -f "$(dirname "$0")/update-goose-config.py" ]; then
echo "Updating Goose configuration with MCP servers as mcuser..." echo "Updating Goose configuration with MCP servers as cubbi..."
gosu mcuser "$(dirname "$0")/update-goose-config.py" gosu cubbi "$(dirname "$0")/update-goose-config.py"
else else
echo "Warning: update-goose-config.py script not found. Goose configuration will not be updated." echo "Warning: update-goose-config.py script not found. Goose configuration will not be updated."
fi fi
# Run the user command first, if set, as mcuser # Run the user command first, if set, as cubbi
if [ -n "$MC_RUN_COMMAND" ]; then if [ -n "$CUBBI_RUN_COMMAND" ]; then
echo "--- Executing initial command: $MC_RUN_COMMAND ---"; echo "--- Executing initial command: $CUBBI_RUN_COMMAND ---";
gosu mcuser sh -c "$MC_RUN_COMMAND"; # Run user command as mcuser gosu cubbi sh -c "$CUBBI_RUN_COMMAND"; # Run user command as cubbi
COMMAND_EXIT_CODE=$?; COMMAND_EXIT_CODE=$?;
echo "--- Initial command finished (exit code: $COMMAND_EXIT_CODE) ---"; echo "--- Initial command finished (exit code: $COMMAND_EXIT_CODE) ---";
fi; fi;
# Mark initialization as complete # Mark initialization as complete
echo "=== MC Initialization completed at $(date) ===" echo "=== Cubbi Initialization completed at $(date) ==="
echo "INIT_COMPLETE=true" > /init.status echo "INIT_COMPLETE=true" > /init.status
exec gosu mcuser "$@" exec gosu cubbi "$@"

View File

@@ -4,4 +4,4 @@
# service startup (like sshd), and switching to the non-root user # service startup (like sshd), and switching to the non-root user
# before running the container's command (CMD). # before running the container's command (CMD).
exec /mc-init.sh "$@" exec /cubbi-init.sh "$@"

View File

@@ -28,4 +28,4 @@ if ! grep -q "INIT_COMPLETE=true" "/init.status" 2>/dev/null; then
fi fi
fi fi
exec gosu mcuser /bin/bash -il exec gosu cubbi /bin/bash -il

View File

@@ -39,8 +39,8 @@ def update_config():
} }
# Update goose configuration with model and provider from environment variables # Update goose configuration with model and provider from environment variables
goose_model = os.environ.get("MC_MODEL") goose_model = os.environ.get("CUBBI_MODEL")
goose_provider = os.environ.get("MC_PROVIDER") goose_provider = os.environ.get("CUBBI_PROVIDER")
if goose_model: if goose_model:
config_data["GOOSE_MODEL"] = goose_model config_data["GOOSE_MODEL"] = goose_model

View File

@@ -1,5 +1,5 @@
""" """
MCP (Model Control Protocol) server management for Monadical Container. MCP (Model Control Protocol) server management for Cubbi Container.
""" """
import logging import logging
@@ -38,7 +38,7 @@ class MCPManager:
"""Ensure the MCP network exists and return its name. """Ensure the MCP network exists and return its name.
Note: This is used only by the inspector, not for session-to-MCP connections. Note: This is used only by the inspector, not for session-to-MCP connections.
""" """
network_name = "mc-mcp-network" network_name = "cubbi-mcp-network"
if self.client: if self.client:
networks = self.client.networks.list(names=[network_name]) networks = self.client.networks.list(names=[network_name])
if not networks: if not networks:
@@ -54,7 +54,7 @@ class MCPManager:
Returns: Returns:
The name of the dedicated network The name of the dedicated network
""" """
network_name = f"mc-mcp-{mcp_name}-network" network_name = f"cubbi-mcp-{mcp_name}-network"
if self.client: if self.client:
networks = self.client.networks.list(names=[network_name]) networks = self.client.networks.list(names=[network_name])
if not networks: if not networks:
@@ -282,7 +282,7 @@ class MCPManager:
def get_mcp_container_name(self, mcp_name: str) -> str: def get_mcp_container_name(self, mcp_name: str) -> str:
"""Get the Docker container name for an MCP server.""" """Get the Docker container name for an MCP server."""
return f"mc_mcp_{mcp_name}" return f"cubbi_mcp_{mcp_name}"
def start_mcp(self, name: str) -> Dict[str, Any]: def start_mcp(self, name: str) -> Dict[str, Any]:
"""Start an MCP server container.""" """Start an MCP server container."""
@@ -374,9 +374,9 @@ class MCPManager:
network=None, # Start without network, we'll add it with aliases network=None, # Start without network, we'll add it with aliases
environment=mcp_config.get("env", {}), environment=mcp_config.get("env", {}),
labels={ labels={
"mc.mcp": "true", "cubbi.mcp": "true",
"mc.mcp.name": name, "cubbi.mcp.name": name,
"mc.mcp.type": "docker", "cubbi.mcp.type": "docker",
}, },
) )
@@ -540,7 +540,7 @@ ENTRYPOINT ["/entrypoint.sh"]
f.write(dockerfile_content) f.write(dockerfile_content)
# Build the image # Build the image
custom_image_name = f"mc_mcp_proxy_{name}" custom_image_name = f"cubbi_mcp_proxy_{name}"
logger.info(f"Building custom proxy image: {custom_image_name}") logger.info(f"Building custom proxy image: {custom_image_name}")
self.client.images.build( self.client.images.build(
path=tmp_dir, path=tmp_dir,
@@ -577,9 +577,9 @@ ENTRYPOINT ["/entrypoint.sh"]
} }
}, },
labels={ labels={
"mc.mcp": "true", "cubbi.mcp": "true",
"mc.mcp.name": name, "cubbi.mcp.name": name,
"mc.mcp.type": "proxy", "cubbi.mcp.type": "proxy",
}, },
ports=port_bindings, # Bind the SSE port to the host if configured ports=port_bindings, # Bind the SSE port to the host if configured
) )
@@ -816,8 +816,10 @@ ENTRYPOINT ["/entrypoint.sh"]
if not self.client: if not self.client:
raise Exception("Docker client is not available") raise Exception("Docker client is not available")
# Get all containers with the mc.mcp label # Get all containers with the cubbi.mcp label
containers = self.client.containers.list(all=True, filters={"label": "mc.mcp"}) containers = self.client.containers.list(
all=True, filters={"label": "cubbi.mcp"}
)
result = [] result = []
for container in containers: for container in containers:
@@ -858,13 +860,13 @@ ENTRYPOINT ["/entrypoint.sh"]
# Create MCPContainer object # Create MCPContainer object
mcp_container = MCPContainer( mcp_container = MCPContainer(
name=labels.get("mc.mcp.name", "unknown"), name=labels.get("cubbi.mcp.name", "unknown"),
container_id=container.id, container_id=container.id,
status=status, status=status,
image=container_info["Config"]["Image"], image=container_info["Config"]["Image"],
ports=ports, ports=ports,
created_at=container_info["Created"], created_at=container_info["Created"],
type=labels.get("mc.mcp.type", "unknown"), type=labels.get("cubbi.mcp.type", "unknown"),
) )
result.append(mcp_container) result.append(mcp_container)

View File

@@ -1,5 +1,5 @@
""" """
Session storage management for Monadical Container Tool. Session storage management for Cubbi Container Tool.
""" """
import os import os
@@ -8,7 +8,7 @@ from typing import Dict, Optional
import yaml import yaml
DEFAULT_SESSIONS_FILE = Path.home() / ".config" / "mc" / "sessions.yaml" DEFAULT_SESSIONS_FILE = Path.home() / ".config" / "cubbi" / "sessions.yaml"
class SessionManager: class SessionManager:
@@ -19,7 +19,7 @@ class SessionManager:
Args: Args:
sessions_path: Optional path to the sessions file. sessions_path: Optional path to the sessions file.
Defaults to ~/.config/mc/sessions.yaml. Defaults to ~/.config/cubbi/sessions.yaml.
""" """
self.sessions_path = sessions_path or DEFAULT_SESSIONS_FILE self.sessions_path = sessions_path or DEFAULT_SESSIONS_FILE
self.sessions = self._load_sessions() self.sessions = self._load_sessions()

View File

@@ -1,5 +1,5 @@
""" """
User configuration manager for Monadical Container Tool. User configuration manager for Cubbi Container Tool.
""" """
import os import os
@@ -28,11 +28,11 @@ class UserConfigManager:
Args: Args:
config_path: Optional path to the configuration file. config_path: Optional path to the configuration file.
Defaults to ~/.config/mc/config.yaml. Defaults to ~/.config/cubbi/config.yaml.
""" """
# Default to ~/.config/mc/config.yaml # Default to ~/.config/cubbi/config.yaml
self.config_path = Path( self.config_path = Path(
config_path or os.path.expanduser("~/.config/mc/config.yaml") config_path or os.path.expanduser("~/.config/cubbi/config.yaml")
) )
self.config = self._load_config() self.config = self._load_config()
@@ -93,7 +93,7 @@ class UserConfigManager:
"image": "goose", "image": "goose",
"connect": True, "connect": True,
"mount_local": True, "mount_local": True,
"networks": [], # Default networks to connect to (besides mc-network) "networks": [], # Default networks to connect to (besides cubbi-network)
"volumes": [], # Default volumes to mount, format: "source:dest" "volumes": [], # Default volumes to mount, format: "source:dest"
"mcps": [], # Default MCP servers to connect to "mcps": [], # Default MCP servers to connect to
"model": "claude-3-5-sonnet-latest", # Default LLM model to use "model": "claude-3-5-sonnet-latest", # Default LLM model to use
@@ -107,7 +107,7 @@ class UserConfigManager:
"google": {}, "google": {},
}, },
"docker": { "docker": {
"network": "mc-network", "network": "cubbi-network",
}, },
"ui": { "ui": {
"colors": True, "colors": True,

View File

@@ -1,22 +1,22 @@
# MC - Monadical AI Container Tool # Cubbi - Container Tool
## Overview ## Overview
MC (Monadical Container) is a command-line tool for managing ephemeral Cubbi is a command-line tool for managing ephemeral
containers that run AI tools and development environments. It works with both 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. Docker-in-Docker (DinD) environment.
## Technology Stack ## Technology Stack
### MC Service ### Cubbi Service
- **Web Framework**: FastAPI for high-performance, async API endpoints - **Web Framework**: FastAPI for high-performance, async API endpoints
- **Package Management**: uv (Astral) for dependency management - **Package Management**: uv (Astral) for dependency management
- **Database**: SQLite for development, PostgreSQL for production - **Database**: SQLite for development, PostgreSQL for production
- **Container Management**: Docker SDK for Python - **Container Management**: Docker SDK for Python
- **Authentication**: OAuth 2.0 integration with Authentik - **Authentication**: OAuth 2.0 integration with Authentik
### MC CLI ### Cubbi CLI
- **Language**: Python - **Language**: Python
- **Package Management**: uv for dependency management - **Package Management**: uv for dependency management
- **Distribution**: Standalone binary via PyInstaller or similar - **Distribution**: Standalone binary via PyInstaller or similar
@@ -26,8 +26,8 @@ Docker-in-Docker (DinD) environment.
### Components ### Components
1. **CLI Tool (`mc`)**: The command-line interface users interact with 1. **CLI Tool (`cubbi`)**: The command-line interface users interact with
2. **MC Service**: A web service that handles remote container execution 2. **Cubbi Service**: A web service that handles remote container execution
3. **Container Images**: Predefined container templates for various AI tools 3. **Container Images**: Predefined container templates for various AI tools
### Architecture Diagram ### Architecture Diagram
@@ -35,8 +35,8 @@ Docker-in-Docker (DinD) environment.
``` ```
┌─────────────┐ ┌─────────────────────────┐ ┌─────────────┐ ┌─────────────────────────┐
│ │ │ │ │ │ │ │
MC CLI │◄─────────►│ Local Docker Daemon │ │ Cubbi CLI │◄─────────►│ Local Docker Daemon │
│ (mc) │ │ │ │ (cubbi) │ │ │
│ │ └─────────────────────────┘ │ │ └─────────────────────────┘
└──────┬──────┘ └──────┬──────┘
@@ -44,8 +44,8 @@ Docker-in-Docker (DinD) environment.
┌──────▼──────┐ ┌─────────────────────────┐ ┌──────▼──────┐ ┌─────────────────────────┐
│ │ │ │ │ │ │ │
MC Service │◄─────────►│ Docker-in-Docker │ Cubbi │◄─────────►│ Docker-in-Docker │
(Web API) │ │ │ Service │ │ │
│ │ └─────────────────────────┘ │ │ └─────────────────────────┘
└─────────────┘ └─────────────┘
@@ -63,21 +63,21 @@ Docker-in-Docker (DinD) environment.
- **Session**: An active container instance with a specific image - **Session**: An active container instance with a specific image
- **Image**: A predefined container template with specific AI tools installed - **Image**: A predefined container template with specific AI tools installed
- **Remote**: A configured MC service instance - **Remote**: A configured cubbi service instance
## User Configuration ## User Configuration
MC supports user-specific configuration via a YAML file located at `~/.config/mc/config.yaml`. This provides a way to set default values, store service credentials, and customize behavior without modifying code. Cubbi supports user-specific configuration via a YAML file located at `~/.config/cubbi/config.yaml`. This provides a way to set default values, store service credentials, and customize behavior without modifying code.
### Configuration File Structure ### Configuration File Structure
```yaml ```yaml
# ~/.config/mc/config.yaml # ~/.config/cubbi/config.yaml
defaults: defaults:
image: "goose" # Default image to use image: "goose" # Default image to use
connect: true # Automatically connect after creating session connect: true # Automatically connect after creating session
mount_local: true # Mount local directory by default mount_local: true # Mount local directory by default
networks: [] # Default networks to connect to (besides mc-network) networks: [] # Default networks to connect to (besides cubbi-network)
services: services:
# Service credentials with simplified naming # Service credentials with simplified naming
@@ -97,17 +97,17 @@ services:
api_key: "sk-or-..." api_key: "sk-or-..."
docker: docker:
network: "mc-network" # Default Docker network to use network: "cubbi-network" # Default Docker network to use
socket: "/var/run/docker.sock" # Docker socket path socket: "/var/run/docker.sock" # Docker socket path
remote: remote:
default: "production" # Default remote to use default: "production" # Default remote to use
endpoints: endpoints:
production: production:
url: "https://mc.monadical.com" url: "https://cubbi.monadical.com"
auth_method: "oauth" auth_method: "oauth"
staging: staging:
url: "https://mc-staging.monadical.com" url: "https://cubbi-staging.monadical.com"
auth_method: "oauth" auth_method: "oauth"
ui: ui:
@@ -145,22 +145,22 @@ The simplified configuration names are mapped to environment variables:
```bash ```bash
# View entire configuration # View entire configuration
mc config list cubbi config list
# Get specific configuration value # Get specific configuration value
mc config get defaults.driver cubbi config get defaults.driver
# Set configuration value (using simplified naming) # Set configuration value (using simplified naming)
mc config set langfuse.url "https://cloud.langfuse.com" cubbi config set langfuse.url "https://cloud.langfuse.com"
mc config set openai.api_key "sk-..." cubbi config set openai.api_key "sk-..."
# Network configuration # Network configuration
mc config network list # List default networks cubbi config network list # List default networks
mc config network add example-network # Add a network to defaults cubbi config network add example-network # Add a network to defaults
mc config network remove example-network # Remove a network from defaults cubbi config network remove example-network # Remove a network from defaults
# Reset configuration to defaults # Reset configuration to defaults
mc config reset cubbi config reset
``` ```
## CLI Tool Commands ## CLI Tool Commands
@@ -169,81 +169,81 @@ mc config reset
```bash ```bash
# Create a new session locally (shorthand) # Create a new session locally (shorthand)
mc cubbi
# List active sessions on local system # List active sessions on local system
mc session list cubbi session list
# Create a new session locally # Create a new session locally
mc session create [OPTIONS] cubbi session create [OPTIONS]
# Create a session with a specific image # Create a session with a specific image
mc session create --image goose cubbi session create --image goose
# Create a session with a specific project repository # Create a session with a specific project repository
mc session create --image goose --project github.com/hello/private cubbi session create --image goose --project github.com/hello/private
# Create a session with external networks # Create a session with external networks
mc session create --network teamnet --network othernetwork cubbi session create --network teamnet --network othernetwork
# Create a session with a project (shorthand) # Create a session with a project (shorthand)
mc git@github.com:hello/private cubbi git@github.com:hello/private
# Close a specific session # Close a specific session
mc session close <id> cubbi session close <id>
# Connect to an existing session # Connect to an existing session
mc session connect <id> cubbi session connect <id>
``` ```
### Remote Management ### Remote Management
```bash ```bash
# Add a remote MC service # Add a remote Cubbi service
mc remote add <name> <url> cubbi remote add <name> <url>
# List configured remote services # List configured remote services
mc remote list cubbi remote list
# Remove a remote service # Remove a remote service
mc remote remove <name> cubbi remote remove <name>
# Authenticate with a remote service # Authenticate with a remote service
mc -r <remote_name> auth cubbi -r <remote_name> auth
# Create a session on a remote service # Create a session on a remote service
mc -r <remote_name> [session create] cubbi -r <remote_name> [session create]
# List sessions on a remote service # List sessions on a remote service
mc -r <remote_name> session list cubbi -r <remote_name> session list
``` ```
### Environment Variables ### Environment Variables
```bash ```bash
# Set environment variables for a session # Set environment variables for a session
mc session create -e VAR1=value1 -e VAR2=value2 cubbi session create -e VAR1=value1 -e VAR2=value2
# Set environment variables for a remote session # Set environment variables for a remote session
mc -r <remote_name> session create -e VAR1=value1 cubbi -r <remote_name> session create -e VAR1=value1
``` ```
### Logging ### Logging
```bash ```bash
# Stream logs from a session # Stream logs from a session
mc session logs <id> cubbi session logs <id>
# Stream logs with follow option # Stream logs with follow option
mc session logs <id> -f cubbi session logs <id> -f
``` ```
## MC Service Specification ## Cubbi Service Specification
### Overview ### Overview
The MC Service is a web service that manages ephemeral containers in a Docker-in-Docker environment. It provides a REST API for container lifecycle management, authentication, and real-time log streaming. The Cubbi Service is a web service that manages ephemeral containers in a Docker-in-Docker environment. It provides a REST API for container lifecycle management, authentication, and real-time log streaming.
### API Endpoints ### API Endpoints
@@ -258,19 +258,19 @@ POST /auth/logout - Invalidate current token
### Authentik Integration ### Authentik Integration
The MC Service integrates with Authentik at https://authentik.monadical.io using OAuth 2.0: The Cubbi Service integrates with Authentik at https://authentik.monadical.io using OAuth 2.0:
1. **Application Registration**: 1. **Application Registration**:
- MC Service is registered as an OAuth application in Authentik - Cubbi Service is registered as an OAuth application in Authentik
- Configured with redirect URI to `/auth/callback` - Configured with redirect URI to `/auth/callback`
- Assigned appropriate scopes for user identification - Assigned appropriate scopes for user identification
2. **Authentication Flow**: 2. **Authentication Flow**:
- User initiates authentication via CLI - User initiates authentication via CLI
- MC CLI opens browser to Authentik authorization URL - Cubbi CLI opens browser to Authentik authorization URL
- User logs in through Authentik's interface - User logs in through Authentik's interface
- Authentik redirects to callback URL with authorization code - Authentik redirects to callback URL with authorization code
- MC Service exchanges code for access and refresh tokens - Cubbi Service exchanges code for access and refresh tokens
- CLI receives and securely stores tokens - CLI receives and securely stores tokens
3. **Token Management**: 3. **Token Management**:
@@ -309,19 +309,19 @@ DELETE /projects/{id} - Remove a project
### Service Configuration ### Service Configuration
```yaml ```yaml
# mc-service.yaml # cubbi-service.yaml
server: server:
port: 3000 port: 3000
host: 0.0.0.0 host: 0.0.0.0
docker: docker:
socket: /var/run/docker.sock socket: /var/run/docker.sock
network: mc-network network: cubbi-network
auth: auth:
provider: authentik provider: authentik
url: https://authentik.monadical.io url: https://authentik.monadical.io
clientId: mc-service clientId: cubbi-service
logging: logging:
providers: providers:
@@ -334,11 +334,11 @@ logging:
images: images:
- name: goose - name: goose
image: monadical/mc-goose:latest image: monadical/cubbi-goose:latest
- name: aider - name: aider
image: monadical/mc-aider:latest image: monadical/cubbi-aider:latest
- name: claude-code - name: claude-code
image: monadical/mc-claude-code:latest image: monadical/cubbi-claude-code:latest
projects: projects:
storage: storage:
@@ -352,7 +352,7 @@ projects:
### Docker-in-Docker Implementation ### Docker-in-Docker Implementation
The MC Service runs in a container with access to the host's Docker socket, allowing it to create and manage sibling containers. This approach provides: The Cubbi Service runs in a container with access to the host's Docker socket, allowing it to create and manage sibling containers. This approach provides:
1. Isolation between containers 1. Isolation between containers
2. Simple lifecycle management 2. Simple lifecycle management
@@ -367,7 +367,7 @@ For remote connections to containers, the service provides two methods:
### Logging Implementation ### Logging Implementation
The MC Service implements log collection and forwarding: The Cubbi Service implements log collection and forwarding:
1. Container logs are captured using Docker's logging drivers 1. Container logs are captured using Docker's logging drivers
2. Logs are forwarded to configured providers (Fluentd, Langfuse) 2. Logs are forwarded to configured providers (Fluentd, Langfuse)
@@ -377,22 +377,22 @@ The MC Service implements log collection and forwarding:
### Persistent Project Configuration ### Persistent Project Configuration
MC provides persistent storage for project-specific configurations that need to survive container restarts. This is implemented through a dedicated volume mount and symlink system: Cubbi provides persistent storage for project-specific configurations that need to survive container restarts. This is implemented through a dedicated volume mount and symlink system:
1. **Configuration Storage**: 1. **Configuration Storage**:
- Each project has a dedicated configuration directory on the host at `~/.mc/projects/<project-hash>/config` - Each project has a dedicated configuration directory on the host at `~/.cubbi/projects/<project-hash>/config`
- For projects specified by URL, the hash is derived from the repository URL - For projects specified by URL, the hash is derived from the repository URL
- For local projects, the hash is derived from the absolute path of the local directory - For local projects, the hash is derived from the absolute path of the local directory
- This directory is mounted into the container at `/mc-config` - This directory is mounted into the container at `/cubbi-config`
2. **Image Configuration**: 2. **Image Configuration**:
- Each image can specify configuration files/directories that should persist across sessions - Each image can specify configuration files/directories that should persist across sessions
- These are defined in the image's `mc-image.yaml` file in the `persistent_configs` section - These are defined in the image's `cubbi-image.yaml` file in the `persistent_configs` section
- Example for Goose image: - Example for Goose image:
```yaml ```yaml
persistent_configs: persistent_configs:
- source: "/app/.goose" # Path in container - source: "/app/.goose" # Path in container
target: "/mc-config/goose" # Path in persistent storage target: "/cubbi-config/goose" # Path in persistent storage
type: "directory" # directory or file type: "directory" # directory or file
description: "Goose memory and configuration" description: "Goose memory and configuration"
``` ```
@@ -406,8 +406,8 @@ MC provides persistent storage for project-specific configurations that need to
4. **Environment Variables**: 4. **Environment Variables**:
- Container has access to configuration location via environment variables: - Container has access to configuration location via environment variables:
``` ```
MC_CONFIG_DIR=/mc-config CUBBI_CONFIG_DIR=/cubbi-config
MC_IMAGE_CONFIG_DIR=/mc-config/<image-name> CUBBI_IMAGE_CONFIG_DIR=/cubbi-config/<image-name>
``` ```
This ensures that important configurations like Goose's memory store, authentication tokens, and other state information persist between container sessions while maintaining isolation between different projects. This ensures that important configurations like Goose's memory store, authentication tokens, and other state information persist between container sessions while maintaining isolation between different projects.
@@ -418,21 +418,21 @@ Users can add projects with associated credentials:
```bash ```bash
# Add a project with SSH key # Add a project with SSH key
mc project add github.com/hello/private --ssh-key ~/.ssh/id_ed25519 cubbi project add github.com/hello/private --ssh-key ~/.ssh/id_ed25519
# Add a project with token authentication # Add a project with token authentication
mc project add github.com/hello/private --token ghp_123456789 cubbi project add github.com/hello/private --token ghp_123456789
# List all projects # List all projects
mc project list cubbi project list
# Remove a project # Remove a project
mc project remove github.com/hello/private cubbi project remove github.com/hello/private
``` ```
### Project Configuration ### Project Configuration
Projects are stored in the MC service and referenced by their repository URL. The configuration includes: Projects are stored in the Cubbi service and referenced by their repository URL. The configuration includes:
```yaml ```yaml
# Project configuration # Project configuration
@@ -457,50 +457,50 @@ Each image is a Docker container with a standardized structure:
``` ```
/ /
├── entrypoint.sh # Container initialization ├── entrypoint.sh # Container initialization
├── mc-init.sh # Standardized initialization script ├── cubbi-init.sh # Standardized initialization script
├── mc-image.yaml # Image metadata and configuration ├── cubbi-image.yaml # Image metadata and configuration
├── tool/ # AI tool installation ├── tool/ # AI tool installation
└── ssh/ # SSH server configuration └── ssh/ # SSH server configuration
``` ```
### Standardized Initialization Script ### Standardized Initialization Script
All images include a standardized `mc-init.sh` script that handles common initialization tasks: All images include a standardized `cubbi-init.sh` script that handles common initialization tasks:
```bash ```bash
#!/bin/bash #!/bin/bash
# Project initialization # Project initialization
if [ -n "$MC_PROJECT_URL" ]; then if [ -n "$CUBBI_PROJECT_URL" ]; then
echo "Initializing project: $MC_PROJECT_URL" echo "Initializing project: $CUBBI_PROJECT_URL"
# Set up SSH key if provided # Set up SSH key if provided
if [ -n "$MC_GIT_SSH_KEY" ]; then if [ -n "$CUBBI_GIT_SSH_KEY" ]; then
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "$MC_GIT_SSH_KEY" > ~/.ssh/id_ed25519 echo "$CUBBI_GIT_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
fi fi
# Set up token if provided # Set up token if provided
if [ -n "$MC_GIT_TOKEN" ]; then if [ -n "$CUBBI_GIT_TOKEN" ]; then
git config --global credential.helper store git config --global credential.helper store
echo "https://$MC_GIT_TOKEN:x-oauth-basic@github.com" > ~/.git-credentials echo "https://$CUBBI_GIT_TOKEN:x-oauth-basic@github.com" > ~/.git-credentials
fi fi
# Clone repository # Clone repository
git clone $MC_PROJECT_URL /app git clone $CUBBI_PROJECT_URL /app
cd /app cd /app
# Run project-specific initialization if present # Run project-specific initialization if present
if [ -f "/app/.mc/init.sh" ]; then if [ -f "/app/.cubbi/init.sh" ]; then
bash /app/.mc/init.sh bash /app/.cubbi/init.sh
fi fi
fi fi
# Image-specific initialization continues... # Image-specific initialization continues...
``` ```
### Image Configuration (mc-image.yaml) ### Image Configuration (cubbi-image.yaml)
```yaml ```yaml
name: goose name: goose
@@ -509,7 +509,7 @@ version: 1.0.0
maintainer: team@monadical.com maintainer: team@monadical.com
init: init:
pre_command: /mc-init.sh pre_command: /cubbi-init.sh
command: /entrypoint.sh command: /entrypoint.sh
environment: environment:
@@ -523,21 +523,21 @@ environment:
required: false required: false
# Project environment variables # Project environment variables
- name: MC_PROJECT_URL - name: CUBBI_PROJECT_URL
description: Project repository URL description: Project repository URL
required: false required: false
- name: MC_PROJECT_TYPE - name: CUBBI_PROJECT_TYPE
description: Project repository type (git, svn, etc.) description: Project repository type (git, svn, etc.)
required: false required: false
default: git default: git
- name: MC_GIT_SSH_KEY - name: CUBBI_GIT_SSH_KEY
description: SSH key for Git authentication description: SSH key for Git authentication
required: false required: false
sensitive: true sensitive: true
- name: MC_GIT_TOKEN - name: CUBBI_GIT_TOKEN
description: Token for Git authentication description: Token for Git authentication
required: false required: false
sensitive: true sensitive: true
@@ -552,7 +552,7 @@ volumes:
persistent_configs: persistent_configs:
- source: "/app/.goose" - source: "/app/.goose"
target: "/mc-config/goose" target: "/cubbi-config/goose"
type: "directory" type: "directory"
description: "Goose memory and configuration" description: "Goose memory and configuration"
``` ```
@@ -568,32 +568,32 @@ persistent_configs:
### Docker Network Integration ### Docker Network Integration
MC provides flexible network management for containers: Cubbi provides flexible network management for containers:
1. **Default MC Network**: 1. **Default Cubbi Network**:
- Each container is automatically connected to the MC network (`mc-network` by default) - Each container is automatically connected to the Cubbi network (`cubbi-network` by default)
- This ensures containers can communicate with each other - This ensures containers can communicate with each other
2. **External Network Connection**: 2. **External Network Connection**:
- Containers can be connected to one or more external Docker networks - Containers can be connected to one or more external Docker networks
- This allows integration with existing infrastructure (e.g., databases, web servers) - This allows integration with existing infrastructure (e.g., databases, web servers)
- Networks can be specified at session creation time: `mc session create --network mynetwork` - Networks can be specified at session creation time: `cubbi session create --network mynetwork`
3. **Default Networks Configuration**: 3. **Default Networks Configuration**:
- Users can configure default networks in their configuration - Users can configure default networks in their configuration
- These networks will be used for all new sessions unless overridden - These networks will be used for all new sessions unless overridden
- Managed with `mc config network` commands - Managed with `cubbi config network` commands
4. **Network Command Examples**: 4. **Network Command Examples**:
```bash ```bash
# Use with session creation # Use with session creation
mc session create --network teamnet cubbi session create --network teamnet
# Use with multiple networks # Use with multiple networks
mc session create --network teamnet --network dbnet cubbi session create --network teamnet --network dbnet
# Configure default networks # Configure default networks
mc config network add teamnet cubbi config network add teamnet
``` ```
## Security Considerations ## Security Considerations
@@ -606,15 +606,15 @@ MC provides flexible network management for containers:
## Deployment ## Deployment
### MC Service Deployment ### Cubbi Service Deployment
```yaml ```yaml
# docker-compose.yml for MC Service # docker-compose.yml for Cubbi Service
version: '3.8' version: '3.8'
services: services:
mc-service: cubbi-service:
image: monadical/mc-service:latest image: monadical/cubbi-service:latest
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./config:/app/config - ./config:/app/config
@@ -624,10 +624,10 @@ services:
- AUTH_URL=https://authentik.monadical.io - AUTH_URL=https://authentik.monadical.io
- LANGFUSE_API_KEY=your_api_key - LANGFUSE_API_KEY=your_api_key
networks: networks:
- mc-network - cubbi-network
networks: networks:
mc-network: cubbi-network:
driver: bridge driver: bridge
``` ```
@@ -637,33 +637,33 @@ networks:
1. User adds project repository with authentication: 1. User adds project repository with authentication:
```bash ```bash
mc project add github.com/hello/private --ssh-key ~/.ssh/id_ed25519 cubbi project add github.com/hello/private --ssh-key ~/.ssh/id_ed25519
``` ```
2. MC CLI reads the SSH key, encrypts it, and sends to MC Service 2. Cubbi CLI reads the SSH key, encrypts it, and sends to Cubbi Service
3. MC Service stores the project configuration securely 3. Cubbi Service stores the project configuration securely
### Using a Project in a Session ### Using a Project in a Session
1. User creates a session with a project: 1. User creates a session with a project:
```bash ```bash
mc -r monadical git@github.com:hello/private cubbi -r monadical git@github.com:hello/private
``` ```
2. MC Service: 2. Cubbi Service:
- Identifies the project from the URL - Identifies the project from the URL
- Retrieves project authentication details - Retrieves project authentication details
- Sets up environment variables: - Sets up environment variables:
``` ```
MC_PROJECT_URL=git@github.com:hello/private CUBBI_PROJECT_URL=git@github.com:hello/private
MC_PROJECT_TYPE=git CUBBI_PROJECT_TYPE=git
MC_GIT_SSH_KEY=<contents of the SSH key> CUBBI_GIT_SSH_KEY=<contents of the SSH key>
``` ```
- Creates container with these environment variables - Creates container with these environment variables
3. Container initialization: 3. Container initialization:
- The standardized `mc-init.sh` script detects the project environment variables - The standardized `cubbi-init.sh` script detects the project environment variables
- Sets up SSH key or token authentication - Sets up SSH key or token authentication
- Clones the repository to `/app` - Clones the repository to `/app`
- Runs any project-specific initialization scripts - Runs any project-specific initialization scripts
@@ -673,7 +673,7 @@ networks:
## Implementation Roadmap ## Implementation Roadmap
1. **Phase 1**: Local CLI tool with Docker integration 1. **Phase 1**: Local CLI tool with Docker integration
2. **Phase 2**: MC Service REST API with basic container management 2. **Phase 2**: Cubbi Service REST API with basic container management
3. **Phase 3**: Authentication and secure connections 3. **Phase 3**: Authentication and secure connections
4. **Phase 4**: Project management functionality 4. **Phase 4**: Project management functionality
5. **Phase 5**: Image implementation (Goose, Aider, Claude Code) 5. **Phase 5**: Image implementation (Goose, Aider, Claude Code)

View File

@@ -2,7 +2,7 @@
## Overview ## Overview
This document specifies the implementation for Model Control Protocol (MCP) server support in the Monadical Container (MC) system. The MCP server feature allows users to connect, build, and manage external MCP servers that can be attached to MC sessions. This document specifies the implementation for Model Control Protocol (MCP) server support in the Cubbi system. The MCP server feature allows users to connect, build, and manage external MCP servers that can be attached to Cubbi sessions.
An MCP server is a service that can be accessed by a image (such as Goose or Claude Code) to extend the LLM's capabilities through tool calls. It can be either: An MCP server is a service that can be accessed by a image (such as Goose or Claude Code) to extend the LLM's capabilities through tool calls. It can be either:
- A local stdio-based MCP server running in a container (accessed via an SSE proxy) - A local stdio-based MCP server running in a container (accessed via an SSE proxy)
@@ -53,48 +53,48 @@ mcps:
### MCP Management ### MCP Management
``` ```
mc mcp list # List all configured MCP servers and their status cubbi mcp list # List all configured MCP servers and their status
mc mcp status <name> # Show detailed status of a specific MCP server cubbi mcp status <name> # Show detailed status of a specific MCP server
mc mcp start <name> # Start an MCP server container cubbi mcp start <name> # Start an MCP server container
mc mcp stop <name> # Stop and remove an MCP server container cubbi mcp stop <name> # Stop and remove an MCP server container
mc mcp restart <name> # Restart an MCP server container cubbi mcp restart <name> # Restart an MCP server container
mc mcp start --all # Start all MCP server containers cubbi mcp start --all # Start all MCP server containers
mc mcp stop --all # Stop and remove all MCP server containers cubbi 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 cubbi 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) cubbi 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 cubbi mcp inspector --detach # Run the inspector in detached mode
mc mcp inspector --stop # Stop the running inspector cubbi mcp inspector --stop # Stop the running inspector
mc mcp logs <name> # Show logs for an MCP server container cubbi mcp logs <name> # Show logs for an MCP server container
``` ```
### MCP Configuration ### MCP Configuration
``` ```
# Add a proxy-based MCP server (default) # Add a proxy-based MCP server (default)
mc mcp add <name> <base_image> [--command CMD] [--proxy-image IMG] [--sse-port PORT] [--sse-host HOST] [--allow-origin ORIGIN] [--env KEY=VALUE...] cubbi mcp add <name> <base_image> [--command CMD] [--proxy-image IMG] [--sse-port PORT] [--sse-host HOST] [--allow-origin ORIGIN] [--env KEY=VALUE...]
# Add a remote MCP server # Add a remote MCP server
mc mcp add-remote <name> <url> [--header KEY=VALUE...] cubbi mcp add-remote <name> <url> [--header KEY=VALUE...]
# Remove an MCP configuration # Remove an MCP configuration
mc mcp remove <name> cubbi mcp remove <name>
``` ```
### Session Integration ### Session Integration
``` ```
mc session create [--mcp <name>] # Create a session with an MCP server attached cubbi session create [--mcp <name>] # Create a session with an MCP server attached
``` ```
## Implementation Details ## Implementation Details
### MCP Container Management ### MCP Container Management
1. MCP containers will have their own dedicated Docker network (`mc-mcp-network`) 1. MCP containers will have their own dedicated Docker network (`cubbi-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 (`cubbi_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`) 5. Each MCP container will have a network alias matching its name without the prefix (e.g., `cubbi_mcp_github` will have the alias `github`)
6. Network aliases enable DNS-based service discovery between containers 6. Network aliases enable DNS-based service discovery between containers
### MCP Inspector ### MCP Inspector

View File

@@ -1,7 +1,7 @@
[project] [project]
name = "mcontainer" name = "cubbi"
version = "0.1.0" version = "0.1.0"
description = "Monadical Container Tool" description = "Cubbi Container Tool"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
@@ -24,8 +24,8 @@ dev = [
] ]
[project.scripts] [project.scripts]
mc = "mcontainer.cli:app" cubbi = "cubbi.cli:app"
mcx = "mcontainer.cli:session_create_entry_point" cubbix = "cubbi.cli:session_create_entry_point"
[tool.ruff] [tool.ruff]
line-length = 88 line-length = 88

View File

@@ -1,5 +1,5 @@
""" """
Common test fixtures for Monadical Container tests. Common test fixtures for Cubbi Container tests.
""" """
import uuid import uuid
@@ -9,11 +9,11 @@ import docker
from pathlib import Path from pathlib import Path
from unittest.mock import patch from unittest.mock import patch
from mcontainer.container import ContainerManager from cubbi.container import ContainerManager
from mcontainer.session import SessionManager from cubbi.session import SessionManager
from mcontainer.config import ConfigManager from cubbi.config import ConfigManager
from mcontainer.models import Session, SessionStatus from cubbi.models import Session, SessionStatus
from mcontainer.user_config import UserConfigManager from cubbi.user_config import UserConfigManager
# Check if Docker is available # Check if Docker is available
@@ -81,7 +81,7 @@ def isolated_config_manager():
@pytest.fixture @pytest.fixture
def mock_session_manager(): def mock_session_manager():
"""Mock the SessionManager class.""" """Mock the SessionManager class."""
with patch("mcontainer.cli.session_manager") as mock_manager: with patch("cubbi.cli.session_manager") as mock_manager:
yield mock_manager yield mock_manager
@@ -96,7 +96,7 @@ def mock_container_manager():
ports={"8080": "8080"}, ports={"8080": "8080"},
) )
with patch("mcontainer.cli.container_manager") as mock_manager: with patch("cubbi.cli.container_manager") as mock_manager:
# Set behaviors to avoid TypeErrors # Set behaviors to avoid TypeErrors
mock_manager.list_sessions.return_value = [] mock_manager.list_sessions.return_value = []
mock_manager.create_session.return_value = mock_session mock_manager.create_session.return_value = mock_session
@@ -149,7 +149,7 @@ def test_file_content(temp_dir):
@pytest.fixture @pytest.fixture
def test_network_name(): def test_network_name():
"""Generate a unique network name for testing.""" """Generate a unique network name for testing."""
return f"mc-test-network-{uuid.uuid4().hex[:8]}" return f"cubbi-test-network-{uuid.uuid4().hex[:8]}"
@pytest.fixture @pytest.fixture
@@ -175,5 +175,5 @@ def docker_test_network(test_network_name):
@pytest.fixture @pytest.fixture
def patched_config_manager(isolated_config): def patched_config_manager(isolated_config):
"""Patch the UserConfigManager in cli.py to use our isolated instance.""" """Patch the UserConfigManager in cli.py to use our isolated instance."""
with patch("mcontainer.cli.user_config", isolated_config): with patch("cubbi.cli.user_config", isolated_config):
yield isolated_config yield isolated_config

View File

@@ -1,6 +1,6 @@
from typer.testing import CliRunner from typer.testing import CliRunner
from mcontainer.cli import app from cubbi.cli import app
runner = CliRunner() runner = CliRunner()
@@ -9,7 +9,7 @@ def test_version() -> None:
"""Test version command""" """Test version command"""
result = runner.invoke(app, ["version"]) result = runner.invoke(app, ["version"])
assert result.exit_code == 0 assert result.exit_code == 0
assert "MC - Monadical Container Tool" in result.stdout assert "Cubbi - Cubbi Container Tool" in result.stdout
def test_session_list() -> None: def test_session_list() -> None:
@@ -25,4 +25,4 @@ def test_help() -> None:
result = runner.invoke(app, ["--help"]) result = runner.invoke(app, ["--help"])
assert result.exit_code == 0 assert result.exit_code == 0
assert "Usage" in result.stdout assert "Usage" in result.stdout
assert "Monadical Container Tool" in result.stdout assert "Cubbi Container Tool" in result.stdout

View File

@@ -2,11 +2,11 @@
Tests for the configuration management commands. Tests for the configuration management commands.
""" """
from mcontainer.cli import app from cubbi.cli import app
def test_config_list(cli_runner, patched_config_manager): def test_config_list(cli_runner, patched_config_manager):
"""Test the 'mc config list' command.""" """Test the 'cubbi config list' command."""
result = cli_runner.invoke(app, ["config", "list"]) result = cli_runner.invoke(app, ["config", "list"])
assert result.exit_code == 0 assert result.exit_code == 0
@@ -20,7 +20,7 @@ def test_config_list(cli_runner, patched_config_manager):
def test_config_get(cli_runner, patched_config_manager): def test_config_get(cli_runner, patched_config_manager):
"""Test the 'mc config get' command.""" """Test the 'cubbi config get' command."""
# Test getting an existing value # Test getting an existing value
result = cli_runner.invoke(app, ["config", "get", "defaults.image"]) result = cli_runner.invoke(app, ["config", "get", "defaults.image"])
@@ -36,7 +36,7 @@ def test_config_get(cli_runner, patched_config_manager):
def test_config_set(cli_runner, patched_config_manager): def test_config_set(cli_runner, patched_config_manager):
"""Test the 'mc config set' command.""" """Test the 'cubbi config set' command."""
# Test setting a string value # Test setting a string value
result = cli_runner.invoke(app, ["config", "set", "defaults.image", "claude"]) result = cli_runner.invoke(app, ["config", "set", "defaults.image", "claude"])
@@ -60,7 +60,7 @@ def test_config_set(cli_runner, patched_config_manager):
def test_volume_list_empty(cli_runner, patched_config_manager): def test_volume_list_empty(cli_runner, patched_config_manager):
"""Test the 'mc config volume list' command with no volumes.""" """Test the 'cubbi config volume list' command with no volumes."""
result = cli_runner.invoke(app, ["config", "volume", "list"]) result = cli_runner.invoke(app, ["config", "volume", "list"])
assert result.exit_code == 0 assert result.exit_code == 0
@@ -133,7 +133,7 @@ def test_volume_add_nonexistent_path(cli_runner, patched_config_manager, monkeyp
def test_network_list_empty(cli_runner, patched_config_manager): def test_network_list_empty(cli_runner, patched_config_manager):
"""Test the 'mc config network list' command with no networks.""" """Test the 'cubbi config network list' command with no networks."""
result = cli_runner.invoke(app, ["config", "network", "list"]) result = cli_runner.invoke(app, ["config", "network", "list"])
assert result.exit_code == 0 assert result.exit_code == 0

View File

@@ -1,5 +1,5 @@
""" """
Integration tests for Docker interactions in Monadical Container. Integration tests for Docker interactions in Cubbi Container.
These tests require Docker to be running. These tests require Docker to be running.
""" """
@@ -31,7 +31,7 @@ def test_integration_session_create_with_volumes(container_manager, test_file_co
# Create a session with a volume mount # Create a session with a volume mount
session = container_manager.create_session( session = container_manager.create_session(
image_name="goose", image_name="goose",
session_name=f"mc-test-volume-{uuid.uuid4().hex[:8]}", session_name=f"cubbi-test-volume-{uuid.uuid4().hex[:8]}",
mount_local=False, # Don't mount current directory mount_local=False, # Don't mount current directory
volumes={str(test_file): {"bind": "/test/volume_test.txt", "mode": "ro"}}, volumes={str(test_file): {"bind": "/test/volume_test.txt", "mode": "ro"}},
) )
@@ -66,7 +66,7 @@ def test_integration_session_create_with_networks(
# Create a session with the test network # Create a session with the test network
session = container_manager.create_session( session = container_manager.create_session(
image_name="goose", image_name="goose",
session_name=f"mc-test-network-{uuid.uuid4().hex[:8]}", session_name=f"cubbi-test-network-{uuid.uuid4().hex[:8]}",
mount_local=False, # Don't mount current directory mount_local=False, # Don't mount current directory
networks=[docker_test_network], networks=[docker_test_network],
) )
@@ -85,7 +85,7 @@ def test_integration_session_create_with_networks(
container = client.containers.get(session.container_id) container = client.containers.get(session.container_id)
container_networks = container.attrs["NetworkSettings"]["Networks"] container_networks = container.attrs["NetworkSettings"]["Networks"]
# Container should be connected to both the default mc-network and our test network # Container should be connected to both the default cubbi-network and our test network
assert docker_test_network in container_networks assert docker_test_network in container_networks
# Verify network interface exists in container # Verify network interface exists in container
@@ -93,7 +93,7 @@ def test_integration_session_create_with_networks(
session.container_id, "ip link show | grep -v 'lo' | wc -l" session.container_id, "ip link show | grep -v 'lo' | wc -l"
) )
# Should have at least 2 interfaces (eth0 for mc-network, eth1 for test network) # Should have at least 2 interfaces (eth0 for cubbi-network, eth1 for test network)
assert int(network_interfaces) >= 2 assert int(network_interfaces) >= 2
finally: finally:

View File

@@ -4,15 +4,15 @@ Tests for the MCP server management commands.
import pytest import pytest
from unittest.mock import patch from unittest.mock import patch
from mcontainer.cli import app from cubbi.cli import app
def test_mcp_list_empty(cli_runner, patched_config_manager): def test_mcp_list_empty(cli_runner, patched_config_manager):
"""Test the 'mc mcp list' command with no MCPs configured.""" """Test the 'cubbi mcp list' command with no MCPs configured."""
# Make sure mcps is empty # Make sure mcps is empty
patched_config_manager.set("mcps", []) patched_config_manager.set("mcps", [])
with patch("mcontainer.cli.mcp_manager.list_mcps") as mock_list_mcps: with patch("cubbi.cli.mcp_manager.list_mcps") as mock_list_mcps:
mock_list_mcps.return_value = [] mock_list_mcps.return_value = []
result = cli_runner.invoke(app, ["mcp", "list"]) result = cli_runner.invoke(app, ["mcp", "list"])
@@ -94,7 +94,7 @@ def test_mcp_remove(cli_runner, patched_config_manager):
) )
# Mock the get_mcp and remove_mcp methods # Mock the get_mcp and remove_mcp methods
with patch("mcontainer.cli.mcp_manager.get_mcp") as mock_get_mcp: with patch("cubbi.cli.mcp_manager.get_mcp") as mock_get_mcp:
# First make get_mcp return our MCP # First make get_mcp return our MCP
mock_get_mcp.return_value = { mock_get_mcp.return_value = {
"name": "test-mcp", "name": "test-mcp",
@@ -128,7 +128,7 @@ def test_mcp_status(cli_runner, patched_config_manager, mock_container_manager):
) )
# First mock get_mcp to return our MCP config # First mock get_mcp to return our MCP config
with patch("mcontainer.cli.mcp_manager.get_mcp") as mock_get_mcp: with patch("cubbi.cli.mcp_manager.get_mcp") as mock_get_mcp:
mock_get_mcp.return_value = { mock_get_mcp.return_value = {
"name": "test-docker-mcp", "name": "test-docker-mcp",
"type": "docker", "type": "docker",
@@ -138,7 +138,7 @@ def test_mcp_status(cli_runner, patched_config_manager, mock_container_manager):
} }
# Then mock the get_mcp_status method # Then mock the get_mcp_status method
with patch("mcontainer.cli.mcp_manager.get_mcp_status") as mock_get_status: with patch("cubbi.cli.mcp_manager.get_mcp_status") as mock_get_status:
mock_get_status.return_value = { mock_get_status.return_value = {
"status": "running", "status": "running",
"container_id": "test-container-id", "container_id": "test-container-id",
@@ -262,7 +262,7 @@ def test_mcp_logs(cli_runner, patched_config_manager, mock_container_manager):
) )
# Mock the logs operation # Mock the logs operation
with patch("mcontainer.cli.mcp_manager.get_mcp_logs") as mock_get_logs: with patch("cubbi.cli.mcp_manager.get_mcp_logs") as mock_get_logs:
mock_get_logs.return_value = "Test log output" mock_get_logs.return_value = "Test log output"
# View MCP logs # View MCP logs
@@ -288,7 +288,7 @@ def test_session_with_mcp(cli_runner, patched_config_manager, mock_container_man
) )
# Mock the session creation with MCP # Mock the session creation with MCP
from mcontainer.models import Session, SessionStatus from cubbi.models import Session, SessionStatus
# timestamp no longer needed since we don't use created_at in Session # timestamp no longer needed since we don't use created_at in Session
mock_container_manager.create_session.return_value = Session( mock_container_manager.create_session.return_value = Session(

View File

@@ -6,7 +6,7 @@ import time
import uuid import uuid
from conftest import requires_docker from conftest import requires_docker
from mcontainer.mcp import MCPManager from cubbi.mcp import MCPManager
@requires_docker @requires_docker

View File

@@ -5,11 +5,11 @@ Tests for the session management commands.
from unittest.mock import patch from unittest.mock import patch
from mcontainer.cli import app from cubbi.cli import app
def test_session_list_empty(cli_runner, mock_container_manager): def test_session_list_empty(cli_runner, mock_container_manager):
"""Test 'mc session list' with no active sessions.""" """Test 'cubbi session list' with no active sessions."""
mock_container_manager.list_sessions.return_value = [] mock_container_manager.list_sessions.return_value = []
result = cli_runner.invoke(app, ["session", "list"]) result = cli_runner.invoke(app, ["session", "list"])
@@ -19,9 +19,9 @@ def test_session_list_empty(cli_runner, mock_container_manager):
def test_session_list_with_sessions(cli_runner, mock_container_manager): def test_session_list_with_sessions(cli_runner, mock_container_manager):
"""Test 'mc session list' with active sessions.""" """Test 'cubbi session list' with active sessions."""
# Create a mock session and set list_sessions to return it # Create a mock session and set list_sessions to return it
from mcontainer.models import Session, SessionStatus from cubbi.models import Session, SessionStatus
mock_session = Session( mock_session = Session(
id="test-session-id", id="test-session-id",
@@ -40,9 +40,9 @@ def test_session_list_with_sessions(cli_runner, mock_container_manager):
def test_session_create_basic(cli_runner, mock_container_manager): def test_session_create_basic(cli_runner, mock_container_manager):
"""Test 'mc session create' with basic options.""" """Test 'cubbi session create' with basic options."""
# We need to patch user_config.get with a side_effect to handle different keys # We need to patch user_config.get with a side_effect to handle different keys
with patch("mcontainer.cli.user_config") as mock_user_config: with patch("cubbi.cli.user_config") as mock_user_config:
# Handle different key requests appropriately # Handle different key requests appropriately
def mock_get_side_effect(key, default=None): def mock_get_side_effect(key, default=None):
if key == "defaults.image": if key == "defaults.image":
@@ -76,7 +76,7 @@ def test_session_create_basic(cli_runner, mock_container_manager):
def test_session_close(cli_runner, mock_container_manager): def test_session_close(cli_runner, mock_container_manager):
"""Test 'mc session close' command.""" """Test 'cubbi session close' command."""
mock_container_manager.close_session.return_value = True mock_container_manager.close_session.return_value = True
result = cli_runner.invoke(app, ["session", "close", "test-session-id"]) result = cli_runner.invoke(app, ["session", "close", "test-session-id"])
@@ -87,9 +87,9 @@ def test_session_close(cli_runner, mock_container_manager):
def test_session_close_all(cli_runner, mock_container_manager): def test_session_close_all(cli_runner, mock_container_manager):
"""Test 'mc session close --all' command.""" """Test 'cubbi session close --all' command."""
# Set up mock sessions # Set up mock sessions
from mcontainer.models import Session, SessionStatus from cubbi.models import Session, SessionStatus
# timestamp no longer needed since we don't use created_at in Session # timestamp no longer needed since we don't use created_at in Session
mock_sessions = [ mock_sessions = [

78
uv.lock generated
View File

@@ -75,6 +75,45 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
] ]
[[package]]
name = "cubbi"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "docker" },
{ name = "pydantic" },
{ name = "pyyaml" },
{ name = "rich" },
{ name = "typer" },
]
[package.optional-dependencies]
dev = [
{ name = "mypy" },
{ name = "pytest" },
{ name = "ruff" },
]
[package.dev-dependencies]
dev = [
{ name = "pytest" },
]
[package.metadata]
requires-dist = [
{ name = "docker", specifier = ">=7.0.0" },
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.7.0" },
{ name = "pydantic", specifier = ">=2.5.0" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" },
{ name = "pyyaml", specifier = ">=6.0.1" },
{ name = "rich", specifier = ">=13.6.0" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.9" },
{ name = "typer", specifier = ">=0.9.0" },
]
[package.metadata.requires-dev]
dev = [{ name = "pytest", specifier = ">=8.3.5" }]
[[package]] [[package]]
name = "docker" name = "docker"
version = "7.1.0" version = "7.1.0"
@@ -119,45 +158,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
] ]
[[package]]
name = "mcontainer"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "docker" },
{ name = "pydantic" },
{ name = "pyyaml" },
{ name = "rich" },
{ name = "typer" },
]
[package.optional-dependencies]
dev = [
{ name = "mypy" },
{ name = "pytest" },
{ name = "ruff" },
]
[package.dev-dependencies]
dev = [
{ name = "pytest" },
]
[package.metadata]
requires-dist = [
{ name = "docker", specifier = ">=7.0.0" },
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.7.0" },
{ name = "pydantic", specifier = ">=2.5.0" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" },
{ name = "pyyaml", specifier = ">=6.0.1" },
{ name = "rich", specifier = ">=13.6.0" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.9" },
{ name = "typer", specifier = ">=0.9.0" },
]
[package.metadata.requires-dev]
dev = [{ name = "pytest", specifier = ">=8.3.5" }]
[[package]] [[package]]
name = "mdurl" name = "mdurl"
version = "0.1.2" version = "0.1.2"