gfp-mcp 0.2.1__py3-none-any.whl → 0.3.2__py3-none-any.whl
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.
- {mcp_standalone → gfp_mcp}/__init__.py +10 -8
- {mcp_standalone → gfp_mcp}/client.py +57 -33
- gfp_mcp/config.py +161 -0
- {mcp_standalone → gfp_mcp}/registry.py +0 -4
- gfp_mcp/render.py +139 -0
- {mcp_standalone → gfp_mcp}/resources.py +0 -3
- gfp_mcp/samples.py +206 -0
- gfp_mcp/server.py +235 -0
- gfp_mcp/tools/__init__.py +134 -0
- gfp_mcp/tools/base.py +235 -0
- gfp_mcp/tools/bbox.py +115 -0
- gfp_mcp/tools/build.py +159 -0
- gfp_mcp/tools/cells.py +103 -0
- gfp_mcp/tools/connectivity.py +70 -0
- gfp_mcp/tools/drc.py +379 -0
- gfp_mcp/tools/freeze.py +82 -0
- gfp_mcp/tools/lvs.py +86 -0
- gfp_mcp/tools/pdk.py +47 -0
- gfp_mcp/tools/port.py +82 -0
- gfp_mcp/tools/project.py +160 -0
- gfp_mcp/tools/samples.py +215 -0
- gfp_mcp/tools/simulation.py +153 -0
- gfp_mcp/utils.py +55 -0
- {gfp_mcp-0.2.1.dist-info → gfp_mcp-0.3.2.dist-info}/METADATA +37 -8
- gfp_mcp-0.3.2.dist-info/RECORD +29 -0
- gfp_mcp-0.3.2.dist-info/entry_points.txt +2 -0
- gfp_mcp-0.3.2.dist-info/top_level.txt +1 -0
- gfp_mcp-0.2.1.dist-info/RECORD +0 -14
- gfp_mcp-0.2.1.dist-info/entry_points.txt +0 -2
- gfp_mcp-0.2.1.dist-info/top_level.txt +0 -1
- mcp_standalone/config.py +0 -56
- mcp_standalone/mappings.py +0 -386
- mcp_standalone/server.py +0 -294
- mcp_standalone/tools.py +0 -530
- {gfp_mcp-0.2.1.dist-info → gfp_mcp-0.3.2.dist-info}/WHEEL +0 -0
- {gfp_mcp-0.2.1.dist-info → gfp_mcp-0.3.2.dist-info}/licenses/LICENSE +0 -0
gfp_mcp/tools/project.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Project discovery tool handlers.
|
|
2
|
+
|
|
3
|
+
These tools handle project listing and info retrieval from the
|
|
4
|
+
server registry without making HTTP requests to the backend.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
from mcp.types import TextContent, Tool
|
|
14
|
+
|
|
15
|
+
from .base import EndpointMapping, ToolHandler
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..client import FastAPIClient
|
|
19
|
+
|
|
20
|
+
__all__ = ["ListProjectsHandler", "GetProjectInfoHandler"]
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ListProjectsHandler(ToolHandler):
|
|
26
|
+
"""Handler for listing all active GDSFactory+ projects.
|
|
27
|
+
|
|
28
|
+
This is a registry-only tool that doesn't make HTTP requests.
|
|
29
|
+
It reads from the local server registry to discover running servers.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def name(self) -> str:
|
|
34
|
+
return "list_projects"
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def definition(self) -> Tool:
|
|
38
|
+
return Tool(
|
|
39
|
+
name="list_projects",
|
|
40
|
+
description=(
|
|
41
|
+
"List all active GDSFactory+ projects. Returns information about "
|
|
42
|
+
"all running servers including project name, path, port, PID, and PDK. "
|
|
43
|
+
"Use this to discover which projects are available for interaction."
|
|
44
|
+
),
|
|
45
|
+
inputSchema={
|
|
46
|
+
"type": "object",
|
|
47
|
+
"properties": {},
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def mapping(self) -> EndpointMapping | None:
|
|
53
|
+
# Registry-only tool, no HTTP endpoint
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
async def handle(
|
|
57
|
+
self,
|
|
58
|
+
arguments: dict[str, Any],
|
|
59
|
+
client: FastAPIClient,
|
|
60
|
+
) -> list[TextContent]:
|
|
61
|
+
"""List all projects from the registry.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
arguments: MCP tool arguments (unused for this tool)
|
|
65
|
+
client: FastAPI client (used to access registry)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
List containing TextContent with project list
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
projects = client.list_projects()
|
|
72
|
+
return [
|
|
73
|
+
TextContent(
|
|
74
|
+
type="text",
|
|
75
|
+
text=json.dumps({"projects": projects}, indent=2),
|
|
76
|
+
)
|
|
77
|
+
]
|
|
78
|
+
except Exception as e:
|
|
79
|
+
error_msg = f"Failed to list projects: {e!s}"
|
|
80
|
+
logger.exception(error_msg)
|
|
81
|
+
return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class GetProjectInfoHandler(ToolHandler):
|
|
85
|
+
"""Handler for getting detailed info about a specific project.
|
|
86
|
+
|
|
87
|
+
This tool makes an HTTP request to the project's /info endpoint
|
|
88
|
+
to retrieve detailed information including version and PDK details.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def name(self) -> str:
|
|
93
|
+
return "get_project_info"
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def definition(self) -> Tool:
|
|
97
|
+
return Tool(
|
|
98
|
+
name="get_project_info",
|
|
99
|
+
description=(
|
|
100
|
+
"Get detailed information about a specific project. Returns metadata "
|
|
101
|
+
"including project name, path, port, PID, PDK, and version. "
|
|
102
|
+
"Use this to get information about a running project."
|
|
103
|
+
),
|
|
104
|
+
inputSchema={
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"project": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": (
|
|
110
|
+
"Project name or path. Can be the project directory name "
|
|
111
|
+
"or full path."
|
|
112
|
+
),
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
"required": ["project"],
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def mapping(self) -> EndpointMapping:
|
|
121
|
+
return EndpointMapping(method="GET", path="/info")
|
|
122
|
+
|
|
123
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
124
|
+
"""No request transformation needed - just routes to /info."""
|
|
125
|
+
return {}
|
|
126
|
+
|
|
127
|
+
async def handle(
|
|
128
|
+
self,
|
|
129
|
+
arguments: dict[str, Any],
|
|
130
|
+
client: FastAPIClient,
|
|
131
|
+
) -> list[TextContent]:
|
|
132
|
+
"""Get project info via HTTP request.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
arguments: MCP tool arguments with 'project' key
|
|
136
|
+
client: FastAPI client for making requests
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List containing TextContent with project info
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
project = arguments.get("project")
|
|
143
|
+
if not project:
|
|
144
|
+
return [
|
|
145
|
+
TextContent(
|
|
146
|
+
type="text",
|
|
147
|
+
text=json.dumps({"error": "project parameter is required"}),
|
|
148
|
+
)
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
response = await client.request(
|
|
152
|
+
method="GET",
|
|
153
|
+
path="/info",
|
|
154
|
+
project=project,
|
|
155
|
+
)
|
|
156
|
+
return [TextContent(type="text", text=json.dumps(response, indent=2))]
|
|
157
|
+
except Exception as e:
|
|
158
|
+
error_msg = f"Failed to get project info: {e!s}"
|
|
159
|
+
logger.exception(error_msg)
|
|
160
|
+
return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
|
gfp_mcp/tools/samples.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Sample project tool handlers.
|
|
2
|
+
|
|
3
|
+
These tools allow AI assistants to browse and read sample files
|
|
4
|
+
from GDSFactory+ General PDK sample projects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
from mcp.types import TextContent, Tool
|
|
14
|
+
|
|
15
|
+
from ..config import get_gfp_api_key
|
|
16
|
+
from ..samples import (
|
|
17
|
+
GENERAL_PDK_PROJECTS,
|
|
18
|
+
get_sample_file_content,
|
|
19
|
+
list_sample_projects,
|
|
20
|
+
)
|
|
21
|
+
from .base import ToolHandler
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from ..client import FastAPIClient
|
|
25
|
+
|
|
26
|
+
__all__ = ["ListSamplesHandler", "GetSampleFileHandler"]
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
_API_KEY_ERROR = {
|
|
31
|
+
"error": "GFP API key not configured",
|
|
32
|
+
"instructions": (
|
|
33
|
+
"Set the GFP_API_KEY environment variable or configure "
|
|
34
|
+
"~/.gdsfactory/gdsfactoryplus.toml with your API key. "
|
|
35
|
+
"Get your key at: https://api.gdsfactory.com/api-keys/list"
|
|
36
|
+
),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ListSamplesHandler(ToolHandler):
|
|
41
|
+
"""Handler for listing sample files from General PDK projects.
|
|
42
|
+
|
|
43
|
+
Downloads project ZIPs from the registry and returns file listings.
|
|
44
|
+
This is a registry-only tool that doesn't use the FastAPI backend.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def name(self) -> str:
|
|
49
|
+
return "list_samples"
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def definition(self) -> Tool:
|
|
53
|
+
return Tool(
|
|
54
|
+
name="list_samples",
|
|
55
|
+
description=(
|
|
56
|
+
"List all available sample files from GDSFactory+ General PDK "
|
|
57
|
+
"sample projects. Returns file listings for the basic and full "
|
|
58
|
+
"public PDK projects. Requires a GFP API key."
|
|
59
|
+
),
|
|
60
|
+
inputSchema={
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {},
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
async def handle(
|
|
67
|
+
self,
|
|
68
|
+
arguments: dict[str, Any],
|
|
69
|
+
client: FastAPIClient,
|
|
70
|
+
) -> list[TextContent]:
|
|
71
|
+
"""List sample files from all General PDK projects.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
arguments: MCP tool arguments (unused).
|
|
75
|
+
client: FastAPI client (unused, samples come from registry).
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List containing TextContent with project file listings.
|
|
79
|
+
"""
|
|
80
|
+
api_key = get_gfp_api_key()
|
|
81
|
+
if not api_key:
|
|
82
|
+
return [TextContent(type="text", text=json.dumps(_API_KEY_ERROR))]
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
projects = await list_sample_projects(api_key)
|
|
86
|
+
result = {
|
|
87
|
+
"projects": [{"name": p.name, "files": p.files} for p in projects]
|
|
88
|
+
}
|
|
89
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
90
|
+
except Exception as e:
|
|
91
|
+
error_msg = f"Failed to fetch samples: {e!s}"
|
|
92
|
+
logger.exception(error_msg)
|
|
93
|
+
return [
|
|
94
|
+
TextContent(
|
|
95
|
+
type="text",
|
|
96
|
+
text=json.dumps({"error": error_msg}),
|
|
97
|
+
)
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class GetSampleFileHandler(ToolHandler):
|
|
102
|
+
"""Handler for reading a specific sample file's content.
|
|
103
|
+
|
|
104
|
+
Downloads a project ZIP and extracts the requested file.
|
|
105
|
+
This is a registry-only tool that doesn't use the FastAPI backend.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def name(self) -> str:
|
|
110
|
+
return "get_sample_file"
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def definition(self) -> Tool:
|
|
114
|
+
return Tool(
|
|
115
|
+
name="get_sample_file",
|
|
116
|
+
description=(
|
|
117
|
+
"Get the content of a specific sample file from a GDSFactory+ "
|
|
118
|
+
"General PDK project. Use list_samples first to discover "
|
|
119
|
+
"available files. Requires a GFP API key."
|
|
120
|
+
),
|
|
121
|
+
inputSchema={
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"project": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": (
|
|
127
|
+
"Sample project name "
|
|
128
|
+
"(e.g., 'photonics--basic--public--pdk')."
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
"path": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"description": (
|
|
134
|
+
"File path within the project ZIP "
|
|
135
|
+
"(as returned by list_samples)."
|
|
136
|
+
),
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
"required": ["project", "path"],
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
async def handle(
|
|
144
|
+
self,
|
|
145
|
+
arguments: dict[str, Any],
|
|
146
|
+
client: FastAPIClient,
|
|
147
|
+
) -> list[TextContent]:
|
|
148
|
+
"""Get the content of a specific sample file.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
arguments: MCP tool arguments with 'project' and 'path'.
|
|
152
|
+
client: FastAPI client (unused, samples come from registry).
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
List containing TextContent with file content and metadata.
|
|
156
|
+
"""
|
|
157
|
+
api_key = get_gfp_api_key()
|
|
158
|
+
if not api_key:
|
|
159
|
+
return [TextContent(type="text", text=json.dumps(_API_KEY_ERROR))]
|
|
160
|
+
|
|
161
|
+
project = arguments.get("project")
|
|
162
|
+
path = arguments.get("path")
|
|
163
|
+
|
|
164
|
+
if not project or not path:
|
|
165
|
+
return [
|
|
166
|
+
TextContent(
|
|
167
|
+
type="text",
|
|
168
|
+
text=json.dumps(
|
|
169
|
+
{"error": "Both 'project' and 'path' parameters are required"}
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
sample_file = await get_sample_file_content(api_key, project, path)
|
|
176
|
+
result = {
|
|
177
|
+
"project": sample_file.project,
|
|
178
|
+
"path": sample_file.path,
|
|
179
|
+
"content": sample_file.content,
|
|
180
|
+
"metadata": {"mime_type": sample_file.mime_type},
|
|
181
|
+
}
|
|
182
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
183
|
+
except ValueError:
|
|
184
|
+
return [
|
|
185
|
+
TextContent(
|
|
186
|
+
type="text",
|
|
187
|
+
text=json.dumps(
|
|
188
|
+
{
|
|
189
|
+
"error": f"Unknown sample project: {project}",
|
|
190
|
+
"available_projects": GENERAL_PDK_PROJECTS,
|
|
191
|
+
}
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
]
|
|
195
|
+
except KeyError:
|
|
196
|
+
return [
|
|
197
|
+
TextContent(
|
|
198
|
+
type="text",
|
|
199
|
+
text=json.dumps(
|
|
200
|
+
{
|
|
201
|
+
"error": f"File not found in project: {path}",
|
|
202
|
+
"suggestion": ("Use list_samples to see available files."),
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
]
|
|
207
|
+
except Exception as e:
|
|
208
|
+
error_msg = f"Failed to fetch sample file: {e!s}"
|
|
209
|
+
logger.exception(error_msg)
|
|
210
|
+
return [
|
|
211
|
+
TextContent(
|
|
212
|
+
type="text",
|
|
213
|
+
text=json.dumps({"error": error_msg}),
|
|
214
|
+
)
|
|
215
|
+
]
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""SAX circuit simulation tool handler.
|
|
2
|
+
|
|
3
|
+
This module provides the handler for running SAX circuit simulations
|
|
4
|
+
on photonic components, with fallback to the old API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from mcp.types import Tool
|
|
13
|
+
|
|
14
|
+
from .base import EndpointMapping, ToolHandler, add_project_param
|
|
15
|
+
|
|
16
|
+
__all__ = ["SimulateComponentHandler"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SimulateComponentHandler(ToolHandler):
|
|
20
|
+
"""Handler for running SAX circuit simulations.
|
|
21
|
+
|
|
22
|
+
Supports both the new SAX API (/api/sax/{name}/simulate) and
|
|
23
|
+
falls back to the old API (/api/simulate) if the new endpoint
|
|
24
|
+
returns 404.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def name(self) -> str:
|
|
29
|
+
return "simulate_component"
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def definition(self) -> Tool:
|
|
33
|
+
return Tool(
|
|
34
|
+
name="simulate_component",
|
|
35
|
+
description=(
|
|
36
|
+
"Run a SAX circuit simulation on a photonic component by name. "
|
|
37
|
+
"This simulates the optical behavior of the component using its "
|
|
38
|
+
"SAX model. Returns S-parameters showing how light propagates "
|
|
39
|
+
"through the component's ports. Use this to analyze component "
|
|
40
|
+
"performance before fabrication. Supports customizing both layout "
|
|
41
|
+
"parameters (e.g., {'length_mmi': 12, 'gap_mmi': 0.3}) and SAX "
|
|
42
|
+
"model simulation settings (e.g., {'wl': [1.5, 1.55, 1.6], 'loss': 0.2})."
|
|
43
|
+
),
|
|
44
|
+
inputSchema=add_project_param(
|
|
45
|
+
{
|
|
46
|
+
"type": "object",
|
|
47
|
+
"properties": {
|
|
48
|
+
"name": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": (
|
|
51
|
+
"Name of the component/cell to simulate. The component "
|
|
52
|
+
"must have a SAX model defined."
|
|
53
|
+
),
|
|
54
|
+
},
|
|
55
|
+
"layout": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"description": (
|
|
58
|
+
"Optional component-specific geometric parameters. "
|
|
59
|
+
"Use this to customize the component's physical dimensions "
|
|
60
|
+
"before simulation. Example: {'length_mmi': 12, 'gap_mmi': 0.3}. "
|
|
61
|
+
"Default is empty (use default layout parameters)."
|
|
62
|
+
),
|
|
63
|
+
},
|
|
64
|
+
"model": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"description": (
|
|
67
|
+
"Optional SAX simulation settings. Use this to configure "
|
|
68
|
+
"simulation parameters like wavelength sweeps or loss values. "
|
|
69
|
+
"Example: {'wl': [1.5, 1.55, 1.6], 'loss': 0.2}. "
|
|
70
|
+
"Default is empty (use default model parameters)."
|
|
71
|
+
),
|
|
72
|
+
},
|
|
73
|
+
"how": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"enum": ["from_layout", "from_netlist"],
|
|
76
|
+
"description": (
|
|
77
|
+
"Simulation method. 'from_layout' simulates from the physical "
|
|
78
|
+
"layout geometry (default). 'from_netlist' simulates from the "
|
|
79
|
+
"hierarchical netlist representation."
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
"required": ["name"],
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def mapping(self) -> EndpointMapping:
|
|
90
|
+
return EndpointMapping(method="POST", path="/api/sax/{name}/simulate")
|
|
91
|
+
|
|
92
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
93
|
+
"""Transform simulate_component MCP args to FastAPI request body.
|
|
94
|
+
|
|
95
|
+
Supports both new SAX API (/api/sax/{name}/simulate) and falls back
|
|
96
|
+
to old API (/api/simulate) if the new endpoint returns 404.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
args: MCP tool arguments
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dict with 'path', 'json_data' for new API and fallback data for old API
|
|
103
|
+
"""
|
|
104
|
+
name = args["name"]
|
|
105
|
+
layout = args.get("layout", {})
|
|
106
|
+
model = args.get("model", {})
|
|
107
|
+
how = args.get("how", "from_layout")
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"path": f"/api/sax/{name}/simulate",
|
|
111
|
+
"json_data": {
|
|
112
|
+
"layout": layout,
|
|
113
|
+
"model": model,
|
|
114
|
+
"from_netlist": how == "from_netlist",
|
|
115
|
+
},
|
|
116
|
+
"fallback_path": "/api/simulate",
|
|
117
|
+
"fallback_json_data": {
|
|
118
|
+
"name": name,
|
|
119
|
+
"layout": layout,
|
|
120
|
+
"model": model,
|
|
121
|
+
"how": how,
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def transform_response(self, response: Any) -> dict[str, Any]:
|
|
126
|
+
"""Transform simulate_component response for LLM consumption.
|
|
127
|
+
|
|
128
|
+
Returns only the file path and token estimate instead of the full
|
|
129
|
+
S-parameter data, which can be extremely large (500k+ tokens).
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
response: FastAPI response containing simulation results
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dict with file path, token estimate, and metadata
|
|
136
|
+
"""
|
|
137
|
+
if not isinstance(response, dict):
|
|
138
|
+
return {"error": f"Unexpected response type: {type(response).__name__}"}
|
|
139
|
+
|
|
140
|
+
# Estimate tokens (rough heuristic: ~4 chars per token)
|
|
141
|
+
json_str = json.dumps(response)
|
|
142
|
+
estimated_tokens = len(json_str) // 4
|
|
143
|
+
|
|
144
|
+
file_path = response.get("file_path")
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"file_path": file_path,
|
|
148
|
+
"estimated_tokens": estimated_tokens,
|
|
149
|
+
"message": f"Simulation complete. Results saved to: {file_path}",
|
|
150
|
+
"note": "Use the Read tool to view the simulation results at this path.",
|
|
151
|
+
"simulation_id": response.get("simulation_id"),
|
|
152
|
+
"cached": response.get("cached", False),
|
|
153
|
+
}
|
gfp_mcp/utils.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Common utilities for MCP server.
|
|
2
|
+
|
|
3
|
+
This module provides shared utility functions used across the MCP server,
|
|
4
|
+
including file polling and other async helpers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
__all__ = ["wait_for_gds_file"]
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def wait_for_gds_file(
|
|
20
|
+
gds_path: Path,
|
|
21
|
+
timeout: float = 30.0,
|
|
22
|
+
poll_interval: float = 0.5,
|
|
23
|
+
min_age: float = 0.1,
|
|
24
|
+
) -> bool:
|
|
25
|
+
"""Poll for GDS file to be created with timeout.
|
|
26
|
+
|
|
27
|
+
This function waits for a GDS file to exist and be stable (not actively
|
|
28
|
+
being written to) before returning. Useful for waiting on build operations.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
gds_path: Path to the expected GDS file
|
|
32
|
+
timeout: Maximum time to wait in seconds (default: 30.0)
|
|
33
|
+
poll_interval: Time between polls in seconds (default: 0.5)
|
|
34
|
+
min_age: Minimum file age in seconds to ensure write is complete (default: 0.1)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
True if file exists and is ready, False if timeout reached
|
|
38
|
+
"""
|
|
39
|
+
start_time = time.monotonic()
|
|
40
|
+
logger.debug("Waiting for GDS file: %s (timeout: %.1fs)", gds_path, timeout)
|
|
41
|
+
|
|
42
|
+
while time.monotonic() - start_time < timeout:
|
|
43
|
+
if gds_path.exists():
|
|
44
|
+
file_age = time.time() - gds_path.stat().st_mtime
|
|
45
|
+
if file_age >= min_age:
|
|
46
|
+
logger.debug("GDS file ready: %s (age: %.2fs)", gds_path, file_age)
|
|
47
|
+
return True
|
|
48
|
+
logger.debug(
|
|
49
|
+
"GDS file exists but too new (age: %.2fs), waiting...", file_age
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
await asyncio.sleep(poll_interval)
|
|
53
|
+
|
|
54
|
+
logger.warning("Timeout waiting for GDS file: %s", gds_path)
|
|
55
|
+
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gfp-mcp
|
|
3
|
-
Version: 0.2
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Model Context Protocol (MCP) server for GDSFactory+ photonic IC design
|
|
5
5
|
Author: GDSFactory+ Team
|
|
6
6
|
License: MIT
|
|
@@ -27,19 +27,24 @@ License-File: LICENSE
|
|
|
27
27
|
Requires-Dist: mcp>=1.7.1
|
|
28
28
|
Requires-Dist: httpx>=0.25.0
|
|
29
29
|
Requires-Dist: typing-extensions>=4.0.0; python_version < "3.11"
|
|
30
|
+
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
30
31
|
Requires-Dist: psutil>=5.9.0
|
|
32
|
+
Provides-Extra: render
|
|
33
|
+
Requires-Dist: klayout>=0.28.0; extra == "render"
|
|
31
34
|
Provides-Extra: dev
|
|
32
35
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
36
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
34
37
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
35
38
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
39
|
+
Requires-Dist: bump-my-version>=0.26.0; extra == "dev"
|
|
40
|
+
Requires-Dist: build>=1.4.0; extra == "dev"
|
|
36
41
|
Dynamic: license-file
|
|
37
42
|
|
|
38
43
|
# GDSFactory+ MCP Server
|
|
39
44
|
|
|
40
45
|
[](https://pypi.org/project/gfp-mcp/)
|
|
41
46
|
[](https://pypi.org/project/gfp-mcp/)
|
|
42
|
-
[](https://github.com/doplaydo/gfp-mcp/actions)
|
|
43
48
|
[](https://opensource.org/licenses/MIT)
|
|
44
49
|
|
|
45
50
|
Model Context Protocol (MCP) server for GDSFactory+ that enables AI assistants like Claude to design and build photonic integrated circuits.
|
|
@@ -57,15 +62,32 @@ This MCP server connects AI assistants to [GDSFactory+](https://gdsfactory.com),
|
|
|
57
62
|
|
|
58
63
|
### 2. Install the MCP Server
|
|
59
64
|
|
|
65
|
+
**With uv (recommended):**
|
|
66
|
+
|
|
67
|
+
If you don't have `uv` installed, see the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/).
|
|
68
|
+
|
|
60
69
|
```bash
|
|
61
|
-
|
|
70
|
+
uv tool install gfp-mcp
|
|
62
71
|
```
|
|
63
72
|
|
|
64
|
-
|
|
73
|
+
<details>
|
|
74
|
+
<summary>Ephemeral approach</summary>
|
|
75
|
+
|
|
76
|
+
Run without installing:
|
|
65
77
|
|
|
66
78
|
```bash
|
|
67
79
|
uvx --from gfp-mcp gfp-mcp-serve
|
|
68
80
|
```
|
|
81
|
+
</details>
|
|
82
|
+
|
|
83
|
+
<details>
|
|
84
|
+
<summary><strong>Alternative: pip install</strong></summary>
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install gfp-mcp
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
</details>
|
|
69
91
|
|
|
70
92
|
### 3. Connect to Your AI Assistant
|
|
71
93
|
|
|
@@ -143,16 +165,23 @@ Try these commands with your AI assistant:
|
|
|
143
165
|
|
|
144
166
|
## Available Tools
|
|
145
167
|
|
|
146
|
-
- **
|
|
147
|
-
- **build_cells** - Build multiple GDS cells in batch
|
|
168
|
+
- **build_cells** - Build one or more GDS cells by name (pass a list, can be single-item)
|
|
148
169
|
- **list_cells** - List all available photonic components
|
|
149
170
|
- **get_cell_info** - Get detailed component metadata
|
|
150
|
-
- **download_gds** - Download built GDS files
|
|
151
171
|
- **list_projects** - List all running GDSFactory+ server instances
|
|
152
172
|
- **get_project_info** - Get detailed information about a specific project
|
|
153
|
-
- **check_drc** - Run Design Rule Check verification
|
|
173
|
+
- **check_drc** - Run Design Rule Check verification (returns structured format with all violations including simplified location data for LLM-friendly troubleshooting)
|
|
154
174
|
- **check_connectivity** - Run connectivity verification
|
|
155
175
|
- **check_lvs** - Run Layout vs. Schematic verification
|
|
176
|
+
- **simulate_component** - Run SAX circuit simulations with custom parameters
|
|
177
|
+
- Basic: `{"name": "mzi"}` - Simulate with default parameters
|
|
178
|
+
- Custom layout: `{"name": "mzi", "layout": {"length_mmi": 12, "gap_mmi": 0.3}}` - Customize component geometry
|
|
179
|
+
- Wavelength sweep: `{"name": "coupler", "model": {"wl": [1.5, 1.55, 1.6]}}` - Simulate at multiple wavelengths
|
|
180
|
+
- Full example: `{"name": "mzi", "layout": {"length": 100}, "model": {"wl": [1.5, 1.55, 1.6], "loss": 0.2}, "how": "from_layout"}`
|
|
181
|
+
- **get_port_center** - Get physical coordinates of component ports
|
|
182
|
+
- **generate_bbox** - Generate bounding box GDS from layout
|
|
183
|
+
- **freeze_cell** - Freeze parametric cell as static netlist
|
|
184
|
+
- **get_pdk_info** - Get current PDK information
|
|
156
185
|
|
|
157
186
|
## Multi-Project Support
|
|
158
187
|
|