Skip to content

Architecture

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

Architecture

System Overview

AI Agent (Claude, Cursor, Copilot, etc.)
       │
       │ MCP Protocol (stdio or SSE)
       ▼
┌─────────────────────────────────┐
│   MCP Server (FastMCP)           │
│   ├─ 20 built-in tools           │
│   ├─ Custom tools (from YAML)    │
│   ├─ Session manager             │
│   ├─ Security validator          │
│   ├─ Result formatter            │
│   ├─ Metrics collector           │
│   └─ Server state container      │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   Job Executor                    │
│   ├─ Hybrid sync/async execution  │
│   ├─ Timeout-based promotion      │
│   ├─ Job context injection        │
│   ├─ Progress tracking            │
│   └─ Background task coordination │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   Job Tracker                     │
│   ├─ Job lifecycle management     │
│   ├─ In-memory job store          │
│   ├─ Job pruning                  │
│   └─ Thread-safe operations       │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   Engine Pool Manager             │
│   ├─ Elastic scaling (min→max)    │
│   ├─ Health checks                │
│   ├─ Scale-down idle engines      │
│   ├─ Async queue for acquiring    │
│   └─ Engine state tracking        │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   Engine Wrapper                  │
│   ├─ Lifecycle management         │
│   ├─ Code execution (sync/async)  │
│   ├─ Workspace reset              │
│   ├─ Health checks                │
│   └─ State tracking               │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   MATLAB Engines (2020b+)         │
│   Engine 1 │ Engine 2 │ ... │ N   │
└───────────────────────────────────┘

Component Details

MCP Server (server.py)

The entry point and server state container. Uses FastMCP to handle MCP protocol details. Responsibilities:

  • Create and manage MatlabMCPServer state container holding all components
  • Register all 20 tools + custom tools
  • Manage server lifecycle (startup, shutdown, drain)
  • Route tool calls to implementation modules
  • Run background tasks (health checks, session cleanup)
  • Determine session ID for each request (stdio uses "default", SSE uses context session_id)
  • Get or create temp directories for sessions
  • Initialize directories and MATLAB helper paths on startup
  • Set security warnings for SSE without proxy authentication

Key State Management:

  • state.pool – EnginePoolManager instance
  • state.tracker – JobTracker instance
  • state.executor – JobExecutor instance
  • state.sessions – SessionManager instance
  • state.security – SecurityValidator instance
  • state.collector – MetricsCollector instance (when monitoring enabled)

Engine Pool Manager (pool/manager.py)

Manages a pool of MATLAB engine instances:

  • Elastic scaling: Starts with min_engines, scales up to max_engines under load
  • On-demand scale-up: When acquire() is called and no engines are available, attempts to start a new one (up to max)
  • Scale-down: Idle engines beyond min_engines are stopped if idle longer than scale_down_idle_timeout (15 min)
  • Health checks: Periodic trivial eval ("1") verifies engines are responsive. Unhealthy engines are replaced
  • Async queue: Requests wait in an asyncio Queue when all engines are busy
  • Thread executor: Engine start/stop operations run in thread pool to avoid blocking async event loop
  • Workspace reset: Released engines have workspace cleared before returning to pool

Engine Wrapper (pool/engine.py)

Wraps a single matlab.engine instance with state tracking:

  • Lifecycle: Start/stop with state transitions (STOPPED → STARTING → IDLE → BUSY)
  • Execution: Sync or background code execution with stdout/stderr capture
  • Workspace isolation: Reset between sessions (clear all, clear global, clear functions, fclose all, restore paths, re-run startup)
  • Health checks: Trivial eval ping ("1") to detect dead engines
  • State properties: idle_seconds tracks time since last idle, is_alive checks engine responsiveness
  • Lazy import: matlab.engine imported at runtime so tests can mock it

Job Executor (jobs/executor.py)

Orchestrates the full lifecycle of code execution:

  1. Create job in tracker with unique ID
  2. Acquire engine from pool
  3. Inject job context (job_id, temp_dir) into MATLAB workspace via __mcp_job_id__ and __mcp_temp_dir__ variables
  4. Start background execution with stdout/stderr capture
  5. Wait up to sync_timeout seconds:
    • If completes: build result, mark as completed, return result inline
    • If timeout: promote to async background task, return job_id with status "pending"
  6. On background completion: save result, release engine, emit metrics
  7. On error: mark failed, return error result, release engine

Hybrid execution flow:

  • Synchronous for fast operations (< sync_timeout, typically 30s)
  • Asynchronous for long-running operations (auto-promoted on timeout)
  • Background task continues running even if client disconnects

Job Tracker (jobs/tracker.py)

In-memory store for job metadata:

  • Create/get/list/cancel jobs
  • Track job status (CREATED, RUNNING, PENDING, COMPLETED, FAILED, CANCELLED)
  • Store raw result, error info, execution time
  • Prune completed jobs older than job_retention_seconds
  • Thread-safe with asyncio locks

Session Manager (session/manager.py)

Per-user session isolation:

  • Each session gets unique temp directory under config.execution.temp_dir
  • Session lifecycle: create, get, destroy with timeout-based cleanup
  • Default session ("default") for stdio transport
  • SSE transport creates session per unique session_id
  • Workspace isolation: engines reset workspace when released back to pool
  • Expired sessions cleaned up after session_timeout (default 3600s)
  • Max sessions limit enforced (max_sessions, default 50)
  • Active job detection prevents cleanup of sessions with running jobs
  • Thread-safe with locks

Security Validator (security/validator.py)

Pre-execution security checks:

  • Function blocklist: Scans code for blocked functions (system, unix, dos, !, eval, feval, evalc, evalin, assignin, perl, python). Smart enough to strip string literals and comments first to avoid false positives
  • Filename sanitization: Prevents path traversal in upload filenames
  • Upload size limits: Enforces max_upload_size_mb
  • Metrics collection: Records security events when monitoring enabled

Result Formatter (output/formatter.py)

Structures tool responses:

  • Text formatting: Truncates long output, saves to file if exceeds max_inline_text_length
  • Variable formatting: Summarizes workspace variables with type, size, and value (large arrays represented by type/size only)
  • Success responses: Builds structured dict with status, job_id, output, variables, figures, files, warnings, execution_time
  • Error responses: Includes error_type, message, matlab_id, execution_time
  • Delegates to converters: Uses plotly converter and thumbnail generator for figures

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)
  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 reads saved JSON file and builds result
  4. Result includes: Plotly JSON + static PNG + optional thumbnail

Metrics Collector (monitoring/collector.py)

Optional performance and usage monitoring (when config.monitoring.enabled=true):

  • Records events: job_completed, job_failed, engine_scale_up, session_created, etc.
  • Tracks execution metrics: duration, code size, output size, error types
  • Thread-safe event recording
  • Can be integrated with external observability systems

Data Flow

Sync Execution

Agent → execute_code("x = magic(3)")
  → Security check (OK)
  → Create job
  → Acquire engine from pool
  → Inject job context (__mcp_job_id__, __mcp_temp_dir__)
  → Engine.execute("x = magic(3)", background=True)
  → Wait for future (< sync_timeout)
  → Future completes
  → Build result (text, variables, figures)
  → Mark job completed
  → Release engine (workspace reset)
  → Return {"status": "completed", "job_id": "abc123", ...result}

Async Promotion

Agent → execute_code("long_simulation()")
  → Security check (OK)
  → Create job
  → Acquire engine
  → Inject job context
  → Engine.execute("long_simulation()", background=True)
  → Wait for future (< sync_timeout)
  → Timeout exceeded after 30s
  → Promote to background task
  → Release engine reference (keeps running)
  → Return {"status": "pending", "job_id": "abc123"}
  → Agent polls get_job_status("abc123") → {"status": "pending", "progress": 45}
  → Agent polls get_job_status("abc123") → {"status": "pending", "progress": 90}
  → Background task completes
  → Build result, mark job completed, release engine
  → Agent calls get_job_result("abc123") → {"status": "completed", ...result}

Session Lifecycle (SSE Transport)

Client 1 → SSE connection (session_id="sess-001")
  → SessionManager.create_session("sess-001")
  → Temp dir: /tmp/matlab_mcp/sess-001
  → execute_code() → uses sess-001's temp_dir
  → Multiple jobs share session temp directory
  
Client 2 → SSE connection (session_id="sess-002")
  → SessionManager.create_session("sess-002")
  → Temp dir: /tmp/matlab_mcp/sess-002
  → Full isolation from Client 1
  
Cleanup → SessionManager.cleanup_expired()
  → Remove sessions idle > session_timeout
  → Skip sessions with active jobs
  → Delete temp directories

Transport Modes

stdio (Default)

  • One agent, one session
  • Communication via stdin/stdout
  • Simplest setup, no network
  • Uses fixed "default" session ID
  • Single temp directory shared across all jobs in session

SSE (Server-Sent Events)

  • Multiple agents, multiple sessions
  • HTTP-based, supports remote connections
  • Session isolation via session IDs in context
  • Each session gets unique temp directory
  • Production: Put behind a reverse proxy with auth (require_proxy_auth: true)
  • Security warning emitted at startup if require_proxy_auth=false

Clone this wiki locally