matlab-mcp-server-python

HanSur94/matlab-mcp-server-python
2 starsMITCommunity

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

HanSur94/matlab-mcp-server-python MCP server](https://glama.ai/mcp/servers/HanSur94/matlab-mcp-server-python/badges/score.svg)](https://glama.ai/mcp/servers/HanSur94/matlab-mcp-server-python) 🐍 🏠 🍎 πŸͺŸ 🐧 - Connect AI agents to MATLAB β€” execute code, run...

README.md

<p align="center"> <h1 align="center">MATLAB MCP Server</h1> <p align="center"> Give any AI agent the power of MATLAB β€” via the Model Context Protocol </p> </p>

<p align="center"> <a href="#quick-start">Quick Start</a> &bull; <a href="#examples">Examples</a> &bull; <a href="#mcp-tools-reference">Tools Reference</a> &bull; <a href="#configuration">Configuration</a> &bull; <a href="https://github.com/HanSur94/matlab-mcp-server-python/wiki">Wiki</a> </p>

<p align="center"> <a href="https://github.com/HanSur94/matlab-mcp-server-python/actions/workflows/ci.yml"> <img src="https://github.com/HanSur94/matlab-mcp-server-python/actions/workflows/ci.yml/badge.svg" alt="CI"> </a> <a href="https://pypi.org/project/matlab-mcp-python/"> <img src="https://img.shields.io/pypi/v/matlab-mcp-python" alt="PyPI"> </a> <a href="https://pypi.org/project/matlab-mcp-python/"> <img src="https://img.shields.io/pypi/pyversions/matlab-mcp-python" alt="Python"> </a> <a href="https://codecov.io/gh/HanSur94/matlab-mcp-server-python"> <img src="https://codecov.io/gh/HanSur94/matlab-mcp-server-python/branch/master/graph/badge.svg" alt="codecov"> </a> <a href="https://github.com/HanSur94/matlab-mcp-server-python/blob/master/LICENSE"> <img src="https://img.shields.io/github/license/HanSur94/matlab-mcp-server-python" alt="License"> </a> <a href="https://glama.ai/mcp/servers/@HanSur94/matlab-mcp-server-python"> <img src="https://glama.ai/mcp/servers/@HanSur94/matlab-mcp-server-python/badge" alt="MATLAB MCP server MCP server"> </a> </p>

---

A Python MCP server that connects any AI agent (Claude, Cursor, Copilot, custom agents) to a shared MATLAB installation. Execute code, discover toolboxes, check code quality, get interactive Plotly plots, and run long simulations β€” all through MCP.

Why?

  • Your AI agent can now write and run MATLAB code directly
  • Long-running jobs (hours!) run async β€” the agent keeps working while MATLAB computes
  • Multiple users share one MATLAB server via an elastic engine pool
  • Interactive plots come back as Plotly JSON β€” renderable in any web UI
  • Custom MATLAB libraries become first-class AI tools with zero code changes

Features

| Feature | Description | |---------|-------------| | Execute MATLAB code | Sync for fast commands, auto-async for long jobs | | Elastic engine pool | Scales 2-10+ engines based on demand | | Toolbox discovery | Browse installed toolboxes, functions, help text | | Code checker | Run checkcode/mlint before execution | | Interactive plots | Figures auto-converted to Plotly JSON | | Multi-user (SSE) | Session isolation with per-user workspaces | | Custom tools | Expose your .m functions as MCP tools via YAML | | Progress reporting | Long jobs report percentage back to the agent | | Cross-platform | Windows + macOS, MATLAB R2022b+ | | One-click Windows install | Offline install.bat β€” no admin rights needed |

MATLAB Plot Conversion to Interactive Plotly

Every MATLAB figure is automatically converted into an interactive Plotly chart β€” no extra code needed. When your MATLAB code creates a plot, the server:

  1. Extracts figure properties via mcp_extract_props.m β€” axes, line data, labels, colors, markers, legends, subplots
  2. Maps MATLAB styles to Plotly β€” line styles (-- β†’ dash), markers (o β†’ circle), legend positions, axis scales, colormaps
  3. Returns interactive JSON β€” renderable in any web UI with Plotly.newPlot()
  4. Generates a static PNG + thumbnail as fallback for non-interactive clients

Supported plot types: line, scatter, bar, area, subplots (subplot/tiledlayout), multiple axes, log/linear scales

Style fidelity: Line styles, marker shapes, colors (RGB), line widths, font sizes, axis labels, titles, legends, grid lines, axis limits, and background colors are all preserved.

% This MATLAB code...
x = linspace(0, 2*pi, 200);
plot(x, sin(x), 'r-', 'LineWidth', 2); hold on;
plot(x, cos(x), 'b--', 'LineWidth', 2);
plot(x, sin(x) .* cos(x), 'g-.', 'LineWidth', 2);
legend('sin(x)', 'cos(x)', 'sin(x)*cos(x)');
xlabel('x'); ylabel('y');
title('Trigonometric Functions');

...automatically becomes this interactive Plotly chart:

!MATLAB to Plotly Conversion

Line styles, colors, markers, legends, and axis labels are all preserved in the conversion.

Quick Start

Prerequisites

# Install MATLAB Engine API (from your MATLAB installation)
cd /Applications/MATLAB_R2024a.app/extern/engines/python  # macOS
# cd "C:\Program Files\MATLAB\R2024a\extern\engines\python"  # Windows
pip install .

Install the server

Windows (one-click, no admin needed):

git clone https://github.com/HanSur94/matlab-mcp-server-python.git
cd matlab-mcp-server-python
install.bat

The installer auto-detects MATLAB, creates a virtual environment, and installs everything from bundled wheels β€” fully offline, no internet required. Works on Windows 10/11 with Python 3.10, 3.11, or 3.12.

macOS / Linux:

# Option 1: Install from PyPI
pip install matlab-mcp-python

# Option 2: Install from source
git clone https://github.com/HanSur94/matlab-mcp-server-python.git
cd matlab-mcp-server-python
pip install -e ".[dev]"

Run it

# Single user (stdio) β€” simplest setup
matlab-mcp

# Multi-user (SSE) β€” shared server
matlab-mcp --transport sse

Connect to Claude Desktop

Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

{
  "mcpServers": {
    "matlab": {
      "command": "matlab-mcp"
    }
  }
}

Connect to Claude Code

claude mcp add matlab -- matlab-mcp

Connect to Cursor

Add to .cursor/mcp.json in your project:

{
  "mcpServers": {
    "matlab": {
      "command": "matlab-mcp"
    }
  }
}

Run with Docker

# Build the image
docker build -t matlab-mcp .

# Run with your MATLAB mounted
docker run -p 8765:8765 -p 8766:8766 \
  -v /path/to/MATLAB:/opt/matlab:ro \
  -e MATLAB_MCP_POOL_MATLAB_ROOT=/opt/matlab \
  matlab-mcp

# Or use docker-compose (edit docker-compose.yml to set your MATLAB path)
docker compose up

Note: The Docker image does not include MATLAB. You must mount your own MATLAB installation.

Upgrading? If you previously installed as matlab-mcp-server, uninstall first: pip uninstall matlab-mcp-server && pip install matlab-mcp-python

Examples

Basic: Run MATLAB Code

Ask your AI agent:

"Calculate the eigenvalues of a 3x3 magic square in MATLAB"

The agent calls execute_code: ``matlab A = magic(3); eigenvalues = eig(A); disp(eigenvalues) ``

Result returned inline: `` 15.0000 4.8990 -4.8990 ``

Signal Processing

"Generate a 1kHz sine wave, add noise, then filter it with a low-pass Butterworth filter and plot both"

fs = 8000;
t = 0:1/fs:0.1;
clean = sin(2*pi*1000*t);
noisy = clean + 0.5*randn(size(t));

[b, a] = butter(6, 1500/(fs/2));
filtered = filter(b, a, noisy);

subplot(2,1,1); plot(t, noisy); title('Noisy Signal');
subplot(2,1,2); plot(t, filtered); title('Filtered Signal');

Returns: Interactive Plotly chart + static PNG + thumbnail.

Long-Running Simulation (Async)

"Run a Monte Carlo simulation with 1 million trials"

n = 1e6;
results = zeros(n, 1);
for i = 1:n
    results(i) = simulate_trial();  % your custom function
    if mod(i, 1e5) == 0
        mcp_progress(__mcp_job_id__, i/n*100, sprintf('Trial %d/%d', i, n));
    end
end
disp(mean(results));

The agent gets a job ID immediately, polls progress ("Trial 500000/1000000 β€” 50%"), and retrieves results when done.

Custom Tools

Expose your proprietary MATLAB functions as first-class AI tools. Create custom_tools.yaml:

tools:
  - name: analyze_signal
    matlab_function: mylib.analyze_signal
    description: "Analyze a signal and return frequency components, SNR, and peak detection"
    parameters:
      - name: signal_path
        type: string
        required: true
      - name: sample_rate
        type: float
        required: true
      - name: window_size
        type: int
        default: 1024
    returns: "Struct with fields: frequencies, magnitudes, snr, peaks"

  - name: train_model
    matlab_function: ml.train_classifier
    description: "Train a classification model on the given dataset"
    parameters:
      - name: dataset_path
        type: string
        required: true
      - name: model_type
        type: string
        default: "svm"
    returns: "Trained model object saved to workspace"

Now the agent can call analyze_signal or train_model directly β€” with full parameter validation and help text.

MCP Tools Reference

Code Execution

| Tool | Parameters | Description | |------|-----------|-------------| | execute_code | code: str | Run MATLAB code. Returns inline if fast (<30s), or a job ID if promoted to async | | check_code | code: str | Run checkcode/mlint. Returns structured warnings/errors | | get_workspace | β€” | Show variables in the current MATLAB workspace |

Async Job Management

| Tool | Parameters | Description | |------|-----------|-------------| | get_job_status | job_id: str | Status + progress percentage for running jobs | | get_job_result | job_id: str | Full result of a completed job | | cancel_job | job_id: str | Cancel a pending or running job | | list_jobs | β€” | List all jobs in this session |

Discovery

| Tool | Parameters | Description | |------|-----------|-------------| | list_toolboxes | β€” | List installed MATLAB toolboxes | | list_functions | toolbox_name: str | List functions in a toolbox | | get_help | function_name: str | Get MATLAB help text for any function |

File Management

| Tool | Parameters | Description | |------|-----------|-------------| | upload_data | filename: str, content_base64: str | Upload data files to the session | | delete_file | filename: str | Delete a session file | | list_files | β€” | List files in the session directory |

File Reading

| Tool | Parameters | Description | |------|-----------|-------------| | read_script | filename: str | Read a MATLAB .m script file as text | | read_data | filename: str, format: str | Read data files (.mat, .csv, .json, .txt, .xlsx). format: summary or raw | | read_image | filename: str | Read image files (.png, .jpg, .gif) β€” renders inline in agent UIs |

Admin

| Tool | Parameters | Description | |------|-----------|-------------| | get_pool_status | β€” | Engine pool stats (available/busy/max) |

Monitoring

| Tool | Parameters | Description | |------|-----------|-------------| | get_server_metrics | β€” | Comprehensive server metrics (pool, jobs, sessions, system) | | get_server_health | β€” | Health status with issue detection (healthy/degraded/unhealthy) | | get_error_log | limit: int | Recent errors and notable events |

Configuration

All settings live in config.yaml with sensible defaults. Override any setting via environment variables:

# Override pool size
export MATLAB_MCP_POOL_MIN_ENGINES=4
export MATLAB_MCP_POOL_MAX_ENGINES=16

# Override sync timeout (promote to async after 60s instead of 30s)
export MATLAB_MCP_EXECUTION_SYNC_TIMEOUT=60

# Override transport
export MATLAB_MCP_SERVER_TRANSPORT=sse

Key Configuration Sections

<details> <summary><b>Server</b> β€” transport, host, port, logging</summary>

server:
  name: "matlab-mcp-server"
  transport: "stdio"        # stdio | sse
  host: "0.0.0.0"           # SSE only
  port: 8765                # SSE only
  log_level: "info"         # debug | info | warning | error
  log_file: "./logs/server.log"
  result_dir: "./results"
  drain_timeout_seconds: 300

</details>

<details> <summary><b>Pool</b> β€” engine count, scaling, health checks</summary>

pool:
  min_engines: 2            # always warm
  max_engines: 10           # hard ceiling
  scale_down_idle_timeout: 900   # 15 min
  engine_start_timeout: 120
  health_check_interval: 60
  proactive_warmup_threshold: 0.8
  queue_max_size: 50
  matlab_root: null         # auto-detect

</details>

<details> <summary><b>Execution</b> β€” timeouts, workspace isolation</summary>

execution:
  sync_timeout: 30          # seconds before async promotion
  max_execution_time: 86400 # 24h hard limit
  workspace_isolation: true
  engine_affinity: false    # pin session to engine
  temp_dir: "./temp"
  temp_cleanup_on_disconnect: true

</details>

<details> <summary><b>Security</b> β€” function blocklist, upload limits</summary>

security:
  blocked_functions_enabled: true
  blocked_functions:
    - "system"
    - "unix"
    - "dos"
    - "!"
    - "eval"
    - "feval"
    - "evalc"
    - "evalin"
    - "assignin"
    - "perl"
    - "python"
  max_upload_size_mb: 100
  require_proxy_auth: false

</details>

<details> <summary><b>Toolboxes</b> β€” whitelist/blacklist exposure</summary>

toolboxes:
  mode: "whitelist"         # whitelist | blacklist | all
  list:
    - "Signal Processing Toolbox"
    - "Optimization Toolbox"
    - "Statistics and Machine Learning Toolbox"
    - "Image Processing Toolbox"

</details>

<details> <summary><b>Output</b> β€” Plotly, images, thumbnails</summary>

output:
  plotly_conversion: true
  static_image_format: "png"
  static_image_dpi: 150
  thumbnail_enabled: true
  thumbnail_max_width: 400
  large_result_threshold: 10000
  max_inline_text_length: 50000

</details>

Monitoring

Built-in observability with a web dashboard, JSON health/metrics endpoints, and MCP tools for AI agent self-monitoring.

Dashboard

Access at http://localhost:8766/dashboard (stdio) or http://localhost:8765/dashboard (SSE).

!Dashboard Overview

Features:

  • 7 live gauges: pool utilization, engines (busy/total), active jobs, completed jobs, active sessions, avg execution time, errors/min
  • 6 time-series charts (Plotly.js): pool utilization, job throughput, execution time (avg + p95), active sessions, memory usage, error count
  • MATLAB execution log: filterable table showing time, event type, MATLAB code, output, and duration for every job
  • Time range selector: 1h, 6h, 24h, 7d views
  • Auto-refreshes every 10 seconds

!Execution Log

Health Endpoint

curl http://localhost:8766/health
{
  "status": "healthy",
  "uptime_seconds": 3600.1,
  "issues": [],
  "engines": {"total": 2, "available": 1, "busy": 1},
  "active_jobs": 1,
  "active_sessions": 3
}

Status codes: 200 for healthy/degraded, 503 for unhealthy.

Health evaluation rules:

| Status | Condition | |--------|-----------| | unhealthy | No engines running (total == 0) | | unhealthy | All engines busy at max capacity (available == 0 && total >= max_engines) | | degraded | Pool utilization > 90% | | degraded | Health check failures detected | | degraded | Error rate > 5/min | | healthy | None of the above |

Metrics Endpoint

curl http://localhost:8766/metrics
{
  "timestamp": "2026-03-12T23:01:56.799Z",
  "pool": {"total": 2, "available": 1, "busy": 1, "max": 10, "utilization_pct": 50.0},
  "jobs": {"active": 1, "completed_total": 47, "failed_total": 2, "cancelled_total": 0, "avg_execution_ms": 28.5},
  "sessions": {"total_created": 5, "active": 3},
  "errors": {"total": 2, "blocked_attempts": 0, "health_check_failures": 0},
  "system": {"uptime_seconds": 3600.1, "memory_mb": 108.8, "cpu_percent": 12.3}
}

Dashboard API

| Endpoint | Parameters | Description | |----------|-----------|-------------| | GET /health | β€” | Health status + issues | | GET /metrics | β€” | Live metrics snapshot (no DB hit) | | GET /dashboard | β€” | Web dashboard HTML | | GET /dashboard/api/current | β€” | Same as /metrics | | GET /dashboard/api/history | metric, hours | Time-series data from SQLite | | GET /dashboard/api/events | limit, type | Event log with MATLAB output |

Available history metrics: pool.utilization_pct, pool.total_engines, pool.busy_engines, jobs.completed_total, jobs.failed_total, jobs.avg_execution_ms, jobs.p95_execution_ms, sessions.active_count, system.memory_mb, system.cpu_percent, errors.total

Backend Architecture

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚           MetricsCollector                   β”‚
                    β”‚                                             β”‚
                    β”‚  In-memory:                                 β”‚
  record_event() ──│─▢ _counters (7 counters)                    β”‚
  (sync, from any  β”‚   _execution_times (ring buffer, maxlen=100)β”‚
   component)      β”‚                                             β”‚
                    β”‚  Background task (every 10s):               β”‚
                    β”‚   sample_once() ─▢ MetricsStore.insert()   β”‚
                    β”‚                                             β”‚
                    β”‚  Live snapshot (no DB):                     β”‚
                    β”‚   get_current_snapshot() ─▢ /metrics        β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚           MetricsStore (aiosqlite)           β”‚
                    β”‚                                             β”‚
                    β”‚  metrics table:                             β”‚
                    β”‚   id | timestamp | category | metric | valueβ”‚
                    β”‚   (4 indexes for fast queries)              β”‚
                    β”‚                                             β”‚
                    β”‚  events table:                              β”‚
                    β”‚   id | timestamp | event_type | details     β”‚
                    β”‚   (details = JSON with code, output, etc.)  β”‚
                    β”‚                                             β”‚
                    β”‚  Methods:                                   β”‚
                    β”‚   insert_metrics(), insert_event()          β”‚
                    β”‚   get_latest(), get_history(), get_events() β”‚
                    β”‚   get_aggregates(), prune()                 β”‚
                    β”‚                                             β”‚
                    β”‚  SQLite WAL mode, log-and-swallow errors    β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚     Starlette Dashboard App                  β”‚
                    β”‚                                             β”‚
                    β”‚  /health ─▢ evaluate_health(collector)      β”‚
                    β”‚  /metrics ─▢ collector.get_current_snapshot()β”‚
                    β”‚  /dashboard ─▢ cached index.html            β”‚
                    β”‚  /dashboard/api/* ─▢ store queries          β”‚
                    β”‚  /dashboard/static/* ─▢ JS, CSS, Plotly.js  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Event Types

Events are recorded synchronously via collector.record_event() from any server component. Each event includes a JSON details field.

| Event Type | Source | Details Fields | |------------|--------|---------------| | job_completed | Executor | job_id, execution_ms, code, output | | job_failed | Executor | job_id, code, error | | session_created | SessionManager | session_id_short | | engine_scale_up | PoolManager | engine_id, total_after | | engine_scale_down | PoolManager | engine_id, total_after | | engine_replaced | PoolManager | old_id, new_id | | health_check_fail | PoolManager | engine_id, error | | blocked_function | SecurityValidator | function, code_snippet |

In-Memory Counters

The collector maintains 7 counters updated on every event (no DB hit):

| Counter | Incremented By | |---------|---------------| | completed_total | job_completed | | failed_total | job_failed | | cancelled_total | job_cancelled | | total_created_sessions | session_created | | error_total | Any error event (job_failed, blocked_function, engine_crash, health_check_fail) | | blocked_attempts | blocked_function | | health_check_failures | health_check_fail |

Execution Time Tracking

Job execution times are stored in a ring buffer (deque(maxlen=100)) for O(1) avg/p95 calculation without DB queries. The p95 is computed as sorted_times[int((len-1) * 0.95)].

Transport Integration

| Transport | Monitoring Port | How | |-----------|----------------|-----| | SSE | Same as SSE port (8765) | Dashboard mounted as Starlette sub-app via mcp._additional_http_routes | | stdio | Separate port (8766) | Uvicorn started as background asyncio.Task |

Data Retention

The cleanup loop runs every 60 seconds and calls store.prune(retention_days=7) to delete metrics and events older than the configured retention period. SQLite WAL mode ensures reads aren't blocked during writes.

Configuration

monitoring:
  enabled: true
  sample_interval: 10      # seconds between metric samples
  retention_days: 7         # days to keep historical data
  db_path: "./monitoring/metrics.db"
  dashboard_enabled: true
  http_port: 8766           # dashboard/health port (stdio only)

Environment overrides: MATLAB_MCP_MONITORING_ENABLED, MATLAB_MCP_MONITORING_SAMPLE_INTERVAL, etc.

Architecture

AI Agent (Claude, Cursor, etc.)
       β”‚
       β”‚ MCP Protocol (stdio or SSE)
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   MCP Server (FastMCP 2.x)                                β”‚
β”‚   20 tools + custom tools                                 β”‚
β”‚   Session manager  β”‚  Security validator  β”‚  Formatter    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                               β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Job Executor               β”‚  β”‚  MetricsCollector       β”‚
β”‚   Sync/async execution       β”‚  β”‚  In-memory counters     β”‚
β”‚   Timeout auto-promotion     β”‚  β”‚  Ring buffer (p95)      β”‚
β”‚   stdout/stderr capture      β”‚  β”‚  Background sampling    β”‚
β”‚   Event recording ──────────────▢  Event recording       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                               β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   MATLAB Pool Manager        β”‚  β”‚  MetricsStore (SQLite)  β”‚
β”‚   Elastic engine pool        β”‚  β”‚  Time-series metrics    β”‚
β”‚   Scale up/down on demand    β”‚  β”‚  Event log with output  β”‚
β”‚   Health checks & replace    β”‚  β”‚  Aggregates & history   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                               β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   MATLAB Engines (R2022b+)    β”‚  β”‚  Dashboard (Starlette)  β”‚
β”‚   Engine 1 β”‚ Engine 2 β”‚ ... β”‚  β”‚  /health  /metrics      β”‚
β”‚   Workspace isolation        β”‚  β”‚  /dashboard (Plotly.js) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Request Flow

  1. AI agent sends execute_code via MCP protocol
  2. SecurityValidator checks code against function blocklist
  3. JobExecutor creates a job, acquires an engine from the pool
  4. Code runs in MATLAB with stdout/stderr captured via StringIO
  5. If completes within sync_timeout (30s): result returned inline
  6. If exceeds timeout: promoted to async, agent gets job_id to poll
  7. MetricsCollector.record_event() logs code + output + duration
  8. Engine released back to pool, workspace reset

Component Wiring

All components receive a collector reference at construction time. The collector is wired to live pool/tracker/sessions in the lifespan handler after startup. This allows synchronous record_event() calls from any component without async overhead.

# Construction (before event loop)
collector = MetricsCollector(config)
pool = EnginePoolManager(config, collector=collector)
executor = JobExecutor(pool, tracker, config, collector=collector)
sessions = SessionManager(config, collector=collector)
security = SecurityValidator(config.security, collector=collector)

# Lifespan (after event loop starts)
collector.pool = pool
collector.tracker = tracker
collector.sessions = sessions
collector.store = MetricsStore(config.monitoring.db_path)

Development

# Install dev dependencies
pip install -e ".[dev]"

# Run tests (no MATLAB needed β€” uses mock engine)
pytest tests/ -v

# Run with coverage
pytest tests/ --cov=matlab_mcp --cov-report=term-missing

# Lint
ruff check src/ tests/

Project Structure

src/matlab_mcp/
β”œβ”€β”€ server.py          # MCP server entry point, tool registration
β”œβ”€β”€ config.py          # YAML config, pydantic validation, env overrides
β”œβ”€β”€ pool/
β”‚   β”œβ”€β”€ engine.py      # Single MATLAB engine wrapper
β”‚   └── manager.py     # Elastic pool manager
β”œβ”€β”€ jobs/
β”‚   β”œβ”€β”€ models.py      # Job data model, lifecycle
β”‚   β”œβ”€β”€ tracker.py     # Job store, pruning
β”‚   └── executor.py    # Sync/async execution, timeout promotion
β”œβ”€β”€ tools/
β”‚   β”œβ”€β”€ core.py        # execute_code, check_code, get_workspace
β”‚   β”œβ”€β”€ discovery.py   # list_toolboxes, list_functions, get_help
β”‚   β”œβ”€β”€ jobs.py        # job status, result, cancel, list
β”‚   β”œβ”€β”€ files.py       # upload, delete, list files
β”‚   β”œβ”€β”€ admin.py       # pool status
β”‚   β”œβ”€β”€ monitoring.py  # get_server_metrics, get_server_health, get_error_log
β”‚   └── custom.py      # Custom tool loader from YAML
β”œβ”€β”€ monitoring/
β”‚   β”œβ”€β”€ collector.py   # Background metrics sampling, event recording
β”‚   β”œβ”€β”€ store.py       # Async SQLite storage for time-series data
β”‚   β”œβ”€β”€ health.py      # Health evaluation (healthy/degraded/unhealthy)
β”‚   β”œβ”€β”€ routes.py      # HTTP route handlers (/health, /metrics)
β”‚   β”œβ”€β”€ dashboard.py   # Starlette sub-app with dashboard API
β”‚   └── static/        # Dashboard HTML, CSS, JS (Plotly.js)
β”œβ”€β”€ output/
β”‚   β”œβ”€β”€ formatter.py   # Result formatting
β”‚   β”œβ”€β”€ plotly_convert.py       # Load Plotly JSON from MATLAB extraction
β”‚   β”œβ”€β”€ plotly_style_mapper.py  # MATLABβ†’Plotly style/property conversion
β”‚   └── thumbnail.py
β”œβ”€β”€ session/
β”‚   └── manager.py     # Session lifecycle, temp dirs
β”œβ”€β”€ security/
β”‚   └── validator.py   # Function blocklist, filename sanitization
└── matlab_helpers/
    β”œβ”€β”€ mcp_extract_props.m
    β”œβ”€β”€ mcp_checkcode.m
    └── mcp_progress.m

Security

| Protection | Description | |-----------|-------------| | Function blocklist | Blocks system(), unix(), dos(), !, eval(), feval(), evalc(), evalin(), assignin(), perl(), python() by default | | Filename sanitization | Rejects filenames with path traversal or invalid characters | | Workspace isolation | clear all; clear global; clear functions; fclose all; restoredefaultpath; between sessions | | SSE proxy auth | Requires reverse proxy with auth for production | | Upload size limits | Configurable max upload size (default 100MB) |

License

MIT

Contributing

Contributions welcome! Please open an issue or PR on GitHub.

Related MCP servers

Browse all β†’