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           │
│   ├─ Job tracker               │
│   └─ Monitoring (optional)     │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   Job Executor                   │
│   ├─ Hybrid sync/async execution │
│   ├─ Timeout-based promotion     │
│   ├─ Job context injection       │
│   └─ Background task monitoring  │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   Engine Pool Manager            │
│   ├─ Elastic scaling (min→max)   │
│   ├─ Health checks               │
│   ├─ Workspace reset             │
│   └─ Idle scale-down             │
└──────────┬──────────────────────┘
           │
┌──────────▼──────────────────────┐
│   MATLAB Engines (2020b+)        │
│   Engine 1 │ Engine 2 │ ... │ N  │
└───────────────────────────────────┘

Component Details

MCP Server (server.py)

The entry point. Uses FastMCP to handle MCP protocol details. Responsibilities:

  • Register all 20 built-in tools + custom tools from YAML
  • Manage server lifecycle (startup/shutdown via lifespan context manager)
  • Route tool calls to implementation modules
  • Create and manage shared component instances: EnginePoolManager, JobTracker, JobExecutor, SessionManager, SecurityValidator, optional MetricsCollector
  • Run background tasks (health checks, session cleanup, metrics collection)
  • Handle both stdio (single default session) and SSE (multi-session) transports
  • Log security warnings (e.g., SSE without proxy auth)

Key class: MatlabMCPServer — holds all stateful components. Key methods:

  • _get_session_id(ctx) — returns "default" for stdio, or context's session_id for SSE
  • _get_temp_dir(session_id) — retrieves or creates session, returns its temp directory

Engine Pool Manager (pool/manager.py)

Manages a pool of MATLAB engine instances:

  • Initialization: Starts min_engines engines in parallel during startup
  • Elastic scaling: On acquire(), returns an available engine if present. If none available and total < max_engines, starts a new engine. If at max capacity, waits for one to become available
  • Scale-down: run_health_checks() stops idle engines beyond min_engines that have been idle longer than scale_down_idle_timeout (default 15 min)
  • Health checks: Periodic 1+1 eval to verify engines are responsive. Dead engines are stopped and replaced
  • Queue management: Uses asyncio.Queue to track available engines and serialize acquisitions
  • Recording events: Emits engine_scale_up event to optional MetricsCollector

Key methods:

  • async start() — start min_engines in parallel
  • async acquire() — acquire or create an engine, mark busy
  • async release(engine) — reset workspace, mark idle, return to queue
  • async stop() — gracefully stop all engines
  • async run_health_checks() — check idle engines, replace dead ones, scale down excess

Engine Wrapper (pool/engine.py)

Wraps a single matlab.engine instance:

  • State tracking: Tracks EngineState (STOPPED, STARTING, IDLE, BUSY) and idle time
  • Lifecycle: start() launches the engine, applies default paths and startup commands; stop() quits it
  • Health check: health_check() runs trivial 1+1 eval to confirm responsiveness
  • Code execution: execute(code, nargout, background, stdout, stderr) evaluates MATLAB code with optional background execution and I/O capture
  • Workspace reset: reset_workspace() clears all, runs cleanup sequence, restores paths and startup commands
  • Lazy imports: Uses lazy importlib to import matlab.engine so tests can mock it

Key properties:

  • state — current engine state
  • idle_seconds — seconds since last marked idle
  • is_alive — checks if underlying engine object is healthy

Job Executor (jobs/executor.py)

Hybrid sync/async execution orchestrator:

  1. Job creation: Creates a Job in the tracker with unique ID
  2. Engine acquisition: Acquires an engine from the pool
  3. Context injection: Injects job metadata (__mcp_job_id__, __mcp_temp_dir__, __mcp_session_id__) into MATLAB workspace
  4. Background execution: Starts code execution with engine.execute(code, background=True) and captures stdout/stderr via io.StringIO
  5. Sync wait: Waits up to sync_timeout seconds for the future to complete
    • If completes: builds result dict inline, marks job completed, releases engine, returns status="completed"
    • If times out: promotes to async, launches background task, returns status="pending" with job_id
  6. Error handling: On execution error, marks job failed and returns error result
  7. Metrics: Records job_completed and job_failed events to optional MetricsCollector

Key methods:

  • async execute(session_id, code, temp_dir) — main entry point, returns result dict
  • _inject_job_context(engine, job, temp_dir) — sets job variables in MATLAB
  • _build_result(engine, raw_result, job, temp_dir) — formats output, variables, figures
  • async _wait_for_completion(job, engine, future, temp_dir) — background task for async jobs

Job Tracker (jobs/tracker.py)

In-memory store for job metadata and results:

  • Create/get/list/cancel jobs
  • Store raw results and status transitions
  • Prune completed jobs older than job_retention_seconds (default 3600)
  • Thread-safe with asyncio.Lock

Key model: Job with fields: job_id, session_id, code, status, engine_id, future, result, error_type, message, created_at, started_at, completed_at, _stdout, _stderr

Session Manager (session/manager.py)

Per-user session isolation:

  • Session creation: Each session gets a unique temp directory under config.execution.temp_dir
  • Default session: stdio transport uses a fixed "default" session
  • SSE transport: Creates sessions on-demand keyed to client's session_id
  • Session cleanup: cleanup_expired() removes sessions idle longer than session_timeout (default 3600s), skipping those with active jobs
  • Max sessions: Enforces max_sessions limit (default 50)
  • Metrics: Records session_created event to optional MetricsCollector

Key class: Session with fields: session_id, temp_dir, created_at, last_active

Key methods:

  • create_session(session_id=None) — create session with unique temp dir
  • get_session(session_id) — retrieve existing session
  • get_or_create_default() — get or create "default" session for stdio
  • destroy_session(session_id) — clean up session and remove temp dir
  • cleanup_expired(has_active_jobs_fn=None) — remove idle sessions

Security Validator (security/validator.py)

Pre-execution security checks:

  • Function blocklist: Scans code for dangerous functions: system, unix, dos, !, eval, feval, evalc, evalin, assignin, perl, python. Strips comments and string literals first to avoid false positives
  • Filename sanitization: Prevents path traversal in upload filenames
  • Upload size limits: Enforces max_upload_size_mb (default 100)
  • Metrics: Records security_violation events to optional MetricsCollector

Result Formatter (output/formatter.py)

Structures tool responses:

  • Text output: Formats with length limits (max_inline_text_length, default 10000 chars). Truncated text can be saved to file
  • Variable formatting: Summarizes workspace variables with name, type, size, value (large items show type/size only)
  • Success response: Builds dict with status, job_id, output, variables, figures, files, warnings, execution_time
  • Error response: Builds dict with error_type, message, matlab_id (if available)
  • File listings: Formats workspace and session file listings

Key methods:

  • format_text(text, save_dir=None) — truncate text, optionally save to file
  • format_variables(variables) — summarize workspace dict
  • build_success_response(...) — assemble result dict
  • build_error_response(...) — assemble error dict

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

Monitoring (Optional) (monitoring/collector.py, monitoring/store.py)

Optional metrics collection and storage:

  • MetricsCollector: Records events (engine scale-up/down, job completed/failed, security violations, session created) with timestamps
  • MetricsStore: Persists metrics to disk, supports filtering and retrieval
  • Enabled via: config.monitoring.enabled = true
  • No overhead when disabled

Data Flow

Sync Execution

Agent → execute_code("x = magic(3)")
  → Security check (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)
  → Complete in <sync_timeout (30s default)
  → Build result (text, variables, figures)
  → Release engine (reset workspace)
  → Mark job completed
  → Return {"status": "completed", "job_id": "abc123", output: {...}}

Async Promotion

Agent → execute_code("long_simulation()")
  → Security check (OK)
  → Create job in tracker
  → Acquire engine from pool
  → Inject job context
  → Engine.execute(background=True) starts in background
  → sync_timeout (30s) exceeded
  → Promote to async background task
  → Release engine from executor (kept busy in background)
  → 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}
  → Background task completes, marks job completed
  → Agent calls get_job_result("abc123") → full result
  → Engine released

Job Context Injection

Job context variables injected into MATLAB workspace before execution:
  __mcp_job_id__ = "uuid-string"
  __mcp_temp_dir__ = "/path/to/session/temp"
  __mcp_session_id__ = "session-uuid"

User code can reference these to:
  - Save artifacts to __mcp_temp_dir__
  - Log job progress to files
  - Access workspace isolation guarantees

Transport Modes

stdio (Default)

  • One agent, one session (the "default" session)
  • Communication via stdin/stdout
  • Simplest setup, no network
  • Session manager creates and reuses a single "default" session

SSE (Server-Sent Events)

  • Multiple agents, multiple sessions
  • HTTP-based, supports remote connections
  • Session isolation: each request's context.session_id maps to a unique Session
  • Sessions are created on-demand and cleaned up after session_timeout
  • Production: Put behind a reverse proxy with auth (require_proxy_auth: true)
  • Security warning logged if SSE enabled without require_proxy_auth

Component Relationships

┌─────────────────────────────────────────────────┐
│           FastMCP Server (server.py)            │
│                                                 │
│  ┌────────────────────────────────────────┐   │
│  │      MatlabMCPServer (state holder)    │   │
│  ├────────────────────────────────────────┤   │
│  │ - config: AppConfig                   │   │
│  │ - pool: EnginePoolManager              │───┼──→ Manages engine lifecycle
│  │ - tracker: JobTracker                  │───┼──→ Stores job state/results
│  │ - executor: JobExecutor                │───┼──→ Executes code (uses pool)
│  │ - sessions: SessionManager             │───┼──→ Manages temp dirs
│  │ - security: SecurityValidator          │───┼──→ Pre-exec checks
│  │ - collector: MetricsCollector (opt)   │───┼──→ Records metrics
│  └────────────────────────────────────────┘   │
│                     ▲                          │
│                     │                          │
│         Tool handlers call state               │
│                                                 │
└─────────────────────────────────────────────────┘
          │             │             │
          │             │             │
      Tool A         Tool B      Tool N
  (execute_code) (get_workspace) (list_files)
      └─────────────────┬─────────────────┘
                        │
              All use shared pool,
            tracker, executor, sessions,
             security, formatter, etc.

Clone this wiki locally