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.
@@ -0,0 +1,160 @@
1
+ """Project discovery tool handlers.
2
+
3
+ These tools handle project listing and info retrieval from the
4
+ server registry without making HTTP requests to the backend.
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 TextContent, Tool
14
+
15
+ from .base import EndpointMapping, ToolHandler
16
+
17
+ if TYPE_CHECKING:
18
+ from ..client import FastAPIClient
19
+
20
+ __all__ = ["ListProjectsHandler", "GetProjectInfoHandler"]
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class ListProjectsHandler(ToolHandler):
26
+ """Handler for listing all active GDSFactory+ projects.
27
+
28
+ This is a registry-only tool that doesn't make HTTP requests.
29
+ It reads from the local server registry to discover running servers.
30
+ """
31
+
32
+ @property
33
+ def name(self) -> str:
34
+ return "list_projects"
35
+
36
+ @property
37
+ def definition(self) -> Tool:
38
+ return Tool(
39
+ name="list_projects",
40
+ description=(
41
+ "List all active GDSFactory+ projects. Returns information about "
42
+ "all running servers including project name, path, port, PID, and PDK. "
43
+ "Use this to discover which projects are available for interaction."
44
+ ),
45
+ inputSchema={
46
+ "type": "object",
47
+ "properties": {},
48
+ },
49
+ )
50
+
51
+ @property
52
+ def mapping(self) -> EndpointMapping | None:
53
+ # Registry-only tool, no HTTP endpoint
54
+ return None
55
+
56
+ async def handle(
57
+ self,
58
+ arguments: dict[str, Any],
59
+ client: FastAPIClient,
60
+ ) -> list[TextContent]:
61
+ """List all projects from the registry.
62
+
63
+ Args:
64
+ arguments: MCP tool arguments (unused for this tool)
65
+ client: FastAPI client (used to access registry)
66
+
67
+ Returns:
68
+ List containing TextContent with project list
69
+ """
70
+ try:
71
+ projects = client.list_projects()
72
+ return [
73
+ TextContent(
74
+ type="text",
75
+ text=json.dumps({"projects": projects}, indent=2),
76
+ )
77
+ ]
78
+ except Exception as e:
79
+ error_msg = f"Failed to list projects: {e!s}"
80
+ logger.exception(error_msg)
81
+ return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
82
+
83
+
84
+ class GetProjectInfoHandler(ToolHandler):
85
+ """Handler for getting detailed info about a specific project.
86
+
87
+ This tool makes an HTTP request to the project's /info endpoint
88
+ to retrieve detailed information including version and PDK details.
89
+ """
90
+
91
+ @property
92
+ def name(self) -> str:
93
+ return "get_project_info"
94
+
95
+ @property
96
+ def definition(self) -> Tool:
97
+ return Tool(
98
+ name="get_project_info",
99
+ description=(
100
+ "Get detailed information about a specific project. Returns metadata "
101
+ "including project name, path, port, PID, PDK, and version. "
102
+ "Use this to get information about a running project."
103
+ ),
104
+ inputSchema={
105
+ "type": "object",
106
+ "properties": {
107
+ "project": {
108
+ "type": "string",
109
+ "description": (
110
+ "Project name or path. Can be the project directory name "
111
+ "or full path."
112
+ ),
113
+ },
114
+ },
115
+ "required": ["project"],
116
+ },
117
+ )
118
+
119
+ @property
120
+ def mapping(self) -> EndpointMapping:
121
+ return EndpointMapping(method="GET", path="/info")
122
+
123
+ def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
124
+ """No request transformation needed - just routes to /info."""
125
+ return {}
126
+
127
+ async def handle(
128
+ self,
129
+ arguments: dict[str, Any],
130
+ client: FastAPIClient,
131
+ ) -> list[TextContent]:
132
+ """Get project info via HTTP request.
133
+
134
+ Args:
135
+ arguments: MCP tool arguments with 'project' key
136
+ client: FastAPI client for making requests
137
+
138
+ Returns:
139
+ List containing TextContent with project info
140
+ """
141
+ try:
142
+ project = arguments.get("project")
143
+ if not project:
144
+ return [
145
+ TextContent(
146
+ type="text",
147
+ text=json.dumps({"error": "project parameter is required"}),
148
+ )
149
+ ]
150
+
151
+ response = await client.request(
152
+ method="GET",
153
+ path="/info",
154
+ project=project,
155
+ )
156
+ return [TextContent(type="text", text=json.dumps(response, indent=2))]
157
+ except Exception as e:
158
+ error_msg = f"Failed to get project info: {e!s}"
159
+ logger.exception(error_msg)
160
+ return [TextContent(type="text", text=json.dumps({"error": error_msg}))]
@@ -0,0 +1,215 @@
1
+ """Sample project tool handlers.
2
+
3
+ These tools allow AI assistants to browse and read sample files
4
+ from GDSFactory+ General PDK sample projects.
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 TextContent, Tool
14
+
15
+ from ..config import get_gfp_api_key
16
+ from ..samples import (
17
+ GENERAL_PDK_PROJECTS,
18
+ get_sample_file_content,
19
+ list_sample_projects,
20
+ )
21
+ from .base import ToolHandler
22
+
23
+ if TYPE_CHECKING:
24
+ from ..client import FastAPIClient
25
+
26
+ __all__ = ["ListSamplesHandler", "GetSampleFileHandler"]
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ _API_KEY_ERROR = {
31
+ "error": "GFP API key not configured",
32
+ "instructions": (
33
+ "Set the GFP_API_KEY environment variable or configure "
34
+ "~/.gdsfactory/gdsfactoryplus.toml with your API key. "
35
+ "Get your key at: https://api.gdsfactory.com/api-keys/list"
36
+ ),
37
+ }
38
+
39
+
40
+ class ListSamplesHandler(ToolHandler):
41
+ """Handler for listing sample files from General PDK projects.
42
+
43
+ Downloads project ZIPs from the registry and returns file listings.
44
+ This is a registry-only tool that doesn't use the FastAPI backend.
45
+ """
46
+
47
+ @property
48
+ def name(self) -> str:
49
+ return "list_samples"
50
+
51
+ @property
52
+ def definition(self) -> Tool:
53
+ return Tool(
54
+ name="list_samples",
55
+ description=(
56
+ "List all available sample files from GDSFactory+ General PDK "
57
+ "sample projects. Returns file listings for the basic and full "
58
+ "public PDK projects. Requires a GFP API key."
59
+ ),
60
+ inputSchema={
61
+ "type": "object",
62
+ "properties": {},
63
+ },
64
+ )
65
+
66
+ async def handle(
67
+ self,
68
+ arguments: dict[str, Any],
69
+ client: FastAPIClient,
70
+ ) -> list[TextContent]:
71
+ """List sample files from all General PDK projects.
72
+
73
+ Args:
74
+ arguments: MCP tool arguments (unused).
75
+ client: FastAPI client (unused, samples come from registry).
76
+
77
+ Returns:
78
+ List containing TextContent with project file listings.
79
+ """
80
+ api_key = get_gfp_api_key()
81
+ if not api_key:
82
+ return [TextContent(type="text", text=json.dumps(_API_KEY_ERROR))]
83
+
84
+ try:
85
+ projects = await list_sample_projects(api_key)
86
+ result = {
87
+ "projects": [{"name": p.name, "files": p.files} for p in projects]
88
+ }
89
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
90
+ except Exception as e:
91
+ error_msg = f"Failed to fetch samples: {e!s}"
92
+ logger.exception(error_msg)
93
+ return [
94
+ TextContent(
95
+ type="text",
96
+ text=json.dumps({"error": error_msg}),
97
+ )
98
+ ]
99
+
100
+
101
+ class GetSampleFileHandler(ToolHandler):
102
+ """Handler for reading a specific sample file's content.
103
+
104
+ Downloads a project ZIP and extracts the requested file.
105
+ This is a registry-only tool that doesn't use the FastAPI backend.
106
+ """
107
+
108
+ @property
109
+ def name(self) -> str:
110
+ return "get_sample_file"
111
+
112
+ @property
113
+ def definition(self) -> Tool:
114
+ return Tool(
115
+ name="get_sample_file",
116
+ description=(
117
+ "Get the content of a specific sample file from a GDSFactory+ "
118
+ "General PDK project. Use list_samples first to discover "
119
+ "available files. Requires a GFP API key."
120
+ ),
121
+ inputSchema={
122
+ "type": "object",
123
+ "properties": {
124
+ "project": {
125
+ "type": "string",
126
+ "description": (
127
+ "Sample project name "
128
+ "(e.g., 'photonics--basic--public--pdk')."
129
+ ),
130
+ },
131
+ "path": {
132
+ "type": "string",
133
+ "description": (
134
+ "File path within the project ZIP "
135
+ "(as returned by list_samples)."
136
+ ),
137
+ },
138
+ },
139
+ "required": ["project", "path"],
140
+ },
141
+ )
142
+
143
+ async def handle(
144
+ self,
145
+ arguments: dict[str, Any],
146
+ client: FastAPIClient,
147
+ ) -> list[TextContent]:
148
+ """Get the content of a specific sample file.
149
+
150
+ Args:
151
+ arguments: MCP tool arguments with 'project' and 'path'.
152
+ client: FastAPI client (unused, samples come from registry).
153
+
154
+ Returns:
155
+ List containing TextContent with file content and metadata.
156
+ """
157
+ api_key = get_gfp_api_key()
158
+ if not api_key:
159
+ return [TextContent(type="text", text=json.dumps(_API_KEY_ERROR))]
160
+
161
+ project = arguments.get("project")
162
+ path = arguments.get("path")
163
+
164
+ if not project or not path:
165
+ return [
166
+ TextContent(
167
+ type="text",
168
+ text=json.dumps(
169
+ {"error": "Both 'project' and 'path' parameters are required"}
170
+ ),
171
+ )
172
+ ]
173
+
174
+ try:
175
+ sample_file = await get_sample_file_content(api_key, project, path)
176
+ result = {
177
+ "project": sample_file.project,
178
+ "path": sample_file.path,
179
+ "content": sample_file.content,
180
+ "metadata": {"mime_type": sample_file.mime_type},
181
+ }
182
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
183
+ except ValueError:
184
+ return [
185
+ TextContent(
186
+ type="text",
187
+ text=json.dumps(
188
+ {
189
+ "error": f"Unknown sample project: {project}",
190
+ "available_projects": GENERAL_PDK_PROJECTS,
191
+ }
192
+ ),
193
+ )
194
+ ]
195
+ except KeyError:
196
+ return [
197
+ TextContent(
198
+ type="text",
199
+ text=json.dumps(
200
+ {
201
+ "error": f"File not found in project: {path}",
202
+ "suggestion": ("Use list_samples to see available files."),
203
+ }
204
+ ),
205
+ )
206
+ ]
207
+ except Exception as e:
208
+ error_msg = f"Failed to fetch sample file: {e!s}"
209
+ logger.exception(error_msg)
210
+ return [
211
+ TextContent(
212
+ type="text",
213
+ text=json.dumps({"error": error_msg}),
214
+ )
215
+ ]
@@ -0,0 +1,153 @@
1
+ """SAX circuit simulation tool handler.
2
+
3
+ This module provides the handler for running SAX circuit simulations
4
+ on photonic components, with fallback to the old API.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
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__ = ["SimulateComponentHandler"]
17
+
18
+
19
+ class SimulateComponentHandler(ToolHandler):
20
+ """Handler for running SAX circuit simulations.
21
+
22
+ Supports both the new SAX API (/api/sax/{name}/simulate) and
23
+ falls back to the old API (/api/simulate) if the new endpoint
24
+ returns 404.
25
+ """
26
+
27
+ @property
28
+ def name(self) -> str:
29
+ return "simulate_component"
30
+
31
+ @property
32
+ def definition(self) -> Tool:
33
+ return Tool(
34
+ name="simulate_component",
35
+ description=(
36
+ "Run a SAX circuit simulation on a photonic component by name. "
37
+ "This simulates the optical behavior of the component using its "
38
+ "SAX model. Returns S-parameters showing how light propagates "
39
+ "through the component's ports. Use this to analyze component "
40
+ "performance before fabrication. Supports customizing both layout "
41
+ "parameters (e.g., {'length_mmi': 12, 'gap_mmi': 0.3}) and SAX "
42
+ "model simulation settings (e.g., {'wl': [1.5, 1.55, 1.6], 'loss': 0.2})."
43
+ ),
44
+ inputSchema=add_project_param(
45
+ {
46
+ "type": "object",
47
+ "properties": {
48
+ "name": {
49
+ "type": "string",
50
+ "description": (
51
+ "Name of the component/cell to simulate. The component "
52
+ "must have a SAX model defined."
53
+ ),
54
+ },
55
+ "layout": {
56
+ "type": "object",
57
+ "description": (
58
+ "Optional component-specific geometric parameters. "
59
+ "Use this to customize the component's physical dimensions "
60
+ "before simulation. Example: {'length_mmi': 12, 'gap_mmi': 0.3}. "
61
+ "Default is empty (use default layout parameters)."
62
+ ),
63
+ },
64
+ "model": {
65
+ "type": "object",
66
+ "description": (
67
+ "Optional SAX simulation settings. Use this to configure "
68
+ "simulation parameters like wavelength sweeps or loss values. "
69
+ "Example: {'wl': [1.5, 1.55, 1.6], 'loss': 0.2}. "
70
+ "Default is empty (use default model parameters)."
71
+ ),
72
+ },
73
+ "how": {
74
+ "type": "string",
75
+ "enum": ["from_layout", "from_netlist"],
76
+ "description": (
77
+ "Simulation method. 'from_layout' simulates from the physical "
78
+ "layout geometry (default). 'from_netlist' simulates from the "
79
+ "hierarchical netlist representation."
80
+ ),
81
+ },
82
+ },
83
+ "required": ["name"],
84
+ }
85
+ ),
86
+ )
87
+
88
+ @property
89
+ def mapping(self) -> EndpointMapping:
90
+ return EndpointMapping(method="POST", path="/api/sax/{name}/simulate")
91
+
92
+ def transform_request(self, args: dict[str, Any]) -> dict[str, Any]:
93
+ """Transform simulate_component MCP args to FastAPI request body.
94
+
95
+ Supports both new SAX API (/api/sax/{name}/simulate) and falls back
96
+ to old API (/api/simulate) if the new endpoint returns 404.
97
+
98
+ Args:
99
+ args: MCP tool arguments
100
+
101
+ Returns:
102
+ Dict with 'path', 'json_data' for new API and fallback data for old API
103
+ """
104
+ name = args["name"]
105
+ layout = args.get("layout", {})
106
+ model = args.get("model", {})
107
+ how = args.get("how", "from_layout")
108
+
109
+ return {
110
+ "path": f"/api/sax/{name}/simulate",
111
+ "json_data": {
112
+ "layout": layout,
113
+ "model": model,
114
+ "from_netlist": how == "from_netlist",
115
+ },
116
+ "fallback_path": "/api/simulate",
117
+ "fallback_json_data": {
118
+ "name": name,
119
+ "layout": layout,
120
+ "model": model,
121
+ "how": how,
122
+ },
123
+ }
124
+
125
+ def transform_response(self, response: Any) -> dict[str, Any]:
126
+ """Transform simulate_component response for LLM consumption.
127
+
128
+ Returns only the file path and token estimate instead of the full
129
+ S-parameter data, which can be extremely large (500k+ tokens).
130
+
131
+ Args:
132
+ response: FastAPI response containing simulation results
133
+
134
+ Returns:
135
+ Dict with file path, token estimate, and metadata
136
+ """
137
+ if not isinstance(response, dict):
138
+ return {"error": f"Unexpected response type: {type(response).__name__}"}
139
+
140
+ # Estimate tokens (rough heuristic: ~4 chars per token)
141
+ json_str = json.dumps(response)
142
+ estimated_tokens = len(json_str) // 4
143
+
144
+ file_path = response.get("file_path")
145
+
146
+ return {
147
+ "file_path": file_path,
148
+ "estimated_tokens": estimated_tokens,
149
+ "message": f"Simulation complete. Results saved to: {file_path}",
150
+ "note": "Use the Read tool to view the simulation results at this path.",
151
+ "simulation_id": response.get("simulation_id"),
152
+ "cached": response.get("cached", False),
153
+ }
gfp_mcp/utils.py ADDED
@@ -0,0 +1,55 @@
1
+ """Common utilities for MCP server.
2
+
3
+ This module provides shared utility functions used across the MCP server,
4
+ including file polling and other async helpers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import logging
11
+ import time
12
+ from pathlib import Path
13
+
14
+ __all__ = ["wait_for_gds_file"]
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ async def wait_for_gds_file(
20
+ gds_path: Path,
21
+ timeout: float = 30.0,
22
+ poll_interval: float = 0.5,
23
+ min_age: float = 0.1,
24
+ ) -> bool:
25
+ """Poll for GDS file to be created with timeout.
26
+
27
+ This function waits for a GDS file to exist and be stable (not actively
28
+ being written to) before returning. Useful for waiting on build operations.
29
+
30
+ Args:
31
+ gds_path: Path to the expected GDS file
32
+ timeout: Maximum time to wait in seconds (default: 30.0)
33
+ poll_interval: Time between polls in seconds (default: 0.5)
34
+ min_age: Minimum file age in seconds to ensure write is complete (default: 0.1)
35
+
36
+ Returns:
37
+ True if file exists and is ready, False if timeout reached
38
+ """
39
+ start_time = time.monotonic()
40
+ logger.debug("Waiting for GDS file: %s (timeout: %.1fs)", gds_path, timeout)
41
+
42
+ while time.monotonic() - start_time < timeout:
43
+ if gds_path.exists():
44
+ file_age = time.time() - gds_path.stat().st_mtime
45
+ if file_age >= min_age:
46
+ logger.debug("GDS file ready: %s (age: %.2fs)", gds_path, file_age)
47
+ return True
48
+ logger.debug(
49
+ "GDS file exists but too new (age: %.2fs), waiting...", file_age
50
+ )
51
+
52
+ await asyncio.sleep(poll_interval)
53
+
54
+ logger.warning("Timeout waiting for GDS file: %s", gds_path)
55
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gfp-mcp
3
- Version: 0.2.1
3
+ Version: 0.3.2
4
4
  Summary: Model Context Protocol (MCP) server for GDSFactory+ photonic IC design
5
5
  Author: GDSFactory+ Team
6
6
  License: MIT
@@ -27,19 +27,24 @@ License-File: LICENSE
27
27
  Requires-Dist: mcp>=1.7.1
28
28
  Requires-Dist: httpx>=0.25.0
29
29
  Requires-Dist: typing-extensions>=4.0.0; python_version < "3.11"
30
+ Requires-Dist: tomli>=2.0.0; python_version < "3.11"
30
31
  Requires-Dist: psutil>=5.9.0
32
+ Provides-Extra: render
33
+ Requires-Dist: klayout>=0.28.0; extra == "render"
31
34
  Provides-Extra: dev
32
35
  Requires-Dist: pytest>=7.0.0; extra == "dev"
33
36
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
34
37
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
35
38
  Requires-Dist: ruff>=0.1.0; extra == "dev"
39
+ Requires-Dist: bump-my-version>=0.26.0; extra == "dev"
40
+ Requires-Dist: build>=1.4.0; extra == "dev"
36
41
  Dynamic: license-file
37
42
 
38
43
  # GDSFactory+ MCP Server
39
44
 
40
45
  [![PyPI version](https://img.shields.io/pypi/v/gfp-mcp.svg)](https://pypi.org/project/gfp-mcp/)
41
46
  [![Python versions](https://img.shields.io/pypi/pyversions/gfp-mcp.svg)](https://pypi.org/project/gfp-mcp/)
42
- [![Tests](https://github.com/doplaydo/gfp-mcp/workflows/Tests/badge.svg)](https://github.com/doplaydo/gfp-mcp/actions)
47
+ [![Tests](https://github.com/doplaydo/gfp-mcp/actions/workflows/test.yml/badge.svg)](https://github.com/doplaydo/gfp-mcp/actions)
43
48
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
44
49
 
45
50
  Model Context Protocol (MCP) server for GDSFactory+ that enables AI assistants like Claude to design and build photonic integrated circuits.
@@ -57,15 +62,32 @@ This MCP server connects AI assistants to [GDSFactory+](https://gdsfactory.com),
57
62
 
58
63
  ### 2. Install the MCP Server
59
64
 
65
+ **With uv (recommended):**
66
+
67
+ If you don't have `uv` installed, see the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/).
68
+
60
69
  ```bash
61
- pip install gfp-mcp
70
+ uv tool install gfp-mcp
62
71
  ```
63
72
 
64
- Or with uv:
73
+ <details>
74
+ <summary>Ephemeral approach</summary>
75
+
76
+ Run without installing:
65
77
 
66
78
  ```bash
67
79
  uvx --from gfp-mcp gfp-mcp-serve
68
80
  ```
81
+ </details>
82
+
83
+ <details>
84
+ <summary><strong>Alternative: pip install</strong></summary>
85
+
86
+ ```bash
87
+ pip install gfp-mcp
88
+ ```
89
+
90
+ </details>
69
91
 
70
92
  ### 3. Connect to Your AI Assistant
71
93
 
@@ -143,16 +165,23 @@ Try these commands with your AI assistant:
143
165
 
144
166
  ## Available Tools
145
167
 
146
- - **build_cell** - Build a single GDS cell by name
147
- - **build_cells** - Build multiple GDS cells in batch
168
+ - **build_cells** - Build one or more GDS cells by name (pass a list, can be single-item)
148
169
  - **list_cells** - List all available photonic components
149
170
  - **get_cell_info** - Get detailed component metadata
150
- - **download_gds** - Download built GDS files
151
171
  - **list_projects** - List all running GDSFactory+ server instances
152
172
  - **get_project_info** - Get detailed information about a specific project
153
- - **check_drc** - Run Design Rule Check verification
173
+ - **check_drc** - Run Design Rule Check verification (returns structured format with all violations including simplified location data for LLM-friendly troubleshooting)
154
174
  - **check_connectivity** - Run connectivity verification
155
175
  - **check_lvs** - Run Layout vs. Schematic verification
176
+ - **simulate_component** - Run SAX circuit simulations with custom parameters
177
+ - Basic: `{"name": "mzi"}` - Simulate with default parameters
178
+ - Custom layout: `{"name": "mzi", "layout": {"length_mmi": 12, "gap_mmi": 0.3}}` - Customize component geometry
179
+ - Wavelength sweep: `{"name": "coupler", "model": {"wl": [1.5, 1.55, 1.6]}}` - Simulate at multiple wavelengths
180
+ - Full example: `{"name": "mzi", "layout": {"length": 100}, "model": {"wl": [1.5, 1.55, 1.6], "loss": 0.2}, "how": "from_layout"}`
181
+ - **get_port_center** - Get physical coordinates of component ports
182
+ - **generate_bbox** - Generate bounding box GDS from layout
183
+ - **freeze_cell** - Freeze parametric cell as static netlist
184
+ - **get_pdk_info** - Get current PDK information
156
185
 
157
186
  ## Multi-Project Support
158
187