Skip to content

Architecture

github-actions[bot] edited this page Mar 22, 2026 · 20 revisions

Architecture

System Overview

AI Agent (Claude, Cursor, Copilot, etc.)
       │
       │ MCP Protocol (stdio or SSE)
       ▼
┌─────────────────────────────────┐
│   MCP Server (FastMCP)           │
│   ├─ Server Entry Point          │
│   ├─ Tool Router                 │
│   ├─ Session Manager             │
│   ├─ Security Validator          │
│   ├─ Output Formatter            │
│   ├─ Job Tracker                 │
│   └─ Monitoring Subsystem        │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   Job Executor                    │
│   ├─ Hybrid sync/async execution  │
│   ├─ Timeout-based promotion      │
│   ├─ Job context injection        │
│   └─ Progress tracking            │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   Engine Pool Manager             │
│   ├─ Elastic scaling (min→max)    │
│   ├─ Health checks                │
│   ├─ Proactive warmup             │
│   └─ Idle scale-down              │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   MATLAB Engines (2020b+)         │
│   Engine 1 │ Engine 2 │ ... │ N   │
└───────────────────────────────────┘

Component Details

Server Entry Point (server.py)

The main MCP server orchestrator. Uses FastMCP to handle MCP protocol details. Responsibilities:

  • Component initialization: Creates and wires together the engine pool, job tracker, executor, session manager, security validator, and (optionally) monitoring subsystem
  • Lifespan management: Handles server startup (engine pool initialization, background task launch), shutdown (graceful drain, cleanup), and security warnings
  • Tool registration: Registers all built-in tools plus custom tools loaded from YAML
  • Session routing: Routes requests to the appropriate session (via context) and injects session ID / temp directory
  • Background tasks: Runs periodic health checks, session cleanup, and job expiration in the background

Engine Pool Manager (pool/manager.py)

Manages a pool of MATLAB engine instances with dynamic scaling:

  • Elastic scaling: Starts with min_engines, scales up to max_engines under load
  • Proactive warmup: When utilization exceeds threshold (80% by default), starts a new engine before it's needed
  • Scale-down: Engines idle longer than scale_down_idle_timeout (15 min by default) are stopped, down to min_engines
  • Health checks: Periodic 1+1 eval to verify engines are responsive. Unhealthy engines are replaced
  • Queue management: Requests wait in an async queue when all engines are busy
  • Metrics collection: Records scale-up/down events and engine state changes to the monitoring subsystem (if enabled)

Engine Wrapper (pool/engine.py)

Wraps a single matlab.engine instance with full lifecycle management:

  • Lifecycle states: STOPPED, STARTING, IDLE, BUSY (tracked via EngineState enum)
  • Start/stop: Initializes MATLAB engine, applies default paths and startup commands; cleanly quits on stop
  • Execution: Executes code synchronously or asynchronously (background=True) with stdout/stderr capture
  • Workspace reset: Clears all variables, paths, and functions; restores defaults and re-applies startup commands
  • Health check: Trivial 1+1 eval to confirm engine responsiveness
  • Idle tracking: Records time of last transition to IDLE state for scale-down decisions

Job Executor (jobs/executor.py)

Hybrid sync/async execution orchestrator:

  1. Creates a job in the tracker with a unique ID
  2. Acquires an engine from the pool
  3. Injects job context into MATLAB workspace (__mcp_job_id__, __mcp_temp_dir__, etc.)
  4. Starts code execution asynchronously via engine.execute(..., background=True)
  5. Sync path: Waits up to sync_timeout seconds
    • If completes: Returns result inline with status "completed"
    • If times out: Promotes to async background task, returns status "pending" with job_id
  6. Async path: Spawns a background coroutine to monitor completion, capture output, build final result
  7. Engine release: Returns engine to pool once execution (sync or async) completes
  8. Metrics: Records job creation, completion, and failures to the monitoring subsystem

Job Tracker (jobs/tracker.py)

In-memory store for job metadata and state:

  • Job lifecycle: Create, get, list, mark_running, mark_completed, mark_failed
  • Job storage: Maps job IDs to Job objects containing code, status, results, stdout/stderr, timing
  • Automatic pruning: Periodically removes completed jobs older than job_retention_seconds (default 3600)
  • Thread-safe: Uses asyncio locks to protect concurrent access
  • Query: List all jobs or jobs for a specific session

Session Manager (session/manager.py)

Per-user session isolation with per-session temporary directories:

  • Session creation: Each session gets a unique temp directory (UUID-based or explicit ID)
  • Max sessions: Enforces max_sessions limit (default 50)
  • Idle cleanup: Removes sessions that have been idle longer than session_timeout (default 3600s)
  • Session tracking: Records creation time and last-active time for timeout calculation
  • Workspace isolation: Per-session temp directories prevent cross-user interference
  • Single-user stdio mode: Uses a fixed "default" session when stdio transport is active
  • Multi-user SSE mode: Creates/retrieves sessions by session ID from the request context

Security Validator (security/validator.py)

Pre-execution security checks preventing dangerous operations:

  • Function blocklist: Scans code for blocked functions (system, unix, dos, !, eval, feval, evalc, evalin, assignin, perl, python). Smart parsing strips string literals and comments first to avoid false positives
  • Filename sanitization: Prevents path traversal in upload filenames via safe name generation
  • Upload size limits: Enforces max_upload_size_mb (default 100)
  • Metrics: Records security events (check requests, violations) to the monitoring subsystem

Result Formatter (output/formatter.py)

Structures tool responses with consistent formatting:

  • Text output: Formats captured stdout with length limits; saves large output to file if save_dir provided
  • Variable formatting: Summarizes workspace variables with type, size, and (for small values) the actual value
  • Success/error responses: Builds structured dicts with status, job_id, output, variables, figures, files, warnings, execution_time
  • Figure handling: Delegates to Plotly converter for interactive plots and thumbnail generation
  • JSON serialization: Ensures all response values are JSON-serializable

Plotly Converter (output/plotly_convert.py, output/plotly_style_mapper.py + matlab_helpers/mcp_extract_props.m)

Converts MATLAB figures to interactive Plotly JSON:

  1. MATLAB-side: mcp_extract_props.m extracts raw figure properties (line, scatter, bar, histogram, surface, image data and styling)
  2. Python-side: plotly_style_mapper.py converts MATLAB styles (line styles, markers, colormaps, fonts, colors) to Plotly equivalents with WebGL support for large datasets (10,000+ points)
  3. Python-side: plotly_convert.py / load_plotly_json() reads the saved JSON file
  4. Result: Plotly JSON (interactive), static PNG (fallback), optional thumbnail

Monitoring Subsystem (Optional)

When monitoring.enabled = true:

  • Metrics Collector: Records events (job creation/completion/failure, session creation, engine scale-up/down, security checks)
  • Metrics Store: Aggregates and stores metrics (if a backing store is configured)
  • Per-component integration: Pool, executor, session manager, and security validator all report events to the collector
  • Graceful fallback: If monitoring is disabled, collectors are None and no metrics are recorded

Data Flow

Sync Execution

Agent → execute_code("x = magic(3)")
  → Security validator (OK)
  → Create job in tracker
  → Acquire engine from pool
  → Inject job context (__mcp_job_id__, __mcp_temp_dir__)
  → Engine.execute("x = magic(3)", background=True)
  → Wait for future (default: 30s sync_timeout)
  → Future completes in <30s
  → Collect stdout/stderr, query workspace variables, convert figures
  → Format result dict (status="completed", job_id, output, variables, figures)
  → Release engine to pool
  → Return result to agent

Async Promotion (Long-Running)

Agent → execute_code("long_simulation()")
  → Security validator (OK)
  → Create job in tracker
  → Acquire engine from pool
  → Inject job context
  → Engine.execute("long_simulation()", background=True)
  → Wait for future (30s timeout)
  → 30s exceeded → TimeoutError
  → Spawn background coroutine to monitor future
  → Return {status: "pending", job_id: "abc123"}
  → Agent polls get_job_status("abc123") → {status: "running", progress: 45%}
  → Agent polls get_job_status("abc123") → {status: "running", progress: 90%}
  → Agent calls get_job_result("abc123") → {status: "completed", output: {...}}
  → Background coroutine collects final result, releases engine

Multi-Job Queuing

Agent → execute_code(code1)  [Pool has 2 engines, both busy]
  → Acquire engine → Blocked (queue size=1)
Agent → execute_code(code2)  [Queue size increases]
  → Acquire engine → Blocked (queue size=2)
[Engine 1 finishes, released]
  → Acquire engine → Unblocks code1's executor
[Engine 2 finishes]
  → Acquire engine → Unblocks code2's executor

Transport Modes

stdio (Default)

  • One agent, one session (the fixed "default" session)
  • Communication via stdin/stdout
  • Simplest setup, no network configuration
  • Session manager uses get_or_create_default() for all requests

SSE (Server-Sent Events)

  • Multiple agents, multiple sessions (each agent has a unique session_id)
  • HTTP-based, supports remote connections
  • Session isolation via context's session_id
  • Enables per-client temp directories and workspace isolation
  • Production: Put behind a reverse proxy with authentication (require_proxy_auth: true)
    • Prevents unauthorized agents from accessing the server
    • Reverse proxy adds authentication headers; server validates via config flag

Clone this wiki locally