- Kicad 9.0.7 with PCB Editor opened (necessary)
-
install plugins:
https://github.com/bouni/kicad-jlcpcb-tools
https://github.com/espressif/kicad-libraries
https://github.com/CDFER/JLCPCB-Kicad-Library
espressif
(optional) https://github.com/rasmushauschild/easyeda2kicad_plugin 1a. Install library loader(used v2.50, found on mouser) https://componentsearchengine.com/ga/downloads.php
-
- Couldnt get IPC working, uses legacy backend
- Install Cline in VSCode
- git clone https://github.com/mixelpixx/KiCAD-MCP-Server
- cd KiCAD-MCP-SERVER
- npm install
- npm run build
- configure mcp server in cline using cline_mcp_settings.json
- Using free nvidia nim api/build api create nvidia account, create api key(phone number required)
- api provider: OpenAI compatible
- baseurl: https://integrate.api.nvidia.com/v1
- openai compatabile key:
- model used: meta/llama-4-maverick-17b-128e-instruct
-
-
Save raspberrypisig/cffc0f6b1fb15904178d70f6a53ef3a2 to your computer and use it in GitHub Desktop.
| { | |
| "mcpServers": { | |
| "kicad": { | |
| "disabled": false, | |
| "timeout": 60, | |
| "type": "stdio", | |
| "command": "node", | |
| "args": [ | |
| "C:\\Users\\mohan\\Developer\\kicad\\KiCAD-MCP-Server\\dist\\index.js" | |
| ], | |
| "env": { | |
| "PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\bin\\Lib\\site-packages;C:\\Users\\user\\Documents\\KiCad\\9.0\\3rdparty\\Python311\\site-packages", | |
| "KICAD9_3RD_PARTY": "C:\\Users\\user\\Documents\\KiCad\\9.0\\3rdparty", | |
| "LOG_LEVEL": "info" | |
| }, | |
| "autoApprove": [ | |
| "open_project", | |
| "place_component", | |
| "list_library_footprints", | |
| "search_footprints", | |
| "list_symbol_libraries", | |
| "create_project", | |
| "add_schematic_component" | |
| ] | |
| } | |
| } | |
| } |
| @staticmethod | |
| def add_component(schematic: Schematic, component_def: dict, schematic_path: Optional[Path] = None): | |
| """ | |
| Add a component to the schematic by cloning from template | |
| """ | |
| try: | |
| from commands.schematic import SchematicManager | |
| logger.info(f"Adding component: type={component_def.get('type')}, ref={component_def.get('reference')}") | |
| # Get component type and determine template | |
| comp_type = component_def.get('type', 'R') | |
| library = component_def.get('library', None) | |
| # Get template reference | |
| template_ref, needs_reload = ComponentManager.get_or_create_template(schematic, comp_type, library, schematic_path) | |
| if needs_reload and schematic_path: | |
| logger.info(f"Reloading schematic after dynamic loading: {schematic_path}") | |
| schematic = SchematicManager.load_schematic(str(schematic_path)) | |
| # Find template symbol | |
| template_symbol = None | |
| for symbol in schematic.symbol: | |
| if hasattr(symbol.property, 'Reference') and symbol.property.Reference.value == template_ref: | |
| template_symbol = symbol | |
| break | |
| if not template_symbol: | |
| raise ValueError(f"Template symbol {template_ref} not found.") | |
| # Clone the template symbol | |
| new_symbol = template_symbol.clone() | |
| # --- 1. Set Initial Properties --- | |
| base_ref = component_def.get('reference', 'R?') | |
| if 'value' in component_def: | |
| new_symbol.property.Value.value = component_def['value'] | |
| if 'footprint' in component_def: | |
| new_symbol.property.Footprint.value = component_def['footprint'] | |
| if 'datasheet' in component_def: | |
| new_symbol.property.Datasheet.value = component_def['datasheet'] | |
| # Set position | |
| x = component_def.get('x', 0) | |
| y = component_def.get('y', 0) | |
| rotation = component_def.get('rotation', 0) | |
| new_symbol.at.value = [x, y, rotation] | |
| new_symbol.in_bom.value = component_def.get('in_bom', True) | |
| new_symbol.on_board.value = component_def.get('on_board', True) | |
| new_symbol.dnp.value = component_def.get('dnp', False) | |
| new_symbol.uuid.value = str(uuid.uuid4()) | |
| # --- 2. Manual Unique Reference Calculation --- | |
| # We calculate the unique name ourselves to ensure R1 -> R2, not R1_ | |
| existing_refs = set() | |
| for s in schematic.symbol: | |
| if hasattr(s, 'reference'): | |
| existing_refs.add(s.reference) | |
| elif hasattr(s.property, 'Reference'): | |
| existing_refs.add(s.property.Reference.value) | |
| unique_ref = base_ref | |
| # Simple collision handler: append underscore if exists | |
| # (You can replace this with smart number incrementing logic if you prefer) | |
| while unique_ref in existing_refs: | |
| unique_ref = f"{unique_ref}_" | |
| if unique_ref != base_ref: | |
| logger.info(f"Renaming {base_ref} to {unique_ref} to avoid collision") | |
| # --- 3. The Bypass Trick --- | |
| # Step A: clear the reference temporarily. | |
| # The ElementCollection.append method checks 'if len(name_for):' | |
| # If the name is empty, it adds the item to the list but SKIPS the collision logic. | |
| new_symbol.reference = "" | |
| new_symbol.property.Reference.value = "" | |
| # Step B: Append to the list | |
| schematic.symbol.append(new_symbol) | |
| # Step C: Restore the correct unique reference | |
| new_symbol.reference = unique_ref | |
| new_symbol.property.Reference.value = unique_ref | |
| # Step D: Manually Register (Important!) | |
| # Because we tricked append into skipping logic, the symbol isn't in the internal | |
| # lookup dictionary yet. We manually add it using elementAdd (which you showed exists). | |
| if hasattr(schematic.symbol, 'elementAdd'): | |
| schematic.symbol.elementAdd(unique_ref, new_symbol) | |
| logger.info(f"Successfully added component {unique_ref} to schematic") | |
| return new_symbol | |
| except Exception as e: | |
| logger.error(f"Error adding component: {e}", exc_info=True) | |
| raise |
| import asyncio | |
| from mcp_use import MCPClient | |
| import os | |
| async def main(): | |
| config = { | |
| "mcpServers": { | |
| "kicad": { | |
| "command": "node", | |
| "args": ["C:\\Users\\mohan\\Developer\\kicad\\KiCAD-MCP-Server\\dist\\index.js"], | |
| "env": { | |
| "PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\bin\\Lib\\site-packages;C:\\Users\\mohan\\Documents\\KiCad\\9.0\\3rdparty\\Python311\\site-packages", | |
| "LOG_LEVEL": "info" | |
| } | |
| } | |
| } | |
| } | |
| client = MCPClient.from_dict(config) | |
| await client.create_all_sessions() | |
| session = client.get_session("kicad") | |
| # create project | |
| #result = await session.call_tool(name="create_project", arguments={"name": "boo", "path": os.getcwd()}) | |
| # list all footprints | |
| result = await session.call_tool(name="list_libraries", arguments={}) | |
| # list footprint in PCM_JLCPCB library | |
| #result = await session.call_tool(name="list_library_footprints", arguments={"library_name": "PCM_JLCPCB"}) | |
| # list footprint in Library Loader | |
| result = await session.call_tool(name="list_library_footprints", arguments={"library_name": "SamacSys_Parts"}) | |
| # search for footprint in PCM_JLCPCB library | |
| result = await session.call_tool(name="search_footprints", arguments={"library_name": "SamacSys_Parts"}) | |
| print(f"Result: {result.content[0].text}") | |
| await client.close_all_sessions() | |
| asyncio.run(main()) | |
| { | |
| "version": "0.2.0", | |
| "configurations": [ | |
| { | |
| "name": "Debug KiCAD Interface (Windows)", | |
| "type": "debugpy", | |
| "request": "attach", | |
| "connect": { | |
| "host": "localhost", | |
| "port": 5678 | |
| }, | |
| "justMyCode": false, | |
| "pathMappings": [ | |
| { | |
| "localRoot": "${workspaceFolder}/python", | |
| "remoteRoot": "${workspaceFolder}/python" | |
| } | |
| ], | |
| } | |
| ] | |
| } |
| """ | |
| Library management for KiCAD footprints | |
| Handles parsing fp-lib-table files, discovering footprints, | |
| and providing search functionality for component placement. | |
| """ | |
| import os | |
| import re | |
| import logging | |
| from pathlib import Path | |
| from typing import Dict, List, Optional, Tuple | |
| import glob | |
| logger = logging.getLogger('kicad_interface') | |
| class LibraryManager: | |
| """ | |
| Manages KiCAD footprint libraries | |
| Parses fp-lib-table files (both global and project-specific), | |
| indexes available footprints, and provides search functionality. | |
| """ | |
| def __init__(self, project_path: Optional[Path] = None): | |
| """ | |
| Initialize library manager | |
| Args: | |
| project_path: Optional path to project directory for project-specific libraries | |
| """ | |
| self.project_path = project_path | |
| self.libraries: Dict[str, str] = {} # nickname -> path mapping | |
| self.footprint_cache: Dict[str, List[str]] = {} # library -> [footprint names] | |
| self._load_libraries() | |
| def _load_libraries(self): | |
| """Load libraries from fp-lib-table files""" | |
| # Load global libraries | |
| global_table = self._get_global_fp_lib_table() | |
| if global_table and global_table.exists(): | |
| logger.info(f"Loading global fp-lib-table from: {global_table}") | |
| self._parse_fp_lib_table(global_table) | |
| else: | |
| logger.warning(f"Global fp-lib-table not found at: {global_table}") | |
| # Load project-specific libraries if project path provided | |
| if self.project_path: | |
| project_table = self.project_path / "fp-lib-table" | |
| if project_table.exists(): | |
| logger.info(f"Loading project fp-lib-table from: {project_table}") | |
| self._parse_fp_lib_table(project_table) | |
| logger.info(f"Loaded {len(self.libraries)} footprint libraries") | |
| def _get_global_fp_lib_table(self) -> Optional[Path]: | |
| """Get path to global fp-lib-table file""" | |
| # Try different possible locations | |
| kicad_config_paths = [ | |
| Path.home() / ".config" / "kicad" / "9.0" / "fp-lib-table", | |
| Path.home() / ".config" / "kicad" / "8.0" / "fp-lib-table", | |
| Path.home() / ".config" / "kicad" / "fp-lib-table", | |
| # Windows paths | |
| Path.home() / "AppData" / "Roaming" / "kicad" / "9.0" / "fp-lib-table", | |
| Path.home() / "AppData" / "Roaming" / "kicad" / "8.0" / "fp-lib-table", | |
| # macOS paths | |
| Path.home() / "Library" / "Preferences" / "kicad" / "9.0" / "fp-lib-table", | |
| Path.home() / "Library" / "Preferences" / "kicad" / "8.0" / "fp-lib-table", | |
| ] | |
| for path in kicad_config_paths: | |
| if path.exists(): | |
| return path | |
| return None | |
| def _parse_fp_lib_table(self, table_path: Path): | |
| """ | |
| Parse fp-lib-table file | |
| """ | |
| try: | |
| with open(table_path, 'r') as f: | |
| content = f.read() | |
| # Simple regex-based parser for lib entries | |
| lib_pattern = r'\(lib\s+\(name\s+"?([^")\s]+)"?\)\s*\(type\s+[^)]+\)\s*\(uri\s+"?([^")\s]+)"?' | |
| for match in re.finditer(lib_pattern, content, re.IGNORECASE): | |
| nickname = match.group(1) | |
| uri = match.group(2) | |
| # Resolve environment variables in URI | |
| resolved_uri = self._resolve_uri(uri) | |
| if resolved_uri: | |
| self.libraries[nickname] = resolved_uri | |
| logger.debug(f" Found library: {nickname} -> {resolved_uri}") | |
| else: | |
| logger.warning(f" Could not resolve URI for library {nickname}: {uri}") | |
| except Exception as e: | |
| logger.error(f"Error parsing fp-lib-table at {table_path}: {e}") | |
| def _resolve_uri(self, uri: str) -> Optional[str]: | |
| """ | |
| Resolve environment variables and paths in library URI | |
| """ | |
| # Replace environment variables | |
| resolved = uri | |
| # Common KiCAD environment variables | |
| env_vars = { | |
| 'KICAD9_FOOTPRINT_DIR': self._find_kicad_footprint_dir(), | |
| 'KICAD8_FOOTPRINT_DIR': self._find_kicad_footprint_dir(), | |
| 'KICAD_FOOTPRINT_DIR': self._find_kicad_footprint_dir(), | |
| 'KISYSMOD': self._find_kicad_footprint_dir(), | |
| 'KICAD9_3RD_PARTY': self._find_kicad_3rdparty_dir(), | |
| 'KICAD8_3RD_PARTY': self._find_kicad_3rdparty_dir(), | |
| } | |
| # Project directory | |
| if self.project_path: | |
| env_vars['KIPRJMOD'] = str(self.project_path) | |
| # Replace environment variables | |
| for var, value in env_vars.items(): | |
| if value: | |
| resolved = resolved.replace(f'${{{var}}}', value) | |
| resolved = resolved.replace(f'${var}', value) | |
| # Expand ~ to home directory | |
| resolved = os.path.expanduser(resolved) | |
| # Convert to absolute path | |
| path = Path(resolved) | |
| # Check if path exists | |
| if path.exists(): | |
| return str(path) | |
| else: | |
| return None | |
| def _find_kicad_footprint_dir(self) -> Optional[str]: | |
| """Find KiCAD footprint directory""" | |
| # Try common locations | |
| possible_paths = [ | |
| "/usr/share/kicad/footprints", | |
| "/usr/local/share/kicad/footprints", | |
| "C:/Program Files/KiCad/9.0/share/kicad/footprints", | |
| "C:/Program Files/KiCad/8.0/share/kicad/footprints", | |
| "/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints", | |
| ] | |
| # Also check environment variable | |
| if 'KICAD9_FOOTPRINT_DIR' in os.environ: | |
| possible_paths.insert(0, os.environ['KICAD9_FOOTPRINT_DIR']) | |
| if 'KICAD8_FOOTPRINT_DIR' in os.environ: | |
| possible_paths.insert(0, os.environ['KICAD8_FOOTPRINT_DIR']) | |
| for path in possible_paths: | |
| if os.path.isdir(path): | |
| return path | |
| return None | |
| def _find_kicad_3rdparty_dir(self) -> Optional[str]: | |
| """Find KiCAD 3rd party libraries directory.""" | |
| import json | |
| # 1. Check shell environment variable first | |
| if 'KICAD9_3RD_PARTY' in os.environ: | |
| path = os.environ['KICAD9_3RD_PARTY'] | |
| if os.path.isdir(path): | |
| return path | |
| # 2. Check kicad_common.json for user-defined variables | |
| kicad_common_paths = [ | |
| Path.home() / "Library" / "Preferences" / "kicad" / "9.0" / "kicad_common.json", | |
| Path.home() / ".config" / "kicad" / "9.0" / "kicad_common.json", | |
| Path.home() / "AppData" / "Roaming" / "kicad" / "9.0" / "kicad_common.json", | |
| ] | |
| version = "9.0" # Default | |
| for config_path in kicad_common_paths: | |
| if config_path.exists(): | |
| try: | |
| with open(config_path, 'r') as f: | |
| config = json.load(f) | |
| env_vars = config.get('environment', {}).get('vars', {}) | |
| if env_vars and 'KICAD9_3RD_PARTY' in env_vars: | |
| path = env_vars['KICAD9_3RD_PARTY'] | |
| if os.path.isdir(path): | |
| return path | |
| except (json.JSONDecodeError, KeyError, TypeError): | |
| pass | |
| version = config_path.parent.name | |
| break | |
| # 3. Use platform-specific defaults | |
| possible_paths = [ | |
| Path.home() / "Documents" / "KiCad" / version / "3rdparty", | |
| Path.home() / ".local" / "share" / "kicad" / version / "3rdparty", | |
| ] | |
| for path in possible_paths: | |
| if path.exists(): | |
| return str(path) | |
| return None | |
| def list_libraries(self) -> List[str]: | |
| """Get list of available library nicknames""" | |
| return list(self.libraries.keys()) | |
| def get_library_path(self, nickname: str) -> Optional[str]: | |
| """Get filesystem path for a library nickname""" | |
| return self.libraries.get(nickname) | |
| def list_footprints(self, library_nickname: str) -> List[str]: | |
| """List all footprints in a library""" | |
| # Check cache first | |
| if library_nickname in self.footprint_cache: | |
| return self.footprint_cache[library_nickname] | |
| library_path = self.libraries.get(library_nickname) | |
| if not library_path: | |
| logger.warning(f"Library not found: {library_nickname}") | |
| return [] | |
| try: | |
| footprints = [] | |
| lib_dir = Path(library_path) | |
| # List all .kicad_mod files | |
| for fp_file in lib_dir.glob("*.kicad_mod"): | |
| footprint_name = fp_file.stem | |
| footprints.append(footprint_name) | |
| # Cache the results | |
| self.footprint_cache[library_nickname] = footprints | |
| return footprints | |
| except Exception as e: | |
| logger.error(f"Error listing footprints in {library_nickname}: {e}") | |
| return [] | |
| def find_footprint(self, footprint_spec: str) -> Optional[Tuple[str, str]]: | |
| """Find a footprint by specification""" | |
| if ":" in footprint_spec: | |
| # Format: Library:Footprint | |
| library_nickname, footprint_name = footprint_spec.split(":", 1) | |
| library_path = self.libraries.get(library_nickname) | |
| if not library_path: | |
| return None | |
| fp_file = Path(library_path) / f"{footprint_name}.kicad_mod" | |
| if fp_file.exists(): | |
| return (library_path, footprint_name) | |
| else: | |
| return None | |
| else: | |
| # Format: Footprint (search all libraries) | |
| footprint_name = footprint_spec | |
| # Search in all libraries | |
| for library_nickname, library_path in self.libraries.items(): | |
| fp_file = Path(library_path) / f"{footprint_name}.kicad_mod" | |
| if fp_file.exists(): | |
| return (library_path, footprint_name) | |
| return None | |
| def search_footprints(self, pattern: str, limit: int = 20) -> List[Dict[str, str]]: | |
| """Search for footprints matching a pattern""" | |
| results = [] | |
| pattern_lower = pattern.lower() | |
| # Convert wildcards to regex | |
| regex_pattern = pattern_lower.replace("*", ".*") | |
| try: | |
| regex = re.compile(regex_pattern) | |
| except re.error: | |
| regex = re.compile(re.escape(pattern_lower)) | |
| for library_nickname in self.libraries.keys(): | |
| footprints = self.list_footprints(library_nickname) | |
| for footprint in footprints: | |
| if regex.search(footprint.lower()): | |
| results.append({ | |
| 'library': library_nickname, | |
| 'footprint': footprint, | |
| 'full_name': f"{library_nickname}:{footprint}" | |
| }) | |
| if len(results) >= limit: | |
| return results | |
| return results | |
| def get_footprint_info(self, library_nickname: str, footprint_name: str) -> Optional[Dict[str, str]]: | |
| """Get information about a specific footprint""" | |
| library_path = self.libraries.get(library_nickname) | |
| if not library_path: | |
| return None | |
| fp_file = Path(library_path) / f"{footprint_name}.kicad_mod" | |
| if not fp_file.exists(): | |
| return None | |
| return { | |
| 'library': library_nickname, | |
| 'footprint': footprint_name, | |
| 'full_name': f"{library_nickname}:{footprint_name}", | |
| 'path': str(fp_file), | |
| 'library_path': library_path | |
| } | |
| class LibraryCommands: | |
| """Command handlers for library operations""" | |
| def __init__(self, library_manager: Optional[LibraryManager] = None): | |
| """Initialize with optional library manager""" | |
| self.library_manager = library_manager or LibraryManager() | |
| def list_libraries(self, params: Dict) -> Dict: | |
| """List all available footprint libraries""" | |
| try: | |
| libraries = self.library_manager.list_libraries() | |
| return { | |
| "success": True, | |
| "libraries": libraries, | |
| "count": len(libraries) | |
| } | |
| except Exception as e: | |
| logger.error(f"Error listing libraries: {e}") | |
| return { | |
| "success": False, | |
| "message": "Failed to list libraries", | |
| "errorDetails": str(e) | |
| } | |
| def search_footprints(self, params: Dict) -> Dict: | |
| """Search for footprints by pattern""" | |
| try: | |
| # Handle both parameter names: 'pattern' (internal) or 'search_term' (MCP) | |
| pattern = params.get("pattern") or params.get("search_term") or "*" | |
| limit = params.get("limit", 20) | |
| results = self.library_manager.search_footprints(pattern, limit) | |
| return { | |
| "success": True, | |
| "footprints": results, | |
| "count": len(results), | |
| "pattern": pattern | |
| } | |
| except Exception as e: | |
| logger.error(f"Error searching footprints: {e}") | |
| return { | |
| "success": False, | |
| "message": "Failed to search footprints", | |
| "errorDetails": str(e) | |
| } | |
| def list_library_footprints(self, params: Dict) -> Dict: | |
| """List all footprints in a specific library""" | |
| try: | |
| # FIX: Handle both parameter names: 'library' (internal) or 'library_name' (MCP) | |
| library = params.get("library") or params.get("library_name") | |
| if not library: | |
| return { | |
| "success": False, | |
| "message": "Missing library parameter" | |
| } | |
| footprints = self.library_manager.list_footprints(library) | |
| return { | |
| "success": True, | |
| "library": library, | |
| "footprints": footprints, | |
| "count": len(footprints) | |
| } | |
| except Exception as e: | |
| logger.error(f"Error listing library footprints: {e}") | |
| return { | |
| "success": False, | |
| "message": "Failed to list library footprints", | |
| "errorDetails": str(e) | |
| } | |
| def get_footprint_info(self, params: Dict) -> Dict: | |
| """Get information about a specific footprint""" | |
| try: | |
| # Support 'footprint' (combined) or 'library_name' + 'footprint_name' | |
| footprint_spec = params.get("footprint") | |
| if not footprint_spec: | |
| lib = params.get("library_name") | |
| fp = params.get("footprint_name") | |
| if lib and fp: | |
| footprint_spec = f"{lib}:{fp}" | |
| if not footprint_spec: | |
| return { | |
| "success": False, | |
| "message": "Missing footprint parameter" | |
| } | |
| # Try to find the footprint | |
| result = self.library_manager.find_footprint(footprint_spec) | |
| if result: | |
| library_path, footprint_name = result | |
| # Extract library nickname from path | |
| library_nickname = None | |
| for nick, path in self.library_manager.libraries.items(): | |
| if path == library_path: | |
| library_nickname = nick | |
| break | |
| info = { | |
| "library": library_nickname, | |
| "footprint": footprint_name, | |
| "full_name": f"{library_nickname}:{footprint_name}", | |
| "library_path": library_path | |
| } | |
| return { | |
| "success": True, | |
| "footprint_info": info | |
| } | |
| else: | |
| return { | |
| "success": False, | |
| "message": f"Footprint not found: {footprint_spec}" | |
| } | |
| except Exception as e: | |
| logger.error(f"Error getting footprint info: {e}") | |
| return { | |
| "success": False, | |
| "message": "Failed to get footprint info", | |
| "errorDetails": str(e) | |
| } | |
| diff --git a/python/kicad_api/ipc_backend.py b/python/kicad_api/ipc_backend.py | |
| index 0f9339e..4b1a8c5 100644 | |
| --- a/python/kicad_api/ipc_backend.py | |
| +++ b/python/kicad_api/ipc_backend.py | |
| @@ -67,19 +67,15 @@ class IPCBackend(KiCADBackend): | |
| # Import here to allow module to load even without kicad-python | |
| from kipy import KiCad | |
| + | |
| + | |
| logger.info("Connecting to KiCAD via IPC...") | |
| + | |
| + | |
| + from tempfile import gettempdir | |
| # Try to connect with provided path or auto-detect | |
| - socket_paths_to_try = [] | |
| - if socket_path: | |
| - socket_paths_to_try.append(socket_path) | |
| - else: | |
| - # Common socket locations | |
| - socket_paths_to_try = [ | |
| - 'ipc:///tmp/kicad/api.sock', # Linux default | |
| - f'ipc:///run/user/{os.getuid()}/kicad/api.sock', # XDG runtime | |
| - None # Let kipy auto-detect | |
| - ] | |
| + socket_paths_to_try = ["ipc://C:/Users/mohan/AppData/Local/Temp/kicad/api.sock"] | |
| last_error = None | |
| for path in socket_paths_to_try: |
| if __name__ == "__main__": | |
| try: | |
| import debugpy | |
| # Listen on port 5678 | |
| debugpy.listen(("0.0.0.0", 5678)) | |
| # Print to stderr so we don't break JSON-RPC over stdout | |
| print("⏳ Waiting for debugger to attach on port 5678...", file=sys.stderr) | |
| # Pause execution until VS Code attaches | |
| debugpy.wait_for_client() | |
| print("✅ Debugger attached! Starting main...", file=sys.stderr) | |
| except ImportError: | |
| print("⚠️ debugpy not found, skipping debug wait", file=sys.stderr) | |
| main() |
For a dead simple, minimal, and elegant first project that works perfectly with standard JLCPCB/PCBWay assembly services, I recommend:
This is the "Hello World" of artistic PCBs. It is physically small, requires no programming, has only ~3 components, and you can make the board itself a custom shape (like a heart, a cloud, or your logo).
- Complexity: 1/10 (Dead Simple)
- Cost: <$5 for 5 boards
- Power: CR2032 Coin Cell (lasts for days/weeks)
You can copy-paste these prompts into the LLM/MCP server one by one to build this.
First, create the container for your design.
create_project "glowing_badge" "my_projects"
Let's make it a nice compact square or circle (e.g., 40mm).
set_board_size 40 40(Note: Later you can ask to "make the board circular", but starting square is safer for auto-routing).
You only need three things: A battery holder, a resistor, and an LED.
add_schematic_component "Battery_Cell_Coin_CR2032" "BT1" "3V"add_schematic_component "R_Small" "R1" "330"add_schematic_component "LED_Small" "D1" "Red"
(The 330 ohm resistor is perfect for a Red LED on a 3V battery).
Connect them in a simple loop: Battery (+) -> Resistor -> LED -> Battery (-).
add_schematic_wire "BT1" 1 "R1" 1add_schematic_wire "R1" 2 "D1" 2add_schematic_wire "D1" 1 "BT1" 2
(Note: Check the pin numbers—usually LED pin 2 is Anode/positive, but if it's backwards, you just flip the LED later).
Now, place them on the board and connect the copper traces.
place_component "BT1" 20 20place_component "D1" 20 10place_component "R1" 20 30route_trace "BT1" 1 "R1" 1 0.5route_trace "R1" 2 "D1" 2 0.5route_trace "D1" 1 "BT1" 2 0.5
- No Wires: The battery holder snaps right onto the board.
- No Code: It turns on when you put the battery in.
- Useful: You can attach a pin to the back and wear it, or put a magnet on it for your fridge.
- Cheap: Both JLCPCB and PCBWay have the "CR2032 Holder" in their standard parts library (LCSC Part #
C70377is a common one if you need to specify).
Once you have the basics above working, try this command to make it look professional:
"Add a mounting hole at the top so I can hang it on a keychain"
add_mounting_hole 20 5 3.0
Yes, the KiCAD MCP server has a specific tool for this called align_components.
It is part of the "Component Placement" toolset and allows you to align multiple selected components relative to one another (e.g., to the left, right, top, bottom, or center).
You don't typically need to calculate the coordinates yourself. You can issue natural language commands like:
- "Align R1, R2, and R3 vertically."
- "Align all LEDs horizontally and distribute them evenly."
- "Align U1 and U2 to the top."
If you are trying to create a grid or a specific pattern (like for the LED badge example), these two tools are often better than simple alignment:
place_component_array: Instantly creates a grid (rows/columns) or circular pattern of components.- Example: "Place 16 LEDs in a 4x4 grid with 10mm spacing."
distribute_components(often implied in alignment prompts): Ensures equal spacing between parts.
If you want absolute precision without using the align tool, you can simply tell the LLM to set a shared coordinate:
- "Move R1, R2, and R3 so they all have a Y coordinate of 50." (This effectively creates a horizontal alignment).
| 1. if moo doesn't already exist, | |
| Create a new KiCAD project named moo in C:\Users\mohan\Documents\melbpc\kicad\helloworld | |
| otherwise | |
| Delete the project called moo | |
| then do step 1 again. | |
| Under powershell, the command is: Remove-Item -Path moo.kicad_pro, moo.kicad_sch, moo.kicad_pcb, moo.kicad_prl -Force | |
| 2. List all symbol libaries available | |
| 3. List all footprints available | |
| 4. Find the following symbols: | |
| - USB4135-GF-A | |
| 5. Search footprint with pattern *R_0603* |
| ... | |
| (lib (name "Triac_Thyristor")(type "KiCad")(uri "${KICAD9_SYMBOL_DIR}/Triac_Thyristor.kicad_sym")(options "")(descr "TRIAC and thyristor symbols")) | |
| (lib (name "Valve")(type "KiCad")(uri "${KICAD9_SYMBOL_DIR}/Valve.kicad_sym")(options "")(descr "Valve symbols")) | |
| (lib (name "Video")(type "KiCad")(uri "${KICAD9_SYMBOL_DIR}/Video.kicad_sym")(options "")(descr "Video symbols")) | |
| (lib (name "JLCPCB_Resistors")(type "KiCad")(uri "C:/Users/mohan/Documents/KiCad/9.0/3rdparty/symbols/com_github_CDFER_JLCPCB-Kicad-Library/JLCPCB-Resistors.kicad_sym")(options "")(descr "")) | |
| ... |