MATLAB MCP Server — v2.0 Milestone
A Python-based MCP server that lets AI coding agents (Claude Code, Codex CLI, etc.) execute MATLAB code, inspect workspaces, manage files, and monitor server health. It bridges the Model Context Protocol to MATLAB's Engine API with elastic pooling, session isolation, and async job orchestration.
Core Value: Any MCP-compatible coding agent can connect to MATLAB and run code securely — with minimal setup, proper authentication, and production-grade reliability.
- Tech stack: Python 3.10+, must keep backward compat with existing config.yaml format
- Platform: Must work on Windows 10 without admin rights (no service installation, no elevated ports)
- Dependency: FastMCP 3.0 migration must not break existing stdio/SSE clients
- MATLAB: Requires MATLAB R2022b+ with Engine API — this is an external user dependency
- Python 3.10+ - Core application language, enforced via
pyproject.toml - MATLAB R2022b+ - Target runtime for code execution (via MATLAB Engine API)
- JavaScript/TypeScript - Dashboard frontend (static HTML/CSS/JS in monitoring UI)
- Python 3.10, 3.11, 3.12 - Supported versions per
pyproject.toml - MATLAB Engine API for Python - Bridge to MATLAB execution (imported at
src/matlab_mcp/pool/engine.py) - pip - Python dependency management
- Lockfile:
requirements-lock.txt(pinned versions) - Build backend: hatchling (via
[build-system]inpyproject.toml)
- FastMCP 2.14.5 - MCP server framework (
fastmcp>=2.0.0,<3.0.0in dependencies) - Starlette 0.52.1 - Web framework for HTTP monitoring dashboard (
src/matlab_mcp/monitoring/dashboard.py) - Uvicorn 0.42.0 - ASGI server for HTTP transport and dashboard
- Model Context Protocol (MCP 1.26.0) - Protocol implementation
- Pydantic 2.12.5 - Configuration validation and data models (
src/matlab_mcp/config.py) - PyYAML 6.0.3 - Configuration file parsing (YAML format)
- aiosqlite 0.22.1 - Async SQLite for metrics persistence (
src/matlab_mcp/monitoring/store.py) - Plotly 6.6.0 - Interactive plot generation and JSON export (
src/matlab_mcp/output/plotly_convert.py) - Pillow 12.1.1 - Image processing for thumbnails (
src/matlab_mcp/output/thumbnail.py) - sse-starlette 3.3.3 - Server-sent events for multi-user SSE transport
- websockets 16.0 - WebSocket support
- pytest 7.0+ - Test runner
- pytest-asyncio 0.21+ - Async test support
- pytest-cov 4.0+ - Coverage reporting
- ruff 0.1.0+ - Code linting and formatting
- pip-audit 2.6.0+ - Security vulnerability scanning
- build 1.0.0+ - Python package builder
fastmcp>=2.0.0,<3.0.0- MCP server framework enabling tool registration and protocol handlingpydantic>=2.0.0- Configuration validation with type safety (used extensively insrc/matlab_mcp/config.py)matlab.engine- Actual MATLAB execution bridge (external, installed from MATLAB installation)pyyaml>=6.0- Configuration file loading (load_configinsrc/matlab_mcp/config.py)aiosqlite>=0.19.0- Metrics database for monitoring (src/matlab_mcp/monitoring/store.py)plotly>=5.9.0- Interactive visualization and figure conversionPillow>=9.0.0- Image processing and thumbnail generationpsutil>=5.9.0- System metrics collection (optional monitoring dependency)uvicorn>=0.20.0- ASGI server for dashboard HTTP endpoints
- YAML-based via
config.yaml(optional; defaults used if absent) - Environment variable overrides via
MATLAB_MCP_*prefix convention - Example:
MATLAB_MCP_POOL_MAX_ENGINES=20overridesconfig.pool.max_engines - Configuration system:
src/matlab_mcp/config.pywithload_config()and_apply_env_overrides() pyproject.toml- Modern Python packaging with PEP 517/518- Hatchling backend - Build system
- Entry point:
matlab-mcp = "matlab_mcp.server:main"(CLI command) server- Transport (stdio/sse), host/port, loggingpool- MATLAB engine pool sizing (min/max engines, timeouts)execution- Job execution timeouts, workspace isolationsecurity- Blocked functions, upload size limits, proxy authmonitoring- Metrics collection, SQLite database path, dashboard settingssessions- Session limits, idle timeoutsworkspace- Default MATLAB paths and startup commands
- Python 3.10+ with pip
- MATLAB R2022b+ with Python Engine API installed (
python setup.py installfrom MATLAB'sextern/engines/python) - Optional: hatchling, ruff, pytest, coverage tools
- Python 3.10+ runtime
- MATLAB R2022b+ installation (single shared instance or containerized)
- Transports:
- Storage:
- Docker support via
docker-compose.yml(provided) - Services: Python environment with mounted MATLAB installation
- Volumes: config files (RO), results, monitoring data
- Ports: 8765 (main), 8766 (dashboard HTTP)
- Module files use lowercase with underscores:
executor.py,security_validator.py - Test files use
test_<module_name>.py:test_session.py,test_security.py - Mock files use
<name>_mock.py:matlab_engine_mock.py - Package directories use lowercase with underscores:
matlab_mcp,matlab_mcp/tools,matlab_mcp/security - Use snake_case for all functions:
execute_code_impl,check_code_impl,get_workspace_impl - Implementation functions commonly end with
_implsuffix for clarity - Async functions use
async defprefix:async def execute_code_impl(...) - Private/internal functions use leading underscore:
_strip_string_literals,_make_config - Use snake_case for all variables and constants:
session_id,max_sessions,temp_dir - Constants (module-level, typically) use UPPERCASE:
_DEFAULT_SESSION_ID,_DEFAULT_MAX_SIZE_MB - Abbreviations expand naturally:
temp_dirnottmp_dir,session_idnotsess_id - Thread locks use
_locksuffix:self._lock = threading.Lock() - Custom exception classes use PascalCase:
BlockedFunctionError,MatlabExecutionError - Dataclass and Pydantic model classes use PascalCase:
Session,Job,CustomToolParam,AppConfig - Type hints use full paths only when necessary for clarity (avoid noise)
- Line length: 100 characters (configured in
pyproject.tomlviaruff) - Indentation: 4 spaces (Python default)
- Trailing commas in multiline collections encouraged for diffs
- Tool:
ruff(configured inpyproject.toml) - Target version: Python 3.10+ (
target-version = "py310") - Configuration minimal - only line-length and version specified
- Async functions are marked
async def - All test methods that call async code are
async def test_*(pytest-asyncio handles auto-execution viaasyncio_mode = "auto") - Background operations use
asyncio.create_task()for fire-and-forget
- No path aliases configured; all imports use full module paths from package root
- Absolute imports preferred over relative imports
- All modules use
from __future__ import annotationsat the top for forward reference support - This is a universal practice across the codebase
- Custom exceptions inherit from standard Python exceptions:
class BlockedFunctionError(Exception): - Error handling uses try/except blocks that catch specific exceptions when possible
- Generic
Exception as excused only when catching broad categories - Security violations raise
BlockedFunctionErrorwith descriptive message - File operations catch
FileNotFoundError,ValueError, and genericExceptionfor disk issues - Results return error dicts instead of raising exceptions where appropriate:
{"status": "error", "message": "..."}
- Every module defines
logger = logging.getLogger(__name__)near the top - Log levels used appropriately:
- String formatting uses
%sstyle with arguments:logger.info("Starting pool with %d engines", num_engines) - Complex objects logged only at debug level
- Docstrings required for all public classes and functions
- Inline comments rare; code should be self-explanatory
- Comments explain WHY something is done, not WHAT it does
- Section separators used:
# --------lines with hyphens - NumPy-style docstrings used for comprehensive API documentation
- All parameters documented with type and description
- Returns section specifies return type and keys/structure
- Raises section documents exceptions that can be raised
- Maximum 4-5 positional parameters; additional parameters via context objects or config
- Keyword-only arguments used where clarity helps:
async def execute_code_impl(..., temp_dir: Optional[str] = None) - Type hints on all parameters and return values (even
Anywhen necessary) - Simple return values (dict, str, bool) preferred
- Complex returns use structured objects (dataclasses, Pydantic models)
- Async functions return coroutines that resolve to above types
- Implementation functions often return dicts:
{"status": "ok", "key": value}
- No explicit
__all__lists; public functions/classes are those not starting with underscore - Module docstrings at the top describe the module's purpose and main exports
- Internal utilities prefixed with underscore:
_strip_string_literals,_make_config - Minimal use of barrel files;
__init__.pyfiles mostly empty or import key classes src/matlab_mcp/__init__.pytypically empty (no re-exports)- Direct imports from submodules preferred
- Related functions/classes grouped by responsibility in single files
- Config models (
config.py) use PydanticBaseModel - Dataclass models use
@dataclassdecorator withfrom dataclasses import - Each tool category has its own module:
tools/core.py,tools/files.py,tools/admin.py
- All function parameters have type hints
- All return types specified (including
-> dict,-> None) - Class attributes typed (especially in dataclasses and Pydantic models)
- Optional parameters use
Optional[T]orT | None
- FastMCP framework — Exposes tools via the Model Context Protocol
- Async-first design — Hybrid sync/async execution with background job promotion
- Resource pooling — Elastic pool of MATLAB engines (scales 2-10+ on demand)
- Session isolation — Per-session workspaces and temporary directories
- Event-driven monitoring — Real-time metrics collection with retention-based cleanup
- Purpose: Top-level FastMCP server initialization, lifespan management, tool registration
- Location:
src/matlab_mcp/server.py - Contains:
MatlabMCPServerstate container,create_server()factory, tool decorators - Depends on: All subsystems (pool, executor, sessions, security, monitoring)
- Used by: CLI entry point (
main()) - Purpose: Manage lifecycle of MATLAB engine instances with auto-scaling and health checks
- Location:
src/matlab_mcp/pool/ - Contains:
- Depends on: MATLAB Engine API (via lazy import)
- Used by:
JobExecutor - Purpose: Orchestrate job lifecycle from creation through completion
- Location:
src/matlab_mcp/jobs/ - Contains:
- Depends on: Engine Pool, Security Validator
- Used by: Core tools (
execute_code,check_code) - Purpose: Manage user sessions with workspace isolation and idle timeout cleanup
- Location:
src/matlab_mcp/session/manager.py - Contains:
SessionManagermanagingSessionobjects (temp_dir, created_at, last_active) - Depends on: Config
- Used by: Server layer for temp_dir routing
- Purpose: Validate MATLAB code against blocked functions and sanitize filenames
- Location:
src/matlab_mcp/security/validator.py - Contains:
SecurityValidatorwith precompiled regex patterns, string-literal stripping - Depends on: Config
- Used by: Core execution tools
- Purpose: Implement MCP tool handlers (execute, check, discover, files, jobs, monitoring)
- Location:
src/matlab_mcp/tools/ - Contains:
- Depends on: Executor, Tracker, Security, Sessions, Pool, Output Formatter
- Used by: Server layer (tool registration)
- Purpose: Format MATLAB execution results for MCP responses
- Location:
src/matlab_mcp/output/ - Contains:
- Depends on: Config
- Used by:
JobExecutorfor result formatting - Purpose: Collect metrics and provide health/diagnostic views
- Location:
src/matlab_mcp/monitoring/ - Contains:
- Depends on: Config, Pool, Tracker, Sessions
- Used by: Server layer (background sampling task), dashboard UI
- Purpose: Load, validate, and apply environment overrides
- Location:
src/matlab_mcp/config.py - Contains: Pydantic models (ServerConfig, PoolConfig, ExecutionConfig, SecurityConfig, etc.),
load_config(), environment variable override logic - Depends on: None (external: yaml, pydantic)
- Used by: Server factory, all subsystems
- Pool state: In-memory asyncio.Queue for available engines, list of all engines
- Job state: Thread-safe dict keyed by job_id, retained for 24 hours after terminal state
- Session state: Thread-safe dict keyed by session_id, idle timeout of 1 hour default
- Monitoring state: In-memory counters + SQLite for historical metrics (7 days default)
- Purpose: Single MATLAB engine instance with lifecycle state machine
- Examples:
src/matlab_mcp/pool/engine.py - Pattern: State enum (STOPPED → STARTING → IDLE ↔ BUSY), lazy MATLAB Engine API import, health check via
is_aliveproperty - Purpose: Represents one MATLAB code execution with full lifecycle tracking
- Examples:
src/matlab_mcp/jobs/models.py - Pattern: Dataclass with state transition methods (mark_running, mark_completed, mark_failed, mark_cancelled), Optional result/error fields
- Purpose: User session context with workspace isolation
- Examples:
src/matlab_mcp/session/manager.py - Pattern: Dataclass with session_id → temp_dir mapping, last_active timestamp for idle detection
- Purpose: Code validation before execution
- Examples:
src/matlab_mcp/security/validator.py - Pattern: Precompiled regex patterns for blocked functions, string-literal stripping to avoid false positives, configurable blocklist
- Purpose: Transform raw execution results into MCP response format
- Examples:
src/matlab_mcp/output/formatter.py - Pattern: Text truncation with optional file save, variable summarization, figure attachment handling
- Purpose: Record events and sample system metrics
- Examples:
src/matlab_mcp/monitoring/collector.py - Pattern: Event queue for async store writes, ring buffer for execution times, periodic sampling task
- Location:
src/matlab_mcp/server.py::main() - Triggers:
python -m matlab_mcpor installed script - Responsibilities: Parse CLI args (--config, --transport, --inspect), load config, set up logging, create server, run FastMCP in selected transport mode
- Location:
src/matlab_mcp/server.py::create_server()— all tools registered at lines 391-679 - Triggers: MCP client calls (e.g.,
execute_code,get_workspace,upload_data) - Responsibilities: Deserialize request, route to implementation function, serialize response
- Location:
src/matlab_mcp/server.py::lifespan()at lines 158-363 - Triggers: Server startup
- Responsibilities:
- Framework: Python logging module (stdlib)
- Pattern: Logger per module with
logger = logging.getLogger(__name__), configured via--log-level(debug/info/warning/error) - Files: Dual output to stderr + file (
config.server.log_filedefaults to./logs/server.log) - Code validation:
SecurityValidator.check_code()with precompiled regex patterns - File validation:
SecurityValidator.sanitize_filename()prevents path traversal - Config validation: Pydantic validators in
AppConfig.validate_pool()(min_engines ≤ max_engines) - MATLAB functions: Blocked function list in config (system, eval, perl, python, etc.)
- Transport-specific:
- No built-in token/API key auth (relying on deployment layer)
- Event recording: Fire-and-forget to async store writes (MetricsCollector.record_event)
- Metrics types: Counters (completed_total, failed_total), ring buffer (execution_times), system stats (memory_mb, cpu_percent)
- Dashboard: Real-time HTML UI with Plotly charts (stdio transport on http://127.0.0.1:8766/dashboard by default)
- Persistence: SQLite at config.monitoring.db_path (./monitoring/metrics.db), pruned after retention_days (7 default)
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
Use these entry points:
/gsd:quickfor small fixes, doc updates, and ad-hoc tasks/gsd:debugfor investigation and bug fixing/gsd:execute-phasefor planned phase work
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
Profile not yet configured. Run
/gsd:profile-userto generate your developer profile. This section is managed bygenerate-claude-profile-- do not edit manually.