-
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 │
└──────────┬──────────────────────┘
│
┌──────────▼──────────────────────┐
│ 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 isolation │
│ └─ Idle scale-down │
└──────────┬──────────────────────┘
│
┌──────────▼──────────────────────┐
│ MATLAB Engines (2020b+) │
│ Engine 1 │ Engine 2 │ ... │ N │
└───────────────────────────────────┘
The entry point. Uses FastMCP to handle MCP protocol details. Responsibilities:
- Register all 20 built-in tools + custom tools from YAML
- Create and manage core components:
EnginePoolManager,JobTracker,JobExecutor,SessionManager,SecurityValidator - Manage server lifecycle (startup, shutdown, drain)
- Route tool calls to implementation modules
- Handle session identification (stdio uses
"default"session; SSE uses context'ssession_id) - Run background tasks (health checks, session cleanup, monitoring)
- Optionally initialize metrics collector and monitoring store when
monitoring.enabled=true
Manages a pool of MATLAB engine instances with elastic scaling:
-
Startup: Initializes
min_enginesengines in parallel on server start -
Acquisition: Returns an available engine immediately; if none available but under
max_engines, starts a new one; if at capacity, waits for one to become available - State tracking: Each engine is marked busy or idle
-
Health checks: Periodic
1+1eval to verify engines are responsive. Unhealthy engines are stopped and replaced -
Scale-down: Engines idle longer than
scale_down_idle_timeout(15 min) are stopped, down tomin_engines - Queue: Maintains an async queue of available engines for FIFO acquire/release
Wraps a single matlab.engine instance with lifecycle and execution control:
-
State tracking: Maintains
EngineStateenum (STOPPED, STARTING, IDLE, BUSY) -
Lifecycle:
start()launches MATLAB, appliesdefault_paths, runsstartup_commands;stop()cleanly quits -
Execution:
execute()method supports sync/background code evaluation with optional stdout/stderr capture -
Workspace reset:
reset_workspace()clears all variables/functions, closes files, restores paths, and re-runs startup commands between jobs -
Health checks:
health_check()runs trivial eval to confirm responsiveness - Idle tracking: Monitors time since last transition to IDLE state
Orchestrates the full lifecycle of a MATLAB code execution request:
-
Job creation: Creates a job record in
JobTracker - Engine acquisition: Acquires an engine from the pool
-
Context injection: Injects job metadata (
__mcp_job_id__,__mcp_temp_dir__,__mcp_session_id__) into MATLAB workspace -
Background execution: Launches code with
engine.execute(code, background=True)and captures stdout/stderr -
Sync/async decision:
- Waits up to
sync_timeoutseconds for completion - If done: returns result inline with
status="completed" - If timeout: promotes to async background task (status="pending") and monitors completion asynchronously
- Waits up to
- Error handling: Catches execution exceptions, marks job failed, releases engine
-
Result building: Calls
_build_result()to format output, variables, figures, and files - Metrics collection: Records events (job_created, job_completed, job_failed) when collector is enabled
In-memory store for job metadata:
- Create/get/list/cancel jobs with unique
job_id - Store job state (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED)
- Prune completed jobs older than
job_retention_seconds - Thread-safe operations using asyncio locks
Per-user session isolation with temporary directory management:
-
Session creation: Each session gets a unique ID and temporary directory under
config.execution.temp_dir -
Default session: stdio transport uses a single
"default"session for backward compatibility -
Session lookup: SSE transport uses context's
session_idto route requests to appropriate session -
Idle tracking: Sessions maintain
last_activetimestamp -
Session expiration:
cleanup_expired()removes sessions idle beyondsession_timeout(skipping sessions with active jobs) - Resource cleanup: Destroys temp directories when sessions are removed
-
Max sessions: Enforces
max_sessionslimit to prevent unbounded growth
Pre-execution security checks:
-
Function blocklist: Scans code for blocked functions (
system,unix,dos,!,eval,feval,evalc,evalin,assignin,perl,python). Smart scanning strips string literals and comments first to avoid false positives -
Filename sanitization: Prevents path traversal in upload filenames via
pathlib.Path.resolve() -
Upload size limits: Enforces
max_upload_size_mbon file uploads - Metrics recording: Records security check events when collector is enabled
Structures tool responses:
-
Text formatting: Truncates output longer than
max_inline_text_length; saves full text to file if truncated and save_dir provided - Variable formatting: Summarizes workspace variables with name, type, size; includes value for small items, omits for large arrays
- Success response: Builds structured dict with status, job_id, output, variables, figures, files, warnings, execution_time
- Error response: Builds error dict with status, job_id, error_type, message, matlab_id (if available), execution_time
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 extraction:
mcp_extract_props.mextracts raw figure properties (line objects, scatter plots, bar charts, histograms, surface plots, images) and saves as JSON -
Python-side style mapping:
plotly_style_mapper.pyconverts MATLAB styles (line styles, markers, colormaps, fonts, colors) to Plotly equivalents - WebGL optimization: Automatically uses WebGL rendering for large datasets (10,000+ points)
-
Result composition:
plotly_convert.pyreads saved JSON, returns Plotly JSON + static PNG + optional thumbnail
Optional metrics collection and reporting (enabled via monitoring.enabled=true):
-
Event recording:
MetricsCollectorrecords system events (engine_scale_up, job_completed, job_failed, session_created, etc.) -
Time-series storage:
MetricsStorepersists events with timestamps for historical analysis - Reporting: Metrics accessible via admin tools and logs
Agent → execute_code("x = magic(3)")
→ Security validator.check_code() (OK)
→ JobExecutor.execute() creates job
→ Pool.acquire() gets engine
→ Inject job context into MATLAB workspace
→ Engine.execute(code, background=True) with stdout/stderr capture
→ Wait sync_timeout (default 30s)
→ Execution completes in <30s
→ ResultFormatter.build_success_response() structures result
→ Pool.release() resets workspace and returns engine
→ Return {status: "completed", job_id: "...", output: {...}, ...}
Agent → execute_code("long_simulation()")
→ Security validator.check_code() (OK)
→ JobExecutor.execute() creates job
→ Pool.acquire() gets engine
→ Inject job context into MATLAB workspace
→ Engine.execute(code, background=True) starts background future
→ Wait sync_timeout (default 30s)
→ Timeout exceeded → promote to async
→ Create background task: JobExecutor._wait_for_completion()
→ Return {status: "pending", job_id: "abc123"}
→ Engine held by background task
→
→ Agent calls get_job_status("abc123")
→ JobTracker returns {status: "running", progress: 45%}
→
→ Agent calls get_job_status("abc123")
→ JobTracker returns {status: "running", progress: 90%}
→
→ Agent calls get_job_result("abc123")
→ Background task completes, builds result
→ JobTracker.get_job() returns {status: "completed", output: {...}, ...}
→ Pool.release() resets workspace and returns engine
- One agent, one session
- Communication via stdin/stdout
- Uses fixed
"default"session ID - Simplest setup, no network, suitable for local Claude/Cursor/Copilot integration
- Multiple agents, multiple sessions
- HTTP-based, supports remote connections
- Session isolation via context's
session_idfrom request - Each session gets its own temporary directory
-
Production: Put behind a reverse proxy with authentication (
require_proxy_auth: true) - Security warning logged if SSE enabled without
require_proxy_auth=true