Mathcad 15 MCP Server
by Nighty3098
README.md
# Mathcad 15 MCP Server
An MCP server (stdio transport) for driving **Mathcad 15** from an LLM:
set input variables, recalculate, read results, inspect regions, diagnose
errors, save/export worksheets.
Windows + Python 3.10+ (developed and verified on 64-bit Python 3.14.3).
## Quick start (Windows)
```powershell
cd C:\Users\Nighty\Desktop\MathCadMCP
.\.venv\Scripts\pip.exe install -r requirements.txt
.\.venv\Scripts\pip.exe install -e .
# Check without the MCP protocol (COM thread + live Mathcad):
.\.venv\Scripts\python.exe scripts\test_manager.py
# Unit + protocol:
.\.venv\Scripts\python.exe -m pytest tests\test_converters.py tests\test_schemas.py tests\test_protocol_smoke.py -q
# Live (requires a running Mathcad 15):
.\.venv\Scripts\python.exe -m pytest tests\test_live.py -q -m live
```
Manual server start: `scripts\smoke.ps1` (it will hang waiting on stdio — that is expected).
## Client configuration
### opencode (`opencode.json`, already in the repo root)
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"mathcad15": {
"type": "local",
"command": ["C:\\Users\\Nighty\\Desktop\\MathCadMCP\\.venv\\Scripts\\python.exe", "-m", "mathcad_mcp.server"],
"cwd": "C:\\Users\\Nighty\\Desktop\\MathCadMCP",
"environment": {
"PYTHONPATH": "C:\\Users\\Nighty\\Desktop\\MathCadMCP\\src",
"MATHCAD_ATTACH_ONLY": "1",
"MATHCAD_MCP_LOG": "INFO"
},
"timeout": 310000,
"enabled": true
}
}
}
```
Restart opencode after editing the config (it is loaded once at startup).
`timeout: 310000` matters: `recalculate` may run for up to 300 s.
### Claude Desktop / other MCP clients (`mcp.json`)
```json
{
"mcpServers": {
"mathcad15": {
"command": "C:\\Users\\Nighty\\Desktop\\MathCadMCP\\.venv\\Scripts\\python.exe",
"args": ["-m", "mathcad_mcp.server"],
"cwd": "C:\\Users\\Nighty\\Desktop\\MathCadMCP",
"env": {
"PYTHONPATH": "C:\\Users\\Nighty\\Desktop\\MathCadMCP\\src",
"MATHCAD_ATTACH_ONLY": "1",
"MATHCAD_MCP_LOG": "INFO"
}
}
}
}
```
## Environment variables
| Variable | Default | Description |
|---|---|---|
| `MATHCAD_MCP_LOG` | `INFO` | `DEBUG\|INFO\|WARNING\|ERROR`; logs go to stderr only |
| `MATHCAD_VISIBLE` | (untouched) | `1/0…`; empty = do not change `Visible` |
| `MATHCAD_ATTACH_ONLY` | `1` | `1` = attach only, otherwise try `DispatchEx` |
| `MATHCAD_TIMEOUT_S` | `30` | connect timeout |
| `MATHCAD_DEFAULT_DIR` | — | base for relative paths |
| `MATHCAD_ALLOWED_DIRS` | — | `;`-separated path jail; empty = no restriction |
| `MATHCAD_MAX_MATRIX_ELEMENTS` | `10000` | cap + `truncated` flag |
| `MATHCAD_MAX_REGIONS` | `1000` | scan cap |
| `MATHCAD_AUTO_CONNECT` | `1` | connect at server startup |
| `MATHCAD_AUTOQUIT_ON_SHUTDOWN` | `0` | (reserved; `Quit` still requires `force`) |
| `MATHCAD_ALLOW_DESTRUCTIVE_NO_CONFIRM` | `0` | **dangerous**: bypasses `confirm_discard` in tests |
| `MATHCAD_READ_ONLY` | `0` | `1` disables all mutating tools (set/recalc/save/close/quit); reads still work |
| `MATHCAD_BRIDGE_PYTHON` | — | path to 32-bit Python for complex input (see below) |
| `MATHCAD_BRIDGE_TIMEOUT_S` | `60` | bridge call timeout |
CLI flags (`--attach-only/--no-attach-only`, `--visible/--no-visible`,
`--timeout-s`, `--log-level`, `--auto-connect/--no-auto-connect`)
override the environment. Booleans accept `1/0, true/false, yes/no, on/off`.
## Example session
```
status() -> connected, Mathcad 14.1.5.594
open_worksheet(path="C:/docs/calc.xmcd") -> {name, fullname, ...}
set_value(name="F", value=10) -> {name: "F"}
recalculate() -> {recalculated: ...}
get_value(name="Smax") -> {type: "real", real: 25.0, as_string: "25"}
batch_compute(inputs={"F": 15}, outputs=["Smax"]) -> {applied_inputs, outputs, recalculated}
```
See also `examples/demo_script.py` (cleans up after itself via `VT_NULL`).
## Listing variables and units
`list_variables()` parses math-region XML (`<define>` per Math10.xsd) and
returns defined names with kind (`variable`/`function`) and region index.
`get_value()` additionally reports `unit` (e.g. `"m*s^-1"`) rendered from the
defining region's UnitsXML — `null` when the region has no interface or units
(old/protected sheets).
## Complex input via the 32-bit bridge (optional)
64-bit Python cannot construct `Mathcad.NumericValue`, so
`set_value(name="z", value={"real": 3, "imag": 4})` needs a helper:
1. Install a 32-bit Python (e.g. 3.12 32-bit) and `pywin32` into it.
2. Set `MATHCAD_BRIDGE_PYTHON=C:\Python312-32\python.exe`.
3. Complex scalars and complex matrices are then written through
`scripts/com_bridge_main.py` (own process, same live Mathcad via
`GetActiveObject`); results read back normally with `Real`/`Imag`.
Without the bridge, complex input returns `UNSUPPORTED_OPERATION`.
## Demo sheet for full live checks (create manually, 2 minutes)
The Automation API cannot create math regions, so this file is created once
by hand in Mathcad 15 and saved as `examples\beamdef_demo.xmcd`:
1. `F := 10`
2. `Smax := F * 2.5`
3. `M := [[1, 2], [3, 4]]` (2×2 matrix via the matrix operator)
4. Save as `examples\beamdef_demo.xmcd`.
Without it, live tests run in round-trip mode on a copy of the stock
`Handbook\datapack\basics.xmcd`; with it, they additionally verify the
computation `F=10 → Smax=25`.
## Known limitations (empirically confirmed)
1. **Complex INPUT from 64-bit Python needs the bridge** (`UNSUPPORTED_OPERATION`
without it): `Mathcad.NumericValue/MatrixValue/StringValue` are 32-bit
in-proc objects (`automation.dll` → `CLASS_E_CLASSNOTAVAILABLE`). With
`MATHCAD_BRIDGE_PYTHON` set, complex scalars/matrices work. Reading complex
values (`Real`/`Imag`) always works.
2. **Top/bottom binding**: `SetValue` binds at the top of the sheet,
`GetValue` reads the bottommost definition; redefinitions in the sheet
shadow the input.
3. **Units** cannot be passed via Automation (per the VB sample notes in
DevRef); `AsString` may carry units and locale formatting (`,` vs `.`) —
it is never parsed as a number.
4. **Editing formulas/regions is impossible** via the Automation API.
5. **Sheets with protected/scripted regions** (e.g. DiffTool fixtures) may
reject `GetValue` for `SetValue` bindings — use ordinary sheets.
6. **Variable listing** (`list_variables`) only sees regions exposing
`MathInterface` (fresh math regions); old/protected sheets yield `[]`.
7. **Collections and matrices are 0-based** (empirical + DevRef `GetElement`);
the outer API is 0-based everywhere.
8. **No UI**: format pickers (`mcPromptUser=1` is forbidden), interactive
handbooks, modal dialogs — the server never triggers them.
9. **MCP SDK**: uses the installed `mcp==2.2.0` (`MCPServer`,
`run_stdio_async`). The separate PyPI package `fastmcp` is NOT required
and NOT installed (different project, would conflict with `mcp` v2).
## Safety
- `save_option` only `0/2`; `1` (prompt) is rejected by validation.
- Closing an unsaved sheet without `confirm_discard=true` is refused; nothing is closed.
- `Quit()` on a foreign process only with `force=true`.
- Overwrite via `save_as`/export: existing file → `.bak-<timestamp>`,
then size > 0 is verified.
- Paths: absolute, `~` expanded, `MATHCAD_ALLOWED_DIRS` jail,
open restricted to `.xmcd/.mcd/.xmcdz/.xmct`; handbooks are refused by Mathcad itself.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues