Skip to content

Architecture

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

Architecture

System Overview

The MATLAB MCP Server acts as a bridge between AI agents (Claude, Cursor, Copilot) and MATLAB, providing controlled, asynchronous access to MATLAB computation with interactive plotting and custom tool integration.

graph TB
    Agent["AI Agent<br/>(Claude, Cursor, etc.)"]
    Agent -->|MCP Protocol<br/>stdio or SSE| Server["FastMCP Server<br/>(server.py)"]
    
    Server -->|Route calls| Tools["20 Built-in Tools<br/>+ Custom Tools"]
    Server -->|Manage sessions| SessionMgr["Session Manager<br/>(isolation, temp dirs)"]
    Server -->|Security check| Security["Security Validator<br/>(blocklist, sanitize)"]
    Server -->|Track jobs| JobTracker["Job Tracker<br/>(in-memory store)"]
    Server -->|Execute code| JobExecutor["Job Executor<br/>(sync/async hybrid)"]
    
    JobExecutor -->|Acquire/release| PoolMgr["Engine Pool Manager<br/>(elastic scaling)"]
    PoolMgr -->|Manage| Engines["MATLAB Engines<br/>(2020b+)"]
    
    JobExecutor -->|Format output| Formatter["Result Formatter<br/>(text, vars, figures)"]
    Formatter -->|Convert plots| PlotlyConv["Plotly Converter<br/>(MATLAB→interactive)"]
    
    Server -->|Health/metrics| Monitor["Monitoring<br/>(collector, dashboard)"]
    
    style Server fill:#4a90e2
    style Tools fill:#7ed321
    style SessionMgr fill:#f5a623
    style Security fill:#d0021b
    style JobExecutor fill:#50e3c2
    style PoolMgr fill:#b8e986
    style Engines fill:#9013fe
    style Monitor fill:#417505
Loading

Major Components

MCP Server (src/matlab_mcp/server.py)

Entry point and orchestrator. Built on FastMCP, which handles the MCP protocol details (stdio, SSE, JSON-RPC).

Responsibilities:

  • Load and validate configuration (config.yaml + environment overrides)
  • Initialize all sub-components: pool, tracker, executor, sessions, security, monitoring
  • Register all 20 built-in tools (code execution, discovery, job management, file I/O, admin, monitoring)
  • Register custom tools from custom_tools.yaml
  • Manage server lifecycle: startup initialization, background health checks, graceful shutdown with job draining
  • Route incoming tool calls to implementation modules

Lifespan Management:

  • Startup: Initialize pool, store, background tasks
  • Shutdown: Drain active jobs (up to drain_timeout_seconds), cleanup resources

Engine Pool Manager (src/matlab_mcp/pool/manager.py)

Manages a dynamic pool of MATLAB engine instances with elastic scaling.

Scaling Strategy:

  • Starts with min_engines engines (default 2)
  • Scales up to max_engines (default 10) when demand is high
  • Proactive warmup: When pool utilization exceeds proactive_warmup_threshold (default 80%), a new engine is started before it's actually needed
  • Scale-down: Engines idle longer than scale_down_idle_timeout (default 15 min) are stopped, but never below min_engines

Health Management:

  • Periodic health checks (every health_check_interval seconds): sends a trivial 1+1 eval to each engine
  • Dead engines are immediately replaced
  • Health check failures logged for diagnostics

Request Queuing:

  • When all engines are busy, incoming execution requests wait in an async queue (max queue_max_size)
  • Queue is FIFO; oldest requests acquire engines first

Engine Wrapper (src/matlab_mcp/pool/engine.py)

Wraps a single matlab.engine.MatlabEngine instance with state tracking and lifecycle management.

State Machine:

  • STOPPEDSTARTINGIDLEBUSYIDLESTOPPED

Capabilities:

  • Start/stop lifecycle with timeouts
  • Execute code (synchronous or background)
  • Workspace reset between sessions
  • Health check pings
  • Idle duration tracking for scale-down decisions
  • Default MATLAB path setup and startup commands (e.g., format long)

Integration:

  • Lazy imports matlab.engine to enable mock-based testing
  • Catches MatlabExecutionError for proper error handling

Job Executor (src/matlab_mcp/jobs/executor.py)

Orchestrates the full MATLAB code execution lifecycle with hybrid sync/async behavior.

Execution Pipeline:

  1. Security validation: SecurityValidator.check_code() scans for blocked functions
  2. Job creation: Create a Job in the tracker (initial status: PENDING)
  3. Engine acquisition: Wait for an available engine from the pool
  4. Job context injection: Set __mcp_job_id__ and __mcp_temp_dir__ in the MATLAB workspace
  5. Code execution: Start code execution via engine.eval()
  6. Timeout decision:
    • If execution completes within sync_timeout (default 30s) → return result immediately
    • If timeout exceeded → promote to async, return job_id, spawn background monitor task
  7. Result formatting: Build success/error response with output, variables, figures, files, warnings
  8. Engine release: Return engine to pool

Sync vs. Async:

  • Sync: For quick operations (< 30s), agent gets result immediately
  • Async: For long operations, agent gets job_id and polls via get_job_status() / get_job_result()

Error Handling:

  • Execution exceptions → job marked FAILED with error dict
  • Timeout or cancellation → job marked CANCELLED
  • Output/result serialization failures → logged but don't crash the server

Job Tracker (src/matlab_mcp/jobs/tracker.py)

In-memory CRUD store for job metadata with lifecycle management.

Operations:

  • Create a new job (assigns unique UUID as job_id)
  • Retrieve job status by ID or list all jobs for a session
  • Mark jobs as RUNNING, COMPLETED, FAILED, or CANCELLED
  • Prune completed/terminal jobs older than job_retention_seconds (default 24 hours)

Thread Safety:

  • All operations are synchronized via asyncio locks
  • Supports concurrent polling and job creation

Session Manager (src/matlab_mcp/session/manager.py)

Manages per-user execution contexts with workspace isolation.

Session Lifecycle:

  • Each session gets a unique temporary directory under temp_dir (default ./temp/)
  • Session tracks creation and last-activity timestamps
  • Expiration: Sessions with no activity for session_timeout seconds (default 1 hour) are cleaned up
  • Optional: Clear MATLAB workspace between sessions to prevent variable leakage

Transport Modes:

  • stdio: Single "default" session per server instance
  • SSE: Multiple sessions, one per connected client (identified by session_id context)

Cleanup:

  • Expired sessions have their temp directories deleted
  • If temp_cleanup_on_disconnect: true, cleanup happens immediately when session expires
  • Job metadata retained for job_retention_seconds before being pruned

Security Validator (src/matlab_mcp/security/validator.py)

Pre-execution security controls with intelligent pattern matching.

Function Blocklist (default):

  • system, unix, dos — OS command execution
  • ! — Shell escape operator
  • eval, feval, evalc, evalin, assignin — Dynamic code execution / workspace manipulation
  • perl, python — External interpreter execution

Smart Scanning:

  • Before pattern matching, the validator:
    1. Strips all string literals ('...' and "...")
    2. Strips all comments (%... to end of line)
    3. Then applies regex patterns with word boundaries to avoid false positives
  • Example: msg = 'call system to run'; is allowed; system('ls'); is blocked

File Protection:

  • Sanitizes filenames to prevent path traversal: rejects ../, absolute paths, special characters
  • Enforces upload size limits via max_upload_size_mb (default 100MB)

Optional Disabling:

  • Set blocked_functions_enabled: false to allow all code (not recommended for production)

Monitoring:

  • Security events (blocked attempts) are logged to the metrics collector

Result Formatter (src/matlab_mcp/output/formatter.py)

Structures tool responses into MCP-compliant dictionaries.

Formatting:

  • Output text: Truncated to max_inline_text_length (default 50,000 chars); excess saved to file
  • Variables: Extracted via whos command, formatted with type/size/value summaries
  • Figures: Converted to Plotly JSON or static PNG/JPEG
  • Files: Paths to saved files returned for agent download
  • Warnings: Execution warnings (e.g., uninitialized variables) captured and included

Response Structure:

{
  "status": "completed",
  "job_id": "...",
  "output": "...",
  "variables": {...},
  "figures": [...],
  "files": [...],
  "execution_time": 0.5
}

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

Converts MATLAB figures to interactive Plotly visualizations.

MATLAB Side (mcp_extract_props.m):

  • Iterates over figure axes and child objects (lines, surfaces, images, patches)
  • Extracts properties: colors, line styles, markers, fonts, axis limits, titles, labels, legends, grids
  • Exports as JSON to a temporary file

Python Side (plotly_style_mapper.py):

  • Maps MATLAB styles to Plotly equivalents:
    • Line styles: -solid, --dash, :dot, -.dashdot
    • Markers: ocircle, ssquare, ^triangle-up, etc.
    • Colormaps: jet, viridis, hot, etc. → Plotly color scales
    • Fonts: Arial, Courier → web-safe defaults
  • WebGL optimization: Large datasets (>10,000 points) use scattergl for browser performance
  • Handles multi-axes layouts and subplot positioning

Output:

  • Complete Plotly JSON suitable for Plotly.newPlot() in JavaScript
  • Static PNG/JPEG copy via matlab.engine built-in figure export
  • Optional thumbnail (base64-encoded, resized to fit narrow displays)

Monitoring System

Collector (src/matlab_mcp/monitoring/collector.py)

  • Tracks counters: jobs completed/failed, sessions created, blocked attempts
  • Records execution time statistics (ring buffer: min/avg/max/p95 latencies)
  • Samples system metrics (CPU %, memory MB via psutil)
  • Fire-and-forget async writes to the metrics store

Store (src/matlab_mcp/monitoring/store.py)

  • SQLite3 backing with WAL mode for concurrent access
  • Two tables: metrics (time-series snapshots) and events (discrete events)
  • Supports historical queries: get_latest(), get_history(since_time), filtered event retrieval
  • Auto-pruning of old data based on retention_days

Dashboard (src/matlab_mcp/monitoring/dashboard.py)

  • Starlette sub-application exposing HTTP routes:
    • /health — Server health status (healthy/degraded/unhealthy)
    • /metrics — Live metrics snapshot (pool, jobs, sessions, system)
    • /dashboard — Web UI with Plotly charts
    • /api/current, /api/history, /api/events — JSON data endpoints
  • Static assets: HTML, CSS, JavaScript for interactive charts

Health Evaluation (src/matlab_mcp/monitoring/health.py)

  • Checks:
    • Pool utilization (if >90% → degraded)
    • Max capacity reached (if all engines busy → unhealthy)
    • Error rate (if >10% of jobs failed in last 10 min → degraded)
    • Health check failures (if 2+ consecutive failures → unhealthy)
  • Returns status + list of detected issues for display

Data Flow: Code Execution

Synchronous Execution (< 30 seconds)

sequenceDiagram
    participant A as Agent
    participant S as MCP Server
    participant J as Job Executor
    participant P as Engine Pool
    participant E as MATLAB Engine
    
    A->>S: execute_code("x = magic(3)")
    S->>J: execute(code, session_id)
    J->>J: Security check (OK)
    J->>P: acquire()
    P->>E: [waiting for engine]
    P-->>J: engine
    J->>E: eval("x = magic(3)")
    E-->>J: result: {x: [[8,1,6],[3,5,7],[4,9,2]]}
    J->>J: Format result
    J->>P: release(engine)
    J-->>S: {status: completed, output: "...", variables: {...}}
    S-->>A: result
Loading

Asynchronous Execution (> 30 seconds)

sequenceDiagram
    participant A as Agent
    participant S as MCP Server
    participant J as Job Executor
    participant P as Engine Pool
    participant E as MATLAB Engine
    participant T as Job Tracker
    
    A->>S: execute_code("simulation(n=1000000)")
    S->>J: execute(code, session_id)
    J->>J: Security check (OK)
    J->>T: create_job()
    T-->>J: job_id = "abc123"
    J->>P: acquire()
    P-->>J: engine
    J->>E: eval(code, background=True)
    E-->>J: future
    J->>J: Monitor timeout (30s exceeded)
    J->>T: mark_running("abc123")
    J-->>S: {status: running, job_id: "abc123"}
    S-->>A: job_id
    
    Note over J,E: Background execution continues
    
    A->>S: get_job_status("abc123")
    S->>T: get_job("abc123")
    T-->>S: {status: running, progress: 45%}
    S-->>A: progress
    
    Note over J,E: Execution completes
    J->>T: mark_completed("abc123", result)
    J->>P: release(engine)
    
    A->>S: get_job_result("abc123")
    S->>T: get_job("abc123")
    T-->>S: {status: completed, result: {...}}
    S-->>A: result
Loading

Data Flow: Tool Registration & Discovery

graph LR
    Config["config.yaml"]
    CustomTools["custom_tools.yaml"]
    
    Config -->|Load| Server["MCP Server"]
    CustomTools -->|Load custom_tools.py| Server
    
    Server -->|Register 20 built-in| Tools["Tool Registry"]
    Tools -->|execute_code| Exec["exec_code_impl()"]
    Tools -->|check_code| Check["check_code_impl()"]
    Tools -->|get_workspace| GetWS["get_workspace_impl()"]
    Tools -->|[4 job tools]| Jobs["job tools"]
    Tools -->|[3 discovery]| Discovery["discovery tools"]
    Tools -->|[5 file I/O]| Files["file tools"]
    Tools -->|[3 monitoring]| Monitor["monitor tools"]
    
    Server -->|Dynamic registration| CustomTools2["Custom tools"]
    CustomTools2 -->|From YAML| Handlers["make_custom_tool_handler()"]
    
    Agent["Agent"]
    Agent -->|List tools| Server
    Server -->>|Tool list| Agent
    Agent -->|Call tool| Tools
    Tools -->|Execute| Impl["Implementation"]
Loading

Key Design Decisions & Trade-Offs

1. Hybrid Sync/Async Execution

Decision: Auto-promote to async if code exceeds sync_timeout.

Rationale:

  • Agents expect quick feedback; synchronous execution keeps the interaction snappy
  • Long-running jobs (simulations, data processing) don't block the agent
  • No need for agents to pre-declare async intent

Trade-off:

  • More complex executor logic
  • Agents must understand two response patterns (inline vs. job_id)

2. In-Memory Job Tracking

Decision: Jobs stored in RAM with optional SQLite metrics store.

Rationale:

  • Fast polling for job status
  • No database dependency for core functionality
  • Suitable for single-server deployments

Trade-off:

  • Job results lost on server restart
  • Limited to one server (no horizontal scaling)
  • Mitigation: Job completion exported to metrics store for durability if monitoring enabled

3. Elastic Engine Pool with Proactive Warmup

Decision: Scale up gradually; pre-warm when threshold is near.

Rationale:

  • Reduces cold-start latency for scaling events
  • Avoids thundering herd of engine startups
  • Engines are expensive (MATLAB license) and consume memory

Trade-off:

  • Slightly higher baseline memory usage
  • Warmup engines may not get used

4. Security Validator Strips Strings & Comments

Decision: Remove literals before pattern matching.

Rationale:

  • Avoids false positives (e.g., msg = 'system resources')
  • Whitelist-by-pattern is simpler than AST parsing
  • Covers the common case of benign text containing function names

Trade-off:

  • Regex-based (not a full MATLAB parser)
  • Edge cases possible (e.g., complex string escapes)
  • Mitigation: Default blocklist covers high-risk functions; can be customized per deployment

5. Plotly Conversion with MATLAB Helper Function

Decision: Extract figure properties via MATLAB (mcp_extract_props.m), convert in Python.

Rationale:

  • MATLAB can reliably extract figure state (no black-box rendering)
  • Python conversion logic is easier to test and customize
  • Output is standard Plotly JSON (portable, no extra dependencies)

Trade-off:

  • Adds MATLAB execution overhead per figure
  • Complex style mapping (many MATLAB line styles, markers, colormaps)
  • Mitigation: Cached mapping tables; optional WebGL for large plots

6. Per-Session Temp Directories

Decision: Each session gets an isolated temp directory.

Rationale:

  • Prevents accidental data leakage between users
  • Simplifies file I/O (agents know their sandbox)
  • Easy cleanup (delete directory on session end)

Trade-off:

  • Slightly higher I/O overhead
  • Agents can't easily share files across sessions
  • Mitigation: Custom tools can coordinate via MATLAB's persistent variables

7. Configuration via YAML + Environment Overrides

Decision: File-based config with MATLAB_MCP_* env var overrides.

Rationale:

  • YAML is human-readable and flexible
  • Environment overrides suit containerized deployments
  • Hierarchical config (nested objects) maps naturally to Pydantic

Trade-off:

  • Complex override precedence rules
  • Schema validation errors can be hard to debug
  • Mitigation: Comprehensive logging and config dump at startup

8. Optional Monitoring with Metrics Store

Decision: Monitoring is opt-in; disabled by default in config.

Rationale:

  • No performance overhead for deployments that don't need it
  • Dashboard and metrics collection are separate from core execution
  • Suitable for both development (no DB) and production (with monitoring)

Trade-off:

  • Job history and metrics lost on server restart (if monitoring off)
  • Extra setup required for production observability
  • Mitigation: config.yaml defaults to monitoring.enabled: true; SQLite is lightweight

Communication Between Components

MCP Server ↔ Tools: FastMCP context injection + direct function calls

Tools ↔ Job Executor: Delegate actual execution; return structured results

Job Executor ↔ Engine Pool: Acquire/release engines; get status

Job Executor ↔ Job Tracker: Create/update job state; poll for results

Job Executor ↔ Session Manager: Get session temp dir; track session activity

Job Executor ↔ Security Validator: Pre-execute security check

Job Executor ↔ Result Formatter: Structure output for MCP response

Result Formatter ↔ Plotly Converter: Convert MATLAB figures to interactive JSON

All ↔ Monitoring Collector: Log events (fire-and-forget async writes)

Monitoring Collector ↔ Metrics Store: Persist metrics and events (async)

Monitoring Dashboard ↔ Metrics Store: Query history, health, events


Performance Characteristics

Operation Latency Notes
Simple eval (e.g., 1+1) 10–50 ms Depends on engine state (busy/idle)
Matrix ops (100×100) 50–200 ms MATLAB computation time
Figure extraction 100–500 ms Includes figure iteration and JSON generation
Engine startup 2–5 sec MATLAB JVM initialization
Job polling <10 ms In-memory; minimal overhead
Plotly conversion 50–200 ms Depends on plot complexity

Scalability

  • Horizontal: Not supported; single-server design (job state in RAM)
  • Vertical: Limited by MATLAB licensing and host resources
    • Typical: 4–8 concurrent engines per machine
    • macOS: Capped at 4 due to stability issues
  • Concurrency: Hundreds of queued requests; actual parallelism limited by engine count

Clone this wiki locally