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/server.py DELETED
@@ -1,282 +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
- server = Server("gdsfactoryplus")
44
-
45
- client = FastAPIClient(api_url)
46
-
47
- @server.list_tools()
48
- async def list_tools() -> list[Tool]:
49
- """List all available MCP tools.
50
-
51
- Returns:
52
- List of tool definitions
53
- """
54
- tools = get_all_tools()
55
- logger.info("Listing %d tools", len(tools))
56
- return tools
57
-
58
- @server.list_resources()
59
- async def list_resources() -> list[Resource]:
60
- """List all available MCP resources.
61
-
62
- Returns:
63
- List of resource definitions
64
- """
65
- resources = get_all_resources()
66
- logger.info("Listing %d resources", len(resources))
67
- return resources
68
-
69
- @server.read_resource()
70
- async def read_resource(uri: str) -> str:
71
- """Read a specific resource by URI.
72
-
73
- Args:
74
- uri: Resource URI
75
-
76
- Returns:
77
- Resource content as string
78
-
79
- Raises:
80
- ValueError: If resource URI is not found
81
- """
82
- logger.info("Resource requested: %s", uri)
83
-
84
- content = get_resource_content(uri)
85
- if content is None:
86
- error_msg = f"Unknown resource URI: {uri}"
87
- logger.error(error_msg)
88
- raise ValueError(error_msg)
89
-
90
- return content
91
-
92
- @server.call_tool()
93
- async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]: # noqa: PLR0911
94
- """Call an MCP tool.
95
-
96
- Args:
97
- name: Tool name
98
- arguments: Tool arguments
99
-
100
- Returns:
101
- List of text content responses
102
- """
103
- logger.info("Tool called: %s", name)
104
- logger.debug("Arguments: %s", arguments)
105
-
106
- if name == "list_projects":
107
- try:
108
- projects = client.list_projects()
109
- return [
110
- TextContent(
111
- type="text",
112
- text=json.dumps({"projects": projects}, indent=2),
113
- )
114
- ]
115
- except Exception as e:
116
- error_msg = f"Failed to list projects: {e!s}"
117
- logger.exception(error_msg)
118
- return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
119
-
120
- if name == "get_project_info":
121
- try:
122
- project = arguments.get("project")
123
- if not project:
124
- return [
125
- TextContent(
126
- type="text",
127
- text=json.dumps({"error": "project parameter is required"}),
128
- )
129
- ]
130
- info = await client.get_project_info(project)
131
- return [TextContent(type="text", text=json.dumps(info, indent=2))]
132
- except Exception as e:
133
- error_msg = f"Failed to get project info: {e!s}"
134
- logger.exception(error_msg)
135
- return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
136
-
137
- mapping = get_mapping(name)
138
- if mapping is None:
139
- error_msg = f"Unknown tool: {name}"
140
- logger.error(error_msg)
141
- return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
142
-
143
- try:
144
- project = arguments.get("project")
145
-
146
- transformed = transform_request(name, arguments)
147
- logger.debug("Transformed request: %s", transformed)
148
-
149
- method = mapping.method
150
- path = transformed.get("path", mapping.path)
151
- params = transformed.get("params")
152
- json_data = transformed.get("json_data")
153
- data = transformed.get("data")
154
-
155
- response = await client.request(
156
- method=method,
157
- path=path,
158
- params=params,
159
- json_data=json_data,
160
- data=data,
161
- project=project,
162
- )
163
-
164
- result = transform_response(name, response)
165
- logger.debug("Tool result: %s", result)
166
-
167
- return [
168
- TextContent(
169
- type="text",
170
- text=json.dumps(result, indent=2),
171
- )
172
- ]
173
-
174
- except Exception as e:
175
- error_msg = f"Tool execution failed: {e!s}"
176
- logger.exception(error_msg)
177
- return [
178
- TextContent(
179
- type="text",
180
- text=json.dumps({"error": error_msg}),
181
- )
182
- ]
183
-
184
- server._http_client = client # type: ignore[attr-defined] # noqa: SLF001
185
-
186
- return server
187
-
188
-
189
- def _log_server_discovery(client: FastAPIClient) -> None:
190
- """Log discovered GDSFactory+ servers at startup."""
191
- try:
192
- projects = client.list_projects()
193
- if projects:
194
- logger.info("Discovered %d GDSFactory+ server(s):", len(projects))
195
- for project in projects[:3]: # Show first 3
196
- logger.info(
197
- " - %s (port %d, PDK: %s)",
198
- project["project_name"],
199
- project["port"],
200
- project.get("pdk", "unknown"),
201
- )
202
- if len(projects) > 3:
203
- logger.info(" ... and %d more", len(projects) - 3)
204
- else:
205
- logger.warning(
206
- "No GDSFactory+ servers found. To start a server:\n"
207
- " 1. Open VSCode with the GDSFactory+ extension installed\n"
208
- " 2. Open a GDSFactory+ project folder\n"
209
- " 3. The extension will automatically start and register the server"
210
- )
211
- logger.info("Registry location: %s", get_registry_path())
212
- logger.info(
213
- "Alternatively, set GFP_API_URL environment variable to connect to a specific server"
214
- )
215
- except Exception as e:
216
- logger.warning("Could not read server registry: %s", e)
217
- logger.info(
218
- "If GDSFactory+ is running, ensure registry is accessible at: %s",
219
- get_registry_path(),
220
- )
221
-
222
-
223
- async def run_server(api_url: str | None = None) -> None:
224
- """Run the MCP server with STDIO transport.
225
-
226
- Args:
227
- api_url: Optional FastAPI base URL (default from config)
228
- """
229
- log_level = logging.DEBUG if MCPConfig.DEBUG else logging.INFO
230
- logging.basicConfig(
231
- level=log_level,
232
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
233
- )
234
-
235
- logger.info("Starting GDSFactory+ MCP server")
236
- base_url_info = MCPConfig.get_api_url(api_url) or "registry-based routing"
237
- logger.info("Base URL: %s", base_url_info)
238
- logger.info("Timeout: %ds", MCPConfig.get_timeout())
239
-
240
- server = create_server(api_url)
241
- client: FastAPIClient = server._http_client # type: ignore[attr-defined] # noqa: SLF001
242
-
243
- try:
244
- await client.start()
245
-
246
- _log_server_discovery(client)
247
-
248
- async with stdio_server() as (read_stream, write_stream):
249
- logger.info("MCP server ready (STDIO transport)")
250
- await server.run(
251
- read_stream,
252
- write_stream,
253
- server.create_initialization_options(),
254
- )
255
-
256
- except KeyboardInterrupt:
257
- logger.info("Shutting down MCP server (keyboard interrupt)")
258
- except Exception:
259
- logger.exception("MCP server error")
260
- raise
261
- finally:
262
- await client.close()
263
- logger.info("MCP server stopped")
264
-
265
-
266
- def main(api_url: str | None = None) -> None:
267
- """Main entry point for MCP server.
268
-
269
- Args:
270
- api_url: Optional FastAPI base URL (default from config)
271
- """
272
- try:
273
- asyncio.run(run_server(api_url))
274
- except KeyboardInterrupt:
275
- pass
276
- except Exception:
277
- logger.exception("Fatal error")
278
- raise
279
-
280
-
281
- if __name__ == "__main__":
282
- main()