kicad-sch-api 0.3.0__py3-none-any.whl → 0.5.1__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.
- kicad_sch_api/__init__.py +68 -3
- kicad_sch_api/cli/__init__.py +45 -0
- kicad_sch_api/cli/base.py +302 -0
- kicad_sch_api/cli/bom.py +164 -0
- kicad_sch_api/cli/erc.py +229 -0
- kicad_sch_api/cli/export_docs.py +289 -0
- kicad_sch_api/cli/kicad_to_python.py +169 -0
- kicad_sch_api/cli/netlist.py +94 -0
- kicad_sch_api/cli/types.py +43 -0
- kicad_sch_api/collections/__init__.py +36 -0
- kicad_sch_api/collections/base.py +604 -0
- kicad_sch_api/collections/components.py +1623 -0
- kicad_sch_api/collections/junctions.py +206 -0
- kicad_sch_api/collections/labels.py +508 -0
- kicad_sch_api/collections/wires.py +292 -0
- kicad_sch_api/core/__init__.py +37 -2
- kicad_sch_api/core/collections/__init__.py +5 -0
- kicad_sch_api/core/collections/base.py +248 -0
- kicad_sch_api/core/component_bounds.py +34 -7
- kicad_sch_api/core/components.py +213 -52
- kicad_sch_api/core/config.py +110 -15
- kicad_sch_api/core/connectivity.py +692 -0
- kicad_sch_api/core/exceptions.py +175 -0
- kicad_sch_api/core/factories/__init__.py +5 -0
- kicad_sch_api/core/factories/element_factory.py +278 -0
- kicad_sch_api/core/formatter.py +60 -9
- kicad_sch_api/core/geometry.py +94 -5
- kicad_sch_api/core/junctions.py +26 -75
- kicad_sch_api/core/labels.py +324 -0
- kicad_sch_api/core/managers/__init__.py +30 -0
- kicad_sch_api/core/managers/base.py +76 -0
- kicad_sch_api/core/managers/file_io.py +246 -0
- kicad_sch_api/core/managers/format_sync.py +502 -0
- kicad_sch_api/core/managers/graphics.py +580 -0
- kicad_sch_api/core/managers/hierarchy.py +661 -0
- kicad_sch_api/core/managers/metadata.py +271 -0
- kicad_sch_api/core/managers/sheet.py +492 -0
- kicad_sch_api/core/managers/text_elements.py +537 -0
- kicad_sch_api/core/managers/validation.py +476 -0
- kicad_sch_api/core/managers/wire.py +410 -0
- kicad_sch_api/core/nets.py +305 -0
- kicad_sch_api/core/no_connects.py +252 -0
- kicad_sch_api/core/parser.py +194 -970
- kicad_sch_api/core/parsing_utils.py +63 -0
- kicad_sch_api/core/pin_utils.py +103 -9
- kicad_sch_api/core/schematic.py +1328 -1079
- kicad_sch_api/core/texts.py +316 -0
- kicad_sch_api/core/types.py +159 -23
- kicad_sch_api/core/wires.py +27 -75
- kicad_sch_api/exporters/__init__.py +10 -0
- kicad_sch_api/exporters/python_generator.py +610 -0
- kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
- kicad_sch_api/geometry/__init__.py +38 -0
- kicad_sch_api/geometry/font_metrics.py +22 -0
- kicad_sch_api/geometry/routing.py +211 -0
- kicad_sch_api/geometry/symbol_bbox.py +608 -0
- kicad_sch_api/interfaces/__init__.py +17 -0
- kicad_sch_api/interfaces/parser.py +76 -0
- kicad_sch_api/interfaces/repository.py +70 -0
- kicad_sch_api/interfaces/resolver.py +117 -0
- kicad_sch_api/parsers/__init__.py +14 -0
- kicad_sch_api/parsers/base.py +145 -0
- kicad_sch_api/parsers/elements/__init__.py +22 -0
- kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
- kicad_sch_api/parsers/elements/label_parser.py +216 -0
- kicad_sch_api/parsers/elements/library_parser.py +165 -0
- kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
- kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
- kicad_sch_api/parsers/elements/symbol_parser.py +485 -0
- kicad_sch_api/parsers/elements/text_parser.py +250 -0
- kicad_sch_api/parsers/elements/wire_parser.py +242 -0
- kicad_sch_api/parsers/registry.py +155 -0
- kicad_sch_api/parsers/utils.py +80 -0
- kicad_sch_api/symbols/__init__.py +18 -0
- kicad_sch_api/symbols/cache.py +467 -0
- kicad_sch_api/symbols/resolver.py +361 -0
- kicad_sch_api/symbols/validators.py +504 -0
- kicad_sch_api/utils/logging.py +555 -0
- kicad_sch_api/utils/logging_decorators.py +587 -0
- kicad_sch_api/utils/validation.py +16 -22
- kicad_sch_api/validation/__init__.py +25 -0
- kicad_sch_api/validation/erc.py +171 -0
- kicad_sch_api/validation/erc_models.py +203 -0
- kicad_sch_api/validation/pin_matrix.py +243 -0
- kicad_sch_api/validation/validators.py +391 -0
- kicad_sch_api/wrappers/__init__.py +14 -0
- kicad_sch_api/wrappers/base.py +89 -0
- kicad_sch_api/wrappers/wire.py +198 -0
- kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
- kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
- kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
- mcp_server/__init__.py +34 -0
- mcp_server/example_logging_integration.py +506 -0
- mcp_server/models.py +252 -0
- mcp_server/server.py +357 -0
- mcp_server/tools/__init__.py +32 -0
- mcp_server/tools/component_tools.py +516 -0
- mcp_server/tools/connectivity_tools.py +532 -0
- mcp_server/tools/consolidated_tools.py +1216 -0
- mcp_server/tools/pin_discovery.py +333 -0
- mcp_server/utils/__init__.py +38 -0
- mcp_server/utils/logging.py +127 -0
- mcp_server/utils.py +36 -0
- kicad_sch_api/core/manhattan_routing.py +0 -430
- kicad_sch_api/core/simple_manhattan.py +0 -228
- kicad_sch_api/core/wire_routing.py +0 -380
- kicad_sch_api-0.3.0.dist-info/METADATA +0 -483
- kicad_sch_api-0.3.0.dist-info/RECORD +0 -31
- kicad_sch_api-0.3.0.dist-info/entry_points.txt +0 -2
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
mcp_server/models.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic models for MCP server API responses.
|
|
3
|
+
|
|
4
|
+
Provides type-safe data models for MCP tools that interact with the schematic API,
|
|
5
|
+
ensuring consistent serialization and validation across all endpoints.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PointModel(BaseModel):
|
|
13
|
+
"""2D point coordinates."""
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(
|
|
16
|
+
json_schema_extra={
|
|
17
|
+
"example": {"x": 100.0, "y": 100.0},
|
|
18
|
+
"description": "Position in KiCAD schematic coordinates",
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
x: float = Field(..., description="X coordinate in mm")
|
|
23
|
+
y: float = Field(..., description="Y coordinate in mm")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PinInfoOutput(BaseModel):
|
|
27
|
+
"""
|
|
28
|
+
Complete pin information for MCP clients.
|
|
29
|
+
|
|
30
|
+
Provides comprehensive pin metadata including position, electrical type,
|
|
31
|
+
graphical representation, and unique identification.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(
|
|
35
|
+
json_schema_extra={
|
|
36
|
+
"example": {
|
|
37
|
+
"number": "1",
|
|
38
|
+
"name": "~",
|
|
39
|
+
"position": {"x": 100.33, "y": 104.14},
|
|
40
|
+
"electrical_type": "passive",
|
|
41
|
+
"shape": "line",
|
|
42
|
+
"length": 2.54,
|
|
43
|
+
"orientation": 0.0,
|
|
44
|
+
"uuid": "1f8ab1be-1ad8-469d-8ba9-667910bdee9e:1",
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
number: str = Field(
|
|
50
|
+
...,
|
|
51
|
+
description="Pin number or designator (e.g., '1', '2', 'A1')",
|
|
52
|
+
examples=["1", "2", "A1", "CLK"],
|
|
53
|
+
)
|
|
54
|
+
name: str = Field(
|
|
55
|
+
...,
|
|
56
|
+
description="Pin name or signal designation (e.g., 'VCC', 'GND', 'CLK')",
|
|
57
|
+
examples=["VCC", "GND", "CLK", "D0"],
|
|
58
|
+
)
|
|
59
|
+
position: PointModel = Field(
|
|
60
|
+
...,
|
|
61
|
+
description="Absolute position in schematic coordinates (mm), "
|
|
62
|
+
"accounting for component rotation and mirroring",
|
|
63
|
+
)
|
|
64
|
+
electrical_type: str = Field(
|
|
65
|
+
...,
|
|
66
|
+
description="Pin electrical type (input, output, passive, power_in, power_out, bidirectional, etc.)",
|
|
67
|
+
examples=["passive", "input", "output", "power_in", "power_out"],
|
|
68
|
+
)
|
|
69
|
+
shape: str = Field(
|
|
70
|
+
...,
|
|
71
|
+
description="Pin graphical shape (line, inverted, clock, inverted_clock, input_low, etc.)",
|
|
72
|
+
examples=["line", "inverted", "clock"],
|
|
73
|
+
)
|
|
74
|
+
length: float = Field(
|
|
75
|
+
...,
|
|
76
|
+
description="Pin length in mm (typically 2.54 for standard pins)",
|
|
77
|
+
examples=[2.54],
|
|
78
|
+
)
|
|
79
|
+
orientation: float = Field(
|
|
80
|
+
...,
|
|
81
|
+
description="Pin orientation in degrees (0, 90, 180, or 270)",
|
|
82
|
+
examples=[0.0, 90.0, 180.0, 270.0],
|
|
83
|
+
)
|
|
84
|
+
uuid: str = Field(
|
|
85
|
+
...,
|
|
86
|
+
description="Unique identifier for this pin instance (composite: component_uuid:pin_number)",
|
|
87
|
+
examples=["1f8ab1be-1ad8-469d-8ba9-667910bdee9e:1"],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ComponentPinsOutput(BaseModel):
|
|
92
|
+
"""
|
|
93
|
+
Output model for get_component_pins MCP tool.
|
|
94
|
+
|
|
95
|
+
Returns all pins for a specified component with complete metadata.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
model_config = ConfigDict(
|
|
99
|
+
json_schema_extra={
|
|
100
|
+
"example": {
|
|
101
|
+
"reference": "R1",
|
|
102
|
+
"lib_id": "Device:R",
|
|
103
|
+
"pins": [
|
|
104
|
+
{
|
|
105
|
+
"number": "1",
|
|
106
|
+
"name": "~",
|
|
107
|
+
"position": {"x": 100.33, "y": 104.14},
|
|
108
|
+
"electrical_type": "passive",
|
|
109
|
+
"shape": "line",
|
|
110
|
+
"length": 2.54,
|
|
111
|
+
"orientation": 0.0,
|
|
112
|
+
"uuid": "1f8ab1be-1ad8-469d-8ba9-667910bdee9e:1",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"number": "2",
|
|
116
|
+
"name": "~",
|
|
117
|
+
"position": {"x": 100.33, "y": 95.86},
|
|
118
|
+
"electrical_type": "passive",
|
|
119
|
+
"shape": "line",
|
|
120
|
+
"length": 2.54,
|
|
121
|
+
"orientation": 0.0,
|
|
122
|
+
"uuid": "1f8ab1be-1ad8-469d-8ba9-667910bdee9e:2",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
"pin_count": 2,
|
|
126
|
+
"success": True,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
reference: str = Field(
|
|
132
|
+
...,
|
|
133
|
+
description="Component reference designator (e.g., 'R1', 'U2', 'C1')",
|
|
134
|
+
examples=["R1", "U2", "C1"],
|
|
135
|
+
)
|
|
136
|
+
lib_id: str = Field(
|
|
137
|
+
...,
|
|
138
|
+
description="Library identifier (e.g., 'Device:R', 'Amplifier_Operational:TL072')",
|
|
139
|
+
examples=["Device:R", "Amplifier_Operational:TL072", "LED:LED"],
|
|
140
|
+
)
|
|
141
|
+
pins: List[PinInfoOutput] = Field(
|
|
142
|
+
...,
|
|
143
|
+
description="List of all pins for this component with complete metadata",
|
|
144
|
+
)
|
|
145
|
+
pin_count: int = Field(
|
|
146
|
+
...,
|
|
147
|
+
description="Total number of pins",
|
|
148
|
+
examples=[2, 8, 14, 48],
|
|
149
|
+
)
|
|
150
|
+
success: bool = Field(
|
|
151
|
+
default=True,
|
|
152
|
+
description="Whether the operation was successful",
|
|
153
|
+
)
|
|
154
|
+
message: Optional[str] = Field(
|
|
155
|
+
default=None,
|
|
156
|
+
description="Optional message or error description",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class ComponentInfoOutput(BaseModel):
|
|
161
|
+
"""
|
|
162
|
+
Component information output model.
|
|
163
|
+
|
|
164
|
+
Provides comprehensive component metadata including position, properties,
|
|
165
|
+
and unique identification.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
model_config = ConfigDict(
|
|
169
|
+
json_schema_extra={
|
|
170
|
+
"example": {
|
|
171
|
+
"reference": "R1",
|
|
172
|
+
"lib_id": "Device:R",
|
|
173
|
+
"value": "10k",
|
|
174
|
+
"position": {"x": 100.0, "y": 100.0},
|
|
175
|
+
"rotation": 0.0,
|
|
176
|
+
"footprint": "Resistor_SMD:R_0603_1608Metric",
|
|
177
|
+
"uuid": "1f8ab1be-1ad8-469d-8ba9-667910bdee9e",
|
|
178
|
+
"success": True,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
reference: str = Field(
|
|
184
|
+
...,
|
|
185
|
+
description="Component reference designator (e.g., 'R1', 'U2', 'C1')",
|
|
186
|
+
examples=["R1", "U2", "C1"],
|
|
187
|
+
)
|
|
188
|
+
lib_id: str = Field(
|
|
189
|
+
...,
|
|
190
|
+
description="Library identifier (e.g., 'Device:R', 'Amplifier_Operational:TL072')",
|
|
191
|
+
examples=["Device:R", "Amplifier_Operational:TL072", "LED:LED"],
|
|
192
|
+
)
|
|
193
|
+
value: str = Field(
|
|
194
|
+
...,
|
|
195
|
+
description="Component value or part description",
|
|
196
|
+
examples=["10k", "100nF", "TL072"],
|
|
197
|
+
)
|
|
198
|
+
position: PointModel = Field(
|
|
199
|
+
...,
|
|
200
|
+
description="Component position in schematic coordinates (mm)",
|
|
201
|
+
)
|
|
202
|
+
rotation: float = Field(
|
|
203
|
+
...,
|
|
204
|
+
description="Component rotation in degrees (0, 90, 180, or 270)",
|
|
205
|
+
examples=[0.0, 90.0, 180.0, 270.0],
|
|
206
|
+
)
|
|
207
|
+
footprint: Optional[str] = Field(
|
|
208
|
+
default=None,
|
|
209
|
+
description="PCB footprint identifier",
|
|
210
|
+
examples=["Resistor_SMD:R_0603_1608Metric", "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"],
|
|
211
|
+
)
|
|
212
|
+
uuid: str = Field(
|
|
213
|
+
...,
|
|
214
|
+
description="Unique identifier for this component instance",
|
|
215
|
+
examples=["1f8ab1be-1ad8-469d-8ba9-667910bdee9e"],
|
|
216
|
+
)
|
|
217
|
+
success: bool = Field(
|
|
218
|
+
default=True,
|
|
219
|
+
description="Whether the operation was successful",
|
|
220
|
+
)
|
|
221
|
+
message: Optional[str] = Field(
|
|
222
|
+
default=None,
|
|
223
|
+
description="Optional message or error description",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ErrorOutput(BaseModel):
|
|
228
|
+
"""Standard error response model."""
|
|
229
|
+
|
|
230
|
+
model_config = ConfigDict(
|
|
231
|
+
json_schema_extra={
|
|
232
|
+
"example": {
|
|
233
|
+
"success": False,
|
|
234
|
+
"error": "COMPONENT_NOT_FOUND",
|
|
235
|
+
"message": "Component 'R999' not found in schematic",
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
success: bool = Field(
|
|
241
|
+
default=False,
|
|
242
|
+
description="Operation success status",
|
|
243
|
+
)
|
|
244
|
+
error: str = Field(
|
|
245
|
+
...,
|
|
246
|
+
description="Error type or code",
|
|
247
|
+
examples=["COMPONENT_NOT_FOUND", "LIBRARY_ERROR"],
|
|
248
|
+
)
|
|
249
|
+
message: str = Field(
|
|
250
|
+
...,
|
|
251
|
+
description="Detailed error message",
|
|
252
|
+
)
|
mcp_server/server.py
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP server main entry point for kicad-sch-api.
|
|
4
|
+
|
|
5
|
+
Provides schematic management and pin discovery tools via the Model Context Protocol.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from fastmcp import FastMCP
|
|
14
|
+
|
|
15
|
+
import kicad_sch_api as ksa
|
|
16
|
+
from mcp_server.models import ErrorOutput
|
|
17
|
+
from mcp_server.tools.pin_discovery import (
|
|
18
|
+
find_pins_by_name,
|
|
19
|
+
find_pins_by_type,
|
|
20
|
+
get_component_pins,
|
|
21
|
+
get_current_schematic,
|
|
22
|
+
set_current_schematic,
|
|
23
|
+
)
|
|
24
|
+
from mcp_server.tools.component_tools import (
|
|
25
|
+
add_component,
|
|
26
|
+
list_components,
|
|
27
|
+
update_component,
|
|
28
|
+
remove_component,
|
|
29
|
+
filter_components,
|
|
30
|
+
)
|
|
31
|
+
from mcp_server.tools.connectivity_tools import (
|
|
32
|
+
add_wire,
|
|
33
|
+
add_label,
|
|
34
|
+
add_junction,
|
|
35
|
+
connect_components,
|
|
36
|
+
)
|
|
37
|
+
from mcp_server.tools.consolidated_tools import (
|
|
38
|
+
manage_schematic,
|
|
39
|
+
manage_components,
|
|
40
|
+
manage_wires,
|
|
41
|
+
manage_labels,
|
|
42
|
+
manage_text_boxes,
|
|
43
|
+
manage_power,
|
|
44
|
+
manage_sheets,
|
|
45
|
+
manage_global_labels,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Configure logging
|
|
50
|
+
logging.basicConfig(
|
|
51
|
+
level=logging.INFO,
|
|
52
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
53
|
+
stream=sys.stderr,
|
|
54
|
+
)
|
|
55
|
+
logger = logging.getLogger(__name__)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Initialize FastMCP server
|
|
59
|
+
mcp = FastMCP("kicad-sch-api")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ========== Schematic Management Tools ==========
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@mcp.tool()
|
|
66
|
+
async def create_schematic(name: str) -> dict:
|
|
67
|
+
"""
|
|
68
|
+
Create a new blank KiCAD schematic.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
name: Project name for the schematic
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Dictionary with success status and schematic information
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
>>> result = await create_schematic("MyProject")
|
|
78
|
+
>>> print(result['message'])
|
|
79
|
+
Created new schematic: MyProject
|
|
80
|
+
"""
|
|
81
|
+
logger.info(f"[MCP] create_schematic called with name: {name}")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# Create new schematic
|
|
85
|
+
schematic = ksa.create_schematic(name)
|
|
86
|
+
|
|
87
|
+
# Set as current working schematic
|
|
88
|
+
set_current_schematic(schematic)
|
|
89
|
+
|
|
90
|
+
logger.info(f"[MCP] Created and set schematic: {name}")
|
|
91
|
+
return {
|
|
92
|
+
"success": True,
|
|
93
|
+
"message": f"Created new schematic: {name}",
|
|
94
|
+
"project_name": name,
|
|
95
|
+
"uuid": str(schematic.uuid),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error(f"[MCP] Error creating schematic: {e}", exc_info=True)
|
|
100
|
+
return {
|
|
101
|
+
"success": False,
|
|
102
|
+
"error": "CREATION_ERROR",
|
|
103
|
+
"message": f"Failed to create schematic: {str(e)}",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@mcp.tool()
|
|
108
|
+
async def load_schematic(file_path: str) -> dict:
|
|
109
|
+
"""
|
|
110
|
+
Load an existing KiCAD schematic file.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
file_path: Absolute path to .kicad_sch file
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Dictionary with success status and schematic information
|
|
117
|
+
|
|
118
|
+
Examples:
|
|
119
|
+
>>> result = await load_schematic("/path/to/project.kicad_sch")
|
|
120
|
+
>>> print(f"Loaded: {result['project_name']}")
|
|
121
|
+
"""
|
|
122
|
+
logger.info(f"[MCP] load_schematic called with path: {file_path}")
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
# Validate path
|
|
126
|
+
path = Path(file_path)
|
|
127
|
+
if not path.exists():
|
|
128
|
+
logger.error(f"[MCP] File not found: {file_path}")
|
|
129
|
+
return {
|
|
130
|
+
"success": False,
|
|
131
|
+
"error": "FILE_NOT_FOUND",
|
|
132
|
+
"message": f"Schematic file not found: {file_path}",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if not path.suffix == ".kicad_sch":
|
|
136
|
+
logger.error(f"[MCP] Invalid file extension: {file_path}")
|
|
137
|
+
return {
|
|
138
|
+
"success": False,
|
|
139
|
+
"error": "INVALID_FILE",
|
|
140
|
+
"message": "File must have .kicad_sch extension",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Load schematic
|
|
144
|
+
schematic = ksa.Schematic.load(str(path))
|
|
145
|
+
|
|
146
|
+
# Set as current working schematic
|
|
147
|
+
set_current_schematic(schematic)
|
|
148
|
+
|
|
149
|
+
# Count components
|
|
150
|
+
component_count = len(list(schematic.components.all()))
|
|
151
|
+
|
|
152
|
+
logger.info(
|
|
153
|
+
f"[MCP] Loaded schematic: {schematic.title_block.title} "
|
|
154
|
+
f"({component_count} components)"
|
|
155
|
+
)
|
|
156
|
+
return {
|
|
157
|
+
"success": True,
|
|
158
|
+
"message": f"Loaded schematic: {path.name}",
|
|
159
|
+
"file_path": str(path),
|
|
160
|
+
"project_name": schematic.title_block.title,
|
|
161
|
+
"uuid": str(schematic.uuid),
|
|
162
|
+
"component_count": component_count,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(f"[MCP] Error loading schematic: {e}", exc_info=True)
|
|
167
|
+
return {
|
|
168
|
+
"success": False,
|
|
169
|
+
"error": "LOAD_ERROR",
|
|
170
|
+
"message": f"Failed to load schematic: {str(e)}",
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@mcp.tool()
|
|
175
|
+
async def save_schematic(file_path: Optional[str] = None) -> dict:
|
|
176
|
+
"""
|
|
177
|
+
Save the current schematic to disk.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
file_path: Optional path to save to. If not provided, saves to original location.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Dictionary with success status
|
|
184
|
+
|
|
185
|
+
Examples:
|
|
186
|
+
>>> # Save to original location
|
|
187
|
+
>>> result = await save_schematic()
|
|
188
|
+
|
|
189
|
+
>>> # Save to new location
|
|
190
|
+
>>> result = await save_schematic("/path/to/new_location.kicad_sch")
|
|
191
|
+
"""
|
|
192
|
+
logger.info(f"[MCP] save_schematic called with path: {file_path}")
|
|
193
|
+
|
|
194
|
+
# Check if schematic is loaded
|
|
195
|
+
schematic = get_current_schematic()
|
|
196
|
+
if schematic is None:
|
|
197
|
+
logger.error("[MCP] No schematic loaded")
|
|
198
|
+
return {
|
|
199
|
+
"success": False,
|
|
200
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
201
|
+
"message": "No schematic is currently loaded. Please load or create a schematic first.",
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
if file_path:
|
|
206
|
+
# Save to specified path
|
|
207
|
+
path = Path(file_path)
|
|
208
|
+
if not path.suffix == ".kicad_sch":
|
|
209
|
+
logger.error(f"[MCP] Invalid file extension: {file_path}")
|
|
210
|
+
return {
|
|
211
|
+
"success": False,
|
|
212
|
+
"error": "INVALID_FILE",
|
|
213
|
+
"message": "File must have .kicad_sch extension",
|
|
214
|
+
}
|
|
215
|
+
schematic.save(str(path))
|
|
216
|
+
logger.info(f"[MCP] Saved schematic to: {path}")
|
|
217
|
+
return {
|
|
218
|
+
"success": True,
|
|
219
|
+
"message": f"Saved schematic to: {path.name}",
|
|
220
|
+
"file_path": str(path),
|
|
221
|
+
}
|
|
222
|
+
else:
|
|
223
|
+
# Save to original location
|
|
224
|
+
schematic.save()
|
|
225
|
+
logger.info("[MCP] Saved schematic to original location")
|
|
226
|
+
return {
|
|
227
|
+
"success": True,
|
|
228
|
+
"message": "Saved schematic to original location",
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f"[MCP] Error saving schematic: {e}", exc_info=True)
|
|
233
|
+
return {
|
|
234
|
+
"success": False,
|
|
235
|
+
"error": "SAVE_ERROR",
|
|
236
|
+
"message": f"Failed to save schematic: {str(e)}",
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@mcp.tool()
|
|
241
|
+
async def get_schematic_info() -> dict:
|
|
242
|
+
"""
|
|
243
|
+
Get information about the currently loaded schematic.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Dictionary with schematic metadata
|
|
247
|
+
|
|
248
|
+
Examples:
|
|
249
|
+
>>> result = await get_schematic_info()
|
|
250
|
+
>>> print(f"Components: {result['component_count']}")
|
|
251
|
+
"""
|
|
252
|
+
logger.info("[MCP] get_schematic_info called")
|
|
253
|
+
|
|
254
|
+
# Check if schematic is loaded
|
|
255
|
+
schematic = get_current_schematic()
|
|
256
|
+
if schematic is None:
|
|
257
|
+
logger.error("[MCP] No schematic loaded")
|
|
258
|
+
return {
|
|
259
|
+
"success": False,
|
|
260
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
261
|
+
"message": "No schematic is currently loaded",
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
# Collect schematic information
|
|
266
|
+
components = list(schematic.components.all())
|
|
267
|
+
component_refs = [c.reference for c in components]
|
|
268
|
+
|
|
269
|
+
info = {
|
|
270
|
+
"success": True,
|
|
271
|
+
"project_name": schematic.title_block.title,
|
|
272
|
+
"uuid": str(schematic.uuid),
|
|
273
|
+
"component_count": len(components),
|
|
274
|
+
"component_references": component_refs,
|
|
275
|
+
"lib_symbols_count": len(schematic.lib_symbols),
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
logger.info(
|
|
279
|
+
f"[MCP] Schematic info: {info['project_name']} ({info['component_count']} components)"
|
|
280
|
+
)
|
|
281
|
+
return info
|
|
282
|
+
|
|
283
|
+
except Exception as e:
|
|
284
|
+
logger.error(f"[MCP] Error getting schematic info: {e}", exc_info=True)
|
|
285
|
+
return {
|
|
286
|
+
"success": False,
|
|
287
|
+
"error": "INTERNAL_ERROR",
|
|
288
|
+
"message": f"Failed to get schematic info: {str(e)}",
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# ========== Register Pin Discovery Tools ==========
|
|
293
|
+
|
|
294
|
+
# Register the pin discovery tools from pin_discovery.py
|
|
295
|
+
mcp.tool()(get_component_pins)
|
|
296
|
+
mcp.tool()(find_pins_by_name)
|
|
297
|
+
mcp.tool()(find_pins_by_type)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# ========== Register Component Management Tools ==========
|
|
301
|
+
|
|
302
|
+
# Register the component management tools from component_tools.py
|
|
303
|
+
mcp.tool()(add_component)
|
|
304
|
+
mcp.tool()(list_components)
|
|
305
|
+
mcp.tool()(update_component)
|
|
306
|
+
mcp.tool()(remove_component)
|
|
307
|
+
mcp.tool()(filter_components)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ========== Register Connectivity Tools ==========
|
|
311
|
+
|
|
312
|
+
# Register the connectivity tools from connectivity_tools.py
|
|
313
|
+
mcp.tool()(add_wire)
|
|
314
|
+
mcp.tool()(add_label)
|
|
315
|
+
mcp.tool()(add_junction)
|
|
316
|
+
mcp.tool()(connect_components)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
# ========== Register Consolidated Tools ==========
|
|
320
|
+
|
|
321
|
+
# Register the 8 consolidated CRUD tools for schematic management
|
|
322
|
+
# These tools consolidate all operations by entity type with action parameters
|
|
323
|
+
mcp.tool()(manage_schematic) # Schematic: create, read, save, load
|
|
324
|
+
mcp.tool()(manage_components) # Components: add, list, get_pins, update, remove
|
|
325
|
+
mcp.tool()(manage_wires) # Wires: add, remove
|
|
326
|
+
mcp.tool()(manage_labels) # Labels: add, remove
|
|
327
|
+
mcp.tool()(manage_text_boxes) # TextBoxes: add, update, remove
|
|
328
|
+
mcp.tool()(manage_power) # Power: add, list, remove
|
|
329
|
+
mcp.tool()(manage_sheets) # Sheets: add, set_context, list, remove
|
|
330
|
+
mcp.tool()(manage_global_labels) # GlobalLabels: add, remove
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# ========== Server Entry Point ==========
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def main() -> None:
|
|
337
|
+
"""
|
|
338
|
+
Main entry point for the MCP server.
|
|
339
|
+
|
|
340
|
+
Starts the FastMCP server with STDIO transport for Claude Desktop integration.
|
|
341
|
+
"""
|
|
342
|
+
logger.info("Starting kicad-sch-api MCP server...")
|
|
343
|
+
logger.info(f"kicad-sch-api version: {ksa.__version__}")
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
# Run the MCP server with STDIO transport
|
|
347
|
+
mcp.run(transport="stdio")
|
|
348
|
+
except KeyboardInterrupt:
|
|
349
|
+
logger.info("Server shutdown requested")
|
|
350
|
+
sys.exit(0)
|
|
351
|
+
except Exception as e:
|
|
352
|
+
logger.error(f"Server error: {e}", exc_info=True)
|
|
353
|
+
sys.exit(1)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
if __name__ == "__main__":
|
|
357
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP tools for kicad-sch-api.
|
|
3
|
+
|
|
4
|
+
Provides tool implementations for pin discovery, component manipulation,
|
|
5
|
+
and connectivity operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from mcp_server.tools.connectivity_tools import (
|
|
9
|
+
add_junction,
|
|
10
|
+
add_label,
|
|
11
|
+
add_wire,
|
|
12
|
+
connect_components,
|
|
13
|
+
)
|
|
14
|
+
from mcp_server.tools.pin_discovery import (
|
|
15
|
+
find_pins_by_name,
|
|
16
|
+
find_pins_by_type,
|
|
17
|
+
get_component_pins,
|
|
18
|
+
get_current_schematic,
|
|
19
|
+
set_current_schematic,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"get_component_pins",
|
|
24
|
+
"find_pins_by_name",
|
|
25
|
+
"find_pins_by_type",
|
|
26
|
+
"get_current_schematic",
|
|
27
|
+
"set_current_schematic",
|
|
28
|
+
"add_wire",
|
|
29
|
+
"add_label",
|
|
30
|
+
"add_junction",
|
|
31
|
+
"connect_components",
|
|
32
|
+
]
|