# KiCAD MCP Server Architecture This document describes the system architecture for contributors who want to understand, modify, or extend the server. --- ## System Overview ``` AI Assistant (Claude, etc.) | | MCP Protocol (JSON-RPC 2.0 over STDIO) v TypeScript MCP Server (src/) | | Spawn Python subprocess, pass JSON commands v Python KiCAD Interface (python/) | | pcbnew SWIG API or KiCAD IPC API v KiCAD 9.0+ ``` The server has two layers: 1. **TypeScript layer** -- implements the MCP protocol, registers tools with schemas, validates input, manages the Python subprocess 2. **Python layer** -- interfaces with KiCAD's pcbnew API (SWIG bindings) or IPC API for actual PCB/schematic operations --- ## Directory Structure ``` KiCAD-MCP-Server/ src/ # TypeScript MCP server server.ts # Main server, tool registration, Python subprocess logger.ts # Logging configuration tools/ # Tool definitions (one file per category) registry.ts # Tool category definitions and lookup router.ts # Router tools (list/search/execute) project.ts # Project management tools board.ts # Board operations tools component.ts # Component tools routing.ts # Routing tools design-rules.ts # DRC tools export.ts # Export tools schematic.ts # Schematic tools library.ts # Footprint library tools library-symbol.ts # Symbol library tools footprint.ts # Footprint creator tools symbol-creator.ts # Symbol creator tools datasheet.ts # Datasheet tools jlcpcb-api.ts # JLCPCB integration tools freerouting.ts # Autorouter tools ui.ts # UI management tools resources/ # MCP resource definitions prompts/ # MCP prompt templates utils/ # Utility functions python/ # Python KiCAD interface kicad_interface.py # Main entry point, command router commands/ # Command implementations project.py # Project operations board.py # Board manipulation component.py # PCB component operations component_schematic.py # Schematic component operations connection_schematic.py # Schematic wiring and connections schematic.py # Schematic file management routing.py # Trace routing design_rules.py # DRC operations export.py # File export library.py # Footprint library access library_symbol.py # Symbol library access footprint.py # Custom footprint creation symbol_creator.py # Custom symbol creation datasheet_manager.py # Datasheet enrichment jlcpcb.py # JLCPCB API client jlcsearch.py # JLCSearch public API client jlcpcb_parts.py # JLCPCB parts database freerouting.py # Freerouting autorouter svg_import.py # SVG to PCB polygon conversion dynamic_symbol_loader.py # Dynamic symbol injection wire_manager.py # S-expression wire creation pin_locator.py # Pin position discovery layers.py # Layer utilities outline.py # Board outline utilities size.py # Size/dimension utilities view.py # Board rendering utilities kicad_api/ # Backend abstraction base.py # Abstract base class factory.py # Backend auto-detection swig_backend.py # pcbnew SWIG API backend ipc_backend.py # KiCAD 9.0 IPC API backend schemas/ # JSON Schema definitions tool_schemas.py # Tool parameter schemas resources/ # Resource handlers templates/ # Schematic/project templates tests/ # Python test suite utils/ # Platform detection, helpers docs/ # Documentation config/ # Configuration examples ``` --- ## TypeScript Layer ### Server Startup (`src/server.ts`) 1. Creates an MCP server instance 2. Registers all tools from each tool file (registerProjectTools, registerBoardTools, etc.) 3. Registers resources and prompts 4. Starts the STDIO transport for MCP communication 5. On first tool call, spawns the Python subprocess ### Tool Registration Each tool file exports a `register*Tools(server, callKicadScript)` function that: - Defines tool name, description, and Zod schema for parameters - Registers a handler that calls `callKicadScript(command, args)` Example from `src/tools/project.ts`: ```typescript server.tool( "create_project", "Create a new KiCAD project", { name: z.string(), path: z.string() }, async (args) => { const result = await callKicadScript("create_project", args); return { content: [{ type: "text", text: JSON.stringify(result) }] }; }, ); ``` ### Tool Router (`src/tools/router.ts` and `src/tools/registry.ts`) The router pattern reduces AI context usage: - `registry.ts` defines tool categories and which tools are "direct" (always visible) vs "routed" (discoverable) - `router.ts` provides 4 meta-tools: `list_tool_categories`, `get_category_tools`, `search_tools`, `execute_tool` - Routed tools are not registered as individual MCP tools -- they are invoked through `execute_tool` ### Python Subprocess Communication `callKicadScript(command, args)` in `server.ts`: 1. Spawns `python3 python/kicad_interface.py` (if not already running) 2. Sends a JSON message: `{"command": "...", "params": {...}}` 3. Reads the JSON response 4. Returns the result to the MCP tool handler --- ## Python Layer ### Main Entry Point (`python/kicad_interface.py`) - Reads JSON commands from stdin - Routes commands to the appropriate handler - Manages the pcbnew board object lifecycle - Handles backend selection (SWIG vs IPC) - Auto-saves after board-modifying operations ### Command Routing Commands are routed by name to handler methods. The mapping is defined in `kicad_interface.py`. Each handler: 1. Receives a params dict 2. Calls the appropriate command class method 3. Returns a result dict with `success`, `message`, and any additional data ### Backend System (`python/kicad_api/`) Two backends for interacting with KiCAD: **SWIG Backend** (default): - Direct Python bindings to KiCAD's C++ API via SWIG - Operates on files -- loads .kicad_pcb, modifies in memory, saves back - Works without KiCAD running - Requires manual UI reload to see changes **IPC Backend** (experimental): - Communicates with running KiCAD via IPC API socket - Changes appear in the UI immediately - Requires KiCAD 9.0+ running with IPC enabled - Falls back to SWIG when unavailable `factory.py` auto-detects which backend to use. ### Schematic System Schematic manipulation uses a different stack than PCB operations: - **kicad-skip** library for reading/modifying schematic files - **S-expression parsing** for direct file manipulation (wires, symbols) - **DynamicSymbolLoader** for injecting any KiCad symbol into a schematic - **WireManager** for creating wires via S-expression injection - **PinLocator** for discovering pin positions with rotation support --- ## Adding a New Tool ### Step 1: Define the TypeScript Schema Create or edit a file in `src/tools/`. Register the tool with `server.tool()`: ```typescript server.tool( "my_new_tool", "Description of what the tool does", { param1: z.string().describe("Description of param1"), param2: z.number().optional().describe("Optional param2"), }, async (args) => { const result = await callKicadScript("my_new_tool", args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, ); ``` ### Step 2: Add to Registry (if routed) If the tool should be discoverable via the router (not always visible), add it to a category in `src/tools/registry.ts`: ```typescript { name: "category_name", tools: ["existing_tool", "my_new_tool"] } ``` If the tool should always be visible, add it to `directToolNames` instead. ### Step 3: Import in server.ts Import and call the registration function in `src/server.ts`: ```typescript import { registerMyTools } from "./tools/my-tools.js"; registerMyTools(server, callKicadScript); ``` ### Step 4: Implement the Python Handler Add a handler in `python/kicad_interface.py` or create a new command module in `python/commands/`: ```python def handle_my_new_tool(self, params): # Implementation using pcbnew API return {"success": True, "message": "Done", "data": result} ``` Route the command in the main handler: ```python elif command == "my_new_tool": return self.handle_my_new_tool(params) ``` ### Step 5: Build and Test ```bash npm run build # Compile TypeScript npm run test:py # Run Python tests ``` --- ## Testing ### Python Tests Located in `python/tests/`. Run with: ```bash pytest python/tests/ -v ``` Key test files: - `test_schematic_tools.py` -- schematic tool tests - `test_freerouting.py` -- autorouter tests - `test_delete_schematic_component.py` -- component deletion tests - `test_schematic_component_fields.py` -- field inspection tests - `test_platform_helper.py` -- platform detection tests ### Manual Testing 1. Build the server: `npm run build` 2. Configure in Claude Desktop or Claude Code 3. Test tools interactively through your MCP client --- ## Key Design Decisions - **TypeScript + Python split**: TypeScript handles MCP protocol (well-supported SDK), Python handles KiCAD (only available API) - **Router pattern**: Reduces AI context from ~80K tokens (122 tools) to manageable size - **Auto-save**: Every board-modifying SWIG operation auto-saves to prevent data loss - **Dynamic symbol loading**: Works around kicad-skip's inability to create symbols from scratch - **S-expression wire injection**: Works around kicad-skip's inability to create wires --- ## Source Files Reference | File | Purpose | | ------------------------------------------ | ----------------------------------- | | `src/server.ts` | MCP server, subprocess management | | `src/tools/registry.ts` | Tool categories and organization | | `src/tools/router.ts` | Router meta-tools | | `python/kicad_interface.py` | Python entry point, command routing | | `python/kicad_api/factory.py` | Backend selection | | `python/commands/dynamic_symbol_loader.py` | Symbol injection system | | `python/commands/wire_manager.py` | Wire creation engine | | `python/commands/pin_locator.py` | Pin position discovery |