#
tokens: 49891/50000 52/94 files (page 1/6)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 6. Use http://codebase.md/mixelpixx/kicad-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .github
│   └── workflows
│       └── ci.yml
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG_2025-10-26.md
├── CHANGELOG_2025-11-01.md
├── CHANGELOG_2025-11-05.md
├── CHANGELOG_2025-11-30.md
├── config
│   ├── claude-desktop-config.json
│   ├── default-config.json
│   ├── linux-config.example.json
│   ├── macos-config.example.json
│   └── windows-config.example.json
├── CONTRIBUTING.md
├── docs
│   ├── BUILD_AND_TEST_SESSION.md
│   ├── CLIENT_CONFIGURATION.md
│   ├── IPC_API_MIGRATION_PLAN.md
│   ├── IPC_BACKEND_STATUS.md
│   ├── JLCPCB_INTEGRATION_PLAN.md
│   ├── KNOWN_ISSUES.md
│   ├── LIBRARY_INTEGRATION.md
│   ├── LINUX_COMPATIBILITY_AUDIT.md
│   ├── PLATFORM_GUIDE.md
│   ├── REALTIME_WORKFLOW.md
│   ├── ROADMAP.md
│   ├── STATUS_SUMMARY.md
│   ├── UI_AUTO_LAUNCH.md
│   ├── VISUAL_FEEDBACK.md
│   ├── WEEK1_SESSION1_SUMMARY.md
│   ├── WEEK1_SESSION2_SUMMARY.md
│   └── WINDOWS_TROUBLESHOOTING.md
├── LICENSE
├── package-json.json
├── package-lock.json
├── package.json
├── pytest.ini
├── python
│   ├── commands
│   │   ├── __init__.py
│   │   ├── board
│   │   │   ├── __init__.py
│   │   │   ├── layers.py
│   │   │   ├── outline.py
│   │   │   ├── size.py
│   │   │   └── view.py
│   │   ├── board.py
│   │   ├── component_schematic.py
│   │   ├── component.py
│   │   ├── connection_schematic.py
│   │   ├── design_rules.py
│   │   ├── export.py
│   │   ├── library_schematic.py
│   │   ├── library.py
│   │   ├── project.py
│   │   ├── routing.py
│   │   └── schematic.py
│   ├── kicad_api
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── factory.py
│   │   ├── ipc_backend.py
│   │   └── swig_backend.py
│   ├── kicad_interface.py
│   ├── requirements.txt
│   ├── resources
│   │   ├── __init__.py
│   │   └── resource_definitions.py
│   ├── schemas
│   │   ├── __init__.py
│   │   └── tool_schemas.py
│   ├── test_ipc_backend.py
│   └── utils
│       ├── __init__.py
│       ├── kicad_process.py
│       └── platform_helper.py
├── README.md
├── requirements-dev.txt
├── requirements.txt
├── scripts
│   ├── auto_refresh_kicad.sh
│   └── install-linux.sh
├── setup-windows.ps1
├── src
│   ├── config.ts
│   ├── index.ts
│   ├── kicad-server.ts
│   ├── logger.ts
│   ├── prompts
│   │   ├── component.ts
│   │   ├── design.ts
│   │   ├── index.ts
│   │   └── routing.ts
│   ├── resources
│   │   ├── board.ts
│   │   ├── component.ts
│   │   ├── index.ts
│   │   ├── library.ts
│   │   └── project.ts
│   ├── server.ts
│   ├── tools
│   │   ├── board.ts
│   │   ├── component.ts
│   │   ├── component.txt
│   │   ├── design-rules.ts
│   │   ├── export.ts
│   │   ├── index.ts
│   │   ├── library.ts
│   │   ├── project.ts
│   │   ├── routing.ts
│   │   ├── schematic.ts
│   │   └── ui.ts
│   └── utils
│       └── resource-helpers.ts
├── tests
│   ├── __init__.py
│   └── test_platform_helper.py
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Node.js
 2 | node_modules/
 3 | npm-debug.log*
 4 | yarn-debug.log*
 5 | yarn-error.log*
 6 | dist/
 7 | .npm
 8 | .eslintcache
 9 | 
10 | # Python
11 | __pycache__/
12 | *.py[cod]
13 | *$py.class
14 | *.so
15 | .Python
16 | build/
17 | develop-eggs/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 | .pytest_cache/
31 | .coverage
32 | htmlcov/
33 | .tox/
34 | .hypothesis/
35 | *.cover
36 | .mypy_cache/
37 | .dmypy.json
38 | dmypy.json
39 | 
40 | # Virtual Environments
41 | venv/
42 | env/
43 | ENV/
44 | 
45 | # IDEs
46 | .vscode/
47 | .idea/
48 | *.swp
49 | *.swo
50 | *~
51 | .DS_Store
52 | 
53 | # Logs
54 | logs/
55 | *.log
56 | ~/.kicad-mcp/
57 | 
58 | # Environment
59 | .env
60 | .env.local
61 | .env.*.local
62 | 
63 | # KiCAD
64 | *.kicad_pcb-bak
65 | *.kicad_sch-bak
66 | *.kicad_pro-bak
67 | *.kicad_prl
68 | *-backups/
69 | fp-info-cache
70 | 
71 | # Testing
72 | test_output/
73 | schematic_test_output/
74 | coverage.xml
75 | .coverage.*
76 | 
77 | # OS
78 | Thumbs.db
79 | Desktop.ini
80 | 
```

--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Pre-commit hooks configuration
 2 | # See https://pre-commit.com for more information
 3 | 
 4 | repos:
 5 |   # Python code formatting
 6 |   - repo: https://github.com/psf/black
 7 |     rev: 23.7.0
 8 |     hooks:
 9 |       - id: black
10 |         language_version: python3
11 |         files: ^python/
12 | 
13 |   # Python import sorting
14 |   - repo: https://github.com/pycqa/isort
15 |     rev: 5.12.0
16 |     hooks:
17 |       - id: isort
18 |         files: ^python/
19 |         args: ["--profile", "black"]
20 | 
21 |   # Python type checking
22 |   - repo: https://github.com/pre-commit/mirrors-mypy
23 |     rev: v1.5.0
24 |     hooks:
25 |       - id: mypy
26 |         files: ^python/
27 |         args: [--ignore-missing-imports]
28 | 
29 |   # Python linting
30 |   - repo: https://github.com/pycqa/flake8
31 |     rev: 6.1.0
32 |     hooks:
33 |       - id: flake8
34 |         files: ^python/
35 |         args: [--max-line-length=100, --extend-ignore=E203]
36 | 
37 |   # TypeScript/JavaScript formatting
38 |   - repo: https://github.com/pre-commit/mirrors-prettier
39 |     rev: v3.0.3
40 |     hooks:
41 |       - id: prettier
42 |         types_or: [javascript, typescript, json, yaml, markdown]
43 |         files: \.(ts|js|json|ya?ml|md)$
44 | 
45 |   # General file checks
46 |   - repo: https://github.com/pre-commit/pre-commit-hooks
47 |     rev: v4.4.0
48 |     hooks:
49 |       - id: trailing-whitespace
50 |       - id: end-of-file-fixer
51 |       - id: check-yaml
52 |       - id: check-json
53 |       - id: check-added-large-files
54 |         args: [--maxkb=500]
55 |       - id: check-merge-conflict
56 |       - id: detect-private-key
57 | 
58 |   # Python security checks
59 |   - repo: https://github.com/PyCQA/bandit
60 |     rev: 1.7.5
61 |     hooks:
62 |       - id: bandit
63 |         args: [-c, pyproject.toml]
64 |         files: ^python/
65 | 
66 |   # Markdown linting
67 |   - repo: https://github.com/igorshubovych/markdownlint-cli
68 |     rev: v0.37.0
69 |     hooks:
70 |       - id: markdownlint
71 |         args: [--fix]
72 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # KiCAD MCP Server
  2 | 
  3 | A Model Context Protocol (MCP) server that enables AI assistants like Claude to interact with KiCAD for PCB design automation. Built on the MCP 2025-06-18 specification, this server provides comprehensive tool schemas and real-time project state access for intelligent PCB design workflows.
  4 | 
  5 | ## Overview
  6 | 
  7 | The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standard from Anthropic that allows AI assistants to securely connect to external tools and data sources. This implementation provides a standardized bridge between AI assistants and KiCAD, enabling natural language control of PCB design operations.
  8 | 
  9 | **Key Capabilities:**
 10 | - 52 fully-documented tools with JSON Schema validation
 11 | - 8 dynamic resources exposing project state
 12 | - Full MCP 2025-06-18 protocol compliance
 13 | - Cross-platform support (Linux, Windows, macOS)
 14 | - Real-time KiCAD UI integration via IPC API (experimental)
 15 | - Comprehensive error handling and logging
 16 | 
 17 | ## What's New in v2.1.0
 18 | 
 19 | ### IPC Backend (Experimental)
 20 | We are currently implementing and testing the KiCAD 9.0 IPC API for real-time UI synchronization:
 21 | - Changes made via MCP tools appear immediately in the KiCAD UI
 22 | - No manual reload required when IPC is active
 23 | - Hybrid backend: uses IPC when available, falls back to SWIG API
 24 | - 20+ commands now support IPC including routing, component placement, and zone operations
 25 | 
 26 | Note: IPC features are under active development and testing. Enable IPC in KiCAD via Preferences > Plugins > Enable IPC API Server.
 27 | 
 28 | ### Comprehensive Tool Schemas
 29 | Every tool now includes complete JSON Schema definitions with:
 30 | - Detailed parameter descriptions and constraints
 31 | - Input validation with type checking
 32 | - Required vs. optional parameter specifications
 33 | - Enumerated values for categorical inputs
 34 | - Clear documentation of what each tool does
 35 | 
 36 | ### Resources Capability
 37 | Access project state without executing tools:
 38 | - `kicad://project/current/info` - Project metadata
 39 | - `kicad://project/current/board` - Board properties
 40 | - `kicad://project/current/components` - Component list (JSON)
 41 | - `kicad://project/current/nets` - Electrical nets
 42 | - `kicad://project/current/layers` - Layer stack configuration
 43 | - `kicad://project/current/design-rules` - Current DRC settings
 44 | - `kicad://project/current/drc-report` - Design rule violations
 45 | - `kicad://board/preview.png` - Board visualization (PNG)
 46 | 
 47 | ### Protocol Compliance
 48 | - Updated to MCP SDK 1.21.0 (latest)
 49 | - Full JSON-RPC 2.0 support
 50 | - Proper capability negotiation
 51 | - Standards-compliant error codes
 52 | 
 53 | ## Available Tools
 54 | 
 55 | The server provides 52 tools organized into functional categories:
 56 | 
 57 | ### Project Management (4 tools)
 58 | - `create_project` - Initialize new KiCAD projects
 59 | - `open_project` - Load existing project files
 60 | - `save_project` - Save current project state
 61 | - `get_project_info` - Retrieve project metadata
 62 | 
 63 | ### Board Operations (9 tools)
 64 | - `set_board_size` - Configure PCB dimensions
 65 | - `add_board_outline` - Create board edge (rectangle, circle, polygon)
 66 | - `add_layer` - Add custom layers to stack
 67 | - `set_active_layer` - Switch working layer
 68 | - `get_layer_list` - List all board layers
 69 | - `get_board_info` - Retrieve board properties
 70 | - `get_board_2d_view` - Generate board preview image
 71 | - `add_mounting_hole` - Place mounting holes
 72 | - `add_board_text` - Add text annotations
 73 | 
 74 | ### Component Placement (10 tools)
 75 | - `place_component` - Place single component with footprint
 76 | - `move_component` - Reposition existing component
 77 | - `rotate_component` - Rotate component by angle
 78 | - `delete_component` - Remove component from board
 79 | - `edit_component` - Modify component properties
 80 | - `get_component_properties` - Query component details
 81 | - `get_component_list` - List all placed components
 82 | - `place_component_array` - Create component grids/patterns
 83 | - `align_components` - Align multiple components
 84 | - `duplicate_component` - Copy existing component
 85 | 
 86 | ### Routing & Nets (8 tools)
 87 | - `add_net` - Create electrical net
 88 | - `route_trace` - Route copper traces
 89 | - `add_via` - Place vias for layer transitions
 90 | - `delete_trace` - Remove traces
 91 | - `get_nets_list` - List all nets
 92 | - `create_netclass` - Define net class with rules
 93 | - `add_copper_pour` - Create copper zones/pours
 94 | - `route_differential_pair` - Route differential signals
 95 | 
 96 | ### Library Management (4 tools)
 97 | - `list_libraries` - List available footprint libraries
 98 | - `search_footprints` - Search for footprints
 99 | - `list_library_footprints` - List footprints in library
100 | - `get_footprint_info` - Get footprint details
101 | 
102 | ### Design Rules (4 tools)
103 | - `set_design_rules` - Configure DRC parameters
104 | - `get_design_rules` - Retrieve current rules
105 | - `run_drc` - Execute design rule check
106 | - `get_drc_violations` - Get DRC error report
107 | 
108 | ### Export (5 tools)
109 | - `export_gerber` - Generate Gerber fabrication files
110 | - `export_pdf` - Export PDF documentation
111 | - `export_svg` - Create SVG vector graphics
112 | - `export_3d` - Generate 3D models (STEP/VRML)
113 | - `export_bom` - Produce bill of materials
114 | 
115 | ### Schematic Design (6 tools)
116 | - `create_schematic` - Initialize new schematic
117 | - `load_schematic` - Open existing schematic
118 | - `add_schematic_component` - Place symbols
119 | - `add_schematic_wire` - Connect component pins
120 | - `list_schematic_libraries` - List symbol libraries
121 | - `export_schematic_pdf` - Export schematic PDF
122 | 
123 | ### UI Management (2 tools)
124 | - `check_kicad_ui` - Check if KiCAD is running
125 | - `launch_kicad_ui` - Launch KiCAD application
126 | 
127 | ## Prerequisites
128 | 
129 | ### Required Software
130 | 
131 | **KiCAD 9.0 or Higher**
132 | - Download from [kicad.org/download](https://www.kicad.org/download/)
133 | - Must include Python module (pcbnew)
134 | - Verify installation:
135 |   ```bash
136 |   python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())"
137 |   ```
138 | 
139 | **Node.js 18 or Higher**
140 | - Download from [nodejs.org](https://nodejs.org/)
141 | - Verify: `node --version` and `npm --version`
142 | 
143 | **Python 3.10 or Higher**
144 | - Usually included with KiCAD
145 | - Required packages (auto-installed):
146 |   - kicad-python (kipy) >= 0.5.0 (IPC API support, optional but recommended)
147 |   - kicad-skip >= 0.1.0 (schematic support)
148 |   - Pillow >= 9.0.0 (image processing)
149 |   - cairosvg >= 2.7.0 (SVG rendering)
150 |   - colorlog >= 6.7.0 (logging)
151 |   - pydantic >= 2.5.0 (validation)
152 |   - requests >= 2.32.5 (HTTP client)
153 |   - python-dotenv >= 1.0.0 (environment)
154 | 
155 | **MCP Client**
156 | Choose one:
157 | - [Claude Desktop](https://claude.ai/download) - Official Anthropic desktop app
158 | - [Claude Code](https://docs.claude.com/claude-code) - Official CLI tool
159 | - [Cline](https://github.com/cline/cline) - VSCode extension
160 | 
161 | ### Supported Platforms
162 | - **Linux** (Ubuntu 22.04+, Fedora, Arch) - Primary platform, fully tested
163 | - **Windows 10/11** - Fully supported with automated setup
164 | - **macOS** - Experimental support
165 | 
166 | ## Installation
167 | 
168 | ### Linux (Ubuntu/Debian)
169 | 
170 | ```bash
171 | # Install KiCAD 9.0
172 | sudo add-apt-repository --yes ppa:kicad/kicad-9.0-releases
173 | sudo apt-get update
174 | sudo apt-get install -y kicad kicad-libraries
175 | 
176 | # Install Node.js
177 | curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
178 | sudo apt-get install -y nodejs
179 | 
180 | # Clone and build
181 | git clone https://github.com/mixelpixx/KiCAD-MCP-Server.git
182 | cd KiCAD-MCP-Server
183 | npm install
184 | pip3 install -r requirements.txt
185 | npm run build
186 | 
187 | # Verify
188 | python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())"
189 | ```
190 | 
191 | ### Windows 10/11
192 | 
193 | **Automated Setup (Recommended):**
194 | ```powershell
195 | git clone https://github.com/mixelpixx/KiCAD-MCP-Server.git
196 | cd KiCAD-MCP-Server
197 | .\setup-windows.ps1
198 | ```
199 | 
200 | The script will:
201 | - Detect KiCAD installation
202 | - Verify prerequisites
203 | - Install dependencies
204 | - Build project
205 | - Generate configuration
206 | - Run diagnostics
207 | 
208 | **Manual Setup:**
209 | See [Windows Installation Guide](docs/WINDOWS_SETUP.md) for detailed instructions.
210 | 
211 | ### macOS
212 | 
213 | ```bash
214 | # Install KiCAD 9.0 from kicad.org/download/macos
215 | 
216 | # Install Node.js
217 | brew install node@20
218 | 
219 | # Clone and build
220 | git clone https://github.com/mixelpixx/KiCAD-MCP-Server.git
221 | cd KiCAD-MCP-Server
222 | npm install
223 | pip3 install -r requirements.txt
224 | npm run build
225 | ```
226 | 
227 | ## Configuration
228 | 
229 | ### Claude Desktop
230 | 
231 | Edit configuration file:
232 | - **Linux/macOS:** `~/.config/Claude/claude_desktop_config.json`
233 | - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
234 | 
235 | **Configuration:**
236 | ```json
237 | {
238 |   "mcpServers": {
239 |     "kicad": {
240 |       "command": "node",
241 |       "args": ["/path/to/KiCAD-MCP-Server/dist/index.js"],
242 |       "env": {
243 |         "PYTHONPATH": "/path/to/kicad/python",
244 |         "LOG_LEVEL": "info"
245 |       }
246 |     }
247 |   }
248 | }
249 | ```
250 | 
251 | **Platform-specific PYTHONPATH:**
252 | - **Linux:** `/usr/lib/kicad/lib/python3/dist-packages`
253 | - **Windows:** `C:\Program Files\KiCad\9.0\lib\python3\dist-packages`
254 | - **macOS:** `/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages`
255 | 
256 | ### Cline (VSCode)
257 | 
258 | Edit: `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
259 | 
260 | Use the same configuration format as Claude Desktop above.
261 | 
262 | ### Claude Code
263 | 
264 | Claude Code automatically detects MCP servers in the current directory. No additional configuration needed.
265 | 
266 | ## Usage Examples
267 | 
268 | ### Basic PCB Design Workflow
269 | 
270 | ```text
271 | Create a new KiCAD project named 'LEDBoard' in my Documents folder.
272 | Set the board size to 50mm x 50mm and add a rectangular outline.
273 | Place a mounting hole at each corner, 3mm from the edges, with 3mm diameter.
274 | Add text 'LED Controller v1.0' on the front silkscreen at position x=25mm, y=45mm.
275 | ```
276 | 
277 | ### Component Placement
278 | 
279 | ```text
280 | Place an LED at x=10mm, y=10mm using footprint LED_SMD:LED_0805_2012Metric.
281 | Create a grid of 4 resistors (R1-R4) starting at x=20mm, y=20mm with 5mm spacing.
282 | Align all resistors horizontally and distribute them evenly.
283 | ```
284 | 
285 | ### Routing
286 | 
287 | ```text
288 | Create a net named 'LED1' and route a 0.3mm trace from R1 pad 2 to LED1 anode.
289 | Add a copper pour for GND on the bottom layer covering the entire board.
290 | Create a differential pair for USB_P and USB_N with 0.2mm width and 0.15mm gap.
291 | ```
292 | 
293 | ### Design Verification
294 | 
295 | ```text
296 | Set design rules with 0.15mm clearance and 0.2mm minimum track width.
297 | Run a design rule check and show me any violations.
298 | Export Gerber files to the 'fabrication' folder.
299 | ```
300 | 
301 | ### Using Resources
302 | 
303 | Resources provide read-only access to project state:
304 | 
305 | ```text
306 | Show me the current component list.
307 | What are the current design rules?
308 | Display the board preview.
309 | List all electrical nets.
310 | ```
311 | 
312 | ## Architecture
313 | 
314 | ### MCP Protocol Layer
315 | - **JSON-RPC 2.0 Transport:** Bi-directional communication via STDIO
316 | - **Protocol Version:** MCP 2025-06-18
317 | - **Capabilities:** Tools (52), Resources (8)
318 | - **Error Handling:** Standard JSON-RPC error codes
319 | 
320 | ### TypeScript Server (`src/`)
321 | - Implements MCP protocol specification
322 | - Manages Python subprocess lifecycle
323 | - Handles message routing and validation
324 | - Provides logging and error recovery
325 | 
326 | ### Python Interface (`python/`)
327 | - **kicad_interface.py:** Main entry point, MCP message handler, command routing
328 | - **kicad_api/:** Backend implementations
329 |   - `base.py` - Abstract base classes for backends
330 |   - `ipc_backend.py` - KiCAD 9.0 IPC API backend (real-time UI sync)
331 |   - `swig_backend.py` - pcbnew SWIG API backend (file-based operations)
332 |   - `factory.py` - Backend auto-detection and instantiation
333 | - **schemas/tool_schemas.py:** JSON Schema definitions for all tools
334 | - **resources/resource_definitions.py:** Resource handlers and URIs
335 | - **commands/:** Modular command implementations
336 |   - `project.py` - Project operations
337 |   - `board.py` - Board manipulation
338 |   - `component.py` - Component placement
339 |   - `routing.py` - Trace routing and nets
340 |   - `design_rules.py` - DRC operations
341 |   - `export.py` - File generation
342 |   - `schematic.py` - Schematic design
343 |   - `library.py` - Footprint libraries
344 | 
345 | ### KiCAD Integration
346 | - **pcbnew API (SWIG):** Direct Python bindings to KiCAD for file operations
347 | - **IPC API (kipy):** Real-time communication with running KiCAD instance (experimental)
348 | - **Hybrid Backend:** Automatically uses IPC when available, falls back to SWIG
349 | - **kicad-skip:** Schematic file manipulation
350 | - **Platform Detection:** Cross-platform path handling
351 | - **UI Management:** Automatic KiCAD UI launch/detection
352 | 
353 | ## Development
354 | 
355 | ### Building from Source
356 | 
357 | ```bash
358 | # Install dependencies
359 | npm install
360 | pip3 install -r requirements.txt
361 | 
362 | # Build TypeScript
363 | npm run build
364 | 
365 | # Watch mode for development
366 | npm run dev
367 | ```
368 | 
369 | ### Running Tests
370 | 
371 | ```bash
372 | # TypeScript tests
373 | npm run test:ts
374 | 
375 | # Python tests
376 | npm run test:py
377 | 
378 | # All tests with coverage
379 | npm run test:coverage
380 | ```
381 | 
382 | ### Linting and Formatting
383 | 
384 | ```bash
385 | # Lint TypeScript and Python
386 | npm run lint
387 | 
388 | # Format code
389 | npm run format
390 | ```
391 | 
392 | ## Troubleshooting
393 | 
394 | ### Server Not Appearing in Client
395 | 
396 | **Symptoms:** MCP server doesn't show up in Claude Desktop or Cline
397 | 
398 | **Solutions:**
399 | 1. Verify build completed: `ls dist/index.js`
400 | 2. Check configuration paths are absolute
401 | 3. Restart MCP client completely
402 | 4. Check client logs for error messages
403 | 
404 | ### Python Module Import Errors
405 | 
406 | **Symptoms:** `ModuleNotFoundError: No module named 'pcbnew'`
407 | 
408 | **Solutions:**
409 | 1. Verify KiCAD installation: `python3 -c "import pcbnew"`
410 | 2. Check PYTHONPATH in configuration matches your KiCAD installation
411 | 3. Ensure KiCAD was installed with Python support
412 | 
413 | ### Tool Execution Failures
414 | 
415 | **Symptoms:** Tools fail with unclear errors
416 | 
417 | **Solutions:**
418 | 1. Check server logs: `~/.kicad-mcp/logs/kicad_interface.log`
419 | 2. Verify a project is loaded before running board operations
420 | 3. Ensure file paths are absolute, not relative
421 | 4. Check tool parameter types match schema requirements
422 | 
423 | ### Windows-Specific Issues
424 | 
425 | **Symptoms:** Server fails to start on Windows
426 | 
427 | **Solutions:**
428 | 1. Run automated diagnostics: `.\setup-windows.ps1`
429 | 2. Verify Python path uses double backslashes: `C:\\Program Files\\KiCad\\9.0`
430 | 3. Check Windows Event Viewer for Node.js errors
431 | 4. See [Windows Troubleshooting Guide](docs/WINDOWS_TROUBLESHOOTING.md)
432 | 
433 | ### Getting Help
434 | 
435 | 1. Check the [GitHub Issues](https://github.com/mixelpixx/KiCAD-MCP-Server/issues)
436 | 2. Review server logs: `~/.kicad-mcp/logs/kicad_interface.log`
437 | 3. Open a new issue with:
438 |    - Operating system and version
439 |    - KiCAD version (`python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())"`)
440 |    - Node.js version (`node --version`)
441 |    - Full error message and stack trace
442 |    - Relevant log excerpts
443 | 
444 | ## Project Status
445 | 
446 | **Current Version:** 2.1.0-alpha
447 | 
448 | **Working Features:**
449 | - Project creation and management
450 | - Board outline and sizing
451 | - Layer management
452 | - Component placement with footprint library loading
453 | - Mounting holes and text annotations
454 | - Design rule checking
455 | - Export to Gerber, PDF, SVG, 3D
456 | - Schematic creation and editing
457 | - UI auto-launch
458 | - Full MCP protocol compliance
459 | 
460 | **Under Active Development (IPC Backend):**
461 | - Real-time UI synchronization via KiCAD 9.0 IPC API
462 | - IPC-enabled commands: route_trace, add_via, place_component, move_component, delete_component, add_copper_pour, refill_zones, add_board_outline, add_mounting_hole, and more
463 | - Hybrid footprint loading (SWIG for library access, IPC for placement)
464 | - Zone/copper pour support via IPC
465 | 
466 | Note: IPC features are experimental and under testing. Some commands may not work as expected in all scenarios.
467 | 
468 | **Planned:**
469 | - JLCPCB parts integration
470 | - Digikey API integration
471 | - Advanced routing algorithms
472 | - Smart BOM management
473 | - AI-assisted component selection
474 | - Design pattern library (Arduino shields, RPi HATs)
475 | 
476 | See [ROADMAP.md](docs/ROADMAP.md) for detailed development timeline.
477 | 
478 | ## Contributing
479 | 
480 | Contributions are welcome! Please follow these guidelines:
481 | 
482 | 1. **Report Bugs:** Open an issue with reproduction steps
483 | 2. **Suggest Features:** Describe use case and expected behavior
484 | 3. **Submit Pull Requests:**
485 |    - Fork the repository
486 |    - Create a feature branch
487 |    - Follow existing code style
488 |    - Add tests for new functionality
489 |    - Update documentation
490 |    - Submit PR with clear description
491 | 
492 | See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
493 | 
494 | ## License
495 | 
496 | This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
497 | 
498 | ## Acknowledgments
499 | 
500 | - Built on the [Model Context Protocol](https://modelcontextprotocol.io/) by Anthropic
501 | - Powered by [KiCAD](https://www.kicad.org/) open-source PCB design software
502 | - Uses [kicad-skip](https://github.com/kicad-skip) for schematic manipulation
503 | 
504 | ## Citation
505 | 
506 | If you use this project in your research or publication, please cite:
507 | 
508 | ```bibtex
509 | @software{kicad_mcp_server,
510 |   title = {KiCAD MCP Server: AI-Assisted PCB Design},
511 |   author = {mixelpixx},
512 |   year = {2025},
513 |   url = {https://github.com/mixelpixx/KiCAD-MCP-Server},
514 |   version = {2.1.0-alpha}
515 | }
516 | ```
517 | 
518 | 
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Contributing to KiCAD MCP Server
  2 | 
  3 | Thank you for your interest in contributing to the KiCAD MCP Server! This guide will help you get started with development.
  4 | 
  5 | ## Table of Contents
  6 | 
  7 | - [Development Environment Setup](#development-environment-setup)
  8 | - [Project Structure](#project-structure)
  9 | - [Development Workflow](#development-workflow)
 10 | - [Testing](#testing)
 11 | - [Code Style](#code-style)
 12 | - [Pull Request Process](#pull-request-process)
 13 | - [Roadmap & Planning](#roadmap--planning)
 14 | 
 15 | ---
 16 | 
 17 | ## Development Environment Setup
 18 | 
 19 | ### Prerequisites
 20 | 
 21 | - **KiCAD 9.0 or higher** - [Download here](https://www.kicad.org/download/)
 22 | - **Node.js v18+** - [Download here](https://nodejs.org/)
 23 | - **Python 3.10+** - Should come with KiCAD, or install separately
 24 | - **Git** - For version control
 25 | 
 26 | ### Platform-Specific Setup
 27 | 
 28 | #### Linux (Ubuntu/Debian)
 29 | 
 30 | ```bash
 31 | # Install KiCAD 9.0 from official PPA
 32 | sudo add-apt-repository --yes ppa:kicad/kicad-9.0-releases
 33 | sudo apt-get update
 34 | sudo apt-get install -y kicad kicad-libraries
 35 | 
 36 | # Install Node.js (if not already installed)
 37 | curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
 38 | sudo apt-get install -y nodejs
 39 | 
 40 | # Clone the repository
 41 | git clone https://github.com/yourusername/kicad-mcp-server.git
 42 | cd kicad-mcp-server
 43 | 
 44 | # Install Node.js dependencies
 45 | npm install
 46 | 
 47 | # Install Python dependencies
 48 | pip3 install -r requirements-dev.txt
 49 | 
 50 | # Build TypeScript
 51 | npm run build
 52 | 
 53 | # Run tests
 54 | npm test
 55 | pytest
 56 | ```
 57 | 
 58 | #### Windows
 59 | 
 60 | ```powershell
 61 | # Install KiCAD 9.0 from https://www.kicad.org/download/windows/
 62 | 
 63 | # Install Node.js from https://nodejs.org/
 64 | 
 65 | # Clone the repository
 66 | git clone https://github.com/yourusername/kicad-mcp-server.git
 67 | cd kicad-mcp-server
 68 | 
 69 | # Install Node.js dependencies
 70 | npm install
 71 | 
 72 | # Install Python dependencies
 73 | pip install -r requirements-dev.txt
 74 | 
 75 | # Build TypeScript
 76 | npm run build
 77 | 
 78 | # Run tests
 79 | npm test
 80 | pytest
 81 | ```
 82 | 
 83 | #### macOS
 84 | 
 85 | ```bash
 86 | # Install KiCAD 9.0 from https://www.kicad.org/download/macos/
 87 | 
 88 | # Install Node.js via Homebrew
 89 | brew install node
 90 | 
 91 | # Clone the repository
 92 | git clone https://github.com/yourusername/kicad-mcp-server.git
 93 | cd kicad-mcp-server
 94 | 
 95 | # Install Node.js dependencies
 96 | npm install
 97 | 
 98 | # Install Python dependencies
 99 | pip3 install -r requirements-dev.txt
100 | 
101 | # Build TypeScript
102 | npm run build
103 | 
104 | # Run tests
105 | npm test
106 | pytest
107 | ```
108 | 
109 | ---
110 | 
111 | ## Project Structure
112 | 
113 | ```
114 | kicad-mcp-server/
115 | ├── .github/
116 | │   └── workflows/        # CI/CD pipelines
117 | ├── config/               # Configuration examples
118 | │   ├── linux-config.example.json
119 | │   ├── windows-config.example.json
120 | │   └── macos-config.example.json
121 | ├── docs/                 # Documentation
122 | ├── python/               # Python interface layer
123 | │   ├── commands/         # KiCAD command handlers
124 | │   ├── integrations/     # External API integrations (JLCPCB, Digikey)
125 | │   ├── utils/            # Utility modules
126 | │   └── kicad_interface.py  # Main Python entry point
127 | ├── src/                  # TypeScript MCP server
128 | │   ├── tools/            # MCP tool implementations
129 | │   ├── resources/        # MCP resource implementations
130 | │   ├── prompts/          # MCP prompt implementations
131 | │   └── server.ts         # Main server
132 | ├── tests/                # Test suite
133 | │   ├── unit/
134 | │   ├── integration/
135 | │   └── fixtures/
136 | ├── dist/                 # Compiled JavaScript (generated)
137 | ├── node_modules/         # Node dependencies (generated)
138 | ├── package.json          # Node.js configuration
139 | ├── tsconfig.json         # TypeScript configuration
140 | ├── pytest.ini            # Pytest configuration
141 | ├── requirements.txt      # Python production dependencies
142 | └── requirements-dev.txt  # Python dev dependencies
143 | ```
144 | 
145 | ---
146 | 
147 | ## Development Workflow
148 | 
149 | ### 1. Create a Feature Branch
150 | 
151 | ```bash
152 | git checkout -b feature/your-feature-name
153 | ```
154 | 
155 | ### 2. Make Changes
156 | 
157 | - Edit TypeScript files in `src/`
158 | - Edit Python files in `python/`
159 | - Add tests for new features
160 | 
161 | ### 3. Build & Test
162 | 
163 | ```bash
164 | # Build TypeScript
165 | npm run build
166 | 
167 | # Run TypeScript linter
168 | npm run lint
169 | 
170 | # Run Python formatter
171 | black python/
172 | 
173 | # Run Python type checker
174 | mypy python/
175 | 
176 | # Run all tests
177 | npm test
178 | pytest
179 | 
180 | # Run specific test file
181 | pytest tests/test_platform_helper.py -v
182 | 
183 | # Run with coverage
184 | pytest --cov=python --cov-report=html
185 | ```
186 | 
187 | ### 4. Commit Changes
188 | 
189 | ```bash
190 | git add .
191 | git commit -m "feat: Add your feature description"
192 | ```
193 | 
194 | **Commit Message Convention:**
195 | - `feat:` - New feature
196 | - `fix:` - Bug fix
197 | - `docs:` - Documentation changes
198 | - `test:` - Adding/updating tests
199 | - `refactor:` - Code refactoring
200 | - `chore:` - Maintenance tasks
201 | 
202 | ### 5. Push and Create Pull Request
203 | 
204 | ```bash
205 | git push origin feature/your-feature-name
206 | ```
207 | 
208 | Then create a Pull Request on GitHub.
209 | 
210 | ---
211 | 
212 | ## Testing
213 | 
214 | ### Running Tests
215 | 
216 | ```bash
217 | # All tests
218 | pytest
219 | 
220 | # Unit tests only
221 | pytest -m unit
222 | 
223 | # Integration tests (requires KiCAD installed)
224 | pytest -m integration
225 | 
226 | # Platform-specific tests
227 | pytest -m linux      # Linux tests only
228 | pytest -m windows    # Windows tests only
229 | 
230 | # With coverage report
231 | pytest --cov=python --cov-report=term-missing
232 | 
233 | # Verbose output
234 | pytest -v
235 | 
236 | # Stop on first failure
237 | pytest -x
238 | ```
239 | 
240 | ### Writing Tests
241 | 
242 | Tests should be placed in `tests/` directory:
243 | 
244 | ```python
245 | # tests/test_my_feature.py
246 | import pytest
247 | 
248 | @pytest.mark.unit
249 | def test_my_feature():
250 |     """Test description"""
251 |     # Arrange
252 |     expected = "result"
253 | 
254 |     # Act
255 |     result = my_function()
256 | 
257 |     # Assert
258 |     assert result == expected
259 | 
260 | @pytest.mark.integration
261 | @pytest.mark.linux
262 | def test_linux_integration():
263 |     """Integration test for Linux"""
264 |     # This test will only run on Linux in CI
265 |     pass
266 | ```
267 | 
268 | ---
269 | 
270 | ## Code Style
271 | 
272 | ### Python
273 | 
274 | We use **Black** for code formatting and **MyPy** for type checking.
275 | 
276 | ```bash
277 | # Format all Python files
278 | black python/
279 | 
280 | # Check types
281 | mypy python/
282 | 
283 | # Run linter
284 | pylint python/
285 | ```
286 | 
287 | **Python Style Guidelines:**
288 | - Use type hints for all function signatures
289 | - Use pathlib.Path for file paths (not os.path)
290 | - Use descriptive variable names
291 | - Add docstrings to all public functions/classes
292 | - Follow PEP 8
293 | 
294 | **Example:**
295 | ```python
296 | from pathlib import Path
297 | from typing import List, Optional
298 | 
299 | def find_kicad_libraries(search_path: Path) -> List[Path]:
300 |     """
301 |     Find all KiCAD symbol libraries in the given path.
302 | 
303 |     Args:
304 |         search_path: Directory to search for .kicad_sym files
305 | 
306 |     Returns:
307 |         List of paths to found library files
308 | 
309 |     Raises:
310 |         ValueError: If search_path doesn't exist
311 |     """
312 |     if not search_path.exists():
313 |         raise ValueError(f"Search path does not exist: {search_path}")
314 | 
315 |     return list(search_path.glob("**/*.kicad_sym"))
316 | ```
317 | 
318 | ### TypeScript
319 | 
320 | We use **ESLint** and **Prettier** for TypeScript.
321 | 
322 | ```bash
323 | # Format TypeScript files
324 | npx prettier --write "src/**/*.ts"
325 | 
326 | # Run linter
327 | npx eslint src/
328 | ```
329 | 
330 | **TypeScript Style Guidelines:**
331 | - Use interfaces for data structures
332 | - Use async/await for asynchronous code
333 | - Use descriptive variable names
334 | - Add JSDoc comments to exported functions
335 | 
336 | ---
337 | 
338 | ## Pull Request Process
339 | 
340 | 1. **Update Documentation** - If you change functionality, update docs
341 | 2. **Add Tests** - All new features should have tests
342 | 3. **Run CI Locally** - Ensure all tests pass before pushing
343 | 4. **Create PR** - Use a clear, descriptive title
344 | 5. **Request Review** - Tag relevant maintainers
345 | 6. **Address Feedback** - Make requested changes
346 | 7. **Merge** - Maintainer will merge when approved
347 | 
348 | ### PR Checklist
349 | 
350 | - [ ] Code follows style guidelines
351 | - [ ] All tests pass locally
352 | - [ ] New tests added for new features
353 | - [ ] Documentation updated
354 | - [ ] Commit messages follow convention
355 | - [ ] No merge conflicts
356 | - [ ] CI/CD pipeline passes
357 | 
358 | ---
359 | 
360 | ## Roadmap & Planning
361 | 
362 | We track work using GitHub Projects and Issues:
363 | 
364 | - **GitHub Projects** - High-level roadmap and sprints
365 | - **GitHub Issues** - Specific bugs and features
366 | - **GitHub Discussions** - Design discussions and proposals
367 | 
368 | ### Current Priorities (Week 1-4)
369 | 
370 | 1. ✅ Linux compatibility fixes
371 | 2. ✅ Platform-agnostic path handling
372 | 3. ✅ CI/CD pipeline setup
373 | 4. 🔄 Migrate to KiCAD IPC API
374 | 5. ⏳ Add JLCPCB integration
375 | 6. ⏳ Add Digikey integration
376 | 
377 | See [docs/REBUILD_PLAN.md](docs/REBUILD_PLAN.md) for the complete 12-week roadmap.
378 | 
379 | ---
380 | 
381 | ## Getting Help
382 | 
383 | - **GitHub Discussions** - Ask questions, propose ideas
384 | - **GitHub Issues** - Report bugs, request features
385 | - **Discord** - Real-time chat (link TBD)
386 | 
387 | ---
388 | 
389 | ## License
390 | 
391 | By contributing, you agree that your contributions will be licensed under the MIT License.
392 | 
393 | ---
394 | 
395 | ## Thank You! 🎉
396 | 
397 | Your contributions make this project better for everyone. We appreciate your time and effort!
398 | 
```

--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Tests for KiCAD MCP Server"""
2 | 
```

--------------------------------------------------------------------------------
/python/utils/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Utility modules for KiCAD MCP Server"""
2 | 
```

--------------------------------------------------------------------------------
/python/schemas/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | Tool schema definitions for KiCAD MCP Server
3 | """
4 | 
5 | from .tool_schemas import TOOL_SCHEMAS
6 | 
7 | __all__ = ['TOOL_SCHEMAS']
8 | 
```

--------------------------------------------------------------------------------
/python/requirements.txt:
--------------------------------------------------------------------------------

```
 1 | # KiCAD MCP Python Interface Requirements
 2 | 
 3 | # Image processing
 4 | Pillow>=9.0.0
 5 | cairosvg>=2.7.0
 6 | 
 7 | # Type hints
 8 | typing-extensions>=4.0.0
 9 | 
10 | # Logging
11 | colorlog>=6.7.0
12 | 
13 | kicad-skip
14 | 
```

--------------------------------------------------------------------------------
/config/default-config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "kicad-mcp-server",
 3 |   "version": "1.0.0",
 4 |   "description": "MCP server for KiCAD PCB design operations",
 5 |   "pythonPath": "",
 6 |   "kicadPath": "",
 7 |   "logLevel": "info",
 8 |   "logDir": ""
 9 | }
10 | 
```

--------------------------------------------------------------------------------
/python/resources/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | Resource definitions for KiCAD MCP Server
3 | """
4 | 
5 | from .resource_definitions import RESOURCE_DEFINITIONS, handle_resource_read
6 | 
7 | __all__ = ['RESOURCE_DEFINITIONS', 'handle_resource_read']
8 | 
```

--------------------------------------------------------------------------------
/src/prompts/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Prompts index for KiCAD MCP server
 3 |  * 
 4 |  * Exports all prompt registration functions
 5 |  */
 6 | 
 7 | export { registerComponentPrompts } from './component.js';
 8 | export { registerRoutingPrompts } from './routing.js';
 9 | export { registerDesignPrompts } from './design.js';
10 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "strict": true,
 8 |     "outDir": "dist",
 9 |     "declaration": true,
10 |     "sourceMap": true
11 |   },
12 |   "include": ["src/**/*"],
13 |   "exclude": ["node_modules", "dist"]
14 | }
15 | 
```

--------------------------------------------------------------------------------
/config/claude-desktop-config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "kicad_helper": {
 4 |       "command": "node",
 5 |       "args": ["dist/index.js"],
 6 |       "cwd": "c:/repo/KiCAD-MCP",
 7 |       "env": {
 8 |         "NODE_ENV": "production",
 9 |         "PYTHONPATH": "C:/Program Files/KiCad/9.0/lib/python3/dist-packages"
10 |       },
11 |       "description": "KiCAD PCB Design Assistant"
12 |     }
13 |   }
14 | }
15 | 
```

--------------------------------------------------------------------------------
/python/commands/board.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Board-related command implementations for KiCAD interface
 3 | 
 4 | This file is maintained for backward compatibility.
 5 | It imports and re-exports the BoardCommands class from the board package.
 6 | """
 7 | 
 8 | from commands.board import BoardCommands
 9 | 
10 | # Re-export the BoardCommands class for backward compatibility
11 | __all__ = ['BoardCommands']
12 | 
```

--------------------------------------------------------------------------------
/src/resources/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Resources index for KiCAD MCP server
 3 |  * 
 4 |  * Exports all resource registration functions
 5 |  */
 6 | 
 7 | export { registerProjectResources } from './project.js';
 8 | export { registerBoardResources } from './board.js';
 9 | export { registerComponentResources } from './component.js';
10 | export { registerLibraryResources } from './library.js';
11 | 
```

--------------------------------------------------------------------------------
/python/commands/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | KiCAD command implementations package
 3 | """
 4 | 
 5 | from .project import ProjectCommands
 6 | from .board import BoardCommands
 7 | from .component import ComponentCommands
 8 | from .routing import RoutingCommands
 9 | from .design_rules import DesignRuleCommands
10 | from .export import ExportCommands
11 | 
12 | __all__ = [
13 |     'ProjectCommands',
14 |     'BoardCommands',
15 |     'ComponentCommands',
16 |     'RoutingCommands',
17 |     'DesignRuleCommands',
18 |     'ExportCommands'
19 | ]
20 | 
```

--------------------------------------------------------------------------------
/config/windows-config.example.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "kicad": {
 4 |       "command": "node",
 5 |       "args": ["C:\\Users\\YOUR_USERNAME\\MCP\\KiCAD-MCP-Server\\dist\\index.js"],
 6 |       "env": {
 7 |         "NODE_ENV": "production",
 8 |         "PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\bin\\Lib\\site-packages",
 9 |         "LOG_LEVEL": "info",
10 |         "KICAD_AUTO_LAUNCH": "false"
11 |       },
12 |       "description": "KiCAD PCB Design Assistant - Note: PYTHONPATH auto-detected if venv exists"
13 |     }
14 |   }
15 | }
16 | 
```

--------------------------------------------------------------------------------
/config/linux-config.example.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "kicad": {
 4 |       "command": "node",
 5 |       "args": ["/home/YOUR_USERNAME/MCP/KiCAD-MCP-Server/dist/index.js"],
 6 |       "env": {
 7 |         "NODE_ENV": "production",
 8 |         "PYTHONPATH": "/usr/share/kicad/scripting/plugins:/usr/lib/kicad/lib/python3/dist-packages",
 9 |         "LOG_LEVEL": "info",
10 |         "KICAD_AUTO_LAUNCH": "false"
11 |       },
12 |       "description": "KiCAD PCB Design Assistant - Note: PYTHONPATH auto-detected if venv exists"
13 |     }
14 |   }
15 | }
16 | 
```

--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------

```
 1 | # KiCAD MCP Server - Development Dependencies
 2 | # Testing, linting, and development tools
 3 | 
 4 | # Include production dependencies
 5 | -r requirements.txt
 6 | 
 7 | # Testing framework
 8 | pytest>=7.4.0
 9 | pytest-cov>=4.1.0
10 | pytest-asyncio>=0.21.0
11 | pytest-mock>=3.11.0
12 | 
13 | # Code quality
14 | black>=23.7.0
15 | mypy>=1.5.0
16 | pylint>=2.17.0
17 | flake8>=6.1.0
18 | isort>=5.12.0
19 | 
20 | # Type stubs
21 | types-requests>=2.31.0
22 | types-Pillow>=10.0.0
23 | 
24 | # Pre-commit hooks
25 | pre-commit>=3.3.0
26 | 
27 | # Development utilities
28 | ipython>=8.14.0
29 | ipdb>=0.13.13
30 | 
```

--------------------------------------------------------------------------------
/config/macos-config.example.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "kicad": {
 4 |       "command": "node",
 5 |       "args": ["/Users/YOUR_USERNAME/MCP/KiCAD-MCP-Server/dist/index.js"],
 6 |       "env": {
 7 |         "NODE_ENV": "production",
 8 |         "PYTHONPATH": "/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/Current/lib/python3.11/site-packages",
 9 |         "LOG_LEVEL": "info",
10 |         "KICAD_AUTO_LAUNCH": "false"
11 |       },
12 |       "description": "KiCAD PCB Design Assistant - Note: PYTHONPATH auto-detected if venv exists"
13 |     }
14 |   }
15 | }
16 | 
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
 1 | # KiCAD MCP Server - Python Dependencies
 2 | # Production dependencies only
 3 | 
 4 | # KiCAD Python API (IPC - for future migration)
 5 | # kicad-python>=0.5.0  # Uncomment when migrating to IPC API
 6 | 
 7 | # Schematic manipulation
 8 | kicad-skip>=0.1.0
 9 | 
10 | # Image processing for board rendering
11 | Pillow>=9.0.0
12 | 
13 | # SVG rendering
14 | cairosvg>=2.7.0
15 | 
16 | # Colored logging
17 | colorlog>=6.7.0
18 | 
19 | # Data validation (for future features)
20 | pydantic>=2.5.0
21 | 
22 | # HTTP requests (for JLCPCB/Digikey APIs - future)
23 | requests>=2.32.5
24 | 
25 | # Environment variable management
26 | python-dotenv>=1.0.0
27 | 
```

--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Tools index for KiCAD MCP server
 3 |  * 
 4 |  * Exports all tool registration functions
 5 |  */
 6 | 
 7 | export { registerProjectTools } from './project.js';
 8 | export { registerBoardTools } from './board.js';
 9 | export { registerComponentTools } from './component.js';
10 | export { registerRoutingTools } from './routing.js';
11 | export { registerDesignRuleTools } from './design-rules.js';
12 | export { registerExportTools } from './export.js';
13 | export { registerSchematicTools } from './schematic.js';
14 | export { registerLibraryTools } from './library.js';
15 | export { registerUITools } from './ui.js';
16 | 
```

--------------------------------------------------------------------------------
/scripts/auto_refresh_kicad.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | # Auto-refresh KiCAD when .kicad_pcb files change
 3 | # Usage: ./auto_refresh_kicad.sh /path/to/project.kicad_pcb
 4 | 
 5 | if [ -z "$1" ]; then
 6 |     echo "Usage: $0 <path-to-kicad-pcb-file>"
 7 |     exit 1
 8 | fi
 9 | 
10 | PCB_FILE="$1"
11 | 
12 | if [ ! -f "$PCB_FILE" ]; then
13 |     echo "Error: File not found: $PCB_FILE"
14 |     exit 1
15 | fi
16 | 
17 | echo "Monitoring: $PCB_FILE"
18 | echo "When changes are saved, KiCAD will detect them and prompt to reload."
19 | echo "Press Ctrl+C to stop monitoring."
20 | 
21 | # Watch for file changes
22 | inotifywait -m -e modify "$PCB_FILE" |
23 | while read path action file; do
24 |     echo "[$(date '+%H:%M:%S')] File changed - KiCAD should prompt to reload"
25 |     # KiCAD automatically detects file changes in most versions
26 | done
27 | 
```

--------------------------------------------------------------------------------
/package-json.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "kicad-mcp",
 3 |   "version": "1.0.0",
 4 |   "description": "Model Context Protocol server for KiCAD PCB design",
 5 |   "type": "module",
 6 |   "main": "dist/index.js",
 7 |   "scripts": {
 8 |     "build": "tsc",
 9 |     "start": "node dist/index.js",
10 |     "dev": "tsc -w & nodemon dist/index.js",
11 |     "test": "echo \"Error: no test specified\" && exit 1"
12 |   },
13 |   "keywords": [
14 |     "kicad",
15 |     "mcp",
16 |     "model-context-protocol",
17 |     "pcb-design",
18 |     "ai",
19 |     "claude"
20 |   ],
21 |   "author": "",
22 |   "license": "MIT",
23 |   "dependencies": {
24 |     "@modelcontextprotocol/sdk": "^1.10.0",
25 |     "dotenv": "^16.0.3",
26 |     "zod": "^3.22.2"
27 |   },
28 |   "devDependencies": {
29 |     "@types/node": "^20.5.6",
30 |     "nodemon": "^3.0.1",
31 |     "typescript": "^5.2.2"
32 |   }
33 | }
34 | 
```

--------------------------------------------------------------------------------
/python/kicad_api/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | KiCAD API Abstraction Layer
 3 | 
 4 | This module provides a unified interface to KiCAD's Python APIs,
 5 | supporting both the legacy SWIG bindings and the new IPC API.
 6 | 
 7 | Usage:
 8 |     from kicad_api import create_backend
 9 | 
10 |     # Auto-detect best available backend
11 |     backend = create_backend()
12 | 
13 |     # Or specify explicitly
14 |     backend = create_backend('ipc')  # Use IPC API
15 |     backend = create_backend('swig')  # Use legacy SWIG
16 | 
17 |     # Connect and use
18 |     if backend.connect():
19 |         board = backend.get_board()
20 |         board.set_size(100, 80)
21 | """
22 | 
23 | from kicad_api.factory import create_backend
24 | from kicad_api.base import KiCADBackend
25 | 
26 | __all__ = ['create_backend', 'KiCADBackend']
27 | __version__ = '2.0.0-alpha.1'
28 | 
```

--------------------------------------------------------------------------------
/src/tools/component.txt:
--------------------------------------------------------------------------------

```
 1 | /**
 2 |  * Component management tools for KiCAD MCP server
 3 |  */
 4 | 
 5 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
 6 | import { z } from 'zod';
 7 | import { logger } from '../logger.js';
 8 | 
 9 | // Command function type for KiCAD script calls
10 | type CommandFunction = (command: string, params: any) => Promise<any>;
11 | 
12 | /**
13 |  * Register component management tools with the MCP server
14 |  * 
15 |  * @param server MCP server instance
16 |  * @param callKicadScript Function to call KiCAD script commands
17 |  */
18 | export function registerComponentTools(server: McpServer, callKicadScript: CommandFunction): void {
19 |   logger.info('Registering component management tools');
20 |   
21 |   // ------------------------------------------------------
22 |   // Place Component Tool
23 |   // ------------------------------------------------------
24 |   server.registerTool({
25 |     name: "place_component",
26 |     description: "Places a component on the PCB at the specified location",
```

--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------

```
 1 | [pytest]
 2 | # Pytest configuration for KiCAD MCP Server
 3 | 
 4 | # Test discovery patterns
 5 | python_files = test_*.py *_test.py
 6 | python_classes = Test*
 7 | python_functions = test_*
 8 | 
 9 | # Test paths
10 | testpaths = tests python/tests
11 | 
12 | # Minimum Python version
13 | minversion = 6.0
14 | 
15 | # Additional options
16 | addopts =
17 |     -ra
18 |     --strict-markers
19 |     --strict-config
20 |     --showlocals
21 |     --tb=short
22 |     --cov=python
23 |     --cov-report=term-missing
24 |     --cov-report=html
25 |     --cov-report=xml
26 |     --cov-branch
27 | 
28 | # Markers for organizing tests
29 | markers =
30 |     unit: Unit tests (fast, no external dependencies)
31 |     integration: Integration tests (requires KiCAD)
32 |     slow: Slow-running tests
33 |     linux: Linux-specific tests
34 |     windows: Windows-specific tests
35 |     macos: macOS-specific tests
36 | 
37 | # Ignore patterns
38 | norecursedirs = .git .tox dist build *.egg node_modules
39 | 
40 | # Coverage settings
41 | [coverage:run]
42 | source = python
43 | omit =
44 |     */tests/*
45 |     */test_*.py
46 |     */__pycache__/*
47 |     */site-packages/*
48 | 
49 | [coverage:report]
50 | precision = 2
51 | show_missing = True
52 | skip_covered = False
53 | 
```

--------------------------------------------------------------------------------
/src/tools/ui.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * UI/Process management tools for KiCAD MCP server
 3 |  */
 4 | 
 5 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
 6 | import { z } from 'zod';
 7 | import { logger } from '../logger.js';
 8 | 
 9 | export function registerUITools(server: McpServer, callKicadScript: Function) {
10 |   // Check if KiCAD UI is running
11 |   server.tool(
12 |     "check_kicad_ui",
13 |     "Check if KiCAD UI is currently running",
14 |     {},
15 |     async () => {
16 |       logger.info('Checking KiCAD UI status');
17 |       const result = await callKicadScript("check_kicad_ui", {});
18 |       return {
19 |         content: [{
20 |           type: "text",
21 |           text: JSON.stringify(result, null, 2)
22 |         }]
23 |       };
24 |     }
25 |   );
26 | 
27 |   // Launch KiCAD UI
28 |   server.tool(
29 |     "launch_kicad_ui",
30 |     "Launch KiCAD UI, optionally with a project file",
31 |     {
32 |       projectPath: z.string().optional().describe("Optional path to .kicad_pcb file to open"),
33 |       autoLaunch: z.boolean().optional().describe("Whether to launch KiCAD if not running (default: true)")
34 |     },
35 |     async (args: { projectPath?: string; autoLaunch?: boolean }) => {
36 |       logger.info(`Launching KiCAD UI${args.projectPath ? ' with project: ' + args.projectPath : ''}`);
37 |       const result = await callKicadScript("launch_kicad_ui", args);
38 |       return {
39 |         content: [{
40 |           type: "text",
41 |           text: JSON.stringify(result, null, 2)
42 |         }]
43 |       };
44 |     }
45 |   );
46 | 
47 |   logger.info('UI management tools registered');
48 | }
49 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "kicad-mcp",
 3 |   "version": "2.1.0-alpha",
 4 |   "description": "AI-assisted PCB design with KiCAD via Model Context Protocol",
 5 |   "type": "module",
 6 |   "main": "dist/index.js",
 7 |   "scripts": {
 8 |     "build": "tsc",
 9 |     "build:watch": "tsc --watch",
10 |     "start": "node dist/index.js",
11 |     "dev": "npm run build:watch & nodemon dist/index.js",
12 |     "clean": "rm -rf dist",
13 |     "rebuild": "npm run clean && npm run build",
14 |     "test": "npm run test:ts && npm run test:py",
15 |     "test:ts": "echo 'TypeScript tests not yet configured'",
16 |     "test:py": "pytest tests/ -v",
17 |     "test:coverage": "pytest tests/ --cov=python --cov-report=html --cov-report=term",
18 |     "lint": "npm run lint:ts && npm run lint:py",
19 |     "lint:ts": "eslint src/ || echo 'ESLint not configured'",
20 |     "lint:py": "cd python && black . && mypy . && flake8 .",
21 |     "format": "prettier --write 'src/**/*.ts' && black python/",
22 |     "prepare": "npm run build",
23 |     "pretest": "npm run build"
24 |   },
25 |   "keywords": [
26 |     "kicad",
27 |     "mcp",
28 |     "model-context-protocol",
29 |     "pcb-design",
30 |     "ai",
31 |     "claude"
32 |   ],
33 |   "author": "",
34 |   "license": "MIT",
35 |   "dependencies": {
36 |     "@modelcontextprotocol/sdk": "^1.21.0",
37 |     "dotenv": "^17.0.0",
38 |     "express": "^5.1.0",
39 |     "zod": "^3.25.0"
40 |   },
41 |   "devDependencies": {
42 |     "@cfworker/json-schema": "^4.1.1",
43 |     "@types/express": "^5.0.5",
44 |     "@types/glob": "^8.1.0",
45 |     "@types/node": "^20.19.0",
46 |     "nodemon": "^3.0.1",
47 |     "typescript": "^5.9.3"
48 |   }
49 | }
50 | 
```

--------------------------------------------------------------------------------
/src/utils/resource-helpers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Resource helper utilities for MCP resources
 3 |  */
 4 | 
 5 | /**
 6 |  * Create a JSON response for MCP resources
 7 |  *
 8 |  * @param data Data to serialize as JSON
 9 |  * @param uri Optional URI for the resource
10 |  * @returns MCP resource response object
11 |  */
12 | export function createJsonResponse(data: any, uri?: string) {
13 |   return {
14 |     contents: [{
15 |       uri: uri || "data:application/json",
16 |       mimeType: "application/json",
17 |       text: JSON.stringify(data, null, 2)
18 |     }]
19 |   };
20 | }
21 | 
22 | /**
23 |  * Create a binary response for MCP resources
24 |  *
25 |  * @param data Binary data (Buffer or base64 string)
26 |  * @param mimeType MIME type of the binary data
27 |  * @param uri Optional URI for the resource
28 |  * @returns MCP resource response object
29 |  */
30 | export function createBinaryResponse(data: Buffer | string, mimeType: string, uri?: string) {
31 |   const blob = typeof data === 'string' ? data : data.toString('base64');
32 | 
33 |   return {
34 |     contents: [{
35 |       uri: uri || `data:${mimeType}`,
36 |       mimeType: mimeType,
37 |       blob: blob
38 |     }]
39 |   };
40 | }
41 | 
42 | /**
43 |  * Create an error response for MCP resources
44 |  *
45 |  * @param error Error message
46 |  * @param details Optional error details
47 |  * @param uri Optional URI for the resource
48 |  * @returns MCP resource error response
49 |  */
50 | export function createErrorResponse(error: string, details?: string, uri?: string) {
51 |   return {
52 |     contents: [{
53 |       uri: uri || "data:application/json",
54 |       mimeType: "application/json",
55 |       text: JSON.stringify({
56 |         error,
57 |         details
58 |       }, null, 2)
59 |     }]
60 |   };
61 | }
62 | 
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Configuration handling for KiCAD MCP server
 3 |  */
 4 | 
 5 | import { readFile } from 'fs/promises';
 6 | import { existsSync } from 'fs';
 7 | import { join, dirname } from 'path';
 8 | import { fileURLToPath } from 'url';
 9 | import { z } from 'zod';
10 | import { logger } from './logger.js';
11 | 
12 | // Get the current directory
13 | const __filename = fileURLToPath(import.meta.url);
14 | const __dirname = dirname(__filename);
15 | 
16 | // Default config location
17 | const DEFAULT_CONFIG_PATH = join(dirname(__dirname), 'config', 'default-config.json');
18 | 
19 | /**
20 |  * Server configuration schema
21 |  */
22 | const ConfigSchema = z.object({
23 |   name: z.string().default('kicad-mcp-server'),
24 |   version: z.string().default('1.0.0'),
25 |   description: z.string().default('MCP server for KiCAD PCB design operations'),
26 |   pythonPath: z.string().optional(),
27 |   kicadPath: z.string().optional(),
28 |   logLevel: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
29 |   logDir: z.string().optional()
30 | });
31 | 
32 | /**
33 |  * Server configuration type
34 |  */
35 | export type Config = z.infer<typeof ConfigSchema>;
36 | 
37 | /**
38 |  * Load configuration from file
39 |  * 
40 |  * @param configPath Path to the configuration file (optional)
41 |  * @returns Loaded and validated configuration
42 |  */
43 | export async function loadConfig(configPath?: string): Promise<Config> {
44 |   try {
45 |     // Determine which config file to load
46 |     const filePath = configPath || DEFAULT_CONFIG_PATH;
47 |     
48 |     // Check if file exists
49 |     if (!existsSync(filePath)) {
50 |       logger.warn(`Configuration file not found: ${filePath}, using defaults`);
51 |       return ConfigSchema.parse({});
52 |     }
53 |     
54 |     // Read and parse configuration
55 |     const configData = await readFile(filePath, 'utf-8');
56 |     const config = JSON.parse(configData);
57 |     
58 |     // Validate configuration
59 |     return ConfigSchema.parse(config);
60 |   } catch (error) {
61 |     logger.error(`Error loading configuration: ${error}`);
62 |     
63 |     // Return default configuration
64 |     return ConfigSchema.parse({});
65 |   }
66 | }
67 | 
```

--------------------------------------------------------------------------------
/src/tools/project.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Project management tools for KiCAD MCP server
 3 |  */
 4 | 
 5 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
 6 | import { z } from 'zod';
 7 | 
 8 | export function registerProjectTools(server: McpServer, callKicadScript: Function) {
 9 |   // Create project tool
10 |   server.tool(
11 |     "create_project",
12 |     "Create a new KiCAD project",
13 |     {
14 |       path: z.string().describe("Project directory path"),
15 |       name: z.string().describe("Project name"),
16 |     },
17 |     async (args: { path: string; name: string }) => {
18 |       const result = await callKicadScript("create_project", args);
19 |       return {
20 |         content: [{
21 |           type: "text",
22 |           text: JSON.stringify(result, null, 2)
23 |         }]
24 |       };
25 |     }
26 |   );
27 | 
28 |   // Open project tool
29 |   server.tool(
30 |     "open_project",
31 |     "Open an existing KiCAD project",
32 |     {
33 |       filename: z.string().describe("Path to .kicad_pro or .kicad_pcb file"),
34 |     },
35 |     async (args: { filename: string }) => {
36 |       const result = await callKicadScript("open_project", args);
37 |       return {
38 |         content: [{
39 |           type: "text",
40 |           text: JSON.stringify(result, null, 2)
41 |         }]
42 |       };
43 |     }
44 |   );
45 | 
46 |   // Save project tool
47 |   server.tool(
48 |     "save_project",
49 |     "Save the current KiCAD project",
50 |     {
51 |       path: z.string().optional().describe("Optional new path to save to"),
52 |     },
53 |     async (args: { path?: string }) => {
54 |       const result = await callKicadScript("save_project", args);
55 |       return {
56 |         content: [{
57 |           type: "text",
58 |           text: JSON.stringify(result, null, 2)
59 |         }]
60 |       };
61 |     }
62 |   );
63 | 
64 |   // Get project info tool
65 |   server.tool(
66 |     "get_project_info",
67 |     "Get information about the current KiCAD project",
68 |     {},
69 |     async () => {
70 |       const result = await callKicadScript("get_project_info", {});
71 |       return {
72 |         content: [{
73 |           type: "text",
74 |           text: JSON.stringify(result, null, 2)
75 |         }]
76 |       };
77 |     }
78 |   );
79 | }
80 | 
```

--------------------------------------------------------------------------------
/python/commands/board/size.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Board size command implementations for KiCAD interface
 3 | """
 4 | 
 5 | import pcbnew
 6 | import logging
 7 | from typing import Dict, Any, Optional
 8 | 
 9 | logger = logging.getLogger('kicad_interface')
10 | 
11 | class BoardSizeCommands:
12 |     """Handles board size operations"""
13 | 
14 |     def __init__(self, board: Optional[pcbnew.BOARD] = None):
15 |         """Initialize with optional board instance"""
16 |         self.board = board
17 | 
18 |     def set_board_size(self, params: Dict[str, Any]) -> Dict[str, Any]:
19 |         """Set the size of the PCB board by creating edge cuts outline"""
20 |         try:
21 |             if not self.board:
22 |                 return {
23 |                     "success": False,
24 |                     "message": "No board is loaded",
25 |                     "errorDetails": "Load or create a board first"
26 |                 }
27 | 
28 |             width = params.get("width")
29 |             height = params.get("height")
30 |             unit = params.get("unit", "mm")
31 | 
32 |             if width is None or height is None:
33 |                 return {
34 |                     "success": False,
35 |                     "message": "Missing dimensions",
36 |                     "errorDetails": "Both width and height are required"
37 |                 }
38 | 
39 |             # Create board outline using BoardOutlineCommands
40 |             # This properly creates edge cuts on Edge.Cuts layer
41 |             from commands.board.outline import BoardOutlineCommands
42 |             outline_commands = BoardOutlineCommands(self.board)
43 | 
44 |             # Create rectangular outline centered at origin
45 |             result = outline_commands.add_board_outline({
46 |                 "shape": "rectangle",
47 |                 "centerX": width / 2,      # Center X
48 |                 "centerY": height / 2,     # Center Y
49 |                 "width": width,
50 |                 "height": height,
51 |                 "unit": unit
52 |             })
53 | 
54 |             if result.get("success"):
55 |                 return {
56 |                     "success": True,
57 |                     "message": f"Created board outline: {width}x{height} {unit}",
58 |                     "size": {
59 |                         "width": width,
60 |                         "height": height,
61 |                         "unit": unit
62 |                     }
63 |                 }
64 |             else:
65 |                 return result
66 | 
67 |         except Exception as e:
68 |             logger.error(f"Error setting board size: {str(e)}")
69 |             return {
70 |                 "success": False,
71 |                 "message": "Failed to set board size",
72 |                 "errorDetails": str(e)
73 |             }
74 | 
```

--------------------------------------------------------------------------------
/src/logger.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Logger for KiCAD MCP server
  3 |  */
  4 | 
  5 | import { existsSync, mkdirSync, appendFileSync } from 'fs';
  6 | import { join } from 'path';
  7 | import * as os from 'os';
  8 | 
  9 | // Log levels
 10 | type LogLevel = 'error' | 'warn' | 'info' | 'debug';
 11 | 
 12 | // Default log directory
 13 | const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
 14 | 
 15 | /**
 16 |  * Logger class for KiCAD MCP server
 17 |  */
 18 | class Logger {
 19 |   private logLevel: LogLevel = 'info';
 20 |   private logDir: string = DEFAULT_LOG_DIR;
 21 |   
 22 |   /**
 23 |    * Set the log level
 24 |    * @param level Log level to set
 25 |    */
 26 |   setLogLevel(level: LogLevel): void {
 27 |     this.logLevel = level;
 28 |   }
 29 |   
 30 |   /**
 31 |    * Set the log directory
 32 |    * @param dir Directory to store log files
 33 |    */
 34 |   setLogDir(dir: string): void {
 35 |     this.logDir = dir;
 36 |     
 37 |     // Ensure log directory exists
 38 |     if (!existsSync(this.logDir)) {
 39 |       mkdirSync(this.logDir, { recursive: true });
 40 |     }
 41 |   }
 42 |   
 43 |   /**
 44 |    * Log an error message
 45 |    * @param message Message to log
 46 |    */
 47 |   error(message: string): void {
 48 |     this.log('error', message);
 49 |   }
 50 |   
 51 |   /**
 52 |    * Log a warning message
 53 |    * @param message Message to log
 54 |    */
 55 |   warn(message: string): void {
 56 |     if (['error', 'warn', 'info', 'debug'].includes(this.logLevel)) {
 57 |       this.log('warn', message);
 58 |     }
 59 |   }
 60 |   
 61 |   /**
 62 |    * Log an info message
 63 |    * @param message Message to log
 64 |    */
 65 |   info(message: string): void {
 66 |     if (['info', 'debug'].includes(this.logLevel)) {
 67 |       this.log('info', message);
 68 |     }
 69 |   }
 70 |   
 71 |   /**
 72 |    * Log a debug message
 73 |    * @param message Message to log
 74 |    */
 75 |   debug(message: string): void {
 76 |     if (this.logLevel === 'debug') {
 77 |       this.log('debug', message);
 78 |     }
 79 |   }
 80 |   
 81 |   /**
 82 |    * Log a message with the specified level
 83 |    * @param level Log level
 84 |    * @param message Message to log
 85 |    */
 86 |   private log(level: LogLevel, message: string): void {
 87 |     const timestamp = new Date().toISOString();
 88 |     const formattedMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
 89 | 
 90 |     // Log to console.error (stderr) only - stdout is reserved for MCP protocol
 91 |     // All log levels go to stderr to avoid corrupting STDIO MCP transport
 92 |     console.error(formattedMessage);
 93 |     
 94 |     // Log to file
 95 |     try {
 96 |       // Ensure log directory exists
 97 |       if (!existsSync(this.logDir)) {
 98 |         mkdirSync(this.logDir, { recursive: true });
 99 |       }
100 |       
101 |       const logFile = join(this.logDir, `kicad-mcp-${new Date().toISOString().split('T')[0]}.log`);
102 |       appendFileSync(logFile, formattedMessage + '\n');
103 |     } catch (error) {
104 |       console.error(`Failed to write to log file: ${error}`);
105 |     }
106 |   }
107 | }
108 | 
109 | // Create and export logger instance
110 | export const logger = new Logger();
111 | 
```

--------------------------------------------------------------------------------
/src/tools/routing.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Routing tools for KiCAD MCP server
  3 |  */
  4 | 
  5 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  6 | import { z } from 'zod';
  7 | 
  8 | export function registerRoutingTools(server: McpServer, callKicadScript: Function) {
  9 |   // Add net tool
 10 |   server.tool(
 11 |     "add_net",
 12 |     "Create a new net on the PCB",
 13 |     {
 14 |       name: z.string().describe("Net name"),
 15 |       netClass: z.string().optional().describe("Net class name"),
 16 |     },
 17 |     async (args: { name: string; netClass?: string }) => {
 18 |       const result = await callKicadScript("add_net", args);
 19 |       return {
 20 |         content: [{
 21 |           type: "text",
 22 |           text: JSON.stringify(result, null, 2)
 23 |         }]
 24 |       };
 25 |     }
 26 |   );
 27 | 
 28 |   // Route trace tool
 29 |   server.tool(
 30 |     "route_trace",
 31 |     "Route a trace between two points",
 32 |     {
 33 |       start: z.object({
 34 |         x: z.number(),
 35 |         y: z.number(),
 36 |         unit: z.string().optional()
 37 |       }).describe("Start position"),
 38 |       end: z.object({
 39 |         x: z.number(),
 40 |         y: z.number(),
 41 |         unit: z.string().optional()
 42 |       }).describe("End position"),
 43 |       layer: z.string().describe("PCB layer"),
 44 |       width: z.number().describe("Trace width in mm"),
 45 |       net: z.string().describe("Net name"),
 46 |     },
 47 |     async (args: any) => {
 48 |       const result = await callKicadScript("route_trace", args);
 49 |       return {
 50 |         content: [{
 51 |           type: "text",
 52 |           text: JSON.stringify(result, null, 2)
 53 |         }]
 54 |       };
 55 |     }
 56 |   );
 57 | 
 58 |   // Add via tool
 59 |   server.tool(
 60 |     "add_via",
 61 |     "Add a via to the PCB",
 62 |     {
 63 |       position: z.object({
 64 |         x: z.number(),
 65 |         y: z.number(),
 66 |         unit: z.string().optional()
 67 |       }).describe("Via position"),
 68 |       net: z.string().describe("Net name"),
 69 |       viaType: z.string().optional().describe("Via type (through, blind, buried)"),
 70 |     },
 71 |     async (args: any) => {
 72 |       const result = await callKicadScript("add_via", args);
 73 |       return {
 74 |         content: [{
 75 |           type: "text",
 76 |           text: JSON.stringify(result, null, 2)
 77 |         }]
 78 |       };
 79 |     }
 80 |   );
 81 | 
 82 |   // Add copper pour tool
 83 |   server.tool(
 84 |     "add_copper_pour",
 85 |     "Add a copper pour (ground/power plane) to the PCB",
 86 |     {
 87 |       layer: z.string().describe("PCB layer"),
 88 |       net: z.string().describe("Net name"),
 89 |       clearance: z.number().optional().describe("Clearance in mm"),
 90 |     },
 91 |     async (args: any) => {
 92 |       const result = await callKicadScript("add_copper_pour", args);
 93 |       return {
 94 |         content: [{
 95 |           type: "text",
 96 |           text: JSON.stringify(result, null, 2)
 97 |         }]
 98 |       };
 99 |     }
100 |   );
101 | }
102 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * KiCAD Model Context Protocol Server
  3 |  * Main entry point
  4 |  */
  5 | 
  6 | import { join, dirname } from 'path';
  7 | import { fileURLToPath } from 'url';
  8 | import { KiCADMcpServer } from './server.js';
  9 | import { loadConfig } from './config.js';
 10 | import { logger } from './logger.js';
 11 | 
 12 | // Get the current directory
 13 | const __filename = fileURLToPath(import.meta.url);
 14 | const __dirname = dirname(__filename);
 15 | 
 16 | /**
 17 |  * Main function to start the KiCAD MCP server
 18 |  */
 19 | async function main() {
 20 |   try {
 21 |     // Parse command line arguments
 22 |     const args = process.argv.slice(2);
 23 |     const options = parseCommandLineArgs(args);
 24 |     
 25 |     // Load configuration
 26 |     const config = await loadConfig(options.configPath);
 27 |     
 28 |     // Path to the Python script that interfaces with KiCAD
 29 |     const kicadScriptPath = join(dirname(__dirname), 'python', 'kicad_interface.py');
 30 |     
 31 |     // Create the server
 32 |     const server = new KiCADMcpServer(
 33 |       kicadScriptPath,
 34 |       config.logLevel
 35 |     );
 36 |     
 37 |     // Start the server
 38 |     await server.start();
 39 |     
 40 |     // Setup graceful shutdown
 41 |     setupGracefulShutdown(server);
 42 |     
 43 |     logger.info('KiCAD MCP server started with STDIO transport');
 44 |     
 45 |   } catch (error) {
 46 |     logger.error(`Failed to start KiCAD MCP server: ${error}`);
 47 |     process.exit(1);
 48 |   }
 49 | }
 50 | 
 51 | /**
 52 |  * Parse command line arguments
 53 |  */
 54 | function parseCommandLineArgs(args: string[]) {
 55 |   let configPath = undefined;
 56 |   
 57 |   for (let i = 0; i < args.length; i++) {
 58 |     if (args[i] === '--config' && i + 1 < args.length) {
 59 |       configPath = args[i + 1];
 60 |       i++;
 61 |     }
 62 |   }
 63 |   
 64 |   return { configPath };
 65 | }
 66 | 
 67 | /**
 68 |  * Setup graceful shutdown handlers
 69 |  */
 70 | function setupGracefulShutdown(server: KiCADMcpServer) {
 71 |   // Handle termination signals
 72 |   process.on('SIGINT', async () => {
 73 |     logger.info('Received SIGINT signal. Shutting down...');
 74 |     await shutdownServer(server);
 75 |   });
 76 |   
 77 |   process.on('SIGTERM', async () => {
 78 |     logger.info('Received SIGTERM signal. Shutting down...');
 79 |     await shutdownServer(server);
 80 |   });
 81 |   
 82 |   // Handle uncaught exceptions
 83 |   process.on('uncaughtException', async (error) => {
 84 |     logger.error(`Uncaught exception: ${error}`);
 85 |     await shutdownServer(server);
 86 |   });
 87 |   
 88 |   // Handle unhandled promise rejections
 89 |   process.on('unhandledRejection', async (reason) => {
 90 |     logger.error(`Unhandled promise rejection: ${reason}`);
 91 |     await shutdownServer(server);
 92 |   });
 93 | }
 94 | 
 95 | /**
 96 |  * Shut down the server and exit
 97 |  */
 98 | async function shutdownServer(server: KiCADMcpServer) {
 99 |   try {
100 |     logger.info('Shutting down KiCAD MCP server...');
101 |     await server.stop();
102 |     logger.info('Server shutdown complete. Exiting...');
103 |     process.exit(0);
104 |   } catch (error) {
105 |     logger.error(`Error during shutdown: ${error}`);
106 |     process.exit(1);
107 |   }
108 | }
109 | 
110 | // Run the main function - always run when imported as module entry point
111 | // The import.meta.url check was failing on Windows due to path separators
112 | main().catch((error) => {
113 |   console.error(`Unhandled error in main: ${error}`);
114 |   process.exit(1);
115 | });
116 | 
117 | // For testing and programmatic usage
118 | export { KiCADMcpServer };
119 | 
```

--------------------------------------------------------------------------------
/CHANGELOG_2025-11-30.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Changelog - 2025-11-30
 2 | 
 3 | ## IPC Backend Implementation - Real-time UI Synchronization
 4 | 
 5 | This release implements the **KiCAD IPC API backend**, enabling real-time UI synchronization between the MCP server and KiCAD. Changes made through MCP tools now appear **instantly** in the KiCAD UI without requiring manual reload.
 6 | 
 7 | ### Major Features
 8 | 
 9 | #### Real-time UI Sync via IPC API
10 | - **Instant updates**: Tracks, vias, components, and text appear immediately in KiCAD
11 | - **No reload required**: Eliminates the manual File > Reload workflow
12 | - **Transaction support**: Operations can be grouped for single undo/redo steps
13 | - **Auto-detection**: Server automatically uses IPC when KiCAD is running with IPC enabled
14 | 
15 | #### Automatic Backend Selection
16 | - IPC backend is now the **default** when available
17 | - Transparent fallback to SWIG when IPC unavailable
18 | - Environment variable `KICAD_BACKEND` for explicit control:
19 |   - `auto` (default): Try IPC first, fall back to SWIG
20 |   - `ipc`: Force IPC only
21 |   - `swig`: Force SWIG only (deprecated)
22 | 
23 | #### Commands with IPC Support
24 | The following commands now automatically use IPC for real-time updates:
25 | 
26 | | Command | Description |
27 | |---------|-------------|
28 | | `route_trace` | Add traces with instant UI update |
29 | | `add_via` | Add vias with instant UI update |
30 | | `add_text` / `add_board_text` | Add text with instant UI update |
31 | | `set_board_size` | Set board size with instant outline update |
32 | | `get_board_info` | Read live board data |
33 | | `place_component` | Place components with instant UI update |
34 | | `move_component` | Move components with instant UI update |
35 | | `delete_component` | Delete components with instant UI update |
36 | | `get_component_list` | Read live component list |
37 | | `save_project` | Save via IPC |
38 | 
39 | ### New Files
40 | 
41 | - `python/kicad_api/ipc_backend.py` - Complete IPC backend implementation (~870 lines)
42 | - `python/test_ipc_backend.py` - Test script for IPC functionality
43 | - `docs/IPC_BACKEND_STATUS.md` - Implementation status documentation
44 | 
45 | ### Modified Files
46 | 
47 | - `python/kicad_interface.py` - Added IPC integration and automatic command routing
48 | - `python/kicad_api/base.py` - Added routing and transaction methods to base class
49 | - `python/kicad_api/factory.py` - Fixed kipy module detection
50 | - `docs/ROADMAP.md` - Updated Week 3 status to complete
51 | 
52 | ### Dependencies
53 | 
54 | - Added `kicad-python>=0.5.0` - Official KiCAD IPC API Python library
55 | 
56 | ### Requirements
57 | 
58 | To use real-time mode:
59 | 1. KiCAD 9.0+ must be running
60 | 2. Enable IPC API: `Preferences > Plugins > Enable IPC API Server`
61 | 3. Have a board open in PCB editor
62 | 
63 | ### Deprecation Notice
64 | 
65 | The **SWIG backend is now deprecated**:
66 | - Will continue to work as fallback
67 | - No new features will be added to SWIG path
68 | - Will be removed when KiCAD 10.0 drops SWIG support
69 | 
70 | ### Testing
71 | 
72 | Run the IPC test script:
73 | ```bash
74 | ./venv/bin/python python/test_ipc_backend.py
75 | ```
76 | 
77 | Or test individual commands:
78 | ```bash
79 | echo '{"command": "get_backend_info", "params": {}}' | \
80 |   PYTHONPATH=python ./venv/bin/python python/kicad_interface.py
81 | ```
82 | 
83 | ### Breaking Changes
84 | 
85 | None. All existing commands continue to work. IPC is used transparently when available.
86 | 
87 | ---
88 | 
89 | **Version:** 2.1.0-alpha
90 | **Date:** 2025-11-30
91 | 
```

--------------------------------------------------------------------------------
/python/commands/board/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Board-related command implementations for KiCAD interface
 3 | """
 4 | 
 5 | import pcbnew
 6 | import logging
 7 | from typing import Dict, Any, Optional
 8 | 
 9 | # Import specialized modules
10 | from .size import BoardSizeCommands
11 | from .layers import BoardLayerCommands
12 | from .outline import BoardOutlineCommands
13 | from .view import BoardViewCommands
14 | 
15 | logger = logging.getLogger('kicad_interface')
16 | 
17 | class BoardCommands:
18 |     """Handles board-related KiCAD operations"""
19 | 
20 |     def __init__(self, board: Optional[pcbnew.BOARD] = None):
21 |         """Initialize with optional board instance"""
22 |         self.board = board
23 |         
24 |         # Initialize specialized command classes
25 |         self.size_commands = BoardSizeCommands(board)
26 |         self.layer_commands = BoardLayerCommands(board)
27 |         self.outline_commands = BoardOutlineCommands(board)
28 |         self.view_commands = BoardViewCommands(board)
29 |     
30 |     # Delegate board size commands
31 |     def set_board_size(self, params: Dict[str, Any]) -> Dict[str, Any]:
32 |         """Set the size of the PCB board"""
33 |         self.size_commands.board = self.board
34 |         return self.size_commands.set_board_size(params)
35 |     
36 |     # Delegate layer commands
37 |     def add_layer(self, params: Dict[str, Any]) -> Dict[str, Any]:
38 |         """Add a new layer to the PCB"""
39 |         self.layer_commands.board = self.board
40 |         return self.layer_commands.add_layer(params)
41 |     
42 |     def set_active_layer(self, params: Dict[str, Any]) -> Dict[str, Any]:
43 |         """Set the active layer for PCB operations"""
44 |         self.layer_commands.board = self.board
45 |         return self.layer_commands.set_active_layer(params)
46 |     
47 |     def get_layer_list(self, params: Dict[str, Any]) -> Dict[str, Any]:
48 |         """Get a list of all layers in the PCB"""
49 |         self.layer_commands.board = self.board
50 |         return self.layer_commands.get_layer_list(params)
51 |     
52 |     # Delegate board outline commands
53 |     def add_board_outline(self, params: Dict[str, Any]) -> Dict[str, Any]:
54 |         """Add a board outline to the PCB"""
55 |         self.outline_commands.board = self.board
56 |         return self.outline_commands.add_board_outline(params)
57 |     
58 |     def add_mounting_hole(self, params: Dict[str, Any]) -> Dict[str, Any]:
59 |         """Add a mounting hole to the PCB"""
60 |         self.outline_commands.board = self.board
61 |         return self.outline_commands.add_mounting_hole(params)
62 |     
63 |     def add_text(self, params: Dict[str, Any]) -> Dict[str, Any]:
64 |         """Add text annotation to the PCB"""
65 |         self.outline_commands.board = self.board
66 |         return self.outline_commands.add_text(params)
67 |     
68 |     # Delegate view commands
69 |     def get_board_info(self, params: Dict[str, Any]) -> Dict[str, Any]:
70 |         """Get information about the current board"""
71 |         self.view_commands.board = self.board
72 |         return self.view_commands.get_board_info(params)
73 |     
74 |     def get_board_2d_view(self, params: Dict[str, Any]) -> Dict[str, Any]:
75 |         """Get a 2D image of the PCB"""
76 |         self.view_commands.board = self.board
77 |         return self.view_commands.get_board_2d_view(params)
78 | 
79 |     def get_board_extents(self, params: Dict[str, Any]) -> Dict[str, Any]:
80 |         """Get the bounding box extents of the board"""
81 |         self.view_commands.board = self.board
82 |         return self.view_commands.get_board_extents(params)
83 | 
```

--------------------------------------------------------------------------------
/python/commands/schematic.py:
--------------------------------------------------------------------------------

```python
  1 | from skip import Schematic
  2 | import os
  3 | import logging
  4 | 
  5 | logger = logging.getLogger('kicad_interface')
  6 | 
  7 | class SchematicManager:
  8 |     """Core schematic operations using kicad-skip"""
  9 | 
 10 |     @staticmethod
 11 |     def create_schematic(name, metadata=None):
 12 |         """Create a new empty schematic"""
 13 |         # kicad-skip requires a filepath to create a schematic
 14 |         # We'll create a blank schematic file by loading an existing file
 15 |         # or we can create a template file first.
 16 |         
 17 |         # Create an empty template file first
 18 |         temp_path = f"{name}_template.kicad_sch"
 19 |         with open(temp_path, 'w') as f:
 20 |             # Write minimal schematic file content
 21 |             f.write("(kicad_sch (version 20230121) (generator \"KiCAD-MCP-Server\"))\n")
 22 |         
 23 |         # Now load it
 24 |         sch = Schematic(temp_path)
 25 |         sch.version = "20230121"  # Set appropriate version
 26 |         sch.generator = "KiCAD-MCP-Server"
 27 |         
 28 |         # Clean up the template
 29 |         os.remove(temp_path)
 30 |         # Add metadata if provided
 31 |         if metadata:
 32 |             for key, value in metadata.items():
 33 |                  # kicad-skip doesn't have a direct metadata property on Schematic,
 34 |                  # but we can add properties to the root sheet if needed, or
 35 |                  # include it in the file path/name convention.
 36 |                  # For now, we'll just create the schematic.
 37 |                  pass # Placeholder for potential metadata handling
 38 | 
 39 |         logger.info(f"Created new schematic: {name}")
 40 |         return sch
 41 | 
 42 |     @staticmethod
 43 |     def load_schematic(file_path):
 44 |         """Load an existing schematic"""
 45 |         if not os.path.exists(file_path):
 46 |             logger.error(f"Schematic file not found at {file_path}")
 47 |             return None
 48 |         try:
 49 |             sch = Schematic(file_path)
 50 |             logger.info(f"Loaded schematic from: {file_path}")
 51 |             return sch
 52 |         except Exception as e:
 53 |             logger.error(f"Error loading schematic from {file_path}: {e}")
 54 |             return None
 55 | 
 56 |     @staticmethod
 57 |     def save_schematic(schematic, file_path):
 58 |         """Save a schematic to file"""
 59 |         try:
 60 |             # kicad-skip uses write method, not save
 61 |             schematic.write(file_path)
 62 |             logger.info(f"Saved schematic to: {file_path}")
 63 |             return True
 64 |         except Exception as e:
 65 |             logger.error(f"Error saving schematic to {file_path}: {e}")
 66 |             return False
 67 | 
 68 |     @staticmethod
 69 |     def get_schematic_metadata(schematic):
 70 |         """Extract metadata from schematic"""
 71 |         # kicad-skip doesn't expose a direct metadata object on Schematic.
 72 |         # We can return basic info like version and generator.
 73 |         metadata = {
 74 |             "version": schematic.version,
 75 |             "generator": schematic.generator,
 76 |             # Add other relevant properties if needed
 77 |         }
 78 |         logger.debug("Extracted schematic metadata")
 79 |         return metadata
 80 | 
 81 | if __name__ == '__main__':
 82 |     # Example Usage (for testing)
 83 |     # Create a new schematic
 84 |     new_sch = SchematicManager.create_schematic("MyTestSchematic")
 85 | 
 86 |     # Save the schematic
 87 |     test_file = "test_schematic.kicad_sch"
 88 |     SchematicManager.save_schematic(new_sch, test_file)
 89 | 
 90 |     # Load the schematic
 91 |     loaded_sch = SchematicManager.load_schematic(test_file)
 92 |     if loaded_sch:
 93 |         metadata = SchematicManager.get_schematic_metadata(loaded_sch)
 94 |         print(f"Loaded schematic metadata: {metadata}")
 95 | 
 96 |     # Clean up test file
 97 |     if os.path.exists(test_file):
 98 |         os.remove(test_file)
 99 |         print(f"Cleaned up {test_file}")
100 | 
```

--------------------------------------------------------------------------------
/src/tools/library.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Library tools for KiCAD MCP server
  3 |  * Provides access to KiCAD footprint libraries and symbols
  4 |  */
  5 | 
  6 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  7 | import { z } from 'zod';
  8 | 
  9 | export function registerLibraryTools(server: McpServer, callKicadScript: Function) {
 10 |   // List available footprint libraries
 11 |   server.tool(
 12 |     "list_libraries",
 13 |     "List all available KiCAD footprint libraries",
 14 |     {
 15 |       search_paths: z.array(z.string()).optional()
 16 |         .describe("Optional additional search paths for libraries")
 17 |     },
 18 |     async (args: { search_paths?: string[] }) => {
 19 |       const result = await callKicadScript("list_libraries", args);
 20 |       if (result.success && result.libraries) {
 21 |         return {
 22 |           content: [
 23 |             {
 24 |               type: "text",
 25 |               text: `Found ${result.libraries.length} footprint libraries:\n${result.libraries.join('\n')}`
 26 |             }
 27 |           ]
 28 |         };
 29 |       }
 30 |       return {
 31 |         content: [
 32 |           {
 33 |             type: "text",
 34 |             text: `Failed to list libraries: ${result.message || 'Unknown error'}`
 35 |           }
 36 |         ]
 37 |       };
 38 |     }
 39 |   );
 40 | 
 41 |   // Search for footprints across all libraries
 42 |   server.tool(
 43 |     "search_footprints",
 44 |     "Search for footprints matching a pattern across all libraries",
 45 |     {
 46 |       search_term: z.string()
 47 |         .describe("Search term or pattern to match footprint names"),
 48 |       library: z.string().optional()
 49 |         .describe("Optional specific library to search in"),
 50 |       limit: z.number().optional().default(50)
 51 |         .describe("Maximum number of results to return")
 52 |     },
 53 |     async (args: { search_term: string; library?: string; limit?: number }) => {
 54 |       const result = await callKicadScript("search_footprints", args);
 55 |       if (result.success && result.footprints) {
 56 |         const footprintList = result.footprints.map((fp: any) =>
 57 |           `${fp.library}:${fp.name}${fp.description ? ' - ' + fp.description : ''}`
 58 |         ).join('\n');
 59 |         return {
 60 |           content: [
 61 |             {
 62 |               type: "text",
 63 |               text: `Found ${result.footprints.length} matching footprints:\n${footprintList}`
 64 |             }
 65 |           ]
 66 |         };
 67 |       }
 68 |       return {
 69 |         content: [
 70 |           {
 71 |             type: "text",
 72 |             text: `Failed to search footprints: ${result.message || 'Unknown error'}`
 73 |           }
 74 |         ]
 75 |       };
 76 |     }
 77 |   );
 78 | 
 79 |   // List footprints in a specific library
 80 |   server.tool(
 81 |     "list_library_footprints",
 82 |     "List all footprints in a specific KiCAD library",
 83 |     {
 84 |       library_name: z.string()
 85 |         .describe("Name of the library to list footprints from"),
 86 |       filter: z.string().optional()
 87 |         .describe("Optional filter pattern for footprint names"),
 88 |       limit: z.number().optional().default(100)
 89 |         .describe("Maximum number of footprints to list")
 90 |     },
 91 |     async (args: { library_name: string; filter?: string; limit?: number }) => {
 92 |       const result = await callKicadScript("list_library_footprints", args);
 93 |       if (result.success && result.footprints) {
 94 |         const footprintList = result.footprints.map((fp: string) => `  - ${fp}`).join('\n');
 95 |         return {
 96 |           content: [
 97 |             {
 98 |               type: "text",
 99 |               text: `Library ${args.library_name} contains ${result.footprints.length} footprints:\n${footprintList}`
100 |             }
101 |           ]
102 |         };
103 |       }
104 |       return {
105 |         content: [
106 |           {
107 |             type: "text",
108 |             text: `Failed to list footprints in library ${args.library_name}: ${result.message || 'Unknown error'}`
109 |           }
110 |         ]
111 |       };
112 |     }
113 |   );
114 | 
115 |   // Get detailed information about a specific footprint
116 |   server.tool(
117 |     "get_footprint_info",
118 |     "Get detailed information about a specific footprint",
119 |     {
120 |       library_name: z.string()
121 |         .describe("Name of the library containing the footprint"),
122 |       footprint_name: z.string()
123 |         .describe("Name of the footprint to get information about")
124 |     },
125 |     async (args: { library_name: string; footprint_name: string }) => {
126 |       const result = await callKicadScript("get_footprint_info", args);
127 |       if (result.success && result.info) {
128 |         const info = result.info;
129 |         const details = [
130 |           `Footprint: ${info.name}`,
131 |           `Library: ${info.library}`,
132 |           info.description ? `Description: ${info.description}` : '',
133 |           info.keywords ? `Keywords: ${info.keywords}` : '',
134 |           info.pads ? `Number of pads: ${info.pads}` : '',
135 |           info.layers ? `Layers used: ${info.layers.join(', ')}` : '',
136 |           info.courtyard ? `Courtyard size: ${info.courtyard.width}mm x ${info.courtyard.height}mm` : '',
137 |           info.attributes ? `Attributes: ${JSON.stringify(info.attributes)}` : ''
138 |         ].filter(line => line).join('\n');
139 | 
140 |         return {
141 |           content: [
142 |             {
143 |               type: "text",
144 |               text: details
145 |             }
146 |           ]
147 |         };
148 |       }
149 |       return {
150 |         content: [
151 |           {
152 |             type: "text",
153 |             text: `Failed to get footprint info: ${result.message || 'Unknown error'}`
154 |           }
155 |         ]
156 |       };
157 |     }
158 |   );
159 | }
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: CI/CD Pipeline
  2 | 
  3 | on:
  4 |   push:
  5 |     branches: [ main, develop ]
  6 |   pull_request:
  7 |     branches: [ main, develop ]
  8 | 
  9 | jobs:
 10 |   # TypeScript/Node.js tests
 11 |   typescript-tests:
 12 |     name: TypeScript Build & Test
 13 |     runs-on: ${{ matrix.os }}
 14 |     strategy:
 15 |       matrix:
 16 |         os: [ubuntu-24.04, ubuntu-22.04, windows-latest, macos-latest]
 17 |         node-version: [18.x, 20.x, 22.x]
 18 | 
 19 |     steps:
 20 |       - name: Checkout code
 21 |         uses: actions/checkout@v4
 22 | 
 23 |       - name: Setup Node.js ${{ matrix.node-version }}
 24 |         uses: actions/setup-node@v4
 25 |         with:
 26 |           node-version: ${{ matrix.node-version }}
 27 |           cache: 'npm'
 28 | 
 29 |       - name: Install dependencies
 30 |         run: npm ci
 31 | 
 32 |       - name: Run TypeScript compiler
 33 |         run: npm run build
 34 | 
 35 |       - name: Run linter
 36 |         run: npm run lint || echo "Linter not configured yet"
 37 | 
 38 |       - name: Run tests
 39 |         run: npm test || echo "Tests not configured yet"
 40 | 
 41 |   # Python tests
 42 |   python-tests:
 43 |     name: Python Tests
 44 |     runs-on: ${{ matrix.os }}
 45 |     strategy:
 46 |       matrix:
 47 |         os: [ubuntu-24.04, ubuntu-22.04]
 48 |         python-version: ['3.10', '3.11', '3.12']
 49 | 
 50 |     steps:
 51 |       - name: Checkout code
 52 |         uses: actions/checkout@v4
 53 | 
 54 |       - name: Setup Python ${{ matrix.python-version }}
 55 |         uses: actions/setup-python@v5
 56 |         with:
 57 |           python-version: ${{ matrix.python-version }}
 58 |           cache: 'pip'
 59 | 
 60 |       - name: Install Python dependencies
 61 |         run: |
 62 |           python -m pip install --upgrade pip
 63 |           pip install pytest pytest-cov black mypy pylint
 64 |           if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
 65 |           if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
 66 | 
 67 |       - name: Run Black formatter check
 68 |         run: black --check python/ || echo "Black not configured yet"
 69 | 
 70 |       - name: Run MyPy type checker
 71 |         run: mypy python/ || echo "MyPy not configured yet"
 72 | 
 73 |       - name: Run Pylint
 74 |         run: pylint python/ || echo "Pylint not configured yet"
 75 | 
 76 |       - name: Run pytest
 77 |         run: pytest python/ --cov=python --cov-report=xml || echo "Tests not configured yet"
 78 | 
 79 |       - name: Upload coverage to Codecov
 80 |         uses: codecov/codecov-action@v4
 81 |         with:
 82 |           file: ./coverage.xml
 83 |           flags: python
 84 |           name: python-${{ matrix.python-version }}
 85 |         if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-24.04'
 86 | 
 87 |   # Integration tests (requires KiCAD)
 88 |   integration-tests:
 89 |     name: Integration Tests (Linux + KiCAD)
 90 |     runs-on: ubuntu-24.04
 91 | 
 92 |     steps:
 93 |       - name: Checkout code
 94 |         uses: actions/checkout@v4
 95 | 
 96 |       - name: Setup Node.js
 97 |         uses: actions/setup-node@v4
 98 |         with:
 99 |           node-version: '20.x'
100 |           cache: 'npm'
101 | 
102 |       - name: Setup Python
103 |         uses: actions/setup-python@v5
104 |         with:
105 |           python-version: '3.12'
106 |           cache: 'pip'
107 | 
108 |       - name: Add KiCAD PPA and Install KiCAD 9.0
109 |         run: |
110 |           sudo add-apt-repository --yes ppa:kicad/kicad-9.0-releases
111 |           sudo apt-get update
112 |           sudo apt-get install -y kicad kicad-libraries
113 | 
114 |       - name: Verify KiCAD installation
115 |         run: |
116 |           kicad-cli version || echo "kicad-cli not found"
117 |           python3 -c "import pcbnew; print(f'pcbnew version: {pcbnew.GetBuildVersion()}')" || echo "pcbnew module not found"
118 | 
119 |       - name: Install dependencies
120 |         run: |
121 |           npm ci
122 |           pip install -r requirements.txt
123 | 
124 |       - name: Build TypeScript
125 |         run: npm run build
126 | 
127 |       - name: Run integration tests
128 |         run: |
129 |           echo "Integration tests not yet configured"
130 |           # pytest tests/integration/
131 | 
132 |   # Docker build test
133 |   docker-build:
134 |     name: Docker Build Test
135 |     runs-on: ubuntu-latest
136 | 
137 |     steps:
138 |       - name: Checkout code
139 |         uses: actions/checkout@v4
140 | 
141 |       - name: Set up Docker Buildx
142 |         uses: docker/setup-buildx-action@v3
143 | 
144 |       - name: Build Docker image
145 |         run: |
146 |           echo "Docker build not yet configured"
147 |           # docker build -t kicad-mcp-server:test .
148 | 
149 |   # Code quality checks
150 |   code-quality:
151 |     name: Code Quality
152 |     runs-on: ubuntu-latest
153 | 
154 |     steps:
155 |       - name: Checkout code
156 |         uses: actions/checkout@v4
157 | 
158 |       - name: Setup Node.js
159 |         uses: actions/setup-node@v4
160 |         with:
161 |           node-version: '20.x'
162 | 
163 |       - name: Install dependencies
164 |         run: npm ci
165 | 
166 |       - name: Run ESLint
167 |         run: npx eslint src/ || echo "ESLint not configured yet"
168 | 
169 |       - name: Run Prettier check
170 |         run: npx prettier --check "src/**/*.ts" || echo "Prettier not configured yet"
171 | 
172 |       - name: Check for security vulnerabilities
173 |         run: npm audit --audit-level=moderate || echo "No critical vulnerabilities"
174 | 
175 |   # Documentation check
176 |   docs-check:
177 |     name: Documentation Check
178 |     runs-on: ubuntu-latest
179 | 
180 |     steps:
181 |       - name: Checkout code
182 |         uses: actions/checkout@v4
183 | 
184 |       - name: Check README exists
185 |         run: test -f README.md
186 | 
187 |       - name: Check for broken links in docs
188 |         run: |
189 |           sudo apt-get install -y linkchecker || true
190 |           # linkchecker docs/ || echo "Link checker not configured"
191 | 
192 |       - name: Validate JSON files
193 |         run: |
194 |           find . -name "*.json" -not -path "./node_modules/*" -not -path "./dist/*" | xargs -I {} sh -c 'python3 -m json.tool {} > /dev/null && echo "✓ {}" || echo "✗ {}"'
195 | 
```

--------------------------------------------------------------------------------
/docs/KNOWN_ISSUES.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Known Issues & Workarounds
  2 | 
  3 | **Last Updated:** 2025-12-02
  4 | **Version:** 2.1.0-alpha
  5 | 
  6 | This document tracks known issues and provides workarounds where available.
  7 | 
  8 | ---
  9 | 
 10 | ## Current Issues
 11 | 
 12 | ### 1. `get_board_info` KiCAD 9.0 API Issue
 13 | 
 14 | **Status:** KNOWN - Non-critical
 15 | 
 16 | **Symptoms:**
 17 | ```
 18 | AttributeError: 'BOARD' object has no attribute 'LT_USER'
 19 | ```
 20 | 
 21 | **Root Cause:** KiCAD 9.0 changed layer enumeration constants
 22 | 
 23 | **Workaround:** Use `get_project_info` instead for basic project details
 24 | 
 25 | **Impact:** Low - informational command only
 26 | 
 27 | ---
 28 | 
 29 | ### 2. Zone Filling via SWIG Causes Segfault
 30 | 
 31 | **Status:** KNOWN - Workaround available
 32 | 
 33 | **Symptoms:**
 34 | - Copper pours created but not filled automatically when using SWIG backend
 35 | - Calling `ZONE_FILLER` via SWIG causes segfault
 36 | 
 37 | **Workaround Options:**
 38 | 1. Use IPC backend (zones fill correctly via IPC)
 39 | 2. Open the board in KiCAD UI - zones fill automatically when opened
 40 | 
 41 | **Impact:** Medium - affects copper pour visualization until opened in KiCAD
 42 | 
 43 | ---
 44 | 
 45 | ### 3. UI Manual Reload Required (SWIG Backend)
 46 | 
 47 | **Status:** BY DESIGN - Fixed by IPC
 48 | 
 49 | **Symptoms:**
 50 | - MCP makes changes via SWIG backend
 51 | - KiCAD doesn't show changes until file is reloaded
 52 | 
 53 | **Current Workflow:**
 54 | ```
 55 | 1. MCP makes change via SWIG
 56 | 2. KiCAD shows: "File has been modified. Reload? [Yes] [No]"
 57 | 3. User clicks "Yes"
 58 | 4. Changes appear in UI
 59 | ```
 60 | 
 61 | **Why:** SWIG-based backend requires file I/O, can't push changes to running UI
 62 | 
 63 | **Fix:** Use IPC backend for real-time updates (requires KiCAD to be running with IPC enabled)
 64 | 
 65 | **Workaround:** Click reload prompt or use File > Revert
 66 | 
 67 | ---
 68 | 
 69 | ### 4. IPC Backend Experimental
 70 | 
 71 | **Status:** UNDER DEVELOPMENT
 72 | 
 73 | **Description:**
 74 | The IPC backend is currently being implemented and tested. Some commands may not work as expected in all scenarios.
 75 | 
 76 | **Known IPC Limitations:**
 77 | - KiCAD must be running with IPC enabled
 78 | - Some commands fall back to SWIG (e.g., delete_trace)
 79 | - Footprint loading uses hybrid approach (SWIG for library, IPC for placement)
 80 | - Error handling may not be comprehensive in all cases
 81 | 
 82 | **Workaround:** If IPC fails, the server automatically falls back to SWIG backend
 83 | 
 84 | ---
 85 | 
 86 | ### 5. Schematic Support Limited
 87 | 
 88 | **Status:** KNOWN - Partial support
 89 | 
 90 | **Description:**
 91 | Schematic operations use the kicad-skip library which has some limitations with KiCAD 9.0 file format changes.
 92 | 
 93 | **Affected Commands:**
 94 | - `create_schematic`
 95 | - `add_schematic_component`
 96 | - `add_schematic_wire`
 97 | 
 98 | **Workaround:** Manual schematic creation may be more reliable for complex designs
 99 | 
100 | ---
101 | 
102 | ## Recently Fixed
103 | 
104 | ### Component Library Integration (Fixed 2025-11-01)
105 | 
106 | **Was:** Could not find footprint libraries
107 | **Now:** Auto-discovers 153 KiCAD footprint libraries, search and list working
108 | 
109 | ### Routing Operations KiCAD 9.0 (Fixed 2025-11-01)
110 | 
111 | **Was:** Multiple API compatibility issues with KiCAD 9.0
112 | **Now:** All routing commands tested and working:
113 | - `netinfo.FindNet()` -> `netinfo.NetsByName()[name]`
114 | - `zone.SetPriority()` -> `zone.SetAssignedPriority()`
115 | - `ZONE_FILL_MODE_POLYGON` -> `ZONE_FILL_MODE_POLYGONS`
116 | 
117 | ### KiCAD Process Detection (Fixed 2025-10-26)
118 | 
119 | **Was:** `check_kicad_ui` detected MCP server's own processes
120 | **Now:** Properly filters to only detect actual KiCAD binaries
121 | 
122 | ### set_board_size KiCAD 9.0 (Fixed 2025-10-26)
123 | 
124 | **Was:** Failed with `BOX2I_SetSize` type error
125 | **Now:** Works with KiCAD 9.0 API
126 | 
127 | ### add_board_text KiCAD 9.0 (Fixed 2025-10-26)
128 | 
129 | **Was:** Failed with `EDA_ANGLE` type error
130 | **Now:** Works with KiCAD 9.0 API
131 | 
132 | ### Schematic Parameter Mismatch (Fixed 2025-12-02)
133 | 
134 | **Was:** `create_schematic` failed due to parameter name differences between TypeScript and Python
135 | **Now:** Accepts multiple parameter naming conventions (`name`, `projectName`, `title`, `filename`)
136 | 
137 | ---
138 | 
139 | ## Reporting New Issues
140 | 
141 | If you encounter an issue not listed here:
142 | 
143 | 1. **Check MCP logs:** `~/.kicad-mcp/logs/kicad_interface.log`
144 | 2. **Check KiCAD version:** `python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())"` (must be 9.0+)
145 | 3. **Try the operation in KiCAD directly** - is it a KiCAD issue?
146 | 4. **Open GitHub issue** with:
147 |    - Error message
148 |    - Log excerpt
149 |    - Steps to reproduce
150 |    - KiCAD version
151 |    - OS and version
152 | 
153 | ---
154 | 
155 | ## Priority Matrix
156 | 
157 | | Issue | Priority | Impact | Status |
158 | |-------|----------|--------|--------|
159 | | IPC Backend Testing | High | Medium | In Progress |
160 | | get_board_info Fix | Low | Low | Known |
161 | | Zone Filling (SWIG) | Medium | Medium | Workaround Available |
162 | | Schematic Support | Medium | Medium | Partial |
163 | 
164 | ---
165 | 
166 | ## General Workarounds
167 | 
168 | ### Server Won't Start
169 | ```bash
170 | # Check Python can import pcbnew
171 | python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())"
172 | 
173 | # Check paths
174 | python3 python/utils/platform_helper.py
175 | ```
176 | 
177 | ### Commands Fail After Server Restart
178 | ```
179 | # Board reference is lost on restart
180 | # Always run open_project after server restart
181 | ```
182 | 
183 | ### KiCAD UI Doesn't Show Changes (SWIG Mode)
184 | ```
185 | # File > Revert (or click reload prompt)
186 | # Or: Close and reopen file in KiCAD
187 | # Or: Use IPC backend for automatic updates
188 | ```
189 | 
190 | ### IPC Not Connecting
191 | ```
192 | # Ensure KiCAD is running
193 | # Enable IPC: Preferences > Plugins > Enable IPC API Server
194 | # Have a board open in PCB editor
195 | # Check socket exists: ls /tmp/kicad/api.sock
196 | ```
197 | 
198 | ---
199 | 
200 | **Need Help?**
201 | - Check [IPC_BACKEND_STATUS.md](IPC_BACKEND_STATUS.md) for IPC details
202 | - Check [REALTIME_WORKFLOW.md](REALTIME_WORKFLOW.md) for workflow tips
203 | - Check logs: `~/.kicad-mcp/logs/kicad_interface.log`
204 | - Open an issue on GitHub
205 | 
```

--------------------------------------------------------------------------------
/CHANGELOG_2025-11-05.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Changelog - November 5, 2025
  2 | 
  3 | ## Windows Support Package
  4 | 
  5 | **Focus:** Comprehensive Windows support improvements and platform documentation
  6 | 
  7 | **Status:** Complete
  8 | 
  9 | ---
 10 | 
 11 | ## New Features
 12 | 
 13 | ### Windows Automated Setup
 14 | - **setup-windows.ps1** - PowerShell script for one-command setup
 15 |   - Auto-detects KiCAD installation and version
 16 |   - Validates all prerequisites (Node.js, Python, pcbnew)
 17 |   - Installs dependencies automatically
 18 |   - Builds TypeScript project
 19 |   - Generates MCP configuration
 20 |   - Runs comprehensive diagnostic tests
 21 |   - Provides colored output with clear success/failure indicators
 22 |   - Generates detailed error reports with solutions
 23 | 
 24 | ### Enhanced Error Diagnostics
 25 | - **Python Interface** (kicad_interface.py)
 26 |   - Windows-specific environment diagnostics on startup
 27 |   - Auto-detects KiCAD installations in standard Windows locations
 28 |   - Lists found KiCAD versions and Python paths
 29 |   - Platform-specific error messages with actionable troubleshooting steps
 30 |   - Detailed logging of PYTHONPATH and system PATH
 31 | 
 32 | - **Server Startup Validation** (src/server.ts)
 33 |   - New `validatePrerequisites()` method
 34 |   - Tests pcbnew import before starting Python process
 35 |   - Validates Python executable exists
 36 |   - Checks project build status
 37 |   - Catches configuration errors early
 38 |   - Writes errors to both log file and stderr (visible in Claude Desktop)
 39 |   - Platform-specific troubleshooting hints in error messages
 40 | 
 41 | ### Documentation
 42 | 
 43 | - **WINDOWS_TROUBLESHOOTING.md** - Comprehensive Windows guide
 44 |   - 8 common issues with step-by-step solutions
 45 |   - Configuration examples for Claude Desktop and Cline
 46 |   - Manual testing procedures
 47 |   - Advanced diagnostics section
 48 |   - Success checklist
 49 |   - Known limitations
 50 | 
 51 | - **PLATFORM_GUIDE.md** - Linux vs Windows comparison
 52 |   - Detailed comparison table
 53 |   - Installation differences explained
 54 |   - Path handling conventions
 55 |   - Python environment differences
 56 |   - Testing and debugging workflows
 57 |   - Platform-specific best practices
 58 |   - Migration guidance
 59 | 
 60 | - **README.md** - Updated Windows section
 61 |   - Automated setup prominently featured
 62 |   - Honest status: "Supported (community tested)"
 63 |   - Links to troubleshooting resources
 64 |   - Both automated and manual setup paths
 65 |   - Clear verification steps
 66 | 
 67 | ### Documentation Cleanup
 68 | - Removed all emojis from documentation (per project guidelines)
 69 | - Updated STATUS_SUMMARY.md Windows status from "UNTESTED" to "SUPPORTED"
 70 | - Consistent formatting across all documentation files
 71 | 
 72 | ---
 73 | 
 74 | ## Bug Fixes
 75 | 
 76 | ### Startup Reliability
 77 | - Server no longer fails silently on Windows
 78 | - Prerequisite validation catches common configuration errors before they cause crashes
 79 | - Clear error messages guide users to solutions
 80 | 
 81 | ### Path Handling
 82 | - Improved path handling for Windows (backslash and forward slash support)
 83 | - Better documentation of path escaping in JSON configuration files
 84 | 
 85 | ---
 86 | 
 87 | ## Improvements
 88 | 
 89 | ### GitHub Issue Support
 90 | - Responded to Issue #5 with initial troubleshooting steps
 91 | - Posted comprehensive update announcing all Windows improvements
 92 | - Provided clear next steps for affected users
 93 | 
 94 | ### Testing
 95 | - TypeScript build verified with new validation code
 96 | - All changes compile without errors or warnings
 97 | 
 98 | ---
 99 | 
100 | ## Files Changed
101 | 
102 | ### New Files
103 | - `setup-windows.ps1` - Automated Windows setup script (500+ lines)
104 | - `docs/WINDOWS_TROUBLESHOOTING.md` - Windows troubleshooting guide
105 | - `docs/PLATFORM_GUIDE.md` - Linux vs Windows comparison
106 | - `CHANGELOG_2025-11-05.md` - This changelog
107 | 
108 | ### Modified Files
109 | - `README.md` - Updated Windows installation section
110 | - `docs/STATUS_SUMMARY.md` - Updated Windows status and removed emojis
111 | - `docs/ROADMAP.md` - Removed emojis
112 | - `python/kicad_interface.py` - Added Windows diagnostics
113 | - `src/server.ts` - Added startup validation
114 | 
115 | ---
116 | 
117 | ## Breaking Changes
118 | 
119 | None. All changes are backward compatible.
120 | 
121 | ---
122 | 
123 | ## Known Issues
124 | 
125 | ### Not Fixed
126 | - JLCPCB integration still in planning phase (not implemented)
127 | - macOS remains untested
128 | - `get_board_info` layer constants issue (low priority)
129 | - Zone filling disabled due to SWIG API segfault
130 | 
131 | ---
132 | 
133 | ## Migration Notes
134 | 
135 | ### Upgrading from Previous Version
136 | 
137 | **For Windows users:**
138 | 1. Pull latest changes
139 | 2. Run `setup-windows.ps1`
140 | 3. Update your MCP client configuration if prompted
141 | 4. Restart your MCP client
142 | 
143 | **For Linux users:**
144 | 1. Pull latest changes
145 | 2. Run `npm install` and `npm run build`
146 | 3. No configuration changes needed
147 | 
148 | ---
149 | 
150 | ## Testing Performed
151 | 
152 | - PowerShell script tested on Windows 10 (simulated)
153 | - TypeScript compilation verified
154 | - Documentation reviewed for consistency
155 | - Path handling verified in configuration examples
156 | - Startup validation logic tested
157 | 
158 | ---
159 | 
160 | ## Next Steps
161 | 
162 | ### Week 2 Completion
163 | - Consider JLCPCB integration implementation
164 | - Create example projects (LED blinker)
165 | - Windows community testing and feedback
166 | 
167 | ### Week 3 Planning
168 | - IPC Backend implementation for real-time UI updates
169 | - Fix remaining minor issues
170 | - macOS testing and support
171 | 
172 | ---
173 | 
174 | ## Contributors
175 | 
176 | - mixelpixx (Chris) - Windows support implementation
177 | - spplecxer - Issue #5 report (Windows crash)
178 | 
179 | ---
180 | 
181 | ## References
182 | 
183 | - Issue #5: https://github.com/mixelpixx/KiCAD-MCP-Server/issues/5
184 | - Windows Installation Guide: [README.md](README.md#windows-1011)
185 | - Troubleshooting: [docs/WINDOWS_TROUBLESHOOTING.md](docs/WINDOWS_TROUBLESHOOTING.md)
186 | - Platform Comparison: [docs/PLATFORM_GUIDE.md](docs/PLATFORM_GUIDE.md)
187 | 
188 | ---
189 | 
190 | **Summary:** This release significantly improves Windows support with automated setup, comprehensive diagnostics, and detailed documentation. Windows users now have a smooth onboarding experience comparable to Linux users.
191 | 
```

--------------------------------------------------------------------------------
/python/kicad_api/factory.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Backend factory for creating appropriate KiCAD API backend
  3 | 
  4 | Auto-detects available backends and provides fallback mechanism.
  5 | """
  6 | import os
  7 | import logging
  8 | from typing import Optional
  9 | from pathlib import Path
 10 | 
 11 | from kicad_api.base import KiCADBackend, APINotAvailableError
 12 | 
 13 | logger = logging.getLogger(__name__)
 14 | 
 15 | 
 16 | def create_backend(backend_type: Optional[str] = None) -> KiCADBackend:
 17 |     """
 18 |     Create appropriate KiCAD backend
 19 | 
 20 |     Args:
 21 |         backend_type: Backend to use:
 22 |             - 'ipc': Use IPC API (recommended)
 23 |             - 'swig': Use legacy SWIG bindings
 24 |             - None or 'auto': Auto-detect (try IPC first, fall back to SWIG)
 25 | 
 26 |     Returns:
 27 |         KiCADBackend instance
 28 | 
 29 |     Raises:
 30 |         APINotAvailableError: If no backend is available
 31 | 
 32 |     Environment Variables:
 33 |         KICAD_BACKEND: Override backend selection ('ipc', 'swig', or 'auto')
 34 |     """
 35 |     # Check environment variable override
 36 |     if backend_type is None:
 37 |         backend_type = os.environ.get('KICAD_BACKEND', 'auto').lower()
 38 | 
 39 |     logger.info(f"Requested backend: {backend_type}")
 40 | 
 41 |     # Try specific backend if requested
 42 |     if backend_type == 'ipc':
 43 |         return _create_ipc_backend()
 44 |     elif backend_type == 'swig':
 45 |         return _create_swig_backend()
 46 |     elif backend_type == 'auto':
 47 |         return _auto_detect_backend()
 48 |     else:
 49 |         raise ValueError(f"Unknown backend type: {backend_type}")
 50 | 
 51 | 
 52 | def _create_ipc_backend() -> KiCADBackend:
 53 |     """
 54 |     Create IPC backend
 55 | 
 56 |     Returns:
 57 |         IPCBackend instance
 58 | 
 59 |     Raises:
 60 |         APINotAvailableError: If kicad-python not available
 61 |     """
 62 |     try:
 63 |         from kicad_api.ipc_backend import IPCBackend
 64 |         logger.info("Creating IPC backend")
 65 |         return IPCBackend()
 66 |     except ImportError as e:
 67 |         logger.error(f"IPC backend not available: {e}")
 68 |         raise APINotAvailableError(
 69 |             "IPC backend requires 'kicad-python' package. "
 70 |             "Install with: pip install kicad-python"
 71 |         ) from e
 72 | 
 73 | 
 74 | def _create_swig_backend() -> KiCADBackend:
 75 |     """
 76 |     Create SWIG backend
 77 | 
 78 |     Returns:
 79 |         SWIGBackend instance
 80 | 
 81 |     Raises:
 82 |         APINotAvailableError: If pcbnew not available
 83 |     """
 84 |     try:
 85 |         from kicad_api.swig_backend import SWIGBackend
 86 |         logger.info("Creating SWIG backend")
 87 |         logger.warning(
 88 |             "SWIG backend is DEPRECATED and will be removed in KiCAD 10.0. "
 89 |             "Please migrate to IPC backend."
 90 |         )
 91 |         return SWIGBackend()
 92 |     except ImportError as e:
 93 |         logger.error(f"SWIG backend not available: {e}")
 94 |         raise APINotAvailableError(
 95 |             "SWIG backend requires 'pcbnew' module. "
 96 |             "Ensure KiCAD Python module is in PYTHONPATH."
 97 |         ) from e
 98 | 
 99 | 
100 | def _auto_detect_backend() -> KiCADBackend:
101 |     """
102 |     Auto-detect best available backend
103 | 
104 |     Priority:
105 |         1. IPC API (if kicad-python available and KiCAD running)
106 |         2. SWIG API (if pcbnew available)
107 | 
108 |     Returns:
109 |         Best available KiCADBackend
110 | 
111 |     Raises:
112 |         APINotAvailableError: If no backend available
113 |     """
114 |     logger.info("Auto-detecting available KiCAD backend...")
115 | 
116 |     # Try IPC first (preferred)
117 |     try:
118 |         backend = _create_ipc_backend()
119 |         # Test connection
120 |         if backend.connect():
121 |             logger.info("✓ IPC backend available and connected")
122 |             return backend
123 |         else:
124 |             logger.warning("IPC backend available but connection failed")
125 |     except (ImportError, APINotAvailableError) as e:
126 |         logger.debug(f"IPC backend not available: {e}")
127 | 
128 |     # Fall back to SWIG
129 |     try:
130 |         backend = _create_swig_backend()
131 |         logger.warning(
132 |             "Using deprecated SWIG backend. "
133 |             "For best results, use IPC API with KiCAD running."
134 |         )
135 |         return backend
136 |     except (ImportError, APINotAvailableError) as e:
137 |         logger.error(f"SWIG backend not available: {e}")
138 | 
139 |     # No backend available
140 |     raise APINotAvailableError(
141 |         "No KiCAD backend available. Please install either:\n"
142 |         "  - kicad-python (recommended): pip install kicad-python\n"
143 |         "  - Ensure KiCAD Python module (pcbnew) is in PYTHONPATH"
144 |     )
145 | 
146 | 
147 | def get_available_backends() -> dict:
148 |     """
149 |     Check which backends are available
150 | 
151 |     Returns:
152 |         Dictionary with backend availability:
153 |             {
154 |                 'ipc': {'available': bool, 'version': str or None},
155 |                 'swig': {'available': bool, 'version': str or None}
156 |             }
157 |     """
158 |     results = {}
159 | 
160 |     # Check IPC (kicad-python uses 'kipy' module name)
161 |     try:
162 |         import kipy
163 |         results['ipc'] = {
164 |             'available': True,
165 |             'version': getattr(kipy, '__version__', 'unknown')
166 |         }
167 |     except ImportError:
168 |         results['ipc'] = {'available': False, 'version': None}
169 | 
170 |     # Check SWIG
171 |     try:
172 |         import pcbnew
173 |         results['swig'] = {
174 |             'available': True,
175 |             'version': pcbnew.GetBuildVersion()
176 |         }
177 |     except ImportError:
178 |         results['swig'] = {'available': False, 'version': None}
179 | 
180 |     return results
181 | 
182 | 
183 | if __name__ == "__main__":
184 |     # Quick diagnostic
185 |     import json
186 |     print("KiCAD Backend Availability:")
187 |     print(json.dumps(get_available_backends(), indent=2))
188 | 
189 |     print("\nAttempting to create backend...")
190 |     try:
191 |         backend = create_backend()
192 |         print(f"✓ Created backend: {type(backend).__name__}")
193 |         if backend.connect():
194 |             print(f"✓ Connected to KiCAD: {backend.get_version()}")
195 |         else:
196 |             print("✗ Failed to connect to KiCAD")
197 |     except Exception as e:
198 |         print(f"✗ Error: {e}")
199 | 
```

--------------------------------------------------------------------------------
/tests/test_platform_helper.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Tests for platform_helper utility
  3 | 
  4 | These are unit tests that work on all platforms.
  5 | """
  6 | import pytest
  7 | import platform
  8 | from pathlib import Path
  9 | import sys
 10 | import os
 11 | 
 12 | # Add parent directory to path to import utils
 13 | sys.path.insert(0, str(Path(__file__).parent.parent / "python"))
 14 | 
 15 | from utils.platform_helper import PlatformHelper, detect_platform
 16 | 
 17 | 
 18 | class TestPlatformDetection:
 19 |     """Test platform detection functions"""
 20 | 
 21 |     def test_exactly_one_platform_detected(self):
 22 |         """Ensure exactly one platform is detected"""
 23 |         platforms = [
 24 |             PlatformHelper.is_windows(),
 25 |             PlatformHelper.is_linux(),
 26 |             PlatformHelper.is_macos(),
 27 |         ]
 28 |         assert sum(platforms) == 1, "Exactly one platform should be detected"
 29 | 
 30 |     def test_platform_name_is_valid(self):
 31 |         """Test platform name is human-readable"""
 32 |         name = PlatformHelper.get_platform_name()
 33 |         assert name in ["Windows", "Linux", "macOS"], f"Unknown platform: {name}"
 34 | 
 35 |     def test_platform_name_matches_detection(self):
 36 |         """Ensure platform name matches detection functions"""
 37 |         name = PlatformHelper.get_platform_name()
 38 |         if name == "Windows":
 39 |             assert PlatformHelper.is_windows()
 40 |         elif name == "Linux":
 41 |             assert PlatformHelper.is_linux()
 42 |         elif name == "macOS":
 43 |             assert PlatformHelper.is_macos()
 44 | 
 45 | 
 46 | class TestPathGeneration:
 47 |     """Test path generation functions"""
 48 | 
 49 |     def test_config_dir_exists_after_ensure(self):
 50 |         """Test that config directory is created"""
 51 |         PlatformHelper.ensure_directories()
 52 |         config_dir = PlatformHelper.get_config_dir()
 53 |         assert config_dir.exists(), f"Config dir should exist: {config_dir}"
 54 |         assert config_dir.is_dir(), f"Config dir should be a directory: {config_dir}"
 55 | 
 56 |     def test_log_dir_exists_after_ensure(self):
 57 |         """Test that log directory is created"""
 58 |         PlatformHelper.ensure_directories()
 59 |         log_dir = PlatformHelper.get_log_dir()
 60 |         assert log_dir.exists(), f"Log dir should exist: {log_dir}"
 61 |         assert log_dir.is_dir(), f"Log dir should be a directory: {log_dir}"
 62 | 
 63 |     def test_cache_dir_exists_after_ensure(self):
 64 |         """Test that cache directory is created"""
 65 |         PlatformHelper.ensure_directories()
 66 |         cache_dir = PlatformHelper.get_cache_dir()
 67 |         assert cache_dir.exists(), f"Cache dir should exist: {cache_dir}"
 68 |         assert cache_dir.is_dir(), f"Cache dir should be a directory: {cache_dir}"
 69 | 
 70 |     def test_config_dir_is_platform_appropriate(self):
 71 |         """Test that config directory follows platform conventions"""
 72 |         config_dir = PlatformHelper.get_config_dir()
 73 | 
 74 |         if PlatformHelper.is_linux():
 75 |             # Should be ~/.config/kicad-mcp or $XDG_CONFIG_HOME/kicad-mcp
 76 |             if "XDG_CONFIG_HOME" in os.environ:
 77 |                 expected = Path(os.environ["XDG_CONFIG_HOME"]) / "kicad-mcp"
 78 |             else:
 79 |                 expected = Path.home() / ".config" / "kicad-mcp"
 80 |             assert config_dir == expected
 81 | 
 82 |         elif PlatformHelper.is_windows():
 83 |             # Should be %USERPROFILE%\.kicad-mcp
 84 |             expected = Path.home() / ".kicad-mcp"
 85 |             assert config_dir == expected
 86 | 
 87 |         elif PlatformHelper.is_macos():
 88 |             # Should be ~/Library/Application Support/kicad-mcp
 89 |             expected = Path.home() / "Library" / "Application Support" / "kicad-mcp"
 90 |             assert config_dir == expected
 91 | 
 92 |     def test_python_executable_is_valid(self):
 93 |         """Test that Python executable path is valid"""
 94 |         exe = PlatformHelper.get_python_executable()
 95 |         assert exe.exists(), f"Python executable should exist: {exe}"
 96 |         assert str(exe) == sys.executable
 97 | 
 98 |     def test_kicad_library_search_paths_returns_list(self):
 99 |         """Test that library search paths returns a list"""
100 |         paths = PlatformHelper.get_kicad_library_search_paths()
101 |         assert isinstance(paths, list)
102 |         assert len(paths) > 0
103 |         # All paths should be strings (glob patterns)
104 |         assert all(isinstance(p, str) for p in paths)
105 | 
106 | 
107 | class TestDetectPlatform:
108 |     """Test the detect_platform convenience function"""
109 | 
110 |     def test_detect_platform_returns_dict(self):
111 |         """Test that detect_platform returns a dictionary"""
112 |         info = detect_platform()
113 |         assert isinstance(info, dict)
114 | 
115 |     def test_detect_platform_has_required_keys(self):
116 |         """Test that detect_platform includes all required keys"""
117 |         info = detect_platform()
118 |         required_keys = [
119 |             "system",
120 |             "platform",
121 |             "is_windows",
122 |             "is_linux",
123 |             "is_macos",
124 |             "python_version",
125 |             "python_executable",
126 |             "config_dir",
127 |             "log_dir",
128 |             "cache_dir",
129 |             "kicad_python_paths",
130 |         ]
131 |         for key in required_keys:
132 |             assert key in info, f"Missing key: {key}"
133 | 
134 |     def test_detect_platform_python_version_format(self):
135 |         """Test that Python version is in correct format"""
136 |         info = detect_platform()
137 |         version = info["python_version"]
138 |         # Should be like "3.12.3"
139 |         parts = version.split(".")
140 |         assert len(parts) == 3
141 |         assert all(p.isdigit() for p in parts)
142 | 
143 | 
144 | @pytest.mark.integration
145 | class TestKiCADPathDetection:
146 |     """Tests that require KiCAD to be installed"""
147 | 
148 |     def test_kicad_python_paths_exist(self):
149 |         """Test that at least one KiCAD Python path exists (if KiCAD is installed)"""
150 |         paths = PlatformHelper.get_kicad_python_paths()
151 |         # This test only makes sense if KiCAD is installed
152 |         # In CI, KiCAD should be installed
153 |         if paths:
154 |             assert all(p.exists() for p in paths), "All returned paths should exist"
155 | 
156 |     def test_can_import_pcbnew_after_adding_paths(self):
157 |         """Test that pcbnew can be imported after adding KiCAD paths"""
158 |         PlatformHelper.add_kicad_to_python_path()
159 |         try:
160 |             import pcbnew
161 |             # If we get here, pcbnew is available
162 |             assert pcbnew is not None
163 |             version = pcbnew.GetBuildVersion()
164 |             assert version is not None
165 |             print(f"Found KiCAD version: {version}")
166 |         except ImportError:
167 |             pytest.skip("KiCAD pcbnew module not available (KiCAD not installed)")
168 | 
169 | 
170 | if __name__ == "__main__":
171 |     # Run tests with pytest
172 |     pytest.main([__file__, "-v"])
173 | 
```

--------------------------------------------------------------------------------
/python/commands/library_schematic.py:
--------------------------------------------------------------------------------

```python
  1 | from skip import Schematic
  2 | # Symbol class might not be directly importable in the current version
  3 | import os
  4 | import glob
  5 | 
  6 | class LibraryManager:
  7 |     """Manage symbol libraries"""
  8 | 
  9 |     @staticmethod
 10 |     def list_available_libraries(search_paths=None):
 11 |         """List all available symbol libraries"""
 12 |         if search_paths is None:
 13 |             # Default library paths based on common KiCAD installations
 14 |             # This would need to be configured for the specific environment
 15 |             search_paths = [
 16 |                 "C:/Program Files/KiCad/*/share/kicad/symbols/*.kicad_sym",  # Windows path pattern
 17 |                 "/usr/share/kicad/symbols/*.kicad_sym",                      # Linux path pattern
 18 |                 "/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/*.kicad_sym",  # macOS path pattern
 19 |                 os.path.expanduser("~/Documents/KiCad/*/symbols/*.kicad_sym")  # User libraries pattern
 20 |             ]
 21 | 
 22 |         libraries = []
 23 |         for path_pattern in search_paths:
 24 |             try:
 25 |                 # Use glob to find all matching files
 26 |                 matching_libs = glob.glob(path_pattern, recursive=True)
 27 |                 libraries.extend(matching_libs)
 28 |             except Exception as e:
 29 |                 print(f"Error searching for libraries at {path_pattern}: {e}")
 30 | 
 31 |         # Extract library names from paths
 32 |         library_names = [os.path.splitext(os.path.basename(lib))[0] for lib in libraries]
 33 |         print(f"Found {len(library_names)} libraries: {', '.join(library_names[:10])}{'...' if len(library_names) > 10 else ''}")
 34 |         
 35 |         # Return both full paths and library names
 36 |         return {"paths": libraries, "names": library_names}
 37 | 
 38 |     @staticmethod
 39 |     def list_library_symbols(library_path):
 40 |         """List all symbols in a library"""
 41 |         try:
 42 |             # kicad-skip doesn't provide a direct way to simply list symbols in a library
 43 |             # without loading each one. We might need to implement this using KiCAD's Python API
 44 |             # directly, or by using a different approach.
 45 |             # For now, this is a placeholder implementation.
 46 |             
 47 |             # A potential approach would be to load the library file using KiCAD's Python API
 48 |             # or by parsing the library file format.
 49 |             # KiCAD symbol libraries are .kicad_sym files which are S-expression format
 50 |             print(f"Attempted to list symbols in library {library_path}. This requires advanced implementation.")
 51 |             return []
 52 |         except Exception as e:
 53 |             print(f"Error listing symbols in library {library_path}: {e}")
 54 |             return []
 55 | 
 56 |     @staticmethod
 57 |     def get_symbol_details(library_path, symbol_name):
 58 |         """Get detailed information about a symbol"""
 59 |         try:
 60 |             # Similar to list_library_symbols, this might require a more direct approach
 61 |             # using KiCAD's Python API or by parsing the symbol library.
 62 |             print(f"Attempted to get details for symbol {symbol_name} in library {library_path}. This requires advanced implementation.")
 63 |             return {}
 64 |         except Exception as e:
 65 |             print(f"Error getting symbol details for {symbol_name} in {library_path}: {e}")
 66 |             return {}
 67 | 
 68 |     @staticmethod
 69 |     def search_symbols(query, search_paths=None):
 70 |         """Search for symbols matching criteria"""
 71 |         try:
 72 |             # This would typically involve:
 73 |             # 1. Getting a list of all libraries using list_available_libraries
 74 |             # 2. For each library, getting a list of all symbols
 75 |             # 3. Filtering symbols based on the query
 76 |             
 77 |             # For now, this is a placeholder implementation
 78 |             libraries = LibraryManager.list_available_libraries(search_paths)
 79 |             
 80 |             results = []
 81 |             print(f"Searched for symbols matching '{query}'. This requires advanced implementation.")
 82 |             return results
 83 |         except Exception as e:
 84 |             print(f"Error searching for symbols matching '{query}': {e}")
 85 |             return []
 86 |             
 87 |     @staticmethod
 88 |     def get_default_symbol_for_component_type(component_type, search_paths=None):
 89 |         """Get a recommended default symbol for a given component type"""
 90 |         # This method provides a simplified way to get a symbol for common component types
 91 |         # It's useful when the user doesn't specify a particular library/symbol
 92 |         
 93 |         # Define common mappings from component type to library/symbol
 94 |         common_mappings = {
 95 |             "resistor": {"library": "Device", "symbol": "R"},
 96 |             "capacitor": {"library": "Device", "symbol": "C"},
 97 |             "inductor": {"library": "Device", "symbol": "L"},
 98 |             "diode": {"library": "Device", "symbol": "D"},
 99 |             "led": {"library": "Device", "symbol": "LED"},
100 |             "transistor_npn": {"library": "Device", "symbol": "Q_NPN_BCE"},
101 |             "transistor_pnp": {"library": "Device", "symbol": "Q_PNP_BCE"},
102 |             "opamp": {"library": "Amplifier_Operational", "symbol": "OpAmp_Dual_Generic"},
103 |             "microcontroller": {"library": "MCU_Module", "symbol": "Arduino_UNO_R3"},
104 |             # Add more common components as needed
105 |         }
106 |         
107 |         # Normalize input to lowercase
108 |         component_type_lower = component_type.lower()
109 |         
110 |         # Try direct match first
111 |         if component_type_lower in common_mappings:
112 |             return common_mappings[component_type_lower]
113 |             
114 |         # Try partial matches
115 |         for key, value in common_mappings.items():
116 |             if component_type_lower in key or key in component_type_lower:
117 |                 return value
118 |                 
119 |         # Default fallback
120 |         return {"library": "Device", "symbol": "R"}
121 | 
122 | if __name__ == '__main__':
123 |     # Example Usage (for testing)
124 |     # List available libraries
125 |     libraries = LibraryManager.list_available_libraries()
126 |     if libraries["paths"]:
127 |         first_lib = libraries["paths"][0]
128 |         lib_name = libraries["names"][0]
129 |         print(f"Testing with first library: {lib_name} ({first_lib})")
130 |         
131 |         # List symbols in the first library
132 |         symbols = LibraryManager.list_library_symbols(first_lib)
133 |         # This will report that it requires advanced implementation
134 |         
135 |     # Get default symbol for a component type
136 |     resistor_sym = LibraryManager.get_default_symbol_for_component_type("resistor")
137 |     print(f"Default symbol for resistor: {resistor_sym['library']}/{resistor_sym['symbol']}")
138 |     
139 |     # Try a partial match
140 |     cap_sym = LibraryManager.get_default_symbol_for_component_type("cap")
141 |     print(f"Default symbol for 'cap': {cap_sym['library']}/{cap_sym['symbol']}")
142 | 
```

--------------------------------------------------------------------------------
/python/commands/board/layers.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Board layer command implementations for KiCAD interface
  3 | """
  4 | 
  5 | import pcbnew
  6 | import logging
  7 | from typing import Dict, Any, Optional
  8 | 
  9 | logger = logging.getLogger('kicad_interface')
 10 | 
 11 | class BoardLayerCommands:
 12 |     """Handles board layer operations"""
 13 | 
 14 |     def __init__(self, board: Optional[pcbnew.BOARD] = None):
 15 |         """Initialize with optional board instance"""
 16 |         self.board = board
 17 | 
 18 |     def add_layer(self, params: Dict[str, Any]) -> Dict[str, Any]:
 19 |         """Add a new layer to the PCB"""
 20 |         try:
 21 |             if not self.board:
 22 |                 return {
 23 |                     "success": False,
 24 |                     "message": "No board is loaded",
 25 |                     "errorDetails": "Load or create a board first"
 26 |                 }
 27 | 
 28 |             name = params.get("name")
 29 |             layer_type = params.get("type")
 30 |             position = params.get("position")
 31 |             number = params.get("number")
 32 | 
 33 |             if not name or not layer_type or not position:
 34 |                 return {
 35 |                     "success": False,
 36 |                     "message": "Missing parameters",
 37 |                     "errorDetails": "name, type, and position are required"
 38 |                 }
 39 | 
 40 |             # Get layer stack
 41 |             layer_stack = self.board.GetLayerStack()
 42 | 
 43 |             # Determine layer ID based on position and number
 44 |             layer_id = None
 45 |             if position == "inner":
 46 |                 if number is None:
 47 |                     return {
 48 |                         "success": False,
 49 |                         "message": "Missing layer number",
 50 |                         "errorDetails": "number is required for inner layers"
 51 |                     }
 52 |                 layer_id = pcbnew.In1_Cu + (number - 1)
 53 |             elif position == "top":
 54 |                 layer_id = pcbnew.F_Cu
 55 |             elif position == "bottom":
 56 |                 layer_id = pcbnew.B_Cu
 57 | 
 58 |             if layer_id is None:
 59 |                 return {
 60 |                     "success": False,
 61 |                     "message": "Invalid layer position",
 62 |                     "errorDetails": "position must be 'top', 'bottom', or 'inner'"
 63 |                 }
 64 | 
 65 |             # Set layer properties
 66 |             layer_stack.SetLayerName(layer_id, name)
 67 |             layer_stack.SetLayerType(layer_id, self._get_layer_type(layer_type))
 68 |             
 69 |             # Enable the layer
 70 |             self.board.SetLayerEnabled(layer_id, True)
 71 | 
 72 |             return {
 73 |                 "success": True,
 74 |                 "message": f"Added layer: {name}",
 75 |                 "layer": {
 76 |                     "name": name,
 77 |                     "type": layer_type,
 78 |                     "position": position,
 79 |                     "number": number
 80 |                 }
 81 |             }
 82 | 
 83 |         except Exception as e:
 84 |             logger.error(f"Error adding layer: {str(e)}")
 85 |             return {
 86 |                 "success": False,
 87 |                 "message": "Failed to add layer",
 88 |                 "errorDetails": str(e)
 89 |             }
 90 | 
 91 |     def set_active_layer(self, params: Dict[str, Any]) -> Dict[str, Any]:
 92 |         """Set the active layer for PCB operations"""
 93 |         try:
 94 |             if not self.board:
 95 |                 return {
 96 |                     "success": False,
 97 |                     "message": "No board is loaded",
 98 |                     "errorDetails": "Load or create a board first"
 99 |                 }
100 | 
101 |             layer = params.get("layer")
102 |             if not layer:
103 |                 return {
104 |                     "success": False,
105 |                     "message": "No layer specified",
106 |                     "errorDetails": "layer parameter is required"
107 |                 }
108 | 
109 |             # Find layer ID by name
110 |             layer_id = self.board.GetLayerID(layer)
111 |             if layer_id < 0:
112 |                 return {
113 |                     "success": False,
114 |                     "message": "Layer not found",
115 |                     "errorDetails": f"Layer '{layer}' does not exist"
116 |                 }
117 | 
118 |             # Set active layer
119 |             self.board.SetActiveLayer(layer_id)
120 | 
121 |             return {
122 |                 "success": True,
123 |                 "message": f"Set active layer to: {layer}",
124 |                 "layer": {
125 |                     "name": layer,
126 |                     "id": layer_id
127 |                 }
128 |             }
129 | 
130 |         except Exception as e:
131 |             logger.error(f"Error setting active layer: {str(e)}")
132 |             return {
133 |                 "success": False,
134 |                 "message": "Failed to set active layer",
135 |                 "errorDetails": str(e)
136 |             }
137 | 
138 |     def get_layer_list(self, params: Dict[str, Any]) -> Dict[str, Any]:
139 |         """Get a list of all layers in the PCB"""
140 |         try:
141 |             if not self.board:
142 |                 return {
143 |                     "success": False,
144 |                     "message": "No board is loaded",
145 |                     "errorDetails": "Load or create a board first"
146 |                 }
147 | 
148 |             layers = []
149 |             for layer_id in range(pcbnew.PCB_LAYER_ID_COUNT):
150 |                 if self.board.IsLayerEnabled(layer_id):
151 |                     layers.append({
152 |                         "name": self.board.GetLayerName(layer_id),
153 |                         "type": self._get_layer_type_name(self.board.GetLayerType(layer_id)),
154 |                         "id": layer_id
155 |                         # Note: isActive removed - GetActiveLayer() doesn't exist in KiCAD 9.0
156 |                         # Active layer is a UI concept not applicable to headless scripting
157 |                     })
158 | 
159 |             return {
160 |                 "success": True,
161 |                 "layers": layers
162 |             }
163 | 
164 |         except Exception as e:
165 |             logger.error(f"Error getting layer list: {str(e)}")
166 |             return {
167 |                 "success": False,
168 |                 "message": "Failed to get layer list",
169 |                 "errorDetails": str(e)
170 |             }
171 |     
172 |     def _get_layer_type(self, type_name: str) -> int:
173 |         """Convert layer type name to KiCAD layer type constant"""
174 |         type_map = {
175 |             "copper": pcbnew.LT_SIGNAL,
176 |             "technical": pcbnew.LT_SIGNAL,
177 |             "user": pcbnew.LT_SIGNAL,  # LT_USER removed in KiCAD 9.0, use LT_SIGNAL instead
178 |             "signal": pcbnew.LT_SIGNAL
179 |         }
180 |         return type_map.get(type_name.lower(), pcbnew.LT_SIGNAL)
181 | 
182 |     def _get_layer_type_name(self, type_id: int) -> str:
183 |         """Convert KiCAD layer type constant to name"""
184 |         type_map = {
185 |             pcbnew.LT_SIGNAL: "signal",
186 |             pcbnew.LT_POWER: "power",
187 |             pcbnew.LT_MIXED: "mixed",
188 |             pcbnew.LT_JUMPER: "jumper"
189 |         }
190 |         # Note: LT_USER was removed in KiCAD 9.0
191 |         return type_map.get(type_id, "unknown")
192 | 
```

--------------------------------------------------------------------------------
/python/kicad_api/swig_backend.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | SWIG Backend (Legacy - DEPRECATED)
  3 | 
  4 | Uses the legacy SWIG-based pcbnew Python bindings.
  5 | This backend wraps the existing implementation for backward compatibility.
  6 | 
  7 | WARNING: SWIG bindings are deprecated as of KiCAD 9.0
  8 |          and will be removed in KiCAD 10.0.
  9 |          Please migrate to IPC backend.
 10 | """
 11 | import logging
 12 | from pathlib import Path
 13 | from typing import Optional, Dict, Any, List
 14 | 
 15 | from kicad_api.base import (
 16 |     KiCADBackend,
 17 |     BoardAPI,
 18 |     ConnectionError,
 19 |     APINotAvailableError
 20 | )
 21 | 
 22 | logger = logging.getLogger(__name__)
 23 | 
 24 | 
 25 | class SWIGBackend(KiCADBackend):
 26 |     """
 27 |     Legacy SWIG-based backend
 28 | 
 29 |     Wraps existing commands/project.py, commands/component.py, etc.
 30 |     for compatibility during migration period.
 31 |     """
 32 | 
 33 |     def __init__(self):
 34 |         self._connected = False
 35 |         self._pcbnew = None
 36 |         logger.warning(
 37 |             "⚠️ Using DEPRECATED SWIG backend. "
 38 |             "This will be removed in KiCAD 10.0. "
 39 |             "Please migrate to IPC API."
 40 |         )
 41 | 
 42 |     def connect(self) -> bool:
 43 |         """
 44 |         'Connect' to SWIG API (just validates pcbnew import)
 45 | 
 46 |         Returns:
 47 |             True if pcbnew module available
 48 |         """
 49 |         try:
 50 |             import pcbnew
 51 |             self._pcbnew = pcbnew
 52 |             version = pcbnew.GetBuildVersion()
 53 |             logger.info(f"✓ Connected to pcbnew (SWIG): {version}")
 54 |             self._connected = True
 55 |             return True
 56 |         except ImportError as e:
 57 |             logger.error("pcbnew module not found")
 58 |             raise APINotAvailableError(
 59 |                 "SWIG backend requires pcbnew module. "
 60 |                 "Ensure KiCAD Python module is in PYTHONPATH."
 61 |             ) from e
 62 | 
 63 |     def disconnect(self) -> None:
 64 |         """Disconnect from SWIG API (no-op)"""
 65 |         self._connected = False
 66 |         self._pcbnew = None
 67 |         logger.info("Disconnected from SWIG backend")
 68 | 
 69 |     def is_connected(self) -> bool:
 70 |         """Check if connected"""
 71 |         return self._connected
 72 | 
 73 |     def get_version(self) -> str:
 74 |         """Get KiCAD version"""
 75 |         if not self.is_connected():
 76 |             raise ConnectionError("Not connected")
 77 | 
 78 |         return self._pcbnew.GetBuildVersion()
 79 | 
 80 |     # Project Operations
 81 |     def create_project(self, path: Path, name: str) -> Dict[str, Any]:
 82 |         """Create project using existing SWIG implementation"""
 83 |         if not self.is_connected():
 84 |             raise ConnectionError("Not connected")
 85 | 
 86 |         # Import existing implementation
 87 |         from commands.project import ProjectCommands
 88 | 
 89 |         try:
 90 |             result = ProjectCommands.create_project(str(path), name)
 91 |             return result
 92 |         except Exception as e:
 93 |             logger.error(f"Failed to create project: {e}")
 94 |             raise
 95 | 
 96 |     def open_project(self, path: Path) -> Dict[str, Any]:
 97 |         """Open project using existing SWIG implementation"""
 98 |         if not self.is_connected():
 99 |             raise ConnectionError("Not connected")
100 | 
101 |         from commands.project import ProjectCommands
102 | 
103 |         try:
104 |             result = ProjectCommands.open_project(str(path))
105 |             return result
106 |         except Exception as e:
107 |             logger.error(f"Failed to open project: {e}")
108 |             raise
109 | 
110 |     def save_project(self, path: Optional[Path] = None) -> Dict[str, Any]:
111 |         """Save project using existing SWIG implementation"""
112 |         if not self.is_connected():
113 |             raise ConnectionError("Not connected")
114 | 
115 |         from commands.project import ProjectCommands
116 | 
117 |         try:
118 |             path_str = str(path) if path else None
119 |             result = ProjectCommands.save_project(path_str)
120 |             return result
121 |         except Exception as e:
122 |             logger.error(f"Failed to save project: {e}")
123 |             raise
124 | 
125 |     def close_project(self) -> None:
126 |         """Close project (SWIG doesn't have explicit close)"""
127 |         logger.info("Closing project (SWIG backend)")
128 |         # SWIG backend doesn't maintain project state,
129 |         # so this is essentially a no-op
130 | 
131 |     # Board Operations
132 |     def get_board(self) -> BoardAPI:
133 |         """Get board API"""
134 |         if not self.is_connected():
135 |             raise ConnectionError("Not connected")
136 | 
137 |         return SWIGBoardAPI(self._pcbnew)
138 | 
139 | 
140 | class SWIGBoardAPI(BoardAPI):
141 |     """Board API implementation wrapping SWIG/pcbnew"""
142 | 
143 |     def __init__(self, pcbnew_module):
144 |         self.pcbnew = pcbnew_module
145 |         self._board = None
146 | 
147 |     def set_size(self, width: float, height: float, unit: str = "mm") -> bool:
148 |         """Set board size using existing implementation"""
149 |         from commands.board import BoardCommands
150 | 
151 |         try:
152 |             result = BoardCommands.set_board_size(width, height, unit)
153 |             return result.get("success", False)
154 |         except Exception as e:
155 |             logger.error(f"Failed to set board size: {e}")
156 |             return False
157 | 
158 |     def get_size(self) -> Dict[str, float]:
159 |         """Get board size"""
160 |         # TODO: Implement using existing SWIG code
161 |         raise NotImplementedError("get_size not yet wrapped")
162 | 
163 |     def add_layer(self, layer_name: str, layer_type: str) -> bool:
164 |         """Add layer using existing implementation"""
165 |         from commands.board import BoardCommands
166 | 
167 |         try:
168 |             result = BoardCommands.add_layer(layer_name, layer_type)
169 |             return result.get("success", False)
170 |         except Exception as e:
171 |             logger.error(f"Failed to add layer: {e}")
172 |             return False
173 | 
174 |     def list_components(self) -> List[Dict[str, Any]]:
175 |         """List components using existing implementation"""
176 |         from commands.component import ComponentCommands
177 | 
178 |         try:
179 |             result = ComponentCommands.get_component_list()
180 |             if result.get("success"):
181 |                 return result.get("components", [])
182 |             return []
183 |         except Exception as e:
184 |             logger.error(f"Failed to list components: {e}")
185 |             return []
186 | 
187 |     def place_component(
188 |         self,
189 |         reference: str,
190 |         footprint: str,
191 |         x: float,
192 |         y: float,
193 |         rotation: float = 0,
194 |         layer: str = "F.Cu"
195 |     ) -> bool:
196 |         """Place component using existing implementation"""
197 |         from commands.component import ComponentCommands
198 | 
199 |         try:
200 |             result = ComponentCommands.place_component(
201 |                 component_id=footprint,
202 |                 position={"x": x, "y": y, "unit": "mm"},
203 |                 reference=reference,
204 |                 rotation=rotation,
205 |                 layer=layer
206 |             )
207 |             return result.get("success", False)
208 |         except Exception as e:
209 |             logger.error(f"Failed to place component: {e}")
210 |             return False
211 | 
212 | 
213 | # This backend serves as a wrapper during the migration period.
214 | # Once IPC backend is fully implemented, this can be deprecated.
215 | 
```

--------------------------------------------------------------------------------
/python/kicad_api/base.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Abstract base class for KiCAD API backends
  3 | 
  4 | Defines the interface that all KiCAD backends must implement.
  5 | """
  6 | from abc import ABC, abstractmethod
  7 | from pathlib import Path
  8 | from typing import Optional, Dict, Any, List
  9 | import logging
 10 | 
 11 | logger = logging.getLogger(__name__)
 12 | 
 13 | 
 14 | class KiCADBackend(ABC):
 15 |     """Abstract base class for KiCAD API backends"""
 16 | 
 17 |     @abstractmethod
 18 |     def connect(self) -> bool:
 19 |         """
 20 |         Connect to KiCAD
 21 | 
 22 |         Returns:
 23 |             True if connection successful, False otherwise
 24 |         """
 25 |         pass
 26 | 
 27 |     @abstractmethod
 28 |     def disconnect(self) -> None:
 29 |         """Disconnect from KiCAD and clean up resources"""
 30 |         pass
 31 | 
 32 |     @abstractmethod
 33 |     def is_connected(self) -> bool:
 34 |         """
 35 |         Check if currently connected to KiCAD
 36 | 
 37 |         Returns:
 38 |             True if connected, False otherwise
 39 |         """
 40 |         pass
 41 | 
 42 |     @abstractmethod
 43 |     def get_version(self) -> str:
 44 |         """
 45 |         Get KiCAD version
 46 | 
 47 |         Returns:
 48 |             Version string (e.g., "9.0.0")
 49 |         """
 50 |         pass
 51 | 
 52 |     # Project Operations
 53 |     @abstractmethod
 54 |     def create_project(self, path: Path, name: str) -> Dict[str, Any]:
 55 |         """
 56 |         Create a new KiCAD project
 57 | 
 58 |         Args:
 59 |             path: Directory path for the project
 60 |             name: Project name
 61 | 
 62 |         Returns:
 63 |             Dictionary with project info
 64 |         """
 65 |         pass
 66 | 
 67 |     @abstractmethod
 68 |     def open_project(self, path: Path) -> Dict[str, Any]:
 69 |         """
 70 |         Open an existing KiCAD project
 71 | 
 72 |         Args:
 73 |             path: Path to .kicad_pro file
 74 | 
 75 |         Returns:
 76 |             Dictionary with project info
 77 |         """
 78 |         pass
 79 | 
 80 |     @abstractmethod
 81 |     def save_project(self, path: Optional[Path] = None) -> Dict[str, Any]:
 82 |         """
 83 |         Save the current project
 84 | 
 85 |         Args:
 86 |             path: Optional new path to save to
 87 | 
 88 |         Returns:
 89 |             Dictionary with save status
 90 |         """
 91 |         pass
 92 | 
 93 |     @abstractmethod
 94 |     def close_project(self) -> None:
 95 |         """Close the current project"""
 96 |         pass
 97 | 
 98 |     # Board Operations
 99 |     @abstractmethod
100 |     def get_board(self) -> 'BoardAPI':
101 |         """
102 |         Get board API for current project
103 | 
104 |         Returns:
105 |             BoardAPI instance
106 |         """
107 |         pass
108 | 
109 | 
110 | class BoardAPI(ABC):
111 |     """Abstract interface for board operations"""
112 | 
113 |     @abstractmethod
114 |     def set_size(self, width: float, height: float, unit: str = "mm") -> bool:
115 |         """
116 |         Set board size
117 | 
118 |         Args:
119 |             width: Board width
120 |             height: Board height
121 |             unit: Unit of measurement ("mm" or "in")
122 | 
123 |         Returns:
124 |             True if successful
125 |         """
126 |         pass
127 | 
128 |     @abstractmethod
129 |     def get_size(self) -> Dict[str, float]:
130 |         """
131 |         Get current board size
132 | 
133 |         Returns:
134 |             Dictionary with width, height, unit
135 |         """
136 |         pass
137 | 
138 |     @abstractmethod
139 |     def add_layer(self, layer_name: str, layer_type: str) -> bool:
140 |         """
141 |         Add a layer to the board
142 | 
143 |         Args:
144 |             layer_name: Name of the layer
145 |             layer_type: Type ("copper", "technical", "user")
146 | 
147 |         Returns:
148 |             True if successful
149 |         """
150 |         pass
151 | 
152 |     @abstractmethod
153 |     def list_components(self) -> List[Dict[str, Any]]:
154 |         """
155 |         List all components on the board
156 | 
157 |         Returns:
158 |             List of component dictionaries
159 |         """
160 |         pass
161 | 
162 |     @abstractmethod
163 |     def place_component(
164 |         self,
165 |         reference: str,
166 |         footprint: str,
167 |         x: float,
168 |         y: float,
169 |         rotation: float = 0,
170 |         layer: str = "F.Cu"
171 |     ) -> bool:
172 |         """
173 |         Place a component on the board
174 | 
175 |         Args:
176 |             reference: Component reference (e.g., "R1")
177 |             footprint: Footprint library path
178 |             x: X position (mm)
179 |             y: Y position (mm)
180 |             rotation: Rotation angle (degrees)
181 |             layer: Layer name
182 | 
183 |         Returns:
184 |             True if successful
185 |         """
186 |         pass
187 | 
188 |     # Routing Operations
189 |     def add_track(
190 |         self,
191 |         start_x: float,
192 |         start_y: float,
193 |         end_x: float,
194 |         end_y: float,
195 |         width: float = 0.25,
196 |         layer: str = "F.Cu",
197 |         net_name: Optional[str] = None
198 |     ) -> bool:
199 |         """
200 |         Add a track (trace) to the board
201 | 
202 |         Args:
203 |             start_x: Start X position (mm)
204 |             start_y: Start Y position (mm)
205 |             end_x: End X position (mm)
206 |             end_y: End Y position (mm)
207 |             width: Track width (mm)
208 |             layer: Layer name
209 |             net_name: Optional net name
210 | 
211 |         Returns:
212 |             True if successful
213 |         """
214 |         raise NotImplementedError()
215 | 
216 |     def add_via(
217 |         self,
218 |         x: float,
219 |         y: float,
220 |         diameter: float = 0.8,
221 |         drill: float = 0.4,
222 |         net_name: Optional[str] = None,
223 |         via_type: str = "through"
224 |     ) -> bool:
225 |         """
226 |         Add a via to the board
227 | 
228 |         Args:
229 |             x: X position (mm)
230 |             y: Y position (mm)
231 |             diameter: Via diameter (mm)
232 |             drill: Drill diameter (mm)
233 |             net_name: Optional net name
234 |             via_type: Via type ("through", "blind", "micro")
235 | 
236 |         Returns:
237 |             True if successful
238 |         """
239 |         raise NotImplementedError()
240 | 
241 |     # Transaction support for undo/redo
242 |     def begin_transaction(self, description: str = "MCP Operation") -> None:
243 |         """Begin a transaction for grouping operations."""
244 |         pass  # Optional - not all backends support this
245 | 
246 |     def commit_transaction(self, description: str = "MCP Operation") -> None:
247 |         """Commit the current transaction."""
248 |         pass  # Optional
249 | 
250 |     def rollback_transaction(self) -> None:
251 |         """Roll back the current transaction."""
252 |         pass  # Optional
253 | 
254 |     def save(self) -> bool:
255 |         """Save the board."""
256 |         raise NotImplementedError()
257 | 
258 |     # Query operations
259 |     def get_tracks(self) -> List[Dict[str, Any]]:
260 |         """Get all tracks on the board."""
261 |         raise NotImplementedError()
262 | 
263 |     def get_vias(self) -> List[Dict[str, Any]]:
264 |         """Get all vias on the board."""
265 |         raise NotImplementedError()
266 | 
267 |     def get_nets(self) -> List[Dict[str, Any]]:
268 |         """Get all nets on the board."""
269 |         raise NotImplementedError()
270 | 
271 |     def get_selection(self) -> List[Dict[str, Any]]:
272 |         """Get currently selected items."""
273 |         raise NotImplementedError()
274 | 
275 | 
276 | class BackendError(Exception):
277 |     """Base exception for backend errors"""
278 |     pass
279 | 
280 | 
281 | class ConnectionError(BackendError):
282 |     """Raised when connection to KiCAD fails"""
283 |     pass
284 | 
285 | 
286 | class APINotAvailableError(BackendError):
287 |     """Raised when required API is not available"""
288 |     pass
289 | 
```

--------------------------------------------------------------------------------
/docs/IPC_BACKEND_STATUS.md:
--------------------------------------------------------------------------------

```markdown
  1 | # KiCAD IPC Backend Implementation Status
  2 | 
  3 | **Status:** Under Active Development and Testing
  4 | **Date:** 2025-12-02
  5 | **KiCAD Version:** 9.0.6
  6 | **kicad-python Version:** 0.5.0
  7 | 
  8 | ---
  9 | 
 10 | ## Overview
 11 | 
 12 | The IPC backend provides real-time UI synchronization with KiCAD 9.0+ via the official IPC API. When KiCAD is running with IPC enabled, commands can update the KiCAD UI immediately without requiring manual reload.
 13 | 
 14 | This feature is experimental and under active testing. The server uses a hybrid approach: IPC when available, automatic fallback to SWIG when IPC is not connected.
 15 | 
 16 | ## Key Differences
 17 | 
 18 | | Feature | SWIG | IPC |
 19 | |---------|------|-----|
 20 | | UI Updates | Manual reload required | Immediate (when working) |
 21 | | Undo/Redo | Not supported | Transaction support |
 22 | | API Stability | Deprecated in KiCAD 9 | Official, versioned |
 23 | | Connection | File-based | Live socket connection |
 24 | | KiCAD Required | No (file operations) | Yes (must be running) |
 25 | 
 26 | ## Implemented IPC Commands
 27 | 
 28 | The following MCP commands have IPC handlers:
 29 | 
 30 | | Command | IPC Handler | Status |
 31 | |---------|-------------|--------|
 32 | | `route_trace` | `_ipc_route_trace` | Implemented |
 33 | | `add_via` | `_ipc_add_via` | Implemented |
 34 | | `add_net` | `_ipc_add_net` | Implemented |
 35 | | `delete_trace` | `_ipc_delete_trace` | Falls back to SWIG |
 36 | | `get_nets_list` | `_ipc_get_nets_list` | Implemented |
 37 | | `add_copper_pour` | `_ipc_add_copper_pour` | Implemented |
 38 | | `refill_zones` | `_ipc_refill_zones` | Implemented |
 39 | | `add_text` | `_ipc_add_text` | Implemented |
 40 | | `add_board_text` | `_ipc_add_text` | Implemented |
 41 | | `set_board_size` | `_ipc_set_board_size` | Implemented |
 42 | | `get_board_info` | `_ipc_get_board_info` | Implemented |
 43 | | `add_board_outline` | `_ipc_add_board_outline` | Implemented |
 44 | | `add_mounting_hole` | `_ipc_add_mounting_hole` | Implemented |
 45 | | `get_layer_list` | `_ipc_get_layer_list` | Implemented |
 46 | | `place_component` | `_ipc_place_component` | Implemented (hybrid) |
 47 | | `move_component` | `_ipc_move_component` | Implemented |
 48 | | `rotate_component` | `_ipc_rotate_component` | Implemented |
 49 | | `delete_component` | `_ipc_delete_component` | Implemented |
 50 | | `get_component_list` | `_ipc_get_component_list` | Implemented |
 51 | | `get_component_properties` | `_ipc_get_component_properties` | Implemented |
 52 | | `save_project` | `_ipc_save_project` | Implemented |
 53 | 
 54 | ### Implemented Backend Features
 55 | 
 56 | **Core Connection:**
 57 | - Connect to running KiCAD instance
 58 | - Auto-detect socket path (`/tmp/kicad/api.sock`)
 59 | - Version checking and validation
 60 | - Auto-fallback to SWIG when IPC unavailable
 61 | - Change notification callbacks
 62 | 
 63 | **Board Operations:**
 64 | - Get board reference
 65 | - Get/Set board size
 66 | - List enabled layers
 67 | - Save board
 68 | - Add board outline segments
 69 | - Add mounting holes
 70 | 
 71 | **Component Operations:**
 72 | - List all components
 73 | - Place component (hybrid: SWIG for library loading, IPC for placement)
 74 | - Move component
 75 | - Rotate component
 76 | - Delete component
 77 | - Get component properties
 78 | 
 79 | **Routing Operations:**
 80 | - Add track
 81 | - Add via
 82 | - Get all tracks
 83 | - Get all vias
 84 | - Get all nets
 85 | 
 86 | **Zone Operations:**
 87 | - Add copper pour zones
 88 | - Get zones list
 89 | - Refill zones
 90 | 
 91 | **UI Integration:**
 92 | - Add text to board
 93 | - Get current selection
 94 | - Clear selection
 95 | 
 96 | **Transaction Support:**
 97 | - Begin transaction
 98 | - Commit transaction (with description for undo)
 99 | - Rollback transaction
100 | 
101 | ## Usage
102 | 
103 | ### Prerequisites
104 | 
105 | 1. **KiCAD 9.0+** must be running
106 | 2. **IPC API must be enabled**: `Preferences > Plugins > Enable IPC API Server`
107 | 3. A board must be open in the PCB editor
108 | 
109 | ### Installation
110 | 
111 | ```bash
112 | pip install kicad-python
113 | ```
114 | 
115 | ### Testing
116 | 
117 | Run the test script to verify IPC functionality:
118 | 
119 | ```bash
120 | # Make sure KiCAD is running with IPC enabled and a board open
121 | ./venv/bin/python python/test_ipc_backend.py
122 | ```
123 | 
124 | ## Architecture
125 | 
126 | ```
127 | +-------------------------------------------------------------+
128 | |              MCP Server (TypeScript/Node.js)                |
129 | +---------------------------+---------------------------------+
130 |                             | JSON commands
131 | +---------------------------v---------------------------------+
132 | |              Python Interface Layer                         |
133 | |  +--------------------------------------------------------+ |
134 | |  |  kicad_interface.py                                    | |
135 | |  |  - Routes commands to IPC or SWIG handlers             | |
136 | |  |  - IPC_CAPABLE_COMMANDS dict defines routing           | |
137 | |  +--------------------------------------------------------+ |
138 | |  +--------------------------------------------------------+ |
139 | |  |  kicad_api/ipc_backend.py                              | |
140 | |  |  - IPCBackend (connection management)                  | |
141 | |  |  - IPCBoardAPI (board operations)                      | |
142 | |  +--------------------------------------------------------+ |
143 | +---------------------------+---------------------------------+
144 |                             | kicad-python (kipy) library
145 | +---------------------------v---------------------------------+
146 | |          Protocol Buffers over UNIX Sockets                 |
147 | +---------------------------+---------------------------------+
148 |                             |
149 | +---------------------------v---------------------------------+
150 | |              KiCAD 9.0+ (IPC Server)                        |
151 | +-------------------------------------------------------------+
152 | ```
153 | 
154 | ## Known Limitations
155 | 
156 | 1. **KiCAD must be running**: Unlike SWIG, IPC requires KiCAD to be open
157 | 2. **Project creation**: Not supported via IPC, uses file system
158 | 3. **Footprint library access**: Uses hybrid approach (SWIG loads from library, IPC places)
159 | 4. **Delete trace**: Falls back to SWIG (IPC API doesn't support direct deletion)
160 | 5. **Some operations may not work as expected**: This is experimental code
161 | 
162 | ## Troubleshooting
163 | 
164 | ### "Connection failed"
165 | - Ensure KiCAD is running
166 | - Enable IPC API: `Preferences > Plugins > Enable IPC API Server`
167 | - Check if a board is open
168 | 
169 | ### "kicad-python not found"
170 | ```bash
171 | pip install kicad-python
172 | ```
173 | 
174 | ### "Version mismatch"
175 | - Update kicad-python: `pip install --upgrade kicad-python`
176 | - Ensure KiCAD 9.0+ is installed
177 | 
178 | ### "No board open"
179 | - Open a board in KiCAD's PCB editor before connecting
180 | 
181 | ## File Structure
182 | 
183 | ```
184 | python/kicad_api/
185 | ├── __init__.py          # Package exports
186 | ├── base.py              # Abstract base classes
187 | ├── factory.py           # Backend auto-detection
188 | ├── ipc_backend.py       # IPC implementation
189 | └── swig_backend.py      # Legacy SWIG wrapper
190 | 
191 | python/
192 | └── test_ipc_backend.py  # IPC test script
193 | ```
194 | 
195 | ## Future Work
196 | 
197 | 1. More comprehensive testing of all IPC commands
198 | 2. Footprint library integration via IPC (when kipy supports it)
199 | 3. Schematic IPC support (when available in kicad-python)
200 | 4. Event subscriptions to react to changes made in KiCAD UI
201 | 5. Multi-board support
202 | 
203 | ## Related Documentation
204 | 
205 | - [ROADMAP.md](./ROADMAP.md) - Project roadmap
206 | - [IPC_API_MIGRATION_PLAN.md](./IPC_API_MIGRATION_PLAN.md) - Migration details
207 | - [REALTIME_WORKFLOW.md](./REALTIME_WORKFLOW.md) - Collaboration workflows
208 | - [kicad-python docs](https://docs.kicad.org/kicad-python-main/) - Official API docs
209 | 
210 | ---
211 | 
212 | **Last Updated:** 2025-12-02
213 | 
```

--------------------------------------------------------------------------------
/python/commands/component_schematic.py:
--------------------------------------------------------------------------------

```python
  1 | from skip import Schematic
  2 | # Symbol class might not be directly importable in the current version
  3 | import os
  4 | 
  5 | class ComponentManager:
  6 |     """Manage components in a schematic"""
  7 | 
  8 |     @staticmethod
  9 |     def add_component(schematic: Schematic, component_def: dict):
 10 |         """Add a component to the schematic"""
 11 |         try:
 12 |             # Create a new symbol
 13 |             symbol = schematic.add_symbol(
 14 |                 lib=component_def.get('library', 'Device'),
 15 |                 name=component_def.get('type', 'R'), # Default to Resistor symbol 'R'
 16 |                 reference=component_def.get('reference', 'R?'),
 17 |                 at=[component_def.get('x', 0), component_def.get('y', 0)],
 18 |                 unit=component_def.get('unit', 1),
 19 |                 rotation=component_def.get('rotation', 0)
 20 |             )
 21 | 
 22 |             # Set properties
 23 |             if 'value' in component_def:
 24 |                 symbol.property.Value.value = component_def['value']
 25 |             if 'footprint' in component_def:
 26 |                 symbol.property.Footprint.value = component_def['footprint']
 27 |             if 'datasheet' in component_def:
 28 |                  symbol.property.Datasheet.value = component_def['datasheet']
 29 | 
 30 |             # Add additional properties
 31 |             for key, value in component_def.get('properties', {}).items():
 32 |                 # Avoid overwriting standard properties unless explicitly intended
 33 |                 if key not in ['Reference', 'Value', 'Footprint', 'Datasheet']:
 34 |                     symbol.property.append(key, value)
 35 | 
 36 |             print(f"Added component {symbol.reference} ({symbol.name}) to schematic.")
 37 |             return symbol
 38 |         except Exception as e:
 39 |             print(f"Error adding component: {e}")
 40 |             return None
 41 | 
 42 |     @staticmethod
 43 |     def remove_component(schematic: Schematic, component_ref: str):
 44 |         """Remove a component from the schematic by reference designator"""
 45 |         try:
 46 |             # kicad-skip doesn't have a direct remove_symbol method by reference.
 47 |             # We need to find the symbol and then remove it from the symbols list.
 48 |             symbol_to_remove = None
 49 |             for symbol in schematic.symbol:
 50 |                 if symbol.reference == component_ref:
 51 |                     symbol_to_remove = symbol
 52 |                     break
 53 | 
 54 |             if symbol_to_remove:
 55 |                 schematic.symbol.remove(symbol_to_remove)
 56 |                 print(f"Removed component {component_ref} from schematic.")
 57 |                 return True
 58 |             else:
 59 |                 print(f"Component with reference {component_ref} not found.")
 60 |                 return False
 61 |         except Exception as e:
 62 |             print(f"Error removing component {component_ref}: {e}")
 63 |             return False
 64 | 
 65 | 
 66 |     @staticmethod
 67 |     def update_component(schematic: Schematic, component_ref: str, new_properties: dict):
 68 |         """Update component properties by reference designator"""
 69 |         try:
 70 |             symbol_to_update = None
 71 |             for symbol in schematic.symbol:
 72 |                 if symbol.reference == component_ref:
 73 |                     symbol_to_update = symbol
 74 |                     break
 75 | 
 76 |             if symbol_to_update:
 77 |                 for key, value in new_properties.items():
 78 |                     if key in symbol_to_update.property:
 79 |                         symbol_to_update.property[key].value = value
 80 |                     else:
 81 |                          # Add as a new property if it doesn't exist
 82 |                          symbol_to_update.property.append(key, value)
 83 |                 print(f"Updated properties for component {component_ref}.")
 84 |                 return True
 85 |             else:
 86 |                 print(f"Component with reference {component_ref} not found.")
 87 |                 return False
 88 |         except Exception as e:
 89 |             print(f"Error updating component {component_ref}: {e}")
 90 |             return False
 91 | 
 92 |     @staticmethod
 93 |     def get_component(schematic: Schematic, component_ref: str):
 94 |         """Get a component by reference designator"""
 95 |         for symbol in schematic.symbol:
 96 |             if symbol.reference == component_ref:
 97 |                 print(f"Found component with reference {component_ref}.")
 98 |                 return symbol
 99 |         print(f"Component with reference {component_ref} not found.")
100 |         return None
101 | 
102 |     @staticmethod
103 |     def search_components(schematic: Schematic, query: str):
104 |         """Search for components matching criteria (basic implementation)"""
105 |         # This is a basic search, could be expanded to use regex or more complex logic
106 |         matching_components = []
107 |         query_lower = query.lower()
108 |         for symbol in schematic.symbol:
109 |             if query_lower in symbol.reference.lower() or \
110 |                query_lower in symbol.name.lower() or \
111 |                (hasattr(symbol.property, 'Value') and query_lower in symbol.property.Value.value.lower()):
112 |                 matching_components.append(symbol)
113 |         print(f"Found {len(matching_components)} components matching query '{query}'.")
114 |         return matching_components
115 | 
116 |     @staticmethod
117 |     def get_all_components(schematic: Schematic):
118 |         """Get all components in schematic"""
119 |         print(f"Retrieving all {len(schematic.symbol)} components.")
120 |         return list(schematic.symbol)
121 | 
122 | if __name__ == '__main__':
123 |     # Example Usage (for testing)
124 |     from schematic import SchematicManager # Assuming schematic.py is in the same directory
125 | 
126 |     # Create a new schematic
127 |     test_sch = SchematicManager.create_schematic("ComponentTestSchematic")
128 | 
129 |     # Add components
130 |     comp1_def = {"type": "R", "reference": "R1", "value": "10k", "x": 100, "y": 100}
131 |     comp2_def = {"type": "C", "reference": "C1", "value": "0.1uF", "x": 200, "y": 100, "library": "Device"}
132 |     comp3_def = {"type": "LED", "reference": "D1", "x": 300, "y": 100, "library": "Device", "properties": {"Color": "Red"}}
133 | 
134 |     comp1 = ComponentManager.add_component(test_sch, comp1_def)
135 |     comp2 = ComponentManager.add_component(test_sch, comp2_def)
136 |     comp3 = ComponentManager.add_component(test_sch, comp3_def)
137 | 
138 |     # Get a component
139 |     retrieved_comp = ComponentManager.get_component(test_sch, "C1")
140 |     if retrieved_comp:
141 |         print(f"Retrieved component: {retrieved_comp.reference} ({retrieved_comp.value})")
142 | 
143 |     # Update a component
144 |     ComponentManager.update_component(test_sch, "R1", {"value": "20k", "Tolerance": "5%"})
145 | 
146 |     # Search components
147 |     matching_comps = ComponentManager.search_components(test_sch, "100") # Search by position
148 |     print(f"Search results for '100': {[c.reference for c in matching_comps]}")
149 | 
150 |     # Get all components
151 |     all_comps = ComponentManager.get_all_components(test_sch)
152 |     print(f"All components: {[c.reference for c in all_comps]}")
153 | 
154 |     # Remove a component
155 |     ComponentManager.remove_component(test_sch, "D1")
156 |     all_comps_after_remove = ComponentManager.get_all_components(test_sch)
157 |     print(f"Components after removing D1: {[c.reference for c in all_comps_after_remove]}")
158 | 
159 |     # Save the schematic (optional)
160 |     # SchematicManager.save_schematic(test_sch, "component_test.kicad_sch")
161 | 
162 |     # Clean up (if saved)
163 |     # if os.path.exists("component_test.kicad_sch"):
164 |     #     os.remove("component_test.kicad_sch")
165 |     #     print("Cleaned up component_test.kicad_sch")
166 | 
```

--------------------------------------------------------------------------------
/python/commands/project.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Project-related command implementations for KiCAD interface
  3 | """
  4 | 
  5 | import os
  6 | import pcbnew  # type: ignore
  7 | import logging
  8 | from typing import Dict, Any, Optional
  9 | 
 10 | logger = logging.getLogger('kicad_interface')
 11 | 
 12 | class ProjectCommands:
 13 |     """Handles project-related KiCAD operations"""
 14 | 
 15 |     def __init__(self, board: Optional[pcbnew.BOARD] = None):
 16 |         """Initialize with optional board instance"""
 17 |         self.board = board
 18 | 
 19 |     def create_project(self, params: Dict[str, Any]) -> Dict[str, Any]:
 20 |         """Create a new KiCAD project"""
 21 |         try:
 22 |             # Accept both 'name' (from MCP tool) and 'projectName' (legacy)
 23 |             project_name = params.get("name") or params.get("projectName", "New_Project")
 24 |             path = params.get("path", os.getcwd())
 25 |             template = params.get("template")
 26 | 
 27 |             # Generate the full project path
 28 |             project_path = os.path.join(path, project_name)
 29 |             if not project_path.endswith(".kicad_pro"):
 30 |                 project_path += ".kicad_pro"
 31 | 
 32 |             # Create project directory if it doesn't exist
 33 |             os.makedirs(os.path.dirname(project_path), exist_ok=True)
 34 | 
 35 |             # Create a new board
 36 |             board = pcbnew.BOARD()
 37 |             
 38 |             # Set project properties
 39 |             board.GetTitleBlock().SetTitle(project_name)
 40 |             
 41 |             # Set current date with proper parameter
 42 |             from datetime import datetime
 43 |             current_date = datetime.now().strftime("%Y-%m-%d")
 44 |             board.GetTitleBlock().SetDate(current_date)
 45 | 
 46 |             # If template is specified, try to load it
 47 |             if template:
 48 |                 template_path = os.path.expanduser(template)
 49 |                 if os.path.exists(template_path):
 50 |                     template_board = pcbnew.LoadBoard(template_path)
 51 |                     # Copy settings from template
 52 |                     board.SetDesignSettings(template_board.GetDesignSettings())
 53 |                     board.SetLayerStack(template_board.GetLayerStack())
 54 | 
 55 |             # Save the board
 56 |             board_path = project_path.replace(".kicad_pro", ".kicad_pcb")
 57 |             board.SetFileName(board_path)
 58 |             pcbnew.SaveBoard(board_path, board)
 59 | 
 60 |             # Create project file
 61 |             with open(project_path, 'w') as f:
 62 |                 f.write('{\n')
 63 |                 f.write('  "board": {\n')
 64 |                 f.write(f'    "filename": "{os.path.basename(board_path)}"\n')
 65 |                 f.write('  }\n')
 66 |                 f.write('}\n')
 67 | 
 68 |             self.board = board
 69 | 
 70 |             return {
 71 |                 "success": True,
 72 |                 "message": f"Created project: {project_name}",
 73 |                 "project": {
 74 |                     "name": project_name,
 75 |                     "path": project_path,
 76 |                     "boardPath": board_path
 77 |                 }
 78 |             }
 79 | 
 80 |         except Exception as e:
 81 |             logger.error(f"Error creating project: {str(e)}")
 82 |             return {
 83 |                 "success": False,
 84 |                 "message": "Failed to create project",
 85 |                 "errorDetails": str(e)
 86 |             }
 87 | 
 88 |     def open_project(self, params: Dict[str, Any]) -> Dict[str, Any]:
 89 |         """Open an existing KiCAD project"""
 90 |         try:
 91 |             filename = params.get("filename")
 92 |             if not filename:
 93 |                 return {
 94 |                     "success": False,
 95 |                     "message": "No filename provided",
 96 |                     "errorDetails": "The filename parameter is required"
 97 |                 }
 98 | 
 99 |             # Expand user path and make absolute
100 |             filename = os.path.abspath(os.path.expanduser(filename))
101 | 
102 |             # If it's a project file, get the board file
103 |             if filename.endswith(".kicad_pro"):
104 |                 board_path = filename.replace(".kicad_pro", ".kicad_pcb")
105 |             else:
106 |                 board_path = filename
107 | 
108 |             # Load the board
109 |             board = pcbnew.LoadBoard(board_path)
110 |             self.board = board
111 | 
112 |             return {
113 |                 "success": True,
114 |                 "message": f"Opened project: {os.path.basename(board_path)}",
115 |                 "project": {
116 |                     "name": os.path.splitext(os.path.basename(board_path))[0],
117 |                     "path": filename,
118 |                     "boardPath": board_path
119 |                 }
120 |             }
121 | 
122 |         except Exception as e:
123 |             logger.error(f"Error opening project: {str(e)}")
124 |             return {
125 |                 "success": False,
126 |                 "message": "Failed to open project",
127 |                 "errorDetails": str(e)
128 |             }
129 | 
130 |     def save_project(self, params: Dict[str, Any]) -> Dict[str, Any]:
131 |         """Save the current KiCAD project"""
132 |         try:
133 |             if not self.board:
134 |                 return {
135 |                     "success": False,
136 |                     "message": "No board is loaded",
137 |                     "errorDetails": "Load or create a board first"
138 |                 }
139 | 
140 |             filename = params.get("filename")
141 |             if filename:
142 |                 # Save to new location
143 |                 filename = os.path.abspath(os.path.expanduser(filename))
144 |                 self.board.SetFileName(filename)
145 | 
146 |             # Save the board
147 |             pcbnew.SaveBoard(self.board.GetFileName(), self.board)
148 | 
149 |             return {
150 |                 "success": True,
151 |                 "message": f"Saved project to: {self.board.GetFileName()}",
152 |                 "project": {
153 |                     "name": os.path.splitext(os.path.basename(self.board.GetFileName()))[0],
154 |                     "path": self.board.GetFileName()
155 |                 }
156 |             }
157 | 
158 |         except Exception as e:
159 |             logger.error(f"Error saving project: {str(e)}")
160 |             return {
161 |                 "success": False,
162 |                 "message": "Failed to save project",
163 |                 "errorDetails": str(e)
164 |             }
165 | 
166 |     def get_project_info(self, params: Dict[str, Any]) -> Dict[str, Any]:
167 |         """Get information about the current project"""
168 |         try:
169 |             if not self.board:
170 |                 return {
171 |                     "success": False,
172 |                     "message": "No board is loaded",
173 |                     "errorDetails": "Load or create a board first"
174 |                 }
175 | 
176 |             title_block = self.board.GetTitleBlock()
177 |             filename = self.board.GetFileName()
178 |             
179 |             return {
180 |                 "success": True,
181 |                 "project": {
182 |                     "name": os.path.splitext(os.path.basename(filename))[0],
183 |                     "path": filename,
184 |                     "title": title_block.GetTitle(),
185 |                     "date": title_block.GetDate(),
186 |                     "revision": title_block.GetRevision(),
187 |                     "company": title_block.GetCompany(),
188 |                     "comment1": title_block.GetComment(0),
189 |                     "comment2": title_block.GetComment(1),
190 |                     "comment3": title_block.GetComment(2),
191 |                     "comment4": title_block.GetComment(3)
192 |                 }
193 |             }
194 | 
195 |         except Exception as e:
196 |             logger.error(f"Error getting project info: {str(e)}")
197 |             return {
198 |                 "success": False,
199 |                 "message": "Failed to get project information",
200 |                 "errorDetails": str(e)
201 |             }
202 | 
```
Page 1/6FirstPrevNextLast