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/base.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Base classes for tool handlers.
|
|
2
|
+
|
|
3
|
+
This module provides the foundational classes for the MCP tool handler
|
|
4
|
+
architecture, including the abstract ToolHandler base class and
|
|
5
|
+
EndpointMapping for HTTP routing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
from mcp.types import ImageContent, TextContent, Tool
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from ..client import FastAPIClient
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"EndpointMapping",
|
|
23
|
+
"ToolHandler",
|
|
24
|
+
"PROJECT_PARAM_SCHEMA",
|
|
25
|
+
"add_project_param",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# Common schema components for project routing
|
|
31
|
+
PROJECT_PARAM_SCHEMA = {
|
|
32
|
+
"project": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": (
|
|
35
|
+
"Optional project name or path to route this request to a specific server. "
|
|
36
|
+
"If not provided, uses the first available server from the registry or "
|
|
37
|
+
"GFP_API_URL if set. Use list_projects to see available projects."
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def add_project_param(schema: dict) -> dict:
|
|
44
|
+
"""Add optional project parameter to a tool schema.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
schema: Tool input schema dict
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Schema with project parameter added to properties
|
|
51
|
+
"""
|
|
52
|
+
if "properties" not in schema:
|
|
53
|
+
schema["properties"] = {}
|
|
54
|
+
schema["properties"].update(PROJECT_PARAM_SCHEMA)
|
|
55
|
+
return schema
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class EndpointMapping:
|
|
60
|
+
"""Configuration for mapping an MCP tool to a FastAPI endpoint.
|
|
61
|
+
|
|
62
|
+
This class defines the HTTP method and path for a tool's backend endpoint.
|
|
63
|
+
Unlike the previous implementation in mappings.py, request and response
|
|
64
|
+
transformers are now methods on the ToolHandler class.
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
method: HTTP method (GET, POST, etc.)
|
|
68
|
+
path: API endpoint path (e.g., "/api/build-cells")
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
method: str
|
|
72
|
+
path: str
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ToolHandler(ABC):
|
|
76
|
+
"""Abstract base class for MCP tool handlers.
|
|
77
|
+
|
|
78
|
+
Each tool handler encapsulates:
|
|
79
|
+
- The MCP Tool definition (name, description, input schema)
|
|
80
|
+
- The HTTP endpoint mapping (method, path)
|
|
81
|
+
- Request transformation (MCP args -> HTTP params/body)
|
|
82
|
+
- Response transformation (HTTP response -> MCP format)
|
|
83
|
+
- Execution logic (handle method)
|
|
84
|
+
|
|
85
|
+
Subclasses must implement the `name` and `definition` properties.
|
|
86
|
+
Other methods have sensible defaults but can be overridden.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def name(self) -> str:
|
|
92
|
+
"""Tool name identifier.
|
|
93
|
+
|
|
94
|
+
This must match the name in the Tool definition and is used
|
|
95
|
+
for routing in the server's call_tool handler.
|
|
96
|
+
"""
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def definition(self) -> Tool:
|
|
102
|
+
"""MCP Tool definition.
|
|
103
|
+
|
|
104
|
+
Returns the complete Tool object with name, description,
|
|
105
|
+
and input schema for the MCP protocol.
|
|
106
|
+
"""
|
|
107
|
+
...
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def mapping(self) -> EndpointMapping | None:
|
|
111
|
+
"""Endpoint mapping for HTTP-backed tools.
|
|
112
|
+
|
|
113
|
+
Returns None for registry-only tools (like list_projects)
|
|
114
|
+
that don't make HTTP requests to the backend.
|
|
115
|
+
"""
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
119
|
+
"""Transform MCP tool arguments to HTTP request parameters.
|
|
120
|
+
|
|
121
|
+
Override this method to customize how tool arguments are
|
|
122
|
+
converted to HTTP request format.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
args: MCP tool arguments from the client
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Dict that may contain:
|
|
129
|
+
- "params": Query parameters for GET requests
|
|
130
|
+
- "json_data": JSON body for POST requests
|
|
131
|
+
- "data": Form data for POST requests
|
|
132
|
+
- "path": Dynamic path override (e.g., "/api/cell/{name}")
|
|
133
|
+
- "fallback_path": Alternative endpoint for 404 fallback
|
|
134
|
+
- "fallback_json_data": Alternative body for fallback request
|
|
135
|
+
"""
|
|
136
|
+
return {}
|
|
137
|
+
|
|
138
|
+
def transform_response(self, response: Any) -> Any:
|
|
139
|
+
"""Transform HTTP response to MCP-friendly format.
|
|
140
|
+
|
|
141
|
+
Override this method to customize how backend responses are
|
|
142
|
+
formatted for the AI assistant.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
response: Raw response from the FastAPI backend
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Transformed response (typically a dict or the original response)
|
|
149
|
+
"""
|
|
150
|
+
return response
|
|
151
|
+
|
|
152
|
+
async def handle(
|
|
153
|
+
self,
|
|
154
|
+
arguments: dict[str, Any],
|
|
155
|
+
client: FastAPIClient,
|
|
156
|
+
) -> list[TextContent | ImageContent]:
|
|
157
|
+
"""Execute the tool and return results.
|
|
158
|
+
|
|
159
|
+
This is the main entry point called by the server's call_tool handler.
|
|
160
|
+
The default implementation uses the endpoint mapping and transformers.
|
|
161
|
+
Override for custom execution logic (e.g., registry-only tools).
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
arguments: MCP tool arguments from the client
|
|
165
|
+
client: FastAPI client for making HTTP requests
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
List of TextContent and/or ImageContent responses
|
|
169
|
+
"""
|
|
170
|
+
import httpx
|
|
171
|
+
|
|
172
|
+
mapping = self.mapping
|
|
173
|
+
if mapping is None:
|
|
174
|
+
error_msg = f"Tool {self.name} has no endpoint mapping"
|
|
175
|
+
logger.error(error_msg)
|
|
176
|
+
return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
project = arguments.get("project")
|
|
180
|
+
transformed = self.transform_request(arguments)
|
|
181
|
+
|
|
182
|
+
method = mapping.method
|
|
183
|
+
path = transformed.get("path", mapping.path)
|
|
184
|
+
params = transformed.get("params")
|
|
185
|
+
json_data = transformed.get("json_data")
|
|
186
|
+
data = transformed.get("data")
|
|
187
|
+
fallback_path = transformed.get("fallback_path")
|
|
188
|
+
fallback_json_data = transformed.get("fallback_json_data")
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
response = await client.request(
|
|
192
|
+
method=method,
|
|
193
|
+
path=path,
|
|
194
|
+
params=params,
|
|
195
|
+
json_data=json_data,
|
|
196
|
+
data=data,
|
|
197
|
+
project=project,
|
|
198
|
+
)
|
|
199
|
+
except httpx.HTTPStatusError as e:
|
|
200
|
+
if e.response.status_code == 404 and fallback_path:
|
|
201
|
+
logger.info(
|
|
202
|
+
"Endpoint %s returned 404, trying fallback: %s",
|
|
203
|
+
path,
|
|
204
|
+
fallback_path,
|
|
205
|
+
)
|
|
206
|
+
response = await client.request(
|
|
207
|
+
method=method,
|
|
208
|
+
path=fallback_path,
|
|
209
|
+
params=params,
|
|
210
|
+
json_data=fallback_json_data,
|
|
211
|
+
data=data,
|
|
212
|
+
project=project,
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
raise
|
|
216
|
+
|
|
217
|
+
result = self.transform_response(response)
|
|
218
|
+
logger.debug("Tool %s result: %s", self.name, result)
|
|
219
|
+
|
|
220
|
+
return [
|
|
221
|
+
TextContent(
|
|
222
|
+
type="text",
|
|
223
|
+
text=json.dumps(result, indent=2),
|
|
224
|
+
)
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
error_msg = f"Tool execution failed: {e!s}"
|
|
229
|
+
logger.exception(error_msg)
|
|
230
|
+
return [
|
|
231
|
+
TextContent(
|
|
232
|
+
type="text",
|
|
233
|
+
text=json.dumps({"error": error_msg}),
|
|
234
|
+
)
|
|
235
|
+
]
|
gfp_mcp/tools/bbox.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Bounding box generation tool handler.
|
|
2
|
+
|
|
3
|
+
This module provides the handler for generating bounding box
|
|
4
|
+
GDS files from input GDS files.
|
|
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__ = ["GenerateBboxHandler"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GenerateBboxHandler(ToolHandler):
|
|
19
|
+
"""Handler for generating bounding box GDS files.
|
|
20
|
+
|
|
21
|
+
Creates a simplified version of the layout with only a bounding box
|
|
22
|
+
on specified layers.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
return "generate_bbox"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def definition(self) -> Tool:
|
|
31
|
+
return Tool(
|
|
32
|
+
name="generate_bbox",
|
|
33
|
+
description=(
|
|
34
|
+
"Generate a bounding box GDS file from an input GDS. This creates "
|
|
35
|
+
"a simplified version of the layout with only a bounding box on "
|
|
36
|
+
"specified layers. Useful for creating abstract views, floorplanning, "
|
|
37
|
+
"or hierarchical design. Can optionally preserve specific layers and "
|
|
38
|
+
"ports."
|
|
39
|
+
),
|
|
40
|
+
inputSchema=add_project_param(
|
|
41
|
+
{
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"path": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": (
|
|
47
|
+
"Path to the input GDS file. Can be absolute or relative "
|
|
48
|
+
"to the project directory."
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
"outpath": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": (
|
|
54
|
+
"Output path for the bounding box GDS. If not specified, "
|
|
55
|
+
"uses the input filename with '-bbox' suffix."
|
|
56
|
+
),
|
|
57
|
+
"default": "",
|
|
58
|
+
},
|
|
59
|
+
"layers_to_keep": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"items": {"type": "string"},
|
|
62
|
+
"description": (
|
|
63
|
+
"List of layer names to preserve in the output. "
|
|
64
|
+
"Other layers will be replaced by the bounding box."
|
|
65
|
+
),
|
|
66
|
+
"default": [],
|
|
67
|
+
},
|
|
68
|
+
"bbox_layer": {
|
|
69
|
+
"type": "array",
|
|
70
|
+
"items": {"type": "integer"},
|
|
71
|
+
"description": (
|
|
72
|
+
"Layer (as [layer, datatype]) to use for the bounding box. "
|
|
73
|
+
"Default is [99, 0]."
|
|
74
|
+
),
|
|
75
|
+
"default": [99, 0],
|
|
76
|
+
},
|
|
77
|
+
"ignore_ports": {
|
|
78
|
+
"type": "boolean",
|
|
79
|
+
"description": (
|
|
80
|
+
"If true, do not include ports in the output. "
|
|
81
|
+
"Default is false."
|
|
82
|
+
),
|
|
83
|
+
"default": False,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
"required": ["path"],
|
|
87
|
+
}
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def mapping(self) -> EndpointMapping:
|
|
93
|
+
return EndpointMapping(method="POST", path="/api/bbox")
|
|
94
|
+
|
|
95
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
96
|
+
"""Transform generate_bbox MCP args to FastAPI params.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
args: MCP tool arguments
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dict with 'json_data' key for request body
|
|
103
|
+
"""
|
|
104
|
+
json_data: dict[str, Any] = {"path": args["path"]}
|
|
105
|
+
|
|
106
|
+
if "outpath" in args and args["outpath"]:
|
|
107
|
+
json_data["outpath"] = args["outpath"]
|
|
108
|
+
if "layers_to_keep" in args and args["layers_to_keep"]:
|
|
109
|
+
json_data["layers_to_keep"] = args["layers_to_keep"]
|
|
110
|
+
if "bbox_layer" in args and args["bbox_layer"]:
|
|
111
|
+
json_data["bbox_layer"] = args["bbox_layer"]
|
|
112
|
+
if "ignore_ports" in args:
|
|
113
|
+
json_data["ignore_ports"] = args["ignore_ports"]
|
|
114
|
+
|
|
115
|
+
return {"json_data": json_data}
|
gfp_mcp/tools/build.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Build cells tool handler.
|
|
2
|
+
|
|
3
|
+
This module provides the handler for building GDS cells, including
|
|
4
|
+
optional PNG image rendering for visualization.
|
|
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 ImageContent, TextContent, Tool
|
|
14
|
+
|
|
15
|
+
from ..render import HAS_KLAYOUT, render_built_cells
|
|
16
|
+
from .base import EndpointMapping, ToolHandler, add_project_param
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from ..client import FastAPIClient
|
|
20
|
+
|
|
21
|
+
__all__ = ["BuildCellsHandler"]
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BuildCellsHandler(ToolHandler):
|
|
27
|
+
"""Handler for building GDS cells.
|
|
28
|
+
|
|
29
|
+
This handler builds one or more GDS cells and optionally renders
|
|
30
|
+
specified cells to PNG images for visualization.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def name(self) -> str:
|
|
35
|
+
return "build_cells"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def definition(self) -> Tool:
|
|
39
|
+
return Tool(
|
|
40
|
+
name="build_cells",
|
|
41
|
+
description=(
|
|
42
|
+
"Build one or more GDS cells by name. This creates the physical layout "
|
|
43
|
+
"files (.gds) for photonic components. Pass a list of cell names to build. "
|
|
44
|
+
"For a single cell, pass a list with one element. All cells are built "
|
|
45
|
+
"in the background and saved to the project build directory. "
|
|
46
|
+
"Use the optional 'visualize' parameter to specify which cells should be "
|
|
47
|
+
"rendered to PNG images and returned in the response."
|
|
48
|
+
),
|
|
49
|
+
inputSchema=add_project_param(
|
|
50
|
+
{
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
"names": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {"type": "string"},
|
|
56
|
+
"description": "List of cell/component names to build (can be a single-item list)",
|
|
57
|
+
},
|
|
58
|
+
"visualize": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": {"type": "string"},
|
|
61
|
+
"description": (
|
|
62
|
+
"Optional list of cell names to render as PNG images. "
|
|
63
|
+
"Only cells in this list will be visualized. If not provided, "
|
|
64
|
+
"no images are returned. Use this to selectively view only "
|
|
65
|
+
"the cells you need to inspect."
|
|
66
|
+
),
|
|
67
|
+
},
|
|
68
|
+
"with_metadata": {
|
|
69
|
+
"type": "boolean",
|
|
70
|
+
"description": (
|
|
71
|
+
"Include metadata in the GDS files (default: true)"
|
|
72
|
+
),
|
|
73
|
+
"default": True,
|
|
74
|
+
},
|
|
75
|
+
"register": {
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"description": (
|
|
78
|
+
"Re-register the cells in the KLayout cache (default: true)"
|
|
79
|
+
),
|
|
80
|
+
"default": True,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
"required": ["names"],
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def mapping(self) -> EndpointMapping:
|
|
90
|
+
return EndpointMapping(method="POST", path="/api/build-cells")
|
|
91
|
+
|
|
92
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
93
|
+
"""Transform build_cells MCP args to FastAPI params.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
args: MCP tool arguments
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Dict with 'params' key for query parameters and 'json_data' for body
|
|
100
|
+
"""
|
|
101
|
+
return {
|
|
102
|
+
"params": {
|
|
103
|
+
"with_metadata": args.get("with_metadata", True),
|
|
104
|
+
"register": args.get("register", True),
|
|
105
|
+
},
|
|
106
|
+
"json_data": args["names"],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async def handle(
|
|
110
|
+
self,
|
|
111
|
+
arguments: dict[str, Any],
|
|
112
|
+
client: FastAPIClient,
|
|
113
|
+
) -> list[TextContent | ImageContent]:
|
|
114
|
+
"""Build cells and optionally render images.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
arguments: MCP tool arguments
|
|
118
|
+
client: FastAPI client for making requests
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of TextContent with build results and optional ImageContent
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
project = arguments.get("project")
|
|
125
|
+
transformed = self.transform_request(arguments)
|
|
126
|
+
|
|
127
|
+
response = await client.request(
|
|
128
|
+
method=self.mapping.method,
|
|
129
|
+
path=self.mapping.path,
|
|
130
|
+
params=transformed.get("params"),
|
|
131
|
+
json_data=transformed.get("json_data"),
|
|
132
|
+
project=project,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
logger.debug("Build cells result: %s", response)
|
|
136
|
+
|
|
137
|
+
results: list[TextContent | ImageContent] = [
|
|
138
|
+
TextContent(
|
|
139
|
+
type="text",
|
|
140
|
+
text=json.dumps(response, indent=2),
|
|
141
|
+
)
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
# Render images if klayout is available and visualize is specified
|
|
145
|
+
visualize = arguments.get("visualize", [])
|
|
146
|
+
if visualize and HAS_KLAYOUT:
|
|
147
|
+
results.extend(await render_built_cells(visualize, project))
|
|
148
|
+
|
|
149
|
+
return results
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
error_msg = f"Tool execution failed: {e!s}"
|
|
153
|
+
logger.exception(error_msg)
|
|
154
|
+
return [
|
|
155
|
+
TextContent(
|
|
156
|
+
type="text",
|
|
157
|
+
text=json.dumps({"error": error_msg}),
|
|
158
|
+
)
|
|
159
|
+
]
|
gfp_mcp/tools/cells.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Cell listing and info tool handlers.
|
|
2
|
+
|
|
3
|
+
These tools provide information about available cells/components
|
|
4
|
+
in the current PDK.
|
|
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__ = ["ListCellsHandler", "GetCellInfoHandler"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ListCellsHandler(ToolHandler):
|
|
19
|
+
"""Handler for listing all available cells/components."""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def name(self) -> str:
|
|
23
|
+
return "list_cells"
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def definition(self) -> Tool:
|
|
27
|
+
return Tool(
|
|
28
|
+
name="list_cells",
|
|
29
|
+
description=(
|
|
30
|
+
"List all available cells/components that can be built. Returns "
|
|
31
|
+
"the names of all registered component factories in the current PDK. "
|
|
32
|
+
"Use this to discover what components are available before building."
|
|
33
|
+
),
|
|
34
|
+
inputSchema=add_project_param(
|
|
35
|
+
{
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {},
|
|
38
|
+
}
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def mapping(self) -> EndpointMapping:
|
|
44
|
+
return EndpointMapping(method="GET", path="/api/cells")
|
|
45
|
+
|
|
46
|
+
def transform_response(self, response: Any) -> dict[str, Any]:
|
|
47
|
+
"""Transform list_cells response to MCP format.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
response: FastAPI response (list of cell names)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Formatted response with cell names
|
|
54
|
+
"""
|
|
55
|
+
if isinstance(response, list):
|
|
56
|
+
return {"cells": response, "count": len(response)}
|
|
57
|
+
return response
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class GetCellInfoHandler(ToolHandler):
|
|
61
|
+
"""Handler for getting detailed info about a specific cell."""
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def name(self) -> str:
|
|
65
|
+
return "get_cell_info"
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def definition(self) -> Tool:
|
|
69
|
+
return Tool(
|
|
70
|
+
name="get_cell_info",
|
|
71
|
+
description=(
|
|
72
|
+
"Get detailed information about a specific cell/component. Returns "
|
|
73
|
+
"metadata including the source file, parameters, and other details "
|
|
74
|
+
"about the component factory."
|
|
75
|
+
),
|
|
76
|
+
inputSchema=add_project_param(
|
|
77
|
+
{
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"name": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "Name of the cell/component to get info about",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
"required": ["name"],
|
|
86
|
+
}
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def mapping(self) -> EndpointMapping:
|
|
92
|
+
return EndpointMapping(method="GET", path="/api/cell-info")
|
|
93
|
+
|
|
94
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
95
|
+
"""Transform get_cell_info MCP args to FastAPI params.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
args: MCP tool arguments
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Dict with 'params' key for query parameters
|
|
102
|
+
"""
|
|
103
|
+
return {"params": {"name": args["name"]}}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Connectivity check tool handler.
|
|
2
|
+
|
|
3
|
+
This module provides the handler for running local connectivity
|
|
4
|
+
checks on GDS files.
|
|
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__ = ["CheckConnectivityHandler"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CheckConnectivityHandler(ToolHandler):
|
|
19
|
+
"""Handler for running connectivity checks on GDS files.
|
|
20
|
+
|
|
21
|
+
This is a fast, local check that verifies all layers are properly
|
|
22
|
+
connected and identifies any connectivity violations.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
return "check_connectivity"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def definition(self) -> Tool:
|
|
31
|
+
return Tool(
|
|
32
|
+
name="check_connectivity",
|
|
33
|
+
description=(
|
|
34
|
+
"Run a local connectivity check on a GDS file. This verifies that "
|
|
35
|
+
"all layers are properly connected and identifies any connectivity "
|
|
36
|
+
"violations. This is a fast, local check (does not require uploading "
|
|
37
|
+
"to a remote server). Use this to quickly check for disconnected "
|
|
38
|
+
"components. Returns XML results showing connectivity issues."
|
|
39
|
+
),
|
|
40
|
+
inputSchema=add_project_param(
|
|
41
|
+
{
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"path": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": (
|
|
47
|
+
"Path to the GDS file to check. Can be absolute or "
|
|
48
|
+
"relative to the project directory."
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
"required": ["path"],
|
|
53
|
+
}
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def mapping(self) -> EndpointMapping:
|
|
59
|
+
return EndpointMapping(method="POST", path="/api/check-connectivity")
|
|
60
|
+
|
|
61
|
+
def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
62
|
+
"""Transform check_connectivity MCP args to FastAPI params.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
args: MCP tool arguments
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dict with 'json_data' key for request body
|
|
69
|
+
"""
|
|
70
|
+
return {"json_data": {"path": args["path"]}}
|