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
mcp_standalone/server.py
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
"""MCP server implementation for GDSFactory+.
|
|
2
|
-
|
|
3
|
-
This module provides the main MCP server that exposes GDSFactory+ operations
|
|
4
|
-
as tools for AI assistants using the STDIO transport.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import asyncio
|
|
10
|
-
import json
|
|
11
|
-
import logging
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
from mcp.server import Server
|
|
15
|
-
from mcp.server.stdio import stdio_server
|
|
16
|
-
from mcp.types import (
|
|
17
|
-
Resource,
|
|
18
|
-
TextContent,
|
|
19
|
-
Tool,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
from .client import FastAPIClient
|
|
23
|
-
from .config import MCPConfig
|
|
24
|
-
from .mappings import get_mapping, transform_request, transform_response
|
|
25
|
-
from .registry import get_registry_path
|
|
26
|
-
from .resources import get_all_resources, get_resource_content
|
|
27
|
-
from .tools import get_all_tools
|
|
28
|
-
|
|
29
|
-
__all__ = ["create_server", "run_server", "main"]
|
|
30
|
-
|
|
31
|
-
logger = logging.getLogger(__name__)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def create_server(api_url: str | None = None) -> Server:
|
|
35
|
-
"""Create an MCP server instance.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
api_url: Optional FastAPI base URL (default from config)
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
Configured MCP Server instance
|
|
42
|
-
"""
|
|
43
|
-
# Create server instance
|
|
44
|
-
server = Server("gdsfactoryplus")
|
|
45
|
-
|
|
46
|
-
# Create HTTP client for FastAPI backend
|
|
47
|
-
client = FastAPIClient(api_url)
|
|
48
|
-
|
|
49
|
-
@server.list_tools()
|
|
50
|
-
async def list_tools() -> list[Tool]:
|
|
51
|
-
"""List all available MCP tools.
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
List of tool definitions
|
|
55
|
-
"""
|
|
56
|
-
tools = get_all_tools()
|
|
57
|
-
logger.info("Listing %d tools", len(tools))
|
|
58
|
-
return tools
|
|
59
|
-
|
|
60
|
-
@server.list_resources()
|
|
61
|
-
async def list_resources() -> list[Resource]:
|
|
62
|
-
"""List all available MCP resources.
|
|
63
|
-
|
|
64
|
-
Returns:
|
|
65
|
-
List of resource definitions
|
|
66
|
-
"""
|
|
67
|
-
resources = get_all_resources()
|
|
68
|
-
logger.info("Listing %d resources", len(resources))
|
|
69
|
-
return resources
|
|
70
|
-
|
|
71
|
-
@server.read_resource()
|
|
72
|
-
async def read_resource(uri: str) -> str:
|
|
73
|
-
"""Read a specific resource by URI.
|
|
74
|
-
|
|
75
|
-
Args:
|
|
76
|
-
uri: Resource URI
|
|
77
|
-
|
|
78
|
-
Returns:
|
|
79
|
-
Resource content as string
|
|
80
|
-
|
|
81
|
-
Raises:
|
|
82
|
-
ValueError: If resource URI is not found
|
|
83
|
-
"""
|
|
84
|
-
logger.info("Resource requested: %s", uri)
|
|
85
|
-
|
|
86
|
-
# Get resource content
|
|
87
|
-
content = get_resource_content(uri)
|
|
88
|
-
if content is None:
|
|
89
|
-
error_msg = f"Unknown resource URI: {uri}"
|
|
90
|
-
logger.error(error_msg)
|
|
91
|
-
raise ValueError(error_msg)
|
|
92
|
-
|
|
93
|
-
return content
|
|
94
|
-
|
|
95
|
-
@server.call_tool()
|
|
96
|
-
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]: # noqa: PLR0911
|
|
97
|
-
"""Call an MCP tool.
|
|
98
|
-
|
|
99
|
-
Args:
|
|
100
|
-
name: Tool name
|
|
101
|
-
arguments: Tool arguments
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
List of text content responses
|
|
105
|
-
"""
|
|
106
|
-
logger.info("Tool called: %s", name)
|
|
107
|
-
logger.debug("Arguments: %s", arguments)
|
|
108
|
-
|
|
109
|
-
# Handle special tools that don't require HTTP requests
|
|
110
|
-
if name == "list_projects":
|
|
111
|
-
try:
|
|
112
|
-
projects = client.list_projects()
|
|
113
|
-
return [
|
|
114
|
-
TextContent(
|
|
115
|
-
type="text",
|
|
116
|
-
text=json.dumps({"projects": projects}, indent=2),
|
|
117
|
-
)
|
|
118
|
-
]
|
|
119
|
-
except Exception as e:
|
|
120
|
-
error_msg = f"Failed to list projects: {e!s}"
|
|
121
|
-
logger.exception(error_msg)
|
|
122
|
-
return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
|
|
123
|
-
|
|
124
|
-
if name == "get_project_info":
|
|
125
|
-
try:
|
|
126
|
-
project = arguments.get("project")
|
|
127
|
-
if not project:
|
|
128
|
-
return [
|
|
129
|
-
TextContent(
|
|
130
|
-
type="text",
|
|
131
|
-
text=json.dumps({"error": "project parameter is required"}),
|
|
132
|
-
)
|
|
133
|
-
]
|
|
134
|
-
info = await client.get_project_info(project)
|
|
135
|
-
return [TextContent(type="text", text=json.dumps(info, indent=2))]
|
|
136
|
-
except Exception as e:
|
|
137
|
-
error_msg = f"Failed to get project info: {e!s}"
|
|
138
|
-
logger.exception(error_msg)
|
|
139
|
-
return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
|
|
140
|
-
|
|
141
|
-
# Get the endpoint mapping
|
|
142
|
-
mapping = get_mapping(name)
|
|
143
|
-
if mapping is None:
|
|
144
|
-
error_msg = f"Unknown tool: {name}"
|
|
145
|
-
logger.error(error_msg)
|
|
146
|
-
return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
|
|
147
|
-
|
|
148
|
-
try:
|
|
149
|
-
# Extract project parameter (optional)
|
|
150
|
-
project = arguments.get("project")
|
|
151
|
-
|
|
152
|
-
# Transform MCP arguments to HTTP request parameters
|
|
153
|
-
transformed = transform_request(name, arguments)
|
|
154
|
-
logger.debug("Transformed request: %s", transformed)
|
|
155
|
-
|
|
156
|
-
# Extract request parameters
|
|
157
|
-
method = mapping.method
|
|
158
|
-
path = transformed.get("path", mapping.path)
|
|
159
|
-
params = transformed.get("params")
|
|
160
|
-
json_data = transformed.get("json_data")
|
|
161
|
-
data = transformed.get("data")
|
|
162
|
-
|
|
163
|
-
# Make HTTP request to FastAPI backend (with optional project routing)
|
|
164
|
-
response = await client.request(
|
|
165
|
-
method=method,
|
|
166
|
-
path=path,
|
|
167
|
-
params=params,
|
|
168
|
-
json_data=json_data,
|
|
169
|
-
data=data,
|
|
170
|
-
project=project,
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
# Transform response to MCP format
|
|
174
|
-
result = transform_response(name, response)
|
|
175
|
-
logger.debug("Tool result: %s", result)
|
|
176
|
-
|
|
177
|
-
# Return as text content
|
|
178
|
-
return [
|
|
179
|
-
TextContent(
|
|
180
|
-
type="text",
|
|
181
|
-
text=json.dumps(result, indent=2),
|
|
182
|
-
)
|
|
183
|
-
]
|
|
184
|
-
|
|
185
|
-
except Exception as e:
|
|
186
|
-
error_msg = f"Tool execution failed: {e!s}"
|
|
187
|
-
logger.exception(error_msg)
|
|
188
|
-
return [
|
|
189
|
-
TextContent(
|
|
190
|
-
type="text",
|
|
191
|
-
text=json.dumps({"error": error_msg}),
|
|
192
|
-
)
|
|
193
|
-
]
|
|
194
|
-
|
|
195
|
-
# Store client reference for cleanup
|
|
196
|
-
server._http_client = client # type: ignore[attr-defined] # noqa: SLF001
|
|
197
|
-
|
|
198
|
-
return server
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def _log_server_discovery(client: FastAPIClient) -> None:
|
|
202
|
-
"""Log discovered GDSFactory+ servers at startup."""
|
|
203
|
-
try:
|
|
204
|
-
projects = client.list_projects()
|
|
205
|
-
if projects:
|
|
206
|
-
logger.info("Discovered %d GDSFactory+ server(s):", len(projects))
|
|
207
|
-
for project in projects[:3]: # Show first 3
|
|
208
|
-
logger.info(
|
|
209
|
-
" - %s (port %d, PDK: %s)",
|
|
210
|
-
project["project_name"],
|
|
211
|
-
project["port"],
|
|
212
|
-
project.get("pdk", "unknown"),
|
|
213
|
-
)
|
|
214
|
-
if len(projects) > 3:
|
|
215
|
-
logger.info(" ... and %d more", len(projects) - 3)
|
|
216
|
-
else:
|
|
217
|
-
logger.warning(
|
|
218
|
-
"No GDSFactory+ servers found. Start a server with: "
|
|
219
|
-
"gfp serve --port 8787"
|
|
220
|
-
)
|
|
221
|
-
logger.info("Registry location: %s", get_registry_path())
|
|
222
|
-
except Exception as e:
|
|
223
|
-
logger.warning("Could not read server registry: %s", e)
|
|
224
|
-
logger.info(
|
|
225
|
-
"If GDSFactory+ is running, ensure registry is accessible at: %s",
|
|
226
|
-
get_registry_path(),
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
async def run_server(api_url: str | None = None) -> None:
|
|
231
|
-
"""Run the MCP server with STDIO transport.
|
|
232
|
-
|
|
233
|
-
Args:
|
|
234
|
-
api_url: Optional FastAPI base URL (default from config)
|
|
235
|
-
"""
|
|
236
|
-
# Configure logging
|
|
237
|
-
log_level = logging.DEBUG if MCPConfig.DEBUG else logging.INFO
|
|
238
|
-
logging.basicConfig(
|
|
239
|
-
level=log_level,
|
|
240
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
logger.info("Starting GDSFactory+ MCP server")
|
|
244
|
-
logger.info("FastAPI base URL: %s", MCPConfig.get_api_url(api_url))
|
|
245
|
-
logger.info("Timeout: %ds", MCPConfig.get_timeout())
|
|
246
|
-
|
|
247
|
-
# Create server
|
|
248
|
-
server = create_server(api_url)
|
|
249
|
-
client: FastAPIClient = server._http_client # type: ignore[attr-defined] # noqa: SLF001
|
|
250
|
-
|
|
251
|
-
try:
|
|
252
|
-
# Start HTTP client
|
|
253
|
-
await client.start()
|
|
254
|
-
|
|
255
|
-
# Discover available servers
|
|
256
|
-
_log_server_discovery(client)
|
|
257
|
-
|
|
258
|
-
# Run server with STDIO transport
|
|
259
|
-
async with stdio_server() as (read_stream, write_stream):
|
|
260
|
-
logger.info("MCP server ready (STDIO transport)")
|
|
261
|
-
await server.run(
|
|
262
|
-
read_stream,
|
|
263
|
-
write_stream,
|
|
264
|
-
server.create_initialization_options(),
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
except KeyboardInterrupt:
|
|
268
|
-
logger.info("Shutting down MCP server (keyboard interrupt)")
|
|
269
|
-
except Exception:
|
|
270
|
-
logger.exception("MCP server error")
|
|
271
|
-
raise
|
|
272
|
-
finally:
|
|
273
|
-
# Cleanup
|
|
274
|
-
await client.close()
|
|
275
|
-
logger.info("MCP server stopped")
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def main(api_url: str | None = None) -> None:
|
|
279
|
-
"""Main entry point for MCP server.
|
|
280
|
-
|
|
281
|
-
Args:
|
|
282
|
-
api_url: Optional FastAPI base URL (default from config)
|
|
283
|
-
"""
|
|
284
|
-
try:
|
|
285
|
-
asyncio.run(run_server(api_url))
|
|
286
|
-
except KeyboardInterrupt:
|
|
287
|
-
pass
|
|
288
|
-
except Exception:
|
|
289
|
-
logger.exception("Fatal error")
|
|
290
|
-
raise
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if __name__ == "__main__":
|
|
294
|
-
main()
|