-
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) - server.py │
│ ├─ Server entry point │
│ ├─ Tool registration & routing │
│ ├─ Request context management │
│ └─ Lifespan management │
└──────────┬──────────────────────────┘
│
┌──────────▼──────────────────────────┐
│ Security Validator │
│ ├─ Code blocklist scanning │
│ ├─ Filename sanitization │
│ └─ Upload size validation │
└──────────┬──────────────────────────┘
│
┌──────────▼──────────────────────────┐
│ Session Manager │
│ ├─ Per-user session isolation │
│ ├─ Temp directory management │
│ └─ Idle session cleanup │
└──────────┬──────────────────────────┘
│
┌──────────▼──────────────────────────┐
│ Job Executor │
│ ├─ Hybrid sync/async execution │
│ ├─ Timeout-based promotion │
│ ├─ Job context injection │
│ └─ Progress tracking │
└──────────┬──────────────────────────┘
│
┌──────────▼──────────────────────────┐
│ Job Tracker │
│ ├─ In-memory job storage │
│ ├─ State management │
│ └─ Completed job pruning │
└──────────┬──────────────────────────┘
│
┌──────────▼──────────────────────────┐
│ Engine Pool Manager │
│ ├─ Elastic scaling (min→max) │
│ ├─ Health checks │
│ ├─ Proactive warmup │
│ ├─ Idle scale-down │
│ └─ Queue management │
└──────────┬──────────────────────────┘
│
┌──────────▼──────────────────────────┐
│ Engine Wrapper (Pool) │
│ ├─ Lifecycle (start/stop) │
│ ├─ Code execution (sync/async) │
│ ├─ Workspace reset │
│ ├─ Health check ping │
│ └─ State tracking │
└──────────┬──────────────────────────┘
│
┌──────────▼──────────────────────────┐
│ Output Formatter │
│ ├─ Text formatting & truncation │
│ ├─ Variable summarization │
│ ├─ Figure attachment │
│ ├─ File listings │
│ └─ Response structuring │
└──────────┬──────────────────────────┘
│
┌──────────▼──────────────────────────┐
│ MATLAB Engines (2020b+) │
│ Engine 1 │ Engine 2 │ ... │ N │
└──────────────────────────────────────┘
The entry point and orchestrator. Uses FastMCP to handle MCP protocol details. Responsibilities:
- Server initialization: Creates all subsystems (pool, tracker, executor, sessions, security, formatter)
- Tool registration: Registers all 20+ built-in tools and custom tools from YAML
- Request routing: Directs tool calls to implementation modules
-
Session management: Maps stdio/SSE requests to sessions via
_get_session_id() - Lifespan management: Startup (directory creation, helper paths), shutdown (engine cleanup), and periodic background tasks (health checks, session cleanup)
-
Monitoring integration: Optional metrics collection via
MetricsCollector
The server state is encapsulated in MatlabMCPServer class which holds references to all components.
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: Optionally records security events to monitoring system
Per-user session isolation:
- Session creation: Each session gets a unique ID and temp directory
- Session storage: In-memory dict keyed by session ID (thread-safe with lock)
- Default session: Single "default" session for stdio transport
-
Workspace isolation: Sessions can be cleared between requests (via
workspace_isolationconfig) -
Idle cleanup:
cleanup_expired()removes sessions idle beyondsession_timeout, skipping sessions with active jobs -
Session properties: Track
created_at,last_active,idle_seconds,temp_dir
In-memory store for job metadata:
-
Job creation:
create_job()allocates new job with status PENDING -
Job retrieval:
get_job(),list_jobs()for querying -
Job cancellation:
cancel_job()marks job CANCELLED -
State transitions:
get_job()updateslast_activetimestamp -
Automatic pruning:
prune_completed()removes jobs completed more thanjob_retention_secondsago - Thread-safe: Uses asyncio locks for concurrent access
Hybrid sync/async execution orchestration:
- Job creation: Creates job in tracker
-
Engine acquisition:
await pool.acquire()gets an available engine -
Context injection: Injects
__mcp_job_id__,__mcp_temp_dir__and helper paths into MATLAB workspace -
Background execution: Starts code with
engine.execute(code, background=True)returning a future -
Timeout handling:
- Waits up to
sync_timeoutseconds for completion - If completes in time → returns result inline with status "completed"
- If times out → spawns background task, returns job_id with status "pending"
- Waits up to
-
Result building:
_build_result()collects text output, variables, figures, files from the engine workspace - Engine release: Returns engine to pool after completion or promotion to async
- Error handling: Catches exceptions, marks job failed, releases engine
- Metrics: Records job events (created, completed, failed) with timing and output details
Manages a pool of MATLAB engine instances:
-
Initialization:
start()launchesmin_enginesin parallel -
Engine acquisition:
acquire()returns available engine; scales up if needed and undermax_engineslimit -
Engine release:
release()resets workspace and returns engine to queue -
Elastic scaling: Automatic scale-up when queue is empty and
len(all_engines) < max_engines; scale-down during health checks if idle beyondscale_down_idle_timeoutand abovemin_engines -
Proactive warmup: Optional feature (via
proactive_warmup_threshold) to start new engines before load peak -
Health checks:
run_health_checks()runs periodic1+1eval on idle engines; replaces failed engines - Queue management: Async queue of available engines; blocks acquirers when all engines busy
- State tracking: Tracks total engines, available count, busy count
- Metrics collection: Records scale-up events and health check failures
Wraps a single MATLAB engine instance:
-
Lifecycle:
start()launches engine and applies default paths + startup commands;stop()quits engine - State machine: STOPPED → STARTING → IDLE ↔ BUSY
-
Execution:
execute(code, background, nargout, stdout, stderr)runs code viaengine.eval() -
Workspace reset:
reset_workspace()clears all variables, closes files, restores paths, re-runs startup -
Health checks:
health_check()runs trivial1eval to confirm responsiveness -
Idle tracking:
idle_secondsproperty returns time since last marked idle -
State properties:
state,is_alive,idle_seconds
Structures tool responses:
-
Text formatting:
format_text()returns inline text if undermax_inline_text_length; truncates and saves to file otherwise -
Variable formatting:
format_variables()summarizes workspace variables with name, type, size, value (or placeholder for large objects) -
Success response:
build_success_response()assembles dict with status, job_id, output, variables, figures, files, warnings, execution_time -
Error response:
build_error_response()builds error dict with error_type, message, matlab_id (if available) -
Large result handling: Uses
large_result_thresholdto determine when to show only type/size metadata instead of full value
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, scatter, bar, histogram, surface, image, text) -
Python-side style mapping:
plotly_style_mapper.pyconverts MATLAB styles (line styles, markers, colormaps, fonts, colors) to Plotly equivalents - WebGL support: Automatically uses WebGL rendering for large datasets (10,000+ points) to improve performance
-
Result bundling:
plotly_convert.py/load_plotly_json()reads extracted JSON and builds response with:- Interactive Plotly JSON object
- Static PNG snapshot
- Optional thumbnail (small preview image)
Optional metrics collection:
-
Event recording:
record_event(event_type, data)logs events like job creation, completion, engine scale-up - Metrics storage: Collects and aggregates metrics if monitoring is enabled in config
- Integration points: Connected to pool, executor, tracker, sessions, security modules
-
Conditional: Only active if
config.monitoring.enabled = true
Agent → execute_code("x = magic(3)")
→ Session lookup/creation
→ 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)
→ Wait for future (< sync_timeout)
→ Future completes in 50ms
→ Build result (text, variables, figures)
→ Release engine to pool (reset workspace)
→ Mark job completed
→ Return result with status="completed"
Agent → execute_code("long_simulation()")
→ Session lookup/creation
→ Security check (OK)
→ Create job in tracker
→ Acquire engine from pool
→ Inject job context
→ Engine.execute("long_simulation()", background=True)
→ Wait for future (< sync_timeout)
→ 30s timeout exceeded
→ Spawn background task to wait for completion
→ Release engine to pool
→ 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 finishes, future completes
→ Build result and store in job
→ Job status becomes "completed"
→ Agent calls get_job_result("abc123")
→ Return full result with status="completed"
Agent → get_workspace()
→ Session lookup
→ Acquire engine from pool
→ Query workspace variables (who -all)
→ Build variable dict with values
→ Release engine
→ Format variables (truncate large arrays)
→ Return variable list
- One agent, one session
- Communication via stdin/stdout
- Uses fixed "default" session ID
- Simplest setup, no network
- All requests use same MATLAB workspace (unless workspace isolation enabled)
- Multiple agents, multiple sessions
- HTTP-based, supports remote connections
- Session isolation via session IDs from context (
ctx.session_id) - Each session owns temp directory and workspace
-
Production: Put behind a reverse proxy with auth (
require_proxy_auth: true) - Security warning logged at startup if SSE enabled without proxy auth
| Component | Depends On | Provides |
|---|---|---|
| Server | FastMCP, all subsystems | MCP tool interface |
| Security Validator | Config | Code validation |
| Session Manager | Config | Session/temp_dir management |
| Job Tracker | Config | Job storage & lifecycle |
| Job Executor | Pool, Tracker, Config, Security | Code execution |
| Engine Pool Manager | Config, Engine Wrapper | Engine acquisition/release |
| Engine Wrapper | matlab.engine | Code execution interface |
| Output Formatter | Config, Plotly Converter | Response structuring |
| Plotly Converter | MATLAB helpers | Figure conversion |
| Monitoring | Config, Collector | Event recording |