-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
github-actions[bot] edited this page Mar 18, 2026
·
20 revisions
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 │
└───────────────────────────────────┘
The entry point and server state container. Uses FastMCP to handle MCP protocol details. Responsibilities:
- Create and manage
MatlabMCPServerstate 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)
Manages a pool of MATLAB engine instances:
-
Elastic scaling: Starts with
min_engines, scales up tomax_enginesunder 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_enginesare stopped if idle longer thanscale_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
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_secondstracks time since last idle,is_alivechecks engine responsiveness - Lazy import: matlab.engine imported at runtime so tests can mock it
Orchestrates the full lifecycle of code execution:
- Create job in tracker with unique ID
- Acquire engine from pool
- Inject job context (job_id, temp_dir) into MATLAB workspace via
__mcp_job_id__and__mcp_temp_dir__variables - Start background execution with stdout/stderr capture
- Wait up to
sync_timeoutseconds:- If completes: build result, mark as completed, return result inline
- If timeout: promote to async background task, return job_id with status "pending"
- On background completion: save result, release engine, emit metrics
- 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
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
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
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
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:
-
MATLAB-side:
mcp_extract_props.mextracts raw figure properties (line, scatter, bar, histogram, surface, image) -
Python-side:
plotly_style_mapper.pyconverts MATLAB styles (line styles, markers, colormaps, fonts, colors) to Plotly equivalents, with WebGL support for large datasets (10,000+ points) -
Python-side:
plotly_convert.pyreads saved JSON file and builds result - Result includes: Plotly JSON + static PNG + optional thumbnail
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
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}
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}
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
- 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
- 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