#
tokens: 4653/50000 6/6 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | 
```