# MCP-Forge Architecture Document ## Overview MCP-Forge is an MCP (Model Context Protocol) server that provides intelligent code execution capabilities for AI agents. It enables agents to execute Python code in isolated containers while having access to other MCP tools, optimizing data processing by keeping large datasets in the execution environment rather than passing them through the LLM context. ## Core Concept **Problem:** When AI agents use MCP tools that return large amounts of data, the data must flow through the LLM context window, which is: - Token-expensive - Slow - Limited by context window size **Solution:** MCP-Forge provides a code execution environment where: - MCP tools are available as Python functions - Agents can write code to process data locally - Only relevant results flow back to the agent - The agent already knows tool signatures from its own configuration ## Architecture ``` AI Agent (e.g., GitHub Copilot, Claude) │ ├─ MCP Protocol │ MCP-Forge Server │ ├─ MCP Resources (Discovery) │ ├─ tools/available │ ├─ sessions/{id}/state │ ├─ sessions/{id}/variables │ └─ environments/list │ ├─ MCP Tools (Execution) │ ├─ execute_python() │ ├─ document_state() │ └─ build_custom_environment() │ ├─ Execution Backends │ ├─ Simple Backend (stateless) │ └─ Jupyter Backend (stateful) │ ├─ Custom Environment Builder │ ├─ UV Package Installer │ ├─ Build Cache Manager │ ├─ Security Validator │ └─ Template Library │ └─ Podman API (restricted) │ └─ Isolated Execution Containers ├─ Python Runtime ├─ MCP Client (injected tools) └─ Session Volumes ``` ## Components ### 1. MCP Server Interface #### Resources **`mcp://forge/tools/available`** - Returns: List of MCP tool names available in execution environment - Purpose: Agent discovers what tools it can use in generated code - Example: `["github_search_repos", "filesystem_read", "sqlite_query"]` **`mcp://forge/tools/{tool_name}/schema`** (optional) - Returns: Full JSON schema for a specific tool - Purpose: Fallback if agent doesn't have tool definition - Note: Typically not needed as agent has tools in its own config **`mcp://forge/sessions/{session_id}/state`** - Returns: Documented state for a session - Structure: ```json { "documented_variables": { "df": "Customer data, 1000 rows, columns: id, name, purchase_date, amount", "model": "Trained RandomForest, accuracy 0.87" }, "note": "Preprocessing complete, ready for analysis", "last_updated": "2026-02-06T10:30:00Z", "all_variables": ["df", "model", "temp", "result"] } ``` **`mcp://forge/sessions/{session_id}/variables`** - Returns: List of all variables in session namespace (kernel introspection) - Purpose: Quick check of what exists in session **`mcp://forge/environment/info`** (optional) - Returns: Python version, installed packages, system info - Purpose: Agent can verify environment capabilities **`mcp://forge/environments/list`** - Returns: List of available custom environments and templates - Structure: ```json { "custom": [ { "name": "my-ml-env", "image": "mcp-forge/custom:my-ml-env", "description": "Custom ML environment", "packages": ["numpy==1.24.0", "pandas==2.0.0", "scikit-learn==1.3.0"], "created_at": "2026-02-06T10:00:00Z", "size_mb": 1024 } ], "templates": [ { "name": "ml-basic", "description": "Basic ML stack (numpy, pandas, scikit-learn)", "packages": ["numpy", "pandas", "scikit-learn"] }, { "name": "data-science", "description": "Full data science stack", "packages": ["numpy", "pandas", "matplotlib", "seaborn", "jupyter"] } ] } ``` **`mcp://forge/audit/operations`** (optional) - Returns: Audit log of container operations - Purpose: Security monitoring, debugging #### Tools **`execute_python`** Execute Python code in an isolated container with MCP tools available. Parameters: ```json { "code": "string (required) - Python code to execute", "mcp_tools": "array (optional) - List of MCP tool names to inject", "session_id": "string (optional) - Session ID for stateful execution (null = stateless)", "backend": "string (optional) - 'simple' (default) or 'jupyter'", "timeout": "integer (optional) - Max execution time in seconds (default: 300)", "volumes": "object (optional) - Volume mount configuration", "custom_image": "string (optional) - Custom environment name to use", "environment": "string (optional) - Template environment name (e.g., 'ml-basic', 'data-science')" } ``` Returns: ```json { "success": true, "stdout": "string - Standard output", "stderr": "string - Standard error", "result": "any - Return value of last expression", "available_tools": ["list of tools that were injected"], "execution_time": 1.23, "session_id": "abc123 (if stateful)" } ``` Example: ```python # Agent calls: execute_python( code=""" repos = github_search_repos(query="MCP servers", max_results=100) high_quality = [r for r in repos['items'] if r['stars'] > 50] print(f"Found {len(high_quality)} high-quality repos") high_quality[:10] # Return top 10 """, mcp_tools=["github_search_repos"], session_id=None # stateless ) ``` **`document_state`** Document important variables in a stateful session for later retrieval. Parameters: ```json { "session_id": "string (required) - Session to document", "variables": "object (required) - Variable name -> description mapping", "note": "string (optional) - General note about session state", "clear": "boolean (optional) - Clear existing documentation (default: false)" } ``` Returns: ```json { "success": true, "documented_count": 3, "session_id": "abc123" } ``` Example: ```python document_state( session_id="abc123", variables={ "df": "Customer purchase data, 1000 rows, preprocessed and cleaned", "model": "Trained RandomForest classifier, 87% accuracy on test set" }, note="Ready for prediction phase" ) ``` **`build_custom_environment`** Build a custom container image with specified Python packages. This is the **only** way to install additional packages - `pip install` is **not** allowed during code execution for security reasons. Parameters: ```json { "name": "string (required) - Name for the custom environment (alphanumeric + hyphens)", "base_image": "string (optional) - Base image to build from (default: 'python:3.11')", "packages": "array (required) - List of Python package specifications", "python_version": "string (optional) - Python version: '3.11', '3.12' (default: '3.11')", "description": "string (optional) - Description of this environment" } ``` Returns: ```json { "success": true, "image_name": "mcp-forge/custom:my-ml-env", "image_id": "sha256:abc123...", "build_time": 45.2, "installed_packages": ["numpy==1.24.0", "pandas==2.0.0", "scikit-learn==1.3.0"], "cache_hit": false } ``` Example: ```python # Agent builds custom environment for ML work build_custom_environment( name="my-ml-env", packages=[ "numpy>=1.24.0", "pandas>=2.0.0", "scikit-learn>=1.3.0", "matplotlib>=3.7.0", "seaborn>=0.12.0" ], description="Machine learning environment with common libraries" ) # Later, use the custom environment execute_python( code="import pandas as pd; df = pd.read_csv('data.csv'); ...", custom_image="mcp-forge/custom:my-ml-env", session_id="ml-analysis" ) ``` ### 2. Execution Backends #### Simple Backend (Default) **Purpose:** Stateless, single-shot code execution **Implementation:** - Spawn Podman container with Python image - Inject code and MCP tool functions into namespace - Execute via `python -c` - Capture stdout, stderr, result - Destroy container **Characteristics:** - Fast startup - No state between calls - Ideal for data transformation tasks - Lower memory footprint **Use cases:** - Data processing and filtering - One-off computations - Format conversions - Quick analyses #### Jupyter Backend **Purpose:** Stateful, multi-step workflows **Implementation:** - Spawn Podman container with IPython kernel (one per session) - jupyter-client runs in MCP-Forge server (host), not in container - ipykernel runs inside container as kernel process - Keep kernel running for session lifetime - Execute code cells via Jupyter protocol (ZMQ) - Maintain namespace between executions - Support rich output formats **Architecture Flow:** ``` MCP-Forge Server (Host) │ ├─ jupyter-client (ZMQ client library) │ │ │ ├─ Manages connections to kernel containers │ └─ Communicates via ZMQ sockets (shell, iopub, stdin, control, heartbeat) │ ├─ Session "session-123" ──→ Container A ──→ ipykernel Process A ├─ Session "session-456" ──→ Container B ──→ ipykernel Process B └─ Session "session-789" ──→ Container C ──→ ipykernel Process C ``` **Session-to-Kernel Mapping (1:1):** - **One container per session** - complete isolation - **One kernel process per container** - dedicated resources - **Separate Python namespaces** - no variable sharing between sessions - **Independent resource limits** - each session has own CPU/memory quota - **Strong security boundary** - container escape affects only one session **File Sharing Between Sessions:** Sessions can share files (not variables) via shared volumes: ```python # Session 1: Write data execute_python( code="df.to_parquet('/shared/data.parquet')", session_id="session-123", volumes={"/shared": {"bind": "/mcp-forge/projects/abc", "mode": "rw"}} ) # Session 2: Read data (different container, different namespace) execute_python( code="df = pd.read_parquet('/shared/data.parquet')", session_id="session-456", volumes={"/shared": {"bind": "/mcp-forge/projects/abc", "mode": "ro"}} ) ``` **Characteristics:** - State persists between calls within same session - Variable persistence in session namespace - Interactive workflow support - Higher resource usage per session - Full isolation between sessions **Use cases:** - Multi-step data analysis - Iterative model training - Building up complex state - Exploratory workflows **Session Management:** - Sessions identified by unique ID - Each session gets dedicated container and kernel - Automatic timeout after inactivity (configurable, default: 1 hour) - Manual cleanup via session deletion - Resource limits enforced per container/session - Clean lifecycle: destroy container = destroy session ### 3. Custom Environment Building #### Design Philosophy **Security Principle:** `pip install` is **NOT** allowed during code execution. All package installations must happen during a controlled build process. **Why:** - Prevents malicious package installation during execution - Ensures reproducible environments - Allows security scanning of dependencies - Enables caching for performance - Provides audit trail of what's installed #### Build System: UV-Based **Technology Choice:** [UV](https://github.com/astral-sh/uv) - ultra-fast Python package installer **Benefits:** - 10-100x faster than pip - Built-in caching - Deterministic dependency resolution - Lockfile support for reproducibility - Compatible with pip package specifications #### Implementation **Build Process:** ```python def build_custom_environment(name, packages, base_image="python:3.11"): """ Build a custom container image with specified packages using UV. Steps: 1. Generate Containerfile with UV installation 2. Create requirements specification 3. Build image with layer caching 4. Validate and tag image 5. Store metadata for reuse """ # Generate Containerfile containerfile = f""" FROM {base_image} # Install UV (cached layer) RUN curl -LsSf https://astral.sh/uv/install.sh | sh ENV PATH="/root/.cargo/bin:$PATH" # Non-root user RUN useradd -m -u 1000 forge USER forge # Copy requirements (cache-friendly) COPY requirements.txt /tmp/requirements.txt # Install packages with UV (uses cache) RUN uv pip install --system -r /tmp/requirements.txt # MCP bridge client COPY mcp_tools.py /usr/local/lib/python3.11/site-packages/ WORKDIR /workspace """ # Build with Podman build_context = create_build_context(containerfile, packages) image_id = podman.images.build( path=build_context, tag=f"mcp-forge/custom:{name}", cache_from=[f"mcp-forge/custom:{name}"], buildargs={"UV_CACHE_DIR": "/var/cache/mcp-forge/uv"} ) return image_id ``` #### Package Cache Strategy **Multi-Layer Caching:** ``` Layer 1: Base Python image (rarely changes) └─ python:3.11-slim Layer 2: UV installation (stable) └─ UV binary + dependencies Layer 3: Common packages (pre-cached) └─ numpy, pandas, requests, etc. Layer 4: User packages (specific to build) └─ Custom requirements ``` **UV Cache Directory:** - Mounted from host: `/var/cache/mcp-forge/uv` - Persisted between builds - Dramatically speeds up builds with similar dependencies **Cache Invalidation:** - Requirements hash changes → rebuild user layer only - Base image updated → rebuild from base - UV version updated → rebuild from UV layer #### Pre-built Environment Templates **Common Templates:** ```yaml templates: ml-basic: description: "Basic ML stack" packages: - numpy>=1.24.0 - pandas>=2.0.0 - scikit-learn>=1.3.0 ml-advanced: description: "Advanced ML with deep learning" packages: - numpy>=1.24.0 - pandas>=2.0.0 - scikit-learn>=1.3.0 - tensorflow>=2.13.0 - torch>=2.0.0 data-science: description: "Data science stack" packages: - numpy>=1.24.0 - pandas>=2.0.0 - matplotlib>=3.7.0 - seaborn>=0.12.0 - jupyter>=1.0.0 web-scraping: description: "Web scraping tools" packages: - requests>=2.31.0 - beautifulsoup4>=4.12.0 - lxml>=4.9.0 - selenium>=4.11.0 ``` **Usage:** ```python # Use a template build_custom_environment( name="my-ml-env", template="ml-basic", additional_packages=["xgboost>=2.0.0"] ) # Or build from scratch build_custom_environment( name="custom-env", packages=["specific-package==1.0.0"] ) ``` #### Build Validation **Post-Build Checks:** ```python def validate_built_environment(image_id): """ Validate that the built environment is safe and functional. Checks: 1. All requested packages are installed 2. No malicious packages (against allowlist/blocklist) 3. Image size is within limits 4. Security scan passes (trivy/grype) 5. Python imports work """ # Import test test_code = """ import sys import json installed = [(pkg.key, pkg.version) for pkg in __import__('pkg_resources').working_set] print(json.dumps(installed)) """ result = run_in_container(image_id, test_code) installed_packages = json.loads(result.stdout) # Security scan scan_result = security_scan_image(image_id) if scan_result.critical_vulns > 0: raise SecurityError(f"Image has {scan_result.critical_vulns} critical vulnerabilities") return { "installed_packages": installed_packages, "security_scan": scan_result, "valid": True } ``` #### Package Allowlist/Blocklist **Security Control:** ```yaml packages: # Automatically allowed (common, vetted) allowlist: - numpy - pandas - scikit-learn - matplotlib - seaborn - requests # ... many more # Explicitly forbidden blocklist: - "os-sys" # Known malicious - "malicious-package" # Packages with known vulnerabilities # Requires manual approval manual_approval: - "*crypto*" # Cryptographic packages - "*ssh*" # SSH-related - "*subprocess*" # Process spawning ``` **Validation:** ```python def validate_package_list(packages): """Check packages against allowlist/blocklist before building.""" for pkg in packages: pkg_name = pkg.split("==")[0].split(">=")[0].split("<=")[0] if pkg_name in BLOCKLIST: raise SecurityError(f"Package {pkg_name} is blocked") if not in_allowlist(pkg_name) and needs_approval(pkg_name): raise ApprovalRequiredError(f"Package {pkg_name} requires manual approval") ``` #### Build Resource Limits **Prevent Build Abuse:** ```python BUILD_LIMITS = { "max_packages": 50, # Max packages per build "max_build_time": 600, # 10 minutes "max_image_size": "2GB", # Final image size "max_concurrent_builds": 3, # Per user "build_rate_limit": { "requests": 10, "period": 3600 # 10 builds per hour } } ``` #### Environment Lifecycle **Management:** ```python # List user's custom environments list_custom_environments() → [ { "name": "my-ml-env", "image": "mcp-forge/custom:my-ml-env", "created_at": "2026-02-06T10:00:00Z", "size_mb": 1024, "last_used": "2026-02-06T11:30:00Z" } ] # Delete unused environment delete_custom_environment(name="old-env") # Rebuild environment (e.g., after base image update) rebuild_custom_environment(name="my-ml-env") ``` **Auto-cleanup:** - Environments unused for 30 days → archived - Archived for 90 days → deleted - User notification before deletion #### execute_python Integration **Using Custom Environments:** ```python # Updated execute_python parameters { "code": "...", "custom_image": "string (optional) - Custom environment name or image tag", "environment": "string (optional) - Template name (alternative to custom_image)", ... } # Examples: execute_python( code="import numpy as np; ...", custom_image="my-ml-env" # Use user's custom environment ) execute_python( code="import pandas as pd; ...", environment="data-science" # Use pre-built template ) ``` ### 4. Container Runtime (Podman) #### Why Podman? - **Rootless by design:** No daemon running as root - **Docker-compatible API:** Easy integration - **Better security defaults:** No privileged operations needed - **Daemonless:** Containers are child processes #### Deployment Modes **Development:** ```bash # Run mcp-forge directly on host mcp-forge serve --podman-socket /run/user/1000/podman/podman.sock ``` **Production:** ```yaml # Run mcp-forge in container with socket mount services: mcp-forge: image: mcp-forge:latest volumes: - /run/user/1000/podman/podman.sock:/run/podman/podman.sock:ro environment: - PODMAN_SOCKET=/run/podman/podman.sock ``` #### Security: Allowlist-Based Container Operations **Allowed Operations:** ```python ALLOWED_OPERATIONS = { "container.create": { "allowed_images": [ "mcp-forge/python:3.11", "mcp-forge/python:3.12", "mcp-forge/jupyter:latest", "mcp-forge/custom:*" # User-built custom environments ], "forbidden_params": [ "privileged", "cap_add", "devices", "pid_mode", "ipc_mode" ], "required_params": { "network_mode": "none", # Or restricted network "read_only": True, # Filesystem read-only except volumes "memory_limit": "512m", "cpu_quota": 50000, # 50% of one CPU "security_opt": ["no-new-privileges"], "user": "1000:1000" # Non-root user } }, "container.start": { "session_containers_only": True # Only containers we created }, "container.stop": { "session_containers_only": True }, "container.remove": { "session_containers_only": True }, "container.logs": { "session_containers_only": True } } ``` **Forbidden Operations:** ```python FORBIDDEN_OPERATIONS = [ "container.exec", # No direct shell access # Note: image.build IS allowed, but ONLY through build_custom_environment tool # with validation, security scanning, and package allowlists "image.pull", # No arbitrary image pulling (only pre-approved images) "volume.create", # Only pre-configured volumes "network.create", # No custom networks "system.prune", # No system-level operations ] # Additionally forbidden within execution containers: EXECUTION_RESTRICTIONS = [ "subprocess.run(['pip', 'install', ...])", # No pip install during execution "subprocess.run(['apt', 'install', ...])", # No system package installs "import os; os.system('...')", # Restricted system calls ] ``` #### Volume Management **Allowed Volume Patterns:** ```python ALLOWED_VOLUME_PATTERNS = [ "/mcp-forge/sessions/{session_id}/*", # Per-session data "/mcp-forge/shared/readonly/*", # Shared read-only data "/mcp-forge/uploads/{session_id}/*" # User uploads ] FORBIDDEN_MOUNT_PATHS = [ "/", "/etc", "/var/run/docker.sock", "/var/run/podman/podman.sock", "/sys", "/proc" ] ``` #### Resource Limits (Enforced) ```python RESOURCE_LIMITS = { "memory": { "default": "512m", "max": "2g" }, "cpu_quota": { "default": 50000, # 50% of one CPU "max": 100000 # 100% of one CPU }, "timeout": { "default": 300, # 5 minutes "max": 1800 # 30 minutes }, "pids_limit": 100, "storage": { "default": "1g", "max": "10g" } } ``` ### 5. MCP Tool Injection #### Tool Discovery Flow 1. **Agent reads resource:** `mcp://forge/tools/available` 2. **MCP-Forge returns:** `["github_search_repos", "filesystem_read"]` 3. **Agent knows signatures:** Already configured in agent's tool config 4. **Agent generates code:** Using these tool names as Python functions #### Tool Injection Implementation **At container startup:** ```python # MCP-Forge generates injection code def inject_mcp_tools(container, mcp_client, tool_names): """ Inject MCP tools as Python functions into container namespace. Args: container: Podman container instance mcp_client: Connected MCP client with available tools tool_names: List of tool names to inject """ injection_code = """ # MCP Tool Functions (auto-injected) import json from typing import Any def _mcp_call(tool_name: str, **kwargs) -> Any: '''Internal: Call MCP tool via bridge''' # This communicates with MCP-Forge server which forwards to MCP client import socket s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect('/tmp/mcp-bridge.sock') s.send(json.dumps({'tool': tool_name, 'params': kwargs}).encode()) result = json.loads(s.recv(1024*1024).decode()) s.close() return result """ # Generate wrapper function for each tool for tool_name in tool_names: tool_schema = mcp_client.get_tool_schema(tool_name) # Generate function signature from schema params = tool_schema.get('inputSchema', {}).get('properties', {}) param_str = ', '.join(f"{k}: Any = None" for k in params.keys()) injection_code += f""" def {tool_name}({param_str}): ''' {tool_schema.get('description', '')} Auto-generated wrapper for MCP tool: {tool_name} ''' kwargs = {{{', '.join(f"'{k}': {k}" for k in params.keys())}}} kwargs = {{k: v for k, v in kwargs.items() if v is not None}} return _mcp_call('{tool_name}', **kwargs) """ # Write to container's Python site-packages or PYTHONSTARTUP container.write_file('/usr/local/lib/python3.11/site-packages/mcp_tools.py', injection_code) # Start MCP bridge server (forwards calls to actual MCP client) start_mcp_bridge(container, mcp_client) ``` **Alternative: Environment Variable Approach** ```python # Set PYTHONSTARTUP to auto-import tools container.env['PYTHONSTARTUP'] = '/mcp-forge/startup.py' ``` #### MCP Bridge Server A lightweight server running in the MCP-Forge process that: - Listens on Unix socket mounted into container - Receives tool calls from container code - Forwards to actual MCP client - Returns results to container This keeps the MCP client logic outside the untrusted container. ### 6. Session State Management #### State Lifecycle **Stateless Execution (session_id=None):** ``` Request → Create Container → Execute → Return Results → Destroy Container ``` **Stateful Execution (session_id="abc123"):** ``` First Request: Request → Create Container → Start Kernel → Execute → Return Results Container stays alive Subsequent Requests: Request → Reuse Container → Execute in Same Kernel → Return Results Cleanup: Timeout or Manual → Stop Container → Destroy Container ``` #### State Documentation **Agent Workflow:** ```python # Step 1: Load and process data execute_python( code="df = load_large_dataset(); df_clean = preprocess(df)", session_id="analysis-123", backend="jupyter" ) # Step 2: Document important state document_state( session_id="analysis-123", variables={ "df": "Raw dataset, 100k rows", "df_clean": "Cleaned dataset, 95k rows, ready for modeling" }, note="Data loading complete" ) # ... conversation continues, context window rotates ... # Step 3: Agent checks state after context rotation state = read_resource("mcp://forge/sessions/analysis-123/state") # Agent sees: df_clean exists and is ready # Step 4: Continue work execute_python( code="model = train_model(df_clean); results = evaluate(model)", session_id="analysis-123" ) ``` #### State Introspection **Automatic tracking:** - Kernel namespace inspection (all variables) - Variable types and sizes - Last execution timestamp **Agent-documented:** - Semantic descriptions - Workflow notes - Important variables highlighted **Combined in resource:** ```json { "all_variables": ["df", "df_clean", "model", "temp", "i", "results"], "documented_variables": { "df_clean": "Cleaned dataset, 95k rows, ready for modeling", "model": "Trained model, 87% accuracy", "results": "Evaluation metrics" }, "introspection": { "df_clean": {"type": "DataFrame", "shape": [95000, 12], "memory_mb": 87}, "model": {"type": "RandomForestClassifier", "memory_mb": 234} }, "note": "Ready for final predictions", "last_updated": "2026-02-06T10:30:00Z" } ``` ## Implementation Considerations ### Technology Stack **MCP Server:** - Language: Python 3.11+ - Framework: `mcp` SDK (official Python implementation) - Async: `asyncio` for concurrent operations **Container Runtime:** - Podman via `podman-py` library - Fallback: Direct Podman CLI calls **Jupyter Backend:** - `jupyter_client` for kernel management - ZMQ for communication - `ipykernel` in container images **Simple Backend:** - Direct Python execution via `subprocess` - Or: `python:3.11-slim` base image with `exec` entrypoint ### Pre-built Container Images **Base Image: `mcp-forge/python:3.11`** ```dockerfile FROM python:3.11-slim # Non-root user RUN useradd -m -u 1000 forge USER forge # Install common packages RUN pip install --user numpy pandas requests # MCP bridge client COPY mcp_tools.py /usr/local/lib/python3.11/site-packages/ # Startup script COPY startup.py /mcp-forge/startup.py WORKDIR /workspace ``` **Jupyter Image: `mcp-forge/jupyter:latest`** ```dockerfile FROM mcp-forge/python:3.11 USER root RUN pip install ipykernel jupyter_client USER forge # IPython config for security COPY ipython_config.py /home/forge/.ipython/profile_default/ CMD ["python", "-m", "ipykernel_launcher", "-f", "/tmp/kernel.json"] ``` ### Error Handling **Container Failures:** - Timeout: Kill container, return timeout error - OOM: Return memory limit error with suggestion to increase - Crash: Return stderr and exit code **MCP Tool Failures:** - Tool not available: Clear error message with available tools - Tool call error: Return tool error to agent, don't crash execution - Network error: Retry logic for transient failures **Security Violations:** - Forbidden operation: Reject immediately, log to audit - Resource limit exceeded: Terminate execution, clear error message - Invalid volume mount: Reject with explanation ### Performance Considerations **Container Reuse:** - Pool of warm containers for simple backend (optional) - Lazy cleanup of idle Jupyter sessions **Parallel Execution:** - Support multiple concurrent executions - Per-session locking for stateful operations - Configurable max concurrent containers **Image Caching:** - Pre-pull images on startup - Periodic image updates (configurable schedule) **Volume Performance:** - Use tmpfs for ephemeral data - Persistent volumes for session data - Cleanup strategy for old sessions ## Security Model ### Defense in Depth 1. **Podman rootless:** No root daemon 2. **Allowlist enforcement:** Only permitted operations 3. **Read-only filesystem:** Except specific volumes 4. **Network isolation:** No internet by default 5. **Resource limits:** CPU, memory, storage, PIDs 6. **No privileged mode:** Ever 7. **Capability dropping:** Minimal capabilities 8. **User namespaces:** Non-root user in container 9. **Audit logging:** All operations logged 10. **Session isolation:** Each session has isolated volumes ### Threat Model **Threats Mitigated:** - Container escape → Rootless + restricted operations - Resource exhaustion → Hard limits enforced - Data exfiltration → Network isolation - Privilege escalation → No privileged mode, capability restrictions - Host filesystem access → Allowlist volumes only **Out of Scope:** - Side-channel attacks (Spectre, Meltdown) - Physical security - Supply chain attacks on base images (use trusted registries) ### Audit and Monitoring **Logged Events:** - Container create/start/stop/remove - Execution requests (code hash, not full code for privacy) - Resource limit violations - Security policy violations - Session lifecycle events **Log Format:** ```json { "timestamp": "2026-02-06T10:30:00Z", "event": "container.create", "session_id": "abc123", "image": "mcp-forge/python:3.11", "resources": {"memory": "512m", "cpu_quota": 50000}, "success": true } ``` ## Configuration ### Server Configuration **Example `mcp-forge.yaml`:** ```yaml server: host: localhost port: 3000 podman_socket: /run/user/1000/podman/podman.sock execution: default_backend: simple default_timeout: 300 max_timeout: 1800 default_memory: 512m max_memory: 2g default_cpu_quota: 50000 max_cpu_quota: 100000 images: python_3_11: mcp-forge/python:3.11 python_3_12: mcp-forge/python:3.12 jupyter: mcp-forge/jupyter:latest auto_pull: true pull_interval: 86400 # 24 hours sessions: idle_timeout: 3600 # 1 hour max_concurrent: 10 cleanup_interval: 300 # 5 minutes volumes: base_path: /var/lib/mcp-forge session_quota: 1g max_session_quota: 10g security: audit_log: /var/log/mcp-forge/audit.log enforce_resource_limits: true allow_network: false environment_builder: enabled: true uv_cache_path: /var/cache/mcp-forge/uv max_packages: 50 max_build_time: 600 max_image_size: 2GB templates_path: /etc/mcp-forge/templates mcp_tools: # MCP servers to connect to and expose github: command: "npx" args: ["-y", "@modelcontextprotocol/server-github"] env: GITHUB_TOKEN: ${GITHUB_TOKEN} filesystem: command: "npx" args: ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"] ``` ### Environment Variables ```bash # Podman socket location MCP_FORGE_PODMAN_SOCKET=/run/user/1000/podman/podman.sock # Base path for volumes MCP_FORGE_VOLUMES_PATH=/var/lib/mcp-forge # UV cache for fast package installation MCP_FORGE_UV_CACHE_PATH=/var/cache/mcp-forge/uv # Security MCP_FORGE_AUDIT_LOG=/var/log/mcp-forge/audit.log # MCP tool credentials GITHUB_TOKEN=ghp_xxxxx ``` ## API Examples ### Example 1: Simple Data Processing ```python # Agent discovers available tools tools = read_resource("mcp://forge/tools/available") # Returns: ["github_search_repos", "filesystem_read"] # Agent generates and executes code result = execute_python( code=""" repos = github_search_repos(query="machine learning", max_results=100) python_repos = [r for r in repos['items'] if r['language'] == 'Python'] top_10 = sorted(python_repos, key=lambda x: x['stars'], reverse=True)[:10] # Return summary [{ 'name': r['name'], 'stars': r['stars'], 'url': r['html_url'] } for r in top_10] """, mcp_tools=["github_search_repos"], session_id=None ) print(result['result']) # Returns top 10 Python ML repos ``` ### Example 2: Building and Using Custom Environment ```python # Agent needs ML libraries for analysis # First, check available environments envs = read_resource("mcp://forge/environments/list") # Build custom environment if needed build_result = build_custom_environment( name="my-ml-analysis", packages=[ "numpy>=1.24.0", "pandas>=2.0.0", "scikit-learn>=1.3.0", "xgboost>=2.0.0", "matplotlib>=3.7.0", "seaborn>=0.12.0" ], description="Custom ML environment for analysis project" ) # Wait for build to complete (typically 30-60 seconds with UV cache) print(f"Built {build_result['image_name']} in {build_result['build_time']}s") print(f"Installed: {build_result['installed_packages']}") # Now use the custom environment for analysis result = execute_python( code=""" import pandas as pd import numpy as np from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split import xgboost as xgb # Load data (from volume or MCP tool) data = filesystem_read('/data/customer_churn.csv') df = pd.read_csv(data) # Preprocess X = df.drop('churn', axis=1) y = df['churn'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) # Train models rf_model = RandomForestClassifier(n_estimators=100) rf_model.fit(X_train, y_train) rf_score = rf_model.score(X_test, y_test) xgb_model = xgb.XGBClassifier() xgb_model.fit(X_train, y_train) xgb_score = xgb_model.score(X_test, y_test) # Return results { 'random_forest_accuracy': rf_score, 'xgboost_accuracy': xgb_score, 'best_model': 'xgboost' if xgb_score > rf_score else 'random_forest' } """, custom_image="my-ml-analysis", mcp_tools=["filesystem_read"], session_id="churn-analysis" ) print(result['result']) # Returns: {'random_forest_accuracy': 0.87, 'xgboost_accuracy': 0.91, 'best_model': 'xgboost'} ``` ### Example 3: Stateful Analysis with Custom Environment ```python # Create session with custom environment session_id = "deep-analysis" # Step 1: Load and explore data execute_python( code=""" import pandas as pd import matplotlib.pyplot as plt df = filesystem_read_csv('/data/sales_2024.csv') print(f"Loaded {len(df)} rows") print(df.describe()) # Store for later df.to_pickle('/tmp/sales_df.pkl') """, custom_image="data-science", # Use pre-built template mcp_tools=["filesystem_read_csv"], session_id=session_id, backend="jupyter" ) # Document what we have document_state( session_id=session_id, variables={ "df": "Sales data for 2024, loaded and stored as pickle" }, note="Data loaded and ready for analysis" ) # ... conversation continues, context rotates ... # Step 2: Later, continue analysis state = read_resource(f"mcp://forge/sessions/{session_id}/state") # Agent sees df is available execute_python( code=""" import pandas as pd df = pd.read_pickle('/tmp/sales_df.pkl') # Analyze trends monthly_sales = df.groupby('month')['revenue'].sum() growth_rate = monthly_sales.pct_change().mean() * 100 { 'total_revenue': float(df['revenue'].sum()), 'avg_monthly_growth': float(growth_rate), 'top_products': df.groupby('product')['revenue'].sum().nlargest(5).to_dict() } """, session_id=session_id ) ``` ### Example 4: Using Pre-built Templates ```python # Quick start with pre-built template result = execute_python( code=""" import requests from bs4 import BeautifulSoup # Scrape some data response = requests.get('https://example.com/data') soup = BeautifulSoup(response.content, 'lxml') # Extract and process data = [item.text for item in soup.find_all('div', class_='data-item')] data[:10] # Return first 10 items """, environment="web-scraping", # Use template instead of building custom session_id=None ) print(result['result']) ``` ## Implementation Roadmap ### Phase 1: Core Execution (MVP) - Basic execute_python tool with simple backend - Podman integration with security restrictions - MCP tool injection - Pre-built Python 3.11 image ### Phase 2: Stateful Execution - Jupyter backend implementation - Session management - State documentation tool - Session resources ### Phase 3: Custom Environments - build_custom_environment tool - UV-based package installation - Build caching - Package allowlist/blocklist - Security validation ### Phase 4: Advanced Features - Pre-built template library - Environment lifecycle management - Build rate limiting - Advanced audit logging - Multi-user support with quotas ### Phase 5: Production Hardening - Performance optimization - Advanced security scanning - Comprehensive monitoring - High availability setup - Documentation and examples ### Example 5: Multi-Step Stateful Analysis (Original Approach) ```python # Step 1: Load data execute_python( code=""" data = filesystem_read('/data/sales.csv') import pandas as pd df = pd.read_csv(io.StringIO(data)) print(f"Loaded {len(df)} rows") """, session_id="analysis-001", backend="jupyter", mcp_tools=["filesystem_read"] ) # Step 2: Document state document_state( session_id="analysis-001", variables={ "df": "Sales data, 50k rows, columns: date, product, amount, region" }, note="Data loaded, ready for analysis" ) # Step 3: Process (in same session) execute_python( code=""" # df still exists from previous execution monthly_sales = df.groupby(df['date'].dt.month)['amount'].sum() monthly_sales.plot() plt.savefig('/workspace/output/monthly.png') monthly_sales.to_dict() # Return summary """, session_id="analysis-001" ) # Step 4: Check state later state = read_resource("mcp://forge/sessions/analysis-001/state") # See what variables exist ``` ### Example 6: Tool Error Handling ```python result = execute_python( code=""" try: repos = github_search_repos(query="test", max_results=1000) except Exception as e: print(f"GitHub API error: {e}") repos = {'items': []} # Fallback len(repos['items']) """, mcp_tools=["github_search_repos"] ) # Agent sees stdout with error message but execution continues ``` ## Deployment ### Development Setup ```bash # Install Podman (rootless) sudo apt install podman podman system migrate # Enable rootless # Install MCP-Forge pip install mcp-forge # Build container images mcp-forge build-images # Start server mcp-forge serve --config mcp-forge.yaml ``` ### Production Deployment **Docker Compose (using Podman):** ```yaml version: '3.8' services: mcp-forge: image: mcp-forge:latest volumes: - /run/user/1000/podman/podman.sock:/run/podman/podman.sock:ro - ./mcp-forge.yaml:/etc/mcp-forge/config.yaml:ro - mcp-forge-data:/var/lib/mcp-forge - mcp-forge-logs:/var/log/mcp-forge environment: - GITHUB_TOKEN=${GITHUB_TOKEN} restart: unless-stopped user: "1000:1000" volumes: mcp-forge-data: mcp-forge-logs: ``` **Systemd Service:** ```ini [Unit] Description=MCP-Forge Server After=podman.socket [Service] Type=simple User=forge Environment=MCP_FORGE_PODMAN_SOCKET=/run/user/1000/podman/podman.sock ExecStart=/usr/local/bin/mcp-forge serve --config /etc/mcp-forge/config.yaml Restart=on-failure [Install] WantedBy=multi-user.target ``` ### Monitoring **Metrics to track:** - Active sessions count - Container count (running, idle) - Execution duration (p50, p95, p99) - Memory usage per container - CPU usage per container - Failed executions (rate, reasons) - Security violations (rate, types) **Health Checks:** - Podman socket accessible - Container image availability - Volume filesystem writable - MCP tool connectivity ## Future Enhancements ### Planned Features 1. **Language Support:** - JavaScript/Node.js execution - R execution - Shell script execution 2. **Advanced Volumes:** - S3/blob storage integration - Shared volumes between sessions - Volume snapshots 3. **Networking:** - Opt-in restricted internet access - Allowlist for specific domains - VPN/proxy support 4. **Collaboration:** - Shared sessions between agents - Session forking - Session snapshots/restore 5. **Performance:** - GPU support for ML workloads - Container warm pools - Compilation caching (for numba, etc.) 6. **Observability:** - Execution tracing - Performance profiling - Resource usage visualization ### Open Questions 1. **Long-Running Tasks:** - Background job execution? - Streaming results during execution? - Callback URLs for completion? 2. **Data Sharing:** - Session-to-session data transfer? - Export results to external storage? - Import data from URLs? 3. **Cost Management:** - Resource usage quotas per user? - Billing integration? - Fair scheduling? ## Conclusion MCP-Forge provides a secure, efficient way for AI agents to execute code with access to MCP tools, solving the problem of large data payloads in LLM contexts. The architecture prioritizes security through defense-in-depth while maintaining flexibility for both simple and complex workflows. Key design principles: - **Security first:** Rootless containers, allowlists, resource limits - **Agent-friendly:** Resources for discovery, state documentation - **Flexible:** Stateless and stateful execution modes - **Efficient:** Process data locally, return only results - **Observable:** Comprehensive audit logging and monitoring The architecture is designed for implementation with clear component boundaries, well-defined interfaces, and security considerations baked in from the start.