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
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP tools for pin discovery and semantic lookup.
|
|
3
|
+
|
|
4
|
+
Provides MCP-compatible tools that wrap the kicad-sch-api pin discovery
|
|
5
|
+
functionality for use by AI assistants.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from fastmcp import Context
|
|
13
|
+
else:
|
|
14
|
+
try:
|
|
15
|
+
from fastmcp import Context
|
|
16
|
+
except ImportError:
|
|
17
|
+
Context = None # type: ignore
|
|
18
|
+
|
|
19
|
+
import kicad_sch_api as ksa
|
|
20
|
+
from kicad_sch_api.core.exceptions import LibraryError
|
|
21
|
+
from mcp_server.models import ComponentPinsOutput, ErrorOutput, PinInfoOutput, PointModel
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Global schematic state (simplified for initial implementation)
|
|
28
|
+
_current_schematic: Optional[ksa.Schematic] = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def set_current_schematic(schematic: ksa.Schematic) -> None:
|
|
32
|
+
"""Set the current working schematic for MCP tools."""
|
|
33
|
+
global _current_schematic
|
|
34
|
+
_current_schematic = schematic
|
|
35
|
+
logger.info(f"Set current schematic: {schematic}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_current_schematic() -> Optional[ksa.Schematic]:
|
|
39
|
+
"""Get the current working schematic."""
|
|
40
|
+
return _current_schematic
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def get_component_pins(
|
|
44
|
+
reference: str,
|
|
45
|
+
ctx: Optional[Context] = None,
|
|
46
|
+
) -> ComponentPinsOutput | ErrorOutput:
|
|
47
|
+
"""
|
|
48
|
+
Get comprehensive pin information for a component.
|
|
49
|
+
|
|
50
|
+
Returns all pins for the specified component with complete metadata including
|
|
51
|
+
positions, electrical types, and pin names. Positions are in absolute schematic
|
|
52
|
+
coordinates accounting for component rotation.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
reference: Component reference designator (e.g., "R1", "U2", "IC1")
|
|
56
|
+
ctx: MCP context for progress reporting (optional)
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
ComponentPinsOutput with all pin information, or ErrorOutput on failure
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
>>> # Get all pins for a resistor
|
|
63
|
+
>>> result = await get_component_pins("R1")
|
|
64
|
+
>>> print(f"Found {result.pin_count} pins")
|
|
65
|
+
|
|
66
|
+
>>> # Get pins for an IC
|
|
67
|
+
>>> result = await get_component_pins("U1")
|
|
68
|
+
>>> for pin in result.pins:
|
|
69
|
+
... print(f"Pin {pin.number}: {pin.name} @ ({pin.position.x}, {pin.position.y})")
|
|
70
|
+
"""
|
|
71
|
+
logger.info(f"[MCP] get_component_pins called for reference: {reference}")
|
|
72
|
+
|
|
73
|
+
# Report progress if context available
|
|
74
|
+
if ctx:
|
|
75
|
+
await ctx.report_progress(0, 100, f"Looking up component {reference}")
|
|
76
|
+
|
|
77
|
+
# Check if schematic is loaded
|
|
78
|
+
if _current_schematic is None:
|
|
79
|
+
logger.error("[MCP] No schematic loaded")
|
|
80
|
+
return ErrorOutput(
|
|
81
|
+
error="NO_SCHEMATIC_LOADED",
|
|
82
|
+
message="No schematic is currently loaded. Please load or create a schematic first.",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
if ctx:
|
|
87
|
+
await ctx.report_progress(25, 100, f"Finding pins for {reference}")
|
|
88
|
+
|
|
89
|
+
# Get pin information from library
|
|
90
|
+
pins = _current_schematic.components.get_pins_info(reference)
|
|
91
|
+
|
|
92
|
+
if pins is None:
|
|
93
|
+
logger.warning(f"[MCP] Component not found: {reference}")
|
|
94
|
+
return ErrorOutput(
|
|
95
|
+
error="COMPONENT_NOT_FOUND",
|
|
96
|
+
message=f"Component '{reference}' not found in schematic",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if ctx:
|
|
100
|
+
await ctx.report_progress(75, 100, f"Converting {len(pins)} pins to MCP format")
|
|
101
|
+
|
|
102
|
+
# Convert to MCP output models
|
|
103
|
+
pin_outputs = []
|
|
104
|
+
for pin in pins:
|
|
105
|
+
pin_output = PinInfoOutput(
|
|
106
|
+
number=pin.number,
|
|
107
|
+
name=pin.name,
|
|
108
|
+
position=PointModel(x=pin.position.x, y=pin.position.y),
|
|
109
|
+
electrical_type=pin.electrical_type.value,
|
|
110
|
+
shape=pin.shape.value,
|
|
111
|
+
length=pin.length,
|
|
112
|
+
orientation=pin.orientation,
|
|
113
|
+
uuid=pin.uuid,
|
|
114
|
+
)
|
|
115
|
+
pin_outputs.append(pin_output)
|
|
116
|
+
|
|
117
|
+
# Get component info
|
|
118
|
+
component = _current_schematic.components.get(reference)
|
|
119
|
+
if not component:
|
|
120
|
+
logger.error(f"[MCP] Component lookup failed after pin retrieval: {reference}")
|
|
121
|
+
return ErrorOutput(
|
|
122
|
+
error="INTERNAL_ERROR",
|
|
123
|
+
message=f"Internal error: Component '{reference}' lookup inconsistent",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
result = ComponentPinsOutput(
|
|
127
|
+
reference=component.reference,
|
|
128
|
+
lib_id=component.lib_id,
|
|
129
|
+
pins=pin_outputs,
|
|
130
|
+
pin_count=len(pin_outputs),
|
|
131
|
+
success=True,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if ctx:
|
|
135
|
+
await ctx.report_progress(100, 100, f"Complete: {len(pins)} pins retrieved")
|
|
136
|
+
|
|
137
|
+
logger.info(f"[MCP] Successfully retrieved {len(pins)} pins for {reference}")
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
except LibraryError as e:
|
|
141
|
+
logger.error(f"[MCP] Library error for {reference}: {e}")
|
|
142
|
+
return ErrorOutput(
|
|
143
|
+
error="LIBRARY_ERROR",
|
|
144
|
+
message=f"Symbol library error: {str(e)}",
|
|
145
|
+
)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(f"[MCP] Unexpected error for {reference}: {e}", exc_info=True)
|
|
148
|
+
return ErrorOutput(
|
|
149
|
+
error="INTERNAL_ERROR",
|
|
150
|
+
message=f"Unexpected error: {str(e)}",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def find_pins_by_name(
|
|
155
|
+
reference: str,
|
|
156
|
+
name_pattern: str,
|
|
157
|
+
case_sensitive: bool = False,
|
|
158
|
+
ctx: Optional[Context] = None,
|
|
159
|
+
) -> dict:
|
|
160
|
+
"""
|
|
161
|
+
Find pin numbers matching a name pattern.
|
|
162
|
+
|
|
163
|
+
Supports wildcard patterns (e.g., "CLK*", "*IN*") for semantic pin lookup.
|
|
164
|
+
By default, matching is case-insensitive for maximum flexibility.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
reference: Component reference designator (e.g., "R1", "U2")
|
|
168
|
+
name_pattern: Name pattern to search for (e.g., "VCC", "CLK*", "*IN*")
|
|
169
|
+
case_sensitive: If True, matching is case-sensitive (default: False)
|
|
170
|
+
ctx: MCP context for progress reporting (optional)
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Dictionary with pin_numbers list and metadata, or error information
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
>>> # Find all clock pins
|
|
177
|
+
>>> result = await find_pins_by_name("U1", "CLK*")
|
|
178
|
+
>>> print(f"Clock pins: {result['pin_numbers']}")
|
|
179
|
+
|
|
180
|
+
>>> # Find power pins (case-insensitive)
|
|
181
|
+
>>> result = await find_pins_by_name("U1", "vcc")
|
|
182
|
+
>>> print(f"VCC pins: {result['pin_numbers']}")
|
|
183
|
+
"""
|
|
184
|
+
logger.info(
|
|
185
|
+
f"[MCP] find_pins_by_name called for {reference} with pattern '{name_pattern}' "
|
|
186
|
+
f"(case_sensitive={case_sensitive})"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if ctx:
|
|
190
|
+
await ctx.report_progress(0, 100, f"Searching for pins matching '{name_pattern}'")
|
|
191
|
+
|
|
192
|
+
# Check if schematic is loaded
|
|
193
|
+
if _current_schematic is None:
|
|
194
|
+
logger.error("[MCP] No schematic loaded")
|
|
195
|
+
return {
|
|
196
|
+
"success": False,
|
|
197
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
198
|
+
"message": "No schematic is currently loaded",
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
if ctx:
|
|
203
|
+
await ctx.report_progress(50, 100, f"Searching component {reference}")
|
|
204
|
+
|
|
205
|
+
# Find pins by name pattern
|
|
206
|
+
pin_numbers = _current_schematic.components.find_pins_by_name(
|
|
207
|
+
reference, name_pattern, case_sensitive
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if pin_numbers is None:
|
|
211
|
+
logger.warning(f"[MCP] Component not found: {reference}")
|
|
212
|
+
return {
|
|
213
|
+
"success": False,
|
|
214
|
+
"error": "COMPONENT_NOT_FOUND",
|
|
215
|
+
"message": f"Component '{reference}' not found in schematic",
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if ctx:
|
|
219
|
+
await ctx.report_progress(100, 100, f"Found {len(pin_numbers)} matching pins")
|
|
220
|
+
|
|
221
|
+
logger.info(
|
|
222
|
+
f"[MCP] Found {len(pin_numbers)} pins matching '{name_pattern}' in {reference}"
|
|
223
|
+
)
|
|
224
|
+
return {
|
|
225
|
+
"success": True,
|
|
226
|
+
"reference": reference,
|
|
227
|
+
"pattern": name_pattern,
|
|
228
|
+
"case_sensitive": case_sensitive,
|
|
229
|
+
"pin_numbers": pin_numbers,
|
|
230
|
+
"count": len(pin_numbers),
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
except ValueError as e:
|
|
234
|
+
logger.error(f"[MCP] Validation error: {e}")
|
|
235
|
+
return {
|
|
236
|
+
"success": False,
|
|
237
|
+
"error": "VALIDATION_ERROR",
|
|
238
|
+
"message": str(e),
|
|
239
|
+
}
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(f"[MCP] Unexpected error: {e}", exc_info=True)
|
|
242
|
+
return {
|
|
243
|
+
"success": False,
|
|
244
|
+
"error": "INTERNAL_ERROR",
|
|
245
|
+
"message": f"Unexpected error: {str(e)}",
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
async def find_pins_by_type(
|
|
250
|
+
reference: str,
|
|
251
|
+
pin_type: str,
|
|
252
|
+
ctx: Optional[Context] = None,
|
|
253
|
+
) -> dict:
|
|
254
|
+
"""
|
|
255
|
+
Find pin numbers by electrical type.
|
|
256
|
+
|
|
257
|
+
Returns all pins of a specific electrical type (e.g., all inputs, all power pins).
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
reference: Component reference designator (e.g., "R1", "U2")
|
|
261
|
+
pin_type: Electrical type filter. Must be one of:
|
|
262
|
+
"input", "output", "bidirectional", "passive", "power_in",
|
|
263
|
+
"power_out", "open_collector", "open_emitter", "tri_state",
|
|
264
|
+
"unspecified", "free", "no_connect"
|
|
265
|
+
ctx: MCP context for progress reporting (optional)
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Dictionary with pin_numbers list and metadata, or error information
|
|
269
|
+
|
|
270
|
+
Examples:
|
|
271
|
+
>>> # Find all input pins
|
|
272
|
+
>>> result = await find_pins_by_type("U1", "input")
|
|
273
|
+
>>> print(f"Input pins: {result['pin_numbers']}")
|
|
274
|
+
|
|
275
|
+
>>> # Find all power input pins
|
|
276
|
+
>>> result = await find_pins_by_type("U1", "power_in")
|
|
277
|
+
>>> print(f"Power pins: {result['pin_numbers']}")
|
|
278
|
+
"""
|
|
279
|
+
logger.info(f"[MCP] find_pins_by_type called for {reference} with type '{pin_type}'")
|
|
280
|
+
|
|
281
|
+
if ctx:
|
|
282
|
+
await ctx.report_progress(0, 100, f"Searching for {pin_type} pins")
|
|
283
|
+
|
|
284
|
+
# Check if schematic is loaded
|
|
285
|
+
if _current_schematic is None:
|
|
286
|
+
logger.error("[MCP] No schematic loaded")
|
|
287
|
+
return {
|
|
288
|
+
"success": False,
|
|
289
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
290
|
+
"message": "No schematic is currently loaded",
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
if ctx:
|
|
295
|
+
await ctx.report_progress(50, 100, f"Filtering pins by type {pin_type}")
|
|
296
|
+
|
|
297
|
+
# Find pins by type
|
|
298
|
+
pin_numbers = _current_schematic.components.find_pins_by_type(reference, pin_type)
|
|
299
|
+
|
|
300
|
+
if pin_numbers is None:
|
|
301
|
+
logger.warning(f"[MCP] Component not found: {reference}")
|
|
302
|
+
return {
|
|
303
|
+
"success": False,
|
|
304
|
+
"error": "COMPONENT_NOT_FOUND",
|
|
305
|
+
"message": f"Component '{reference}' not found in schematic",
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if ctx:
|
|
309
|
+
await ctx.report_progress(100, 100, f"Found {len(pin_numbers)} pins of type {pin_type}")
|
|
310
|
+
|
|
311
|
+
logger.info(f"[MCP] Found {len(pin_numbers)} pins of type '{pin_type}' in {reference}")
|
|
312
|
+
return {
|
|
313
|
+
"success": True,
|
|
314
|
+
"reference": reference,
|
|
315
|
+
"pin_type": pin_type,
|
|
316
|
+
"pin_numbers": pin_numbers,
|
|
317
|
+
"count": len(pin_numbers),
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
except ValueError as e:
|
|
321
|
+
logger.error(f"[MCP] Validation error: {e}")
|
|
322
|
+
return {
|
|
323
|
+
"success": False,
|
|
324
|
+
"error": "VALIDATION_ERROR",
|
|
325
|
+
"message": str(e),
|
|
326
|
+
}
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.error(f"[MCP] Unexpected error: {e}", exc_info=True)
|
|
329
|
+
return {
|
|
330
|
+
"success": False,
|
|
331
|
+
"error": "INTERNAL_ERROR",
|
|
332
|
+
"message": f"Unexpected error: {str(e)}",
|
|
333
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""MCP server utilities and helpers."""
|
|
2
|
+
|
|
3
|
+
from .logging import (
|
|
4
|
+
configure_mcp_logging,
|
|
5
|
+
get_mcp_logger,
|
|
6
|
+
operation_context,
|
|
7
|
+
timer_decorator,
|
|
8
|
+
log_exception,
|
|
9
|
+
setup_component_logging,
|
|
10
|
+
search_logs,
|
|
11
|
+
LogQuery,
|
|
12
|
+
log_operation,
|
|
13
|
+
log_timing,
|
|
14
|
+
log_errors,
|
|
15
|
+
ComponentLogger,
|
|
16
|
+
OperationTimer,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# Configuration
|
|
21
|
+
"configure_mcp_logging",
|
|
22
|
+
"get_mcp_logger",
|
|
23
|
+
# Context managers
|
|
24
|
+
"operation_context",
|
|
25
|
+
# Decorators
|
|
26
|
+
"log_operation",
|
|
27
|
+
"log_timing",
|
|
28
|
+
"log_errors",
|
|
29
|
+
"timer_decorator",
|
|
30
|
+
# Helpers
|
|
31
|
+
"ComponentLogger",
|
|
32
|
+
"OperationTimer",
|
|
33
|
+
"log_exception",
|
|
34
|
+
"setup_component_logging",
|
|
35
|
+
# Querying
|
|
36
|
+
"search_logs",
|
|
37
|
+
"LogQuery",
|
|
38
|
+
]
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Logging configuration and helpers for MCP server.
|
|
2
|
+
|
|
3
|
+
This module provides MCP server-specific logging setup with:
|
|
4
|
+
- Integration with kicad_sch_api logging
|
|
5
|
+
- MCP protocol logging
|
|
6
|
+
- Tool invocation tracking
|
|
7
|
+
- Performance metrics
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
# Import base logging utilities from kicad_sch_api
|
|
16
|
+
try:
|
|
17
|
+
from kicad_sch_api.utils.logging import (
|
|
18
|
+
configure_logging,
|
|
19
|
+
operation_context,
|
|
20
|
+
timer_decorator,
|
|
21
|
+
log_exception,
|
|
22
|
+
setup_component_logging,
|
|
23
|
+
search_logs,
|
|
24
|
+
LogQuery,
|
|
25
|
+
)
|
|
26
|
+
from kicad_sch_api.utils.logging_decorators import (
|
|
27
|
+
log_operation,
|
|
28
|
+
log_timing,
|
|
29
|
+
log_errors,
|
|
30
|
+
ComponentLogger,
|
|
31
|
+
OperationTimer,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
LOGGING_AVAILABLE = True
|
|
35
|
+
except ImportError:
|
|
36
|
+
LOGGING_AVAILABLE = False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def configure_mcp_logging(
|
|
40
|
+
log_dir: Path = Path("logs"),
|
|
41
|
+
debug_level: bool = False,
|
|
42
|
+
json_format: bool = True,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Configure logging for MCP server.
|
|
45
|
+
|
|
46
|
+
Extends the base kicad_sch_api logging with MCP-specific setup.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
log_dir: Directory for log files (default: logs/)
|
|
50
|
+
debug_level: Enable DEBUG level logging (default: False)
|
|
51
|
+
json_format: Use JSON format (default: True, production)
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
# Development setup
|
|
55
|
+
configure_mcp_logging(debug_level=True, json_format=False)
|
|
56
|
+
|
|
57
|
+
# Production setup
|
|
58
|
+
configure_mcp_logging(debug_level=False, json_format=True)
|
|
59
|
+
"""
|
|
60
|
+
if not LOGGING_AVAILABLE:
|
|
61
|
+
# Fallback to basic logging if kicad_sch_api logging unavailable
|
|
62
|
+
logging.basicConfig(
|
|
63
|
+
level=logging.DEBUG if debug_level else logging.INFO,
|
|
64
|
+
format="%(asctime)s [%(levelname)-8s] %(name)s: %(message)s",
|
|
65
|
+
)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# Configure base logging
|
|
69
|
+
configure_logging(
|
|
70
|
+
log_dir=log_dir,
|
|
71
|
+
debug_level=debug_level,
|
|
72
|
+
json_format=json_format,
|
|
73
|
+
max_bytes=10 * 1024 * 1024, # 10MB
|
|
74
|
+
backup_count=5,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Get MCP server logger
|
|
78
|
+
logger = logging.getLogger("mcp_server")
|
|
79
|
+
logger.info(
|
|
80
|
+
f"MCP server logging configured: "
|
|
81
|
+
f"debug={debug_level}, json={json_format}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_mcp_logger(component: Optional[str] = None) -> logging.Logger:
|
|
86
|
+
"""Get a logger for MCP server operations.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
component: Optional component name for context
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Configured logger instance
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
logger = get_mcp_logger("tools")
|
|
96
|
+
logger.info("Tool invoked")
|
|
97
|
+
|
|
98
|
+
# Component-specific logger
|
|
99
|
+
logger = get_mcp_logger("resistor_R1")
|
|
100
|
+
logger.debug("Setting value")
|
|
101
|
+
"""
|
|
102
|
+
if component:
|
|
103
|
+
return logging.getLogger(f"mcp_server.{component}")
|
|
104
|
+
return logging.getLogger("mcp_server")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Re-export commonly used utilities for convenience
|
|
108
|
+
__all__ = [
|
|
109
|
+
# Configuration
|
|
110
|
+
"configure_mcp_logging",
|
|
111
|
+
"get_mcp_logger",
|
|
112
|
+
# Context managers
|
|
113
|
+
"operation_context",
|
|
114
|
+
# Decorators
|
|
115
|
+
"log_operation",
|
|
116
|
+
"log_timing",
|
|
117
|
+
"log_errors",
|
|
118
|
+
"timer_decorator",
|
|
119
|
+
# Helpers
|
|
120
|
+
"ComponentLogger",
|
|
121
|
+
"OperationTimer",
|
|
122
|
+
"log_exception",
|
|
123
|
+
"setup_component_logging",
|
|
124
|
+
# Querying
|
|
125
|
+
"search_logs",
|
|
126
|
+
"LogQuery",
|
|
127
|
+
]
|
mcp_server/utils.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for the MCP server.
|
|
3
|
+
|
|
4
|
+
Provides logging configuration and helper functions for MCP tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def configure_mcp_logging(level: int = logging.INFO) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Configure logging for the MCP server.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
level: Logging level (default: INFO)
|
|
18
|
+
"""
|
|
19
|
+
logging.basicConfig(
|
|
20
|
+
level=level,
|
|
21
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
22
|
+
stream=sys.stderr,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_mcp_logger(name: str) -> logging.Logger:
|
|
27
|
+
"""
|
|
28
|
+
Get a logger instance for MCP server components.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
name: Logger name (typically __name__)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Configured logger instance
|
|
35
|
+
"""
|
|
36
|
+
return logging.getLogger(name)
|