# 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 and R 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 language-native functions (Python and R) - 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) │ ├─ Pre-built Images │ ├─ mcp-forge/python:3.11 │ ├─ mcp-forge/python:3.12 │ └─ mcp-forge/jupyter:latest │ ├─ Custom User Images │ └─ mcp-forge/custom:{user-env-name} │ └─ Isolated Execution Containers ├─ Python/R 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", "created": "2026-02-06T10:00:00Z", "packages": ["numpy==1.24.0", "pandas==2.0.0"], "size_mb": 1234 } ], "templates": [ { "name": "ml-basic", "description": "Basic ML stack", "packages": ["numpy", "pandas", "scikit-learn"] }, { "name": "data-science", "description": "Data science stack", "packages": ["numpy", "pandas", "matplotlib", "seaborn"] } ] } ``` **`mcp://forge/audit/operations`** (optional) - Returns: Audit log of container operations - Purpose: Security monitoring, debugging #### Tools **`execute_code`** Execute Python or R code in an isolated container with MCP tools available. Parameters: ```json { "code": "string (required) - Source code to execute", "language": "string (optional) - 'python' (default) or 'r'", "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'", "kernel": "string (optional) - for jupyter backend: 'python' (ipykernel) or 'r' (rkernel)", "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 - Keep kernel running for session lifetime - Execute code cells via Jupyter protocol (ZMQ) - Maintain namespace between executions - Support rich output formats **Characteristics:** - State persists between calls - Variable persistence - Interactive workflow support - Higher resource usage **Use cases:** - Multi-step data analysis - Iterative model training - Building up complex state - Exploratory workflows **Session Management:** - Sessions identified by unique ID - Automatic timeout after inactivity (configurable, default: 1 hour) - Manual cleanup via session deletion - Resource limits per 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=["mcp-forge/cache:uv-base"], # UV cache layer buildargs={ "BUILDKIT_INLINE_CACHE": "1" } ) 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" - "torch>=2.0.0" - "transformers>=4.30.0" data-science: description: "Data science stack" packages: - "numpy>=1.24.0" - "pandas>=2.0.0" - "matplotlib>=3.7.0" - "seaborn>=0.12.0" - "plotly>=5.14.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.10.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 - beautifulsoup4 # ... many more # Explicitly forbidden blocklist: - "os-sys" # Known malicious - "cryptography-backdoor" # Packages with known vulnerabilities # Requires manual approval manual_approval: - "*crypto*" # Cryptographic packages - "*network*" # Network access packages - "*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 requires_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": "2026-02-06T10:00:00Z", "size_mb": 1234, "packages": ["numpy==1.24.0", "pandas==2.0.0"], "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" ], "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_per_build: 50 max_build_time: 600 # 10 minutes max_image_size: 2147483648 # 2GB in bytes max_concurrent_builds: 3 build_rate_limit: requests: 10 period: 3600 # 10 builds per hour per user # Package security package_validation: use_allowlist: true allowlist_path: /etc/mcp-forge/package-allowlist.txt blocklist_path: /etc/mcp-forge/package-blocklist.txt require_approval_patterns: - "*crypto*" - "*network*" - "*subprocess*" # Environment lifecycle auto_cleanup: enabled: true archive_after_days: 30 delete_after_days: 90 # Pre-built templates templates: ml-basic: description: "Basic ML stack" packages: - "numpy>=1.24.0" - "pandas>=2.0.0" - "scikit-learn>=1.3.0" data-science: description: "Data science stack" packages: - "numpy>=1.24.0" - "pandas>=2.0.0" - "matplotlib>=3.7.0" - "seaborn>=0.12.0" - "plotly>=5.14.0" web-scraping: description: "Web scraping tools" packages: - "requests>=2.31.0" - "beautifulsoup4>=4.12.0" - "lxml>=4.9.0" 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", "matplotlib>=3.7.0", "xgboost>=2.0.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, 50k rows, pickled to /tmp/sales_df.pkl" }, 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 ## Multi-language Execution (Python + R) MCP-Forge supports both Python and R execution modes: - **Simple backend** - `language=python` runs python shell execution (existing path) - `language=r` runs R shell execution (new path) - Stateless by default - **Jupyter backend** - `kernel=python` uses IPython kernel (existing path) - `kernel=r` uses IRkernel (new path) - Stateful sessions keyed by `session_id` ### Tool Injection for R The MCP bridge protocol remains Unix-socket JSON based and language-agnostic. R injection wrappers mirror Python wrappers at a behavioral level: - Generate R wrapper functions per allowed MCP tool - Forward tool calls over mounted bridge socket - Decode JSON responses to R lists/vectors This keeps execution containers air-gapped while preserving access to configured MCP tools.