MCP NMAP Server
import os
from pathlib import Path
import base64
import subprocess
import sys
from typing import Optional, Dict, Any
from mcp.server.fastmcp import FastMCP, Image, Context
# Get MATLAB path from environment variable with default fallback
MATLAB_PATH = os.getenv('MATLAB_PATH', '/Applications/MATLAB_R2024a.app')
# Initialize FastMCP server with dependencies
mcp = FastMCP(
"MATLAB",
dependencies=[
"mcp[cli]"
]
)
def ensure_matlab_engine():
"""Ensure MATLAB engine is installed for the current Python environment."""
try:
import matlab.engine
return True
except ImportError:
if not os.path.exists(MATLAB_PATH):
raise RuntimeError(
f"MATLAB installation not found at {MATLAB_PATH}. "
"Please set MATLAB_PATH environment variable to your MATLAB installation directory."
)
# Try to install MATLAB engine
engine_setup = Path(MATLAB_PATH) / "extern/engines/python/setup.py"
if not engine_setup.exists():
raise RuntimeError(
f"MATLAB Python engine setup not found at {engine_setup}. "
"Please verify your MATLAB installation."
)
print(f"Installing MATLAB engine from {engine_setup}...", file=sys.stderr)
try:
subprocess.run(
[sys.executable, str(engine_setup), "install"],
check=True,
capture_output=True,
text=True
)
print("MATLAB engine installed successfully.", file=sys.stderr)
import matlab.engine
return True
except subprocess.CalledProcessError as e:
raise RuntimeError(
f"Failed to install MATLAB engine: {e.stderr}\n"
"Please try installing manually or check your MATLAB installation."
)
# Try to initialize MATLAB engine
ensure_matlab_engine()
import matlab.engine
eng = matlab.engine.start_matlab()
# Create a directory for MATLAB scripts if it doesn't exist
MATLAB_DIR = Path("matlab_scripts")
MATLAB_DIR.mkdir(exist_ok=True)
@mcp.tool()
def create_matlab_script(script_name: str, code: str) -> str:
"""Create a new MATLAB script file.
Args:
script_name: Name of the script (without .m extension)
code: MATLAB code to save
Returns:
Path to the created script
"""
if not script_name.isidentifier():
raise ValueError("Script name must be a valid MATLAB identifier")
script_path = MATLAB_DIR / f"{script_name}.m"
with open(script_path, 'w') as f:
f.write(code)
return str(script_path)
@mcp.tool()
def create_matlab_function(function_name: str, code: str) -> str:
"""Create a new MATLAB function file.
Args:
function_name: Name of the function (without .m extension)
code: MATLAB function code including function definition
Returns:
Path to the created function file
"""
if not function_name.isidentifier():
raise ValueError("Function name must be a valid MATLAB identifier")
# Verify code starts with function definition
if not code.strip().startswith('function'):
raise ValueError("Code must start with function definition")
function_path = MATLAB_DIR / f"{function_name}.m"
with open(function_path, 'w') as f:
f.write(code)
return str(function_path)
@mcp.tool()
def execute_matlab_script(script_name: str, args: Optional[Dict[str, Any]] = None) -> dict:
"""Execute a MATLAB script and return results.
Args:
script_name: Name of the script (without .m extension)
args: Optional dictionary of arguments to pass to the script
Returns:
Dictionary containing:
- output: Text output from MATLAB
- figures: List of figures generated (if any)
- workspace: Variables in MATLAB workspace after execution
"""
script_path = MATLAB_DIR / f"{script_name}.m"
if not script_path.exists():
raise FileNotFoundError(f"Script {script_name}.m not found")
# Add script directory to MATLAB path
eng.addpath(str(MATLAB_DIR))
# Clear previous figures
eng.close('all', nargout=0)
# Execute the script
result = {}
try:
if args:
# Convert Python types to MATLAB types
matlab_args = {k: matlab.double([v]) if isinstance(v, (int, float)) else v
for k, v in args.items()}
eng.workspace['args'] = matlab_args
output = eng.eval(script_name, nargout=0)
result['output'] = str(output) if output else ""
# Capture figures if any were generated
figures = []
fig_handles = eng.eval('get(groot, "Children")', nargout=1)
if fig_handles:
for i, fig in enumerate(fig_handles):
# Save figure to temporary file
temp_file = f"temp_fig_{i}.png"
eng.eval(f"saveas(figure({i+1}), '{temp_file}')", nargout=0)
# Read the file and convert to base64
with open(temp_file, 'rb') as f:
img_data = f.read()
figures.append(Image(data=img_data, format='png'))
# Clean up temp file
os.remove(temp_file)
result['figures'] = figures
# Get workspace variables
workspace = {}
var_names = eng.eval('who', nargout=1)
for var in var_names:
if var != 'args': # Skip the args we passed in
val = eng.workspace[var]
workspace[var] = str(val)
result['workspace'] = workspace
except Exception as e:
raise RuntimeError(f"MATLAB execution error: {str(e)}")
return result
@mcp.tool()
def call_matlab_function(function_name: str, *args: Any) -> dict:
"""Call a MATLAB function with arguments.
Args:
function_name: Name of the function (without .m extension)
*args: Arguments to pass to the function
Returns:
Dictionary containing:
- output: Function return value(s)
- figures: List of figures generated (if any)
"""
function_path = MATLAB_DIR / f"{function_name}.m"
if not function_path.exists():
raise FileNotFoundError(f"Function {function_name}.m not found")
# Add function directory to MATLAB path
eng.addpath(str(MATLAB_DIR))
# Clear previous figures
eng.close('all', nargout=0)
# Convert Python arguments to MATLAB types
matlab_args = []
for arg in args:
if isinstance(arg, (int, float)):
matlab_args.append(matlab.double([arg]))
elif isinstance(arg, list):
matlab_args.append(matlab.double(arg))
else:
matlab_args.append(arg)
result = {}
try:
# Call the function
output = getattr(eng, function_name)(*matlab_args)
if isinstance(output, matlab.double):
result['output'] = output.tolist()
else:
result['output'] = str(output)
# Capture figures if any were generated
figures = []
fig_handles = eng.eval('get(groot, "Children")', nargout=1)
if fig_handles:
for i, fig in enumerate(fig_handles):
# Save figure to temporary file
temp_file = f"temp_fig_{i}.png"
eng.eval(f"saveas(figure({i+1}), '{temp_file}')", nargout=0)
# Read the file and convert to base64
with open(temp_file, 'rb') as f:
img_data = f.read()
figures.append(Image(data=img_data, format='png'))
# Clean up temp file
os.remove(temp_file)
result['figures'] = figures
except Exception as e:
raise RuntimeError(f"MATLAB execution error: {str(e)}")
return result
@mcp.resource("matlab://scripts/{script_name}")
def get_script_content(script_name: str) -> str:
"""Get the content of a MATLAB script.
Args:
script_name: Name of the script (without .m extension)
Returns:
Content of the MATLAB script
"""
script_path = MATLAB_DIR / f"{script_name}.m"
if not script_path.exists():
raise FileNotFoundError(f"Script {script_name}.m not found")
with open(script_path) as f:
return f.read()
if __name__ == "__main__":
mcp.run(transport='stdio')