kicad-sch-api 0.4.1__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.
Files changed (66) hide show
  1. kicad_sch_api/__init__.py +67 -2
  2. kicad_sch_api/cli/kicad_to_python.py +169 -0
  3. kicad_sch_api/collections/__init__.py +23 -8
  4. kicad_sch_api/collections/base.py +369 -59
  5. kicad_sch_api/collections/components.py +1376 -187
  6. kicad_sch_api/collections/junctions.py +129 -289
  7. kicad_sch_api/collections/labels.py +391 -287
  8. kicad_sch_api/collections/wires.py +202 -316
  9. kicad_sch_api/core/__init__.py +37 -2
  10. kicad_sch_api/core/component_bounds.py +34 -12
  11. kicad_sch_api/core/components.py +146 -7
  12. kicad_sch_api/core/config.py +25 -12
  13. kicad_sch_api/core/connectivity.py +692 -0
  14. kicad_sch_api/core/exceptions.py +175 -0
  15. kicad_sch_api/core/factories/element_factory.py +3 -1
  16. kicad_sch_api/core/formatter.py +24 -7
  17. kicad_sch_api/core/geometry.py +94 -5
  18. kicad_sch_api/core/managers/__init__.py +4 -0
  19. kicad_sch_api/core/managers/base.py +76 -0
  20. kicad_sch_api/core/managers/file_io.py +3 -1
  21. kicad_sch_api/core/managers/format_sync.py +3 -2
  22. kicad_sch_api/core/managers/graphics.py +3 -2
  23. kicad_sch_api/core/managers/hierarchy.py +661 -0
  24. kicad_sch_api/core/managers/metadata.py +4 -2
  25. kicad_sch_api/core/managers/sheet.py +52 -14
  26. kicad_sch_api/core/managers/text_elements.py +3 -2
  27. kicad_sch_api/core/managers/validation.py +3 -2
  28. kicad_sch_api/core/managers/wire.py +112 -54
  29. kicad_sch_api/core/parsing_utils.py +63 -0
  30. kicad_sch_api/core/pin_utils.py +103 -9
  31. kicad_sch_api/core/schematic.py +343 -29
  32. kicad_sch_api/core/types.py +79 -7
  33. kicad_sch_api/exporters/__init__.py +10 -0
  34. kicad_sch_api/exporters/python_generator.py +610 -0
  35. kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
  36. kicad_sch_api/geometry/__init__.py +15 -3
  37. kicad_sch_api/geometry/routing.py +211 -0
  38. kicad_sch_api/parsers/elements/label_parser.py +30 -8
  39. kicad_sch_api/parsers/elements/symbol_parser.py +255 -83
  40. kicad_sch_api/utils/logging.py +555 -0
  41. kicad_sch_api/utils/logging_decorators.py +587 -0
  42. kicad_sch_api/utils/validation.py +16 -22
  43. kicad_sch_api/wrappers/__init__.py +14 -0
  44. kicad_sch_api/wrappers/base.py +89 -0
  45. kicad_sch_api/wrappers/wire.py +198 -0
  46. kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
  47. kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
  48. kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
  49. {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
  50. mcp_server/__init__.py +34 -0
  51. mcp_server/example_logging_integration.py +506 -0
  52. mcp_server/models.py +252 -0
  53. mcp_server/server.py +357 -0
  54. mcp_server/tools/__init__.py +32 -0
  55. mcp_server/tools/component_tools.py +516 -0
  56. mcp_server/tools/connectivity_tools.py +532 -0
  57. mcp_server/tools/consolidated_tools.py +1216 -0
  58. mcp_server/tools/pin_discovery.py +333 -0
  59. mcp_server/utils/__init__.py +38 -0
  60. mcp_server/utils/logging.py +127 -0
  61. mcp_server/utils.py +36 -0
  62. kicad_sch_api-0.4.1.dist-info/METADATA +0 -491
  63. kicad_sch_api-0.4.1.dist-info/RECORD +0 -87
  64. kicad_sch_api-0.4.1.dist-info/entry_points.txt +0 -2
  65. {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
  66. {kicad_sch_api-0.4.1.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)