Content
# lumerical-mcp
[](https://github.com/davydov-nikita/lumerical-mcp/actions/workflows/tests.yml)
An [MCP](https://modelcontextprotocol.io) server that lets an AI assistant
drive ANSYS Lumerical FDTD: build scenes, run simulations, and read results
back — with a correctness layer that catches simulation settings which would
silently produce wrong physics.
Built on the official Python `lumapi`. 87 async tools cover geometry,
sources, monitors, materials (including anisotropic and magneto-optic
tensors), mesh control, boundaries, ports/S-parameters, parametric sweeps,
field visualisation, and ready-made figure-of-merit extractors (Q-factor,
Purcell factor, cross-sections, grating orders, far-field, bandstructure,
χ² harmonics). A [benchmark suite](./benchmarks) rebuilds Lumerical
Application Gallery examples through the tools and scores them against the
gallery's own references.
## What this is for
You ask the assistant something like *"set up Mie scattering from a 100 nm
sphere across 300–1000 nm and find the resonances"*, and it builds the
project through the tools, validates the setup, runs it, and reports the
numbers — or you debug an existing `.fsp` interactively without switching to
the GUI.
This is not a remote service. It runs locally, talks to your local Lumerical
install over Lumerical's own Python API, and uses your license seat.
## Why a correctness layer
FDTD fails quietly: a mesh too coarse for the in-material wavelength, a
monitor inside the near field, a symmetry boundary that contradicts the
source polarisation, or a tilted beam launched in the wrong medium all
produce plausible-looking, wrong numbers. The server treats this as a
first-class problem:
| Stage | Tool | What it does |
|---|---|---|
| before a run | `validate_setup` | mesh vs in-material wavelength, metal skin depth, PML clearance, monitor band vs source band, symmetry vs polarisation, tilted-source medium, nonlinear-needs-time-domain-source, overlaps — each finding has a severity and a suggested fix |
| after a run | `assess_results` | autoshutoff convergence, monitor data, field decay, NaN, unphysical transmission, energy balance R+T+A ≈ 1 |
| on any error | `diagnose` | maps the raw Lumerical error to a cause and fix (attached automatically to failing tool calls) |
| before trusting a number | `convergence_test` / `certify` | refine mesh / time / mesh-override, re-run, compare — returns a trust verdict for the figure of merit |
Findings are advisory — the assistant decides — but they are the difference
between "the run finished" and "the number is right".
## Requirements
- Windows 10/11, Linux, or macOS. Standard install paths for Lumerical
v241/v242/v251 are probed at import; set `LUMAPI_PATH` to override.
- ANSYS Lumerical FDTD installed (developed against 2024 R1 / v241; other
versions are expected to work through `lumapi`'s compatibility).
- Python 3.10+.
- An MCP-capable client (tested with [Claude Code](https://claude.com/claude-code)).
## Installation
From the [latest release](https://github.com/davydov-nikita/lumerical-mcp/releases/latest)
(no cloning needed):
```bash
pip install https://github.com/davydov-nikita/lumerical-mcp/releases/download/v0.4.0/lumerical_mcp-0.4.0-py3-none-any.whl
```
or straight from the repository:
```bash
pip install git+https://github.com/davydov-nikita/lumerical-mcp
```
For development, clone and install editable:
```bash
git clone https://github.com/davydov-nikita/lumerical-mcp
cd lumerical-mcp
pip install -e .
```
Any of these installs the `lumerical-mcp` console script and the
dependencies (`mcp[cli]`, `numpy`, `matplotlib`).
## Configuration
Register the server with your MCP client. For Claude Code, create
`.mcp.json` in the project you work from:
```json
{
"mcpServers": {
"lumerical-fdtd": {
"type": "stdio",
"command": "python",
"args": ["-m", "lumerical_mcp.server"],
"env": {
"LUMAPI_PATH": "C:\\Program Files\\Lumerical\\v241\\api\\python"
}
}
}
}
```
(`env` is only needed when Lumerical is not in a standard location.) The
tools appear as `mcp__lumerical-fdtd__*`. Any other MCP client works the
same way with its own registration mechanism.
## Tool overview
Units are Lumerical's internal SI: metres, seconds, hertz.
| Area | Tools |
|---|---|
| Session / project | `open_fdtd`, `close_fdtd`, `new_project`, `load_project`, `save_project` |
| Region & mesh | `add_fdtd_region`, `set_fdtd_boundaries`, `add_mesh_override`, `set_global_monitor` |
| Structures | `add_rectangle` (centre+span or min/max per axis), `add_sphere`, `add_circle`, `add_ring`, `add_polygon`, `add_pyramid`, `add_structure_group` |
| Sources | `add_dipole_source`, `add_plane_wave_source` (oblique, Bloch/BFAST), `add_gaussian_source`, `add_mode_source`, `add_tfsf_source` |
| Monitors | `add_power_monitor`, `add_time_monitor`, `add_index_monitor`, `add_movie_monitor`, `add_mode_expansion_monitor` |
| Materials | `add_dielectric_material`, `add_lorentz_material`, `add_sampled_material`, `add_chi2_material`, `list_materials`, `delete_material` |
| Anisotropy / magneto-optics | `add_anisotropic_material`, `add_tensor_material` (arbitrary 3×3 hermitian ε), `add_magneto_optic_material`, `add_grid_attribute` / `attach_grid_attribute` |
| Ports / S-parameters | `add_port`, `set_source_port`, `set_ports_frequency_points`, `get_s_parameters` |
| Running | `run_simulation`, `run_sweep` (parametric axes + per-point collection), `run_project_async` / `cancel_run`, `get_run_progress`, `switch_to_layout` |
| Results | `get_result`, `list_results`, `render_field` (field → PNG heatmap), `get_simulation_log`, `script_get`, `eval_script` |
| Introspection | `scene_summary`, `describe_object`, `list_objects`, `set_property` / `get_property` |
| Correctness | `validate_setup`, `assess_results`, `diagnose`, `convergence_test`, `certify`, `auto_run`, `run_with_recovery`, `energy_balance`, `propose_settings`, `auto_configure` |
| Figures of merit | `compute_q_factor`, `compute_purcell_factor`, `add_cross_section_analysis` / `compute_cross_sections`, `add_grating_analysis`, `add_farfield_analysis` / `compute_farfield`, `set_bloch_k` + `find_resonances` |
| Guidance | `recipes(topic)` — ready-made tool sequences for common workflows |
## Benchmarks
[`benchmarks/`](./benchmarks) measures the server's generality: each task
takes a Lumerical Application Gallery example, rebuilds it through the tools
from a written spec, runs it, and scores named quantities against the
gallery's own reference scene. Verdicts are computed by the scoring script,
never judged by the solver. Tasks that expose a missing capability become
fixes or new tools; tasks whose oracle cannot be reproduced from a text spec
(reference-embedded material fits, construction-script geometry) are
documented as such rather than force-passed. See the
[status table](./benchmarks/README.md).
## Examples
Worked scripts live in [`examples/`](./examples): a magneto-optic grating
built entirely through the tool surface, grating R/T/A sweeps over angle and
wavelength, end-to-end Mie scattering with cross-section analysis, a
convergence-certified dielectric Purcell factor, and post-processing /
plotting helpers. Outputs go to a `results/` directory next to the repo
(override with `LUMERICAL_MCP_RESULTS`).
## Transport notes (Windows)
Three startup measures make the stdio transport reliable, particularly on
Windows:
- File descriptors 0/1 are duplicated to private handles and the originals
are redirected to `NUL`. The Lumerical engine writes diagnostics directly
to fd 1 at the C level; if it inherits the MCP pipe, the JSON-RPC stream
is corrupted and the engine handshake can deadlock.
- `sys.stdout` is re-wrapped with `write_through=True` to avoid a buffering
issue that can hold MCP responses on Windows pipes.
- Every tool is async and dispatches `lumapi` calls to a worker thread, so
the event loop stays responsive during long simulations.
## Testing
```bash
python -m unittest discover -s tests
```
The unit tests mock the `lumapi` session and run without a Lumerical
install or license, including an MCP tool-surface check that spawns the
server over stdio. On a machine with Lumerical, `python check_physics.py`
additionally runs a small live physics panel (vacuum transmission,
validation rules firing on a deliberately bad scene, a known resonator Q)
as a pre-release check.
## Limitations
- One license seat, one engine handle per process; simulations run
serially.
- `eval_script` loses return values from script-level calls such as
`addmaterial(...)`; prefer the typed wrappers, or `script_get` for
expression values.
- Known workarounds for remaining sharp edges (grating-analysis result
access, fibre-beam injection for couplers) are documented in
[CLAUDE.md](./CLAUDE.md), which also serves as the field guide an
MCP-driving assistant loads automatically.
## License
[MIT](./LICENSE).