-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
AI Agent (Claude Code, Codex CLI, Cursor, etc.)
│
│ MCP Protocol
│ (stdio, SSE, or streamable HTTP)
▼
┌─────────────────────────────────────────┐
│ FastMCP 3.2.0 Server │
│ ├─ 20 built-in MCP tools │
│ ├─ Custom tools (from YAML) │
│ ├─ Human-in-the-loop approval gates │
│ ├─ Bearer token authentication │
│ └─ Session & context management │
└──────────┬──────────────────────────────┘
│
┌──────────▼──────────────────────────────┐
│ Job Executor │
│ ├─ Hybrid sync/async execution │
│ ├─ Timeout-based async promotion │
│ ├─ Progress tracking (mcp_progress) │
│ └─ Result formatting & Plotly convert │
└──────────┬──────────────────────────────┘
│
┌──────────▼──────────────────────────────┐
│ Engine Pool Manager │
│ ├─ Elastic scaling (min → max engines) │
│ ├─ Health checks & proactive warmup │
│ ├─ Idle scale-down │
│ └─ Queue-based acquisition │
└──────────┬──────────────────────────────┘
│
┌──────────▼──────────────────────────────┐
│ MATLAB Engine Pool │
│ (R2022b+ with Engine API) │
│ Engine 1 │ Engine 2 │ ... │ Engine N │
└────────────────────────────────────────┘
Responsibilities:
- Register and dispatch all 20 built-in MCP tools
- Load and register custom tools from
custom_tools.yaml - Manage server lifecycle (startup, shutdown, graceful drain)
- Run background tasks (health checks, session cleanup, job pruning)
- Support three transport modes: stdio (single-user), SSE (deprecated), streamable HTTP (multi-agent)
- Wire authentication middleware for HTTP/SSE transports
Key Methods:
-
create_server(config)— Factory that assembles all subsystems -
run()— Main entry point; selects transport and starts server -
main()— CLI handler with--transport,--config,--generate-tokenflags - Tool implementations organized by category: core, files, discovery, jobs, admin, monitoring, custom
Responsibilities:
- Enforce bearer token authentication on HTTP/SSE transports via ASGI middleware
- Read token from
MATLAB_MCP_AUTH_TOKENenvironment variable at startup - Return HTTP 401 with
WWW-Authenticate: Bearerheader on auth failure - Bypass auth for
/healthand OPTIONS pre-flight requests - Support CORS for browser-based agents
- Provide
--generate-tokenCLI helper to generate 64-character hex tokens
Components:
-
BearerAuthMiddleware— Pure ASGI middleware (notBaseHTTPMiddleware) usinghmac.compare_digest()for constant-time token comparison - Token generation via
secrets.token_hex(32)with shell snippets for POSIX, Windows cmd, and PowerShell - Startup warning when HTTP transport is selected without
MATLAB_MCP_AUTH_TOKENenv var
Responsibilities:
- Load YAML configuration with sensible defaults
- Apply environment variable overrides (
MATLAB_MCP_*prefix) - Validate all settings via Pydantic models
- Warn users if sensitive keys appear in config files
Configuration Domains:
-
ServerConfig: transport (
stdio|sse|streamablehttp), host, port, logging - PoolConfig: min/max engines, health checks, warmup thresholds
- ExecutionConfig: sync timeout, workspace isolation, temp directory
- SecurityConfig: blocked function list, upload size limits
- SessionConfig: max sessions, idle timeout, job retention
- OutputConfig: Plotly conversion, image formats, truncation thresholds
- MonitoringConfig: metrics collection, dashboard settings
- HITLConfig: Human-in-the-loop approval gates (execute, file ops)
Responsibilities:
- Manage a pool of MATLAB engine instances with elastic scaling
- Acquire/release engines from the pool
- Health check engines periodically; replace unhealthy ones
- Proactively start engines when utilization exceeds threshold (80%)
- Scale down idle engines beyond minimum count
Components:
-
EnginePoolManager— Orchestrates pool lifecycle -
MatlabEngineWrapper— Wraps individual MATLAB engine with state (STOPPED → STARTING → IDLE ↔ BUSY) -
matlab.engineintegration (lazy-imported, R2022b+)
Responsibilities:
- Execute MATLAB code with optional security checks
- Implement hybrid sync/async execution model
- Promote code to async when
sync_timeoutexceeded - Track job metadata (status, timestamps, results)
- Implement progress tracking via
mcp_progress.mhelper
Components:
-
JobExecutor— Orchestrates code execution lifecycle -
JobTracker— In-memory registry of jobs by session -
Job&JobStatus— Data models for job state - MATLAB helpers:
mcp_progress.m(progress reporting),mcp_checkcode.m(linting)
Execution Flow:
- Security validation (blocked functions scan)
- Job context injection (
__mcp_job_id__,__mcp_temp_dir__) - Synchronous execution up to
sync_timeout(default 30s) - Auto-promotion to async if timeout exceeded
- Background task monitors async completion
- Result formatting (text, variables, figures, files)
Responsibilities:
- Create and manage per-user sessions (SSE and streamable HTTP only)
- Isolate temporary working directories by session
- Track session idle time and enforce cleanup timeouts
- Enforce maximum session count limit
Components:
-
SessionManager— CRUD operations on sessions -
Session— Per-user workspace directory and metadata - Automatic cleanup of expired sessions (default 1 hour idle)
Transport-Specific Behavior:
- stdio: Single default session (no multi-user isolation)
- SSE/streamable HTTP: Each connected client gets a unique session with isolated temp directory
Responsibilities:
- Scan code for blocked MATLAB functions before execution
- Prevent shell injection via
!escape andsystem()family - Sanitize filenames to prevent path traversal attacks
- Enforce upload size limits
Blocked Functions: system, unix, dos, !, eval, feval, evalc, evalin, assignin, perl, python
Implementation Details:
- Smart detection: strips string literals and comments before scanning
- Regex-based pattern matching (best-effort heuristic)
- Metrics integration: logs violations for monitoring
Responsibilities:
- Format MATLAB execution results (text, variables, figures)
- Convert MATLAB figures to interactive Plotly JSON
- Generate PNG thumbnails for figures
- Truncate large results with optional file storage
Components:
-
ResultFormatter— Structures success/error responses -
plotly_style_mapper.py— MATLAB → Plotly style conversion (line styles, markers, colormaps, fonts) -
plotly_convert.py— Loads and validates Plotly JSON from MATLAB helper output -
thumbnail.py— Pillow-based thumbnail generation - MATLAB helper:
mcp_extract_props.m— Extracts figure properties from MATLAB figures
Plotly Conversion Pipeline:
- MATLAB side:
mcp_extract_props.mextracts raw figure data (axes, lines, surfaces, etc.) - Python side:
plotly_style_mapper.pyconverts MATLAB properties to Plotly equivalents - JSON validation:
load_plotly_json()reads and validates schema - WebGL support: Large datasets (10,000+ points) use WebGL traces for performance
Responsibilities:
- Collect real-time metrics (pool utilization, job counts, error rates)
- Evaluate overall server health (healthy, degraded, unhealthy)
- Expose metrics and health status via HTTP endpoints
- Provide interactive web dashboard
Components:
-
MetricsCollector— Accumulates counters and timing statistics -
MetricsStore— Async SQLite backend for time-series data -
evaluate_health()— Classification logic (healthy/degraded/unhealthy) - Dashboard: HTML/JS/CSS at
/dashboardwith real-time Plotly charts - Routes:
/health,/metrics,/dashboard,/dashboard/api/*
Responsibilities:
- Intercept sensitive operations (code execution, file operations)
- Request human approval via FastMCP's elicitation protocol
- Allow/deny/cancel operations based on response
- Audit log all approvals
Configuration:
-
HITLConfigin config.yaml:enabled,protected_functions,protect_file_ops,all_execute - Approval gates wired into
execute_code_impl,upload_data_impl,delete_file_impl - Disabled by default (zero operational cost)
graph LR
Agent["AI Agent<br/>(Claude Code, Codex CLI, Cursor)"]
Auth["BearerAuthMiddleware<br/>(Phase 2)"]
Server["FastMCP 3.2.0<br/>Tool Router"]
SecurityValidator["SecurityValidator<br/>(blocked functions)"]
HITL["HITL Approval Gates<br/>(Phase 4)"]
JobExecutor["JobExecutor<br/>(sync/async)"]
EnginePool["EnginePoolManager<br/>(elastic scaling)"]
MatlabEngine["MATLAB Engine<br/>Instance"]
ResultFormatter["ResultFormatter<br/>(Plotly convert)"]
SessionMgr["SessionManager<br/>(workspace isolation)"]
Agent -->|stdio/SSE/HTTP + Bearer token| Auth
Auth -->|Authenticated request| Server
Server -->|Route to tool| SecurityValidator
SecurityValidator -->|Scan for blocked functions| HITL
HITL -->|Request approval if enabled| JobExecutor
JobExecutor -->|Acquire engine| EnginePool
EnginePool -->|Get/create workspace| SessionMgr
EnginePool -->|Allocate engine| MatlabEngine
MatlabEngine -->|Execute code| MatlabEngine
MatlabEngine -->|Return output| ResultFormatter
ResultFormatter -->|Format + convert figures| Server
Server -->|MCP response| Agent
sequenceDiagram
participant Agent
participant Server as MCP Server
participant Validator as SecurityValidator
participant Executor as JobExecutor
participant Pool as EnginePool
participant Engine as MATLAB Engine
participant Formatter as ResultFormatter
Agent->>Server: execute_code("x = magic(3)")
Server->>Validator: validate_code()
alt Blocked Function Detected
Validator-->>Server: SecurityError
Server-->>Agent: {"error": "..."}
else Code OK
Validator-->>Server: OK
Server->>Executor: execute(code)
Executor->>Pool: acquire_engine()
Pool-->>Executor: engine_handle
Executor->>Engine: eval(code + job_context)
Engine-->>Executor: stdout, workspace
Executor->>Formatter: build_response()
Formatter-->>Executor: MCP response
Executor->>Pool: release_engine()
Server-->>Agent: {"text": "ans = 8 6 4...", ...}
end
sequenceDiagram
participant Agent
participant Server as MCP Server
participant Executor as JobExecutor
participant Engine as MATLAB Engine
participant Tracker as JobTracker
participant Pool as EnginePool
Agent->>Server: execute_code("long_simulation()")
Note over Executor: sync_timeout = 30s
Server->>Executor: execute(code)
Executor->>Pool: acquire_engine()
Executor->>Engine: eval(code, background=True)
Note over Executor: Waiting...
Executor-->>Executor: 30s timeout!
Executor->>Tracker: create_job(status=RUNNING)
Executor-->>Server: {job_id: "abc123", status: "RUNNING"}
Server-->>Agent: {"job_id": "abc123", "status": "RUNNING"}
par Background Monitoring
Executor->>Engine: poll future
Engine-->>Executor: 45% complete
Executor->>Tracker: update progress
and Agent Polls
Agent->>Server: get_job_status("abc123")
Server->>Tracker: get_job("abc123")
Tracker-->>Server: {status: "RUNNING", progress: 45%}
Server-->>Agent: {status: "RUNNING", progress: 45%}
end
Engine-->>Executor: Completed!
Executor->>Tracker: update_job(status=COMPLETED, result=...)
Agent->>Server: get_job_result("abc123")
Tracker-->>Server: Full result
Server-->>Agent: MCP response with output
- Direct stdin/stdout communication
- No network stack needed
- Simplest setup, safest for local development
- No authentication (by MCP design)
- Single session per server instance
Example:
matlab-mcp --transport stdio- HTTP-based, supports multiple concurrent agents
- Session isolation via
mcp-session-idheader - Deprecated: Codex CLI does not support SSE; use streamable HTTP instead
- Requires bearer token auth via
BearerAuthMiddleware - Suitable for teams behind reverse proxy
Example:
MATLAB_MCP_AUTH_TOKEN="$(matlab-mcp --generate-token)" \
matlab-mcp --transport sse --host 127.0.0.1 --port 8765- Single
/mcpendpoint for all MCP requests - HTTP POST for client requests, GET for streaming responses
- Per-session workspace isolation via
mcp-session-idheader - Stateless mode available (
stateless_http: true) for load-balanced deployments - Requires bearer token auth via
BearerAuthMiddleware - Recommended for Codex CLI, Claude Code, and Cursor
Example:
MATLAB_MCP_AUTH_TOKEN="$(matlab-mcp --generate-token)" \
matlab-mcp --transport streamablehttp --host 127.0.0.1 --port 8765The matlab.engine module is imported lazily inside pool/engine.py only when needed. This allows the server to start and respond to /health checks even if MATLAB is not installed or not available in PATH.
# In pool/engine.py
def start(self):
import matlab.engine
self._engine = matlab.engine.start_matlab()BearerAuthMiddleware is a pure ASGI middleware (not Starlette's BaseHTTPMiddleware) to avoid the known streaming double-send bug in Starlette versions < 0.50. This ensures auth works correctly with SSE and streamable HTTP transports.
The executor runs code synchronously up to sync_timeout, then auto-promotes to async if the timeout is exceeded. This provides:
- Fast path: Short scripts complete and return immediately
-
Long path: Long-running simulations return a
job_idfor polling - Transparency: Agents don't need to know execution mode in advance
Each session gets its own temporary directory (/tmp/matlab_mcp/<session_id>/ on Unix, %TEMP%\matlab_mcp\<session_id>\ on Windows). This provides:
- Workspace isolation: Files and data don't leak between agents
- Cleanup: Expired sessions' temp directories are removed automatically
- Multi-tenant safety: Concurrent agents can't interfere with each other
All configuration can be overridden via environment variables (MATLAB_MCP_*), which:
- Eliminates config file security debt (no secrets in YAML)
- Enables CI/CD and containerized deployments
- Allows per-deployment customization without file editing
Metrics collection (MetricsCollector, MetricsStore) is optional and disabled by default to reduce memory overhead in resource-constrained environments. Enable via monitoring.enabled: true in config.
- What: MATLAB's Python API for evaluating code in MATLAB engines
-
Where:
src/matlab_mcp/pool/engine.py -
How:
engine.eval(code)for sync,engine.eval(..., background=True)for async - Lazy import: Only imported when first engine starts
- What: MCP server framework and protocol handler
-
Where:
src/matlab_mcp/server.py -
How:
@mcp.tool()decorators,ctxparameter for context - Features used: Tool registration, elicitation API, custom routes, middleware integration
- What: ASGI middleware and HTTP routing
-
Where:
src/matlab_mcp/auth/middleware.py, monitoring dashboard -
How:
Middlewareclass, custom ASGI callables,cors.CORSMiddleware - Why: Pure ASGI for auth; CORS support for browser agents
- What: Time-series metrics storage
-
Where:
src/matlab_mcp/monitoring/store.py -
How:
aiosqliteasync wrapper for WAL-mode SQLite -
Optional: Disabled by default; enable with
monitoring.enabled: true
| Metric | Target | Notes |
|---|---|---|
| Tool registration | <100ms | On startup; cached after |
| Code security scan | <10ms | Regex-based; depends on code size |
| Sync execution | Agent timeout (default 30s) | Fast MATLAB code completes quickly |
| Async promotion | <1s overhead | Minimal; mostly engine overhead |
| Health check | ~500ms per engine |
1+1 eval per engine; runs periodically |
| Session cleanup | <100ms | Runs on configurable interval |
| Plotly conversion | 100-500ms | Depends on figure complexity |
| Dashboard load | <1s | Cached static HTML + API calls |