-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Hannes Suhr edited this page Mar 12, 2026
·
20 revisions
AI Agent (Claude, Cursor, Copilot, etc.)
│
│ MCP Protocol (stdio or SSE)
▼
┌─────────────────────────────────┐
│ MCP Server (FastMCP) │
│ ├─ 14 built-in tools │
│ ├─ Custom tools (from YAML) │
│ ├─ Session manager │
│ ├─ Security validator │
│ └─ Result formatter │
└──────────┬──────────────────────┘
│
┌──────────▼──────────────────────┐
│ Job Executor │
│ ├─ Hybrid sync/async execution │
│ ├─ Timeout-based promotion │
│ └─ Progress injection │
└──────────┬──────────────────────┘
│
┌──────────▼──────────────────────┐
│ Engine Pool Manager │
│ ├─ Elastic scaling (min→max) │
│ ├─ Health checks │
│ ├─ Proactive warmup │
│ └─ Idle scale-down │
└──────────┬──────────────────────┘
│
┌──────────▼──────────────────────┐
│ MATLAB Engines (2020b+) │
│ Engine 1 │ Engine 2 │ ... │ N │
└───────────────────────────────────┘
The entry point. Uses FastMCP to handle MCP protocol details. Responsibilities:
- Register all 14 tools + custom tools
- Manage server lifecycle (startup, shutdown, drain)
- Route tool calls to implementation modules
- Run background tasks (health checks, cleanup)
Manages a pool of MATLAB engine instances:
-
Elastic scaling: Starts with
min_engines, scales up tomax_enginesunder load -
Proactive warmup: When utilization exceeds
proactive_warmup_threshold(80%), starts a new engine before it's needed -
Scale-down: Engines idle longer than
scale_down_idle_timeout(15 min) are stopped, down tomin_engines -
Health checks: Periodic
1+1eval to verify engines are responsive. Unhealthy engines are replaced - Queue: Requests wait in an async queue when all engines are busy
Wraps a single matlab.engine instance:
- Start/stop lifecycle
- Execute code (sync or background)
- Workspace reset between sessions
- Health check ping
- State tracking (idle, busy, error)
Hybrid sync/async execution:
- Code is security-checked (blocked functions scan)
- Job context is injected (
__mcp_job_id__,MCP_TEMP_DIR) - Execution starts synchronously
- If
sync_timeoutexceeded → auto-promote to async, returnjob_id - Background task monitors completion, stores result
In-memory store for job metadata:
- Create/get/list/cancel jobs
- Prune completed jobs older than
job_retention_seconds - Thread-safe with asyncio locks
Per-user session isolation:
- Each session gets a unique temp directory
- Workspace cleared between sessions (when
workspace_isolation=true) - Expired sessions cleaned up after
session_timeout - stdio transport uses a single "default" session
Pre-execution security checks:
-
Function blocklist: Scans code for blocked functions (
system,unix,dos,!). 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
Structures tool responses:
- Text output formatting with length limits
- Variable formatting from workspace queries
- Success/error response builders
- Delegates to Plotly converter and thumbnail generator
Converts MATLAB figures to interactive Plotly JSON:
- MATLAB-side:
mcp_fig2plotly.mextracts plot data (line, scatter, bar, histogram, surface, image) - Python-side:
load_plotly_json()reads the saved JSON file - Result includes: Plotly JSON + static PNG + optional thumbnail
Agent → execute_code("x = magic(3)")
→ Security check (OK)
→ Acquire engine from pool
→ Inject job context
→ Engine.eval("x = magic(3)")
→ Complete in <30s
→ Format result
→ Release engine
→ Return result to agent
Agent → execute_code("long_simulation()")
→ Security check (OK)
→ Acquire engine
→ Engine.eval (background=True)
→ 30s timeout exceeded
→ Return {job_id: "abc123", status: "running"}
→ Agent polls get_job_status("abc123") → progress: 45%
→ Agent polls get_job_status("abc123") → progress: 90%
→ Agent calls get_job_result("abc123") → full result
→ Engine released
- One agent, one session
- Communication via stdin/stdout
- Simplest setup, no network
- Multiple agents, multiple sessions
- HTTP-based, supports remote connections
- Session isolation via session IDs
-
Production: Put behind a reverse proxy with auth (
require_proxy_auth: true)