lumerical-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: lumerical-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for Lumerical FDTD automation
5
+ Author-email: Saku1234 <lxh875410020@gmail.com>
6
+ Maintainer-email: Saku1234 <lxh875410020@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/saku123hh/lumerical-mcp
9
+ Project-URL: Repository, https://github.com/saku123hh/lumerical-mcp
10
+ Project-URL: Issues, https://github.com/saku123hh/lumerical-mcp/issues
11
+ Keywords: mcp,lumerical,fdtd,photonics,simulation
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Physics
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: mcp>=1.0.0
22
+ Requires-Dist: numpy
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: ruff; extra == "dev"
26
+
27
+ # Lumerical MCP Server
28
+
29
+ MCP (Model Context Protocol) server for controlling Lumerical FDTD simulations from LLMs.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ cd lumerical-mcp
35
+ pip install -e .
36
+ ```
37
+
38
+ ## Configuration
39
+
40
+ ### Required Environment Variable
41
+
42
+ ```bash
43
+ # Windows (PowerShell)
44
+ $env:LUMERICAL_ROOT = "D:\Program Files\ANSYS Inc\v251\Lumerical"
45
+
46
+ # Windows (cmd)
47
+ set LUMERICAL_ROOT=D:\Program Files\ANSYS Inc\v251\Lumerical
48
+
49
+ # Linux/macOS
50
+ export LUMERICAL_ROOT=/opt/lumerical/v251
51
+ ```
52
+
53
+ ### Optional Environment Variables
54
+
55
+ | Variable | Default | Description |
56
+ |----------|---------|-------------|
57
+ | `MCP_HOST` | `127.0.0.1` | Server bind address |
58
+ | `MCP_PORT` | `8000` | Server port |
59
+ | `MCP_TRANSPORT` | `streamable-http` | Transport protocol (`streamable-http` or `stdio`) |
60
+ | `MCP_WORK_DIR` | Current directory | Working directory for relative paths |
61
+ | `MCP_LOG_LEVEL` | `INFO` | Log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
62
+
63
+ ### Config File (Optional)
64
+
65
+ Create `~/.config/lumerical-mcp/config.json`:
66
+
67
+ ```json
68
+ {
69
+ "MCP_HOST": "127.0.0.1",
70
+ "MCP_PORT": 8000,
71
+ "MCP_TRANSPORT": "streamable-http",
72
+ "MCP_WORK_DIR": "C:/Users/name/projects/lumerical",
73
+ "MCP_LOG_LEVEL": "INFO"
74
+ }
75
+ ```
76
+
77
+ Environment variables take precedence over config file.
78
+
79
+ ## Running the Server
80
+
81
+ ```bash
82
+ lumerical-mcp
83
+ ```
84
+
85
+ Or with custom settings:
86
+
87
+ ```bash
88
+ MCP_PORT=9000 MCP_TRANSPORT=stdio lumerical-mcp
89
+ ```
90
+
91
+ ## Available Tools
92
+
93
+ | Tool | Description |
94
+ |------|-------------|
95
+ | `hello` | Connectivity check |
96
+ | `start_fdtd` / `end_fdtd` | Start/stop FDTD API session |
97
+ | `open_projects` / `fdtd_load_project_tool` | Load `.fsp` project |
98
+ | `save_project` / `fdtd_save_project_tool` | Save project |
99
+ | `set_parameter` | Set object property |
100
+ | `run_simulation` / `fdtd_run_simulation_tool` | Run simulation |
101
+ | `get_transmission` | Get transmission spectrum from monitor |
102
+ | `fdtd_eval_script_tool` | Execute Lumerical script |
103
+ | `fdtd_eval_script_get_tool` | Execute script and return variable |
104
+ | `fdtd_run_script_file_tool` | Run `.lsf`/`.lsfx` script file |
105
+ | `fdtd_get_variable_tool` / `fdtd_put_variable_tool` | Read/write script variables |
106
+ | `fdtd_launch_gui_tool` | Launch Lumerical GUI |
107
+ | `fdtd_command_docs_tool` | Search command documentation |
108
+
109
+ ## Client Usage
110
+
111
+ ```bash
112
+ # Using the test client
113
+ python client_test.py
114
+
115
+ # Or with custom server address
116
+ MCP_HOST=192.168.1.100 MCP_PORT=9000 python client_test.py
117
+ ```
118
+
119
+ ## LLM Client Configuration
120
+
121
+ ### Claude Desktop (`claude_desktop_config.json`)
122
+
123
+ ```json
124
+ {
125
+ "mcpServers": {
126
+ "lumerical": {
127
+ "command": "lumerical-mcp",
128
+ "env": {
129
+ "LUMERICAL_ROOT": "D:\\Program Files\\ANSYS Inc\\v251\\Lumerical"
130
+ }
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### VS Code / Continue / Other MCP Clients
137
+
138
+ ```json
139
+ {
140
+ "mcpServers": {
141
+ "lumerical": {
142
+ "command": "lumerical-mcp",
143
+ "args": [],
144
+ "env": {
145
+ "LUMERICAL_ROOT": "/opt/lumerical/v251"
146
+ }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ## Development
153
+
154
+ ```bash
155
+ pip install -e .[dev]
156
+ ruff check .
157
+ pytest
158
+ ```
159
+
160
+ ## License
161
+
162
+ MIT
@@ -0,0 +1,136 @@
1
+ # Lumerical MCP Server
2
+
3
+ MCP (Model Context Protocol) server for controlling Lumerical FDTD simulations from LLMs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ cd lumerical-mcp
9
+ pip install -e .
10
+ ```
11
+
12
+ ## Configuration
13
+
14
+ ### Required Environment Variable
15
+
16
+ ```bash
17
+ # Windows (PowerShell)
18
+ $env:LUMERICAL_ROOT = "D:\Program Files\ANSYS Inc\v251\Lumerical"
19
+
20
+ # Windows (cmd)
21
+ set LUMERICAL_ROOT=D:\Program Files\ANSYS Inc\v251\Lumerical
22
+
23
+ # Linux/macOS
24
+ export LUMERICAL_ROOT=/opt/lumerical/v251
25
+ ```
26
+
27
+ ### Optional Environment Variables
28
+
29
+ | Variable | Default | Description |
30
+ |----------|---------|-------------|
31
+ | `MCP_HOST` | `127.0.0.1` | Server bind address |
32
+ | `MCP_PORT` | `8000` | Server port |
33
+ | `MCP_TRANSPORT` | `streamable-http` | Transport protocol (`streamable-http` or `stdio`) |
34
+ | `MCP_WORK_DIR` | Current directory | Working directory for relative paths |
35
+ | `MCP_LOG_LEVEL` | `INFO` | Log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
36
+
37
+ ### Config File (Optional)
38
+
39
+ Create `~/.config/lumerical-mcp/config.json`:
40
+
41
+ ```json
42
+ {
43
+ "MCP_HOST": "127.0.0.1",
44
+ "MCP_PORT": 8000,
45
+ "MCP_TRANSPORT": "streamable-http",
46
+ "MCP_WORK_DIR": "C:/Users/name/projects/lumerical",
47
+ "MCP_LOG_LEVEL": "INFO"
48
+ }
49
+ ```
50
+
51
+ Environment variables take precedence over config file.
52
+
53
+ ## Running the Server
54
+
55
+ ```bash
56
+ lumerical-mcp
57
+ ```
58
+
59
+ Or with custom settings:
60
+
61
+ ```bash
62
+ MCP_PORT=9000 MCP_TRANSPORT=stdio lumerical-mcp
63
+ ```
64
+
65
+ ## Available Tools
66
+
67
+ | Tool | Description |
68
+ |------|-------------|
69
+ | `hello` | Connectivity check |
70
+ | `start_fdtd` / `end_fdtd` | Start/stop FDTD API session |
71
+ | `open_projects` / `fdtd_load_project_tool` | Load `.fsp` project |
72
+ | `save_project` / `fdtd_save_project_tool` | Save project |
73
+ | `set_parameter` | Set object property |
74
+ | `run_simulation` / `fdtd_run_simulation_tool` | Run simulation |
75
+ | `get_transmission` | Get transmission spectrum from monitor |
76
+ | `fdtd_eval_script_tool` | Execute Lumerical script |
77
+ | `fdtd_eval_script_get_tool` | Execute script and return variable |
78
+ | `fdtd_run_script_file_tool` | Run `.lsf`/`.lsfx` script file |
79
+ | `fdtd_get_variable_tool` / `fdtd_put_variable_tool` | Read/write script variables |
80
+ | `fdtd_launch_gui_tool` | Launch Lumerical GUI |
81
+ | `fdtd_command_docs_tool` | Search command documentation |
82
+
83
+ ## Client Usage
84
+
85
+ ```bash
86
+ # Using the test client
87
+ python client_test.py
88
+
89
+ # Or with custom server address
90
+ MCP_HOST=192.168.1.100 MCP_PORT=9000 python client_test.py
91
+ ```
92
+
93
+ ## LLM Client Configuration
94
+
95
+ ### Claude Desktop (`claude_desktop_config.json`)
96
+
97
+ ```json
98
+ {
99
+ "mcpServers": {
100
+ "lumerical": {
101
+ "command": "lumerical-mcp",
102
+ "env": {
103
+ "LUMERICAL_ROOT": "D:\\Program Files\\ANSYS Inc\\v251\\Lumerical"
104
+ }
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### VS Code / Continue / Other MCP Clients
111
+
112
+ ```json
113
+ {
114
+ "mcpServers": {
115
+ "lumerical": {
116
+ "command": "lumerical-mcp",
117
+ "args": [],
118
+ "env": {
119
+ "LUMERICAL_ROOT": "/opt/lumerical/v251"
120
+ }
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ ## Development
127
+
128
+ ```bash
129
+ pip install -e .[dev]
130
+ ruff check .
131
+ pytest
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,56 @@
1
+ import os
2
+ import json
3
+ from pathlib import Path
4
+ from dataclasses import dataclass, field
5
+
6
+
7
+ @dataclass
8
+ class Config:
9
+ lumerical_root: Path
10
+ host: str = "127.0.0.1"
11
+ port: int = 8000
12
+ transport: str = "streamable-http"
13
+ work_dir: Path = field(default_factory=Path.cwd)
14
+ log_level: str = "INFO"
15
+
16
+ @property
17
+ def lumerical_api_python(self) -> Path:
18
+ return self.lumerical_root / "api" / "python"
19
+
20
+ @property
21
+ def lumerical_bin(self) -> Path:
22
+ return self.lumerical_root / "bin"
23
+
24
+ @classmethod
25
+ def load(cls) -> "Config":
26
+ lumerical_root = os.environ.get("LUMERICAL_ROOT")
27
+ if not lumerical_root:
28
+ raise RuntimeError(
29
+ "LUMERICAL_ROOT environment variable not set. "
30
+ "Point it to your Lumerical installation directory, e.g.: "
31
+ " Windows: D:\\Program Files\\ANSYS Inc\\v251\\Lumerical "
32
+ " Linux: /opt/lumerical/v251"
33
+ )
34
+ root = Path(lumerical_root).expanduser().resolve()
35
+ if not root.exists():
36
+ raise RuntimeError(f"LUMERICAL_ROOT path does not exist: {root}")
37
+
38
+ config_file = Path.home() / ".config" / "lumerical-mcp" / "config.json"
39
+ file_cfg = {}
40
+ if config_file.exists():
41
+ try:
42
+ file_cfg = json.loads(config_file.read_text(encoding="utf-8"))
43
+ except Exception:
44
+ pass
45
+
46
+ def get(key: str, default):
47
+ return os.environ.get(key) or file_cfg.get(key) or default
48
+
49
+ return cls(
50
+ lumerical_root=root,
51
+ host=get("MCP_HOST", "127.0.0.1"),
52
+ port=int(get("MCP_PORT", "8000")),
53
+ transport=get("MCP_TRANSPORT", "streamable-http"),
54
+ work_dir=Path(get("MCP_WORK_DIR", str(Path.cwd()))).expanduser().resolve(),
55
+ log_level=get("MCP_LOG_LEVEL", "INFO"),
56
+ )
@@ -0,0 +1,311 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ import json
5
+ import subprocess
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import Any, Optional
9
+
10
+ from .config import Config
11
+
12
+ cfg = Config.load()
13
+
14
+ logging.basicConfig(level=cfg.log_level, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
15
+ logger = logging.getLogger("lumerical-mcp")
16
+
17
+ for _directory in (cfg.lumerical_api_python, cfg.lumerical_bin):
18
+ _directory_str = str(_directory)
19
+ if _directory.exists():
20
+ if _directory_str not in sys.path:
21
+ sys.path.append(_directory_str)
22
+ if hasattr(os, "add_dll_directory"):
23
+ try:
24
+ os.add_dll_directory(_directory_str)
25
+ except OSError:
26
+ pass
27
+
28
+ import lumapi
29
+
30
+ from mcp.server.mcpserver import MCPServer
31
+
32
+ mcp = MCPServer("lumerical")
33
+
34
+ fdtd = None
35
+ _gui_jobs: dict[str, dict[str, Any]] = {}
36
+
37
+
38
+ def _ensure_fdtd(hide: bool = False):
39
+ global fdtd
40
+ if fdtd is None:
41
+ fdtd = lumapi.FDTD(hide=hide)
42
+ return fdtd
43
+
44
+
45
+ def _jsonable(value: Any, max_array_items: int = 2000) -> Any:
46
+ try:
47
+ import numpy as np
48
+ except Exception:
49
+ np = None
50
+
51
+ if value is None or isinstance(value, (bool, int, float, str)):
52
+ return value
53
+
54
+ if isinstance(value, bytes):
55
+ return value.decode("utf-8", errors="replace")
56
+
57
+ if isinstance(value, complex):
58
+ return {"real": value.real, "imag": value.imag}
59
+
60
+ if np is not None:
61
+ if isinstance(value, np.generic):
62
+ return _jsonable(value.item(), max_array_items)
63
+ if isinstance(value, np.ndarray):
64
+ size = int(value.size)
65
+ if size <= max_array_items:
66
+ return {
67
+ "type": "ndarray",
68
+ "shape": list(value.shape),
69
+ "dtype": str(value.dtype),
70
+ "data": _jsonable(value.tolist(), max_array_items),
71
+ }
72
+ flat = value.ravel()
73
+ return {
74
+ "type": "ndarray",
75
+ "shape": list(value.shape),
76
+ "dtype": str(value.dtype),
77
+ "size": size,
78
+ "sample": _jsonable(flat[:20].tolist(), max_array_items),
79
+ "truncated": True,
80
+ }
81
+
82
+ if isinstance(value, dict):
83
+ return {str(k): _jsonable(v, max_array_items) for k, v in value.items()}
84
+
85
+ if isinstance(value, (list, tuple)):
86
+ return [_jsonable(item, max_array_items) for item in value]
87
+
88
+ return repr(value)
89
+
90
+
91
+ @mcp.tool()
92
+ def hello(name: str) -> str:
93
+ return f"Hello {name}, MCP is working."
94
+
95
+
96
+ @mcp.tool()
97
+ def start_fdtd() -> str:
98
+ global fdtd
99
+ if fdtd is not None:
100
+ return "FDTD is already running."
101
+ fdtd = lumapi.FDTD()
102
+ return "Lumerical FDTD started successfully."
103
+
104
+
105
+ @mcp.tool()
106
+ def end_fdtd() -> str:
107
+ global fdtd
108
+ if fdtd is None:
109
+ return "No FDTD session is running."
110
+ fdtd.close()
111
+ fdtd = None
112
+ return "Lumerical FDTD closed."
113
+
114
+
115
+ @mcp.tool()
116
+ def open_projects(path: str) -> str:
117
+ session = _ensure_fdtd()
118
+ session.load(str(cfg.work_dir / path) if not Path(path).is_absolute() else path)
119
+ return f"Project opened: {path}"
120
+
121
+
122
+ @mcp.tool()
123
+ def set_parameter(object_name: str, property_name: str, value: float) -> str:
124
+ if fdtd is None:
125
+ return "FDTD is not running."
126
+ fdtd.switchtolayout()
127
+ fdtd.setnamed(object_name, property_name, value)
128
+ return f"{object_name}: {property_name} = {value}"
129
+
130
+
131
+ @mcp.tool()
132
+ def run_simulation() -> str:
133
+ if fdtd is None:
134
+ return "FDTD is not running."
135
+ fdtd.run()
136
+ return "Simulation finished."
137
+
138
+
139
+ @mcp.tool()
140
+ def save_project(path: str) -> str:
141
+ if fdtd is None:
142
+ return "FDTD is not running."
143
+ fdtd.save(str(cfg.work_dir / path) if not Path(path).is_absolute() else path)
144
+ return f"Project saved: {path}"
145
+
146
+
147
+ @mcp.tool()
148
+ def get_transmission(monitor_name: str) -> dict:
149
+ if fdtd is None:
150
+ return {"error": "FDTD is not running."}
151
+ result = fdtd.getresult(monitor_name, "T")
152
+ wavelength = result["lambda"].flatten().flip()
153
+ transmission = result["T"].flatten().flip()
154
+ return {
155
+ "wavelength_nm": (wavelength * 1e9).tolist(),
156
+ "T": transmission.tolist(),
157
+ }
158
+
159
+
160
+ @mcp.tool()
161
+ def fdtd_eval_script_tool(code: str) -> dict:
162
+ session = _ensure_fdtd()
163
+ session.eval(code)
164
+ return {"ok": True, "executed": True}
165
+
166
+
167
+ @mcp.tool()
168
+ def fdtd_eval_script_get_tool(code: str, return_var: str, max_array_items: int = 2000) -> dict:
169
+ session = _ensure_fdtd()
170
+ session.eval(code)
171
+ value = session.getv(return_var)
172
+ return {
173
+ "ok": True,
174
+ "variable": return_var,
175
+ "value": _jsonable(value, max_array_items),
176
+ }
177
+
178
+
179
+ @mcp.tool()
180
+ def fdtd_run_script_file_tool(script_path: str) -> dict:
181
+ session = _ensure_fdtd()
182
+ path = str(cfg.work_dir / script_path) if not Path(script_path).is_absolute() else script_path
183
+ if path.lower().endswith(".lsfx"):
184
+ session.eval(path[:-5] + ";")
185
+ else:
186
+ session.feval(path)
187
+ return {"ok": True, "script_path": path}
188
+
189
+
190
+ @mcp.tool()
191
+ def fdtd_get_variable_tool(name: str, max_array_items: int = 2000) -> dict:
192
+ session = _ensure_fdtd()
193
+ value = session.getv(name)
194
+ return {
195
+ "ok": True,
196
+ "variable": name,
197
+ "value": _jsonable(value, max_array_items),
198
+ }
199
+
200
+
201
+ @mcp.tool()
202
+ def fdtd_put_variable_tool(name: str, value: Any) -> dict:
203
+ session = _ensure_fdtd()
204
+ session.putv(name, value)
205
+ return {"ok": True, "variable": name}
206
+
207
+
208
+ @mcp.tool()
209
+ def fdtd_load_project_tool(project_path: str) -> dict:
210
+ session = _ensure_fdtd()
211
+ path = str(cfg.work_dir / project_path) if not Path(project_path).is_absolute() else project_path
212
+ session.load(path)
213
+ return {"ok": True, "project_path": path}
214
+
215
+
216
+ @mcp.tool()
217
+ def fdtd_save_project_tool(project_path: Optional[str] = None) -> dict:
218
+ session = _ensure_fdtd()
219
+ if project_path:
220
+ path = str(cfg.work_dir / project_path) if not Path(project_path).is_absolute() else project_path
221
+ session.save(path)
222
+ else:
223
+ session.save()
224
+ path = None
225
+ return {"ok": True, "project_path": path}
226
+
227
+
228
+ @mcp.tool()
229
+ def fdtd_run_simulation_tool() -> dict:
230
+ session = _ensure_fdtd()
231
+ started_at = time.time()
232
+ session.eval("run;")
233
+ return {"ok": True, "elapsed_seconds": time.time() - started_at}
234
+
235
+
236
+ @mcp.tool()
237
+ def fdtd_launch_gui_tool(target: str = "launcher", project_path: Optional[str] = None) -> dict:
238
+ if target.lower() in {"fdtd", "fdtd-solutions", "solutions"}:
239
+ exe = cfg.lumerical_bin / "fdtd-solutions.exe"
240
+ else:
241
+ exe = cfg.lumerical_bin / "launcher.exe"
242
+
243
+ if not exe.exists():
244
+ return {"ok": False, "error": f"Executable not found: {exe}"}
245
+
246
+ args = [str(exe)]
247
+ if project_path:
248
+ path = str(cfg.work_dir / project_path) if not Path(project_path).is_absolute() else project_path
249
+ args.append(path)
250
+
251
+ try:
252
+ process = subprocess.Popen(
253
+ args,
254
+ cwd=str(cfg.lumerical_bin) if cfg.lumerical_bin.exists() else None,
255
+ stdout=subprocess.DEVNULL,
256
+ stderr=subprocess.DEVNULL,
257
+ stdin=subprocess.DEVNULL,
258
+ )
259
+ except OSError as exc:
260
+ return {"ok": False, "error": f"{type(exc).__name__}: {exc}"}
261
+
262
+ job_id = f"fdtd-gui-{int(time.time())}-{process.pid}"
263
+ _gui_jobs[job_id] = {
264
+ "job_id": job_id,
265
+ "pid": process.pid,
266
+ "target": target,
267
+ "args": args,
268
+ "started_at": time.time(),
269
+ }
270
+ return {"ok": True, **_gui_jobs[job_id]}
271
+
272
+
273
+ @mcp.tool()
274
+ def fdtd_command_docs_tool(query: str = "", limit: int = 50) -> dict:
275
+ docs_path = cfg.lumerical_api_python / "docs.json"
276
+ if not docs_path.exists():
277
+ return {"ok": False, "error": f"docs.json not found: {docs_path}"}
278
+
279
+ docs = json.loads(docs_path.read_text(encoding="utf-8", errors="replace"))
280
+ needle = query.lower() if query else None
281
+ items = []
282
+
283
+ for name, entry in docs.items():
284
+ if isinstance(entry, dict):
285
+ text = entry.get("text", "")
286
+ link = entry.get("link", "")
287
+ else:
288
+ text = str(entry)
289
+ link = ""
290
+
291
+ if needle:
292
+ haystack = f"{name}\n{text}".lower()
293
+ if needle not in haystack:
294
+ continue
295
+
296
+ items.append({"name": name, "text": text, "link": link})
297
+ if len(items) >= limit:
298
+ break
299
+
300
+ return {"ok": True, "count": len(items), "commands": items}
301
+
302
+
303
+ def main():
304
+ logger.info(f"Starting Lumerical MCP server on {cfg.host}:{cfg.port} ({cfg.transport})")
305
+ logger.info(f"Lumerical root: {cfg.lumerical_root}")
306
+ logger.info(f"Work directory: {cfg.work_dir}")
307
+ mcp.run(transport=cfg.transport, host=cfg.host, port=cfg.port)
308
+
309
+
310
+ if __name__ == "__main__":
311
+ main()
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: lumerical-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for Lumerical FDTD automation
5
+ Author-email: Saku1234 <lxh875410020@gmail.com>
6
+ Maintainer-email: Saku1234 <lxh875410020@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/saku123hh/lumerical-mcp
9
+ Project-URL: Repository, https://github.com/saku123hh/lumerical-mcp
10
+ Project-URL: Issues, https://github.com/saku123hh/lumerical-mcp/issues
11
+ Keywords: mcp,lumerical,fdtd,photonics,simulation
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Physics
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: mcp>=1.0.0
22
+ Requires-Dist: numpy
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: ruff; extra == "dev"
26
+
27
+ # Lumerical MCP Server
28
+
29
+ MCP (Model Context Protocol) server for controlling Lumerical FDTD simulations from LLMs.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ cd lumerical-mcp
35
+ pip install -e .
36
+ ```
37
+
38
+ ## Configuration
39
+
40
+ ### Required Environment Variable
41
+
42
+ ```bash
43
+ # Windows (PowerShell)
44
+ $env:LUMERICAL_ROOT = "D:\Program Files\ANSYS Inc\v251\Lumerical"
45
+
46
+ # Windows (cmd)
47
+ set LUMERICAL_ROOT=D:\Program Files\ANSYS Inc\v251\Lumerical
48
+
49
+ # Linux/macOS
50
+ export LUMERICAL_ROOT=/opt/lumerical/v251
51
+ ```
52
+
53
+ ### Optional Environment Variables
54
+
55
+ | Variable | Default | Description |
56
+ |----------|---------|-------------|
57
+ | `MCP_HOST` | `127.0.0.1` | Server bind address |
58
+ | `MCP_PORT` | `8000` | Server port |
59
+ | `MCP_TRANSPORT` | `streamable-http` | Transport protocol (`streamable-http` or `stdio`) |
60
+ | `MCP_WORK_DIR` | Current directory | Working directory for relative paths |
61
+ | `MCP_LOG_LEVEL` | `INFO` | Log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
62
+
63
+ ### Config File (Optional)
64
+
65
+ Create `~/.config/lumerical-mcp/config.json`:
66
+
67
+ ```json
68
+ {
69
+ "MCP_HOST": "127.0.0.1",
70
+ "MCP_PORT": 8000,
71
+ "MCP_TRANSPORT": "streamable-http",
72
+ "MCP_WORK_DIR": "C:/Users/name/projects/lumerical",
73
+ "MCP_LOG_LEVEL": "INFO"
74
+ }
75
+ ```
76
+
77
+ Environment variables take precedence over config file.
78
+
79
+ ## Running the Server
80
+
81
+ ```bash
82
+ lumerical-mcp
83
+ ```
84
+
85
+ Or with custom settings:
86
+
87
+ ```bash
88
+ MCP_PORT=9000 MCP_TRANSPORT=stdio lumerical-mcp
89
+ ```
90
+
91
+ ## Available Tools
92
+
93
+ | Tool | Description |
94
+ |------|-------------|
95
+ | `hello` | Connectivity check |
96
+ | `start_fdtd` / `end_fdtd` | Start/stop FDTD API session |
97
+ | `open_projects` / `fdtd_load_project_tool` | Load `.fsp` project |
98
+ | `save_project` / `fdtd_save_project_tool` | Save project |
99
+ | `set_parameter` | Set object property |
100
+ | `run_simulation` / `fdtd_run_simulation_tool` | Run simulation |
101
+ | `get_transmission` | Get transmission spectrum from monitor |
102
+ | `fdtd_eval_script_tool` | Execute Lumerical script |
103
+ | `fdtd_eval_script_get_tool` | Execute script and return variable |
104
+ | `fdtd_run_script_file_tool` | Run `.lsf`/`.lsfx` script file |
105
+ | `fdtd_get_variable_tool` / `fdtd_put_variable_tool` | Read/write script variables |
106
+ | `fdtd_launch_gui_tool` | Launch Lumerical GUI |
107
+ | `fdtd_command_docs_tool` | Search command documentation |
108
+
109
+ ## Client Usage
110
+
111
+ ```bash
112
+ # Using the test client
113
+ python client_test.py
114
+
115
+ # Or with custom server address
116
+ MCP_HOST=192.168.1.100 MCP_PORT=9000 python client_test.py
117
+ ```
118
+
119
+ ## LLM Client Configuration
120
+
121
+ ### Claude Desktop (`claude_desktop_config.json`)
122
+
123
+ ```json
124
+ {
125
+ "mcpServers": {
126
+ "lumerical": {
127
+ "command": "lumerical-mcp",
128
+ "env": {
129
+ "LUMERICAL_ROOT": "D:\\Program Files\\ANSYS Inc\\v251\\Lumerical"
130
+ }
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### VS Code / Continue / Other MCP Clients
137
+
138
+ ```json
139
+ {
140
+ "mcpServers": {
141
+ "lumerical": {
142
+ "command": "lumerical-mcp",
143
+ "args": [],
144
+ "env": {
145
+ "LUMERICAL_ROOT": "/opt/lumerical/v251"
146
+ }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ## Development
153
+
154
+ ```bash
155
+ pip install -e .[dev]
156
+ ruff check .
157
+ pytest
158
+ ```
159
+
160
+ ## License
161
+
162
+ MIT
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ lumerical_mcp/__init__.py
4
+ lumerical_mcp/config.py
5
+ lumerical_mcp/server.py
6
+ lumerical_mcp.egg-info/PKG-INFO
7
+ lumerical_mcp.egg-info/SOURCES.txt
8
+ lumerical_mcp.egg-info/dependency_links.txt
9
+ lumerical_mcp.egg-info/entry_points.txt
10
+ lumerical_mcp.egg-info/requires.txt
11
+ lumerical_mcp.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ lumerical-mcp = lumerical_mcp.server:main
@@ -0,0 +1,6 @@
1
+ mcp>=1.0.0
2
+ numpy
3
+
4
+ [dev]
5
+ pytest
6
+ ruff
@@ -0,0 +1 @@
1
+ lumerical_mcp
@@ -0,0 +1,47 @@
1
+ # D:\Files\gitProjects\lumerical-mcp\pyproject.toml
2
+ [build-system]
3
+ requires = ["setuptools>=61.0", "wheel"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ name = "lumerical-mcp"
8
+ version = "0.1.0"
9
+ description = "MCP server for Lumerical FDTD automation"
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ authors = [{name = "Saku1234", email = "lxh875410020@gmail.com"}]
13
+ maintainers = [{name = "Saku1234", email = "lxh875410020@gmail.com"}]
14
+ keywords = ["mcp", "lumerical", "fdtd", "photonics", "simulation"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Science/Research",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Scientific/Engineering :: Physics",
23
+ ]
24
+ requires-python = ">=3.10"
25
+ dependencies = [
26
+ "mcp>=1.0.0",
27
+ "numpy",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/saku123hh/lumerical-mcp"
32
+ Repository = "https://github.com/saku123hh/lumerical-mcp"
33
+ Issues = "https://github.com/saku123hh/lumerical-mcp/issues"
34
+
35
+ [project.optional-dependencies]
36
+ dev = ["pytest", "ruff"]
37
+
38
+ [project.scripts]
39
+ "lumerical-mcp" = "lumerical_mcp.server:main"
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["."]
43
+ include = ["lumerical_mcp*"]
44
+
45
+ [tool.ruff]
46
+ line-length = 100
47
+ target-version = "py310"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+