# Directory Structure
```
├── .gitignore
├── .python-version
├── claude_desktop_config_example.json
├── matlab_server.py
├── pyproject.toml
├── README.md
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
```
1 | 3.11
2 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Python-generated files
2 | __pycache__/
3 | *.py[oc]
4 | build/
5 | dist/
6 | wheels/
7 | *.egg-info
8 |
9 | # Virtual environments
10 | .venv
11 | matlab_scripts/*
12 | *.png
13 | *.jpg
14 | .DS_Store
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # MATLAB MCP Server
2 |
3 | This Model Context Protocol (MCP) server provides integration with MATLAB, allowing you to create and execute MATLAB scripts and functions through Claude or other MCP clients.
4 |
5 | ## Setup Requirements
6 |
7 | - Python 3.11 (Python 3.13 and 3.12 are not currently supported by MATLAB Engine)
8 | - MATLAB R2024a (or compatible version)
9 | - uv package manager
10 |
11 | ## Installation
12 |
13 | 1. Create and set up the Python environment:
14 | ```bash
15 | # Pin Python version
16 | uv python pin 3.11
17 |
18 | # Create virtual environment
19 | uv venv
20 |
21 | # Activate virtual environment
22 | source .venv/bin/activate
23 |
24 | # Install MCP
25 | uv add "mcp[cli]"
26 | ```
27 |
28 | 2. Install MATLAB Engine
29 | The MATLAB Engine will be installed automatically when the server first runs, using the MATLAB installation specified in the `MATLAB_PATH` environment variable.
30 |
31 | ## Directory Structure
32 |
33 | - `matlab_server.py`: The main MCP server implementation
34 | - `matlab_scripts/`: Directory where all MATLAB scripts and functions are saved (created automatically)
35 | - `pyproject.toml`: Python project configuration
36 | - `.python-version`: Specifies Python version for uv
37 |
38 | ## Claude Desktop Integration
39 |
40 | 1. Open your Claude Desktop configuration:
41 | ```bash
42 | # On macOS
43 | code ~/Library/Application\ Support/Claude/claude_desktop_config.json
44 | ```
45 |
46 | 2. Add the MATLAB server configuration:
47 | ```json
48 | {
49 | "mcpServers": {
50 | "matlab": {
51 | "command": "uv",
52 | "args": [
53 | "--directory",
54 | "/absolute/path/to/matlab-mcp",
55 | "run",
56 | "matlab_server.py"
57 | ],
58 | "env": {
59 | "MATLAB_PATH": "/Applications/MATLAB_R2024a.app"
60 | }
61 | }
62 | }
63 | }
64 | ```
65 |
66 | Make sure to:
67 | - Replace `/absolute/path/to/matlab-mcp` with the actual path to your project directory
68 | - Verify the `MATLAB_PATH` points to your MATLAB installation
69 | - Use absolute paths (not relative)
70 |
71 | ## Features
72 |
73 | The server provides several tools:
74 |
75 | 1. `create_matlab_script`: Create a new MATLAB script file
76 | - Scripts are saved in the `matlab_scripts` directory
77 | - File names must be valid MATLAB identifiers
78 |
79 | 2. `create_matlab_function`: Create a new MATLAB function file
80 | - Functions are saved in the `matlab_scripts` directory
81 | - Must include valid function definition
82 |
83 | 3. `execute_matlab_script`: Run a MATLAB script and get results
84 | - Returns output text, generated figures, and workspace variables
85 | - Can pass arguments to scripts
86 |
87 | 4. `call_matlab_function`: Call a MATLAB function with arguments
88 | - Returns function output and any generated figures
89 |
90 | ## Testing
91 |
92 | You can test the server using the MCP Inspector:
93 | ```bash
94 | # Make sure you're in your virtual environment
95 | source .venv/bin/activate
96 |
97 | # Run the inspector
98 | MATLAB_PATH=/Applications/MATLAB_R2024a.app mcp dev matlab_server.py
99 | ```
100 |
101 | Example test script:
102 | ```matlab
103 | t = 0:0.01:2*pi;
104 | y = sin(t);
105 | plot(t, y);
106 | title('Test Plot');
107 | xlabel('Time');
108 | ylabel('Amplitude');
109 | ```
110 |
111 | ## Script Storage
112 |
113 | - All MATLAB scripts and functions are saved in the `matlab_scripts` directory
114 | - This directory is created automatically when the server starts
115 | - Files are named `<script_name>.m` or `<function_name>.m`
116 | - The directory is in the same location as `matlab_server.py`
117 |
118 | ## Environment Variables
119 |
120 | - `MATLAB_PATH`: Path to your MATLAB installation
121 | - Default: `/Applications/MATLAB_R2024a.app`
122 | - Set in Claude Desktop config or when running directly
123 |
124 | ## Troubleshooting
125 |
126 | 1. **MATLAB Engine Installation Fails**
127 | - Verify MATLAB_PATH is correct
128 | - Try installing engine manually:
129 | ```bash
130 | cd $MATLAB_PATH/extern/engines/python
131 | python setup.py install
132 | ```
133 |
134 | 2. **Python Version Issues**
135 | - Make sure you're using Python 3.11
136 | - Check with: `python --version`
137 | - Use `uv python pin 3.11` if needed
138 |
139 | 3. **Script Execution Errors**
140 | - Check the `matlab_scripts` directory exists
141 | - Verify script syntax is valid
142 | - Look for error messages in MATLAB output
143 |
144 | ## Updates and Maintenance
145 |
146 | - Keep your MATLAB installation updated
147 | - Update Python packages as needed: `uv pip install --upgrade mcp[cli]`
148 | - Check MATLAB engine compatibility when updating Python
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [project]
2 | name = "matlab-mcp"
3 | version = "0.1.0"
4 | description = "MATLAB MCP Server"
5 | readme = "README.md"
6 | requires-python = ">=3.11,<3.12"
7 | dependencies = [
8 | "mcp[cli]"
9 | ]
```
--------------------------------------------------------------------------------
/claude_desktop_config_example.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "mcpServers": {
3 | "matlab": {
4 | "command": "uv",
5 | "args": [
6 | "--directory",
7 | "/Users/tsuchijo/Documents/matlab-mcp",
8 | "run",
9 | "matlab_server.py"
10 | ],
11 | "env": {
12 | "MATLAB_PATH": "/Applications/MATLAB_R2024a.app"
13 | }
14 | }
15 | }
16 | }
17 |
```
--------------------------------------------------------------------------------
/matlab_server.py:
--------------------------------------------------------------------------------
```python
1 | import os
2 | from pathlib import Path
3 | import base64
4 | import subprocess
5 | import sys
6 | from typing import Optional, Dict, Any
7 | from mcp.server.fastmcp import FastMCP, Image, Context
8 | import io
9 | from contextlib import redirect_stdout
10 |
11 | # Get MATLAB path from environment variable with default fallback
12 | MATLAB_PATH = os.getenv('MATLAB_PATH', '/Applications/MATLAB_R2024a.app')
13 |
14 | # Initialize FastMCP server with dependencies
15 | mcp = FastMCP(
16 | "MATLAB",
17 | dependencies=[
18 | "mcp[cli]"
19 | ]
20 | )
21 |
22 | def ensure_matlab_engine():
23 | """Ensure MATLAB engine is installed for the current Python environment."""
24 | try:
25 | import matlab.engine
26 | return True
27 | except ImportError:
28 | if not os.path.exists(MATLAB_PATH):
29 | raise RuntimeError(
30 | f"MATLAB installation not found at {MATLAB_PATH}. "
31 | "Please set MATLAB_PATH environment variable to your MATLAB installation directory."
32 | )
33 |
34 | # Try to install MATLAB engine
35 | engine_setup = Path(MATLAB_PATH) / "extern/engines/python/setup.py"
36 | if not engine_setup.exists():
37 | raise RuntimeError(
38 | f"MATLAB Python engine setup not found at {engine_setup}. "
39 | "Please verify your MATLAB installation."
40 | )
41 |
42 | print(f"Installing MATLAB engine from {engine_setup}...", file=sys.stderr)
43 | try:
44 | subprocess.run(
45 | [sys.executable, str(engine_setup), "install"],
46 | check=True,
47 | capture_output=True,
48 | text=True
49 | )
50 | print("MATLAB engine installed successfully.", file=sys.stderr)
51 | import matlab.engine
52 | return True
53 | except subprocess.CalledProcessError as e:
54 | raise RuntimeError(
55 | f"Failed to install MATLAB engine: {e.stderr}\n"
56 | "Please try installing manually or check your MATLAB installation."
57 | )
58 |
59 | # Try to initialize MATLAB engine
60 | ensure_matlab_engine()
61 | import matlab.engine
62 | eng = matlab.engine.start_matlab()
63 |
64 | # Create a directory for MATLAB scripts if it doesn't exist
65 | MATLAB_DIR = Path("matlab_scripts")
66 | MATLAB_DIR.mkdir(exist_ok=True)
67 |
68 | @mcp.tool()
69 | def create_matlab_script(script_name: str, code: str) -> str:
70 | """Create a new MATLAB script file.
71 |
72 | Args:
73 | script_name: Name of the script (without .m extension)
74 | code: MATLAB code to save
75 |
76 | Returns:
77 | Path to the created script
78 | """
79 | if not script_name.isidentifier():
80 | raise ValueError("Script name must be a valid MATLAB identifier")
81 |
82 | script_path = MATLAB_DIR / f"{script_name}.m"
83 | with open(script_path, 'w') as f:
84 | f.write(code)
85 |
86 | return str(script_path)
87 |
88 | @mcp.tool()
89 | def create_matlab_function(function_name: str, code: str) -> str:
90 | """Create a new MATLAB function file.
91 |
92 | Args:
93 | function_name: Name of the function (without .m extension)
94 | code: MATLAB function code including function definition
95 |
96 | Returns:
97 | Path to the created function file
98 | """
99 | if not function_name.isidentifier():
100 | raise ValueError("Function name must be a valid MATLAB identifier")
101 |
102 | # Verify code starts with function definition
103 | if not code.strip().startswith('function'):
104 | raise ValueError("Code must start with function definition")
105 |
106 | function_path = MATLAB_DIR / f"{function_name}.m"
107 | with open(function_path, 'w') as f:
108 | f.write(code)
109 |
110 | return str(function_path)
111 |
112 | @mcp.tool()
113 | def execute_matlab_script(script_name: str, args: Optional[Dict[str, Any]] = None) -> dict:
114 | """Execute a MATLAB script and return results."""
115 | script_path = MATLAB_DIR / f"{script_name}.m"
116 | if not script_path.exists():
117 | raise FileNotFoundError(f"Script {script_name}.m not found")
118 |
119 | # Add script directory to MATLAB path
120 | eng.addpath(str(MATLAB_DIR))
121 |
122 | # Clear previous figures
123 | eng.close('all', nargout=0)
124 |
125 | # Create a temporary file for MATLAB output
126 | temp_output_file = MATLAB_DIR / f"temp_output_{script_name}.txt"
127 |
128 | # Execute the script
129 | result = {}
130 | try:
131 | if args:
132 | # Convert Python types to MATLAB types
133 | matlab_args = {k: matlab.double([v]) if isinstance(v, (int, float)) else v
134 | for k, v in args.items()}
135 | eng.workspace['args'] = matlab_args
136 |
137 | # Set up diary to capture output
138 | eng.eval(f"diary('{temp_output_file}')", nargout=0)
139 | eng.eval(script_name, nargout=0)
140 | eng.eval("diary off", nargout=0)
141 |
142 | # Read captured output
143 | if temp_output_file.exists():
144 | with open(temp_output_file, 'r') as f:
145 | printed_output = f.read().strip()
146 | # Clean up temp file
147 | os.remove(temp_output_file)
148 | else:
149 | printed_output = "No output captured"
150 |
151 | result['printed_output'] = printed_output
152 |
153 | # Rest of your code for figures and workspace variables...
154 |
155 | # Capture figures if any were generated
156 | figures = []
157 | fig_handles = eng.eval('get(groot, "Children")', nargout=1)
158 | if fig_handles:
159 | for i, fig in enumerate(fig_handles):
160 | # Save figure to temporary file
161 | temp_file = f"temp_fig_{i}.png"
162 | eng.eval(f"saveas(figure({i+1}), '{temp_file}')", nargout=0)
163 |
164 | # Read the file and convert to base64
165 | with open(temp_file, 'rb') as f:
166 | img_data = f.read()
167 | figures.append(Image(data=img_data, format='png'))
168 |
169 | # Clean up temp file
170 | os.remove(temp_file)
171 |
172 | result['figures'] = figures
173 |
174 | # Get workspace variables
175 | var_names = eng.eval('who', nargout=1)
176 | for var in var_names:
177 | if var != 'args': # Skip the args we passed in
178 | val = eng.workspace[var]
179 | # Clean variable name for JSON compatibility
180 | clean_var_name = var.strip().replace(' ', '_')
181 |
182 | val_str = str(val)
183 | # Truncate long values to prevent excessive output
184 | max_length = 1000 # Maximum length for variable values
185 | if len(val_str) > max_length:
186 | val_str = val_str[:max_length] + "... [truncated]"
187 |
188 | val = val_str # Replace the original value with the string representation
189 | result[clean_var_name] = val
190 |
191 | except Exception as e:
192 | raise RuntimeError(f"MATLAB execution error: {str(e)}")
193 |
194 | return result
195 |
196 | @mcp.tool()
197 | def call_matlab_function(function_name: str, args: Any) -> dict:
198 | """Call a MATLAB function with arguments."""
199 | function_path = MATLAB_DIR / f"{function_name}.m"
200 | if not function_path.exists():
201 | raise FileNotFoundError(f"Function {function_name}.m not found")
202 |
203 | # Add function directory to MATLAB path
204 | eng.addpath(str(MATLAB_DIR))
205 |
206 | # Clear previous figures
207 | eng.close('all', nargout=0)
208 |
209 | # Create a temporary file for MATLAB output
210 | temp_output_file = MATLAB_DIR / f"temp_output_{function_name}.txt"
211 |
212 | # Convert Python arguments to MATLAB types
213 | matlab_args = []
214 | for arg in args:
215 | if isinstance(arg, (int, float)):
216 | matlab_args.append(matlab.double([arg]))
217 | elif isinstance(arg, list):
218 | matlab_args.append(matlab.double(arg))
219 | else:
220 | matlab_args.append(arg)
221 |
222 | result = {}
223 | try:
224 | # Set up diary to capture output
225 | eng.eval(f"diary('{temp_output_file}')", nargout=0)
226 |
227 | # Call the function
228 | output = getattr(eng, function_name)(*matlab_args)
229 |
230 | # Turn off diary
231 | eng.eval("diary off", nargout=0)
232 |
233 | # Read captured output
234 | if temp_output_file.exists():
235 | with open(temp_output_file, 'r') as f:
236 | printed_output = f.read().strip()
237 | # Clean up temp file
238 | os.remove(temp_output_file)
239 | else:
240 | printed_output = "No output captured"
241 |
242 | result['output'] = str(output)
243 | result['printed_output'] = printed_output
244 |
245 | # Capture figures - rest of your code remains the same
246 | figures = []
247 | fig_handles = eng.eval('get(groot, "Children")', nargout=1)
248 | if fig_handles:
249 | for i, fig in enumerate(fig_handles):
250 | # Save figure to temporary file
251 | temp_file = f"temp_fig_{i}.png"
252 | eng.eval(f"saveas(figure({i+1}), '{temp_file}')", nargout=0)
253 |
254 | # Read the file and convert to base64
255 | with open(temp_file, 'rb') as f:
256 | img_data = f.read()
257 | figures.append(Image(data=img_data, format='png'))
258 |
259 | # Clean up temp file
260 | os.remove(temp_file)
261 |
262 | result['figures'] = figures
263 |
264 | except Exception as e:
265 | raise RuntimeError(f"MATLAB execution error: {str(e)}")
266 |
267 | return result
268 |
269 | @mcp.resource("matlab://scripts/{script_name}")
270 | def get_script_content(script_name: str) -> str:
271 | """Get the content of a MATLAB script.
272 |
273 | Args:
274 | script_name: Name of the script (without .m extension)
275 |
276 | Returns:
277 | Content of the MATLAB script
278 | """
279 | script_path = MATLAB_DIR / f"{script_name}.m"
280 | if not script_path.exists():
281 | raise FileNotFoundError(f"Script {script_name}.m not found")
282 |
283 | with open(script_path) as f:
284 | return f.read()
285 |
286 | if __name__ == "__main__":
287 | mcp.run(transport='stdio')
288 |
```