kicad-sch-api 0.1.7__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kicad-sch-api might be problematic. Click here for more details.

@@ -1,1511 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Basic KiCAD Schematic MCP Server
4
-
5
- Simple FastMCP server for schematic manipulation with stateful operation.
6
- """
7
-
8
- import logging
9
- import sys
10
- import traceback
11
- from typing import Optional, Dict, Any, List, Tuple
12
-
13
- # Configure logging to stderr (required for MCP STDIO)
14
- logging.basicConfig(
15
- level=logging.INFO,
16
- format='%(asctime)s - %(levelname)s - %(message)s',
17
- handlers=[logging.StreamHandler(sys.stderr)]
18
- )
19
- logger = logging.getLogger(__name__)
20
-
21
- try:
22
- from mcp.server import FastMCP
23
- except ImportError:
24
- logger.error("MCP not installed. Run: uv add 'mcp[cli]'")
25
- sys.exit(1)
26
-
27
- try:
28
- import kicad_sch_api as ksa
29
- from ..library.cache import get_symbol_cache
30
- from ..discovery.search_index import get_search_index, ensure_index_built
31
- except ImportError:
32
- logger.error("kicad-sch-api not found. Make sure it's installed.")
33
- sys.exit(1)
34
-
35
-
36
- class SchematicState:
37
- """Maintains current schematic state for stateful operations."""
38
-
39
- def __init__(self):
40
- self.current_schematic: Optional[ksa.Schematic] = None
41
- self.current_file_path: Optional[str] = None
42
-
43
- def load_schematic(self, file_path: str) -> bool:
44
- """Load a schematic file."""
45
- try:
46
- self.current_schematic = ksa.load_schematic(file_path)
47
- self.current_file_path = file_path
48
- logger.info(f"Loaded schematic: {file_path}")
49
- return True
50
- except Exception as e:
51
- logger.error(f"Failed to load schematic {file_path}: {e}")
52
- return False
53
-
54
- def create_schematic(self, name: str) -> bool:
55
- """Create a new schematic."""
56
- try:
57
- self.current_schematic = ksa.create_schematic(name)
58
- self.current_file_path = None # Not saved yet
59
- logger.info(f"Created new schematic: {name}")
60
- return True
61
- except Exception as e:
62
- logger.error(f"Failed to create schematic {name}: {e}")
63
- return False
64
-
65
- def save_schematic(self, file_path: Optional[str] = None) -> bool:
66
- """Save the current schematic."""
67
- if not self.current_schematic:
68
- return False
69
-
70
- try:
71
- save_path = file_path or self.current_file_path
72
- if not save_path:
73
- return False
74
-
75
- self.current_schematic.save(save_path)
76
- self.current_file_path = save_path
77
- logger.info(f"Saved schematic to: {save_path}")
78
- return True
79
- except Exception as e:
80
- logger.error(f"Failed to save schematic: {e}")
81
- return False
82
-
83
- def is_loaded(self) -> bool:
84
- """Check if a schematic is currently loaded."""
85
- return self.current_schematic is not None
86
-
87
-
88
- # Global state instance
89
- state = SchematicState()
90
-
91
- # Initialize FastMCP server
92
- mcp = FastMCP("KiCAD-Sch-API")
93
-
94
- # Initialize discovery system on server startup
95
- logger.info("Initializing component discovery system...")
96
- try:
97
- # Ensure search index is available (build if needed)
98
- component_count = ensure_index_built()
99
- logger.info(f"Component discovery ready: {component_count} components indexed")
100
- except Exception as e:
101
- logger.warning(f"Component discovery initialization failed: {e}")
102
- logger.warning("Search features may not work until manually initialized")
103
-
104
- @mcp.tool()
105
- def create_schematic(name: str) -> Dict[str, Any]:
106
- """Create a new schematic file."""
107
- try:
108
- success = state.create_schematic(name)
109
- return {
110
- "success": success,
111
- "message": f"Created schematic: {name}" if success else "Failed to create schematic",
112
- "current_schematic": name if success else None
113
- }
114
- except Exception as e:
115
- return {
116
- "success": False,
117
- "message": f"Error creating schematic: {str(e)}",
118
- "errorDetails": traceback.format_exc()
119
- }
120
-
121
- @mcp.tool()
122
- def load_schematic(file_path: str) -> Dict[str, Any]:
123
- """Load an existing schematic file."""
124
- try:
125
- success = state.load_schematic(file_path)
126
- return {
127
- "success": success,
128
- "message": f"Loaded schematic: {file_path}" if success else f"Failed to load: {file_path}",
129
- "current_schematic": file_path if success else None
130
- }
131
- except Exception as e:
132
- return {
133
- "success": False,
134
- "message": f"Error loading schematic: {str(e)}",
135
- "errorDetails": traceback.format_exc()
136
- }
137
-
138
- @mcp.tool()
139
- def save_schematic(file_path: Optional[str] = None) -> Dict[str, Any]:
140
- """Save the current schematic to a file."""
141
- try:
142
- if not state.is_loaded():
143
- return {
144
- "success": False,
145
- "message": "No schematic loaded"
146
- }
147
-
148
- success = state.save_schematic(file_path)
149
- save_path = file_path or state.current_file_path
150
- return {
151
- "success": success,
152
- "message": f"Saved to: {save_path}" if success else "Failed to save",
153
- "file_path": save_path if success else None
154
- }
155
- except Exception as e:
156
- return {
157
- "success": False,
158
- "message": f"Error saving schematic: {str(e)}",
159
- "errorDetails": traceback.format_exc()
160
- }
161
-
162
- @mcp.tool()
163
- def get_schematic_info() -> Dict[str, Any]:
164
- """Get information about the currently loaded schematic."""
165
- try:
166
- if not state.is_loaded():
167
- return {
168
- "success": False,
169
- "message": "No schematic loaded"
170
- }
171
-
172
- sch = state.current_schematic
173
- component_count = len(sch.components) if hasattr(sch, 'components') else 0
174
-
175
- return {
176
- "success": True,
177
- "file_path": state.current_file_path,
178
- "component_count": component_count,
179
- "message": f"Schematic loaded with {component_count} components"
180
- }
181
- except Exception as e:
182
- return {
183
- "success": False,
184
- "message": f"Error getting schematic info: {str(e)}",
185
- "errorDetails": traceback.format_exc()
186
- }
187
-
188
- @mcp.tool()
189
- def add_component(lib_id: str, reference: str, value: str,
190
- position: Tuple[float, float], **properties) -> Dict[str, Any]:
191
- """Add a component to the current schematic with enhanced error handling.
192
-
193
- IMPORTANT DESIGN RULES:
194
- 1. REFERENCES: Always provide proper component references (R1, R2, C1, C2, etc.)
195
- - Never use "?" or leave references undefined
196
- - Use standard prefixes: R=resistor, C=capacitor, U=IC, D=diode, L=inductor
197
-
198
- 2. COMPONENT SPACING: Space components appropriately for readability
199
- - Minimum 50 units between components
200
- - Use grid-aligned positions (multiples of 25.4 or 12.7)
201
- - Leave room for labels and connections
202
-
203
- 3. FOOTPRINTS: Specify appropriate footprints in properties
204
- - Common SMD: R_0603_1608Metric, C_0603_1608Metric
205
- - Through-hole: R_Axial_DIN0207, C_Disc_D3.0mm
206
-
207
- 4. VALUES: Use standard component values
208
- - Resistors: 1k, 10k, 100k (E12 series)
209
- - Capacitors: 100nF, 10uF, 100uF
210
- """
211
- try:
212
- if not state.is_loaded():
213
- return {
214
- "success": False,
215
- "message": "No schematic loaded. Use load_schematic() or create_schematic() first."
216
- }
217
-
218
- # Pre-validate the component exists
219
- cache = get_symbol_cache()
220
- symbol = cache.get_symbol(lib_id)
221
-
222
- if not symbol:
223
- # Provide suggestions for invalid lib_id
224
- search_term = lib_id.split(":")[-1] if ":" in lib_id else lib_id
225
- suggestions = cache.search_symbols(search_term, limit=3)
226
-
227
- return {
228
- "success": False,
229
- "message": f"Component '{lib_id}' not found",
230
- "suggestions": [
231
- {
232
- "lib_id": s.lib_id,
233
- "name": s.name,
234
- "description": s.description,
235
- "common_footprints": _suggest_common_footprints(s)[:2]
236
- }
237
- for s in suggestions
238
- ],
239
- "help": "Use search_components() to find valid component lib_ids"
240
- }
241
-
242
- # Validate and fix reference if needed
243
- if reference == "?" or not reference or reference.strip() == "":
244
- # Auto-generate proper reference based on component type
245
- prefix = _get_reference_prefix(lib_id)
246
- reference = _generate_next_reference(state.current_schematic, prefix)
247
- logger.info(f"Auto-generated reference: {reference} for {lib_id}")
248
-
249
- # Add component using our API
250
- component = state.current_schematic.components.add(
251
- lib_id=lib_id,
252
- reference=reference,
253
- value=value,
254
- position=position,
255
- **properties
256
- )
257
-
258
- return {
259
- "success": True,
260
- "message": f"Added component {reference} ({lib_id}) at {position}",
261
- "reference": reference,
262
- "lib_id": lib_id,
263
- "position": position,
264
- "pins": len(symbol.pins),
265
- "suggested_footprints": _suggest_common_footprints(symbol)[:3]
266
- }
267
- except Exception as e:
268
- return {
269
- "success": False,
270
- "message": f"Error adding component: {str(e)}",
271
- "errorDetails": traceback.format_exc()
272
- }
273
-
274
- @mcp.tool()
275
- def list_components() -> Dict[str, Any]:
276
- """List all components in the current schematic."""
277
- try:
278
- if not state.is_loaded():
279
- return {
280
- "success": False,
281
- "message": "No schematic loaded"
282
- }
283
-
284
- components = []
285
- for comp in state.current_schematic.components:
286
- components.append({
287
- "reference": comp.reference,
288
- "lib_id": getattr(comp, 'lib_id', 'Unknown'),
289
- "value": getattr(comp, 'value', ''),
290
- "position": getattr(comp, 'position', (0, 0))
291
- })
292
-
293
- return {
294
- "success": True,
295
- "components": components,
296
- "count": len(components),
297
- "message": f"Found {len(components)} components"
298
- }
299
- except Exception as e:
300
- return {
301
- "success": False,
302
- "message": f"Error listing components: {str(e)}",
303
- "errorDetails": traceback.format_exc()
304
- }
305
-
306
- @mcp.tool()
307
- def add_wire(start_pos: Tuple[float, float], end_pos: Tuple[float, float]) -> Dict[str, Any]:
308
- """Add a wire connection between two points.
309
-
310
- ⚠️ WARNING: AVOID USING WIRES IN MOST CASES!
311
-
312
- BETTER ALTERNATIVES:
313
- 1. **Use hierarchical labels instead** - add_hierarchical_label() for clean connections
314
- 2. **Use global labels** - add_label() with appropriate label type
315
- 3. **Use power symbols** - for VCC/GND connections
316
-
317
- ONLY use wires for:
318
- - Very short connections (< 25 units)
319
- - Direct pin-to-pin connections on same component
320
- - Internal component connections that can't use labels
321
-
322
- For hierarchical designs, NEVER use wires - use hierarchical labels exclusively!
323
- """
324
- try:
325
- if not state.is_loaded():
326
- return {
327
- "success": False,
328
- "message": "No schematic loaded"
329
- }
330
-
331
- # Add wire using our API
332
- wire = state.current_schematic.wires.add(
333
- start=start_pos,
334
- end=end_pos
335
- )
336
-
337
- return {
338
- "success": True,
339
- "message": f"Added wire from {start_pos} to {end_pos}",
340
- "start": start_pos,
341
- "end": end_pos
342
- }
343
- except Exception as e:
344
- return {
345
- "success": False,
346
- "message": f"Error adding wire: {str(e)}",
347
- "errorDetails": traceback.format_exc()
348
- }
349
-
350
- @mcp.tool()
351
- def search_components(query: str, library: Optional[str] = None,
352
- category: Optional[str] = None, limit: int = 20) -> Dict[str, Any]:
353
- """Search for components across KiCAD symbol libraries using fast SQLite index."""
354
- try:
355
- # Ensure search index is built
356
- ensure_index_built()
357
-
358
- # Use the fast search index
359
- search_index = get_search_index()
360
- results = search_index.search(query, library, category, limit)
361
-
362
- # Enhance results with footprint suggestions and usage context
363
- formatted_results = []
364
- for result in results:
365
- # Get footprint suggestions based on component type
366
- common_footprints = _suggest_common_footprints_by_prefix(result["reference_prefix"])
367
-
368
- formatted_results.append({
369
- "lib_id": result["lib_id"],
370
- "name": result["name"],
371
- "description": result["description"],
372
- "library": result["library"],
373
- "pins": result["pin_count"],
374
- "reference_prefix": result["reference_prefix"],
375
- "keywords": result["keywords"],
376
- "category": result["category"],
377
- "match_score": round(result["match_score"], 2),
378
- "common_footprints": common_footprints,
379
- "usage_context": _get_usage_context_by_prefix(result["reference_prefix"])
380
- })
381
-
382
- return {
383
- "success": True,
384
- "results": formatted_results,
385
- "total_found": len(results),
386
- "query": query,
387
- "search_time_ms": 0, # SQLite searches are very fast
388
- "message": f"Found {len(results)} components matching '{query}'"
389
- }
390
-
391
- except Exception as e:
392
- return {
393
- "success": False,
394
- "message": f"Error searching components: {str(e)}",
395
- "errorDetails": traceback.format_exc()
396
- }
397
-
398
- @mcp.tool()
399
- def list_libraries() -> Dict[str, Any]:
400
- """List all available KiCAD symbol libraries."""
401
- try:
402
- cache = get_symbol_cache()
403
-
404
- # Get library statistics
405
- stats = cache.get_performance_stats()
406
-
407
- # Get available libraries from the cache
408
- libraries = []
409
- for lib_name, lib_stats in cache._lib_stats.items():
410
- libraries.append({
411
- "name": lib_name,
412
- "path": str(lib_stats.library_path),
413
- "symbol_count": lib_stats.symbol_count,
414
- "file_size_mb": round(lib_stats.file_size / (1024 * 1024), 2)
415
- })
416
-
417
- return {
418
- "success": True,
419
- "libraries": sorted(libraries, key=lambda x: x["name"]),
420
- "total_libraries": len(libraries),
421
- "cache_stats": {
422
- "total_symbols_cached": stats["total_symbols_cached"],
423
- "cache_hit_rate": stats["hit_rate_percent"]
424
- },
425
- "message": f"Found {len(libraries)} symbol libraries"
426
- }
427
-
428
- except Exception as e:
429
- return {
430
- "success": False,
431
- "message": f"Error listing libraries: {str(e)}",
432
- "errorDetails": traceback.format_exc()
433
- }
434
-
435
- @mcp.tool()
436
- def validate_component(lib_id: str) -> Dict[str, Any]:
437
- """Validate that a component exists and get its details."""
438
- try:
439
- # First check search index for fast validation
440
- search_index = get_search_index()
441
- result = search_index.validate_component(lib_id)
442
-
443
- if not result:
444
- # Search for similar components
445
- search_term = lib_id.split(":")[-1] if ":" in lib_id else lib_id
446
- suggestions = search_index.search(search_term, limit=5)
447
-
448
- return {
449
- "success": False,
450
- "exists": False,
451
- "lib_id": lib_id,
452
- "message": f"Component {lib_id} not found",
453
- "suggestions": [
454
- {
455
- "lib_id": s["lib_id"],
456
- "name": s["name"],
457
- "description": s["description"]
458
- }
459
- for s in suggestions
460
- ]
461
- }
462
-
463
- return {
464
- "success": True,
465
- "exists": True,
466
- "lib_id": lib_id,
467
- "details": {
468
- "name": result["name"],
469
- "description": result["description"],
470
- "library": result["library"],
471
- "pins": result["pin_count"],
472
- "reference_prefix": result["reference_prefix"],
473
- "keywords": result["keywords"],
474
- "category": result["category"]
475
- },
476
- "message": f"Component {lib_id} exists"
477
- }
478
-
479
- except Exception as e:
480
- return {
481
- "success": False,
482
- "message": f"Error validating component: {str(e)}",
483
- "errorDetails": traceback.format_exc()
484
- }
485
-
486
- @mcp.tool()
487
- def browse_library(library_name: str, limit: int = 50) -> Dict[str, Any]:
488
- """Browse components in a specific library."""
489
- try:
490
- cache = get_symbol_cache()
491
-
492
- # Get all symbols from the library
493
- symbols = cache.get_library_symbols(library_name)
494
-
495
- if not symbols:
496
- return {
497
- "success": False,
498
- "message": f"Library '{library_name}' not found or empty"
499
- }
500
-
501
- # Format results
502
- components = []
503
- for symbol in symbols[:limit]:
504
- components.append({
505
- "lib_id": symbol.lib_id,
506
- "name": symbol.name,
507
- "description": symbol.description,
508
- "pins": len(symbol.pins),
509
- "reference_prefix": symbol.reference_prefix
510
- })
511
-
512
- return {
513
- "success": True,
514
- "library": library_name,
515
- "components": components,
516
- "showing": len(components),
517
- "total_in_library": len(symbols),
518
- "message": f"Showing {len(components)} of {len(symbols)} components in {library_name}"
519
- }
520
-
521
- except Exception as e:
522
- return {
523
- "success": False,
524
- "message": f"Error browsing library: {str(e)}",
525
- "errorDetails": traceback.format_exc()
526
- }
527
-
528
- @mcp.tool()
529
- def list_component_categories() -> Dict[str, Any]:
530
- """List all component categories with counts."""
531
- try:
532
- search_index = get_search_index()
533
- categories = search_index.get_categories()
534
-
535
- return {
536
- "success": True,
537
- "categories": categories,
538
- "total_categories": len(categories),
539
- "message": f"Found {len(categories)} component categories"
540
- }
541
-
542
- except Exception as e:
543
- return {
544
- "success": False,
545
- "message": f"Error listing categories: {str(e)}",
546
- "errorDetails": traceback.format_exc()
547
- }
548
-
549
- @mcp.tool()
550
- def get_search_stats() -> Dict[str, Any]:
551
- """Get search index statistics and performance info."""
552
- try:
553
- search_index = get_search_index()
554
- stats = search_index.get_stats()
555
-
556
- return {
557
- "success": True,
558
- "stats": stats,
559
- "message": f"Search index contains {stats['total_components']} components from {stats['total_libraries']} libraries"
560
- }
561
-
562
- except Exception as e:
563
- return {
564
- "success": False,
565
- "message": f"Error getting search stats: {str(e)}",
566
- "errorDetails": traceback.format_exc()
567
- }
568
-
569
- @mcp.tool()
570
- def add_hierarchical_sheet(name: str, filename: str, position: Tuple[float, float],
571
- size: Tuple[float, float], project_name: str = "",
572
- page_number: str = "2") -> Dict[str, Any]:
573
- """Add a hierarchical sheet to the current schematic.
574
-
575
- HIERARCHICAL DESIGN WORKFLOW:
576
- 1. MAIN SCHEMATIC: Add this sheet to your main schematic
577
- 2. CREATE SUBCIRCUIT: Use create_schematic() for the sub-schematic
578
- 3. ADD SHEET PINS: Use add_sheet_pin() to create connection points on the sheet
579
- 4. ADD COMPONENTS: Switch to subcircuit, add components normally
580
- 5. ADD LABELS: In subcircuit, use add_hierarchical_label() on component pins
581
- 6. NAME MATCHING: Sheet pin names must exactly match hierarchical label names
582
-
583
- SHEET SIZING GUIDELINES:
584
- - Small: (60, 40) for 2-3 pins (power supplies, simple filters)
585
- - Medium: (80, 50) for 4-6 pins (op-amp circuits, basic MCU interfaces)
586
- - Large: (100, 60) for 7+ pins (complex subcircuits)
587
- - Maximum: (120, 80) only for very complex sheets (avoid if possible)
588
-
589
- CRITICAL: Sheets are currently being created too large! Use smaller sizes for cleaner schematics.
590
-
591
- POSITIONING:
592
- - Leave space around sheet for connections
593
- - Align with main schematic grid (multiples of 25.4)
594
- - Position where sheet pins will connect logically to main circuit
595
- """
596
- try:
597
- if not state.is_loaded():
598
- return {
599
- "success": False,
600
- "message": "No schematic loaded. Use load_schematic() or create_schematic() first."
601
- }
602
-
603
- # Add the hierarchical sheet
604
- sheet_uuid = state.current_schematic.add_sheet(
605
- name=name,
606
- filename=filename,
607
- position=position,
608
- size=size,
609
- project_name=project_name,
610
- page_number=page_number
611
- )
612
-
613
- return {
614
- "success": True,
615
- "message": f"Added hierarchical sheet '{name}' ({filename}) at {position}",
616
- "sheet_uuid": sheet_uuid,
617
- "name": name,
618
- "filename": filename,
619
- "position": position,
620
- "size": size,
621
- "help": "Use add_hierarchical_label() to add pins to connect this sheet to parent schematic"
622
- }
623
- except Exception as e:
624
- return {
625
- "success": False,
626
- "message": f"Error adding hierarchical sheet: {str(e)}",
627
- "errorDetails": traceback.format_exc()
628
- }
629
-
630
- @mcp.tool()
631
- def add_sheet_pin(sheet_uuid: str, name: str, pin_type: str = "input",
632
- position: Tuple[float, float] = (0, 0), rotation: float = 0,
633
- size: float = 1.27) -> Dict[str, Any]:
634
- """Add a pin to a hierarchical sheet for connecting to parent schematic.
635
-
636
- SHEET PIN POSITIONING (relative to sheet origin):
637
- - LEFT EDGE: (0, Y) - for inputs entering the sheet
638
- - RIGHT EDGE: (sheet_width, Y) - for outputs leaving the sheet
639
- - TOP EDGE: (X, 0) - for control signals
640
- - BOTTOM EDGE: (X, sheet_height) - for power/ground
641
-
642
- CRITICAL NAMING RULE:
643
- Sheet pin names MUST exactly match hierarchical label names in the subcircuit!
644
- Example: Sheet pin "VCC" connects to hierarchical label "VCC"
645
-
646
- PIN TYPES USAGE:
647
- - input: Power coming in, control signals in
648
- - output: Data/signals leaving the sheet
649
- - bidirectional: I2C, SPI data lines
650
- - passive: Ground connections, analog signals
651
-
652
- POSITIONING EXAMPLES for 100×80 sheet:
653
- - Power in: (0, 10) and (0, 20)
654
- - Signals out: (100, 15) and (100, 25)
655
- - Ground: (50, 80) - bottom center
656
- """
657
- try:
658
- if not state.is_loaded():
659
- return {
660
- "success": False,
661
- "message": "No schematic loaded. Use load_schematic() or create_schematic() first."
662
- }
663
-
664
- # Validate pin_type
665
- valid_pin_types = ["input", "output", "bidirectional", "tri_state", "passive"]
666
- if pin_type not in valid_pin_types:
667
- return {
668
- "success": False,
669
- "message": f"Invalid pin_type '{pin_type}'. Valid types: {valid_pin_types}",
670
- "valid_types": valid_pin_types
671
- }
672
-
673
- # Add the sheet pin
674
- pin_uuid = state.current_schematic.add_sheet_pin(
675
- sheet_uuid=sheet_uuid,
676
- name=name,
677
- pin_type=pin_type,
678
- position=position,
679
- rotation=rotation,
680
- size=size
681
- )
682
-
683
- return {
684
- "success": True,
685
- "message": f"Added sheet pin '{name}' ({pin_type}) to sheet {sheet_uuid}",
686
- "pin_uuid": pin_uuid,
687
- "name": name,
688
- "pin_type": pin_type,
689
- "position": position,
690
- "help": "Create matching hierarchical_label in child schematic for connectivity"
691
- }
692
- except ValueError as e:
693
- return {
694
- "success": False,
695
- "message": str(e)
696
- }
697
- except Exception as e:
698
- return {
699
- "success": False,
700
- "message": f"Error adding sheet pin: {str(e)}",
701
- "errorDetails": traceback.format_exc()
702
- }
703
-
704
- @mcp.tool()
705
- def add_hierarchical_label(text: str, position: Tuple[float, float],
706
- label_type: str = "input", rotation: float = 0,
707
- size: float = 1.27) -> Dict[str, Any]:
708
- """Add a hierarchical label for connecting to parent schematic via sheet pins.
709
-
710
- CRITICAL PLACEMENT RULES:
711
- 1. POSITION: Place labels directly on component pins, not floating
712
- - Must touch the actual pin connection point
713
- - Use component pin positions, not arbitrary locations
714
-
715
- 2. ROTATION: Labels must face AWAY from the component
716
- - 0° = right-facing (for pins on left side of component)
717
- - 180° = left-facing (for pins on right side of component)
718
- - 90° = up-facing (for pins on bottom of component)
719
- - 270° = down-facing (for pins on top of component)
720
-
721
- 3. NET NAMES: Use clear, descriptive names
722
- - Power: VCC, 3V3, 5V, GND, VBAT
723
- - Signals: SDA, SCL, TX, RX, CLK, DATA
724
- - Custom: INPUT_A, OUTPUT_B, CTRL_SIGNAL
725
-
726
- 4. LABEL TYPES:
727
- - input: Signal enters this sheet (power in, data in)
728
- - output: Signal leaves this sheet (power out, data out)
729
- - bidirectional: Signal goes both ways (I2C, SPI)
730
- - passive: Non-directional (GND, analog)
731
- """
732
- try:
733
- if not state.is_loaded():
734
- return {
735
- "success": False,
736
- "message": "No schematic loaded. Use load_schematic() or create_schematic() first."
737
- }
738
-
739
- # Validate label_type (hierarchical labels use same types as sheet pins)
740
- valid_types = ["input", "output", "bidirectional", "tri_state", "passive"]
741
- if label_type not in valid_types:
742
- return {
743
- "success": False,
744
- "message": f"Invalid label_type '{label_type}'. Valid types: {valid_types}",
745
- "valid_types": valid_types
746
- }
747
-
748
- # Add the hierarchical label
749
- from ..core.types import HierarchicalLabelShape
750
-
751
- # Map string types to enum
752
- shape_map = {
753
- "input": HierarchicalLabelShape.INPUT,
754
- "output": HierarchicalLabelShape.OUTPUT,
755
- "bidirectional": HierarchicalLabelShape.BIDIRECTIONAL,
756
- "tri_state": HierarchicalLabelShape.TRISTATE,
757
- "passive": HierarchicalLabelShape.PASSIVE
758
- }
759
-
760
- label_uuid = state.current_schematic.add_hierarchical_label(
761
- text=text,
762
- position=position,
763
- shape=shape_map[label_type],
764
- rotation=rotation,
765
- size=size
766
- )
767
-
768
- return {
769
- "success": True,
770
- "message": f"Added hierarchical label '{text}' ({label_type}) at {position}",
771
- "label_uuid": label_uuid,
772
- "text": text,
773
- "label_type": label_type,
774
- "position": position,
775
- "help": "This label connects to a matching sheet pin in the parent schematic"
776
- }
777
- except Exception as e:
778
- return {
779
- "success": False,
780
- "message": f"Error adding hierarchical label: {str(e)}",
781
- "errorDetails": traceback.format_exc()
782
- }
783
-
784
- @mcp.tool()
785
- def list_hierarchical_sheets() -> Dict[str, Any]:
786
- """List all hierarchical sheets in the current schematic."""
787
- try:
788
- if not state.is_loaded():
789
- return {
790
- "success": False,
791
- "message": "No schematic loaded"
792
- }
793
-
794
- sheets_data = state.current_schematic._data.get("sheets", [])
795
-
796
- sheets = []
797
- for sheet_data in sheets_data:
798
- sheet_info = {
799
- "uuid": sheet_data.get("uuid"),
800
- "name": sheet_data.get("name"),
801
- "filename": sheet_data.get("filename"),
802
- "position": [sheet_data.get("position", {}).get("x", 0),
803
- sheet_data.get("position", {}).get("y", 0)],
804
- "size": [sheet_data.get("size", {}).get("width", 0),
805
- sheet_data.get("size", {}).get("height", 0)],
806
- "pin_count": len(sheet_data.get("pins", [])),
807
- "pins": [
808
- {
809
- "uuid": pin.get("uuid"),
810
- "name": pin.get("name"),
811
- "pin_type": pin.get("pin_type"),
812
- "position": [pin.get("position", {}).get("x", 0),
813
- pin.get("position", {}).get("y", 0)]
814
- }
815
- for pin in sheet_data.get("pins", [])
816
- ]
817
- }
818
- sheets.append(sheet_info)
819
-
820
- return {
821
- "success": True,
822
- "sheets": sheets,
823
- "count": len(sheets),
824
- "message": f"Found {len(sheets)} hierarchical sheets"
825
- }
826
- except Exception as e:
827
- return {
828
- "success": False,
829
- "message": f"Error listing hierarchical sheets: {str(e)}",
830
- "errorDetails": traceback.format_exc()
831
- }
832
-
833
- def _get_reference_prefix(lib_id: str) -> str:
834
- """Get the standard reference prefix for a component type."""
835
- lib_id_lower = lib_id.lower()
836
-
837
- # Common component prefixes based on IPC standards
838
- if 'resistor' in lib_id_lower or ':r' in lib_id_lower or lib_id.endswith(':R'):
839
- return 'R'
840
- elif 'capacitor' in lib_id_lower or ':c' in lib_id_lower or lib_id.endswith(':C'):
841
- return 'C'
842
- elif 'inductor' in lib_id_lower or ':l' in lib_id_lower or lib_id.endswith(':L'):
843
- return 'L'
844
- elif 'diode' in lib_id_lower or ':d' in lib_id_lower or 'led' in lib_id_lower:
845
- return 'D'
846
- elif 'transistor' in lib_id_lower or ':q' in lib_id_lower or 'fet' in lib_id_lower:
847
- return 'Q'
848
- elif any(ic_type in lib_id_lower for ic_type in ['mcu', 'microcontroller', 'processor', 'amplifier', 'regulator', 'ic']):
849
- return 'U'
850
- elif 'crystal' in lib_id_lower or 'oscillator' in lib_id_lower:
851
- return 'Y'
852
- elif 'connector' in lib_id_lower or 'header' in lib_id_lower:
853
- return 'J'
854
- elif 'switch' in lib_id_lower or 'button' in lib_id_lower:
855
- return 'SW'
856
- elif 'fuse' in lib_id_lower:
857
- return 'F'
858
- else:
859
- # Default fallback
860
- return 'U'
861
-
862
- def _generate_next_reference(schematic, prefix: str) -> str:
863
- """Generate the next available reference for a given prefix."""
864
- try:
865
- existing_refs = []
866
- if hasattr(schematic, 'components'):
867
- # Get all existing references with this prefix
868
- for comp in schematic.components:
869
- if hasattr(comp, 'reference') and comp.reference.startswith(prefix):
870
- ref = comp.reference
871
- # Extract number from reference (e.g., 'R1' -> 1)
872
- try:
873
- num_part = ref[len(prefix):]
874
- if num_part.isdigit():
875
- existing_refs.append(int(num_part))
876
- except (ValueError, IndexError):
877
- continue
878
-
879
- # Find next available number
880
- next_num = 1
881
- while next_num in existing_refs:
882
- next_num += 1
883
-
884
- return f"{prefix}{next_num}"
885
- except Exception:
886
- # Fallback to R1, C1, etc.
887
- return f"{prefix}1"
888
-
889
- def _suggest_common_footprints(symbol) -> List[str]:
890
- """Suggest common footprints for a component type using SymbolDefinition."""
891
- return _suggest_common_footprints_by_prefix(symbol.reference_prefix.upper())
892
-
893
- def _suggest_common_footprints_by_prefix(prefix: str) -> List[str]:
894
- """Suggest common footprints based on reference prefix."""
895
- footprint_map = {
896
- "R": ["Resistor_SMD:R_0603_1608Metric", "Resistor_SMD:R_0805_2012Metric", "Resistor_SMD:R_1206_3216Metric"],
897
- "C": ["Capacitor_SMD:C_0603_1608Metric", "Capacitor_SMD:C_0805_2012Metric", "Capacitor_SMD:C_1206_3216Metric"],
898
- "L": ["Inductor_SMD:L_0603_1608Metric", "Inductor_SMD:L_0805_2012Metric"],
899
- "D": ["Diode_SMD:D_SOD-323", "Diode_SMD:D_0603_1608Metric"],
900
- "LED": ["LED_SMD:LED_0603_1608Metric", "LED_SMD:LED_0805_2012Metric"],
901
- "Q": ["Package_TO_SOT_SMD:SOT-23", "Package_TO_SOT_SMD:SOT-23-3"],
902
- "U": ["Package_SO:SOIC-8_3.9x4.9mm_P1.27mm", "Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm"],
903
- "J": ["Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical", "Connector_JST:JST_XH_B2B-XH-A_1x02_P2.50mm_Vertical"]
904
- }
905
-
906
- return footprint_map.get(prefix.upper(), ["Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"])
907
-
908
- def _get_usage_context(symbol) -> str:
909
- """Get basic usage context for a component using SymbolDefinition."""
910
- return _get_usage_context_by_prefix(symbol.reference_prefix.upper())
911
-
912
- def _get_usage_context_by_prefix(prefix: str) -> str:
913
- """Get basic usage context based on reference prefix."""
914
- context_map = {
915
- "R": "Use R_0603 for <0.1W signals, R_0805 for 0.1-0.25W power",
916
- "C": "Use 0603 for <10μF ceramic, 0805 for 10-100μF, larger for >100μF",
917
- "L": "Use 0603 for <1μH, 0805 for 1-10μH, larger for >10μH",
918
- "D": "Use SOD-323 for signal diodes, larger packages for power applications",
919
- "LED": "Use 0603 for indicators, 0805+ for higher current applications",
920
- "Q": "Use SOT-23 for small signal, larger packages for power switching",
921
- "U": "Choose package based on pin count and thermal requirements",
922
- "J": "Select connector based on current rating and mechanical requirements"
923
- }
924
-
925
- return context_map.get(prefix.upper(), "Select package based on electrical and mechanical requirements")
926
-
927
- @mcp.resource("schematic://current")
928
- def get_current_schematic() -> str:
929
- """Get current schematic state as text."""
930
- if not state.is_loaded():
931
- return "No schematic currently loaded"
932
-
933
- try:
934
- info = {
935
- "file_path": state.current_file_path or "Unsaved",
936
- "component_count": len(state.current_schematic.components) if hasattr(state.current_schematic, 'components') else 0,
937
- "loaded": True
938
- }
939
- return f"Current schematic: {info}"
940
- except Exception as e:
941
- return f"Error getting current schematic info: {e}"
942
-
943
- @mcp.resource("kicad://components/common-resistors")
944
- def get_common_resistors() -> str:
945
- """Common resistor components with standard footprints."""
946
- return """# Common Resistors
947
-
948
- ## Standard Values (E12 Series)
949
- - Device:R with values: 1.0, 1.2, 1.5, 1.8, 2.2, 2.7, 3.3, 3.9, 4.7, 5.6, 6.8, 8.2
950
- - Multiply by: 1Ω, 10Ω, 100Ω, 1kΩ, 10kΩ, 100kΩ, 1MΩ
951
-
952
- ## Recommended Footprints
953
- - **R_0603_1608Metric**: For <0.1W applications (most common)
954
- - **R_0805_2012Metric**: For 0.1-0.25W applications
955
- - **R_1206_3216Metric**: For 0.25-0.5W applications
956
-
957
- ## Common Usage
958
- ```python
959
- # Example usage:
960
- add_component("Device:R", "R1", "10k", (100, 100), footprint="Resistor_SMD:R_0603_1608Metric")
961
- ```
962
-
963
- ## Popular Values
964
- - 10kΩ (pull-up resistors)
965
- - 1kΩ (current limiting)
966
- - 100Ω (series termination)
967
- - 4.7kΩ (I2C pull-up)
968
- """
969
-
970
- @mcp.resource("kicad://components/common-capacitors")
971
- def get_common_capacitors() -> str:
972
- """Common capacitor components with standard footprints."""
973
- return """# Common Capacitors
974
-
975
- ## Ceramic Capacitors (Device:C)
976
- - Values: 1pF-10µF typical range
977
- - Common: 100nF (0.1µF), 1µF, 10µF, 22µF
978
-
979
- ## Recommended Footprints
980
- - **C_0603_1608Metric**: For <10µF ceramic (most common)
981
- - **C_0805_2012Metric**: For 10µF-100µF ceramic
982
- - **C_1206_3216Metric**: For >100µF ceramic
983
-
984
- ## Electrolytic Capacitors (Device:CP)
985
- - Values: 1µF-1000µF+ for bulk storage
986
- - Common: 10µF, 100µF, 470µF
987
-
988
- ## Common Usage
989
- ```python
990
- # Decoupling capacitor
991
- add_component("Device:C", "C1", "100n", (50, 50), footprint="Capacitor_SMD:C_0603_1608Metric")
992
-
993
- # Power supply bulk capacitor
994
- add_component("Device:CP", "C2", "100u", (75, 75), footprint="Capacitor_SMD:C_0805_2012Metric")
995
- ```
996
-
997
- ## Popular Applications
998
- - 100nF: MCU decoupling
999
- - 1µF: Voltage regulator input/output
1000
- - 10µF: Power supply filtering
1001
- """
1002
-
1003
- @mcp.resource("kicad://components/common-ics")
1004
- def get_common_ics() -> str:
1005
- """Common integrated circuit components and packages."""
1006
- return """# Common ICs
1007
-
1008
- ## Voltage Regulators
1009
- - **Regulator_Linear:AMS1117-3.3**: 3.3V LDO regulator
1010
- - Footprint: Package_TO_SOT_SMD:SOT-223-3_TabPin2
1011
- - **Regulator_Linear:LM7805_TO220**: 5V linear regulator
1012
- - Footprint: Package_TO_SOT_THT:TO-220-3_Vertical
1013
-
1014
- ## Op-Amps
1015
- - **Amplifier_Operational:LM358**: Dual op-amp
1016
- - Footprint: Package_SO:SOIC-8_3.9x4.9mm_P1.27mm
1017
- - **Amplifier_Operational:TL072**: JFET input dual op-amp
1018
- - Footprint: Package_DIP:DIP-8_W7.62mm
1019
-
1020
- ## Microcontrollers
1021
- - **MCU_ST_STM32F4:STM32F401RETx**: ARM Cortex-M4 MCU
1022
- - Footprint: Package_QFP:LQFP-64_10x10mm_P0.5mm
1023
- - **MCU_Microchip_ATmega:ATmega328P-PU**: Arduino-compatible MCU
1024
- - Footprint: Package_DIP:DIP-28_W15.24mm
1025
-
1026
- ## Common Usage
1027
- ```python
1028
- # 3.3V regulator
1029
- add_component("Regulator_Linear:AMS1117-3.3", "U1", "AMS1117", (100, 100),
1030
- footprint="Package_TO_SOT_SMD:SOT-223-3_TabPin2")
1031
- ```
1032
- """
1033
-
1034
- @mcp.resource("kicad://footprints/common-packages")
1035
- def get_common_packages() -> str:
1036
- """Common footprint packages and their applications."""
1037
- return """# Common Footprint Packages
1038
-
1039
- ## Surface Mount Packages
1040
-
1041
- ### Resistors & Capacitors
1042
- - **0603 (1608 Metric)**: 1.6mm x 0.8mm - Most common for passives
1043
- - **0805 (2012 Metric)**: 2.0mm x 1.2mm - Easier hand soldering
1044
- - **1206 (3216 Metric)**: 3.2mm x 1.6mm - Higher power rating
1045
-
1046
- ### ICs
1047
- - **SOIC-8**: 8-pin small outline package - common op-amps
1048
- - **SOT-23**: 3-pin small outline transistor package
1049
- - **SOT-223**: Power regulator package with tab
1050
- - **LQFP-64**: 64-pin low-profile quad flat pack - microcontrollers
1051
- - **QFN-16**: 16-pin quad flat no-lead package - compact ICs
1052
-
1053
- ## Through-Hole Packages
1054
-
1055
- ### Resistors & Capacitors
1056
- - **Axial_DIN0207**: Standard through-hole resistor
1057
- - **Radial_D5.0mm**: Through-hole capacitor, 5mm diameter
1058
-
1059
- ### ICs
1060
- - **DIP-8**: 8-pin dual in-line package - breadboard friendly
1061
- - **TO-220**: Power transistor/regulator package
1062
-
1063
- ## Package Selection Guide
1064
- - Use 0603 for most SMD passives (good balance of size vs assemblability)
1065
- - Use SOIC over TSSOP for easier hand assembly
1066
- - Use through-hole (DIP/TO-220) for prototyping and high power
1067
- - Use QFN/BGA only when density is critical
1068
- """
1069
-
1070
- @mcp.resource("kicad://circuits/common-patterns")
1071
- def get_common_patterns() -> str:
1072
- """Common circuit patterns and component combinations."""
1073
- return """# Common Circuit Patterns
1074
-
1075
- ## Voltage Divider
1076
- ```python
1077
- # R1 and R2 create voltage divider: Vout = Vin * R2/(R1+R2)
1078
- add_component("Device:R", "R1", "10k", (100, 100)) # Top resistor
1079
- add_component("Device:R", "R2", "10k", (100, 120)) # Bottom resistor
1080
- add_wire((100, 110), (100, 120)) # Connect R1 pin2 to R2 pin1
1081
- ```
1082
-
1083
- ## MCU Decoupling
1084
- ```python
1085
- # Always add 100nF ceramic cap near MCU power pins
1086
- add_component("Device:C", "C1", "100n", (mcu_x + 5, mcu_y))
1087
- # Add bulk capacitor nearby
1088
- add_component("Device:C", "C2", "10u", (mcu_x + 10, mcu_y))
1089
- ```
1090
-
1091
- ## LED Current Limiting
1092
- ```python
1093
- # LED with current limiting resistor
1094
- add_component("Device:LED", "D1", "LED", (50, 50))
1095
- add_component("Device:R", "R1", "330", (50, 70)) # ~10mA @ 3.3V
1096
- add_wire((50, 60), (50, 70)) # Connect LED cathode to resistor
1097
- ```
1098
-
1099
- ## Pull-up Resistor
1100
- ```python
1101
- # I2C or digital input pull-up
1102
- add_component("Device:R", "R1", "4k7", (100, 100)) # 4.7kΩ standard for I2C
1103
- add_component("Device:R", "R2", "10k", (120, 100)) # 10kΩ standard for digital
1104
- ```
1105
-
1106
- ## Voltage Regulator Circuit
1107
- ```python
1108
- # Linear regulator with input/output capacitors
1109
- add_component("Regulator_Linear:AMS1117-3.3", "U1", "AMS1117", (100, 100))
1110
- add_component("Device:C", "C1", "10u", (80, 100)) # Input cap
1111
- add_component("Device:C", "C2", "10u", (120, 100)) # Output cap
1112
- add_component("Device:C", "C3", "100n", (120, 110)) # Fast decoupling
1113
- ```
1114
- """
1115
-
1116
- @mcp.prompt()
1117
- def how_to_use_kicad_mcp() -> str:
1118
- """Learn how to use the KiCAD Schematic MCP Server effectively."""
1119
- return """# How to Use KiCAD Schematic MCP Server
1120
-
1121
- ## Getting Started
1122
-
1123
- ### 1. Create a New Schematic
1124
- ```
1125
- Create a new schematic called "MyCircuit"
1126
- ```
1127
-
1128
- ### 2. Search for Components
1129
- ```
1130
- Search for resistor components
1131
- Search for components in the Device library
1132
- Find STM32 microcontrollers
1133
- ```
1134
-
1135
- ### 3. Add Components
1136
- ```
1137
- Add a 10kΩ resistor at position (100, 100) with reference R1
1138
- Add a 100nF capacitor next to the resistor
1139
- Add an LED with a current limiting resistor
1140
- ```
1141
-
1142
- ### 4. Connect Components
1143
- ```
1144
- Connect the resistor R1 pin 1 to the capacitor C1 pin 1
1145
- Add a wire from (100, 100) to (120, 100)
1146
- ```
1147
-
1148
- ### 5. Save Your Work
1149
- ```
1150
- Save the schematic to "MyCircuit.kicad_sch"
1151
- ```
1152
-
1153
- ## Pro Tips
1154
-
1155
- ### Component Discovery
1156
- - Use `search_components("resistor")` to find available resistor symbols
1157
- - Use `validate_component("Device:R")` to check if a component exists
1158
- - Use `list_libraries()` to see all available symbol libraries
1159
-
1160
- ### Common Components
1161
- - **Device:R** - Generic resistor (use with values like "10k", "1M")
1162
- - **Device:C** - Generic ceramic capacitor
1163
- - **Device:LED** - Light emitting diode
1164
- - **power:GND** - Ground symbol
1165
- - **power:+3V3** - 3.3V power symbol
1166
-
1167
- ### Hierarchical Design Tools
1168
- For complex multi-sheet designs:
1169
- - `add_hierarchical_sheet("Sheet Name", "filename.kicad_sch", position, size)` - Create hierarchical sheet
1170
- - `add_sheet_pin(sheet_uuid, "NET_NAME", "input/output/bidirectional", position)` - Add sheet pin
1171
- - `add_hierarchical_label("NET_NAME", position, "input/output/bidirectional")` - Connect to parent sheet
1172
- - `list_hierarchical_sheets()` - Show all sheets and their pins
1173
-
1174
- ### Footprint Selection
1175
- The server suggests appropriate footprints for each component:
1176
- - Resistors: R_0603_1608Metric (most common)
1177
- - Capacitors: C_0603_1608Metric (for <10µF)
1178
- - ICs: Package varies by pin count and thermal needs
1179
-
1180
- ### Best Practices
1181
- 1. Always search for components before adding them
1182
- 2. Use standard component values (E12 series for resistors)
1183
- 3. Add decoupling capacitors near ICs
1184
- 4. Use proper footprints for your assembly method
1185
- 5. Validate your design before saving
1186
-
1187
- The server will guide you with suggestions and error messages if components don't exist!
1188
- """
1189
-
1190
- @mcp.prompt()
1191
- def schematic_design_guidelines() -> str:
1192
- """Essential schematic design guidelines for AI agents creating professional KiCAD schematics."""
1193
- return """# KiCAD Schematic Design Guidelines for AI Agents
1194
-
1195
- ## CRITICAL CONNECTION STRATEGY
1196
-
1197
- ### ⚠️ AVOID WIRES - USE LABELS INSTEAD!
1198
- - **NEVER use add_wire() in hierarchical designs**
1199
- - **ALWAYS use hierarchical labels for connections**
1200
- - **DEFAULT to labels even for simple connections**
1201
- - Wires create messy, hard-to-read schematics
1202
-
1203
- ### 1. Component References - NEVER USE "?"
1204
- - **ALWAYS** assign proper references: R1, R2, C1, C2, U1, etc.
1205
- - **NEVER** leave references as "?" - this creates invalid schematics
1206
- - Use standard prefixes: R=resistor, C=capacitor, U=IC, D=diode, L=inductor, Q=transistor
1207
-
1208
- ### 2. Component Spacing & Grid Alignment
1209
- - **Minimum spacing**: 50 units between components
1210
- - **Grid alignment**: Use multiples of 25.4 (100mil) or 12.7 (50mil)
1211
- - **Examples**: (100,100), (150,100), (100,150) - NOT (103,97) random positions
1212
-
1213
- ### 3. Hierarchical Labels - CRITICAL POSITIONING
1214
- - **Must touch component pins directly** - not floating in space
1215
- - **Must face away from component** using proper rotation:
1216
- * 0° = right-facing (for left-side pins)
1217
- * 180° = left-facing (for right-side pins)
1218
- * 90° = up-facing (for bottom pins)
1219
- * 270° = down-facing (for top pins)
1220
-
1221
- ### 4. Hierarchical Sheet Sizing - KEEP SMALL!
1222
- - **Small sheets**: (60, 40) for 2-3 pins (power, simple circuits)
1223
- - **Medium sheets**: (80, 50) for 4-6 pins (most subcircuits)
1224
- - **Large sheets**: (100, 60) for 7+ pins (complex only)
1225
- - **AVOID**: Sheets larger than (120, 80) - they're too big!
1226
-
1227
- ## HIERARCHICAL DESIGN WORKFLOW
1228
-
1229
- ### Step-by-Step Process:
1230
- 1. **Main schematic**: add_hierarchical_sheet() first
1231
- 2. **Create subcircuit**: create_schematic() for new sheet file
1232
- 3. **Add sheet pins**: add_sheet_pin() on the sheet rectangle
1233
- 4. **Switch to subcircuit**: load_schematic() the new file
1234
- 5. **Add components**: Normal component placement in subcircuit
1235
- 6. **Add hierarchical labels**: On component pins with EXACT same names as sheet pins
1236
- 7. **Save both**: Save main and subcircuit schematics
1237
-
1238
- ### Name Matching Rule:
1239
- Sheet pin "VCC" ↔ Hierarchical label "VCC" (MUST match exactly!)
1240
-
1241
- ## COMMON DESIGN PATTERNS
1242
-
1243
- ### Power Distribution:
1244
- ```
1245
- - VCC/3V3/5V labels on power input pins
1246
- - GND labels on ground pins
1247
- - Use "input" type for power coming into sheet
1248
- - Use "passive" type for ground connections
1249
- ```
1250
-
1251
- ### Signal Routing:
1252
- ```
1253
- - Clear signal names: CLK, DATA, TX, RX, CS, MOSI, MISO
1254
- - Use "output" for signals leaving a sheet
1255
- - Use "input" for signals entering a sheet
1256
- - Use "bidirectional" for I2C (SDA/SCL), SPI data
1257
- ```
1258
-
1259
- ### Component Values:
1260
- ```
1261
- - Standard resistor values: 1k, 10k, 100k, 1M (E12 series)
1262
- - Standard capacitor values: 100nF, 1uF, 10uF, 100uF
1263
- - Specify footprints: R_0603_1608Metric, C_0603_1608Metric
1264
- ```
1265
-
1266
- ## ERROR PREVENTION
1267
-
1268
- ### Before creating any schematic:
1269
- 1. Plan component references (R1, R2, C1, etc.) - never use "?"
1270
- 2. Calculate component positions with proper spacing
1271
- 3. Plan hierarchical label names to match sheet pins exactly
1272
- 4. Verify label positions are on actual component pins
1273
- 5. Check label rotations face away from components
1274
-
1275
- ### Testing Your Design:
1276
- - Use list_components() to verify references are assigned
1277
- - Use get_schematic_info() to check component count
1278
- - Ensure hierarchical labels have matching sheet pins
1279
- """
1280
-
1281
- @mcp.prompt()
1282
- def common_circuit_examples() -> str:
1283
- """Examples of common circuits you can build with KiCAD MCP."""
1284
- return """# Common Circuit Examples
1285
-
1286
- ## 1. Simple LED Blinker
1287
- Create a basic LED circuit with current limiting:
1288
- ```
1289
- Create a new schematic called "LED_Blinker"
1290
- Add an LED at position (100, 100)
1291
- Add a 330Ω resistor at position (100, 80) for current limiting
1292
- Connect the LED anode to one end of the resistor
1293
- Add power and ground symbols
1294
- Save the schematic
1295
- ```
1296
-
1297
- ## 2. Voltage Divider
1298
- Build a voltage divider for level shifting:
1299
- ```
1300
- Add two 10kΩ resistors in series
1301
- Connect them to create Vout = Vin/2
1302
- Add input and output connectors
1303
- ```
1304
-
1305
- ## 3. MCU Power Supply
1306
- Design power conditioning for a microcontroller:
1307
- ```
1308
- Add a 3.3V voltage regulator (AMS1117-3.3)
1309
- Add 10µF input and output capacitors
1310
- Add 100nF decoupling capacitors
1311
- Connect ground plane
1312
- ```
1313
-
1314
- ## 4. I2C Pull-up Network
1315
- Create proper I2C bus conditioning:
1316
- ```
1317
- Add 4.7kΩ pull-up resistors for SDA and SCL lines
1318
- Connect to 3.3V supply
1319
- Add I2C connector
1320
- ```
1321
-
1322
- ## 5. Crystal Oscillator Circuit
1323
- Build timing reference for MCU:
1324
- ```
1325
- Add crystal oscillator component
1326
- Add two 22pF load capacitors to ground
1327
- Connect to MCU clock inputs
1328
- ```
1329
-
1330
- Each example includes component selection, footprint recommendations, and connection guidance!
1331
- """
1332
-
1333
- @mcp.resource("kicad://hierarchy/workflow-guide")
1334
- def get_hierarchical_workflow() -> str:
1335
- """Guide for creating hierarchical schematics with multiple sheets."""
1336
- return """# Hierarchical Schematic Workflow
1337
-
1338
- ## Overview
1339
- Hierarchical schematics organize complex designs into multiple sheets for better readability and modularity. Each hierarchical sheet represents a functional block.
1340
-
1341
- ## Step 1: Plan Your Hierarchy
1342
- Break your design into logical functional blocks:
1343
- - Power Supply
1344
- - MCU Core
1345
- - Communication (USB, WiFi, etc.)
1346
- - Analog Frontend
1347
- - LED Driver
1348
- - Debug/Programming Interface
1349
-
1350
- ## Step 2: Create Main Schematic
1351
- Start with the top-level schematic that shows the overall system architecture:
1352
-
1353
- ```python
1354
- # Create main schematic
1355
- create_schematic("Main_Board")
1356
-
1357
- # Add hierarchical sheets for each subsystem
1358
- add_hierarchical_sheet("Power Supply", "power.kicad_sch", (50, 50), (50, 30))
1359
- add_hierarchical_sheet("MCU Core", "mcu.kicad_sch", (120, 50), (60, 40))
1360
- add_hierarchical_sheet("USB Interface", "usb.kicad_sch", (200, 50), (40, 25))
1361
- ```
1362
-
1363
- ## Step 3: Add Sheet Pins
1364
- Connect hierarchical sheets by adding pins for signals that cross sheet boundaries:
1365
-
1366
- ```python
1367
- # Get sheet UUID from add_hierarchical_sheet() return value
1368
- power_sheet_uuid = "sheet-uuid-from-previous-call"
1369
-
1370
- # Add power output pins to power supply sheet
1371
- add_sheet_pin(power_sheet_uuid, "3V3", "output", (50, 5))
1372
- add_sheet_pin(power_sheet_uuid, "5V", "output", (50, 10))
1373
- add_sheet_pin(power_sheet_uuid, "GND", "passive", (50, 15))
1374
-
1375
- # Add power input pins to MCU sheet
1376
- mcu_sheet_uuid = "mcu-sheet-uuid"
1377
- add_sheet_pin(mcu_sheet_uuid, "3V3", "input", (0, 5))
1378
- add_sheet_pin(mcu_sheet_uuid, "GND", "passive", (0, 10))
1379
-
1380
- # Add communication signals between MCU and USB
1381
- add_sheet_pin(mcu_sheet_uuid, "USB_DP", "bidirectional", (60, 20))
1382
- add_sheet_pin(mcu_sheet_uuid, "USB_DN", "bidirectional", (60, 25))
1383
- ```
1384
-
1385
- ## Step 4: Connect Sheet Pins with Wires
1386
- Wire the sheet pins together on the main schematic:
1387
-
1388
- ```python
1389
- # Connect power distribution
1390
- add_wire((100, 55), (120, 55)) # 3V3: Power sheet output to MCU sheet input
1391
- add_wire((100, 60), (120, 60)) # GND: Power sheet to MCU sheet
1392
-
1393
- # Connect USB signals
1394
- add_wire((180, 70), (200, 70)) # USB_DP: MCU to USB sheet
1395
- add_wire((180, 75), (200, 75)) # USB_DN: MCU to USB sheet
1396
- ```
1397
-
1398
- ## Step 5: Create Child Schematics
1399
- Create separate schematic files for each hierarchical sheet:
1400
-
1401
- ```python
1402
- # Create power supply schematic
1403
- create_schematic("Power_Supply")
1404
- save_schematic("power.kicad_sch")
1405
-
1406
- # Add hierarchical labels that match the sheet pins
1407
- add_hierarchical_label("3V3", (150, 50), "output")
1408
- add_hierarchical_label("5V", (150, 60), "output")
1409
- add_hierarchical_label("GND", (150, 70), "passive")
1410
-
1411
- # Add actual power supply components
1412
- add_component("Regulator_Linear:AMS1117-3.3", "U1", "AMS1117", (100, 60))
1413
- # ... more components
1414
- ```
1415
-
1416
- ## Pin Type Guidelines
1417
-
1418
- **Input**: Signal flows into the sheet (power inputs, control signals)
1419
- **Output**: Signal flows out of the sheet (power outputs, status signals)
1420
- **Bidirectional**: Signal flows both ways (I2C, SPI data lines)
1421
- **Tri-state**: Three-state signals (data buses with enable)
1422
- **Passive**: Non-directional connections (ground, analog signals)
1423
-
1424
- ## Best Practices
1425
-
1426
- 1. **Consistent Naming**: Use the same net names for sheet pins and hierarchical labels
1427
- 2. **Logical Grouping**: Group related signals together on sheet edges
1428
- 3. **Clear Pin Types**: Use correct pin types for signal direction
1429
- 4. **Good Placement**: Position sheet pins at logical connection points
1430
- 5. **Documentation**: Add descriptive names and organize sheets clearly
1431
-
1432
- ## Common Patterns
1433
-
1434
- ### Power Distribution
1435
- ```python
1436
- # Power sheet outputs
1437
- add_sheet_pin(power_uuid, "3V3", "output", (50, 5))
1438
- add_sheet_pin(power_uuid, "GND", "passive", (50, 30))
1439
-
1440
- # Consumer sheet inputs
1441
- add_sheet_pin(mcu_uuid, "3V3", "input", (0, 5))
1442
- add_sheet_pin(mcu_uuid, "GND", "passive", (0, 30))
1443
- ```
1444
-
1445
- ### Communication Bus
1446
- ```python
1447
- # MCU sheet (bus master)
1448
- add_sheet_pin(mcu_uuid, "I2C_SCL", "output", (60, 10))
1449
- add_sheet_pin(mcu_uuid, "I2C_SDA", "bidirectional", (60, 15))
1450
-
1451
- # Sensor sheet (bus slave)
1452
- add_sheet_pin(sensor_uuid, "I2C_SCL", "input", (0, 10))
1453
- add_sheet_pin(sensor_uuid, "I2C_SDA", "bidirectional", (0, 15))
1454
- ```
1455
-
1456
- This hierarchical approach makes complex designs manageable and promotes reusable functional blocks.
1457
- """
1458
-
1459
- def main():
1460
- """Run the MCP server in standard on-demand mode."""
1461
- import argparse
1462
-
1463
- parser = argparse.ArgumentParser(description="KiCAD Schematic MCP Server")
1464
- parser.add_argument('--test', action='store_true', help='Run quick test and exit')
1465
- parser.add_argument('--debug', action='store_true', help='Enable debug logging')
1466
- parser.add_argument('--status', action='store_true', help='Show server status and exit')
1467
- parser.add_argument('--version', action='store_true', help='Show version and exit')
1468
-
1469
- args = parser.parse_args()
1470
-
1471
- if args.debug:
1472
- logging.getLogger().setLevel(logging.DEBUG)
1473
- logger.debug("Debug logging enabled")
1474
-
1475
- if args.version:
1476
- print("KiCAD Schematic MCP Server v0.1.5")
1477
- return
1478
-
1479
- if args.status:
1480
- print("KiCAD Schematic MCP Server Status:")
1481
- print(f" Component discovery: {len(get_search_index().get_categories()) if get_search_index() else 0} categories")
1482
- print(f" Symbol cache: {get_symbol_cache().get_performance_stats()['total_symbols_cached']} symbols")
1483
- print(" Status: Ready")
1484
- return
1485
-
1486
- if args.test:
1487
- logger.info("Running MCP server test...")
1488
- try:
1489
- # Quick initialization test
1490
- ensure_index_built()
1491
- cache = get_symbol_cache()
1492
- stats = cache.get_performance_stats()
1493
- print(f"✅ Test passed: {stats['total_symbols_cached']} symbols, {len(get_search_index().get_categories())} categories")
1494
- return
1495
- except Exception as e:
1496
- print(f"❌ Test failed: {e}")
1497
- sys.exit(1)
1498
-
1499
- logger.info("Starting KiCAD Schematic MCP Server...")
1500
- try:
1501
- # Run normally - Claude Desktop will manage the process lifecycle
1502
- mcp.run()
1503
- except KeyboardInterrupt:
1504
- logger.info("Server stopped by user")
1505
- except Exception as e:
1506
- logger.error(f"Server error: {e}")
1507
- sys.exit(1)
1508
-
1509
-
1510
- if __name__ == "__main__":
1511
- main()