gfp-mcp 0.2.4__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 +0 -14
- gfp_mcp/config.py +161 -0
- gfp_mcp/render.py +139 -0
- 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.4.dist-info → gfp_mcp-0.3.2.dist-info}/METADATA +13 -1
- 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.4.dist-info/RECORD +0 -14
- gfp_mcp-0.2.4.dist-info/entry_points.txt +0 -2
- gfp_mcp-0.2.4.dist-info/top_level.txt +0 -1
- mcp_standalone/config.py +0 -50
- mcp_standalone/mappings.py +0 -565
- mcp_standalone/server.py +0 -282
- mcp_standalone/tools.py +0 -466
- {mcp_standalone → gfp_mcp}/registry.py +0 -0
- {mcp_standalone → gfp_mcp}/resources.py +0 -0
- {gfp_mcp-0.2.4.dist-info → gfp_mcp-0.3.2.dist-info}/WHEEL +0 -0
- {gfp_mcp-0.2.4.dist-info → gfp_mcp-0.3.2.dist-info}/licenses/LICENSE +0 -0
gfp_mcp/tools/drc.py
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""DRC (Design Rule Check) tool handler.
|
|
2
|
+
|
|
3
|
+
This module provides the handler for running DRC checks on GDS files,
|
|
4
|
+
including XML parsing and actionable violation summaries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import xml.etree.ElementTree as ET
|
|
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__ = ["CheckDrcHandler"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _parse_polygon_location(values_elem: ET.Element | None) -> dict[str, Any] | None:
|
|
20
|
+
"""Parse polygon coordinates and compute location data.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
values_elem: XML element containing polygon coordinates
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Dict with bbox, centroid, and size, or None if parsing fails
|
|
27
|
+
"""
|
|
28
|
+
if values_elem is None:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
value_elem = values_elem.find("value")
|
|
32
|
+
if value_elem is None or value_elem.text is None:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
value_text = value_elem.text
|
|
36
|
+
if "polygon:" not in value_text:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
coords_str = value_text.replace("polygon:", "").strip().strip("()")
|
|
41
|
+
if not coords_str:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
pairs = coords_str.split(";")
|
|
45
|
+
coords = []
|
|
46
|
+
for pair in pairs:
|
|
47
|
+
if "," not in pair:
|
|
48
|
+
continue
|
|
49
|
+
x_str, y_str = pair.split(",", 1)
|
|
50
|
+
coords.append((float(x_str), float(y_str)))
|
|
51
|
+
|
|
52
|
+
if not coords:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
xs = [x for x, y in coords]
|
|
56
|
+
ys = [y for x, y in coords]
|
|
57
|
+
|
|
58
|
+
bbox = {
|
|
59
|
+
"min_x": round(min(xs), 3),
|
|
60
|
+
"min_y": round(min(ys), 3),
|
|
61
|
+
"max_x": round(max(xs), 3),
|
|
62
|
+
"max_y": round(max(ys), 3),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
centroid = {
|
|
66
|
+
"x": round(sum(xs) / len(xs), 3),
|
|
67
|
+
"y": round(sum(ys) / len(ys), 3),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
size = {
|
|
71
|
+
"width": round(bbox["max_x"] - bbox["min_x"], 3),
|
|
72
|
+
"height": round(bbox["max_y"] - bbox["min_y"], 3),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
"bbox": bbox,
|
|
77
|
+
"centroid": centroid,
|
|
78
|
+
"size": size,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
except (ValueError, IndexError):
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _generate_drc_recommendations(
|
|
86
|
+
total_violations: int,
|
|
87
|
+
violations_by_category: list[dict[str, Any]],
|
|
88
|
+
cells: list[str],
|
|
89
|
+
) -> list[str]:
|
|
90
|
+
"""Generate actionable recommendations based on DRC results.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
total_violations: Total number of violations
|
|
94
|
+
violations_by_category: List of violation categories with counts
|
|
95
|
+
cells: List of cells checked
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List of recommendation strings
|
|
99
|
+
"""
|
|
100
|
+
if total_violations == 0:
|
|
101
|
+
return ["Design passes all DRC checks"]
|
|
102
|
+
|
|
103
|
+
recommendations = []
|
|
104
|
+
|
|
105
|
+
if violations_by_category:
|
|
106
|
+
top_category = violations_by_category[0]
|
|
107
|
+
if top_category["count"] > 10:
|
|
108
|
+
recommendations.append(
|
|
109
|
+
f"Focus on {top_category['category']} violations "
|
|
110
|
+
f"({top_category['count']:,} occurrences)"
|
|
111
|
+
)
|
|
112
|
+
elif top_category["count"] > 1:
|
|
113
|
+
recommendations.append(
|
|
114
|
+
f"Check {top_category['category']} rules in {', '.join(cells)}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if len(violations_by_category) > 1:
|
|
118
|
+
recommendations.append(
|
|
119
|
+
f"Review {len(violations_by_category)} different DRC rule categories"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if total_violations > 100:
|
|
123
|
+
recommendations.append(
|
|
124
|
+
"Consider fixing systematic issues first to reduce violation count"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return recommendations
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class CheckDrcHandler(ToolHandler):
|
|
131
|
+
"""Handler for running DRC checks on GDS files.
|
|
132
|
+
|
|
133
|
+
Parses KLayout RDB XML and extracts actionable violation summary
|
|
134
|
+
without polygon coordinates or verbose metadata. Computes simplified
|
|
135
|
+
location data (bounding box, centroid, size) from polygon coordinates
|
|
136
|
+
to reduce token usage while preserving all violation details.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def name(self) -> str:
|
|
141
|
+
return "check_drc"
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def definition(self) -> Tool:
|
|
145
|
+
return Tool(
|
|
146
|
+
name="check_drc",
|
|
147
|
+
description=(
|
|
148
|
+
"Run a full DRC (Design Rule Check) on a GDS file. This uploads "
|
|
149
|
+
"the file to a remote DRC server and runs comprehensive design rule "
|
|
150
|
+
"verification for the specified PDK and process. Use this for "
|
|
151
|
+
"complete design rule validation. Returns XML results showing all "
|
|
152
|
+
"DRC violations."
|
|
153
|
+
),
|
|
154
|
+
inputSchema=add_project_param(
|
|
155
|
+
{
|
|
156
|
+
"type": "object",
|
|
157
|
+
"properties": {
|
|
158
|
+
"path": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"description": (
|
|
161
|
+
"Path to the GDS file to check. Can be absolute or "
|
|
162
|
+
"relative to the project directory."
|
|
163
|
+
),
|
|
164
|
+
},
|
|
165
|
+
"pdk": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"description": (
|
|
168
|
+
"PDK to use for the check. If not specified, uses the "
|
|
169
|
+
"default PDK from settings."
|
|
170
|
+
),
|
|
171
|
+
},
|
|
172
|
+
"process": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"description": (
|
|
175
|
+
"Process variant for DRC rules. If not specified, uses "
|
|
176
|
+
"the default process from settings."
|
|
177
|
+
),
|
|
178
|
+
},
|
|
179
|
+
"timeout": {
|
|
180
|
+
"type": "integer",
|
|
181
|
+
"description": (
|
|
182
|
+
"Timeout in seconds for the DRC check. If not specified, "
|
|
183
|
+
"uses the default timeout from settings."
|
|
184
|
+
),
|
|
185
|
+
},
|
|
186
|
+
"host": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"description": (
|
|
189
|
+
"API host for the DRC server. If not specified, uses "
|
|
190
|
+
"the default host from settings."
|
|
191
|
+
),
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
"required": ["path"],
|
|
195
|
+
}
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def mapping(self) -> EndpointMapping:
|
|
201
|
+
return EndpointMapping(method="POST", path="/api/check-drc")
|
|
202
|
+
|
|
203
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
204
|
+
"""Transform check_drc MCP args to FastAPI params.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
args: MCP tool arguments
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Dict with 'json_data' key for request body
|
|
211
|
+
"""
|
|
212
|
+
json_data: dict[str, Any] = {"path": args["path"]}
|
|
213
|
+
|
|
214
|
+
if "pdk" in args and args["pdk"]:
|
|
215
|
+
json_data["pdk"] = args["pdk"]
|
|
216
|
+
if "process" in args and args["process"]:
|
|
217
|
+
json_data["process"] = args["process"]
|
|
218
|
+
if "timeout" in args and args["timeout"]:
|
|
219
|
+
json_data["timeout"] = args["timeout"]
|
|
220
|
+
if "host" in args and args["host"]:
|
|
221
|
+
json_data["host"] = args["host"]
|
|
222
|
+
|
|
223
|
+
return {"json_data": json_data}
|
|
224
|
+
|
|
225
|
+
def transform_response(self, response: Any) -> dict[str, Any]:
|
|
226
|
+
"""Transform check_drc response to LLM-friendly format.
|
|
227
|
+
|
|
228
|
+
Parses KLayout RDB XML and extracts actionable violation summary
|
|
229
|
+
without polygon coordinates or verbose metadata. Computes simplified
|
|
230
|
+
location data (bounding box, centroid, size) from polygon coordinates
|
|
231
|
+
to reduce token usage by ~82% while preserving all violation details.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
response: FastAPI response (XML string or dict with 'content' key)
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Structured summary with all violations including location data,
|
|
238
|
+
or error dict if parsing fails
|
|
239
|
+
"""
|
|
240
|
+
try:
|
|
241
|
+
# Handle FastAPI error responses (e.g., file not found)
|
|
242
|
+
if isinstance(response, dict) and "detail" in response:
|
|
243
|
+
detail = response["detail"]
|
|
244
|
+
# Check for common error patterns and provide helpful messages
|
|
245
|
+
if "does not exist" in str(detail).lower():
|
|
246
|
+
return {
|
|
247
|
+
"error": "File not found",
|
|
248
|
+
"detail": detail,
|
|
249
|
+
"suggestion": (
|
|
250
|
+
"The GDS file does not exist. Please build the cell first "
|
|
251
|
+
"using 'build_cells' before running DRC."
|
|
252
|
+
),
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
"error": "DRC check failed",
|
|
256
|
+
"detail": detail,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if isinstance(response, dict) and "content" in response:
|
|
260
|
+
xml_str = response["content"]
|
|
261
|
+
elif isinstance(response, str):
|
|
262
|
+
xml_str = response
|
|
263
|
+
else:
|
|
264
|
+
return {
|
|
265
|
+
"error": f"Unexpected response type: {type(response).__name__}",
|
|
266
|
+
"suggestion": "Expected XML string or dict with 'content' key",
|
|
267
|
+
"response_preview": str(response)[:500],
|
|
268
|
+
}
|
|
269
|
+
except Exception as e:
|
|
270
|
+
return {
|
|
271
|
+
"error": f"Failed to extract XML from response: {e}",
|
|
272
|
+
"response_preview": str(response)[:500],
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
root = ET.fromstring(xml_str)
|
|
277
|
+
except ET.ParseError as e:
|
|
278
|
+
return {
|
|
279
|
+
"error": f"Failed to parse DRC XML: {e}",
|
|
280
|
+
"raw_preview": xml_str[:500],
|
|
281
|
+
"suggestion": "Check if DRC server returned valid XML",
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
categories_map = {}
|
|
285
|
+
for category in root.findall(".//categories/category"):
|
|
286
|
+
name_elem = category.find("name")
|
|
287
|
+
desc_elem = category.find("description")
|
|
288
|
+
if name_elem is not None and name_elem.text:
|
|
289
|
+
categories_map[name_elem.text] = (
|
|
290
|
+
desc_elem.text
|
|
291
|
+
if desc_elem is not None and desc_elem.text
|
|
292
|
+
else name_elem.text
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
cells = []
|
|
296
|
+
for cell in root.findall(".//cells/cell"):
|
|
297
|
+
name_elem = cell.find("name")
|
|
298
|
+
if name_elem is not None and name_elem.text:
|
|
299
|
+
cells.append(name_elem.text)
|
|
300
|
+
|
|
301
|
+
violations = []
|
|
302
|
+
violation_counts: dict[str, int] = {}
|
|
303
|
+
|
|
304
|
+
for item in root.findall(".//items/item"):
|
|
305
|
+
category_elem = item.find("category")
|
|
306
|
+
cell_elem = item.find("cell")
|
|
307
|
+
comment_elem = item.find("comment")
|
|
308
|
+
values_elem = item.find("values")
|
|
309
|
+
|
|
310
|
+
if category_elem is None or category_elem.text is None:
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
category = category_elem.text
|
|
314
|
+
cell = (
|
|
315
|
+
cell_elem.text
|
|
316
|
+
if cell_elem is not None and cell_elem.text
|
|
317
|
+
else "unknown"
|
|
318
|
+
)
|
|
319
|
+
description = (
|
|
320
|
+
comment_elem.text
|
|
321
|
+
if comment_elem is not None and comment_elem.text
|
|
322
|
+
else categories_map.get(category, category)
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
location = _parse_polygon_location(values_elem)
|
|
326
|
+
|
|
327
|
+
violation = {
|
|
328
|
+
"category": category,
|
|
329
|
+
"cell": cell,
|
|
330
|
+
"description": description,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if location is not None:
|
|
334
|
+
violation["location"] = location
|
|
335
|
+
elif values_elem is not None:
|
|
336
|
+
violation["location_warning"] = "Could not parse coordinates"
|
|
337
|
+
|
|
338
|
+
violations.append(violation)
|
|
339
|
+
|
|
340
|
+
violation_counts[category] = violation_counts.get(category, 0) + 1
|
|
341
|
+
|
|
342
|
+
total_violations = len(violations)
|
|
343
|
+
status = "PASSED" if total_violations == 0 else "FAILED"
|
|
344
|
+
|
|
345
|
+
summary = {
|
|
346
|
+
"total_violations": total_violations,
|
|
347
|
+
"total_categories": len(violation_counts),
|
|
348
|
+
"cells_checked": cells,
|
|
349
|
+
"status": status,
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if total_violations == 0:
|
|
353
|
+
summary["message"] = "No DRC violations found"
|
|
354
|
+
|
|
355
|
+
violations_by_category = [
|
|
356
|
+
{
|
|
357
|
+
"category": cat,
|
|
358
|
+
"description": categories_map.get(cat, cat),
|
|
359
|
+
"count": count,
|
|
360
|
+
}
|
|
361
|
+
for cat, count in sorted(
|
|
362
|
+
violation_counts.items(),
|
|
363
|
+
key=lambda x: x[1],
|
|
364
|
+
reverse=True,
|
|
365
|
+
)
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
recommendations = _generate_drc_recommendations(
|
|
369
|
+
total_violations,
|
|
370
|
+
violations_by_category,
|
|
371
|
+
cells,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
"summary": summary,
|
|
376
|
+
"violations_by_category": violations_by_category,
|
|
377
|
+
"violations": violations,
|
|
378
|
+
"recommendations": recommendations,
|
|
379
|
+
}
|
gfp_mcp/tools/freeze.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Freeze cell tool handler.
|
|
2
|
+
|
|
3
|
+
This module provides the handler for freezing parametric Python cells
|
|
4
|
+
as static schematic netlists.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from mcp.types import Tool
|
|
12
|
+
|
|
13
|
+
from .base import EndpointMapping, ToolHandler, add_project_param
|
|
14
|
+
|
|
15
|
+
__all__ = ["FreezeCellHandler"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FreezeCellHandler(ToolHandler):
|
|
19
|
+
"""Handler for freezing parametric cells.
|
|
20
|
+
|
|
21
|
+
Converts a gdsfactory component with specific parameters into
|
|
22
|
+
a fixed netlist representation in YAML format.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
return "freeze_cell"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def definition(self) -> Tool:
|
|
31
|
+
return Tool(
|
|
32
|
+
name="freeze_cell",
|
|
33
|
+
description=(
|
|
34
|
+
"Freeze a parametric Python cell as a static schematic netlist. "
|
|
35
|
+
"This converts a gdsfactory component with specific parameters into "
|
|
36
|
+
"a fixed netlist representation in YAML format. Useful for creating "
|
|
37
|
+
"versioned snapshots of parametric designs or preparing components "
|
|
38
|
+
"for simulation workflows."
|
|
39
|
+
),
|
|
40
|
+
inputSchema=add_project_param(
|
|
41
|
+
{
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"cell_name": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Name of the cell/component to freeze",
|
|
47
|
+
},
|
|
48
|
+
"kwargs": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"description": (
|
|
51
|
+
"Optional keyword arguments to pass to the component "
|
|
52
|
+
"factory. Use this to specify parameter values when "
|
|
53
|
+
"freezing the cell. Default is empty (use default params)."
|
|
54
|
+
),
|
|
55
|
+
"default": {},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
"required": ["cell_name"],
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def mapping(self) -> EndpointMapping:
|
|
65
|
+
return EndpointMapping(method="POST", path="/freeze/{cell_name}")
|
|
66
|
+
|
|
67
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
68
|
+
"""Transform freeze_cell MCP args to FastAPI params.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
args: MCP tool arguments
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Dict with 'path' and 'json_data' for the request
|
|
75
|
+
"""
|
|
76
|
+
cell_name = args["cell_name"]
|
|
77
|
+
kwargs = args.get("kwargs", {})
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
"path": f"/freeze/{cell_name}",
|
|
81
|
+
"json_data": kwargs, # httpx will JSON-encode this to a string
|
|
82
|
+
}
|
gfp_mcp/tools/lvs.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""LVS (Layout vs. Schematic) check tool handler.
|
|
2
|
+
|
|
3
|
+
This module provides the handler for running LVS verification
|
|
4
|
+
on cells against reference netlists.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from mcp.types import Tool
|
|
12
|
+
|
|
13
|
+
from .base import EndpointMapping, ToolHandler, add_project_param
|
|
14
|
+
|
|
15
|
+
__all__ = ["CheckLvsHandler"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CheckLvsHandler(ToolHandler):
|
|
19
|
+
"""Handler for running LVS verification.
|
|
20
|
+
|
|
21
|
+
Compares physical layout to schematic representation
|
|
22
|
+
to ensure they match.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
return "check_lvs"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def definition(self) -> Tool:
|
|
31
|
+
return Tool(
|
|
32
|
+
name="check_lvs",
|
|
33
|
+
description=(
|
|
34
|
+
"Run LVS (Layout vs. Schematic) verification on a cell against a "
|
|
35
|
+
"reference netlist. This compares the physical layout to the "
|
|
36
|
+
"schematic representation to ensure they match. Returns XML results "
|
|
37
|
+
"showing any mismatches between layout and schematic."
|
|
38
|
+
),
|
|
39
|
+
inputSchema=add_project_param(
|
|
40
|
+
{
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"cell": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "Name of the cell to verify",
|
|
46
|
+
},
|
|
47
|
+
"netpath": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": (
|
|
50
|
+
"Path to the reference netlist file to compare against"
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
"cellargs": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": (
|
|
56
|
+
"Optional cell arguments as a JSON string. "
|
|
57
|
+
"Default is empty string."
|
|
58
|
+
),
|
|
59
|
+
"default": "",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
"required": ["cell", "netpath"],
|
|
63
|
+
}
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def mapping(self) -> EndpointMapping:
|
|
69
|
+
return EndpointMapping(method="POST", path="/api/check-lvs")
|
|
70
|
+
|
|
71
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
72
|
+
"""Transform check_lvs MCP args to FastAPI params.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
args: MCP tool arguments
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Dict with 'json_data' key for request body
|
|
79
|
+
"""
|
|
80
|
+
return {
|
|
81
|
+
"json_data": {
|
|
82
|
+
"cell": args["cell"],
|
|
83
|
+
"netpath": args["netpath"],
|
|
84
|
+
"cellargs": args.get("cellargs", ""),
|
|
85
|
+
}
|
|
86
|
+
}
|
gfp_mcp/tools/pdk.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""PDK info tool handler.
|
|
2
|
+
|
|
3
|
+
This module provides the handler for getting information about
|
|
4
|
+
the current PDK (Process Design Kit) in use.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from mcp.types import Tool
|
|
10
|
+
|
|
11
|
+
from .base import EndpointMapping, ToolHandler, add_project_param
|
|
12
|
+
|
|
13
|
+
__all__ = ["GetPdkInfoHandler"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GetPdkInfoHandler(ToolHandler):
|
|
17
|
+
"""Handler for getting PDK information.
|
|
18
|
+
|
|
19
|
+
Returns metadata including PDK name, project name, project path,
|
|
20
|
+
server port, and version.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def name(self) -> str:
|
|
25
|
+
return "get_pdk_info"
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def definition(self) -> Tool:
|
|
29
|
+
return Tool(
|
|
30
|
+
name="get_pdk_info",
|
|
31
|
+
description=(
|
|
32
|
+
"Get information about the current PDK (Process Design Kit) in use. "
|
|
33
|
+
"Returns metadata including PDK name, project name, project path, "
|
|
34
|
+
"server port, and version. Use this to verify which PDK is active "
|
|
35
|
+
"and get project configuration details."
|
|
36
|
+
),
|
|
37
|
+
inputSchema=add_project_param(
|
|
38
|
+
{
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {},
|
|
41
|
+
}
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def mapping(self) -> EndpointMapping:
|
|
47
|
+
return EndpointMapping(method="GET", path="/info")
|
gfp_mcp/tools/port.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Port center coordinate tool handler.
|
|
2
|
+
|
|
3
|
+
This module provides the handler for getting port center coordinates
|
|
4
|
+
from component instances.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from mcp.types import Tool
|
|
12
|
+
|
|
13
|
+
from .base import EndpointMapping, ToolHandler, add_project_param
|
|
14
|
+
|
|
15
|
+
__all__ = ["GetPortCenterHandler"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GetPortCenterHandler(ToolHandler):
|
|
19
|
+
"""Handler for getting port center coordinates.
|
|
20
|
+
|
|
21
|
+
Returns the physical coordinates (x, y) of a specific port
|
|
22
|
+
in a component instance.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
return "get_port_center"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def definition(self) -> Tool:
|
|
31
|
+
return Tool(
|
|
32
|
+
name="get_port_center",
|
|
33
|
+
description=(
|
|
34
|
+
"Get the center coordinates (x, y) of a specific port in a component "
|
|
35
|
+
"instance. This is useful for positioning components, routing waveguides, "
|
|
36
|
+
"or analyzing layout geometry. Returns the physical coordinates in "
|
|
37
|
+
"microns."
|
|
38
|
+
),
|
|
39
|
+
inputSchema=add_project_param(
|
|
40
|
+
{
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"netlist": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": (
|
|
46
|
+
"Name of the component/netlist containing the instance"
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
"instance": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Name of the instance within the netlist",
|
|
52
|
+
},
|
|
53
|
+
"port": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "Name of the port to get coordinates for",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
"required": ["netlist", "instance", "port"],
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def mapping(self) -> EndpointMapping:
|
|
65
|
+
return EndpointMapping(method="GET", path="/api/port-center")
|
|
66
|
+
|
|
67
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
68
|
+
"""Transform get_port_center MCP args to FastAPI params.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
args: MCP tool arguments
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Dict with 'params' key for query parameters
|
|
75
|
+
"""
|
|
76
|
+
return {
|
|
77
|
+
"params": {
|
|
78
|
+
"netlist": args["netlist"],
|
|
79
|
+
"instance": args["instance"],
|
|
80
|
+
"port": args["port"],
|
|
81
|
+
}
|
|
82
|
+
}
|