gfp-mcp 0.1.0__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.
@@ -0,0 +1,360 @@
1
+ Metadata-Version: 2.4
2
+ Name: gfp-mcp
3
+ Version: 0.1.0
4
+ Summary: Model Context Protocol (MCP) server for GDSFactory+ photonic IC design
5
+ Author: GDSFactory+ Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/doplaydo/gfp-mcp
8
+ Project-URL: Repository, https://github.com/doplaydo/gfp-mcp
9
+ Project-URL: Documentation, https://github.com/doplaydo/gfp-mcp#readme
10
+ Project-URL: Changelog, https://github.com/doplaydo/gfp-mcp/blob/main/CHANGELOG.md
11
+ Project-URL: Issue Tracker, https://github.com/doplaydo/gfp-mcp/issues
12
+ Keywords: mcp,gdsfactory,photonics,ic-design,eda,model-context-protocol,photonic-ic,gds
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3 :: Only
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: mcp>=1.7.1
27
+ Requires-Dist: httpx>=0.25.0
28
+ Requires-Dist: typing-extensions>=4.0.0; python_version < "3.11"
29
+ Requires-Dist: psutil>=5.9.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
34
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
35
+
36
+ # GDSFactory+ MCP Server
37
+
38
+ [![PyPI version](https://img.shields.io/pypi/v/gfp-mcp.svg)](https://pypi.org/project/gfp-mcp/)
39
+ [![Python versions](https://img.shields.io/pypi/pyversions/gfp-mcp.svg)](https://pypi.org/project/gfp-mcp/)
40
+ [![Tests](https://github.com/doplaydo/gfp-mcp/workflows/Tests/badge.svg)](https://github.com/doplaydo/gfp-mcp/actions)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
42
+
43
+ Model Context Protocol (MCP) server for GDSFactory+ that exposes photonic IC design operations as tools for AI assistants like Claude Code and Claude Desktop.
44
+
45
+ ## Overview
46
+
47
+ This project implements a standalone MCP server that bridges AI assistants with GDSFactory+ photonic IC design capabilities. The server acts as a lightweight proxy that exposes GDSFactory+ operations through standardized MCP tools while maintaining zero modifications to the existing FastAPI backend.
48
+
49
+ ## Features
50
+
51
+ ### Phase 1: Core Building Tools (Complete)
52
+
53
+ - **build_cell** - Build a single GDS cell by name
54
+ - **build_cells** - Build multiple GDS cells in batch
55
+ - **list_cells** - List all available photonic components
56
+ - **get_cell_info** - Get detailed component metadata
57
+ - **download_gds** - Download built GDS files
58
+ - **list_projects** - List all running GDSFactory+ server instances
59
+ - **get_project_info** - Get detailed information about a specific project
60
+
61
+ ### Multi-Project Support
62
+
63
+ The MCP server integrates with the GDSFactory+ server registry to support working with multiple projects simultaneously. The registry is stored at `~/.gdsfactory/server-registry.json` and is automatically managed by GDSFactory+ servers.
64
+
65
+ #### How It Works
66
+
67
+ 1. When you start a GDSFactory+ server with `gfp serve`, it registers itself in the shared registry
68
+ 2. The MCP server reads from this registry to discover available projects
69
+ 3. Tools accept an optional `project` parameter to route requests to specific servers
70
+ 4. The MCP server automatically resolves project names to the correct port
71
+
72
+ #### Example Usage
73
+
74
+ ```
75
+ User: "List all running GDSFactory+ projects"
76
+ Claude: [Uses list_projects tool to show all registered servers]
77
+
78
+ User: "Build the mzi component in the my_photonics_project"
79
+ Claude: [Uses build_cell tool with project="my_photonics_project"]
80
+ ```
81
+
82
+ **Note**: The MCP has read-only access to the registry. Only GDSFactory+ servers can register/unregister themselves.
83
+
84
+ ### Architecture
85
+
86
+ ```
87
+ AI Assistant (Claude) <-> MCP Server (STDIO) <-> HTTP Client <-> FastAPI Server
88
+ ```
89
+
90
+ **Key Benefits:**
91
+ - Universal MCP client compatibility via STDIO transport
92
+ - Zero modifications to existing FastAPI server
93
+ - Clean separation of concerns
94
+ - No database conflicts (only FastAPI touches SQLite)
95
+ - Scalable architecture ready for 20+ tools
96
+
97
+ ## Installation
98
+
99
+ ### From PyPI (Recommended)
100
+
101
+ Install the package from PyPI:
102
+
103
+ ```bash
104
+ pip install gfp-mcp
105
+ ```
106
+
107
+ Or with uv:
108
+
109
+ ```bash
110
+ uv pip install gfp-mcp
111
+ ```
112
+
113
+ ### From Source (Development)
114
+
115
+ For development or if you want the latest unreleased changes:
116
+
117
+ ```bash
118
+ git clone https://github.com/doplaydo/gfp-mcp.git
119
+ cd gfp-mcp
120
+ pip install -e ".[dev]"
121
+ ```
122
+
123
+ ### Prerequisites
124
+
125
+ - Python 3.10 or higher
126
+ - GDSFactory+ with FastAPI server running
127
+
128
+ ### Verify Installation
129
+
130
+ ```bash
131
+ gfp-mcp-serve --help
132
+ ```
133
+
134
+ ## Quick Start
135
+
136
+ ### 1. Start the FastAPI Server
137
+
138
+ In one terminal:
139
+
140
+ ```bash
141
+ gfp serve --port 8787
142
+ ```
143
+
144
+ ### 2. Configure Your AI Assistant
145
+
146
+ #### Claude Code
147
+
148
+ Add to `.claude/settings.json`:
149
+
150
+ ```json
151
+ {
152
+ "mcpServers": {
153
+ "gdsfactoryplus": {
154
+ "command": "gfp-mcp-serve",
155
+ "args": [],
156
+ "env": {
157
+ "GFP_API_URL": "http://localhost:8787"
158
+ }
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ Or use the command line:
165
+
166
+ ```bash
167
+ claude mcp add gdsfactoryplus -- gfp-mcp-serve
168
+ ```
169
+
170
+ #### Claude Desktop
171
+
172
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
173
+
174
+ ```json
175
+ {
176
+ "mcpServers": {
177
+ "gdsfactoryplus": {
178
+ "command": "gfp-mcp-serve",
179
+ "args": []
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ For Windows: `%APPDATA%\Claude\claude_desktop_config.json`
186
+
187
+ For Linux: `~/.config/Claude/claude_desktop_config.json`
188
+
189
+ ### 3. Use the Tools
190
+
191
+ Ask your AI assistant to:
192
+
193
+ - "List all available photonic components"
194
+ - "Build the mzi component"
195
+ - "Show me details about the coupler component"
196
+ - "Build multiple components: mzi, coupler, and bend_euler"
197
+
198
+ ## Environment Variables
199
+
200
+ Configure the MCP server using environment variables:
201
+
202
+ ```bash
203
+ # FastAPI server URL (default: http://localhost:8787)
204
+ export GFP_API_URL="http://localhost:8787"
205
+
206
+ # Request timeout in seconds (default: 300)
207
+ export GFP_MCP_TIMEOUT=300
208
+
209
+ # Enable debug logging (default: false)
210
+ export GFP_MCP_DEBUG=true
211
+ ```
212
+
213
+ ## Project Structure
214
+
215
+ ```
216
+ gfp-mcp/
217
+ ├── mcp_standalone/ # MCP server implementation
218
+ │ ├── __init__.py # Package exports
219
+ │ ├── config.py # Configuration management
220
+ │ ├── client.py # HTTP client for FastAPI
221
+ │ ├── registry.py # Server registry (multi-project support)
222
+ │ ├── tools.py # MCP tool definitions
223
+ │ ├── mappings.py # Tool → Endpoint mappings
224
+ │ ├── server.py # MCP server core
225
+ │ └── README.md # Detailed documentation
226
+ ├── tests/ # Test suite
227
+ │ ├── test_mcp_tools.py
228
+ │ ├── test_mcp_mappings.py
229
+ │ ├── test_mcp_integration.py
230
+ │ └── test_registry.py
231
+ ├── mcp_serve.py # CLI entry point
232
+ ├── MCP_QUICKSTART.md # Quick start guide
233
+ ├── MCP_IMPLEMENTATION_STATUS.md # Implementation details
234
+ └── README.md # This file
235
+ ```
236
+
237
+ ## Testing
238
+
239
+ Run the test suite:
240
+
241
+ ```bash
242
+ # Run all MCP tests
243
+ pytest tests/test_mcp_*.py -v
244
+
245
+ # Run with coverage
246
+ pytest tests/test_mcp_*.py --cov=mcp_standalone --cov-report=term-missing
247
+
248
+ # Run specific test file
249
+ pytest tests/test_mcp_tools.py -v
250
+ ```
251
+
252
+ All tests are passing (46/46 tests):
253
+ - Tool definitions: 15 tests
254
+ - Endpoint mappings: 13 tests
255
+ - Integration & client: 9 tests
256
+ - Registry integration: 9 tests
257
+
258
+ ## Example Workflows
259
+
260
+ ### Build and Verify a Component
261
+
262
+ ```
263
+ User: "List all available photonic components"
264
+ Claude: [Uses list_cells tool]
265
+
266
+ User: "Build the mzi component"
267
+ Claude: [Uses build_cell tool with name="mzi"]
268
+
269
+ User: "Show me the details of the mzi component"
270
+ Claude: [Uses get_cell_info tool with name="mzi"]
271
+ ```
272
+
273
+ ### Batch Build Multiple Components
274
+
275
+ ```
276
+ User: "Build the following components: mzi, coupler, and bend_euler"
277
+ Claude: [Uses build_cells tool with names=["mzi", "coupler", "bend_euler"]]
278
+ ```
279
+
280
+ ## Troubleshooting
281
+
282
+ ### Error: "Connection refused to localhost:8787"
283
+
284
+ **Cause**: FastAPI server is not running
285
+
286
+ **Solution**: Start the FastAPI server:
287
+
288
+ ```bash
289
+ gfp serve --port 8787
290
+ ```
291
+
292
+ ### Error: "MCP server not responding"
293
+
294
+ **Cause**: STDIO transport issue or MCP client misconfiguration
295
+
296
+ **Solution**:
297
+ 1. Check Claude Code/Desktop logs with `claude --debug`
298
+ 2. Restart the MCP server
299
+ 3. Verify the configuration in settings.json
300
+
301
+ ### Error: "Tool execution timeout"
302
+
303
+ **Cause**: Long-running operation exceeded timeout
304
+
305
+ **Solution**: Increase the timeout:
306
+
307
+ ```bash
308
+ export GFP_MCP_TIMEOUT=600 # 10 minutes
309
+ gfp mcp-serve
310
+ ```
311
+
312
+ ## Roadmap
313
+
314
+ ### Future Phases
315
+
316
+ - **Phase 2**: Verification tools (DRC, LVS)
317
+ - **Phase 3**: SPICE workflow tools
318
+ - **Phase 4**: Simulation & advanced tools
319
+ - **Phase 5**: Comprehensive testing & documentation
320
+
321
+ ## Documentation
322
+
323
+ - [Quick Start Guide](MCP_QUICKSTART.md) - Step-by-step setup instructions
324
+ - [Implementation Status](MCP_IMPLEMENTATION_STATUS.md) - Detailed implementation notes
325
+ - [MCP Standalone README](mcp_standalone/README.md) - Architecture details
326
+ - [Contributing Guide](CONTRIBUTING.md) - Development and release guidelines
327
+ - [Changelog](CHANGELOG.md) - Version history and release notes
328
+
329
+ ## Contributing
330
+
331
+ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines on:
332
+
333
+ - Development setup
334
+ - Running tests and code quality checks
335
+ - Making changes and submitting PRs
336
+ - Release process (for maintainers)
337
+
338
+ Quick checklist:
339
+
340
+ 1. All tests pass: `pytest tests/test_mcp_*.py -v`
341
+ 2. Code quality: `ruff check . && ruff format --check .`
342
+ 3. Documentation is updated
343
+ 4. Changes are backward compatible
344
+
345
+ ## License
346
+
347
+ See the main GDSFactory+ project for license information.
348
+
349
+ ## Support
350
+
351
+ For issues or questions:
352
+
353
+ 1. Check the [Quick Start Guide](MCP_QUICKSTART.md) troubleshooting section
354
+ 2. Review the [Implementation Status](MCP_IMPLEMENTATION_STATUS.md) document
355
+ 3. Enable debug mode with `GFP_MCP_DEBUG=true` and check server logs
356
+ 4. Open an issue on GitHub
357
+
358
+ ## Acknowledgments
359
+
360
+ Built on the Model Context Protocol by Anthropic, enabling seamless AI assistant integration with photonic IC design workflows.
@@ -0,0 +1,12 @@
1
+ mcp_standalone/__init__.py,sha256=7PhYozHyiRK_oFui0RkF5GJWxA2OZoWVcU_-ESfHIR4,953
2
+ mcp_standalone/client.py,sha256=gUs9m2M27OP-GYmJ9fkojyxv212ETVJFqp0v2uN8Q2c,8205
3
+ mcp_standalone/config.py,sha256=zr2LnI_wNEcMWrnB_WLscR-skkXjE33ZPBtK08XjaX0,1386
4
+ mcp_standalone/mappings.py,sha256=jXEHwSrSYNDuVeMRM2dsSUsr38CzrHBLoe6exkJhqvc,7695
5
+ mcp_standalone/registry.py,sha256=1E61UalVot8HUS3cALjM7ejYB0qR6tI5QbQSZZeQe7Y,6401
6
+ mcp_standalone/server.py,sha256=Celd9j0KY1Fl_Qw09b9Px03vGhtaTUac2ozlYdGJ23A,7100
7
+ mcp_standalone/tools.py,sha256=__j9N396-cJj9CKi6EAdP5_J-xESrbRZG35KNzdszXM,12349
8
+ gfp_mcp-0.1.0.dist-info/METADATA,sha256=dVk1r3JfmlEY3RcqVSDqMLYm2fPAqwYjM4cTUD6sVQE,10442
9
+ gfp_mcp-0.1.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
10
+ gfp_mcp-0.1.0.dist-info/entry_points.txt,sha256=mgyus9dsB_8mjgnztuHNPqzPi-7HcPg1iYzfM5NMIjk,61
11
+ gfp_mcp-0.1.0.dist-info/top_level.txt,sha256=g2hRJHoDDPNtrNdXR70T7FR9Ev6DTRJiGW7ZvlvnXMc,15
12
+ gfp_mcp-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gfp-mcp-serve = mcp_standalone.server:main
@@ -0,0 +1 @@
1
+ mcp_standalone
@@ -0,0 +1,39 @@
1
+ """MCP Standalone Server for GDSFactory+.
2
+
3
+ This package provides a Model Context Protocol (MCP) server that exposes
4
+ GDSFactory+ operations as tools for AI assistants. The server uses STDIO
5
+ transport and proxies requests to the FastAPI backend.
6
+
7
+ Architecture:
8
+ - Standalone MCP server (this package)
9
+ - STDIO transport for universal compatibility
10
+ - HTTP proxy to FastAPI backend
11
+ - Zero changes to existing FastAPI server
12
+ - No database conflicts (only FastAPI touches SQLite)
13
+
14
+ Usage:
15
+ from gdsfactoryplus.mcp_standalone import main
16
+ main()
17
+
18
+ Or via CLI:
19
+ gfp mcp-serve
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from .client import FastAPIClient
25
+ from .config import MCPConfig
26
+ from .server import create_server, main, run_server
27
+ from .tools import get_all_tools, get_tool_by_name
28
+
29
+ __all__ = [
30
+ "FastAPIClient",
31
+ "MCPConfig",
32
+ "create_server",
33
+ "main",
34
+ "run_server",
35
+ "get_all_tools",
36
+ "get_tool_by_name",
37
+ ]
38
+
39
+ __version__ = "0.1.0"
@@ -0,0 +1,268 @@
1
+ """HTTP client wrapper for communicating with FastAPI backend."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import sys
8
+ from typing import Any
9
+
10
+ import httpx
11
+
12
+ if sys.version_info >= (3, 11):
13
+ from typing import Self
14
+ else:
15
+ from typing_extensions import Self
16
+
17
+ from .config import MCPConfig
18
+ from .registry import ServerRegistry
19
+
20
+ __all__ = ["FastAPIClient"]
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class FastAPIClient:
26
+ """Async HTTP client for FastAPI backend.
27
+
28
+ Handles connection pooling, retries, and error handling for
29
+ communication with the GDSFactory+ FastAPI server.
30
+
31
+ Supports multi-project routing via the server registry.
32
+ """
33
+
34
+ def __init__(self, base_url: str | None = None) -> None:
35
+ """Initialize the FastAPI client.
36
+
37
+ Args:
38
+ base_url: Base URL for the FastAPI server (default from config)
39
+ If not provided, will use project-based routing via registry
40
+ """
41
+ self.base_url = base_url or MCPConfig.get_api_url(None)
42
+ self.timeout = MCPConfig.get_timeout()
43
+ self._client: httpx.AsyncClient | None = None
44
+ self._registry = ServerRegistry()
45
+
46
+ async def __aenter__(self) -> Self:
47
+ """Enter async context."""
48
+ await self.start()
49
+ return self
50
+
51
+ async def __aexit__(self, *args: object) -> None:
52
+ """Exit async context."""
53
+ await self.close()
54
+
55
+ async def start(self) -> None:
56
+ """Start the HTTP client with connection pooling."""
57
+ if self._client is None:
58
+ self._client = httpx.AsyncClient(
59
+ base_url=self.base_url,
60
+ timeout=httpx.Timeout(self.timeout),
61
+ limits=httpx.Limits(max_keepalive_connections=5, max_connections=10),
62
+ )
63
+ logger.debug("HTTP client started with base URL: %s", self.base_url)
64
+
65
+ async def close(self) -> None:
66
+ """Close the HTTP client."""
67
+ if self._client is not None:
68
+ await self._client.aclose()
69
+ self._client = None
70
+ logger.debug("HTTP client closed")
71
+
72
+ def _resolve_base_url(self, project: str | None = None) -> str:
73
+ """Resolve the base URL for a request.
74
+
75
+ Args:
76
+ project: Optional project name/path to route to specific server
77
+
78
+ Returns:
79
+ Base URL for the request
80
+
81
+ Raises:
82
+ ValueError: If project not found in registry
83
+ """
84
+ # If no project specified, use default base_url
85
+ if project is None:
86
+ return self.base_url
87
+
88
+ # Look up project in registry
89
+ server_info = self._registry.get_server_by_project(project)
90
+ if server_info is None:
91
+ msg = (
92
+ f"Project '{project}' not found in registry. "
93
+ "Make sure the server is running for this project."
94
+ )
95
+ raise ValueError(msg)
96
+
97
+ return f"http://localhost:{server_info.port}"
98
+
99
+ async def request(
100
+ self,
101
+ method: str,
102
+ path: str,
103
+ params: dict[str, Any] | None = None,
104
+ json_data: dict[str, Any] | None = None,
105
+ data: dict[str, Any] | None = None,
106
+ project: str | None = None,
107
+ ) -> Any:
108
+ """Make an HTTP request with retry logic.
109
+
110
+ Args:
111
+ method: HTTP method (GET, POST, etc.)
112
+ path: API endpoint path
113
+ params: Query parameters
114
+ json_data: JSON body data
115
+ data: Form data
116
+ project: Optional project name to route to specific server
117
+
118
+ Returns:
119
+ Response data (parsed JSON or raw content)
120
+
121
+ Raises:
122
+ httpx.HTTPError: If request fails after retries
123
+ ValueError: If project not found in registry
124
+ """
125
+ if self._client is None:
126
+ await self.start()
127
+
128
+ # Resolve the base URL for this request
129
+ base_url = self._resolve_base_url(project)
130
+
131
+ last_error = None
132
+ backoff = MCPConfig.RETRY_BACKOFF
133
+
134
+ for attempt in range(MCPConfig.MAX_RETRIES):
135
+ try:
136
+ logger.debug(
137
+ "Request attempt %d/%d: %s %s (project=%s, base=%s)",
138
+ attempt + 1,
139
+ MCPConfig.MAX_RETRIES,
140
+ method,
141
+ path,
142
+ project or "default",
143
+ base_url,
144
+ )
145
+
146
+ # Build full URL with the resolved base URL
147
+ full_url = f"{base_url}{path}"
148
+
149
+ response = await self._client.request( # type: ignore[union-attr]
150
+ method=method,
151
+ url=full_url,
152
+ params=params,
153
+ json=json_data,
154
+ data=data,
155
+ )
156
+ response.raise_for_status()
157
+
158
+ # Try to parse JSON, fall back to text
159
+ try:
160
+ return response.json()
161
+ except (ValueError, TypeError):
162
+ return response.text
163
+
164
+ except httpx.HTTPError as e:
165
+ last_error = e
166
+ logger.warning("Request failed (attempt %d): %s", attempt + 1, e)
167
+
168
+ # Don't retry on client errors (4xx)
169
+ if (
170
+ isinstance(e, httpx.HTTPStatusError)
171
+ and 400 <= e.response.status_code < 500
172
+ ):
173
+ raise
174
+
175
+ # Exponential backoff for retries
176
+ if attempt < MCPConfig.MAX_RETRIES - 1:
177
+ await asyncio.sleep(backoff)
178
+ backoff *= 2
179
+
180
+ # All retries failed
181
+ logger.error("All %d attempts failed", MCPConfig.MAX_RETRIES)
182
+ raise last_error # type: ignore[misc]
183
+
184
+ async def get(
185
+ self,
186
+ path: str,
187
+ params: dict[str, Any] | None = None,
188
+ ) -> Any:
189
+ """Make a GET request.
190
+
191
+ Args:
192
+ path: API endpoint path
193
+ params: Query parameters
194
+
195
+ Returns:
196
+ Response data
197
+ """
198
+ return await self.request("GET", path, params=params)
199
+
200
+ async def post(
201
+ self,
202
+ path: str,
203
+ json_data: dict[str, Any] | None = None,
204
+ data: dict[str, Any] | None = None,
205
+ ) -> Any:
206
+ """Make a POST request.
207
+
208
+ Args:
209
+ path: API endpoint path
210
+ json_data: JSON body data
211
+ data: Form data
212
+
213
+ Returns:
214
+ Response data
215
+ """
216
+ return await self.request("POST", path, json_data=json_data, data=data)
217
+
218
+ async def health_check(self, project: str | None = None) -> bool:
219
+ """Check if the FastAPI server is reachable.
220
+
221
+ Args:
222
+ project: Optional project name to check specific server
223
+
224
+ Returns:
225
+ True if server is healthy, False otherwise
226
+ """
227
+ try:
228
+ await self.request("GET", "/health", project=project)
229
+ except Exception:
230
+ logger.exception("FastAPI server health check failed")
231
+ return False
232
+ else:
233
+ base_url = self._resolve_base_url(project)
234
+ logger.info("FastAPI server is healthy at %s", base_url)
235
+ return True
236
+
237
+ def list_projects(self) -> list[dict[str, Any]]:
238
+ """List all active projects from the registry.
239
+
240
+ Returns:
241
+ List of project information dictionaries
242
+ """
243
+ servers = self._registry.list_servers()
244
+ return [
245
+ {
246
+ "project_name": server.project_name,
247
+ "project_path": server.project_path,
248
+ "port": server.port,
249
+ "pid": server.pid,
250
+ "pdk": server.pdk,
251
+ "started_at": server.started_at,
252
+ }
253
+ for server in servers
254
+ ]
255
+
256
+ async def get_project_info(self, project: str) -> dict[str, Any]:
257
+ """Get detailed information about a specific project.
258
+
259
+ Args:
260
+ project: Project name or path
261
+
262
+ Returns:
263
+ Project information from the server's /info endpoint
264
+
265
+ Raises:
266
+ ValueError: If project not found
267
+ """
268
+ return await self.request("GET", "/info", project=project)