mcp-forge/docs/todo.md
2026-02-07 07:45:57 +01:00

92 KiB

MCP-Forge Development TODO

Use uv:

  • To run python, use uv run python. To use pytest, use uv run pytest. Using uv run guarantees that we use the correct virtual environment.

  • To add packages, use uv add. For development dependencies, use uv add --dev.

Progress tracking: Use this todo.md file to track your progress.

Reference Architecture: See architecture1.md for complete system design.

Development Philosophy:

  • Bottom-up approach: build foundational modules first
  • Test-driven development: write tests before implementation
  • Integration testing: test submodules individually, then integration
  • No shortcuts: follow architecture strictly, no simplified POC approaches
  • Clear acceptance criteria: prevent circumventing security and architectural decisions
  • Build according to Implementation Roadmap (Phases 1-5 in architecture)

Progress Summary

Status: Phase 5.2 Complete - 387 tests passing

Completed Phases:

  • Phase 1.1: Configuration Management (35 tests)
    • config/schema.py - Configuration models with validation
    • config/loader.py - YAML loading with environment substitution
  • Phase 1.2: Security & Validation Core (58 tests)
    • security/resource_limits.py - Resource limit parsing and validation
    • security/allowlist.py - Package allowlist/blocklist management
    • security/audit.py - Comprehensive audit logging
  • Phase 1.3: Podman Integration Core (25 tests)
    • podman/client.py - Podman API client wrapper (renamed test_podman_client.py)
    • podman/containers.py - Secure container lifecycle management
  • Phase 2.1: Simple Backend (37 tests)
    • execution/simple/executor.py - Stateless code execution (17 tests)
    • execution/simple/backend.py - Simple backend orchestration (20 tests)
  • Phase 2.2: Jupyter Backend (65 tests)
    • execution/jupyter/kernel.py - IPython kernel management (22 tests)
    • execution/jupyter/sessions.py - Session lifecycle management (24 tests)
    • execution/jupyter/backend.py - Stateful execution orchestration (19 tests)
  • Phase 3.1: Package Management (42 tests)
    • builder/package_validator.py - Package security validation (21 tests)
    • builder/uv_installer.py - UV-based package installer (21 tests)
  • Phase 3.2: Environment Builder (33 tests)
    • builder/image_builder.py - Podman image building (18 tests)
    • ⏭️ builder/security_scanner.py - SKIPPED (optional trivy-based vulnerability scanning)
    • builder/environment_builder.py - Orchestration with rate limiting (15 tests)
  • Phase 4: MCP Tool Integration (40 tests)
    • mcp/client.py - fastmcp-based MCP client wrapper (12 tests)
    • mcp/manager.py - Multi-client manager with collision detection (9 tests)
    • mcp/bridge.py - Unix socket bridge server (8 tests)
    • mcp/injection.py - Python code generator for tool injection (11 tests)
  • Phase 5.1.1: MCP Resource Handlers (13 tests)
    • server/resources.py - MCP resource handlers for discovery and state
  • Phase 5.1.2: MCP Tools (34 tests)
    • server/tools/execute_python.py - Execute Python code with tool injection (15 tests)
    • server/tools/document_state.py - Document session state and variables (8 tests)
    • server/tools/build_environment.py - Build custom environments (11 tests)
  • Phase 5.2: Main MCP Server (5 tests)
    • server/server.py - ForgeServer orchestration with component initialization
    • server/__init__.py - Module exports

Pending:

  • Phase 5.3: Integration & End-to-End Testing
  • Phase 6: Documentation & Deployment

Test Count: 387 tests passing

Last Updated: 2026-02-06


Phase 1: Foundation & Core Infrastructure

1.1 Configuration Management

1.1.1 Configuration Schema Module

Path: src/mcp_forge/config/schema.py

Purpose: Define and validate configuration structures using Pydantic models.

Tests to write first:

  • tests/config/test_schema.py
    • Test valid configuration loads successfully
    • Test invalid configuration raises ValidationError
    • Test default values are applied correctly
    • Test environment variable substitution (e.g., ${GITHUB_TOKEN})
    • Test nested configuration validation
    • Test constraint validation (e.g., max_timeout >= default_timeout)

Implementation requirements:

class ServerConfig(BaseModel):
    host: str = "localhost"
    port: int = 3000
    podman_socket: Path
    
    @validator('port')
    def validate_port(cls, v):
        if not 1 <= v <= 65535:
            raise ValueError("Port must be 1-65535")
        return v

class ExecutionConfig(BaseModel):
    default_backend: Literal["simple", "jupyter"] = "simple"
    default_timeout: int = 300
    max_timeout: int = 1800
    default_memory: str = "512m"
    max_memory: str = "2g"
    default_cpu_quota: int = 50000
    max_cpu_quota: int = 100000
    
    @validator('max_timeout')
    def validate_max_timeout(cls, v, values):
        if v < values.get('default_timeout', 0):
            raise ValueError("max_timeout must be >= default_timeout")
        return v

class ImageConfig(BaseModel):
    python_3_11: str = "mcp-forge/python:3.11"
    python_3_12: str = "mcp-forge/python:3.12"
    jupyter: str = "mcp-forge/jupyter:latest"
    auto_pull: bool = True
    pull_interval: int = 86400

class SessionConfig(BaseModel):
    idle_timeout: int = 3600
    max_concurrent: int = 10
    cleanup_interval: int = 300

class VolumeConfig(BaseModel):
    base_path: Path
    session_quota: str = "1g"
    max_session_quota: str = "10g"

class SecurityConfig(BaseModel):
    audit_log: Path
    enforce_resource_limits: bool = True
    allow_network: bool = False

class PackageValidationConfig(BaseModel):
    use_allowlist: bool = True
    allowlist_path: Path
    blocklist_path: Path
    require_approval_patterns: List[str]

class EnvironmentBuilderConfig(BaseModel):
    enabled: bool = True
    uv_cache_path: Path
    max_packages_per_build: int = 50
    max_build_time: int = 600
    max_image_size: int = 2147483648
    max_concurrent_builds: int = 3
    build_rate_limit: dict
    package_validation: PackageValidationConfig
    auto_cleanup: dict
    templates: Dict[str, dict]

class MCPToolConfig(BaseModel):
    command: str
    args: List[str]
    env: Dict[str, str] = {}

class ForgeConfig(BaseModel):
    server: ServerConfig
    execution: ExecutionConfig
    images: ImageConfig
    sessions: SessionConfig
    volumes: VolumeConfig
    security: SecurityConfig
    environment_builder: EnvironmentBuilderConfig
    mcp_tools: Dict[str, MCPToolConfig]

Acceptance criteria:

  • All configuration fields have proper type validation
  • Cross-field validation works (e.g., max >= default)
  • Environment variables are substituted correctly
  • Invalid configurations raise clear ValidationError with field path
  • No hardcoded values; all configurable
  • 100% test coverage on schema validation

1.1.2 Configuration Loader Module

Path: src/mcp_forge/config/loader.py

Purpose: Load configuration from YAML files with environment variable substitution.

Tests to write first:

  • tests/config/test_loader.py
    • Test load from valid YAML file
    • Test load from non-existent file raises FileNotFoundError
    • Test invalid YAML raises YAMLError
    • Test environment variable substitution in strings
    • Test nested environment variable substitution
    • Test missing environment variable raises clear error
    • Test loading from multiple sources (file + env overrides)
    • Test configuration merging (defaults + file + env)

Implementation requirements:

def substitute_env_vars(value: Any) -> Any:
    """Recursively substitute ${VAR} with environment variables."""
    pass

def load_config(config_path: Optional[Path] = None) -> ForgeConfig:
    """
    Load configuration from YAML file and environment.
    
    Priority: Environment variables > Config file > Defaults
    """
    pass

def load_config_from_dict(config_dict: dict) -> ForgeConfig:
    """Load configuration from dictionary (for testing)."""
    pass

Acceptance criteria:

  • Supports YAML configuration files
  • Environment variable substitution works recursively
  • Missing env vars raise clear errors with variable name
  • Configuration priority is respected (env > file > defaults)
  • Can load partial configurations (missing sections use defaults)
  • No eval() or exec() - only safe string substitution
  • 100% test coverage

1.2 Security & Validation Core

1.2.1 Resource Limit Parser

Path: src/mcp_forge/security/resource_limits.py

Purpose: Parse and validate resource limit strings (memory, CPU, storage).

Tests to write first:

  • tests/security/test_resource_limits.py
    • Test parse_memory_string("512m") → 536870912
    • Test parse_memory_string("2g") → 2147483648
    • Test parse_memory_string("1024k") → 1048576
    • Test invalid format raises ValueError
    • Test negative values raise ValueError
    • Test zero values raise ValueError
    • Test parse_cpu_quota(50000) validates correctly
    • Test CPU quota > 1000000 raises ValueError
    • Test CPU quota < 0 raises ValueError
    • Test storage size parsing (same as memory)

Implementation requirements:

def parse_memory_string(memory: str) -> int:
    """
    Parse memory string to bytes.
    
    Supports: k, m, g suffixes (case-insensitive)
    Examples: "512m" → 536870912, "2g" → 2147483648
    
    Raises:
        ValueError: If format is invalid or value is <= 0
    """
    pass

def parse_cpu_quota(cpu_quota: int) -> int:
    """
    Validate CPU quota value.
    
    CPU quota is in microseconds per 100ms period.
    100000 = 100% of one CPU core
    
    Raises:
        ValueError: If quota <= 0 or > 1000000 (10 cores max)
    """
    pass

def parse_storage_string(storage: str) -> int:
    """Parse storage string to bytes (same as memory)."""
    pass

class ResourceLimits:
    """Resource limits with validation."""
    
    def __init__(
        self,
        memory: str,
        cpu_quota: int,
        storage: Optional[str] = None,
        pids_limit: int = 100,
        timeout: int = 300
    ):
        self.memory_bytes = parse_memory_string(memory)
        self.cpu_quota = parse_cpu_quota(cpu_quota)
        self.storage_bytes = parse_storage_string(storage) if storage else None
        self.pids_limit = pids_limit
        self.timeout = timeout
        
    def to_podman_params(self) -> dict:
        """Convert to Podman container create parameters."""
        pass

Acceptance criteria:

  • Parses all standard units: k, m, g (case-insensitive)
  • Validates positive values only
  • Raises clear ValueError with problematic value
  • CPU quota limited to reasonable maximum (10 cores)
  • ResourceLimits class enforces all constraints
  • to_podman_params() returns valid Podman parameter dict
  • 100% test coverage

1.2.2 Podman Operation Allowlist

Path: src/mcp_forge/security/allowlist.py

Purpose: Define and enforce allowed Podman operations with parameter validation.

Tests to write first:

  • tests/security/test_allowlist.py
    • Test allowed operation with valid params passes
    • Test allowed operation with forbidden params raises SecurityError
    • Test forbidden operation raises SecurityError
    • Test image allowlist enforcement
    • Test required parameters validation
    • Test volume mount path validation
    • Test capability restrictions
    • Test network mode enforcement
    • Test privilege mode always rejected
    • Test session container tracking

Implementation requirements:

class SecurityError(Exception):
    """Raised when security policy is violated."""
    pass

ALLOWED_IMAGES = [
    "mcp-forge/python:3.11",
    "mcp-forge/python:3.12",
    "mcp-forge/jupyter:latest",
    "mcp-forge/custom:*",  # Custom user images
]

FORBIDDEN_CONTAINER_PARAMS = [
    "privileged",
    "cap_add",
    "devices",
    "pid_mode",
    "ipc_mode",
]

REQUIRED_CONTAINER_PARAMS = {
    "network_mode": "none",
    "read_only": True,
    "security_opt": ["no-new-privileges"],
    "user": "1000:1000",
}

ALLOWED_VOLUME_PATTERNS = [
    "/mcp-forge/sessions/{session_id}/*",
    "/mcp-forge/shared/readonly/*",
    "/mcp-forge/uploads/{session_id}/*",
]

FORBIDDEN_MOUNT_PATHS = [
    "/",
    "/etc",
    "/var/run/docker.sock",
    "/var/run/podman/podman.sock",
    "/sys",
    "/proc",
]

class OperationValidator:
    """Validates Podman operations against security policy."""
    
    def __init__(self, config: SecurityConfig):
        self.config = config
        self.session_containers: Set[str] = set()
    
    def validate_container_create(
        self,
        image: str,
        params: dict,
        session_id: Optional[str] = None
    ) -> None:
        """
        Validate container create operation.
        
        Raises:
            SecurityError: If operation violates security policy
        """
        pass
    
    def validate_container_start(self, container_id: str) -> None:
        """Validate container start - must be session container."""
        pass
    
    def validate_container_stop(self, container_id: str) -> None:
        """Validate container stop - must be session container."""
        pass
    
    def validate_container_remove(self, container_id: str) -> None:
        """Validate container remove - must be session container."""
        pass
    
    def validate_volume_mount(self, mount_path: str, session_id: str) -> None:
        """
        Validate volume mount path against allowed patterns.
        
        Raises:
            SecurityError: If path is forbidden or doesn't match allowed patterns
        """
        pass
    
    def validate_image_name(self, image: str) -> None:
        """
        Validate image name against allowlist.
        
        Supports wildcards: mcp-forge/custom:*
        
        Raises:
            SecurityError: If image not in allowlist
        """
        pass
    
    def register_session_container(self, container_id: str) -> None:
        """Register container as belonging to a session."""
        pass
    
    def unregister_session_container(self, container_id: str) -> None:
        """Unregister session container."""
        pass

Acceptance criteria:

  • All forbidden operations are rejected
  • Required parameters are enforced
  • Image allowlist with wildcard support works
  • Volume mount validation prevents host path access
  • Forbidden mount paths are blocked
  • privileged mode always rejected regardless of other params
  • Session container tracking prevents operating on non-session containers
  • Clear SecurityError messages indicate what rule was violated
  • 100% test coverage including edge cases

1.2.3 Audit Logger

Path: src/mcp_forge/security/audit.py

Purpose: Structured logging of security-relevant operations.

Tests to write first:

  • tests/security/test_audit.py
    • Test log entries written to file
    • Test log entries are valid JSON
    • Test log entries contain required fields
    • Test timestamp format is ISO 8601
    • Test log rotation works
    • Test concurrent logging is thread-safe
    • Test security violations are logged with correct severity
    • Test PII is not logged

Implementation requirements:

from enum import Enum
from typing import Any, Optional
import json
from datetime import datetime
from pathlib import Path
import threading

class AuditEventType(Enum):
    CONTAINER_CREATE = "container.create"
    CONTAINER_START = "container.start"
    CONTAINER_STOP = "container.stop"
    CONTAINER_REMOVE = "container.remove"
    EXECUTION_REQUEST = "execution.request"
    SECURITY_VIOLATION = "security.violation"
    BUILD_REQUEST = "build.request"
    BUILD_COMPLETE = "build.complete"
    SESSION_CREATE = "session.create"
    SESSION_DESTROY = "session.destroy"

class AuditSeverity(Enum):
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"

class AuditLogger:
    """Thread-safe structured audit logger."""
    
    def __init__(self, log_path: Path):
        self.log_path = log_path
        self.lock = threading.Lock()
        self._ensure_log_file()
    
    def log(
        self,
        event_type: AuditEventType,
        severity: AuditSeverity = AuditSeverity.INFO,
        session_id: Optional[str] = None,
        user_id: Optional[str] = None,
        details: Optional[dict] = None,
        success: bool = True,
        error: Optional[str] = None
    ) -> None:
        """
        Log an audit event.
        
        Event structure:
        {
            "timestamp": "2026-02-06T10:30:00Z",
            "event": "container.create",
            "severity": "info",
            "session_id": "abc123",
            "user_id": "user@example.com",
            "success": true,
            "details": {...},
            "error": null
        }
        
        Must NOT log:
        - Code content (for privacy)
        - Authentication tokens
        - File contents
        - PII beyond user_id
        """
        pass
    
    def log_container_operation(
        self,
        operation: AuditEventType,
        container_id: str,
        image: str,
        session_id: Optional[str] = None,
        resources: Optional[dict] = None,
        success: bool = True,
        error: Optional[str] = None
    ) -> None:
        """Log container operation with standard fields."""
        pass
    
    def log_security_violation(
        self,
        violation_type: str,
        details: dict,
        session_id: Optional[str] = None
    ) -> None:
        """Log security violation at CRITICAL severity."""
        pass
    
    def _ensure_log_file(self) -> None:
        """Ensure log file and directory exist."""
        pass
    
    def _write_log_entry(self, entry: dict) -> None:
        """Thread-safe write of log entry."""
        pass

Acceptance criteria:

  • All log entries are valid JSON
  • Timestamps are ISO 8601 format
  • Log file is created if it doesn't exist
  • Concurrent logging is thread-safe (test with threading)
  • No PII or sensitive data is logged (code content, tokens, files)
  • Only hash of code is logged for execution requests
  • Security violations logged at CRITICAL severity
  • Log entries contain all required fields
  • 100% test coverage

1.3 Podman Integration Core

1.3.1 Podman Client Wrapper

Path: src/mcp_forge/podman/client.py

Purpose: Wrap Podman API with security validation and error handling.

Tests to write first:

  • tests/podman/test_client.py
    • Test connection to Podman socket succeeds
    • Test connection failure raises clear error
    • Test socket path validation
    • Test socket permissions check
    • Test API version compatibility check
    • Test ping/health check
    • Mock all actual Podman calls (use pytest-mock)

Implementation requirements:

from podman import PodmanClient as BasePodmanClient
from podman.errors import APIError, NotFound
from typing import Optional
from pathlib import Path

class PodmanConnectionError(Exception):
    """Raised when connection to Podman fails."""
    pass

class PodmanClient:
    """
    Wrapper around Podman API with security validation.
    
    All container operations are validated against security policy
    before being sent to Podman.
    """
    
    def __init__(
        self,
        socket_path: Path,
        validator: OperationValidator,
        audit_logger: AuditLogger
    ):
        self.socket_path = socket_path
        self.validator = validator
        self.audit_logger = audit_logger
        self._client: Optional[BasePodmanClient] = None
    
    def connect(self) -> None:
        """
        Connect to Podman socket.
        
        Raises:
            PodmanConnectionError: If connection fails
        """
        pass
    
    def ping(self) -> bool:
        """Test connection to Podman."""
        pass
    
    def disconnect(self) -> None:
        """Disconnect from Podman."""
        pass
    
    def verify_socket_access(self) -> None:
        """
        Verify socket exists and is accessible.
        
        Raises:
            PodmanConnectionError: If socket not accessible
        """
        pass
    
    def check_api_version(self) -> dict:
        """Get Podman API version information."""
        pass
    
    @property
    def client(self) -> BasePodmanClient:
        """Get underlying Podman client (lazy connection)."""
        if self._client is None:
            self.connect()
        return self._client

Acceptance criteria:

  • Validates socket path exists before connecting
  • Checks socket permissions (must be readable)
  • Lazy connection (only connects when needed)
  • Ping/health check works
  • API version check works
  • Clear error messages for connection failures
  • Graceful disconnect/cleanup
  • All tests use mocked Podman client (no actual Podman needed)
  • 100% test coverage

1.3.2 Secure Container Manager

Path: src/mcp_forge/podman/containers.py

Purpose: Create, manage, and cleanup containers with security enforcement.

Tests to write first:

  • tests/podman/test_containers.py
    • Test create_container with valid params succeeds
    • Test create_container with forbidden params raises SecurityError
    • Test create_container with invalid image raises SecurityError
    • Test create_container enforces required parameters
    • Test resource limits are applied correctly
    • Test volume mounts are validated
    • Test start_container on session container succeeds
    • Test start_container on non-session container raises SecurityError
    • Test stop_container works
    • Test remove_container works
    • Test cleanup orphaned containers
    • All tests use mocked Podman client

Implementation requirements:

from typing import Optional, Dict, List
from datetime import datetime, timedelta

class ContainerConfig:
    """Container configuration with security defaults."""
    
    def __init__(
        self,
        image: str,
        command: Optional[List[str]] = None,
        environment: Optional[Dict[str, str]] = None,
        volumes: Optional[Dict[str, dict]] = None,
        resource_limits: Optional[ResourceLimits] = None,
        working_dir: str = "/workspace",
        user: str = "1000:1000"
    ):
        self.image = image
        self.command = command or []
        self.environment = environment or {}
        self.volumes = volumes or {}
        self.resource_limits = resource_limits
        self.working_dir = working_dir
        self.user = user
    
    def to_podman_params(self) -> dict:
        """
        Convert to Podman container create parameters.
        
        Ensures all security requirements are included:
        - network_mode: none
        - read_only: True
        - security_opt: ["no-new-privileges"]
        - resource limits
        """
        pass

class SecureContainerManager:
    """Manages container lifecycle with security enforcement."""
    
    def __init__(
        self,
        podman_client: PodmanClient,
        validator: OperationValidator,
        audit_logger: AuditLogger
    ):
        self.podman = podman_client
        self.validator = validator
        self.audit_logger = audit_logger
    
    def create_container(
        self,
        config: ContainerConfig,
        session_id: Optional[str] = None,
        name: Optional[str] = None
    ) -> str:
        """
        Create a container with security validation.
        
        Returns:
            Container ID
        
        Raises:
            SecurityError: If configuration violates security policy
            PodmanError: If container creation fails
        """
        pass
    
    def start_container(self, container_id: str) -> None:
        """
        Start a container.
        
        Raises:
            SecurityError: If container is not a session container
        """
        pass
    
    def stop_container(
        self,
        container_id: str,
        timeout: int = 10
    ) -> None:
        """Stop a container."""
        pass
    
    def remove_container(
        self,
        container_id: str,
        force: bool = False
    ) -> None:
        """Remove a container."""
        pass
    
    def get_container_logs(
        self,
        container_id: str,
        tail: int = 100
    ) -> tuple[str, str]:
        """
        Get container stdout and stderr logs.
        
        Returns:
            (stdout, stderr)
        """
        pass
    
    def wait_for_container(
        self,
        container_id: str,
        timeout: int = 300
    ) -> int:
        """
        Wait for container to exit.
        
        Returns:
            Exit code
        
        Raises:
            TimeoutError: If container doesn't exit within timeout
        """
        pass
    
    def cleanup_old_containers(
        self,
        max_age: timedelta = timedelta(hours=24)
    ) -> int:
        """
        Cleanup containers older than max_age.
        
        Returns:
            Number of containers removed
        """
        pass

Acceptance criteria:

  • All container operations validated before execution
  • Security parameters (read_only, network_mode, etc.) enforced
  • Resource limits applied correctly
  • Volume mounts validated against allowlist
  • Only session containers can be started/stopped/removed
  • Container creation logs to audit log
  • Clear error messages for security violations
  • Cleanup function removes old containers safely
  • All operations are idempotent where possible
  • All tests use mocked Podman (no real containers)
  • 100% test coverage

Phase 2: Execution Backends

2.1 Simple Backend (Stateless Execution)

2.1.1 Code Executor Module

Path: src/mcp_forge/execution/simple/executor.py

Purpose: Execute Python code in stateless containers.

Tests to write first:

  • tests/execution/simple/test_executor.py
    • Test execute simple Python code returns result
    • Test execute code with stdout capture
    • Test execute code with stderr capture
    • Test execute code timeout enforcement
    • Test execute code with exception handling
    • Test execute code with syntax error returns clear error
    • Test execute code with runtime error returns clear error
    • Test result serialization (JSON-compatible types)
    • Test large output handling
    • All tests use mocked containers

Implementation requirements:

from typing import Any, Optional, Dict
from dataclasses import dataclass
import json

@dataclass
class ExecutionResult:
    """Result of code execution."""
    success: bool
    stdout: str
    stderr: str
    result: Optional[Any]
    execution_time: float
    exit_code: int
    error: Optional[str] = None
    
    def to_dict(self) -> dict:
        """Convert to dictionary for JSON serialization."""
        pass

class CodeExecutor:
    """Executes Python code in isolated containers."""
    
    def __init__(
        self,
        container_manager: SecureContainerManager,
        image: str,
        resource_limits: ResourceLimits
    ):
        self.container_manager = container_manager
        self.image = image
        self.resource_limits = resource_limits
    
    def execute(
        self,
        code: str,
        timeout: Optional[int] = None
    ) -> ExecutionResult:
        """
        Execute Python code in a fresh container.
        
        Process:
        1. Create container with code
        2. Start container
        3. Wait for completion (with timeout)
        4. Capture stdout/stderr
        5. Extract result from last expression
        6. Cleanup container
        
        Args:
            code: Python code to execute
            timeout: Maximum execution time in seconds
        
        Returns:
            ExecutionResult with stdout, stderr, result, and timing
        """
        pass
    
    def _prepare_code(self, code: str) -> str:
        """
        Wrap code to capture result and handle errors.
        
        Wraps code in try/except and captures:
        - Last expression result
        - Exceptions with traceback
        - Execution metadata
        
        Returns wrapped code that outputs JSON to stdout.
        """
        pass
    
    def _parse_output(self, stdout: str) -> tuple[Any, Optional[str]]:
        """
        Parse execution output to extract result and error.
        
        Returns:
            (result, error_message)
        """
        pass

Acceptance criteria:

  • Executes code in fresh container each time
  • Captures stdout and stderr separately
  • Returns result of last expression
  • Handles syntax errors gracefully
  • Handles runtime errors with traceback
  • Enforces timeout strictly
  • Cleans up container after execution (even on error)
  • Result must be JSON-serializable
  • Large output doesn't cause issues
  • Code wrapping preserves line numbers for errors
  • All tests use mocked containers
  • 100% test coverage

2.1.2 Simple Backend Implementation

Path: src/mcp_forge/execution/simple/backend.py

Purpose: Simple backend orchestrating code execution.

Tests to write first:

  • tests/execution/simple/test_backend.py
    • Test execute without MCP tools
    • Test execute with resource limit override
    • Test execute with custom image
    • Test execute with volume mounts
    • Test execute respects configuration defaults
    • Test execute validates resource limits against max
    • Test multiple concurrent executions
    • All tests use mocked components

Implementation requirements:

class SimpleBackend:
    """Stateless code execution backend."""
    
    def __init__(
        self,
        config: ForgeConfig,
        container_manager: SecureContainerManager,
        audit_logger: AuditLogger
    ):
        self.config = config
        self.container_manager = container_manager
        self.audit_logger = audit_logger
    
    def execute(
        self,
        code: str,
        timeout: Optional[int] = None,
        memory: Optional[str] = None,
        cpu_quota: Optional[int] = None,
        custom_image: Optional[str] = None,
        volumes: Optional[Dict[str, dict]] = None
    ) -> ExecutionResult:
        """
        Execute Python code in stateless container.
        
        Args:
            code: Python code to execute
            timeout: Max execution time (uses config default if None)
            memory: Memory limit (uses config default if None)
            cpu_quota: CPU quota (uses config default if None)
            custom_image: Custom image name (uses config default if None)
            volumes: Volume mounts
        
        Returns:
            ExecutionResult
        
        Raises:
            ValueError: If limits exceed configured maximums
        """
        pass
    
    def _validate_limits(
        self,
        timeout: int,
        memory: str,
        cpu_quota: int
    ) -> None:
        """
        Validate resource limits against configuration maximums.
        
        Raises:
            ValueError: If any limit exceeds maximum
        """
        pass
    
    def _get_image(self, custom_image: Optional[str]) -> str:
        """Get image name, defaulting to configured image."""
        pass

Acceptance criteria:

  • Uses configuration defaults for unspecified limits
  • Validates limits against configured maximums
  • Supports custom images
  • Supports volume mounts
  • Logs execution to audit log (hash of code, not content)
  • Handles concurrent executions safely
  • Clear error for limit violations
  • All tests use mocked components
  • 100% test coverage

2.2 Jupyter Backend (Stateful Execution)

2.2.1 Jupyter Kernel Manager

Path: src/mcp_forge/execution/jupyter/kernel.py

Purpose: Manage IPython kernel lifecycle and communication.

Tests to write first:

  • tests/execution/jupyter/test_kernel.py
    • Test start kernel in container
    • Test execute code in kernel
    • Test kernel namespace persistence
    • Test kernel shutdown
    • Test kernel timeout/restart
    • Test kernel error handling
    • Test introspection (list variables)
    • Test variable type/size inspection
    • All tests use mocked ZMQ and containers

Implementation requirements:

from jupyter_client import KernelManager, BlockingKernelClient
from typing import Any, Dict, List, Optional
import zmq

@dataclass
class KernelInfo:
    """Information about running kernel."""
    kernel_id: str
    container_id: str
    connection_file: Path
    started_at: datetime
    last_activity: datetime

class JupyterKernelManager:
    """Manages Jupyter kernel in container."""
    
    def __init__(
        self,
        container_manager: SecureContainerManager,
        image: str,
        resource_limits: ResourceLimits
    ):
        self.container_manager = container_manager
        self.image = image
        self.resource_limits = resource_limits
        self.kernels: Dict[str, KernelInfo] = {}
    
    def start_kernel(
        self,
        session_id: str,
        volumes: Optional[Dict[str, dict]] = None
    ) -> str:
        """
        Start IPython kernel in container.
        
        Process:
        1. Create container with IPython kernel
        2. Start container
        3. Wait for kernel to be ready
        4. Connect to kernel via ZMQ
        5. Verify kernel is responsive
        
        Returns:
            kernel_id
        """
        pass
    
    def execute_code(
        self,
        kernel_id: str,
        code: str,
        timeout: int = 300
    ) -> ExecutionResult:
        """
        Execute code in kernel.
        
        Uses ZMQ to send execute request and receive result.
        Captures stdout, stderr, display data, and result.
        """
        pass
    
    def shutdown_kernel(self, kernel_id: str) -> None:
        """Shutdown kernel and cleanup container."""
        pass
    
    def inspect_namespace(self, kernel_id: str) -> List[str]:
        """
        Get list of variables in kernel namespace.
        
        Executes: dir() to get variable names
        Filters out private variables and builtins
        """
        pass
    
    def get_variable_info(
        self,
        kernel_id: str,
        variable_name: str
    ) -> Dict[str, Any]:
        """
        Get information about a variable.
        
        Returns:
            {
                "type": str,
                "size_bytes": int (if applicable),
                "shape": tuple (if array-like),
                "repr": str (shortened)
            }
        """
        pass
    
    def restart_kernel(self, kernel_id: str) -> None:
        """Restart kernel (keeps container, resets namespace)."""
        pass
    
    def cleanup_idle_kernels(
        self,
        idle_timeout: timedelta
    ) -> int:
        """
        Cleanup kernels idle longer than timeout.
        
        Returns:
            Number of kernels cleaned up
        """
        pass

Acceptance criteria:

  • Kernel starts successfully in container
  • ZMQ connection established correctly
  • Code execution works via ZMQ protocol
  • Namespace persists between executions
  • Variable introspection works
  • Variable info includes type, size, shape
  • Kernel shutdown cleans up container
  • Idle kernel cleanup works
  • Kernel restart works
  • Handles kernel crashes gracefully
  • All tests use mocked ZMQ and containers
  • 100% test coverage

2.2.2 Session Manager

Path: src/mcp_forge/execution/jupyter/sessions.py

Purpose: Manage stateful execution sessions with state documentation.

Tests to write first:

  • tests/execution/jupyter/test_sessions.py
    • Test create session
    • Test execute in session
    • Test session state persistence
    • Test document state
    • Test retrieve documented state
    • Test session cleanup
    • Test session timeout
    • Test max concurrent sessions enforcement
    • Test session isolation
    • All tests use mocked kernel manager

Implementation requirements:

from typing import Dict, Optional
from datetime import datetime, timedelta
from dataclasses import dataclass, field

@dataclass
class SessionState:
    """Documented state for a session."""
    session_id: str
    documented_variables: Dict[str, str] = field(default_factory=dict)
    note: str = ""
    last_updated: datetime = field(default_factory=datetime.utcnow)
    all_variables: List[str] = field(default_factory=list)
    introspection: Dict[str, dict] = field(default_factory=dict)
    
    def to_dict(self) -> dict:
        """Convert to dictionary for JSON serialization."""
        pass

class Session:
    """Stateful execution session."""
    
    def __init__(
        self,
        session_id: str,
        kernel_id: str,
        created_at: datetime,
        resource_limits: ResourceLimits
    ):
        self.session_id = session_id
        self.kernel_id = kernel_id
        self.created_at = created_at
        self.last_activity = created_at
        self.resource_limits = resource_limits
        self.state = SessionState(session_id=session_id)
    
    def update_activity(self) -> None:
        """Update last activity timestamp."""
        pass
    
    def is_idle(self, timeout: timedelta) -> bool:
        """Check if session is idle beyond timeout."""
        pass

class SessionManager:
    """Manages stateful execution sessions."""
    
    def __init__(
        self,
        config: SessionConfig,
        kernel_manager: JupyterKernelManager,
        audit_logger: AuditLogger
    ):
        self.config = config
        self.kernel_manager = kernel_manager
        self.audit_logger = audit_logger
        self.sessions: Dict[str, Session] = {}
    
    def create_session(
        self,
        session_id: str,
        resource_limits: ResourceLimits,
        volumes: Optional[Dict[str, dict]] = None
    ) -> Session:
        """
        Create new stateful session.
        
        Raises:
            ValueError: If session_id already exists
            RuntimeError: If max concurrent sessions exceeded
        """
        pass
    
    def get_session(self, session_id: str) -> Session:
        """
        Get session by ID.
        
        Raises:
            KeyError: If session doesn't exist
        """
        pass
    
    def execute_in_session(
        self,
        session_id: str,
        code: str,
        timeout: int = 300
    ) -> ExecutionResult:
        """Execute code in session kernel."""
        pass
    
    def document_state(
        self,
        session_id: str,
        variables: Dict[str, str],
        note: str = "",
        clear: bool = False
    ) -> None:
        """
        Document important variables in session.
        
        Updates session.state with variable descriptions.
        Also runs introspection to get current variable info.
        """
        pass
    
    def get_session_state(self, session_id: str) -> SessionState:
        """Get documented state for session."""
        pass
    
    def destroy_session(self, session_id: str) -> None:
        """Destroy session and cleanup kernel."""
        pass
    
    def cleanup_idle_sessions(self) -> int:
        """
        Cleanup sessions idle beyond configured timeout.
        
        Returns:
            Number of sessions cleaned up
        """
        pass
    
    def _enforce_max_concurrent(self) -> None:
        """
        Enforce max concurrent sessions limit.
        
        Raises:
            RuntimeError: If at max concurrent sessions
        """
        pass

Acceptance criteria:

  • Sessions are created with unique IDs
  • Max concurrent sessions enforced
  • Session state persists between executions
  • Variable documentation works
  • State introspection captures type/size info
  • Idle sessions cleaned up automatically
  • Session destruction cleans up kernel
  • Session isolation verified (can't access other sessions)
  • Activity tracking works
  • All tests use mocked components
  • 100% test coverage

2.2.3 Jupyter Backend Implementation

Path: src/mcp_forge/execution/jupyter/backend.py

Purpose: Jupyter backend orchestrating stateful execution.

Tests to write first:

  • tests/execution/jupyter/test_backend.py
    • Test execute creates session if needed
    • Test execute reuses existing session
    • Test execute updates session activity
    • Test session state documentation
    • Test session cleanup
    • All tests use mocked session manager

Implementation requirements:

class JupyterBackend:
    """Stateful code execution backend using Jupyter kernels."""
    
    def __init__(
        self,
        config: ForgeConfig,
        container_manager: SecureContainerManager,
        audit_logger: AuditLogger
    ):
        self.config = config
        self.container_manager = container_manager
        self.audit_logger = audit_logger
        
        # Initialize kernel manager and session manager
        kernel_manager = JupyterKernelManager(
            container_manager=container_manager,
            image=config.images.jupyter,
            resource_limits=self._default_resource_limits()
        )
        
        self.session_manager = SessionManager(
            config=config.sessions,
            kernel_manager=kernel_manager,
            audit_logger=audit_logger
        )
    
    def execute(
        self,
        code: str,
        session_id: str,
        timeout: Optional[int] = None,
        memory: Optional[str] = None,
        cpu_quota: Optional[int] = None,
        custom_image: Optional[str] = None,
        volumes: Optional[Dict[str, dict]] = None
    ) -> ExecutionResult:
        """
        Execute code in stateful session.
        
        Creates session if it doesn't exist.
        Reuses existing session if it exists.
        """
        pass
    
    def document_state(
        self,
        session_id: str,
        variables: Dict[str, str],
        note: str = "",
        clear: bool = False
    ) -> dict:
        """Document session state."""
        pass
    
    def get_session_state(self, session_id: str) -> SessionState:
        """Get session state."""
        pass
    
    def destroy_session(self, session_id: str) -> None:
        """Destroy session."""
        pass
    
    def list_sessions(self) -> List[dict]:
        """List all active sessions with metadata."""
        pass
    
    def _default_resource_limits(self) -> ResourceLimits:
        """Get default resource limits from config."""
        pass

Acceptance criteria:

  • Auto-creates session on first execute
  • Reuses session on subsequent executes
  • Updates session activity timestamp
  • Integrates with session manager correctly
  • State documentation works
  • Session listing works
  • All tests use mocked components
  • 100% test coverage

Phase 3: Custom Environment Builder

3.1 Package Management

3.1.1 Package Validator

Path: src/mcp_forge/builder/package_validator.py

Purpose: Validate package names against allowlist/blocklist.

Tests to write first:

  • tests/builder/test_package_validator.py
    • Test allowlisted package passes
    • Test blocklisted package raises SecurityError
    • Test package requiring approval raises ApprovalRequiredError
    • Test wildcard pattern matching
    • Test version specifier parsing
    • Test max packages enforcement
    • Test load allowlist from file
    • Test load blocklist from file

Implementation requirements:

import re
from typing import List, Set, Pattern
from pathlib import Path

class ApprovalRequiredError(Exception):
    """Raised when package requires manual approval."""
    pass

class PackageValidator:
    """Validates package names against security policy."""
    
    def __init__(self, config: PackageValidationConfig):
        self.config = config
        self.allowlist: Set[str] = self._load_allowlist()
        self.blocklist: Set[str] = self._load_blocklist()
        self.approval_patterns: List[Pattern] = self._compile_patterns()
    
    def validate_packages(
        self,
        packages: List[str],
        max_packages: Optional[int] = None
    ) -> None:
        """
        Validate list of package specifications.
        
        Args:
            packages: List of package specs (e.g., ["numpy>=1.24", "pandas"])
            max_packages: Maximum number of packages allowed
        
        Raises:
            ValueError: If too many packages
            SecurityError: If package is blocklisted
            ApprovalRequiredError: If package requires approval
        """
        pass
    
    def validate_package(self, package_spec: str) -> None:
        """
        Validate single package specification.
        
        Extracts package name from spec (handles >=, ==, <=, etc.)
        Checks against blocklist, allowlist, and approval patterns.
        """
        pass
    
    def extract_package_name(self, package_spec: str) -> str:
        """
        Extract package name from specification.
        
        Examples:
            "numpy>=1.24.0" → "numpy"
            "requests==2.28.0" → "requests"
            "pandas" → "pandas"
        """
        pass
    
    def _load_allowlist(self) -> Set[str]:
        """Load allowlist from file."""
        pass
    
    def _load_blocklist(self) -> Set[str]:
        """Load blocklist from file."""
        pass
    
    def _compile_patterns(self) -> List[Pattern]:
        """Compile approval patterns to regex."""
        pass
    
    def _matches_pattern(self, package_name: str, pattern: Pattern) -> bool:
        """Check if package matches approval pattern."""
        pass

Acceptance criteria:

  • Correctly parses package specifications with version operators
  • Blocklisted packages rejected immediately
  • Allowlisted packages pass validation
  • Packages matching approval patterns raise ApprovalRequiredError
  • Max packages limit enforced
  • Allowlist/blocklist loaded from files
  • Wildcard patterns work (e.g., crypto)
  • Clear error messages indicate which package failed
  • 100% test coverage

3.1.2 UV Package Installer

Path: src/mcp_forge/builder/uv_installer.py

Purpose: Install packages using UV with caching.

Tests to write first:

  • tests/builder/test_uv_installer.py
    • Test generate requirements file
    • Test generate Containerfile
    • Test UV installation script
    • Test cache directory handling
    • Test build with cache hit
    • Test build with cache miss
    • All tests use mocked file operations

Implementation requirements:

from typing import List, Optional
from pathlib import Path
import tempfile
import shutil

class UVInstaller:
    """Manages UV-based package installation in containers."""
    
    def __init__(self, cache_path: Path):
        self.cache_path = cache_path
        self._ensure_cache_dir()
    
    def generate_containerfile(
        self,
        base_image: str,
        packages: List[str],
        python_version: str = "3.11"
    ) -> str:
        """
        Generate Containerfile for building custom environment.
        
        Containerfile structure:
        1. Base image
        2. Install UV (cached layer)
        3. Create user
        4. Copy requirements.txt (cache-friendly)
        5. Install packages with UV
        6. Copy MCP tools
        7. Set working directory
        """
        pass
    
    def generate_requirements(self, packages: List[str]) -> str:
        """
        Generate requirements.txt content.
        
        One package per line with version specifiers preserved.
        """
        pass
    
    def create_build_context(
        self,
        base_image: str,
        packages: List[str],
        python_version: str = "3.11"
    ) -> Path:
        """
        Create temporary build context directory.
        
        Contains:
        - Containerfile
        - requirements.txt
        - mcp_tools.py (if exists)
        
        Returns:
            Path to build context directory (caller must cleanup)
        """
        pass
    
    def _ensure_cache_dir(self) -> None:
        """Ensure UV cache directory exists."""
        pass
    
    def _get_cache_volume_mount(self) -> dict:
        """Get volume mount configuration for UV cache."""
        pass

Acceptance criteria:

  • Generates valid Containerfile
  • Containerfile uses multi-stage caching
  • Requirements.txt has one package per line
  • UV installation layer is cached
  • Cache directory mounted correctly during build
  • Build context is temporary and isolated
  • All required files included in context
  • 100% test coverage

3.2 Environment Building

3.2.1 Image Builder

Path: src/mcp_forge/builder/image_builder.py

Purpose: Build container images using Podman with security scanning.

Tests to write first:

  • tests/builder/test_image_builder.py
    • Test build image from context
    • Test build with cache
    • Test build timeout enforcement
    • Test image size validation
    • Test image tagging
    • Test build failure handling
    • All tests use mocked Podman client

Implementation requirements:

from typing import Optional, List
from datetime import datetime
import hashlib

@dataclass
class BuildResult:
    """Result of image build."""
    success: bool
    image_name: str
    image_id: str
    build_time: float
    size_bytes: int
    cache_hit: bool
    installed_packages: List[str]
    error: Optional[str] = None
    
    def to_dict(self) -> dict:
        """Convert to dictionary."""
        pass

class ImageBuilder:
    """Builds container images with security validation."""
    
    def __init__(
        self,
        podman_client: PodmanClient,
        config: EnvironmentBuilderConfig,
        audit_logger: AuditLogger
    ):
        self.podman = podman_client
        self.config = config
        self.audit_logger = audit_logger
    
    def build_image(
        self,
        name: str,
        build_context: Path,
        base_image: str,
        packages: List[str],
        timeout: Optional[int] = None
    ) -> BuildResult:
        """
        Build container image from build context.
        
        Process:
        1. Validate build context
        2. Generate image tag
        3. Build image with Podman
        4. Validate image size
        5. Tag image
        6. Cleanup build artifacts
        
        Args:
            name: Environment name (user-provided)
            build_context: Path to build context directory
            base_image: Base image to build from
            packages: List of packages being installed
            timeout: Build timeout (uses config default if None)
        
        Returns:
            BuildResult
        
        Raises:
            ValueError: If timeout exceeds max
            RuntimeError: If build fails
        """
        pass
    
    def _generate_tag(self, name: str) -> str:
        """
        Generate image tag.
        
        Format: mcp-forge/custom:{name}
        Validates name is alphanumeric + hyphens only.
        """
        pass
    
    def _validate_image_size(self, image_id: str) -> int:
        """
        Validate image size against maximum.
        
        Returns:
            Size in bytes
        
        Raises:
            ValueError: If image exceeds max size
        """
        pass
    
    def _extract_installed_packages(self, image_id: str) -> List[str]:
        """
        Extract list of installed packages from image.
        
        Runs: pip list --format=json in container
        """
        pass
    
    def _calculate_cache_hash(self, packages: List[str]) -> str:
        """Calculate hash of package list for cache key."""
        pass

Acceptance criteria:

  • Builds image successfully with Podman
  • Enforces build timeout
  • Validates image size against maximum
  • Tags image correctly (mcp-forge/custom:name)
  • Extracts installed packages list
  • Logs build to audit log
  • Handles build failures gracefully
  • Name validation (alphanumeric + hyphens only)
  • All tests use mocked Podman
  • 100% test coverage

3.2.2 Security Scanner

Path: src/mcp_forge/builder/security_scanner.py

Purpose: Scan images for security vulnerabilities (optional, uses trivy if available).

Tests to write first:

  • tests/builder/test_security_scanner.py
    • Test scan with trivy available
    • Test scan with trivy unavailable (skip)
    • Test parse trivy JSON output
    • Test vulnerability severity classification
    • Test critical vulnerabilities rejected
    • All tests use mocked subprocess

Implementation requirements:

from typing import Optional, List
from dataclasses import dataclass
from enum import Enum
import subprocess
import json

class VulnerabilitySeverity(Enum):
    UNKNOWN = "UNKNOWN"
    LOW = "LOW"
    MEDIUM = "MEDIUM"
    HIGH = "HIGH"
    CRITICAL = "CRITICAL"

@dataclass
class Vulnerability:
    """Security vulnerability."""
    cve_id: str
    severity: VulnerabilitySeverity
    package: str
    fixed_version: Optional[str]
    description: str

@dataclass
class ScanResult:
    """Result of security scan."""
    scanned: bool
    total_vulns: int
    critical_vulns: int
    high_vulns: int
    medium_vulns: int
    low_vulns: int
    vulnerabilities: List[Vulnerability]
    
    def has_critical(self) -> bool:
        """Check if scan found critical vulnerabilities."""
        return self.critical_vulns > 0

class SecurityScanner:
    """Scans container images for vulnerabilities."""
    
    def __init__(self, enabled: bool = True):
        self.enabled = enabled
        self._trivy_available: Optional[bool] = None
    
    def scan_image(self, image_id: str) -> ScanResult:
        """
        Scan image for vulnerabilities.
        
        Uses trivy if available, otherwise returns empty result.
        
        Args:
            image_id: Image ID or tag to scan
        
        Returns:
            ScanResult with vulnerabilities found
        """
        pass
    
    def is_trivy_available(self) -> bool:
        """Check if trivy is installed and available."""
        pass
    
    def _run_trivy_scan(self, image_id: str) -> dict:
        """
        Run trivy scan and return JSON results.
        
        Command: trivy image --format json {image_id}
        """
        pass
    
    def _parse_trivy_output(self, trivy_json: dict) -> ScanResult:
        """Parse trivy JSON output to ScanResult."""
        pass

Acceptance criteria:

  • Detects if trivy is available
  • Skips scan gracefully if trivy not available
  • Runs trivy correctly if available
  • Parses trivy JSON output correctly
  • Classifies vulnerabilities by severity
  • Returns accurate counts
  • All tests use mocked subprocess
  • 100% test coverage

3.2.3 Environment Builder

Path: src/mcp_forge/builder/environment_builder.py

Purpose: Orchestrate custom environment building with all validations.

Tests to write first:

  • tests/builder/test_environment_builder.py
    • Test build with valid packages
    • Test build with blocklisted package fails
    • Test build with too many packages fails
    • Test build with critical vulns fails
    • Test build timeout enforcement
    • Test build rate limiting
    • Test concurrent build limiting
    • Test template expansion
    • All tests use mocked components

Implementation requirements:

from typing import Optional, Dict, List
from datetime import datetime, timedelta
import threading

class BuildRateLimiter:
    """Rate limiter for build requests."""
    
    def __init__(self, max_requests: int, period_seconds: int):
        self.max_requests = max_requests
        self.period_seconds = period_seconds
        self.requests: Dict[str, List[datetime]] = {}
        self.lock = threading.Lock()
    
    def check_rate_limit(self, user_id: str) -> None:
        """
        Check if user is within rate limit.
        
        Raises:
            RuntimeError: If rate limit exceeded
        """
        pass
    
    def _cleanup_old_requests(self, user_id: str) -> None:
        """Remove requests older than period."""
        pass

class EnvironmentBuilder:
    """Builds custom Python environments with security validation."""
    
    def __init__(
        self,
        config: EnvironmentBuilderConfig,
        podman_client: PodmanClient,
        audit_logger: AuditLogger
    ):
        self.config = config
        self.podman = podman_client
        self.audit_logger = audit_logger
        
        # Initialize sub-components
        self.package_validator = PackageValidator(
            config.package_validation
        )
        self.uv_installer = UVInstaller(config.uv_cache_path)
        self.image_builder = ImageBuilder(
            podman_client, config, audit_logger
        )
        self.security_scanner = SecurityScanner(enabled=True)
        
        # Rate limiting and concurrency control
        self.rate_limiter = BuildRateLimiter(
            max_requests=config.build_rate_limit['requests'],
            period_seconds=config.build_rate_limit['period']
        )
        self.active_builds: Set[str] = set()
        self.active_builds_lock = threading.Lock()
    
    def build_custom_environment(
        self,
        name: str,
        packages: List[str],
        base_image: str = "python:3.11-slim",
        python_version: str = "3.11",
        description: str = "",
        user_id: str = "default"
    ) -> BuildResult:
        """
        Build custom environment with packages.
        
        Process:
        1. Check rate limit
        2. Check concurrent builds limit
        3. Validate package count
        4. Validate package names (allowlist/blocklist)
        5. Generate build context with UV
        6. Build image
        7. Scan for vulnerabilities
        8. Validate scan results
        9. Tag and register environment
        10. Cleanup build context
        
        Args:
            name: Environment name (alphanumeric + hyphens)
            packages: List of package specifications
            base_image: Base image to build from
            python_version: Python version
            description: Optional description
            user_id: User ID for rate limiting
        
        Returns:
            BuildResult
        
        Raises:
            ValueError: If validation fails
            SecurityError: If security check fails
            RuntimeError: If rate limit or concurrency exceeded
        """
        pass
    
    def build_from_template(
        self,
        template_name: str,
        additional_packages: Optional[List[str]] = None,
        name: Optional[str] = None,
        user_id: str = "default"
    ) -> BuildResult:
        """
        Build environment from template.
        
        Expands template packages and adds additional packages.
        """
        pass
    
    def list_templates(self) -> Dict[str, dict]:
        """List available templates."""
        return self.config.templates
    
    def _check_concurrent_builds(self) -> None:
        """
        Check concurrent builds limit.
        
        Raises:
            RuntimeError: If at max concurrent builds
        """
        pass
    
    def _register_build_start(self, name: str) -> None:
        """Register build as started."""
        pass
    
    def _register_build_complete(self, name: str) -> None:
        """Register build as completed."""
        pass

Acceptance criteria:

  • Complete validation pipeline works
  • Rate limiting enforced per user
  • Concurrent builds limited
  • Package validation runs first (fail fast)
  • UV build context generated correctly
  • Image building works
  • Security scanning runs (if available)
  • Critical vulnerabilities rejected
  • Build context cleaned up even on failure
  • Templates expand correctly
  • All validations logged to audit log
  • All tests use mocked components
  • 100% test coverage

Phase 4: MCP Tool Integration

IMPORTANT: MCP Client Library Choice

This project uses fastmcp (https://gofastmcp.com) for MCP client operations instead of the standard mcp SDK. fastmcp provides:

  • Modern, production-ready client implementation
  • Automatic structured data deserialization
  • Multiple transport options (STDIO, HTTP, SSE)
  • Session persistence and caching
  • Better error handling and connection management

Note: Example code in this document may show standard mcp SDK patterns for illustration. The actual implementation uses fastmcp APIs.


4.1 MCP Client Management

4.1.1 MCP Client Wrapper

Path: src/mcp_forge/mcp/client.py

Purpose: Connect to and manage MCP server clients using fastmcp.

Tests written:

  • tests/mcp/test_client.py (12 tests)
    • Test connect to MCP server
    • Test connect with environment variables
    • Test disconnect from server
    • Test list available tools
    • Test list tools when not connected
    • Test get tool schema
    • Test get tool schema for unknown tool
    • Test call tool successfully
    • Test call tool when not connected
    • Test tool call failure handling
    • Test connection failure
    • Test reconnection after disconnect

Implementation complete:

from fastmcp import Client
from fastmcp.client.transports import StdioTransport

class MCPClientWrapper:
    """Wrapper for fastmcp Client connection."""
    
    def __init__(name, command, args, env)
    async def connect() -> None
    async def disconnect() -> None
    def is_connected() -> bool
    async def list_tools() -> List[str]
    async def get_tool_schema(tool_name) -> dict
    async def call_tool(tool_name, arguments) -> Any

Key features:

  • Uses fastmcp for MCP client connections
  • STDIO transport with environment variable support
  • Automatic structured data deserialization via result.data
  • Tools caching for performance
  • Comprehensive error handling
  • All tests use mocked fastmcp client

Acceptance criteria:

  • Connects to MCP server successfully using fastmcp
  • Lists available tools correctly
  • Gets tool schemas correctly
  • Calls tools with proper argument passing
  • Returns structured results (result.data when available)
  • Handles connection failures gracefully
  • Disconnects cleanly
  • All tests use mocked fastmcp client
  • 100% test coverage (12 tests passing)

4.1.2 MCP Client Manager

Path: src/mcp_forge/mcp/manager.py

Purpose: Manage multiple MCP client connections.

Tests written:

  • tests/mcp/test_manager.py (9 tests)
    • Test initialize clients from config with correct parameters
    • Test get client for tool returns correct client
    • Test get client for unknown tool raises KeyError
    • Test list all tools across all clients
    • Test detect tool name collision raises ValueError
    • Test get tool schema routes to correct client
    • Test call tool routes to correct client with arguments
    • Test shutdown all clients cleanly
    • Test manager before initialization raises RuntimeError

Implementation complete:

class MCPClientManager:
    """Manages multiple MCP client connections."""
    
    def __init__(config: Dict[str, Dict[str, Any]])
    async def initialize() -> None
    async def get_client_for_tool(tool_name) -> MCPClientWrapper
    async def list_all_tools() -> List[str]
    async def get_tool_schema(tool_name) -> dict
    async def call_tool(tool_name, arguments) -> Any
    async def shutdown() -> None
    def _check_initialized() -> None
    async def _build_tool_mapping() -> None

Key features:

  • Initializes multiple MCPClientWrapper instances from config dictionary
  • Builds comprehensive tool-to-client mapping for routing
  • Detects and rejects tool name collisions with detailed error messages
  • Routes tool calls to the correct client automatically
  • Aggregates tools from all clients
  • Clean shutdown of all client connections
  • Initialization state checking with clear error messages
  • All tests use mocked fastmcp clients

Acceptance criteria:

  • Initializes all clients from config
  • Builds tool-to-client mapping
  • Detects and rejects tool name collisions
  • Routes tool calls to correct client
  • Lists all tools across clients
  • Shuts down all clients cleanly
  • All tests use mocked clients
  • 100% test coverage (9 tests passing)

4.2 MCP Tool Injection

4.2.1 Tool Bridge Server

Path: src/mcp_forge/mcp/bridge.py

Purpose: Unix socket server that forwards tool calls from container to MCP clients.

Tests written:

  • tests/mcp/test_bridge.py (8 tests)
    • Test bridge server starts and stops cleanly
    • Test receive and forward tool call to client manager
    • Test handle tool call error from client
    • Test handle invalid JSON in request
    • Test handle missing 'tool' field in request
    • Test concurrent requests handling
    • Test audit logging includes tool name only (not params)
    • Test socket cleanup on error

Implementation complete:

class ToolBridgeServer:
    """Unix socket server for MCP tool calls from containers."""
    
    def __init__(socket_path, client_manager, audit_logger)
    def start() -> None
    def stop() -> None
    def _run_server() -> None
    def _handle_connection(conn) -> None
    async def _call_tool_async(tool_name, params) -> dict
    def _send_error(conn, error) -> None

Key features:

  • Unix socket server with AF_UNIX/SOCK_STREAM
  • Background thread with graceful shutdown
  • JSON protocol: {"tool": "name", "params": {...}}
  • Async tool forwarding to MCPClientManager
  • Concurrent request handling via separate threads
  • Error responses with JSON format
  • Audit logging (tool name only, parameters excluded for security)
  • Automatic socket cleanup on shutdown

Acceptance criteria:

  • Server starts and listens on Unix socket
  • Accepts connections from containers
  • Parses JSON tool call requests
  • Forwards to correct MCP client
  • Returns results as JSON
  • Handles errors gracefully
  • Supports concurrent requests
  • Cleans up socket on shutdown
  • Logs tool calls to audit log (tool name only, not params)
  • All tests use mocked sockets and clients
  • 100% test coverage (8 tests passing)

4.2.2 Tool Injection Generator

Path: src/mcp_forge/mcp/injection.py

Purpose: Generate Python code to inject MCP tools into container namespace.

Tests written:

  • tests/mcp/test_injection.py (11 tests)
    • Test generated code is valid Python (AST parse)
    • Test generated code includes bridge client function
    • Test generated code includes tool wrapper functions
    • Test function signatures match tool schemas
    • Test generated functions have docstrings with descriptions
    • Test generated functions call bridge (_mcp_call)
    • Test type hints generated from JSON schema types
    • Test required vs optional parameters
    • Test generated code has necessary imports
    • Test empty tool list handling
    • Test custom socket path usage

Implementation complete:

class ToolInjectionGenerator:
    """Generates Python code to inject MCP tools into container."""
    
    def __init__(client_manager)
    async def generate_injection_code(tool_names, bridge_socket_path) -> str
    def _generate_imports() -> str
    def _generate_bridge_client(socket_path) -> str
    async def _generate_tool_function(tool_name, tool_schema) -> str
    def _extract_parameters(schema) -> List[Tuple]
    def _json_type_to_python(json_type) -> str

Key features:

  • Generates valid, executable Python code
  • Bridge client uses Unix socket to communicate with server
  • Tool functions with proper signatures from schemas
  • Type hints: string→str, integer→int, number→float, boolean→bool, array→list, object→dict
  • Required parameters (no default) vs optional (default=None)
  • Docstrings with tool descriptions and parameter docs
  • Each function calls _mcp_call(tool_name, **kwargs)
  • Socket path configurable for different deployment scenarios
  • Comprehensive imports (socket, json, typing.Any)

Acceptance criteria:

  • Generated code is valid Python

  • Function signatures match tool schemas

  • Type hints included from schema

  • Docstrings include tool descriptions

  • Bridge client code included

  • Generated code can be executed without errors

  • All required parameters are non-optional

  • Optional parameters have defaults

  • 100% test coverage (11 tests passing) Generate Python code that provides MCP tools as functions.

      Generated code includes:
      1. Bridge client to communicate with MCP bridge server
      2. Wrapper function for each tool with proper signature
      3. Docstrings from tool schemas
      4. Type hints from tool schemas
    
      Args:
          tool_names: List of MCP tool names to inject
          bridge_socket_path: Path to bridge socket in container
    
      Returns:
          Python code as string
      """
      pass
    

    def _generate_bridge_client(self, socket_path: str) -> str: """Generate code for bridge client communication.""" return ''' import socket import json from typing import Any

def _mcp_call(tool_name: str, **kwargs) -> Any: """Internal: Call MCP tool via bridge.""" s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect('{socket_path}') request = json.dumps({{'tool': tool_name, 'params': kwargs}}) s.sendall(request.encode('utf-8'))

# Receive response
response_data = b''
while True:
    chunk = s.recv(4096)
    if not chunk:
        break
    response_data += chunk

s.close()
response = json.loads(response_data.decode('utf-8'))

if not response.get('success'):
    raise RuntimeError(f"Tool call failed: {{response.get('error')}}")

return response.get('result')

'''.format(socket_path=socket_path)

async def _generate_tool_function(
    self,
    tool_name: str,
    tool_schema: dict
) -> str:
    """
    Generate wrapper function for a single tool.
    
    Includes:
    - Function signature from schema parameters
    - Type hints
    - Docstring with description and parameters
    - Call to _mcp_call()
    """
    pass

def _extract_parameters(self, schema: dict) -> List[tuple]:
    """
    Extract parameter definitions from tool schema.
    
    Returns:
        List of (name, type_hint, required, default) tuples
    """
    pass

def _json_type_to_python(self, json_type: str) -> str:
    """Convert JSON schema type to Python type hint."""
    type_map = {
        "string": "str",
        "integer": "int",
        "number": "float",
        "boolean": "bool",
        "array": "list",
        "object": "dict",
    }
    return type_map.get(json_type, "Any")

**Acceptance criteria:**
- [ ] Generated code is valid Python
- [ ] Function signatures match tool schemas
- [ ] Type hints included from schema
- [ ] Docstrings include tool descriptions
- [ ] Bridge client code included
- [ ] Generated code can be executed without errors
- [ ] All required parameters are non-optional
- [ ] Optional parameters have defaults
- [ ] 100% test coverage

---

## Phase 5: MCP Server Implementation

### 5.1 MCP Resources

#### 5.1.1 Resource Handlers
**Path:** `src/mcp_forge/server/resources.py`

**Purpose:** Implement MCP resource handlers for discovery and state.

**Tests to write first:**
- `tests/server/test_resources.py`
  - Test tools/available resource
  - Test sessions/{id}/state resource
  - Test sessions/{id}/variables resource
  - Test environments/list resource
  - Test environment/info resource
  - Test resource not found handling
  - All tests use mocked backends

**Implementation requirements:**

*Note: The example code below uses standard mcp.server patterns for illustration. Phase 5 server implementation may use fastmcp server features or standard mcp server depending on requirements.*

```python
from mcp.server import Server
from mcp.types import Resource, TextContent
from typing import Optional, List
import json

class ResourceHandler:
    """Handles MCP resource requests."""
    
    def __init__(
        self,
        client_manager: MCPClientManager,
        session_manager: SessionManager,
        environment_builder: EnvironmentBuilder,
        config: ForgeConfig
    ):
        self.client_manager = client_manager
        self.session_manager = session_manager
        self.environment_builder = environment_builder
        self.config = config
    
    async def handle_resource(self, uri: str) -> Resource:
        """
        Handle resource request based on URI.
        
        Supported URIs:
        - mcp://forge/tools/available
        - mcp://forge/sessions/{id}/state
        - mcp://forge/sessions/{id}/variables
        - mcp://forge/environments/list
        - mcp://forge/environment/info
        
        Raises:
            ValueError: If URI not recognized
        """
        pass
    
    async def _handle_tools_available(self) -> Resource:
        """Return list of available MCP tools."""
        tools = await self.client_manager.list_all_tools()
        return Resource(
            uri="mcp://forge/tools/available",
            mimeType="application/json",
            text=json.dumps(tools)
        )
    
    async def _handle_session_state(self, session_id: str) -> Resource:
        """Return documented state for session."""
        state = self.session_manager.get_session_state(session_id)
        return Resource(
            uri=f"mcp://forge/sessions/{session_id}/state",
            mimeType="application/json",
            text=json.dumps(state.to_dict())
        )
    
    async def _handle_session_variables(self, session_id: str) -> Resource:
        """Return list of variables in session."""
        state = self.session_manager.get_session_state(session_id)
        return Resource(
            uri=f"mcp://forge/sessions/{session_id}/variables",
            mimeType="application/json",
            text=json.dumps(state.all_variables)
        )
    
    async def _handle_environments_list(self) -> Resource:
        """Return list of custom environments and templates."""
        pass
    
    async def _handle_environment_info(self) -> Resource:
        """Return environment information."""
        pass
    
    def _parse_uri(self, uri: str) -> tuple:
        """
        Parse URI into components.
        
        Returns:
            (resource_type, parameters)
        """
        pass

Acceptance criteria:

  • All resource URIs handled correctly
  • Returns proper Resource objects
  • JSON content is valid
  • Session resources validate session exists
  • Clear errors for invalid URIs
  • Clear errors for non-existent sessions
  • All tests use mocked components
  • 100% test coverage

5.2 MCP Tools

5.2.1 Execute Python Tool

Path: src/mcp_forge/server/tools/execute_python.py

Purpose: Implement execute_python MCP tool.

Tests to write first:

  • tests/server/tools/test_execute_python.py
    • Test execute with simple backend
    • Test execute with jupyter backend
    • Test execute with MCP tools
    • Test execute with custom image
    • Test execute with environment template
    • Test parameter validation
    • Test resource limit validation
    • All tests use mocked backends

Implementation requirements:

Note: The example code below uses standard mcp.types patterns for illustration. Phase 5 server implementation may use fastmcp server features or standard mcp server depending on requirements.

from mcp.types import Tool, TextContent
from typing import Optional, List, Dict, Any
import json

class ExecutePythonTool:
    """MCP tool for executing Python code."""
    
    def __init__(
        self,
        simple_backend: SimpleBackend,
        jupyter_backend: JupyterBackend,
        client_manager: MCPClientManager,
        bridge_server: ToolBridgeServer,
        injection_generator: ToolInjectionGenerator,
        config: ForgeConfig
    ):
        self.simple_backend = simple_backend
        self.jupyter_backend = jupyter_backend
        self.client_manager = client_manager
        self.bridge_server = bridge_server
        self.injection_generator = injection_generator
        self.config = config
    
    def get_tool_definition(self) -> Tool:
        """Return MCP tool definition."""
        return Tool(
            name="execute_python",
            description="Execute Python code in isolated container with MCP tools available",
            inputSchema={
                "type": "object",
                "properties": {
                    "code": {
                        "type": "string",
                        "description": "Python code to execute"
                    },
                    "mcp_tools": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "List of MCP tool names to inject"
                    },
                    "session_id": {
                        "type": "string",
                        "description": "Session ID for stateful execution (null = stateless)"
                    },
                    "backend": {
                        "type": "string",
                        "enum": ["simple", "jupyter"],
                        "description": "Backend to use"
                    },
                    "timeout": {
                        "type": "integer",
                        "description": "Max execution time in seconds"
                    },
                    "custom_image": {
                        "type": "string",
                        "description": "Custom environment name"
                    },
                    "environment": {
                        "type": "string",
                        "description": "Template environment name"
                    }
                },
                "required": ["code"]
            }
        )
    
    async def execute(self, arguments: dict) -> List[TextContent]:
        """
        Execute Python code with MCP tool injection.
        
        Process:
        1. Validate arguments
        2. Determine backend (simple vs jupyter)
        3. Generate tool injection code if mcp_tools specified
        4. Inject tools into container
        5. Execute code
        6. Return result
        """
        pass
    
    def _validate_arguments(self, arguments: dict) -> None:
        """Validate tool arguments."""
        pass
    
    async def _inject_tools(
        self,
        container_id: str,
        tool_names: List[str]
    ) -> None:
        """
        Inject MCP tools into container.
        
        Generates injection code and writes to container filesystem.
        Mounts bridge socket into container.
        """
        pass
    
    def _select_backend(
        self,
        session_id: Optional[str],
        backend: Optional[str]
    ) -> str:
        """Determine which backend to use."""
        if session_id is not None:
            return "jupyter"
        return backend or self.config.execution.default_backend

Acceptance criteria:

  • Tool definition matches architecture spec
  • Validates all arguments
  • Routes to correct backend
  • Injects MCP tools correctly
  • Mounts bridge socket
  • Returns proper MCP response format
  • Handles errors gracefully
  • All tests use mocked components
  • 100% test coverage

5.2.2 Document State Tool

Path: src/mcp_forge/server/tools/document_state.py

Purpose: Implement document_state MCP tool.

Tests to write first:

  • tests/server/tools/test_document_state.py
    • Test document variables
    • Test document with note
    • Test clear existing documentation
    • Test session validation
    • All tests use mocked session manager

Implementation requirements:

class DocumentStateTool:
    """MCP tool for documenting session state."""
    
    def __init__(self, session_manager: SessionManager):
        self.session_manager = session_manager
    
    def get_tool_definition(self) -> Tool:
        """Return MCP tool definition."""
        pass
    
    async def execute(self, arguments: dict) -> List[TextContent]:
        """
        Document session state.
        
        Updates session's documented variables and note.
        """
        pass

Acceptance criteria:

  • Tool definition matches architecture spec
  • Documents variables correctly
  • Updates session state
  • Clear option works
  • Validates session exists
  • All tests use mocked components
  • 100% test coverage

5.2.3 Build Custom Environment Tool

Path: src/mcp_forge/server/tools/build_environment.py

Purpose: Implement build_custom_environment MCP tool.

Tests to write first:

  • tests/server/tools/test_build_environment.py
    • Test build with valid packages
    • Test build with template
    • Test validation errors
    • Test rate limiting
    • All tests use mocked builder

Implementation requirements:

class BuildEnvironmentTool:
    """MCP tool for building custom environments."""
    
    def __init__(
        self,
        environment_builder: EnvironmentBuilder,
        audit_logger: AuditLogger
    ):
        self.builder = environment_builder
        self.audit_logger = audit_logger
    
    def get_tool_definition(self) -> Tool:
        """Return MCP tool definition."""
        pass
    
    async def execute(self, arguments: dict) -> List[TextContent]:
        """
        Build custom environment.
        
        Validates packages, builds image, scans for vulnerabilities.
        Returns build result with image name and installed packages.
        """
        pass

Acceptance criteria:

  • Tool definition matches architecture spec
  • Validates all arguments
  • Calls builder correctly
  • Returns build result
  • Logs to audit log
  • All tests use mocked components
  • 100% test coverage

5.3 MCP Server

Note: Use FastMCP for the server implementation. For documentation, see

5.3.1 Server Implementation

Path: src/mcp_forge/server/server.py

Purpose: Main MCP server implementation.

Tests to write first:

  • tests/server/test_server.py
    • Test server initialization
    • Test tool registration
    • Test resource registration
    • Test server startup
    • Test server shutdown
    • All tests use mocked components

Implementation requirements:

from mcp.server import Server
from mcp.server.stdio import stdio_server
import asyncio

class ForgeServer:
    """MCP-Forge server implementation."""
    
    def __init__(self, config: ForgeConfig):
        self.config = config
        self.server = Server("mcp-forge")
        
        # Initialize all components
        self._init_security()
        self._init_podman()
        self._init_mcp_clients()
        self._init_backends()
        self._init_builder()
        self._init_tools()
        self._init_resources()
    
    def _init_security(self) -> None:
        """Initialize security components."""
        self.audit_logger = AuditLogger(self.config.security.audit_log)
        self.operation_validator = OperationValidator(self.config.security)
    
    def _init_podman(self) -> None:
        """Initialize Podman client and container manager."""
        self.podman_client = PodmanClient(
            socket_path=self.config.server.podman_socket,
            validator=self.operation_validator,
            audit_logger=self.audit_logger
        )
        self.container_manager = SecureContainerManager(
            podman_client=self.podman_client,
            validator=self.operation_validator,
            audit_logger=self.audit_logger
        )
    
    def _init_mcp_clients(self) -> None:
        """Initialize MCP client manager and bridge server."""
        pass
    
    def _init_backends(self) -> None:
        """Initialize execution backends."""
        pass
    
    def _init_builder(self) -> None:
        """Initialize environment builder."""
        pass
    
    def _init_tools(self) -> None:
        """Register MCP tools."""
        pass
    
    def _init_resources(self) -> None:
        """Register MCP resources."""
        pass
    
    async def run(self) -> None:
        """Run server on stdio."""
        async with stdio_server() as (read_stream, write_stream):
            await self.server.run(
                read_stream,
                write_stream,
                self.server.create_initialization_options()
            )
    
    async def shutdown(self) -> None:
        """Shutdown server and cleanup."""
        pass

Acceptance criteria:

  • All components initialized correctly
  • Tools registered with server
  • Resources registered with server
  • Server runs on stdio
  • Graceful shutdown works
  • All tests use mocked components
  • Integration test verifies full stack
  • 100% test coverage

Phase 6: Testing & Integration

6.1 Integration Tests

6.1.1 End-to-End Tests

Path: tests/integration/test_e2e.py

Purpose: Test complete workflows end-to-end.

Tests to write:

  • Test simple code execution workflow
  • Test stateful session workflow
  • Test custom environment build and use
  • Test MCP tool injection and usage
  • Test session state documentation and retrieval
  • Test concurrent executions
  • Test resource cleanup

Requirements:

  • Use real Podman (in CI/CD environment)
  • Use real MCP servers (mocked external services)
  • Test actual container creation and execution
  • Verify security controls are enforced
  • Test error handling and recovery

6.1.2 Performance Tests

Path: tests/performance/test_performance.py

Purpose: Test performance characteristics.

Tests to write:

  • Test container startup time
  • Test code execution overhead
  • Test concurrent execution scalability
  • Test session creation overhead
  • Test environment build time
  • Test cache effectiveness

6.2 Security Testing

6.2.1 Security Tests

Path: tests/security/test_security.py

Purpose: Verify security controls.

Tests to write:

  • Test privileged mode is rejected
  • Test forbidden operations are blocked
  • Test volume mount restrictions
  • Test resource limits are enforced
  • Test network isolation
  • Test package blocklist enforcement
  • Test container escape prevention
  • Test audit logging completeness

Phase 7: Documentation & Deployment

7.1 Documentation

7.1.1 User Documentation

  • README.md with:
    • Project overview and features
    • Quick start guide
    • Basic usage examples
    • Link to full documentation
  • Installation guide:
    • System requirements
    • Podman setup (rootless)
    • MCP-Forge installation
    • Configuration setup
    • Verification steps
  • Configuration reference:
    • Complete configuration schema
    • All options explained
    • Environment variable reference
    • Security configuration guide
  • User guide:
    • Using execute_python tool
    • Stateful vs stateless execution
    • Building custom environments
    • MCP tool integration
    • Session management
    • Best practices

7.1.2 API Documentation

  • MCP Protocol documentation:
    • Resources (URIs, response formats)
    • Tools (parameters, return values)
  • Python API documentation:
    • All public classes and functions
    • Usage examples
    • Type signatures

7.1.3 Security Documentation

  • Security model overview
  • Threat model
  • Security best practices:
    • Podman configuration
    • Network isolation
    • Volume mount security
    • Package validation
    • Audit logging
    • Resource limits
  • Security audit checklist
  • Incident response guide

7.1.4 Operations Documentation

  • Deployment guide
  • Monitoring guide
  • Backup and restore
  • Troubleshooting guide:
    • Common errors
    • Diagnostic procedures
    • Log analysis
    • Performance tuning
  • Upgrade guide

7.1.5 Developer Documentation

  • Development setup
  • Architecture overview (link to architecture1.md)
  • Contributing guide
  • Testing guide
  • Code style guide
  • Release process

7.2 Deployment

7.2.1 Container Images

  • Build mcp-forge/python:3.11 base image
  • Build mcp-forge/python:3.12 base image
  • Build mcp-forge/jupyter:latest image
  • Pre-install common packages in base images
  • Include MCP bridge client (mcp_tools.py)
  • Include startup scripts

7.2.2 Deployment Configurations

  • Docker Compose configuration for production
  • Kubernetes deployment manifests (optional)
  • systemd service file
  • Example mcp-forge.yaml configurations:
    • Development (minimal)
    • Production (full security)
    • High-concurrency setup
  • Environment variable templates

7.2.3 Monitoring & Observability

  • Prometheus metrics exporter (optional)
  • Health check endpoints
  • Log aggregation setup (e.g., Loki)
  • Grafana dashboard examples
  • Alert rules for:
    • High memory usage
    • Container creation failures
    • Security violations
    • Build failures

7.2.4 Operations

  • Backup and restore procedures
  • Session data management
  • Image cleanup scripts
  • Cache management (UV cache, image layers)
  • Log rotation config
  • Update procedures

Development Guidelines

Code Quality Standards

  1. Type Hints: All functions must have type hints
  2. Docstrings: All public functions/classes must have docstrings
  3. Error Handling: Use specific exception types
  4. Logging: Use structured logging (JSON)
  5. Testing: Minimum 90% code coverage
  6. Linting: Pass mypy, ruff, black

Test Requirements

  1. Unit Tests: Test individual functions/classes in isolation
  2. Integration Tests: Test component integration
  3. Mocking: Use pytest-mock for external dependencies
  4. Fixtures: Use pytest fixtures for common test setup
  5. Parametrization: Use pytest.mark.parametrize for multiple cases

Security Requirements

  1. No eval/exec: Never use eval() or exec()
  2. Input Validation: Validate all user inputs
  3. Resource Limits: Always enforce resource limits
  4. Audit Logging: Log all security-relevant operations
  5. Least Privilege: Run with minimum required privileges

Progress Tracking

Use this checklist to track completion:

  • Phase 1: Foundation & Core Infrastructure
    • 1.1 Configuration Management
    • 1.2 Security & Validation Core
    • 1.3 Podman Integration Core
  • Phase 2: Execution Backends
    • 2.1 Simple Backend
    • 2.2 Jupyter Backend
  • Phase 3: Custom Environment Builder
    • 3.1 Package Management
    • 3.2 Environment Building
  • Phase 4: MCP Tool Integration
    • 4.1 MCP Client Management
    • 4.2 MCP Tool Injection
  • Phase 5: MCP Server Implementation
    • 5.1 MCP Resources
    • 5.2 MCP Tools
    • 5.3 MCP Server
  • Phase 6: Testing & Integration
    • 6.1 Integration Tests
    • 6.2 Security Testing
  • Phase 7: Documentation & Deployment

Notes for Coding Agent

  1. Always write tests first - No implementation without tests
  2. Follow the architecture - Don't take shortcuts or simplify (see architecture1.md)
  3. Use mocking extensively - Don't require real Podman/MCP for unit tests
  4. Validate everything - Security is paramount
  5. Document as you go - Docstrings and comments are required
  6. Test coverage matters - Aim for 100% on critical security code
  7. Error messages are UX - Make them clear and actionable
  8. Log appropriately - Audit log for security, debug log for troubleshooting
  9. No hardcoded values - Everything should be configurable
  10. Think about concurrency - Use locks where needed
  11. Security first - pip install is NEVER allowed during execution; only through build_custom_environment
  12. UV-based builds - All package installation must use UV for speed and security
  13. Allowlist enforcement - Image allowlist, volume patterns, operation restrictions are non-negotiable
  14. Rootless Podman - All container operations must work in rootless mode
  15. Audit everything - All security-relevant operations must be logged

Implementation Roadmap Alignment

This TODO follows the Implementation Roadmap from architecture1.md:

  • Phase 1 (MVP): Sections 1.1-1.3, 2.1, 4.2, 5.3 (basic execution)
  • Phase 2 (Stateful): Section 2.2, additional resources in 5.1
  • Phase 3 (Custom Environments): Section 3.1-3.2, tool in 5.2.3
  • Phase 4 (Advanced Features): Enhanced monitoring, templates, rate limiting
  • Phase 5 (Production): Phase 6-7 testing, deployment, documentation

Start with Phase 1 components before moving to subsequent phases.