style: apply Prettier formatting to TS/JS/JSON/MD files

Add Prettier as a dev dependency with .prettierrc.json config and
.prettierignore. Hook added via mirrors-prettier in pre-commit config.
All TypeScript, JSON, Markdown, and YAML files auto-formatted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eugene Mikhantyev
2026-03-29 13:05:50 +01:00
parent c44bd9205d
commit 7d50fa1d4c
82 changed files with 18314 additions and 17317 deletions

View File

@@ -2,9 +2,9 @@ name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
branches: [main, develop]
pull_request:
branches: [ main, develop ]
branches: [main, develop]
jobs:
# TypeScript/Node.js tests
@@ -24,7 +24,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache: "npm"
- name: Install dependencies
run: npm ci
@@ -45,7 +45,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-24.04, ubuntu-22.04]
python-version: ['3.10', '3.11', '3.12']
python-version: ["3.10", "3.11", "3.12"]
steps:
- name: Checkout code
@@ -55,7 +55,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache: "pip"
- name: Install Python dependencies
run: |
@@ -96,14 +96,14 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
node-version: "20.x"
cache: "npm"
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
python-version: "3.12"
cache: "pip"
- name: Add KiCAD PPA and Install KiCAD 9.0
run: |
@@ -158,7 +158,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
node-version: "20.x"
- name: Install dependencies
run: npm ci

View File

@@ -7,7 +7,7 @@ repos:
- id: check-yaml
- id: check-json
- id: check-added-large-files
args: ['--maxkb=500']
args: ["--maxkb=500"]
- id: check-merge-conflict
- repo: https://github.com/psf/black
@@ -22,3 +22,9 @@ repos:
hooks:
- id: isort
args: [--settings-path=pyproject.toml]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
exclude: ^(dist/|package-lock\.json|\.venv/|python/)

9
.prettierignore Normal file
View File

@@ -0,0 +1,9 @@
dist/
node_modules/
coverage.xml
htmlcov/
data/
*.kicad_*
python/
package-lock.json
.venv/

7
.prettierrc.json Normal file
View File

@@ -0,0 +1,7 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 100,
"tabWidth": 2
}

View File

@@ -71,6 +71,7 @@ enable developer features:
```
**What it does:**
- `export_gerber` automatically copies the current MCP session log into the project's
`logs/` subdirectory as `mcp_log_<timestamp>.txt`.
- `snapshot_project` copies the MCP session log into `logs/` at every checkpoint as
@@ -142,9 +143,10 @@ automatically retains a traceable record of which tools were called and in what
The comments (`;; PASSIVES`, `;; SEMICONDUCTORS`, `;; INTEGRATED CIRCUITS`, `;; CONNECTORS`,
`;; POWER/REGULATORS`, `;; MISC`, `;; TEMPLATE INSTANCES (...)`) caused KiCAD 9 to reject
every schematic created from this template with a hard parse error:
> `Expecting '(' in <file>.kicad_sch, line 8, offset 5`
**Action required for existing projects:** delete every line beginning with `;;` from any
`.kicad_sch` file created between upstream commit `b98c94b` and this fix.
> **Action required for existing projects:** delete every line beginning with `;;` from any
> `.kicad_sch` file created between upstream commit `b98c94b` and this fix.
- `add_schematic_component` / `inject_symbol_into_schematic`: symbol definition in
`lib_symbols` was never refreshed after editing via `create_symbol` / `edit_symbol`.
@@ -214,6 +216,7 @@ automatically retains a traceable record of which tools were called and in what
### New MCP Tools (TypeScript layer previously Python-only)
**Routing tools:**
- `delete_trace` - Delete traces by UUID, position or net name
- `query_traces` - Query/filter traces on the board
- `get_nets_list` - List all nets with net code and class
@@ -223,6 +226,7 @@ automatically retains a traceable record of which tools were called and in what
- `refill_zones` - Refill all copper zones ⚠️ SWIG segfault risk, prefer IPC/UI
**Component tools:**
- `get_component_pads` - Get all pad data for a component
- `get_component_list` - List all components on the board
- `get_pad_position` - Get absolute position of a specific pad
@@ -246,6 +250,7 @@ automatically retains a traceable record of which tools were called and in what
### New MCP Tools (cont.)
**Datasheet tools:**
- `get_datasheet_url` - Return LCSC datasheet PDF URL and product page URL for a given
LCSC number (e.g. `C179739``https://www.lcsc.com/datasheet/C179739.pdf`).
No API key required URL is constructed directly from the LCSC number.
@@ -257,6 +262,7 @@ automatically retains a traceable record of which tools were called and in what
Implementation: `python/commands/datasheet_manager.py` (text-based, no `skip` writes)
**Schematic tools:**
- `delete_schematic_component` - Remove a placed symbol from a `.kicad_sch` file by
reference designator (e.g. `R1`, `U3`).
@@ -280,7 +286,7 @@ automatically retains a traceable record of which tools were called and in what
direct text manipulation (parenthesis-depth tracking, same approach as PR #40).
Does not call `component_schematic.py::remove_component()` at all.
- Error message explicitly guides the user when the wrong tool is used:
*"note: this tool removes schematic symbols, use delete_component for PCB footprints"*
_"note: this tool removes schematic symbols, use delete_component for PCB footprints"_
### Additional Bug Fixes
@@ -310,6 +316,7 @@ automatically retains a traceable record of which tools were called and in what
### Phase 1: Intelligent Schematic Wiring System - Core Infrastructure
**Major Features:**
- Automatic pin location discovery with rotation support
- Smart wire routing (direct, orthogonal horizontal/vertical)
- Net label management (local, global, hierarchical)
@@ -317,24 +324,28 @@ automatically retains a traceable record of which tools were called and in what
- Professional right-angle routing
**New Components:**
- `python/commands/wire_manager.py` - S-expression wire creation engine
- `python/commands/pin_locator.py` - Intelligent pin discovery with rotation
- Updated `python/commands/connection_schematic.py` - High-level connection API
- `docs/SCHEMATIC_WIRING_PLAN.md` - Implementation roadmap
**MCP Tools Enhanced:**
- `add_schematic_wire` - Create wires with stroke customization
- `add_schematic_connection` - Auto-connect pins with routing options (NEW)
- `add_schematic_net_label` - Add labels with type and orientation control (NEW)
- `connect_to_net` - Connect pins to named nets (ENHANCED)
**Technical Implementation:**
- Rotation transformation matrix for pin coordinates
- S-expression injection for guaranteed format compliance
- Pin definition caching for performance
- Orthogonal path generation for professional schematics
**Testing:**
- End-to-end integration test: 100% passing
- MCP handler integration test: 100% passing
- Pin discovery with rotation: Verified working
@@ -345,6 +356,7 @@ automatically retains a traceable record of which tools were called and in what
### Phase 2: Power Nets & Wire Connectivity - COMPLETE
**Major Features:**
- Power symbol support (VCC, GND, +3V3, +5V, etc.) via dynamic loading
- Wire graph analysis for net connectivity tracking
- Geometric wire tracing with tolerance-based point matching
@@ -352,6 +364,7 @@ automatically retains a traceable record of which tools were called and in what
- Critical template mapping bug fixes
**Updates:**
- `connect_to_net()` - Migrated to WireManager + PinLocator
- `get_net_connections()` - Complete rewrite with geometric wire tracing
- `generate_netlist()` - Now uses wire graph analysis for connectivity
@@ -359,12 +372,14 @@ automatically retains a traceable record of which tools were called and in what
- `add_component()` - Fixed template lookup with symbol iteration
**Bug Fixes:**
- CRITICAL: Template mapping after dynamic symbol loading
- Special character handling in symbol names (+ prefix in +3V3, +5V)
- Schematic reload synchronization after S-expression injection
- Multi-format template reference detection
**Wire Graph Analysis Algorithm:**
1. Find all labels matching target net name
2. Trace wires connected to label positions (point coincidence)
3. Collect all wire endpoints and polyline segments
@@ -372,6 +387,7 @@ automatically retains a traceable record of which tools were called and in what
5. Return accurate component/pin connection pairs
**Technical Implementation:**
- Tolerance-based point matching (0.5mm for grid alignment)
- Multi-segment wire (polyline) support
- Rotation-aware pin location matching via PinLocator
@@ -379,6 +395,7 @@ automatically retains a traceable record of which tools were called and in what
- Template existence checking via symbol iteration (handles special characters)
**Testing:**
- Power symbols: 4/4 loaded (VCC, GND, +3V3, +5V)
- Components: 4/4 placed
- Connections: 8/8 created successfully
@@ -387,11 +404,13 @@ automatically retains a traceable record of which tools were called and in what
- Comprehensive integration test: 100% PASSING
**Commits:**
- `c67f400` - Updated connect_to_net to use WireManager
- `b77f008` - Fixed template mapping bug (critical)
- `a5a542b` - Implemented wire graph analysis
**Addresses:**
- Issue #26 - Schematic workflow wiring functionality (Phase 2)
---
@@ -399,6 +418,7 @@ automatically retains a traceable record of which tools were called and in what
### Phase 2: JLCPCB Integration Complete
**Major Features:**
- ✅ Complete JLCPCB parts integration via JLCSearch public API
- ✅ Access to ~100k JLCPCB parts catalog
- ✅ Real-time stock and pricing data
@@ -408,11 +428,13 @@ automatically retains a traceable record of which tools were called and in what
- ✅ Alternative part suggestions
**New Components:**
- `python/commands/jlcsearch.py` - JLCSearch API client (no auth required)
- `python/commands/jlcpcb_parts.py` - Enhanced with `import_jlcsearch_parts()`
- `docs/JLCPCB_INTEGRATION.md` - Comprehensive integration guide
**MCP Tools Available:**
- `download_jlcpcb_database` - Download full parts catalog
- `search_jlcpcb_parts` - Parametric search with filters
- `get_jlcpcb_part` - Part details + footprint suggestions
@@ -420,18 +442,21 @@ automatically retains a traceable record of which tools were called and in what
- `suggest_jlcpcb_alternatives` - Find similar/cheaper parts
**Technical Improvements:**
- SQLite database with full-text search (FTS5)
- Package-to-footprint mapping for standard SMD packages
- Price comparison and cost optimization algorithms
- HMAC-SHA256 authentication support (for official JLCPCB API)
**Testing:**
- All integration tests passing
- Database operations validated
- Live API connectivity confirmed
- End-to-end MCP tool testing complete
**Documentation:**
- Complete API reference with examples
- Package mapping tables (0402, 0603, 0805, SOT-23, etc.)
- Best practices guide
@@ -444,30 +469,36 @@ automatically retains a traceable record of which tools were called and in what
### Phase 1: Schematic Workflow Fix
**Critical Bug Fix:**
- ✅ Fixed completely broken schematic workflow (Issue #26)
- Created template-based symbol cloning approach
- All schematic tests now passing
**Root Cause:**
- kicad-skip library limitation: cannot create symbols from scratch, only clone existing ones
**Solution:**
- Template schematic with cloneable R, C, LED symbols
- Updated `create_project` to create both PCB and schematic
- Rewrote `add_schematic_component` to use `clone()` API
- Proper UUID generation and position setting
**Files Modified:**
- `python/commands/project.py` - Now creates schematic files
- `python/commands/schematic.py` - Uses template approach
- `python/commands/component_schematic.py` - Complete rewrite
**Files Created:**
- `python/templates/template_with_symbols.kicad_sch`
- `python/templates/empty.kicad_sch`
- `docs/SCHEMATIC_WORKFLOW_FIX.md`
**Testing:**
- Created comprehensive test suite
- All 7 tests passing
- KiCad CLI validation successful
@@ -479,17 +510,20 @@ automatically retains a traceable record of which tools were called and in what
### Router Pattern & Tool Organization
**Major Architecture Change:**
- Implemented tool router pattern (70% context reduction)
- 12 direct tools, 47 routed tools in 7 categories
- Smart tool discovery system
**New Router Tools:**
- `list_tool_categories` - Browse available categories
- `get_category_tools` - View tools in category
- `search_tools` - Find tools by keyword
- `execute_tool` - Run any routed tool
**Benefits:**
- Dramatically reduced AI context usage
- Maintained full functionality (64 tools)
- Improved tool discoverability
@@ -502,18 +536,21 @@ automatically retains a traceable record of which tools were called and in what
### IPC Backend Integration
**Experimental Feature:**
- KiCad 9.0 IPC API integration for real-time UI sync
- Changes appear immediately in KiCad (no manual reload)
- Hybrid backend: IPC + SWIG fallback
- 20+ commands with IPC support
**Implementation:**
- Routing operations (interactive push-and-shove)
- Component placement and modification
- Zone operations and fills
- DRC and verification
**Status:**
- Under active development
- Enable via KiCad: Preferences > Plugins > Enable IPC API Server
- Automatic fallback to SWIG when IPC unavailable
@@ -525,14 +562,17 @@ automatically retains a traceable record of which tools were called and in what
### Initial JLCPCB Integration (Local Libraries)
**Features:**
- Local JLCPCB symbol library search
- Integration with KiCad Plugin and Content Manager
- Search by LCSC part number, manufacturer, description
**Credit:**
- Contributed by [@l3wi](https://github.com/l3wi)
**Components:**
- `python/commands/symbol_library.py`
- Basic library search functionality
@@ -543,6 +583,7 @@ automatically retains a traceable record of which tools were called and in what
### Initial Release
**Core Features:**
- 64 fully-documented MCP tools
- JSON Schema validation for all tools
- 8 dynamic resources for project state
@@ -551,6 +592,7 @@ automatically retains a traceable record of which tools were called and in what
- Detailed logging
**Tool Categories:**
- Project Management (4 tools)
- Board Operations (9 tools)
- Component Management (8 tools)
@@ -562,11 +604,13 @@ automatically retains a traceable record of which tools were called and in what
- JLCPCB Integration (5 tools)
**Platform Support:**
- Linux (KiCad 7.x, 8.x, 9.x)
- Windows (KiCad 9.x)
- macOS (KiCad 9.x)
**Documentation:**
- Complete README with setup instructions
- Platform-specific guides
- Tool reference documentation
@@ -583,9 +627,11 @@ automatically retains a traceable record of which tools were called and in what
## Breaking Changes
### 2.1.0-alpha
- None (additive changes only)
### 2.0.0-alpha
- Tool execution now requires router for 47 tools
- Direct tool access limited to 12 high-frequency tools
- Schema validation stricter (catches errors earlier)
@@ -593,6 +639,7 @@ automatically retains a traceable record of which tools were called and in what
## Deprecations
### 2.1.0-alpha
- `docs/JLCPCB_USAGE_GUIDE.md` - Superseded by `docs/JLCPCB_INTEGRATION.md`
- `docs/JLCPCB_INTEGRATION_PLAN.md` - Implementation complete
@@ -601,15 +648,18 @@ automatically retains a traceable record of which tools were called and in what
### Upgrading to 2.1.0-alpha from 2.0.0-alpha
**New Dependencies:**
- No new system dependencies
- Python packages: `requests` (already in requirements.txt)
**Database Setup:**
1. Run `download_jlcpcb_database` tool (one-time, ~5-10 minutes)
2. Database created at `data/jlcpcb_parts.db`
3. Subsequent searches use local database (instant)
**API Changes:**
- All existing tools remain compatible
- 5 new JLCPCB tools available
- No breaking changes to existing functionality
@@ -617,11 +667,13 @@ automatically retains a traceable record of which tools were called and in what
### Upgrading to 2.0.0-alpha from 1.0.0
**Router Pattern:**
- Some tools now accessed via `execute_tool` instead of direct calls
- Use `list_tool_categories` to discover available tools
- Search with `search_tools` to find specific functionality
**IPC Backend (Optional):**
- Enable in KiCad: Preferences > Plugins > Enable IPC API Server
- Set `KICAD_BACKEND=ipc` environment variable
- Falls back to SWIG if unavailable

View File

@@ -209,6 +209,7 @@ git commit -m "feat: Add your feature description"
```
**Commit Message Convention:**
- `feat:` - New feature
- `fix:` - Bug fix
- `docs:` - Documentation changes
@@ -302,6 +303,7 @@ pylint python/
```
**Python Style Guidelines:**
- Use type hints for all function signatures
- Use pathlib.Path for file paths (not os.path)
- Use descriptive variable names
@@ -309,6 +311,7 @@ pylint python/
- Follow PEP 8
**Example:**
```python
from pathlib import Path
from typing import List, Optional
@@ -345,6 +348,7 @@ npx eslint src/
```
**TypeScript Style Guidelines:**
- Use interfaces for data structures
- Use async/await for asynchronous code
- Use descriptive variable names

103
README.md
View File

@@ -1,6 +1,6 @@
# Discussions. Get in here.
https://github.com/mixelpixx/KiCAD-MCP-Server/discussions/73
https://github.com/mixelpixx/KiCAD-MCP-Server/discussions/73
# KiCAD MCP Server
@@ -11,6 +11,7 @@ A Model Context Protocol (MCP) server that enables AI assistants like Claude to
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.
**Key Capabilities:**
- 122 tools across 16 categories with JSON Schema validation
- Smart tool discovery with router pattern (reduces AI context by 70%)
- 8 dynamic resources exposing project state
@@ -24,15 +25,10 @@ The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standa
- Real-time KiCAD UI integration via IPC API (experimental)
- Comprehensive error handling and logging
## Try out Arduino MCP - now you can get Claude to help in the IDE, real time!:
https://github.com/mixelpixx/arduino-ide
## What's New in v2.2.3
### New Tools: FFC/Ribbon Cable Passthrough Workflow
@@ -80,9 +76,11 @@ See [CHANGELOG](CHANGELOG.md) for the full list of changes in this release.
## What's New in v2.1.0
### Critical Schematic Workflow Fix + Complete Wiring System (Issue #26)
The schematic workflow was completely broken in previous versions - **this is now fixed AND dramatically enhanced!**
**What was broken:**
- `create_project` only created PCB files, no schematics
- `add_schematic_component` called non-existent API methods
- Schematics couldn't be created or edited at all
@@ -92,24 +90,27 @@ The schematic workflow was completely broken in previous versions - **this is no
**Complete Implementation (3 Phases):**
**Phase 1: Component Placement Foundation**
- `create_project` now creates both .kicad_pcb and .kicad_sch files
- Added pre-configured template schematics with 13 common component types
- Rewrote component placement to use proper `clone()` API
- `create_project` now creates both .kicad_pcb and .kicad_sch files
- Added pre-configured template schematics with 13 common component types
- Rewrote component placement to use proper `clone()` API
**Phase 2: Dynamic Symbol Loading (BREAKTHROUGH!)**
- **Access to ALL ~10,000 KiCad symbols** from standard libraries
- Automatic detection and dynamic loading from `.kicad_sym` library files
- Zero configuration required - just specify library and symbol name
- Seamless integration with existing MCP tools
- Full S-expression parsing and injection system
- **Access to ALL ~10,000 KiCad symbols** from standard libraries
- Automatic detection and dynamic loading from `.kicad_sym` library files
- Zero configuration required - just specify library and symbol name
- Seamless integration with existing MCP tools
- Full S-expression parsing and injection system
**Phase 3: Intelligent Wiring System (NEW in v2.1.0)**
- **Automatic pin location discovery** with rotation support (0°, 90°, 180°, 270°)
- **Smart wire routing** (direct, orthogonal horizontal-first, orthogonal vertical-first)
- **Power symbol support** (VCC, GND, +3V3, +5V, etc.)
- **Wire graph analysis** - geometric tracing for net connectivity
- **Net label management** (local, global, hierarchical labels)
- **Netlist generation** with accurate component/pin connections
- **Automatic pin location discovery** with rotation support (0°, 90°, 180°, 270°)
- **Smart wire routing** (direct, orthogonal horizontal-first, orthogonal vertical-first)
- **Power symbol support** (VCC, GND, +3V3, +5V, etc.)
- **Wire graph analysis** - geometric tracing for net connectivity
- **Net label management** (local, global, hierarchical labels)
- **Netlist generation** with accurate component/pin connections
**Technical Architecture:**
The kicad-skip library cannot create symbols or wires from scratch. We implemented a comprehensive solution:
@@ -126,6 +127,7 @@ The kicad-skip library cannot create symbols or wires from scratch. We implement
5. **Connectivity Analysis:** Geometric wire tracing to build net connection graphs
**Example - Complete Circuit Creation:**
```python
# Load power symbols dynamically
loader.load_symbol_dynamically(sch_path, "power", "VCC")
@@ -150,6 +152,7 @@ connections = ConnectionManager.get_net_connections(sch, "VCC", sch_path)
```
**Test Results:**
- Component placement: 100% passing
- Dynamic symbol loading: 10,000+ symbols accessible
- Wire creation: 100% passing (8/8 connections in test circuit)
@@ -160,7 +163,9 @@ connections = ConnectionManager.get_net_connections(sch, "VCC", sch_path)
See [Schematic Tools Reference](docs/SCHEMATIC_TOOLS_REFERENCE.md) for the complete schematic tool documentation.
### IPC Backend (Experimental)
We are currently implementing and testing the KiCAD 9.0 IPC API for real-time UI synchronization:
- Changes made via MCP tools appear immediately in the KiCAD UI
- No manual reload required when IPC is active
- Hybrid backend: uses IPC when available, falls back to SWIG API
@@ -169,7 +174,9 @@ We are currently implementing and testing the KiCAD 9.0 IPC API for real-time UI
Note: IPC features are under active development and testing. Enable IPC in KiCAD via Preferences > Plugins > Enable IPC API Server.
### Tool Discovery & Router Pattern
We've implemented an intelligent tool router to keep AI context efficient while maintaining full functionality:
- **18 direct tools** always visible for high-frequency operations
- **65 routed tools** organized into 8 categories (board, component, export, drc, schematic, library, routing, autoroute)
- **35 additional tools** always visible (symbol/footprint creators, JLCPCB, datasheet, advanced routing)
@@ -183,16 +190,19 @@ We've implemented an intelligent tool router to keep AI context efficient while
**Usage is seamless:** Just ask naturally - "export gerber files" or "add mounting holes" - and Claude will discover and execute the appropriate tools automatically.
### NEEDS TESTING - REPORT ISSUES
### JLCPCB Parts Integration (New!)
Complete integration with JLCPCB's parts catalog, providing two complementary approaches for component selection:
**Dual-Mode Architecture:**
1. **Local Symbol Libraries** - Search JLCPCB libraries installed via KiCAD Plugin and Content Manager (contributed by [@l3wi](https://github.com/l3wi))
2. **JLCPCB API Integration** - Access the complete 2.5M+ parts catalog with real-time pricing and stock data
**Key Features:**
- Real-time pricing with quantity breaks (1+, 10+, 100+, 1000+)
- Stock availability checking
- Basic vs Extended library type identification (Basic = free assembly)
@@ -207,7 +217,9 @@ Complete integration with JLCPCB's parts catalog, providing two complementary ap
See [JLCPCB Usage Guide](docs/JLCPCB_USAGE_GUIDE.md) for detailed setup and usage instructions.
### Comprehensive Tool Schemas
Every tool now includes complete JSON Schema definitions with:
- Detailed parameter descriptions and constraints
- Input validation with type checking
- Required vs. optional parameter specifications
@@ -215,7 +227,9 @@ Every tool now includes complete JSON Schema definitions with:
- Clear documentation of what each tool does
### Resources Capability
Access project state without executing tools:
- `kicad://project/current/info` - Project metadata
- `kicad://project/current/board` - Board properties
- `kicad://project/current/components` - Component list (JSON)
@@ -226,6 +240,7 @@ Access project state without executing tools:
- `kicad://board/preview.png` - Board visualization (PNG)
### Protocol Compliance
- Updated to MCP SDK 1.21.0 (latest)
- Full JSON-RPC 2.0 support
- Proper capability negotiation
@@ -238,6 +253,7 @@ The server provides **122 tools** organized into 16 functional categories. With
For the complete tool reference with access types (direct/routed/additional), see [Tool Inventory](docs/TOOL_INVENTORY.md).
### Project Management (5 tools)
- `create_project` - Initialize new KiCAD projects
- `open_project` - Load existing project files
- `save_project` - Save current project state
@@ -245,6 +261,7 @@ For the complete tool reference with access types (direct/routed/additional), se
- `snapshot_project` - Save named checkpoint snapshot
### Board Operations (12 tools)
- `set_board_size` - Configure PCB dimensions
- `add_board_outline` - Create board edge (rectangle, circle, polygon, rounded rectangle)
- `add_layer` - Add custom layers to stack
@@ -259,6 +276,7 @@ For the complete tool reference with access types (direct/routed/additional), se
- `import_svg_logo` - Import SVG file as PCB silkscreen polygons
### Component Management (16 tools)
- `place_component` - Place single component with footprint
- `move_component` - Reposition existing component
- `rotate_component` - Rotate component by angle
@@ -277,6 +295,7 @@ For the complete tool reference with access types (direct/routed/additional), se
- `duplicate_component` - Copy existing component
### Routing (13 tools)
- `add_net` - Create electrical net
- `route_trace` - Route copper traces between XY points
- `route_pad_to_pad` - Route between pads with auto-via insertion
@@ -292,9 +311,11 @@ For the complete tool reference with access types (direct/routed/additional), se
- `copy_routing_pattern` - Replicate routing between component groups
### Schematic (27 tools)
Complete schematic workflow with dynamic symbol loading (~10,000 symbols) and intelligent wiring.
**Component Operations:**
- `add_schematic_component` - Place symbols from any KiCad library
- `delete_schematic_component` - Remove component
- `edit_schematic_component` - Edit properties and fields
@@ -305,6 +326,7 @@ Complete schematic workflow with dynamic symbol loading (~10,000 symbols) and in
- `annotate_schematic` - Auto-assign reference designators
**Wiring and Connections:**
- `add_wire` - Create wire between points
- `delete_schematic_wire` - Remove wire segment
- `add_schematic_connection` - Auto-connect pins with routing
@@ -315,6 +337,7 @@ Complete schematic workflow with dynamic symbol loading (~10,000 symbols) and in
- `get_schematic_pin_locations` - Get pin locations for component
**Analysis and Export:**
- `get_net_connections` - Trace net connectivity
- `list_schematic_nets` / `list_schematic_wires` / `list_schematic_labels`
- `create_schematic` - Create new schematic file
@@ -327,6 +350,7 @@ Complete schematic workflow with dynamic symbol loading (~10,000 symbols) and in
See [Schematic Tools Reference](docs/SCHEMATIC_TOOLS_REFERENCE.md) for details and examples.
### Design Rules / DRC (8 tools)
- `set_design_rules` / `get_design_rules` - Configure and inspect rules
- `run_drc` - Execute design rule check
- `get_drc_violations` - Get violation list by severity
@@ -334,6 +358,7 @@ See [Schematic Tools Reference](docs/SCHEMATIC_TOOLS_REFERENCE.md) for details a
- `set_layer_constraints` / `check_clearance` - Layer and clearance rules
### Export (8 tools)
- `export_gerber` - Gerber fabrication files
- `export_pdf` / `export_svg` - Documentation and vector graphics
- `export_3d` - 3D models (STEP, STL, VRML, OBJ)
@@ -343,13 +368,16 @@ See [Schematic Tools Reference](docs/SCHEMATIC_TOOLS_REFERENCE.md) for details a
- `export_vrml` - VRML 3D model
### Footprint Libraries (4 tools) and Symbol Libraries (4 tools)
- `list_libraries` / `list_symbol_libraries` - Browse available libraries
- `search_footprints` / `search_symbols` - Search across all libraries
- `list_library_footprints` / `list_library_symbols` - Browse specific library
- `get_footprint_info` / `get_symbol_info` - Detailed information
### Footprint Creator (4 tools) and Symbol Creator (4 tools)
Create custom components when existing libraries do not have what you need.
- `create_footprint` / `create_symbol` - Build from scratch with pads/pins
- `edit_footprint_pad` - Modify pad properties
- `register_footprint_library` / `register_symbol_library` - Register in lib-table
@@ -359,10 +387,12 @@ Create custom components when existing libraries do not have what you need.
See [Footprint and Symbol Creator Guide](docs/FOOTPRINT_SYMBOL_CREATOR_GUIDE.md) for details.
### Datasheet Tools (2 tools)
- `enrich_datasheets` - Auto-populate datasheet URLs using LCSC part numbers
- `get_datasheet_url` - Get LCSC datasheet URL for a component
### JLCPCB Integration (5 tools)
- `download_jlcpcb_database` - Download 2.5M+ parts catalog (one-time setup)
- `search_jlcpcb_parts` - Search with parametric filters
- `get_jlcpcb_part` - Detailed part info with pricing
@@ -370,6 +400,7 @@ See [Footprint and Symbol Creator Guide](docs/FOOTPRINT_SYMBOL_CREATOR_GUIDE.md)
- `suggest_jlcpcb_alternatives` - Find cheaper or in-stock alternatives
### Freerouting Autorouter (4 tools)
- `autoroute` - Run Freerouting autorouter (DSN export, route, SES import)
- `export_dsn` / `import_ses` - Manual Specctra DSN/SES workflow
- `check_freerouting` - Verify Java and Freerouting availability
@@ -377,6 +408,7 @@ See [Footprint and Symbol Creator Guide](docs/FOOTPRINT_SYMBOL_CREATOR_GUIDE.md)
See [Freerouting Guide](docs/FREEROUTING_GUIDE.md) for setup and usage.
### UI Management (2 tools)
- `check_kicad_ui` - Check if KiCAD is running
- `launch_kicad_ui` - Launch KiCAD application
@@ -385,6 +417,7 @@ See [Freerouting Guide](docs/FREEROUTING_GUIDE.md) for setup and usage.
### Required Software
**KiCAD 9.0 or Higher**
- Download from [kicad.org/download](https://www.kicad.org/download/)
- Must include Python module (pcbnew)
- Verify installation:
@@ -393,10 +426,12 @@ See [Freerouting Guide](docs/FREEROUTING_GUIDE.md) for setup and usage.
```
**Node.js 18 or Higher**
- Download from [nodejs.org](https://nodejs.org/)
- Verify: `node --version` and `npm --version`
**Python 3.10 or Higher**
- Usually included with KiCAD
- Required packages (auto-installed):
- kicad-python (kipy) >= 0.5.0 (IPC API support, optional but recommended)
@@ -410,11 +445,13 @@ See [Freerouting Guide](docs/FREEROUTING_GUIDE.md) for setup and usage.
**MCP Client**
Choose one:
- [Claude Desktop](https://claude.ai/download) - Official Anthropic desktop app
- [Claude Code](https://docs.claude.com/claude-code) - Official CLI tool
- [Cline](https://github.com/cline/cline) - VSCode extension
### Supported Platforms
- **Linux** (Ubuntu 22.04+, Fedora, Arch) - Primary platform, fully tested
- **Windows 10/11** - Fully supported with automated setup
- **macOS** - Experimental support
@@ -447,6 +484,7 @@ python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())"
### Windows 10/11
**Automated Setup (Recommended):**
```powershell
git clone https://github.com/mixelpixx/KiCAD-MCP-Server.git
cd KiCAD-MCP-Server
@@ -454,6 +492,7 @@ cd KiCAD-MCP-Server
```
The script will:
- Detect KiCAD installation
- Verify prerequisites
- Install dependencies
@@ -497,10 +536,12 @@ npm run build
### Claude Desktop
Edit configuration file:
- **Linux/macOS:** `~/.config/Claude/claude_desktop_config.json`
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
**Configuration:**
```json
{
"mcpServers": {
@@ -517,6 +558,7 @@ Edit configuration file:
```
**Platform-specific PYTHONPATH:**
- **Linux:** `/usr/lib/kicad/lib/python3/dist-packages`
- **Windows:** `C:\Program Files\KiCad\9.0\lib\python3\dist-packages`
- **macOS:** `/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages`
@@ -553,6 +595,7 @@ If you see "Python executable not found: python3", you can manually specify the
```
To find your Python path:
```bash
which python3 # Example output: /usr/bin/python3
python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())" # Verify pcbnew access
@@ -575,12 +618,14 @@ The JLCPCB integration provides two modes that can be used independently or toge
**Mode 1: JLCSearch Public API (Recommended - No Setup Required)**
The easiest way to access JLCPCB's parts catalog:
- No API credentials needed
- No JLCPCB account required
- Access to 2.5M+ parts with pricing and stock data
- Download time: 40-60 minutes for full catalog (100-part batches due to API limit)
To download the database:
```
Ask Claude: "Download the JLCPCB parts database"
```
@@ -590,6 +635,7 @@ This creates a local SQLite database at `data/jlcpcb_parts.db` (3-5 GB for full
**Mode 2: Local Symbol Libraries (No Setup Required)**
Install JLCPCB libraries via KiCAD's Plugin and Content Manager:
1. Open KiCAD
2. Go to Tools > Plugin and Content Manager
3. Search for "JLCPCB" or "JLC"
@@ -609,12 +655,14 @@ For users with JLCPCB enterprise accounts and order history:
2. **Configure Environment Variables**
Add to your shell profile (`~/.bashrc`, `~/.zshrc`, or `~/.profile`):
```bash
export JLCPCB_API_KEY="your_app_key_here"
export JLCPCB_API_SECRET="your_app_secret_here"
```
Or create a `.env` file in the project root:
```
JLCPCB_API_KEY=your_app_key_here
JLCPCB_API_SECRET=your_app_secret_here
@@ -753,6 +801,7 @@ How many Basic parts are available?
## Architecture
### MCP Protocol Layer
- **JSON-RPC 2.0 Transport:** Bi-directional communication via STDIO
- **Protocol Version:** MCP 2025-06-18
- **Capabilities:** Tools (122), Resources (8)
@@ -760,6 +809,7 @@ How many Basic parts are available?
- **Error Handling:** Standard JSON-RPC error codes
### TypeScript Server (`src/`)
- Implements MCP protocol specification
- Manages Python subprocess lifecycle
- Handles message routing and validation
@@ -770,6 +820,7 @@ How many Basic parts are available?
- Reduces AI context usage by 70% while maintaining full functionality
### Python Interface (`python/`)
- **kicad_interface.py:** Main entry point, MCP message handler, command routing
- **kicad_api/:** Backend implementations
- `base.py` - Abstract base classes for backends
@@ -792,6 +843,7 @@ How many Basic parts are available?
- `jlcpcb_parts.py` - JLCPCB parts database manager
### KiCAD Integration
- **pcbnew API (SWIG):** Direct Python bindings to KiCAD for file operations
- **IPC API (kipy):** Real-time communication with running KiCAD instance (experimental)
- **Hybrid Backend:** Automatically uses IPC when available, falls back to SWIG
@@ -845,6 +897,7 @@ npm run format
**Symptoms:** MCP server doesn't show up in Claude Desktop or Cline
**Solutions:**
1. Verify build completed: `ls dist/index.js`
2. Check configuration paths are absolute
3. Restart MCP client completely
@@ -855,6 +908,7 @@ npm run format
**Symptoms:** `ModuleNotFoundError: No module named 'pcbnew'`
**Solutions:**
1. Verify KiCAD installation: `python3 -c "import pcbnew"`
2. Check PYTHONPATH in configuration matches your KiCAD installation
3. Ensure KiCAD was installed with Python support
@@ -864,6 +918,7 @@ npm run format
**Symptoms:** Tools fail with unclear errors
**Solutions:**
1. Check server logs: `~/.kicad-mcp/logs/kicad_interface.log`
2. Verify a project is loaded before running board operations
3. Ensure file paths are absolute, not relative
@@ -874,6 +929,7 @@ npm run format
**Symptoms:** Server fails to start on Windows
**Solutions:**
1. Run automated diagnostics: `.\setup-windows.ps1`
2. Verify Python path uses double backslashes: `C:\\Program Files\\KiCad\\9.0`
3. Check Windows Event Viewer for Node.js errors
@@ -897,6 +953,7 @@ npm run format
See [STATUS_SUMMARY.md](docs/STATUS_SUMMARY.md) for the complete status matrix and [CHANGELOG.md](CHANGELOG.md) for detailed release notes.
**Working Features (122 tools):**
- Project management with snapshot checkpointing
- Complete board design (outline, layers, zones, mounting holes, text, SVG logos)
- Component placement with arrays, alignment, and duplication
@@ -915,6 +972,7 @@ See [STATUS_SUMMARY.md](docs/STATUS_SUMMARY.md) for the complete status matrix a
- Full MCP 2025-06-18 protocol compliance
**IPC Backend (Experimental):**
- Real-time UI synchronization via KiCAD 9.0 IPC API
- 21 IPC-enabled commands with automatic SWIG fallback
- Hybrid footprint loading (SWIG for library access, IPC for placement)
@@ -929,6 +987,7 @@ See [ROADMAP.md](docs/ROADMAP.md) for planned features.
We are actively developing new features. Your feedback directly shapes development priorities.
**Share your ideas:**
1. [Open a feature request](https://github.com/mixelpixx/KiCAD-MCP-Server/issues/new?labels=enhancement&template=feature_request.md)
2. [Join the discussion](https://github.com/mixelpixx/KiCAD-MCP-Server/discussions)
3. Star the repo if you find it useful

View File

@@ -118,10 +118,12 @@ KiCAD-MCP-Server/
### Tool Registration
Each tool file exports a `register*Tools(server, callKicadScript)` function that:
- Defines tool name, description, and Zod schema for parameters
- Registers a handler that calls `callKicadScript(command, args)`
Example from `src/tools/project.ts`:
```typescript
server.tool(
"create_project",
@@ -130,13 +132,14 @@ server.tool(
async (args) => {
const result = await callKicadScript("create_project", args);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
},
);
```
### Tool Router (`src/tools/router.ts` and `src/tools/registry.ts`)
The router pattern reduces AI context usage:
- `registry.ts` defines tool categories and which tools are "direct" (always visible) vs "routed" (discoverable)
- `router.ts` provides 4 meta-tools: `list_tool_categories`, `get_category_tools`, `search_tools`, `execute_tool`
- Routed tools are not registered as individual MCP tools -- they are invoked through `execute_tool`
@@ -144,6 +147,7 @@ The router pattern reduces AI context usage:
### Python Subprocess Communication
`callKicadScript(command, args)` in `server.ts`:
1. Spawns `python3 python/kicad_interface.py` (if not already running)
2. Sends a JSON message: `{"command": "...", "params": {...}}`
3. Reads the JSON response
@@ -164,6 +168,7 @@ The router pattern reduces AI context usage:
### Command Routing
Commands are routed by name to handler methods. The mapping is defined in `kicad_interface.py`. Each handler:
1. Receives a params dict
2. Calls the appropriate command class method
3. Returns a result dict with `success`, `message`, and any additional data
@@ -173,12 +178,14 @@ Commands are routed by name to handler methods. The mapping is defined in `kicad
Two backends for interacting with KiCAD:
**SWIG Backend** (default):
- Direct Python bindings to KiCAD's C++ API via SWIG
- Operates on files -- loads .kicad_pcb, modifies in memory, saves back
- Works without KiCAD running
- Requires manual UI reload to see changes
**IPC Backend** (experimental):
- Communicates with running KiCAD via IPC API socket
- Changes appear in the UI immediately
- Requires KiCAD 9.0+ running with IPC enabled
@@ -189,6 +196,7 @@ Two backends for interacting with KiCAD:
### Schematic System
Schematic manipulation uses a different stack than PCB operations:
- **kicad-skip** library for reading/modifying schematic files
- **S-expression parsing** for direct file manipulation (wires, symbols)
- **DynamicSymbolLoader** for injecting any KiCad symbol into a schematic
@@ -214,7 +222,7 @@ server.tool(
async (args) => {
const result = await callKicadScript("my_new_tool", args);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
}
},
);
```
@@ -271,11 +279,13 @@ npm run test:py # Run Python tests
### Python Tests
Located in `python/tests/`. Run with:
```bash
pytest python/tests/ -v
```
Key test files:
- `test_schematic_tools.py` -- schematic tool tests
- `test_freerouting.py` -- autorouter tests
- `test_delete_schematic_component.py` -- component deletion tests
@@ -303,7 +313,7 @@ Key test files:
## Source Files Reference
| File | Purpose |
|------|---------|
| ------------------------------------------ | ----------------------------------- |
| `src/server.ts` | MCP server, subprocess management |
| `src/tools/registry.ts` | Tool categories and organization |
| `src/tools/router.ts` | Router meta-tools |

View File

@@ -7,7 +7,7 @@ This guide shows how to configure the KiCAD MCP Server with various MCP-compatib
## Quick Reference
| Client | Config File Location |
|--------|---------------------|
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Claude Desktop** | Linux: `~/.config/Claude/claude_desktop_config.json`<br>macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`<br>Windows: `%APPDATA%\Claude\claude_desktop_config.json` |
| **Cline (VSCode)** | VSCode Settings → Extensions → Cline → MCP Settings |
| **Claude Code** | `~/.config/claude-code/mcp_config.json` |
@@ -194,11 +194,7 @@ For any MCP-compatible client that supports STDIO transport:
```json
{
"command": "node",
"args": [
"/path/to/KiCAD-MCP-Server/dist/index.js",
"--config",
"/path/to/custom-config.json"
],
"args": ["/path/to/KiCAD-MCP-Server/dist/index.js", "--config", "/path/to/custom-config.json"],
"transport": "stdio"
}
```
@@ -210,13 +206,13 @@ For any MCP-compatible client that supports STDIO transport:
### Required
| Variable | Description | Example |
|----------|-------------|---------|
| ------------ | ---------------------------- | ------------------------------------------ |
| `PYTHONPATH` | Path to KiCAD Python modules | `/usr/lib/kicad/lib/python3/dist-packages` |
### Optional
| Variable | Description | Default |
|----------|-------------|---------|
| ----------------- | ------------------------------------------------- | -------------- |
| `LOG_LEVEL` | Logging verbosity | `info` |
| `NODE_ENV` | Node environment | `development` |
| `KICAD_BACKEND` | Force backend (`swig` or `ipc`) | Auto-detect |
@@ -312,6 +308,7 @@ claude-code
**Error:** `Cannot find module 'pcbnew'`
**Solution:** Verify `PYTHONPATH` is correct:
```bash
python3 -c "import sys; sys.path.append('/usr/lib/kicad/lib/python3/dist-packages'); import pcbnew; print(pcbnew.GetBuildVersion())"
```
@@ -319,6 +316,7 @@ python3 -c "import sys; sys.path.append('/usr/lib/kicad/lib/python3/dist-package
**Error:** `ENOENT: no such file or directory`
**Solution:** Check that `dist/index.js` exists:
```bash
cd /path/to/KiCAD-MCP-Server
npm run build
@@ -330,6 +328,7 @@ ls -lh dist/index.js
**Issue:** Claude Desktop doesn't show KiCAD tools
**Solutions:**
1. Restart Claude Desktop completely (quit, not just close window)
2. Check config file syntax with `jq`:
```bash
@@ -345,6 +344,7 @@ ls -lh dist/index.js
**Error:** `ModuleNotFoundError: No module named 'kicad_api'`
**Solution:** Server is looking for the wrong Python modules. This is an internal error. Check:
```bash
# Verify PYTHONPATH in server config includes both KiCAD and our modules
"PYTHONPATH": "/usr/lib/kicad/lib/python3/dist-packages:/path/to/KiCAD-MCP-Server/python"
@@ -398,17 +398,14 @@ Then use it:
```json
{
"command": "node",
"args": [
"/path/to/dist/index.js",
"--config",
"/path/to/config/production.json"
]
"args": ["/path/to/dist/index.js", "--config", "/path/to/config/production.json"]
}
```
### Development vs Production
Development (verbose logging):
```json
{
"env": {
@@ -419,6 +416,7 @@ Development (verbose logging):
```
Production (minimal logging):
```json
{
"env": {
@@ -473,10 +471,7 @@ Running server in WSL2, client on Windows:
"mcpServers": {
"kicad": {
"command": "wsl",
"args": [
"node",
"/home/user/KiCAD-MCP-Server/dist/index.js"
],
"args": ["node", "/home/user/KiCAD-MCP-Server/dist/index.js"],
"env": {
"PYTHONPATH": "/usr/lib/kicad/lib/python3/dist-packages"
}

View File

@@ -15,6 +15,7 @@ Scans a KiCAD schematic and fills in missing Datasheet URLs for components that
**How it works:**
For every placed symbol that has:
- An LCSC property set (e.g., `(property "LCSC" "C123456")`)
- An empty or missing Datasheet field
@@ -25,22 +26,25 @@ The URL is then visible in KiCAD's footprint browser, symbol properties dialog,
**Parameters:**
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| ---------------- | ------- | -------- | ------- | --------------------------------------- |
| `schematic_path` | string | Yes | -- | Path to the .kicad_sch file to enrich |
| `dry_run` | boolean | No | false | Preview changes without writing to disk |
**Returns:**
- Number of components updated
- Number already set (skipped)
- Number without LCSC number
- Details of each updated component (reference, LCSC number, URL)
**Example:**
```
Enrich datasheets for all components in ~/Projects/MyBoard/MyBoard.kicad_sch
```
Use `dry_run=true` to preview what would change:
```
Preview datasheet enrichment for ~/Projects/MyBoard/MyBoard.kicad_sch with dry run enabled.
```
@@ -54,14 +58,16 @@ Get the LCSC datasheet URL for a single component by LCSC number.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | -------------------------------------------------------------------------- |
| `lcsc` | string | Yes | LCSC part number, with or without "C" prefix (e.g., "C179739" or "179739") |
**Returns:**
- Datasheet PDF URL
- Product page URL
**Example:**
```
Get the datasheet URL for LCSC part C179739.
```

View File

@@ -13,7 +13,7 @@ Footprints define the physical copper pads, silkscreen markings, and courtyard b
Create a new KiCAD footprint (.kicad_mod) inside a .pretty library directory. Supports SMD and THT pads, courtyard, silkscreen, and fab-layer rectangles.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------------- | ------- | -------- | ------------------------------------------------------------------------------------------ |
| `libraryPath` | string | Yes | Path to the .pretty library directory (created if missing). E.g. C:/MyProject/MyLib.pretty |
| `name` | string | Yes | Footprint name, e.g. 'R_0603_Custom' |
| `description` | string | No | Human-readable description |
@@ -22,7 +22,7 @@ Create a new KiCAD footprint (.kicad_mod) inside a .pretty library directory. Su
| `courtyard` | object | No | Courtyard rectangle on F.CrtYd (recommended: 0.25 mm clearance around pads) |
| `silkscreen` | object | No | Silkscreen rectangle on F.SilkS |
| `fabLayer` | object | No | Fab-layer rectangle on F.Fab (shows component body) |
| `refPosition` | object | No | Position of the REF** text, e.g. {x: 0, y: -1.27} (default: 0, -1.27) |
| `refPosition` | object | No | Position of the REF\*\* text, e.g. {x: 0, y: -1.27} (default: 0, -1.27) |
| `valuePosition` | object | No | Position of the Value text, e.g. {x: 0, y: 1.27} (default: 0, 1.27) |
| `overwrite` | boolean | No | Replace existing footprint file (default: false) |
@@ -31,7 +31,7 @@ Create a new KiCAD footprint (.kicad_mod) inside a .pretty library directory. Su
Each pad object in the `pads` array supports:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ----------------- | ---------------- | -------- | -------------------------------------------------------------------------------------------- |
| `number` | string | Yes | Pad number / name, e.g. '1', '2', 'A1' |
| `type` | enum | Yes | Pad type: `smd`, `thru_hole`, or `np_thru_hole` |
| `shape` | enum | No | Pad shape: `rect`, `circle`, `oval`, or `roundrect` (default: rect for SMD, circle for THT) |
@@ -44,7 +44,7 @@ Each pad object in the `pads` array supports:
#### Rectangle Schema (courtyard, silkscreen, fabLayer)
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | ---------------- |
| `x1` | number | Yes | Left X in mm |
| `y1` | number | Yes | Top Y in mm |
| `x2` | number | Yes | Right X in mm |
@@ -54,15 +54,15 @@ Each pad object in the `pads` array supports:
#### Pad Types
- **SMD (smd)**: Surface-mount pads for components that sit on top of the PCB. Default layers: F.Cu, F.Paste, F.Mask
- **THT (thru_hole)**: Through-hole pads for components with leads that pass through the PCB. Requires `drill` parameter. Default layers: *.Cu, F.Mask, B.Mask
- **NPTH (np_thru_hole)**: Non-plated through-holes for mechanical mounting. Requires `drill` parameter. Default layers: *.Mask
- **THT (thru_hole)**: Through-hole pads for components with leads that pass through the PCB. Requires `drill` parameter. Default layers: \*.Cu, F.Mask, B.Mask
- **NPTH (np_thru_hole)**: Non-plated through-holes for mechanical mounting. Requires `drill` parameter. Default layers: \*.Mask
### edit_footprint_pad
Edit an existing pad inside a .kicad_mod footprint file. Updates size, position, drill, or shape without recreating the whole footprint.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------------- | ---------------- | -------- | ---------------------------------------------------------------------------- |
| `footprintPath` | string | Yes | Full path to the .kicad_mod file, e.g. C:/MyLib.pretty/R_Custom.kicad_mod |
| `padNumber` | string or number | Yes | Pad number to edit, e.g. '1' or 2 |
| `size` | object | No | New pad size: {w: number, h: number} in mm |
@@ -77,7 +77,7 @@ Edit an existing pad inside a .kicad_mod footprint file. Updates size, position,
Register a .pretty footprint library in KiCAD's fp-lib-table so KiCAD can find the footprints. Run this after create_footprint when KiCAD shows 'library not found in footprint library table'.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `libraryPath` | string | Yes | Full path to the .pretty directory to register |
| `libraryName` | string | No | Nickname for the library in KiCAD (default: directory name without .pretty) |
| `description` | string | No | Optional description |
@@ -91,7 +91,7 @@ Register a .pretty footprint library in KiCAD's fp-lib-table so KiCAD can find t
List available .pretty footprint libraries and their contents (first 20 footprints per library). Searches KiCAD standard install paths by default.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ----- | -------- | --------------------------------------------------------------------------------------------- |
| `searchPaths` | array | No | Override default search paths. Each entry should be a directory that contains .pretty subdirs |
### Example: Creating a Custom SOT-23 Footprint
@@ -172,6 +172,7 @@ Create a new schematic symbol in a .kicad_sym library file (created if missing).
Pin positions are where the wire connects; the symbol body is drawn between them.
**Coordinate tips:**
- Body rectangle typically spans ±2.54 to ±5.08 mm
- Pins on left side: at.x = body_left - length, angle=0 (wire goes right)
- Pins on right side: at.x = body_right + length, angle=180 (wire goes left)
@@ -180,7 +181,7 @@ Pin positions are where the wire connects; the symbol body is drawn between them
- Standard pin length: 2.54 mm, standard grid: 2.54 mm
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ----------------- | ------- | -------- | ---------------------------------------------------------------------------------------- |
| `libraryPath` | string | Yes | Path to the .kicad_sym file (created if missing) |
| `name` | string | Yes | Symbol name, e.g. 'TMC2209', 'MyOpAmp' |
| `referencePrefix` | string | No | Schematic reference prefix: 'U' (IC), 'R' (resistor), 'J' (connector), etc. Default: 'U' |
@@ -200,7 +201,7 @@ Pin positions are where the wire connects; the symbol body is drawn between them
Each pin object in the `pins` array supports:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `name` | string | Yes | Pin name, e.g. 'VCC', 'GND', 'IN+', '~' for unnamed |
| `number` | string or number | Yes | Pin number, e.g. '1', '2', 'A1' |
| `type` | enum | Yes | Electrical pin type (see Pin Types below) |
@@ -209,6 +210,7 @@ Each pin object in the `pins` array supports:
| `shape` | enum | No | Pin graphic shape (default: line) |
**Pin angle conventions:**
- 0 = right (wire extends to the right from the symbol body)
- 90 = up (wire extends upward)
- 180 = left (wire extends to the left)
@@ -217,7 +219,7 @@ Each pin object in the `pins` array supports:
#### Pin Types (Electrical)
| Type | Description |
|------|-------------|
| ---------------- | ----------------------------------------- |
| `input` | Input pin |
| `output` | Output pin |
| `bidirectional` | Bidirectional I/O |
@@ -234,7 +236,7 @@ Each pin object in the `pins` array supports:
#### Pin Shapes (Graphical)
| Shape | Description |
|-------|-------------|
| -------------------- | -------------------------- |
| `line` | Standard pin (default) |
| `inverted` | Pin with inversion bubble |
| `clock` | Clock input (triangle) |
@@ -248,7 +250,7 @@ Each pin object in the `pins` array supports:
#### Rectangle Schema
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | ------------------------------------------------------------------- |
| `x1` | number | Yes | Left X in mm |
| `y1` | number | Yes | Top Y in mm |
| `x2` | number | Yes | Right X in mm |
@@ -259,7 +261,7 @@ Each pin object in the `pins` array supports:
#### Polyline Schema
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | ------------------------------------------------------ |
| `points` | array | Yes | List of XY points: [{x: number, y: number}, ...] in mm |
| `width` | number | No | Stroke width in mm (default 0.254) |
| `fill` | enum | No | Fill type: `none`, `outline`, or `background` |
@@ -269,7 +271,7 @@ Each pin object in the `pins` array supports:
Remove a symbol from a .kicad_sym library file.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | --------------------------- |
| `libraryPath` | string | Yes | Path to the .kicad_sym file |
| `name` | string | Yes | Symbol name to delete |
@@ -278,7 +280,7 @@ Remove a symbol from a .kicad_sym library file.
List all symbol names in a .kicad_sym library file.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | --------------------------- |
| `libraryPath` | string | Yes | Path to the .kicad_sym file |
### register_symbol_library
@@ -286,7 +288,7 @@ List all symbol names in a .kicad_sym library file.
Register a .kicad_sym library in KiCAD's sym-lib-table so symbols can be used in schematics. Run this after create_symbol when KiCAD shows 'library not found'.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------- |
| `libraryPath` | string | Yes | Full path to the .kicad_sym file |
| `libraryName` | string | No | Nickname (default: file name without extension) |
| `description` | string | No | Optional description |
@@ -351,6 +353,7 @@ This example creates a 4-pin IC symbol (VCC, GND, IN, OUT):
```
**Pin positioning explained:**
- VIN pin at (-7.62, 2.54, angle=0): Wire extends to the right, so the symbol body should be to the right. Body left edge is at -5.08, and pin length is 2.54, so -7.62 = -5.08 - 2.54
- GND pin at (0, -7.62, angle=90): Wire extends upward, body bottom is at -5.08, so -7.62 = -5.08 - 2.54
- VOUT pin at (7.62, 2.54, angle=180): Wire extends to the left, body right is at 5.08, so 7.62 = 5.08 + 2.54
@@ -397,6 +400,7 @@ Footprints use a "Y-down" coordinate system (like screen coordinates), while sym
### Validation
After creating custom parts:
- Open KiCAD schematic editor and verify the symbol appears in the "Add Symbol" dialog
- Check pin numbers, names, and electrical types in symbol properties
- Open KiCAD PCB editor and verify the footprint appears in the footprint browser

View File

@@ -32,6 +32,7 @@ curl -L -o ~/.kicad-mcp/freerouting.jar \
```
The default location is `~/.kicad-mcp/freerouting.jar`. You can override this with:
- The `freeroutingJar` parameter on any tool call
- The `FREEROUTING_JAR` environment variable
@@ -85,6 +86,7 @@ Verify that prerequisites are installed before running the autorouter.
**Returns:** Java availability, version, Docker status, JAR location
**Example:**
```
Check if Freerouting is ready on my system.
```
@@ -102,6 +104,7 @@ Run the full autorouting workflow (export DSN, route, import SES).
| `timeout` | number | No | 300 | Timeout in seconds |
**Example:**
```
Autoroute the current board using Freerouting with a 5-minute timeout.
```
@@ -153,6 +156,7 @@ For advanced users or external autorouters:
```
This is useful when you want to:
- Use the Freerouting GUI for interactive routing
- Use a different autorouter that supports DSN/SES
- Route the board on a different machine
@@ -190,12 +194,14 @@ Install either Java 21+ or Docker/Podman. See the Prerequisites section above.
### "Java found but version < 21"
Freerouting 2.x requires Java 21+. Either:
- Upgrade your Java installation
- Install Docker as a fallback
### Timeout Errors
For complex boards, increase the timeout:
```
Autoroute with timeout 600 and max passes 30.
```
@@ -203,6 +209,7 @@ Autoroute with timeout 600 and max passes 30.
### Routing Quality
If the autorouter does not route all connections:
- Increase `maxPasses` (default: 20)
- Check that your design rules allow the autorouter enough clearance
- Run DRC after autorouting to identify any violations
@@ -211,6 +218,7 @@ If the autorouter does not route all connections:
### Docker Permission Errors
If Docker reports permission errors:
```bash
# Add your user to the docker group
sudo usermod -aG docker $USER

View File

@@ -9,7 +9,7 @@ KiCAD MCP Server -- AI-assisted PCB design via Model Context Protocol
## Getting Started
| Document | Description |
|----------|-------------|
| ----------------------------------------------- | -------------------------------------------------------------- |
| [README](../README.md) | Project overview, installation, configuration, quick start |
| [Client Configuration](CLIENT_CONFIGURATION.md) | MCP client setup (Claude Desktop, Cline, Claude Code) |
| [Platform Guide](PLATFORM_GUIDE.md) | Linux vs Windows vs macOS differences |
@@ -20,7 +20,7 @@ KiCAD MCP Server -- AI-assisted PCB design via Model Context Protocol
## Tool References
| Document | Description |
|----------|-------------|
| ----------------------------------------------------------------------- | ----------------------------------------------------------- |
| [Tool Inventory](TOOL_INVENTORY.md) | Complete list of all 122 tools with access types |
| [Schematic Tools Reference](SCHEMATIC_TOOLS_REFERENCE.md) | 27 schematic tools -- components, wiring, analysis, export |
| [Routing Tools Reference](ROUTING_TOOLS_REFERENCE.md) | 13 routing tools -- traces, vias, differential pairs, zones |
@@ -34,7 +34,7 @@ KiCAD MCP Server -- AI-assisted PCB design via Model Context Protocol
## Integration Guides
| Document | Description |
|----------|-------------|
| --------------------------------------------- | -------------------------------------------------- |
| [JLCPCB Integration](JLCPCB_INTEGRATION.md) | JLCPCB parts catalog, pricing, component selection |
| [JLCPCB Usage Guide](JLCPCB_USAGE_GUIDE.md) | Detailed JLCPCB setup and usage |
| [Library Integration](LIBRARY_INTEGRATION.md) | Footprint and symbol library setup |
@@ -45,7 +45,7 @@ KiCAD MCP Server -- AI-assisted PCB design via Model Context Protocol
## Workflows
| Document | Description |
|----------|-------------|
| --------------------------------------------- | ----------------------------------------- |
| [Realtime Workflow](REALTIME_WORKFLOW.md) | Working with IPC backend for live updates |
| [Visual Feedback](VISUAL_FEEDBACK.md) | UI visual feedback guide |
| [UI Auto Launch](UI_AUTO_LAUNCH.md) | Automatic KiCAD UI launch feature |
@@ -58,7 +58,7 @@ KiCAD MCP Server -- AI-assisted PCB design via Model Context Protocol
## Troubleshooting
| Document | Description |
|----------|-------------|
| --------------------------------------------------------- | ------------------------------ |
| [Known Issues](KNOWN_ISSUES.md) | Current issues and workarounds |
| [Windows Troubleshooting](WINDOWS_TROUBLESHOOTING.md) | Windows-specific problems |
| [Linux Compatibility Audit](LINUX_COMPATIBILITY_AUDIT.md) | Linux platform details |
@@ -68,7 +68,7 @@ KiCAD MCP Server -- AI-assisted PCB design via Model Context Protocol
## Project Information
| Document | Description |
|----------|-------------|
| ----------------------------------- | ----------------------------------------- |
| [Status Summary](STATUS_SUMMARY.md) | Current project status and feature matrix |
| [Roadmap](ROADMAP.md) | Development roadmap and planned features |
| [Changelog](../CHANGELOG.md) | Detailed release notes for all versions |
@@ -78,7 +78,7 @@ KiCAD MCP Server -- AI-assisted PCB design via Model Context Protocol
## For Contributors
| Document | Description |
|----------|-------------|
| ---------------------------------- | ---------------------------------------- |
| [Contributing](../CONTRIBUTING.md) | How to contribute to the project |
| [Architecture](ARCHITECTURE.md) | System architecture and adding new tools |

View File

@@ -16,7 +16,7 @@ This feature is experimental and under active testing. The server uses a hybrid
## Key Differences
| Feature | SWIG | IPC |
|---------|------|-----|
| -------------- | ---------------------- | ------------------------ |
| UI Updates | Manual reload required | Immediate (when working) |
| Undo/Redo | Not supported | Transaction support |
| API Stability | Deprecated in KiCAD 9 | Official, versioned |
@@ -28,7 +28,7 @@ This feature is experimental and under active testing. The server uses a hybrid
The following MCP commands have IPC handlers:
| Command | IPC Handler | Status |
|---------|-------------|--------|
| -------------------------- | ------------------------------- | -------------------- |
| `route_trace` | `_ipc_route_trace` | Implemented |
| `add_via` | `_ipc_add_via` | Implemented |
| `add_net` | `_ipc_add_net` | Implemented |
@@ -54,6 +54,7 @@ The following MCP commands have IPC handlers:
### Implemented Backend Features
**Core Connection:**
- Connect to running KiCAD instance
- Auto-detect socket path (`/tmp/kicad/api.sock`)
- Version checking and validation
@@ -61,6 +62,7 @@ The following MCP commands have IPC handlers:
- Change notification callbacks
**Board Operations:**
- Get board reference
- Get/Set board size
- List enabled layers
@@ -69,6 +71,7 @@ The following MCP commands have IPC handlers:
- Add mounting holes
**Component Operations:**
- List all components
- Place component (hybrid: SWIG for library loading, IPC for placement)
- Move component
@@ -77,6 +80,7 @@ The following MCP commands have IPC handlers:
- Get component properties
**Routing Operations:**
- Add track
- Add via
- Get all tracks
@@ -84,16 +88,19 @@ The following MCP commands have IPC handlers:
- Get all nets
**Zone Operations:**
- Add copper pour zones
- Get zones list
- Refill zones
**UI Integration:**
- Add text to board
- Get current selection
- Clear selection
**Transaction Support:**
- Begin transaction
- Commit transaction (with description for undo)
- Rollback transaction
@@ -162,20 +169,24 @@ Run the test script to verify IPC functionality:
## Troubleshooting
### "Connection failed"
- Ensure KiCAD is running
- Enable IPC API: `Preferences > Plugins > Enable IPC API Server`
- Check if a board is open
### "kicad-python not found"
```bash
pip install kicad-python
```
### "Version mismatch"
- Update kicad-python: `pip install --upgrade kicad-python`
- Ensure KiCAD 9.0+ is installed
### "No board open"
- Open a board in KiCAD's PCB editor before connecting
## File Structure

View File

@@ -98,14 +98,17 @@ footprints = db.map_package_to_footprint("0603")
### JLCSearchClient
#### `search_resistors(resistance, package, limit)`
Search for resistors by value and package.
**Parameters:**
- `resistance` (int, optional): Resistance in ohms
- `package` (str, optional): Package size ("0402", "0603", "0805", etc.)
- `limit` (int): Maximum results (default: 100)
**Returns:** List of resistor dicts with fields:
- `lcsc`: LCSC number (integer)
- `mfr`: Manufacturer part number
- `package`: Package size
@@ -117,9 +120,11 @@ Search for resistors by value and package.
- `price1`: Unit price in USD
#### `search_capacitors(capacitance, package, limit)`
Search for capacitors by value and package.
**Parameters:**
- `capacitance` (float, optional): Capacitance in farads (e.g., 1e-7 for 100nF)
- `package` (str, optional): Package size
- `limit` (int): Maximum results
@@ -127,9 +132,11 @@ Search for capacitors by value and package.
**Returns:** List of capacitor dicts
#### `search_components(category, limit, offset, **filters)`
General component search.
**Parameters:**
- `category` (str): "resistors", "capacitors", "components", etc.
- `limit` (int): Maximum results
- `offset` (int): Pagination offset
@@ -138,9 +145,11 @@ General component search.
**Returns:** List of component dicts
#### `download_all_components(callback, batch_size)`
Download entire JLCPCB parts catalog.
**Parameters:**
- `callback` (callable, optional): Progress callback(parts_count, status_msg)
- `batch_size` (int): Parts per batch (default: 1000)
@@ -151,16 +160,20 @@ Download entire JLCPCB parts catalog.
### JLCPCBPartsManager
#### `import_jlcsearch_parts(parts, progress_callback)`
Import parts from JLCSearch into local SQLite database.
**Parameters:**
- `parts` (list): List of part dicts from JLCSearchClient
- `progress_callback` (callable, optional): Progress updates
#### `search_parts(query, category, package, library_type, manufacturer, in_stock, limit)`
Search local database with filters.
**Parameters:**
- `query` (str, optional): Free-text search
- `category` (str, optional): Category filter
- `package` (str, optional): Package filter
@@ -172,17 +185,21 @@ Search local database with filters.
**Returns:** List of matching parts
#### `get_part_info(lcsc_number)`
Get detailed part information.
**Parameters:**
- `lcsc_number` (str): LCSC part number (e.g., "C25804")
**Returns:** Part dict or None
#### `get_database_stats()`
Get database statistics.
**Returns:** Dict with:
- `total_parts`: Total parts count
- `basic_parts`: Basic library count
- `extended_parts`: Extended library count
@@ -190,9 +207,11 @@ Get database statistics.
- `db_path`: Database file path
#### `map_package_to_footprint(package)`
Map JLCPCB package to KiCad footprints.
**Parameters:**
- `package` (str): JLCPCB package name
**Returns:** List of KiCad footprint library references
@@ -239,7 +258,7 @@ CREATE TABLE components (
## Package to Footprint Mappings
| JLCPCB Package | KiCad Footprints |
|----------------|------------------|
| -------------- | ------------------------------------------------------------------------------------------------ |
| 0402 | Resistor_SMD:R_0402_1005Metric<br>Capacitor_SMD:C_0402_1005Metric<br>LED_SMD:LED_0402_1005Metric |
| 0603 | Resistor_SMD:R_0603_1608Metric<br>Capacitor_SMD:C_0603_1608Metric<br>LED_SMD:LED_0603_1608Metric |
| 0805 | Resistor_SMD:R_0805_2012Metric<br>Capacitor_SMD:C_0805_2012Metric |
@@ -254,6 +273,7 @@ CREATE TABLE components (
## Best Practices
### 1. Always Use Basic Library Parts First
Basic library parts have **no assembly fee** ($0/part), while Extended parts cost **$3/part**.
```python
@@ -262,6 +282,7 @@ basic_parts = [p for p in results if p['is_basic']]
```
### 2. Check Stock Availability
Ensure sufficient stock before committing to a design.
```python
@@ -270,6 +291,7 @@ high_stock = [p for p in results if p['stock'] > 1000]
```
### 3. Compare Prices
Even within Basic library, prices vary significantly.
```python
@@ -278,9 +300,11 @@ cheapest = min(results, key=lambda x: x.get('price1', 999))
```
### 4. Use Standardized Packages
Stick to common packages (0402, 0603, 0805) for better availability and pricing.
### 5. Cache Database Locally
Download the full parts database once and search locally for faster results.
```python
@@ -296,23 +320,29 @@ results = db.search_parts(...)
## Troubleshooting
### API Rate Limiting
JLCSearch is a community service. If you hit rate limits:
- Add delays between requests (`time.sleep(0.1)`)
- Use the local database instead of repeated API calls
- Download the full database once and work offline
### Missing Data
JLCSearch may not have all fields that official JLCPCB API provides:
- No datasheets (use manufacturer website)
- Limited category information
- No solder joint count
### Stock Discrepancies
Stock levels are updated periodically but may lag real-time JLCPCB data by a few hours.
## Official JLCPCB API (Alternative)
The project also includes an implementation of the official JLCPCB API with HMAC-SHA256 authentication. However, this requires:
1. API approval from JLCPCB (not all applications are approved)
2. APP_ID, ACCESS_KEY, and SECRET_KEY credentials
3. Previous order history with JLCPCB

View File

@@ -20,6 +20,7 @@ All approaches can be used together to give you maximum flexibility.
## Approach 1: JLCSearch Public API (Recommended)
### What It Does
- Access to 2.5M+ JLCPCB parts with pricing and stock data
- **No authentication required** - works immediately
- **No JLCPCB account needed**
@@ -39,6 +40,7 @@ download_jlcpcb_database({ force: false })
This downloads ~2.5M parts from JLCSearch API and creates a local SQLite database (`data/jlcpcb_parts.db`).
**Expected Output:**
```
Downloading JLCPCB parts database...
Downloaded 100 parts...
@@ -59,6 +61,7 @@ See "Approach 2" usage examples below - the API is the same.
## Approach 2: Local Symbol Libraries (Good for Offline Use)
### What It Does
- Searches symbol libraries you've installed via KiCad's Plugin and Content Manager (PCM)
- Works with community JLCPCB libraries like `JLCPCB-KiCad-Library`
- No API credentials needed
@@ -81,6 +84,7 @@ See "Approach 2" usage examples below - the API is the same.
### Usage Examples
#### Search for Components
```
search_symbols({
query: "ESP32",
@@ -89,6 +93,7 @@ search_symbols({
```
Returns:
```
Found 12 symbols matching "ESP32":
@@ -98,6 +103,7 @@ PCM_JLCPCB-MCUs:ESP32-S2 | LCSC: C701342 | ESP32-S2 WiFi SoC
```
#### Search by LCSC ID
```
search_symbols({
query: "C2934196" // Direct LCSC ID search
@@ -105,6 +111,7 @@ search_symbols({
```
#### Get Symbol Details
```
get_symbol_info({
symbol: "PCM_JLCPCB-MCUs:ESP32-C3"
@@ -112,6 +119,7 @@ get_symbol_info({
```
Returns:
```
Symbol: PCM_JLCPCB-MCUs:ESP32-C3
Description: ESP32-C3 RISC-V WiFi/BLE SoC
@@ -123,6 +131,7 @@ Class: Extended
```
### Advantages
- ✅ No API credentials required
- ✅ Works offline after library installation
- ✅ Symbols pre-configured with correct footprints
@@ -130,6 +139,7 @@ Class: Extended
- ✅ Instant availability
### Limitations
- ❌ Only parts in installed libraries (typically 1k-10k parts)
- ❌ No real-time pricing or stock information
- ❌ Requires manual library updates via PCM
@@ -139,6 +149,7 @@ Class: Extended
## Approach 3: Official JLCPCB API (Advanced - Enterprise Accounts Only)
### What It Does
- Downloads from the **official JLCPCB API** (requires enterprise account)
- Provides **real-time pricing and stock information**
- Automatic **Basic vs Extended** library type identification (Basic = free assembly)
@@ -150,6 +161,7 @@ Class: Extended
#### 1. Get JLCPCB API Credentials
Visit [JLCPCB](https://jlcpcb.com/) and get your API credentials:
1. Log in to your JLCPCB account
2. Go to: **Account → API Management**
3. Click "Create API Key"
@@ -182,6 +194,7 @@ download_jlcpcb_database({ force: false })
This downloads ~100k parts from JLCPCB and creates a local SQLite database (`data/jlcpcb_parts.db`).
**Output:**
```
✓ Successfully downloaded JLCPCB parts database
@@ -205,6 +218,7 @@ search_jlcpcb_parts({
```
**Returns:**
```
Found 15 JLCPCB parts:
@@ -225,6 +239,7 @@ get_jlcpcb_part({
```
**Returns:**
```
LCSC: C58972
MFR Part: 0603WAF1002T5E
@@ -257,6 +272,7 @@ suggest_jlcpcb_alternatives({
```
**Returns:**
```
Alternative parts for C25804:
@@ -289,6 +305,7 @@ get_jlcpcb_database_stats({})
```
**Returns:**
```
JLCPCB Database Statistics:
@@ -300,6 +317,7 @@ Database path: /home/user/KiCAD-MCP-Server/data/jlcpcb_parts.db
```
### Advantages
- ✅ Complete JLCPCB catalog (100k+ parts)
- ✅ Real-time pricing and stock data
- ✅ Automatic Basic/Extended identification
@@ -308,6 +326,7 @@ Database path: /home/user/KiCAD-MCP-Server/data/jlcpcb_parts.db
- ✅ Fast parametric search
### Limitations
- ❌ Requires API credentials
- ❌ Initial download takes 5-10 minutes
- ❌ Database needs periodic updates for latest parts
@@ -320,6 +339,7 @@ Database path: /home/user/KiCAD-MCP-Server/data/jlcpcb_parts.db
### Workflow 1: Design with Known Components
**Use Local Libraries:**
```
1. search_symbols({ query: "STM32F103", library: "JLCPCB" })
2. Select component from installed library
@@ -331,6 +351,7 @@ Database path: /home/user/KiCAD-MCP-Server/data/jlcpcb_parts.db
### Workflow 2: Find Optimal Part for Cost
**Use JLCPCB API:**
```
1. search_jlcpcb_parts({
query: "10k resistor",
@@ -346,6 +367,7 @@ Database path: /home/user/KiCAD-MCP-Server/data/jlcpcb_parts.db
### Workflow 3: Explore Unknown Parts
**Start with API, verify with Libraries:**
```
1. search_jlcpcb_parts({ query: "ESP32", limit: 20 })
2. Find interesting part (e.g., C2934196)
@@ -393,6 +415,7 @@ search_jlcpcb_parts({
### 4. Calculate BOM Cost
For each part in your design:
1. Use `get_jlcpcb_part()` to get price breaks
2. Sum up total cost based on order quantity
3. Check library_type count (each unique Extended part = $3 fee)
@@ -422,6 +445,7 @@ Future versions will support incremental updates that only fetch new/changed par
### "JLCPCB API credentials not configured"
**Solution:** Set environment variables:
```bash
export JLCPCB_API_KEY="your_key"
export JLCPCB_API_SECRET="your_secret"
@@ -430,6 +454,7 @@ export JLCPCB_API_SECRET="your_secret"
### "Database not found or empty"
**Solution:** Run:
```
download_jlcpcb_database({ force: false })
```
@@ -437,6 +462,7 @@ download_jlcpcb_database({ force: false })
### "No symbols found" (Local Libraries)
**Solution:**
1. Install JLCPCB libraries via KiCad PCM
2. Verify library is enabled in KiCad symbol library table
3. Restart KiCad MCP server
@@ -444,6 +470,7 @@ download_jlcpcb_database({ force: false })
### "Authentication failed"
**Solution:**
1. Verify your API credentials are correct
2. Check JLCPCB account has API access enabled
3. Try regenerating API key/secret in JLCPCB dashboard
@@ -453,7 +480,7 @@ download_jlcpcb_database({ force: false })
## API vs Libraries: Quick Reference
| Feature | Local Libraries | JLCPCB API |
|---------|----------------|------------|
| --------------------- | ------------------ | -------------------------- |
| **Parts Count** | 1k-10k (installed) | 100k+ (complete catalog) |
| **Setup** | Install via PCM | API credentials + download |
| **Offline Use** | ✅ Yes | ✅ Yes (after download) |
@@ -505,12 +532,14 @@ search_symbols({ query: "USB-C" })
## Summary
**Use Local Libraries when:**
- Starting a new design with common components
- You want pre-configured, tested symbols
- Working offline
- Components are in installed libraries
**Use JLCPCB API when:**
- Optimizing cost (find cheapest Basic parts)
- Checking real-time stock availability
- Exploring parts outside installed libraries

View File

@@ -14,6 +14,7 @@ This document tracks known issues and provides workarounds where available.
**Status:** KNOWN - Non-critical
**Symptoms:**
```
AttributeError: 'BOARD' object has no attribute 'LT_USER'
```
@@ -31,10 +32,12 @@ AttributeError: 'BOARD' object has no attribute 'LT_USER'
**Status:** KNOWN - Workaround available
**Symptoms:**
- Copper pours created but not filled automatically when using SWIG backend
- Calling `ZONE_FILLER` via SWIG causes segfault
**Workaround Options:**
1. Use IPC backend (zones fill correctly via IPC)
2. Open the board in KiCAD UI -- zones fill automatically when opened
3. Use `refill_zones` tool (may still segfault in some configurations)
@@ -48,6 +51,7 @@ AttributeError: 'BOARD' object has no attribute 'LT_USER'
**Status:** BY DESIGN
**Symptoms:**
- MCP makes changes via SWIG backend
- KiCAD does not show changes until file is reloaded
@@ -64,6 +68,7 @@ AttributeError: 'BOARD' object has no attribute 'LT_USER'
**Status:** EXPERIMENTAL
**Known Limitations:**
- KiCAD must be running with IPC enabled (Preferences > Plugins > Enable IPC API Server)
- Some commands fall back to SWIG (e.g., delete_trace)
- Footprint loading uses hybrid approach (SWIG for library, IPC for placement)
@@ -85,32 +90,40 @@ AttributeError: 'BOARD' object has no attribute 'LT_USER'
## Recently Fixed (v2.2.0 - v2.2.3)
### B.Cu Footprint Routing (Fixed v2.2.3)
- `route_pad_to_pad` now correctly detects B.Cu footprints and inserts vias
- KiCAD 9 SWIG `pad.GetLayerName()` always returned F.Cu for flipped footprints -- fixed using `footprint.GetLayer()`
### B.Cu Placement Hang (Fixed v2.2.3)
- Placing footprints on B.Cu no longer causes ~30s freeze
- Fix: call `board.Add()` before `Flip()`
### Board Outline Rounded Corners (Fixed v2.2.3)
- `add_board_outline` now correctly applies cornerRadius for rounded_rectangle shape
### Project-Local Library Resolution (Fixed v2.2.2)
- `add_schematic_component` and `place_component` now search project-local sym-lib-table and fp-lib-table
- Previously only global KiCAD library paths were searched
### Template File Corruption (Fixed v2.2.2)
- Removed invalid `;;` comment lines from template schematics
- Restored KiCAD 9 format version (20250114) in templates
### copy_routing_pattern Empty Results (Fixed v2.2.2)
- Added geometric fallback when pads have no net assignments
### Schematic Component Corruption (Fixed v2.2.1)
- `add_schematic_component` no longer corrupts .kicad_sch files
- Rewritten to use text manipulation instead of sexpdata formatting
### SWIG/UUID Comparison Bugs (Fixed v2.2.0)
- Fixed SwigPyObject UUID comparison
- Fixed SWIG iterator invalidation after board.Remove()
- Added board.SetModified() to prevent dangling pointer crashes
@@ -136,6 +149,7 @@ If you encounter an issue not listed here:
## General Workarounds
### Server Will Not Start
```bash
# Check Python can import pcbnew
python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())"
@@ -145,12 +159,14 @@ python3 python/utils/platform_helper.py
```
### Commands Fail After Server Restart
```
# Board reference is lost on restart
# Always run open_project after server restart
```
### KiCAD UI Does Not Show Changes (SWIG Mode)
```
# File > Revert (or click reload prompt)
# Or: Close and reopen file in KiCAD
@@ -158,6 +174,7 @@ python3 python/utils/platform_helper.py
```
### IPC Not Connecting
```
# Ensure KiCAD is running
# Enable IPC: Preferences > Plugins > Enable IPC API Server
@@ -168,6 +185,7 @@ python3 python/utils/platform_helper.py
---
**Need Help?**
- Check [IPC_BACKEND_STATUS.md](IPC_BACKEND_STATUS.md) for IPC details
- Check logs: `~/.kicad-mcp/logs/kicad_interface.log`
- Open an issue on GitHub

View File

@@ -7,6 +7,7 @@
## Overview
The KiCAD MCP Server includes full library integration for both footprints and symbols, enabling:
- ✅ Automatic discovery of all installed KiCAD footprint libraries
- ✅ Automatic discovery of KiCAD symbol libraries (including project-local)
- ✅ Search and browse footprints/symbols across all libraries
@@ -45,6 +46,7 @@ The library system automatically discovers both footprint and symbol libraries:
### Supported Formats
**Library:Footprint format (recommended):**
```json
{
"componentId": "Resistor_SMD:R_0603_1608Metric"
@@ -52,6 +54,7 @@ The library system automatically discovers both footprint and symbol libraries:
```
**Footprint-only format (searches all libraries):**
```json
{
"componentId": "R_0603_1608Metric"
@@ -67,6 +70,7 @@ List all available footprint libraries.
**Parameters:** None
**Returns:**
```json
{
"success": true,
@@ -80,6 +84,7 @@ List all available footprint libraries.
Search for footprints matching a pattern.
**Parameters:**
```json
{
"pattern": "*0603*", // Supports wildcards
@@ -88,6 +93,7 @@ Search for footprints matching a pattern.
```
**Returns:**
```json
{
"success": true,
@@ -107,6 +113,7 @@ Search for footprints matching a pattern.
List all footprints in a specific library.
**Parameters:**
```json
{
"library": "Resistor_SMD"
@@ -114,6 +121,7 @@ List all footprints in a specific library.
```
**Returns:**
```json
{
"success": true,
@@ -128,6 +136,7 @@ List all footprints in a specific library.
Get detailed information about a specific footprint.
**Parameters:**
```json
{
"footprint": "Resistor_SMD:R_0603_1608Metric"
@@ -135,6 +144,7 @@ Get detailed information about a specific footprint.
```
**Returns:**
```json
{
"success": true,
@@ -154,7 +164,7 @@ The `place_component` tool now uses the library system:
```json
{
"componentId": "Resistor_SMD:R_0603_1608Metric", // Library:Footprint format
"position": {"x": 50, "y": 40, "unit": "mm"},
"position": { "x": 50, "y": 40, "unit": "mm" },
"reference": "R1",
"value": "10k",
"rotation": 0,
@@ -163,6 +173,7 @@ The `place_component` tool now uses the library system:
```
**Features:**
- ✅ Automatic footprint discovery across all libraries
- ✅ Helpful error messages with suggestions
- ✅ Supports KiCAD 9.0 API (EDA_ANGLE, GetFPIDAsString)
@@ -170,6 +181,7 @@ The `place_component` tool now uses the library system:
## Example Usage (Claude Code)
**Search for a resistor footprint:**
```
User: "Find me a 0603 resistor footprint"
@@ -178,6 +190,7 @@ Claude: [uses search_footprints tool with pattern "*R_0603*"]
```
**Place a component:**
```
User: "Place a 10k 0603 resistor at 50,40mm"
@@ -186,6 +199,7 @@ Claude: [uses place_component with "Resistor_SMD:R_0603_1608Metric"]
```
**List available capacitors:**
```
User: "What capacitor footprints are available?"
@@ -223,18 +237,22 @@ The system automatically detects KiCAD installations, but you can add custom lib
The library integration includes full KiCAD 9.0 API support:
### Fixed API Changes:
1.`SetOrientation()` → now uses `EDA_ANGLE(degrees, DEGREES_T)`
2.`GetOrientation()` → returns `EDA_ANGLE`, call `.AsDegrees()`
3.`GetFootprintName()` → now `GetFPIDAsString()`
### Example Fixes:
**Old (KiCAD 8.0):**
```python
module.SetOrientation(90 * 10) # Decidegrees
rotation = module.GetOrientation() / 10
```
**New (KiCAD 9.0):**
```python
angle = pcbnew.EDA_ANGLE(90, pcbnew.DEGREES_T)
module.SetOrientation(angle)
@@ -248,6 +266,7 @@ rotation = module.GetOrientation().AsDegrees()
**Location:** `python/commands/library.py`
**Key Methods:**
- `_load_libraries()` - Parse fp-lib-table files
- `_parse_fp_lib_table()` - S-expression parser
- `_resolve_uri()` - Handle environment variables
@@ -256,6 +275,7 @@ rotation = module.GetOrientation().AsDegrees()
- `list_footprints()` - List library contents
**Performance:**
- Libraries loaded once at startup
- Footprint lists cached on first access
- Fast search using Python regex
@@ -281,6 +301,7 @@ rotation = module.GetOrientation().AsDegrees()
## Testing
**Test Coverage:**
- ✅ Library path discovery (Linux/Windows/macOS)
- ✅ fp-lib-table parsing
- ✅ Environment variable resolution
@@ -289,6 +310,7 @@ rotation = module.GetOrientation().AsDegrees()
- ✅ Error handling and suggestions
**Verified With:**
- KiCAD 9.0.5 on Ubuntu 24.04
- 153 standard libraries (8,000+ footprints)
- pcbnew Python API
@@ -316,6 +338,7 @@ rotation = module.GetOrientation().AsDegrees()
**Cause:** fp-lib-table not found or empty
**Solution:**
1. Verify KiCAD is installed
2. Open KiCAD and ensure libraries are configured
3. Check `~/.config/kicad/9.0/fp-lib-table` exists
@@ -325,6 +348,7 @@ rotation = module.GetOrientation().AsDegrees()
**Cause:** Footprint doesn't exist or library not loaded
**Solution:**
1. Use `search_footprints` to find similar footprints
2. Check library name is correct
3. Verify library is in fp-lib-table
@@ -334,6 +358,7 @@ rotation = module.GetOrientation().AsDegrees()
**Cause:** Corrupt .kicad_mod file or permissions issue
**Solution:**
1. Check file permissions on library directories
2. Reinstall KiCAD libraries if corrupt
3. Check logs for detailed error
@@ -348,6 +373,7 @@ rotation = module.GetOrientation().AsDegrees()
## Changelog
**2026-03-21 - v2.2.3+**
- Project-local symbol library support (v2.2.2)
- Project-local footprint library support (v2.2.2)
- Implemented LibraryManager class

View File

@@ -1,4 +1,5 @@
# Linux Compatibility Audit Report
**Date:** 2025-10-25
**Target Platform:** Ubuntu 24.04 LTS (primary), Fedora, Arch (secondary)
**Current Status:** Windows-optimized, partial Linux support
@@ -10,6 +11,7 @@
The KiCAD MCP Server was originally developed for Windows and has several compatibility issues preventing smooth operation on Linux. This audit identifies all platform-specific issues and provides remediation priorities.
**Overall Status:** 🟡 **PARTIAL COMPATIBILITY**
- ✅ TypeScript server: Good cross-platform support
- 🟡 Python interface: Mixed (some hardcoded paths)
- ❌ Configuration: Windows-specific examples
@@ -20,7 +22,9 @@ The KiCAD MCP Server was originally developed for Windows and has several compat
## Critical Issues (P0 - Must Fix)
### 1. Hardcoded Windows Paths in Config Examples
**File:** `config/claude-desktop-config.json`
```json
"cwd": "c:/repo/KiCAD-MCP",
"PYTHONPATH": "C:/Program Files/KiCad/9.0/lib/python3/dist-packages"
@@ -33,7 +37,9 @@ The KiCAD MCP Server was originally developed for Windows and has several compat
---
### 2. Library Search Paths (Mixed Approach)
**File:** `python/commands/library_schematic.py:16`
```python
search_paths = [
"C:/Program Files/KiCad/*/share/kicad/symbols/*.kicad_sym", # Windows
@@ -49,7 +55,9 @@ search_paths = [
---
### 3. Python Path Detection
**File:** `python/kicad_interface.py:38-45`
```python
kicad_paths = [
os.path.join(os.path.dirname(sys.executable), 'Lib', 'site-packages'),
@@ -66,9 +74,11 @@ kicad_paths = [
## High Priority Issues (P1)
### 4. Documentation is Windows-Only
**Files:** `README.md`, installation instructions
**Issues:**
- Installation paths reference `C:\Program Files`
- VSCode settings path is Windows format
- No Linux-specific troubleshooting
@@ -79,6 +89,7 @@ kicad_paths = [
---
### 5. Missing Python Dependencies Documentation
**File:** None (no requirements.txt)
**Impact:** Users don't know what Python packages to install
@@ -88,6 +99,7 @@ kicad_paths = [
---
### 6. Path Handling Uses os.path Instead of pathlib
**Files:** All Python files (11 files)
**Impact:** Code is less readable and more error-prone
@@ -99,6 +111,7 @@ kicad_paths = [
## Medium Priority Issues (P2)
### 7. No Linux-Specific Testing
**Impact:** Can't verify Linux compatibility
**Fix:** Add GitHub Actions with Ubuntu runner
**Priority:** P2
@@ -106,9 +119,11 @@ kicad_paths = [
---
### 8. Log File Paths May Differ
**File:** `src/logger.ts:13`
```typescript
const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
const DEFAULT_LOG_DIR = join(os.homedir(), ".kicad-mcp", "logs");
```
**Impact:** `.kicad-mcp` is okay for Linux, but best practice is `~/.config/kicad-mcp`
@@ -118,6 +133,7 @@ const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
---
### 9. No Bash/Shell Scripts for Linux
**Impact:** Manual setup is harder on Linux
**Fix:** Create `install.sh` and `run.sh` scripts
**Priority:** P2
@@ -127,6 +143,7 @@ const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
## Low Priority Issues (P3)
### 10. TypeScript Build Uses Windows Conventions
**File:** `package.json`
**Impact:** Works but could be more Linux-friendly
@@ -151,6 +168,7 @@ const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
### **Week 1 - Critical Fixes (P0)**
1. **Create Platform-Specific Config Templates**
```bash
config/
├── linux-config.example.json
@@ -159,6 +177,7 @@ const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
```
2. **Fix Python Path Detection**
```python
# Detect platform and set appropriate paths
import platform
@@ -187,6 +206,7 @@ const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
### **Week 1 - High Priority (P1)**
4. **Create requirements.txt**
```txt
# requirements.txt
kicad-skip>=0.1.0
@@ -209,6 +229,7 @@ const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
## Testing Checklist
### Ubuntu 24.04 LTS Testing
- [ ] Install KiCAD 9.0 from official PPA
- [ ] Install Node.js 18+ from NodeSource
- [ ] Clone repository
@@ -220,10 +241,12 @@ const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
- [ ] Test: Export Gerbers
### Fedora Testing
- [ ] Install KiCAD from Fedora repos
- [ ] Test same workflow
### Arch Testing
- [ ] Install KiCAD from AUR
- [ ] Test same workflow

View File

@@ -25,6 +25,7 @@ Create a new KiCAD project named "LEDBoard" in ~/Projects/
```
This uses `create_project` to generate:
- `.kicad_pro` -- project file
- `.kicad_pcb` -- PCB layout file
- `.kicad_sch` -- schematic file (with template symbols pre-loaded)
@@ -119,6 +120,7 @@ Align all resistors horizontally.
### Route Traces
**Preferred approach -- pad-to-pad routing:**
```
Route R1 pad 2 to LED1 pad 1 with 0.3mm trace width.
```
@@ -126,6 +128,7 @@ Route R1 pad 2 to LED1 pad 1 with 0.3mm trace width.
**Tool:** `route_pad_to_pad` -- auto-detects pad positions, nets, and inserts vias when pads are on different layers
**Manual approach:**
```
Route a trace from x=15, y=25 to x=25, y=25 on the front copper layer.
```
@@ -135,11 +138,13 @@ Route a trace from x=15, y=25 to x=25, y=25 on the front copper layer.
### Advanced Routing
**Differential pairs:**
```
Route a differential pair for USB_P and USB_N with 0.2mm width and 0.15mm gap.
```
**Copper zones:**
```
Add a GND copper pour on the bottom layer covering the entire board.
```
@@ -149,6 +154,7 @@ Add a GND copper pour on the bottom layer covering the entire board.
### Autorouting
For boards with many connections:
```
Check if Freerouting is available.
Autoroute the board using Freerouting.
@@ -246,6 +252,7 @@ Suggest alternatives to part C25804.
```
After selecting parts, enrich datasheets:
```
Enrich datasheets for all components in the schematic.
```

View File

@@ -9,7 +9,7 @@ This guide explains the differences between using KiCAD MCP Server on Linux and
## Quick Comparison
| Feature | Linux | Windows |
|---------|-------|---------|
| ------------------------ | ------------------------- | ------------------------------- |
| **Primary Support** | Full (tested extensively) | Community tested |
| **Setup Complexity** | Moderate | Easy (automated script) |
| **Prerequisites** | Manual package management | Automated detection |
@@ -25,12 +25,14 @@ This guide explains the differences between using KiCAD MCP Server on Linux and
### Linux Installation
**Advantages:**
- Native package manager integration
- Better tested and documented
- More predictable Python environments
- Standard Unix paths
**Process:**
1. Install KiCAD 9.0 via package manager (apt, dnf, pacman)
2. Install Node.js via package manager or nvm
3. Clone repository
@@ -40,6 +42,7 @@ This guide explains the differences between using KiCAD MCP Server on Linux and
7. Set PYTHONPATH environment variable
**Typical paths:**
```bash
KiCAD Python: /usr/lib/kicad/lib/python3/dist-packages
Node.js: /usr/bin/node
@@ -47,6 +50,7 @@ Python: /usr/bin/python3
```
**Configuration example:**
```json
{
"mcpServers": {
@@ -64,12 +68,14 @@ Python: /usr/bin/python3
### Windows Installation
**Advantages:**
- Automated setup script handles everything
- KiCAD includes bundled Python (no system Python needed)
- Better error diagnostics
- Comprehensive troubleshooting guide
**Process:**
1. Install KiCAD 9.0 from official installer
2. Install Node.js from official installer
3. Clone repository
@@ -82,6 +88,7 @@ Python: /usr/bin/python3
- Validates setup
**Typical paths:**
```powershell
KiCAD Python: C:\Program Files\KiCad\9.0\bin\python.exe
KiCAD Libraries: C:\Program Files\KiCad\9.0\lib\python3\dist-packages
@@ -89,6 +96,7 @@ Node.js: C:\Program Files\nodejs\node.exe
```
**Configuration example:**
```json
{
"mcpServers": {
@@ -108,12 +116,14 @@ Node.js: C:\Program Files\nodejs\node.exe
## Path Handling
### Linux Paths
- Use forward slashes: `/home/user/project`
- Case-sensitive filesystem
- No drive letters
- Symbolic links commonly used
**Example commands:**
```bash
cd /home/username/KiCAD-MCP-Server
export PYTHONPATH=/usr/lib/kicad/lib/python3/dist-packages
@@ -121,6 +131,7 @@ python3 -c "import pcbnew"
```
### Windows Paths
- Use backslashes in native commands: `C:\Users\username`
- Use double backslashes in JSON: `C:\\Users\\username`
- OR use forward slashes in JSON: `C:/Users/username`
@@ -128,6 +139,7 @@ python3 -c "import pcbnew"
- Drive letters required (C:, D:, etc.)
**Example commands:**
```powershell
cd C:\Users\username\KiCAD-MCP-Server
$env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages"
@@ -135,6 +147,7 @@ $env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages"
```
**JSON configuration notes:**
```json
// Wrong - single backslash will cause errors
"args": ["C:\Users\name\project"]
@@ -153,11 +166,13 @@ $env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages"
### Linux
**System Python:**
- Usually Python 3.10+ available system-wide
- KiCAD uses system Python with additional modules
- Virtual environments recommended for isolation
**Setup:**
```bash
# Check Python version
python3 --version
@@ -175,6 +190,7 @@ pip install -r requirements.txt
```
**PYTHONPATH:**
```bash
# Temporary (current session)
export PYTHONPATH=/usr/lib/kicad/lib/python3/dist-packages
@@ -186,11 +202,13 @@ echo 'export PYTHONPATH=/usr/lib/kicad/lib/python3/dist-packages' >> ~/.bashrc
### Windows
**KiCAD Bundled Python:**
- KiCAD 9.0 includes Python 3.11
- No system Python installation needed
- Use KiCAD's Python for all MCP operations
**Setup:**
```powershell
# Check KiCAD Python
& "C:\Program Files\KiCad\9.0\bin\python.exe" --version
@@ -203,6 +221,7 @@ echo 'export PYTHONPATH=/usr/lib/kicad/lib/python3/dist-packages' >> ~/.bashrc
```
**PYTHONPATH:**
```powershell
# Temporary (current session)
$env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages"
@@ -222,18 +241,21 @@ $env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages"
### Linux
**Check KiCAD installation:**
```bash
which kicad
kicad --version
```
**Check Python module:**
```bash
python3 -c "import sys; print(sys.path)"
python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())"
```
**Run tests:**
```bash
cd /home/username/KiCAD-MCP-Server
npm test
@@ -241,11 +263,13 @@ pytest tests/
```
**View logs:**
```bash
tail -f ~/.kicad-mcp/logs/kicad_interface.log
```
**Start server manually:**
```bash
export PYTHONPATH=/usr/lib/kicad/lib/python3/dist-packages
node dist/index.js
@@ -254,28 +278,33 @@ node dist/index.js
### Windows
**Check KiCAD installation:**
```powershell
Test-Path "C:\Program Files\KiCad\9.0"
& "C:\Program Files\KiCad\9.0\bin\kicad.exe" --version
```
**Check Python module:**
```powershell
& "C:\Program Files\KiCad\9.0\bin\python.exe" -c "import sys; print(sys.path)"
& "C:\Program Files\KiCad\9.0\bin\python.exe" -c "import pcbnew; print(pcbnew.GetBuildVersion())"
```
**Run automated diagnostics:**
```powershell
.\setup-windows.ps1
```
**View logs:**
```powershell
Get-Content "$env:USERPROFILE\.kicad-mcp\logs\kicad_interface.log" -Tail 50 -Wait
```
**Start server manually:**
```powershell
$env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages"
node dist\index.js
@@ -288,6 +317,7 @@ node dist\index.js
### Linux-Specific Issues
**1. Permission Errors**
```bash
# Fix file permissions
chmod +x python/kicad_interface.py
@@ -297,6 +327,7 @@ chmod -R 755 ~/KiCAD-MCP-Server
```
**2. PYTHONPATH Not Set**
```bash
# Check current PYTHONPATH
echo $PYTHONPATH
@@ -306,6 +337,7 @@ find /usr -name "pcbnew.py" 2>/dev/null
```
**3. KiCAD Not in PATH**
```bash
# Add to PATH temporarily
export PATH=$PATH:/usr/bin
@@ -315,6 +347,7 @@ export PATH=$PATH:/usr/bin
```
**4. Library Dependencies**
```bash
# Install missing system libraries
sudo apt-get install python3-wxgtk4.0 python3-cairo
@@ -326,11 +359,13 @@ ldd /usr/lib/kicad/lib/python3/dist-packages/pcbnew.so
### Windows-Specific Issues
**1. Server Exits Immediately**
- Most common issue
- Usually means pcbnew import failed
- Solution: Run `setup-windows.ps1` for diagnostics
**2. Path Issues in Configuration**
```powershell
# Test path accessibility
Test-Path "C:\Users\name\KiCAD-MCP-Server\dist\index.js"
@@ -340,6 +375,7 @@ cd C:\Users\[TAB]
```
**3. PowerShell Execution Policy**
```powershell
# Check current policy
Get-ExecutionPolicy
@@ -349,6 +385,7 @@ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```
**4. Antivirus Blocking**
```
Windows Defender may block Node.js or Python processes
Solution: Add exclusion for project directory in Windows Security
@@ -359,12 +396,14 @@ Solution: Add exclusion for project directory in Windows Security
## Performance Considerations
### Linux
- Generally faster file I/O operations
- Better process management
- Lower memory overhead
- Native Unix socket support (future IPC backend)
### Windows
- Slightly slower file operations
- More memory overhead
- Extra startup validation checks (for diagnostics)
@@ -379,6 +418,7 @@ Solution: Add exclusion for project directory in Windows Security
### Linux Development Environment
**Typical workflow:**
```bash
# Start development
cd ~/KiCAD-MCP-Server
@@ -395,6 +435,7 @@ python3 python/kicad_interface.py
```
**Recommended tools:**
- Terminal: GNOME Terminal, Konsole, or Alacritty
- Editor: VSCode with Python and TypeScript extensions
- Process monitoring: `htop` or `top`
@@ -403,6 +444,7 @@ python3 python/kicad_interface.py
### Windows Development Environment
**Typical workflow:**
```powershell
# Start development
cd C:\Users\username\KiCAD-MCP-Server
@@ -419,6 +461,7 @@ npm test
```
**Recommended tools:**
- Terminal: Windows Terminal or PowerShell 7
- Editor: VSCode with Python and TypeScript extensions
- Process monitoring: Task Manager or Process Explorer
@@ -471,12 +514,14 @@ npm test
## Getting Help
### Linux Support
- Check: [README.md](../README.md) Linux installation section
- Read: [KNOWN_ISSUES.md](./KNOWN_ISSUES.md)
- Search: GitHub Issues filtered by `linux` label
- Community: Linux users in Discussions
### Windows Support
- Check: [README.md](../README.md) Windows installation section
- Read: [WINDOWS_TROUBLESHOOTING.md](./WINDOWS_TROUBLESHOOTING.md)
- Run: `setup-windows.ps1` for automated diagnostics
@@ -488,12 +533,14 @@ npm test
## Summary
**Choose Linux if:**
- You're comfortable with command-line tools
- You want the most stable, tested environment
- You're developing or contributing to the project
- You need maximum performance
**Choose Windows if:**
- You want automated setup and diagnostics
- You're less comfortable with terminal commands
- You need detailed troubleshooting guidance
@@ -504,9 +551,11 @@ npm test
---
**For platform-specific installation instructions, see:**
- Linux: [README.md - Linux Installation](../README.md#linux-ubuntudebian)
- Windows: [README.md - Windows Installation](../README.md#windows-1011)
**For troubleshooting:**
- Linux: [KNOWN_ISSUES.md](./KNOWN_ISSUES.md)
- Windows: [WINDOWS_TROUBLESHOOTING.md](./WINDOWS_TROUBLESHOOTING.md)

View File

@@ -36,6 +36,7 @@ The MCP server uses KiCAD's Python API (`pcbnew` module) to read and write `.kic
**Use case:** Claude places components via MCP, human sees them in KiCAD UI
1. **Claude places components** via MCP tools:
```python
# MCP internally uses:
board = pcbnew.LoadBoard('project.kicad_pcb')
@@ -51,6 +52,7 @@ The MCP server uses KiCAD's Python API (`pcbnew` module) to read and write `.kic
- Components appear instantly ✅
**Example:**
```
User: "Place a 10k resistor at position 30, 30mm"
Claude: [uses place_component MCP tool]
@@ -74,6 +76,7 @@ User: [opens KiCAD UI]
- KiCAD writes changes to `.kicad_pcb` file
3. **Claude reads changes** via MCP tools:
```python
# MCP internally uses:
board = pcbnew.LoadBoard('project.kicad_pcb')
@@ -88,6 +91,7 @@ User: [opens KiCAD UI]
- New traces and nets
**Example:**
```
User: "I moved R1 to a new position, can you see it?"
Claude: [uses get_board_info MCP tool]
@@ -100,11 +104,13 @@ Claude: [uses get_board_info MCP tool]
### Test 1: MCP→UI (Verified ✅)
**Setup:**
- Created new board via MCP (100x80mm)
- Placed R1 (10k resistor) at (30, 30) mm
- Placed D1 (RED LED) at (50, 30) mm
**Result:**
- Opened KiCAD PCB editor
- Both components visible at correct positions ✅
- All properties (reference, value, rotation) correct ✅
@@ -112,10 +118,12 @@ Claude: [uses get_board_info MCP tool]
### Test 2: UI→MCP (Verified ✅)
**Setup:**
- User moved R1 from (30, 30) mm to (59.175, 49.0) mm in UI
- User saved file (Ctrl+S)
**Result:**
- MCP read board via `get_board_info`
- New position detected correctly ✅
- D1 position unchanged (as expected) ✅
@@ -136,11 +144,13 @@ Claude: [uses get_board_info MCP tool]
### MCP Tools for Collaboration
**Reading board state:**
- `get_board_info` - Get all components and their positions
- `get_project_info` - Get project metadata
- `list_components` - List all components (if implemented)
**Modifying board:**
- `place_component` - Add new components
- `add_trace` - Add copper traces
- `add_via` - Add vias
@@ -188,6 +198,7 @@ Claude: [uses get_board_info MCP tool]
### For AI-Human Collaboration
1. **Establish Turn-Taking:**
```
User: "I'm going to add some components, one sec"
[User edits in UI]
@@ -201,6 +212,7 @@ Claude: [uses get_board_info MCP tool]
- Claude: Always read fresh before making decisions
3. **Communicate Changes:**
```
Claude: "I'm placing R1-R4 now..."
[MCP places components]
@@ -215,6 +227,7 @@ Claude: [uses get_board_info MCP tool]
### Workflow Patterns
**Pattern 1: AI Does Layout, Human Reviews**
```
1. Claude places all components via MCP
2. Claude routes critical traces via MCP
@@ -225,6 +238,7 @@ Claude: [uses get_board_info MCP tool]
```
**Pattern 2: Human Sketches, AI Refines**
```
1. Human places major components in UI
2. Saves → Claude reads layout
@@ -235,6 +249,7 @@ Claude: [uses get_board_info MCP tool]
```
**Pattern 3: Pair Programming Style**
```
User: "Place a 10k pull-up resistor on pin 3"
Claude: [places R1 at calculated position]
@@ -276,6 +291,7 @@ Claude: [places D1]
### Long-Term Vision
**Fully Real-Time Collaboration:**
- Both AI and human see changes instantly
- No manual save/reload required
- Conflict detection and resolution
@@ -283,6 +299,7 @@ Claude: [places D1]
- Chat/comment system for design discussion
**Example Future Workflow:**
```
[Claude and human both have board open]
Claude: [starts placing R1]
@@ -299,6 +316,7 @@ Claude: "Good position for D1! I'll route them now"
### File Format
KiCAD uses S-expression format (`.kicad_pcb`):
```lisp
(kicad_pcb (version 20240108) (generator "pcbnew")
(footprint "Resistor_SMD:R_0603_1608Metric"
@@ -314,11 +332,13 @@ KiCAD uses S-expression format (`.kicad_pcb`):
### Sync Mechanism
**Current (File-based):**
1. MCP: `pcbnew.SaveBoard(path, board)` → writes file
2. UI: File → Revert → reads file
3. Latency: ~1-5 seconds (manual)
**Future (IPC-based):**
1. MCP: `kicad.AddFootprint(...)` → sends IPC command
2. KiCAD: Receives command → updates internal state
3. UI: Automatically refreshes display
@@ -355,6 +375,7 @@ pcbnew.SaveBoard('project.kicad_pcb', board)
**Cause:** UI hasn't reloaded the file
**Solution:**
1. File → Revert (or Ctrl+R if configured)
2. Or close PCB editor and reopen
3. Or restart KiCAD
@@ -364,6 +385,7 @@ pcbnew.SaveBoard('project.kicad_pcb', board)
**Cause:** File not saved
**Solution:**
1. Save file: Ctrl+S or File → Save
2. Verify save: Check file modification time
3. Ask Claude to read board again
@@ -373,6 +395,7 @@ pcbnew.SaveBoard('project.kicad_pcb', board)
**Cause:** File overwritten by other party
**Solution:**
1. Always save before asking MCP to make changes
2. Don't edit while MCP is working
3. Take turns to avoid conflicts
@@ -382,6 +405,7 @@ pcbnew.SaveBoard('project.kicad_pcb', board)
**Cause:** Unit conversion error or coordinate system mismatch
**Solution:**
1. Check KiCAD units (View → Switch Units)
2. MCP uses millimeters internally
3. Report issue if positions consistently wrong
@@ -410,6 +434,7 @@ The KiCAD MCP Server successfully enables paired circuit board design between AI
## Changelog
**2025-11-01 - v2.1.0-alpha**
- Tested MCPUI workflow (placing components via MCP, viewing in UI)
- Tested UIMCP workflow (editing in UI, reading via MCP)
- Documented best practices and limitations

View File

@@ -10,6 +10,7 @@
## Completed Milestones
### v1.0.0 - Core Foundation (October 2025)
- [x] MCP protocol implementation (JSON-RPC 2.0, MCP 2025-06-18)
- [x] Project management (create, open, save)
- [x] Board operations (size, outline, layers, mounting holes, text)
@@ -21,12 +22,14 @@
- [x] UI auto-launch and detection
### v2.0.0-alpha - Router and IPC (November-December 2025)
- [x] Tool router pattern -- 70% AI context reduction
- [x] IPC backend for real-time KiCAD UI synchronization (21 commands)
- [x] Hybrid SWIG/IPC backend with automatic fallback
- [x] Comprehensive Windows support with automated setup
### v2.1.0-alpha - Schematics and JLCPCB (January 2026)
- [x] Complete schematic workflow fix (Issue #26)
- [x] Dynamic symbol loading -- access to all ~10,000 KiCad symbols
- [x] Intelligent wiring system with pin discovery and smart routing
@@ -36,6 +39,7 @@
- [x] Local symbol library search (contributor: @l3wi)
### v2.2.0 through v2.2.3 - Routing, Creators, Autorouting (February-March 2026)
- [x] 13 new routing/component tools (delete/query/modify traces, arrays, alignment)
- [x] route_pad_to_pad with auto-via insertion for cross-layer connections
- [x] copy_routing_pattern for trace replication
@@ -57,12 +61,14 @@
## Current Focus: v2.3+
### Documentation Overhaul (In Progress)
- [ ] Per-feature documentation for all 122 tools
- [ ] Architecture guide for contributors
- [ ] End-to-end PCB design workflow guide
- [ ] Documentation index
### Quality and Stability
- [ ] Expand test coverage across all tool categories
- [ ] Performance profiling for large boards
- [ ] Update package.json version to match CHANGELOG
@@ -72,24 +78,28 @@
## Planned Features
### Supplier Integration
- [ ] Digikey API integration
- [ ] Mouser API integration
- [ ] Smart BOM management with real-time pricing
- [ ] Cost optimization across suppliers
### Design Patterns and Templates
- [ ] Circuit patterns library (voltage regulators, USB, microcontrollers)
- [ ] Board templates (Arduino shields, RPi HATs, Feather wings)
- [ ] Auto-suggest trace widths by current
- [ ] Impedance-controlled trace support
### Advanced Capabilities
- [ ] Panelization support
- [ ] Multi-board project management
- [ ] High-speed design helpers (length matching, via stitching)
- [ ] SPICE simulation integration
### Community and Education
- [ ] Example project gallery with tutorials
- [ ] Video walkthrough series
- [ ] Interactive beginner tutorials
@@ -111,4 +121,4 @@ Check [CONTRIBUTING.md](../CONTRIBUTING.md) for details.
---
*Maintained by: KiCAD MCP Team and community contributors*
_Maintained by: KiCAD MCP Team and community contributors_

View File

@@ -71,9 +71,11 @@ These cover the primary workflow (80%+ of use cases):
### Routed Categories (8+ categories, 110+ tools)
#### 1. `board` - Board Configuration & Layout (9 tools)
Setup and configuration operations.
**Tools:**
- `add_layer` - Add PCB layer
- `set_active_layer` - Set active layer
- `get_layer_list` - List all layers
@@ -85,9 +87,11 @@ Setup and configuration operations.
- `launch_kicad_ui` - Launch KiCAD UI
#### 2. `component` - Advanced Component Operations (8 tools)
Beyond basic placement.
**Tools:**
- `rotate_component` - Rotate component
- `delete_component` - Delete component
- `edit_component` - Edit component properties
@@ -98,9 +102,11 @@ Beyond basic placement.
- `replace_component` - Replace component with another
#### 3. `export` - File Export & Manufacturing (8 tools)
Generate output files for fabrication and documentation.
**Tools:**
- `export_gerber` - Export Gerber files
- `export_pdf` - Export PDF
- `export_svg` - Export SVG
@@ -111,9 +117,11 @@ Generate output files for fabrication and documentation.
- `export_vrml` - Export VRML 3D model
#### 4. `drc` - Design Rules & Validation (9 tools)
Design rule checking and electrical validation.
**Tools:**
- `set_design_rules` - Configure design rules
- `get_design_rules` - Get current rules
- `run_drc` - Run design rule check
@@ -124,9 +132,11 @@ Design rule checking and electrical validation.
- `get_drc_violations` - Get DRC violations
#### 5. `schematic` - Schematic Operations (9 tools)
Schematic editor operations.
**Tools:**
- `create_schematic` - Create new schematic
- `add_schematic_component` - Add component to schematic
- `add_wire` - Add wire connection
@@ -137,18 +147,22 @@ Schematic editor operations.
- `generate_netlist` - Generate netlist
#### 6. `library` - Footprint Library Access (4 tools)
Search and browse footprint libraries.
**Tools:**
- `list_libraries` - List available libraries
- `search_footprints` - Search footprints
- `list_library_footprints` - List library footprints
- `get_footprint_info` - Get footprint details
#### 7. `routing` - Advanced Routing (3 tools)
Advanced routing operations beyond basic trace routing.
**Tools:**
- `add_via` - Add via
- `add_copper_pour` - Add copper pour
@@ -157,11 +171,13 @@ Advanced routing operations beyond basic trace routing.
## Router Tools
### 1. `list_tool_categories`
**Description:** List all available tool categories with descriptions and tool counts.
**Parameters:** None
**Returns:**
```json
{
"total_categories": 7,
@@ -171,19 +187,22 @@ Advanced routing operations beyond basic trace routing.
"name": "board",
"description": "Board configuration: layers, mounting holes, zones, visualization",
"tool_count": 9
},
}
// ... more categories
]
}
```
### 2. `get_category_tools`
**Description:** Get detailed information about all tools in a specific category.
**Parameters:**
- `category` (string) - Category name from `list_tool_categories`
**Returns:**
```json
{
"category": "export",
@@ -192,29 +211,36 @@ Advanced routing operations beyond basic trace routing.
{
"name": "export_gerber",
"description": "Export Gerber files for PCB fabrication",
"parameters": { /* zod schema */ }
},
"parameters": {
/* zod schema */
}
}
// ... more tools
]
}
```
### 3. `execute_tool`
**Description:** Execute a tool from any category.
**Parameters:**
- `tool_name` (string) - Tool name from `get_category_tools`
- `params` (object, optional) - Tool parameters
**Returns:** Tool execution result
### 4. `search_tools`
**Description:** Search for tools by keyword across all categories.
**Parameters:**
- `query` (string) - Search term (e.g., "gerber", "zone", "export")
**Returns:**
```json
{
"query": "export",
@@ -224,7 +250,7 @@ Advanced routing operations beyond basic trace routing.
"category": "export",
"tool": "export_gerber",
"description": "Export Gerber files for PCB fabrication"
},
}
// ... more matches
]
}
@@ -261,21 +287,25 @@ Advanced routing operations beyond basic trace routing.
## Migration Strategy
### Phase 1: Create Infrastructure
1. Create `registry.ts` with all tool definitions
2. Create `router.ts` with router tools
3. Create `direct.ts` with direct tool list
### Phase 2: Update Server
1. Modify server registration to use direct + router only
2. Keep all existing tool handlers intact
3. Route through `execute_tool`
### Phase 3: Testing
1. Test direct tools work as before
2. Test router tools (list/get/execute/search)
3. Test routed tools via `execute_tool`
### Phase 4: Optimization (Optional)
1. Add caching for tool lookups
2. Add tool usage analytics
3. Implement intelligent tool suggestions

View File

@@ -7,10 +7,12 @@ The KiCAD MCP Server includes an intelligent tool router that organizes 122+ too
## How It Works
Instead of loading all 59 tool schemas into every conversation, Claude now sees:
- **12 direct tools** for high-frequency operations (always visible)
- **4 router tools** for discovering and executing the other 47 tools
When you ask Claude to do something (like "export gerber files"), it will:
1. Search for relevant tools using `search_tools`
2. Find the `export_gerber` tool in the "export" category
3. Execute it via `execute_tool` with your parameters
@@ -23,42 +25,56 @@ When you ask Claude to do something (like "export gerber files"), it will:
The 110+ routed tools are organized into these categories:
### 1. board (9 tools)
Board configuration: layers, mounting holes, zones, visualization
- add_layer, set_active_layer, get_layer_list
- add_mounting_hole, add_board_text
- add_zone, get_board_extents, get_board_2d_view
- launch_kicad_ui
### 2. component (8 tools)
Advanced component operations: edit, delete, search, group, annotate
- rotate_component, delete_component, edit_component
- find_component, get_component_properties
- add_component_annotation, group_components, replace_component
### 3. export (8 tools)
File export for fabrication and documentation
- export_gerber, export_pdf, export_svg, export_3d
- export_bom, export_netlist, export_position_file, export_vrml
### 4. drc (8 tools)
Design rule checking and electrical validation
- set_design_rules, get_design_rules, run_drc
- add_net_class, assign_net_to_class, set_layer_constraints
- check_clearance, get_drc_violations
### 5. schematic (8 tools)
Schematic operations: create, add components, wire connections
- create_schematic, add_schematic_component, add_wire
- add_schematic_connection, add_schematic_net_label
- connect_to_net, get_net_connections, generate_netlist
### 6. library (4 tools)
Footprint library access and search
- list_libraries, search_footprints
- list_library_footprints, get_footprint_info
### 7. routing (2 tools)
Advanced routing operations
- add_via, add_copper_pour
## Direct Tools (Always Available)
@@ -66,47 +82,58 @@ Advanced routing operations
These 12 tools are always visible for common operations:
**Project Lifecycle:**
- create_project, open_project, save_project, get_project_info
**Core PCB Operations:**
- place_component, move_component
- add_net, route_trace
- get_board_info, set_board_size
- add_board_outline
**UI Management:**
- check_kicad_ui
## Router Tools
### list_tool_categories
Browse all available tool categories.
**Example:**
```
Claude, what tool categories are available?
```
### get_category_tools
View all tools in a specific category.
**Example:**
```
Show me all export tools available.
```
### search_tools
Find tools by keyword.
**Example:**
```
Search for tools related to "gerber" or "mounting holes"
```
### execute_tool
Execute any routed tool with parameters.
**Example:**
```
Execute the export_gerber tool with outputDir set to ./fabrication
```
@@ -114,6 +141,7 @@ Execute the export_gerber tool with outputDir set to ./fabrication
## Usage Examples
### Natural Interaction (Recommended)
Just ask Claude what you want - it handles discovery automatically:
```
@@ -124,6 +152,7 @@ Just ask Claude what you want - it handles discovery automatically:
```
### Manual Discovery (Optional)
You can also browse tools explicitly:
```
@@ -147,6 +176,7 @@ You can also browse tools explicitly:
- **Server Integration**: `src/server.ts` - Router tools registered at startup
For implementation details, see:
- [ROUTER_ARCHITECTURE.md](ROUTER_ARCHITECTURE.md) - Design specification
- [ROUTER_IMPLEMENTATION_STATUS.md](ROUTER_IMPLEMENTATION_STATUS.md) - Current status
- [TOOL_INVENTORY.md](TOOL_INVENTORY.md) - Complete tool catalog
@@ -154,9 +184,11 @@ For implementation details, see:
## Token Savings
**Before Router:**
- 122 tools × ~700 tokens each = ~85K tokens per conversation
**After Router (Current):**
- 12 direct tools + 4 router tools = 16 tools visible
- Routed tools discovered on-demand
- ~12-15K tokens per conversation

View File

@@ -13,16 +13,18 @@ Create a new net on the PCB.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | -------------- |
| name | string | Yes | Net name |
| netClass | string | No | Net class name |
**Usage Notes:**
- Creates a new net that can be assigned to traces and pads
- If the net already exists, it will be reused
- Net class assignment is optional; defaults to "Default" if not specified
**Example:**
```json
{
"name": "VCC_3V3",
@@ -39,7 +41,7 @@ Route a trace segment between two XY points on a fixed layer.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | ------------------------------------------- |
| start | object | Yes | Start position with x, y, and optional unit |
| end | object | Yes | End position with x, y, and optional unit |
| layer | string | Yes | PCB layer |
@@ -47,16 +49,18 @@ Route a trace segment between two XY points on a fixed layer.
| net | string | Yes | Net name |
**Usage Notes:**
- WARNING: Does NOT handle layer changes
- If start and end are on different copper layers, use `route_pad_to_pad` instead, which automatically inserts a via
- Coordinates use mm by default unless unit is specified
- This is a low-level tool; prefer `route_pad_to_pad` for component-to-component routing
**Example:**
```json
{
"start": {"x": 100.0, "y": 50.0, "unit": "mm"},
"end": {"x": 120.0, "y": 50.0, "unit": "mm"},
"start": { "x": 100.0, "y": 50.0, "unit": "mm" },
"end": { "x": 120.0, "y": 50.0, "unit": "mm" },
"layer": "F.Cu",
"width": 0.25,
"net": "GND"
@@ -72,7 +76,7 @@ PREFERRED tool for pad-to-pad routing. Looks up pad positions automatically, det
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------------- | -------- | ---------------------------------------------------- |
| fromRef | string | Yes | Reference of the source component (e.g. 'U2') |
| fromPad | string/number | Yes | Pad number on the source component (e.g. '6' or 6) |
| toRef | string | Yes | Reference of the target component (e.g. 'U1') |
@@ -82,6 +86,7 @@ PREFERRED tool for pad-to-pad routing. Looks up pad positions automatically, det
| net | string | No | Net name override (default: auto-detected from pad) |
**Usage Notes:**
- This is the PREFERRED tool for routing between component pads
- Automatically looks up pad positions - no need to query them separately
- Auto-detects the net from the source pad
@@ -90,6 +95,7 @@ PREFERRED tool for pad-to-pad routing. Looks up pad positions automatically, det
- Via is placed at the start pad's X coordinate to avoid stacking issues with back-to-back mirrored connectors
**Example:**
```json
{
"fromRef": "U2",
@@ -111,21 +117,23 @@ Add a via to the PCB.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | ----------------------------------------- |
| position | object | Yes | Via position with x, y, and optional unit |
| net | string | Yes | Net name |
| viaType | string | No | Via type: "through", "blind", or "buried" |
**Usage Notes:**
- Through vias connect all layers (default)
- Blind vias connect an outer layer to one or more inner layers
- Buried vias connect two or more inner layers without reaching outer layers
- Position coordinates use mm by default
**Example:**
```json
{
"position": {"x": 110.0, "y": 50.0, "unit": "mm"},
"position": { "x": 110.0, "y": 50.0, "unit": "mm" },
"net": "GND",
"viaType": "through"
}
@@ -142,7 +150,7 @@ Route a differential pair between two sets of points.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ----------- | ------ | -------- | ------------------------------------------ |
| positivePad | object | Yes | Positive pad with reference and pad number |
| negativePad | object | Yes | Negative pad with reference and pad number |
| layer | string | Yes | PCB layer |
@@ -152,16 +160,18 @@ Route a differential pair between two sets of points.
| negativeNet | string | Yes | Negative net name |
**Usage Notes:**
- Used for high-speed signals like USB, Ethernet, HDMI, etc.
- Maintains controlled impedance through consistent trace width and gap
- Both traces are routed in parallel with specified separation
- Pad object format: `{"reference": "U1", "pad": "1"}`
**Example:**
```json
{
"positivePad": {"reference": "J1", "pad": "2"},
"negativePad": {"reference": "J1", "pad": "3"},
"positivePad": { "reference": "J1", "pad": "2" },
"negativePad": { "reference": "J1", "pad": "3" },
"layer": "F.Cu",
"width": 0.2,
"gap": 0.2,
@@ -179,13 +189,14 @@ Copy routing pattern (traces and vias) from a group of source components to a ma
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ----------- | ------------- | -------- | ----------------------------------------------------------------------------------------- |
| sourceRefs | array[string] | Yes | References of the source components (e.g. ['U1', 'R1', 'C1']) |
| targetRefs | array[string] | Yes | References of the target components in same order as sourceRefs (e.g. ['U2', 'R2', 'C2']) |
| includeVias | boolean | No | Also copy vias (default: true) |
| traceWidth | number | No | Override trace width in mm (default: keep original width) |
**Usage Notes:**
- The offset is calculated automatically from the position difference between the first source and first target component
- Useful for replicating routing between identical circuit blocks
- Component arrays must be in matching order (sourceRefs[0] maps to targetRefs[0], etc.)
@@ -194,6 +205,7 @@ Copy routing pattern (traces and vias) from a group of source components to a ma
- Original trace widths are preserved unless traceWidth override is specified
**Example:**
```json
{
"sourceRefs": ["U1", "R1", "C1"],
@@ -213,17 +225,19 @@ Get a list of all nets in the PCB with optional statistics.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------ | ------- | -------- | ---------------------------------------------------- |
| includeStats | boolean | No | Include statistics (track count, total length, etc.) |
| unit | string | No | Unit for length measurements: "mm" or "inch" |
**Usage Notes:**
- Returns all nets present in the board
- Statistics include track count, via count, and total trace length
- Useful for verifying net connectivity and routing completeness
- Length measurements default to mm
**Example:**
```json
{
"includeStats": true,
@@ -240,7 +254,7 @@ Create a new net class with custom design rules.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ----------- | ------ | -------- | ------------------------- |
| name | string | Yes | Net class name |
| traceWidth | number | No | Default trace width in mm |
| clearance | number | No | Clearance in mm |
@@ -248,12 +262,14 @@ Create a new net class with custom design rules.
| viaDrill | number | No | Via drill size in mm |
**Usage Notes:**
- Net classes define design rules for groups of nets
- Common use cases: power nets (wider traces), high-speed signals (controlled impedance)
- Once created, assign nets to the class using the netClass parameter in `add_net`
- All measurements in mm
**Example:**
```json
{
"name": "Power",
@@ -275,7 +291,7 @@ Delete traces from the PCB. Can delete by UUID, position, or bulk-delete all tra
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ----------- | ------- | -------- | ----------------------------------------------------------- |
| traceUuid | string | No | UUID of a specific trace to delete |
| position | object | No | Delete trace nearest to this position (x, y, optional unit) |
| net | string | No | Delete all traces on this net (bulk delete) |
@@ -283,12 +299,14 @@ Delete traces from the PCB. Can delete by UUID, position, or bulk-delete all tra
| includeVias | boolean | No | Include vias in net-based deletion |
**Usage Notes:**
- Three deletion modes: by UUID (specific), by position (nearest), or by net (bulk)
- Position-based deletion finds the closest trace to the specified coordinates
- Net-based deletion can be filtered by layer
- Vias are excluded from net-based deletion by default unless includeVias is true
**Example (bulk delete):**
```json
{
"net": "GND",
@@ -306,19 +324,21 @@ Query traces on the board with optional filters by net, layer, or bounding box.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ----------- | ------ | -------- | ------------------------------------------------------------- |
| net | string | No | Filter by net name |
| layer | string | No | Filter by layer name |
| boundingBox | object | No | Filter by bounding box region (x1, y1, x2, y2, optional unit) |
| unit | string | No | Unit for coordinates: "mm" or "inch" |
**Usage Notes:**
- Returns trace information including UUID, position, width, layer, and net
- Filters can be combined (e.g., specific net on specific layer)
- Bounding box uses rectangular region defined by opposite corners
- Useful for analyzing routing in specific board regions or on specific nets
**Example:**
```json
{
"net": "VCC_3V3",
@@ -335,19 +355,21 @@ Modify an existing trace (change width, layer, or net).
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | --------------------------- |
| traceUuid | string | Yes | UUID of the trace to modify |
| width | number | No | New trace width in mm |
| layer | string | No | New layer name |
| net | string | No | New net name |
**Usage Notes:**
- Requires the trace UUID, which can be obtained from `query_traces`
- At least one modification parameter (width, layer, or net) must be provided
- Use with caution when changing nets - ensure electrical correctness
- Width changes are useful for adjusting impedance or current capacity
**Example:**
```json
{
"traceUuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
@@ -366,13 +388,14 @@ Add a copper pour (ground/power plane) to the PCB.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------------- | -------- | ----------------------------------------------------------------------------------------- |
| layer | string | Yes | PCB layer |
| net | string | Yes | Net name |
| clearance | number | No | Clearance in mm |
| outline | array[object] | No | Array of {x, y} points defining the pour boundary. If omitted, the board outline is used. |
**Usage Notes:**
- Copper pours are typically used for ground and power planes
- If no outline is specified, the pour fills the entire board area
- Custom outlines are defined as arrays of coordinate points
@@ -380,16 +403,17 @@ Add a copper pour (ground/power plane) to the PCB.
- After adding a pour, use `refill_zones` to fill it
**Example:**
```json
{
"layer": "B.Cu",
"net": "GND",
"clearance": 0.2,
"outline": [
{"x": 10.0, "y": 10.0},
{"x": 90.0, "y": 10.0},
{"x": 90.0, "y": 60.0},
{"x": 10.0, "y": 60.0}
{ "x": 10.0, "y": 10.0 },
{ "x": 90.0, "y": 10.0 },
{ "x": 90.0, "y": 60.0 },
{ "x": 10.0, "y": 60.0 }
]
}
```
@@ -405,6 +429,7 @@ Refill all copper zones on the board.
None
**Usage Notes:**
- WARNING: SWIG path has known segfault risk (see KNOWN_ISSUES.md)
- Prefer using IPC backend (KiCAD open) or triggering zone fill via KiCAD UI instead
- Required after adding or modifying copper pours to calculate the filled areas
@@ -412,6 +437,7 @@ None
- May take several seconds on complex boards with many zones
**Example:**
```json
{}
```
@@ -439,6 +465,7 @@ The simplest and most robust approach for connecting component pads:
```
This automatically:
- Looks up the exact pad positions
- Detects the net from the pads
- Creates the trace on the appropriate layer

View File

@@ -8,10 +8,11 @@ This document provides a complete reference for the 27 schematic tools in the Ki
## Component Operations (8 tools)
### add_schematic_component
Add a component to the schematic. Symbol format is 'Library:SymbolName' (e.g., 'Device:R', 'EDA-MCP:ESP32-C3').
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ---------------------------------------------------------------- |
| schematicPath | string | Yes | Path to the schematic file |
| symbol | string | Yes | Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3) |
| reference | string | Yes | Component reference (e.g., R1, U1) |
@@ -22,18 +23,20 @@ Add a component to the schematic. Symbol format is 'Library:SymbolName' (e.g., '
**Usage Notes:** The dynamic symbol loader provides access to ~10,000 KiCad standard symbols. If a symbol is not in the static template map, it will be loaded dynamically from the specified library.
### delete_schematic_component
Remove a placed symbol from a KiCAD schematic (.kicad_sch). This removes the symbol instance (the placed component) from the schematic. It does NOT remove the symbol definition from lib_symbols. Note: This tool operates on schematic files (.kicad_sch). To remove a footprint from a PCB, use delete_component instead.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ------------------------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Reference designator of the component to remove (e.g. R1, U3) |
### edit_schematic_component
Update properties of a placed symbol in a KiCAD schematic (.kicad_sch) in-place. Use this tool to assign or update a footprint, change the value, or rename the reference of an already-placed component. This is more efficient than delete + re-add because it preserves the component's position and UUID. Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_component.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Current reference designator of the component (e.g. R1, U3) |
| footprint | string | No | New KiCAD footprint string (e.g. Resistor_SMD:R_0603_1608Metric) |
@@ -42,64 +45,71 @@ Update properties of a placed symbol in a KiCAD schematic (.kicad_sch) in-place.
| fieldPositions | object | No | Reposition field labels: map of field name to {x, y, angle} (e.g. {"Reference": {"x": 12.5, "y": 17.0}}) |
### get_schematic_component
Get full component info from a schematic: position, field values, and each field's label position (at x/y/angle). Use this to inspect or prepare repositioning of Reference/Value labels.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | -------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Component reference designator (e.g. R1, U1) |
### list_schematic_components
List all components in a schematic with their references, values, positions, and pins. Essential for inspecting what's on the schematic before making edits.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ---------------------- | ------ | -------- | --------------------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| filter | object | No | Optional filters with libId and/or referencePrefix fields |
| filter.libId | string | No | Filter by library ID (e.g., 'Device:R') |
| filter.referencePrefix | string | No | Filter by reference prefix (e.g., 'R', 'C', 'U') |
### move_schematic_component
Move a placed symbol to a new position in the schematic.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Reference designator (e.g., R1, U1) |
| position | object | Yes | New position with x and y coordinates |
### rotate_schematic_component
Rotate a placed symbol in the schematic.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Reference designator (e.g., R1, U1) |
| angle | number | Yes | Rotation angle in degrees (0, 90, 180, 270) |
| mirror | enum | No | Optional mirror axis ("x" or "y") |
### annotate_schematic
Assign reference designators to unannotated components (R? → R1, R2, ...). Must be called before tools that require known references.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
## Wiring and Connections (8 tools)
### add_wire
Add a wire connection in the schematic.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | --------------------------------------- |
| start | object | Yes | Start position with x and y coordinates |
| end | object | Yes | End position with x and y coordinates |
### add_schematic_connection
Connect two component pins with a wire. Use this for individual connections between components with different pin roles (e.g. U1.SDA → J3.2). WARNING: Do NOT use this in a loop to wire N passthrough pins — use connect_passthrough instead (single call, cleaner layout, far fewer tokens).
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ---------------------------------------- |
| schematicPath | string | Yes | Path to the schematic file |
| sourceRef | string | Yes | Source component reference (e.g., R1) |
| sourcePin | string | Yes | Source pin name/number (e.g., 1, 2, GND) |
@@ -107,19 +117,21 @@ Connect two component pins with a wire. Use this for individual connections betw
| targetPin | string | Yes | Target pin name/number (e.g., 1, 2, VCC) |
### add_schematic_net_label
Add a net label to the schematic.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ------------------------------------------ |
| schematicPath | string | Yes | Path to the schematic file |
| netName | string | Yes | Name of the net (e.g., VCC, GND, SIGNAL_1) |
| position | array | Yes | Position [x, y] for the label |
### connect_to_net
Connect a component pin to a named net.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ---------------------------------- |
| schematicPath | string | Yes | Path to the schematic file |
| componentRef | string | Yes | Component reference (e.g., U1, R1) |
| pinName | string | Yes | Pin name/number to connect |
@@ -128,10 +140,11 @@ Connect a component pin to a named net.
**Usage Notes:** Creates a wire stub from the pin and places a net label at the stub endpoint. The stub direction follows the pin's outward angle. Default stub length is 2.54mm (0.1 inch, standard grid spacing).
### connect_passthrough
Connects all pins of a source connector (e.g. J1) to matching pins of a target connector (e.g. J2) via shared net labels — pin N gets net '{netPrefix}_{N}'. Use this for FFC/ribbon cable passthrough adapters instead of calling connect_to_net for every pin.
Connects all pins of a source connector (e.g. J1) to matching pins of a target connector (e.g. J2) via shared net labels — pin N gets net '{netPrefix}\_{N}'. Use this for FFC/ribbon cable passthrough adapters instead of calling connect_to_net for every pin.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | --------------------------------------------------------- |
| schematicPath | string | Yes | Path to the schematic file |
| sourceRef | string | Yes | Source connector reference (e.g. J1) |
| targetRef | string | Yes | Target connector reference (e.g. J2) |
@@ -141,27 +154,30 @@ Connects all pins of a source connector (e.g. J1) to matching pins of a target c
**Usage Notes:** This is the most efficient way to wire passthrough adapters. For an N-pin connector, this replaces N individual connect_to_net calls with a single operation.
### get_schematic_pin_locations
Returns the exact x/y coordinates of every pin on a schematic component. Use this before add_schematic_net_label to place labels correctly on pin endpoints.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ------------------------------------------------ |
| schematicPath | string | Yes | Path to the schematic file |
| reference | string | Yes | Component reference designator (e.g. U1, R1, J2) |
### delete_schematic_wire
Remove a wire from the schematic by start and end coordinates.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | -------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| start | object | Yes | Wire start position with x and y coordinates |
| end | object | Yes | Wire end position with x and y coordinates |
### delete_schematic_net_label
Remove a net label from the schematic.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | -------------------------------------------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| netName | string | Yes | Name of the net label to remove |
| position | object | No | Position to disambiguate if multiple labels with same name (x and y coordinates) |
@@ -169,79 +185,88 @@ Remove a net label from the schematic.
## Net Analysis (4 tools)
### get_net_connections
Get all connections for a named net.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | -------------------------- |
| schematicPath | string | Yes | Path to the schematic file |
| netName | string | Yes | Name of the net to query |
**Usage Notes:** Uses wire graph analysis to find all component pins connected to the specified net. Returns a list of {component, pin} pairs.
### list_schematic_nets
List all nets in the schematic with their connections.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
### list_schematic_wires
List all wires in the schematic with start/end coordinates.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
### list_schematic_labels
List all net labels, global labels, and power flags in the schematic.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
## Schematic Creation and Export (5 tools)
### create_schematic
Create a new schematic.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| --------- | ------ | -------- | -------------- |
| name | string | Yes | Schematic name |
| path | string | No | Optional path |
### export_schematic_svg
Export schematic to SVG format using kicad-cli.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------- | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| outputPath | string | Yes | Output SVG file path |
| blackAndWhite | boolean | No | Export in black and white |
### export_schematic_pdf
Export schematic to PDF format using kicad-cli.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------- | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| outputPath | string | Yes | Output PDF file path |
| blackAndWhite | boolean | No | Export in black and white |
### get_schematic_view
Return a rasterized image of the schematic (PNG by default, or SVG). Uses kicad-cli to export SVG, then converts to PNG via cairosvg. Use this for visual feedback after placing or wiring components.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | -------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file |
| format | enum | No | Output format ("png" or "svg", default: png) |
| width | number | No | Image width in pixels (default: 1200) |
| height | number | No | Image height in pixels (default: 900) |
### generate_netlist
Generate a netlist from the schematic.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | -------------------------- |
| schematicPath | string | Yes | Path to the schematic file |
**Usage Notes:** Returns a complete netlist with component information (reference, value, footprint) and net connections (net name with all connected component/pin pairs).
@@ -249,19 +274,21 @@ Generate a netlist from the schematic.
## Validation and Synchronization (3 tools)
### run_erc
Runs the KiCAD Electrical Rules Check (ERC) on a schematic and returns all violations. Use after wiring to verify the schematic before generating a netlist.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch schematic file |
**Usage Notes:** Returns violations categorized by severity (error, warning, info) with location coordinates. Essential for catching design errors before PCB layout.
### sync_schematic_to_board
Import the schematic netlist into the PCB board — equivalent to pressing F8 in KiCAD (Tools → Update PCB from Schematic). MUST be called after the schematic is complete and before placing or routing components on the PCB. Without this step, the board has no footprints and no net assignments — place_component and route_pad_to_pad will produce an empty, unroutable board.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ------------- | ------ | -------- | ---------------------------------------------- |
| schematicPath | string | Yes | Absolute path to the .kicad_sch schematic file |
| boardPath | string | Yes | Absolute path to the .kicad_pcb board file |
@@ -270,6 +297,7 @@ Import the schematic netlist into the PCB board — equivalent to pressing F8 in
## Example Workflows
### Basic Circuit Design
1. **Create project:** Use `create_schematic` to initialize a new schematic file
2. **Add components:** Use `add_schematic_component` to place resistors, capacitors, ICs, etc.
- Example: Add a resistor with `symbol: "Device:R"`, `reference: "R1"`, `value: "10k"`
@@ -281,6 +309,7 @@ Import the schematic netlist into the PCB board — equivalent to pressing F8 in
7. **Sync to PCB:** Use `sync_schematic_to_board` to transfer the design to the PCB layout
### FFC Passthrough Adapter
1. **Add connectors:** Place two FFC connectors using `add_schematic_component`
- Example: J1 and J2, both 20-pin FFC connectors
2. **Connect passthrough:** Use `connect_passthrough` with `sourceRef: "J1"`, `targetRef: "J2"`, `netPrefix: "CSI"`

View File

@@ -9,7 +9,7 @@
## Quick Stats
| Metric | Value |
|--------|-------|
| -------------------- | --------------------------- |
| Total MCP Tools | 122 |
| Tool Categories | 16 |
| KiCAD 9.0 Compatible | Yes (verified) |
@@ -25,7 +25,7 @@
## Feature Completion Matrix
| Feature Category | Status | Tool Count | Details |
|-----------------|--------|------------|---------|
| ------------------- | -------- | ---------- | ----------------------------------------------------------------------- |
| Project Management | Complete | 5 | Create, open, save, info, snapshot |
| Board Setup | Complete | 12 | Size, outline, layers, mounting holes, zones, text, 2D view, SVG import |
| Component Placement | Complete | 16 | Place, move, rotate, delete, edit, find, pads, arrays, align, duplicate |
@@ -48,6 +48,7 @@
## Architecture
### SWIG Backend (File-based) -- Default
- **Status:** Stable
- Direct pcbnew API access via KiCAD's Python bindings
- Requires manual KiCAD UI reload to see changes
@@ -55,13 +56,16 @@
- Auto-saves after every board-modifying command
### IPC Backend (Real-time) -- Experimental
- **Status:** Functional, 21 commands implemented
- Real-time UI synchronization with KiCAD 9+
- Requires KiCAD running with IPC API enabled
- Automatic fallback to SWIG when unavailable
### Hybrid Approach
The server automatically selects the best backend:
- IPC when KiCAD is running with IPC enabled
- SWIG fallback when IPC is unavailable
- Some operations use both (e.g., footprint placement)
@@ -71,18 +75,21 @@ The server automatically selects the best backend:
## Platform Support
### Linux -- Primary Platform
- KiCAD 9.0 detection: Working
- Process management: Working
- Library discovery: Working (153+ libraries)
- IPC backend: Working
### Windows -- Fully Supported
- Automated setup script (setup-windows.ps1)
- Process detection via Toolhelp32 API
- Library paths auto-detected
- Troubleshooting guide available (WINDOWS_TROUBLESHOOTING.md)
### macOS -- Community Supported
- Configuration provided
- Process detection implemented
- Library paths configured
@@ -93,6 +100,7 @@ The server automatically selects the best backend:
## Recent Development Highlights
### v2.2.3 (2026-03-11)
- FFC/ribbon cable passthrough workflow (connect_passthrough, sync_schematic_to_board)
- Project snapshot system
- SVG logo import
@@ -101,20 +109,24 @@ The server automatically selects the best backend:
- Critical B.Cu routing fixes
### v2.2.2-alpha (2026-03-01)
- route_pad_to_pad with auto-via insertion
- copy_routing_pattern for trace replication
- Project-local library resolution
### v2.2.1-alpha (2026-02-28)
- edit_schematic_component with field position support
- Footprint and symbol creator tools
### v2.2.0-alpha (2026-02-27)
- 13 new routing/component tools
- Datasheet enrichment tools
- SWIG/UUID bug fixes
### v2.1.0-alpha (2026-01-10)
- Complete schematic wiring system
- Dynamic symbol loading (~10,000 symbols)
- JLCPCB parts integration
@@ -125,7 +137,7 @@ The server automatically selects the best backend:
## Community Contributors
| Contributor | Key Contributions |
|------------|-------------------|
| ------------- | ------------------------------------------------------------------------------ |
| Kletternaut | Routing tools, footprint/symbol creators, passthrough workflow, template fixes |
| Mehanik | Schematic inspection/editing tools, component field positions |
| jflaflamme | Freerouting autorouter integration with Docker/Podman |
@@ -141,18 +153,21 @@ The server automatically selects the best backend:
## Getting Help
**For Users:**
1. Check [README.md](../README.md) for installation
2. Review [KNOWN_ISSUES.md](KNOWN_ISSUES.md) for common problems
3. Check logs: `~/.kicad-mcp/logs/kicad_interface.log`
**For Contributors:**
1. Read [CONTRIBUTING.md](../CONTRIBUTING.md) for development setup
2. Check [ARCHITECTURE.md](ARCHITECTURE.md) for system design
3. Review the [Documentation Index](INDEX.md) for all available docs
**Issues:**
- Open an issue on GitHub with OS, KiCAD version, and error details
---
*Last Updated: 2026-03-21*
_Last Updated: 2026-03-21_

View File

@@ -15,7 +15,7 @@ Imports an SVG file as filled graphic polygons onto a PCB layer. Curves are line
**Parameters:**
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| ------------- | ------- | -------- | ------- | ----------------------------------------------------------------------- |
| `pcbPath` | string | Yes | -- | Path to the .kicad_pcb file |
| `svgPath` | string | Yes | -- | Path to the SVG logo file |
| `x` | number | Yes | -- | X position of the logo top-left corner in mm |
@@ -26,6 +26,7 @@ Imports an SVG file as filled graphic polygons onto a PCB layer. Curves are line
| `filled` | boolean | No | true | Fill polygons with solid color |
**Returns:**
- Polygon count
- Final dimensions (width x height in mm)
- Layer used
@@ -35,18 +36,21 @@ Imports an SVG file as filled graphic polygons onto a PCB layer. Curves are line
## SVG Requirements
### Supported Features
- Path elements with M, L, H, V, C, S, Q, T, A, Z commands
- Filled shapes (polygons, rectangles, circles, ellipses)
- Nested groups and transforms
- Cubic and quadratic Bezier curves (linearized automatically)
### Recommendations
- Use simple, solid shapes -- avoid complex gradients or filters
- Convert text to paths/outlines before importing
- Ensure shapes are filled (not just stroked) for best results
- Keep the SVG clean -- remove unnecessary metadata and layers
### What Will Not Work
- Raster images embedded in SVG
- CSS-based styling (inline style attributes are preferred)
- Complex SVG filters or effects
@@ -59,11 +63,13 @@ Imports an SVG file as filled graphic polygons onto a PCB layer. Curves are line
### 1. Prepare Your SVG
If starting from a raster image (PNG, JPG):
- Use a vector graphics editor (Inkscape, Illustrator, Figma) to trace the image
- In Inkscape: Path > Trace Bitmap to convert
- Export as plain SVG
If starting from a vector logo:
- Open in a vector editor
- Convert all text to paths (Object to Path / Create Outlines)
- Remove unnecessary layers and hidden elements
@@ -88,7 +94,7 @@ Re-run `import_svg_logo` with different position, width, or layer parameters.
## Layer Options
| Layer | Use Case |
|-------|----------|
| --------- | ----------------------------------------------------- |
| `F.SilkS` | Front silkscreen (most common for logos) |
| `B.SilkS` | Back silkscreen |
| `F.Cu` | Front copper (logo as exposed copper) |

View File

@@ -16,10 +16,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Project Management (5 tools)
*Source: `src/tools/project.ts`*
_Source: `src/tools/project.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ------------------ | ---------------------------------------------------------------- | ------ |
| `create_project` | Create a new KiCAD project (.kicad_pro, .kicad_pcb, .kicad_sch) | Direct |
| `open_project` | Open an existing KiCAD project | Direct |
| `save_project` | Save the current project | Direct |
@@ -30,10 +30,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Board Management (12 tools)
*Source: `src/tools/board.ts`*
_Source: `src/tools/board.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ------------------- | ----------------------------------------------------------------- | -------------- |
| `set_board_size` | Set PCB dimensions (width, height, unit) | Direct |
| `add_board_outline` | Add board outline (rectangle, circle, polygon, rounded_rectangle) | Direct |
| `get_board_info` | Get board metadata and properties | Direct |
@@ -51,10 +51,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Component Management (16 tools)
*Source: `src/tools/component.ts`*
_Source: `src/tools/component.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| -------------------------- | ------------------------------------------------------------- | ------------------ |
| `place_component` | Place footprint on PCB (position, rotation, reference, value) | Direct |
| `move_component` | Move component to new position | Direct |
| `rotate_component` | Rotate component (absolute angle) | Routed (component) |
@@ -76,10 +76,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Routing (13 tools)
*Source: `src/tools/routing.ts`*
_Source: `src/tools/routing.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ------------------------- | ---------------------------------------------------- | ---------------- |
| `add_net` | Create a new net on the PCB | Direct |
| `route_trace` | Route trace segment between XY points (single layer) | Direct |
| `add_via` | Add via (through/blind/buried) | Routed (routing) |
@@ -98,10 +98,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Design Rules and DRC (8 tools)
*Source: `src/tools/design-rules.ts`*
_Source: `src/tools/design-rules.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ----------------------- | ----------------------------------------------------------- | ------------ |
| `set_design_rules` | Set global design rules (clearance, track width, via sizes) | Routed (drc) |
| `get_design_rules` | Get current design rules | Routed (drc) |
| `run_drc` | Run design rule check | Routed (drc) |
@@ -115,10 +115,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Export (8 tools)
*Source: `src/tools/export.ts`*
_Source: `src/tools/export.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ---------------------- | ------------------------------------------------- | --------------- |
| `export_gerber` | Export Gerber files for fabrication | Routed (export) |
| `export_pdf` | Export PDF with layer selection and page size | Routed (export) |
| `export_svg` | Export SVG vector graphics | Routed (export) |
@@ -132,12 +132,12 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Schematic (27 tools)
*Source: `src/tools/schematic.ts`*
_Source: `src/tools/schematic.ts`_
### Component Operations
| Tool | Description | Access |
|------|-------------|--------|
| ---------------------------- | ------------------------------------------------------- | ------------------ |
| `add_schematic_component` | Add component to schematic (symbol from library) | Direct |
| `delete_schematic_component` | Remove component from schematic | Additional |
| `edit_schematic_component` | Edit component properties (footprint, value, reference) | Additional |
@@ -150,7 +150,7 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
### Wiring and Connections
| Tool | Description | Access |
|------|-------------|--------|
| ----------------------------- | ------------------------------------------------ | ------------------ |
| `add_wire` | Add wire connection between two points | Routed (schematic) |
| `delete_schematic_wire` | Delete wire segment | Routed (schematic) |
| `add_schematic_connection` | Connect two component pins with wire | Routed (schematic) |
@@ -163,7 +163,7 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
### Net Analysis
| Tool | Description | Access |
|------|-------------|--------|
| ----------------------- | ----------------------------- | ------------------ |
| `get_net_connections` | Get all connections for a net | Routed (schematic) |
| `list_schematic_nets` | List all nets in schematic | Routed (schematic) |
| `list_schematic_wires` | List all wires in schematic | Routed (schematic) |
@@ -172,7 +172,7 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
### Schematic Creation and Export
| Tool | Description | Access |
|------|-------------|--------|
| ---------------------- | -------------------------------- | ------------------ |
| `create_schematic` | Create a new schematic file | Routed (schematic) |
| `get_schematic_view` | Get schematic as image (PNG/SVG) | Routed (schematic) |
| `export_schematic_svg` | Export schematic to SVG | Routed (schematic) |
@@ -181,7 +181,7 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
### Validation and Synchronization
| Tool | Description | Access |
|------|-------------|--------|
| ------------------------- | ----------------------------------------------------- | ------------------ |
| `run_erc` | Run electrical rule check | Additional |
| `generate_netlist` | Generate netlist from schematic | Routed (schematic) |
| `sync_schematic_to_board` | Sync schematic components/nets to PCB (F8 equivalent) | Direct |
@@ -190,10 +190,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Footprint Libraries (4 tools)
*Source: `src/tools/library.ts`*
_Source: `src/tools/library.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ------------------------- | ------------------------------------- | ---------------- |
| `list_libraries` | List all footprint libraries | Routed (library) |
| `search_footprints` | Search footprints across libraries | Routed (library) |
| `list_library_footprints` | List footprints in a specific library | Routed (library) |
@@ -203,10 +203,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Symbol Libraries (4 tools)
*Source: `src/tools/library-symbol.ts`*
_Source: `src/tools/library-symbol.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ----------------------- | ----------------------------------------------- | ---------- |
| `list_symbol_libraries` | List all symbol libraries from sym-lib-table | Additional |
| `search_symbols` | Search symbols by name, LCSC ID, or description | Additional |
| `list_library_symbols` | List symbols in a specific library | Additional |
@@ -216,10 +216,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Footprint Creator (4 tools)
*Source: `src/tools/footprint.ts`*
_Source: `src/tools/footprint.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ---------------------------- | ------------------------------------------------------------------------ | ---------- |
| `create_footprint` | Create custom .kicad_mod footprint (SMD/THT pads, courtyard, silkscreen) | Additional |
| `edit_footprint_pad` | Edit pad in existing footprint (size, position, drill, shape) | Additional |
| `register_footprint_library` | Register .pretty library in fp-lib-table | Additional |
@@ -229,10 +229,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Symbol Creator (4 tools)
*Source: `src/tools/symbol-creator.ts`*
_Source: `src/tools/symbol-creator.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ------------------------- | ------------------------------------------------------------- | ---------- |
| `create_symbol` | Create custom .kicad_sym symbol (pins, rectangles, polylines) | Additional |
| `delete_symbol` | Remove symbol from library | Additional |
| `list_symbols_in_library` | List all symbols in a .kicad_sym file | Additional |
@@ -242,10 +242,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Datasheet Tools (2 tools)
*Source: `src/tools/datasheet.ts`*
_Source: `src/tools/datasheet.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ------------------- | --------------------------------------------------- | ---------- |
| `enrich_datasheets` | Fill missing datasheet URLs using LCSC part numbers | Additional |
| `get_datasheet_url` | Get LCSC datasheet URL for a component | Additional |
@@ -253,10 +253,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## JLCPCB Integration (5 tools)
*Source: `src/tools/jlcpcb-api.ts`*
_Source: `src/tools/jlcpcb-api.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ----------------------------- | ------------------------------------------------------- | ---------- |
| `download_jlcpcb_database` | Download 2.5M+ parts catalog to local SQLite database | Additional |
| `search_jlcpcb_parts` | Search parts by specs (category, package, library type) | Additional |
| `get_jlcpcb_part` | Get detailed part info with pricing | Additional |
@@ -267,10 +267,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Freerouting Autorouter (4 tools)
*Source: `src/tools/freerouting.ts`*
_Source: `src/tools/freerouting.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ------------------- | ---------------------------------------------------------- | ------------------ |
| `autoroute` | Run Freerouting autorouter (export DSN, route, import SES) | Routed (autoroute) |
| `export_dsn` | Export Specctra DSN file for manual routing | Routed (autoroute) |
| `import_ses` | Import routed SES file back into PCB | Routed (autoroute) |
@@ -280,10 +280,10 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## UI Management (2 tools)
*Source: `src/tools/ui.ts`*
_Source: `src/tools/ui.ts`_
| Tool | Description | Access |
|------|-------------|--------|
| ----------------- | ----------------------------------------- | -------------- |
| `check_kicad_ui` | Check if KiCAD UI is running | Direct |
| `launch_kicad_ui` | Launch KiCAD UI (optionally with project) | Routed (board) |
@@ -291,12 +291,12 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Router Tools (4 tools)
*Source: `src/tools/router.ts`*
_Source: `src/tools/router.ts`_
These meta-tools provide discovery and execution of routed tools:
| Tool | Description |
|------|-------------|
| ---------------------- | ------------------------------------ |
| `list_tool_categories` | Browse all available tool categories |
| `get_category_tools` | View tools in a specific category |
| `search_tools` | Find tools by keyword |
@@ -307,7 +307,7 @@ These meta-tools provide discovery and execution of routed tools:
## Summary by Access Type
| Access Type | Count | Description |
|-------------|-------|-------------|
| ----------- | ------- | --------------------------------------------------- |
| Direct | 18 | Always visible, no router needed |
| Routed | 65 | Discovered via router, invoked via `execute_tool` |
| Router | 4 | Meta-tools for discovering and running routed tools |
@@ -317,7 +317,7 @@ These meta-tools provide discovery and execution of routed tools:
## Summary by Category
| Category | Tool Count |
|----------|------------|
| -------------------- | ---------- |
| Project Management | 5 |
| Board Management | 12 |
| Component Management | 16 |

View File

@@ -7,6 +7,7 @@ Automatically detect and launch KiCAD UI when needed, providing seamless visual
## 🎯 Overview
The KiCAD MCP server can now:
- ✅ Detect if KiCAD UI is running
- ✅ Launch KiCAD automatically when needed
- ✅ Open projects directly in the UI
@@ -49,6 +50,7 @@ Check if KiCAD is currently running.
**Parameters:** None
**Example:**
```typescript
{
"command": "check_kicad_ui",
@@ -57,6 +59,7 @@ Check if KiCAD is currently running.
```
**Response:**
```json
{
"success": true,
@@ -77,10 +80,12 @@ Check if KiCAD is currently running.
Launch KiCAD UI, optionally with a project file.
**Parameters:**
- `projectPath` (optional): Path to `.kicad_pcb` file to open
- `autoLaunch` (optional): Whether to launch if not running (default: true)
**Example:**
```typescript
{
"command": "launch_kicad_ui",
@@ -91,6 +96,7 @@ Launch KiCAD UI, optionally with a project file.
```
**Response:**
```json
{
"success": true,
@@ -158,7 +164,7 @@ Claude:
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| ------------------- | ----------- | ------------------------------ |
| `KICAD_AUTO_LAUNCH` | `false` | Auto-launch KiCAD when needed |
| `KICAD_EXECUTABLE` | auto-detect | Override KiCAD executable path |
@@ -182,16 +188,19 @@ If KiCAD is installed in a non-standard location:
### Process Detection
**Linux:**
```bash
pgrep -f "pcbnew|kicad"
```
**macOS:**
```bash
pgrep -f "KiCad|pcbnew"
```
**Windows:**
```powershell
tasklist /FI "IMAGENAME eq pcbnew.exe"
```
@@ -201,15 +210,18 @@ tasklist /FI "IMAGENAME eq pcbnew.exe"
The system searches for KiCAD in:
**Linux:**
- `/usr/bin/pcbnew`
- `/usr/local/bin/pcbnew`
- `/usr/bin/kicad`
**macOS:**
- `/Applications/KiCad/KiCad.app/Contents/MacOS/kicad`
- `/Applications/KiCad/pcbnew.app/Contents/MacOS/pcbnew`
**Windows:**
- `C:/Program Files/KiCad/9.0/bin/pcbnew.exe`
- `C:/Program Files/KiCad/8.0/bin/pcbnew.exe`
@@ -229,6 +241,7 @@ The system searches for KiCAD in:
### 1. Beginner-Friendly Workflow
User doesn't need to know how to launch KiCAD manually:
```
User: "Help me design a simple LED board"
Claude: [Auto-launches KiCAD, creates project, designs board]
@@ -237,6 +250,7 @@ Claude: [Auto-launches KiCAD, creates project, designs board]
### 2. Streamlined Iteration
For rapid prototyping with visual feedback:
```
1. Claude creates board → KiCAD opens
2. User sees board, requests changes
@@ -247,6 +261,7 @@ For rapid prototyping with visual feedback:
### 3. Batch Processing
Process multiple designs without manual intervention:
```python
for design in designs:
create_project(design)
@@ -263,6 +278,7 @@ for design in designs:
### KiCAD Doesn't Launch
**Check executable path:**
```bash
# Linux/macOS
which pcbnew
@@ -272,6 +288,7 @@ where pcbnew.exe
```
**Override if needed:**
```json
{
"env": {
@@ -283,6 +300,7 @@ where pcbnew.exe
### Process Detection Fails
**Manual check:**
```bash
# Linux/macOS
ps aux | grep kicad
@@ -292,6 +310,7 @@ tasklist | findstr kicad
```
**Verify permissions:**
- Ensure user can execute `pgrep` (Linux/macOS)
- Ensure user can execute `tasklist` (Windows)
@@ -309,17 +328,20 @@ tasklist | findstr kicad
### Files Modified/Created
**New Files:**
- `python/utils/kicad_process.py` - Process management utilities
- `src/tools/ui.ts` - MCP tool definitions
- `docs/UI_AUTO_LAUNCH.md` - This documentation
**Modified Files:**
- `python/kicad_interface.py` - Added UI command handlers
- `src/server.ts` - Registered UI tools
### API Reference
**Python:**
```python
from utils.kicad_process import KiCADProcessManager, check_and_launch_kicad
@@ -341,6 +363,7 @@ result = check_and_launch_kicad(
```
**MCP Tools:**
```typescript
// Check status
await callKicadScript("check_kicad_ui", {});
@@ -348,7 +371,7 @@ await callKicadScript("check_kicad_ui", {});
// Launch
await callKicadScript("launch_kicad_ui", {
projectPath: "/path/to/project.kicad_pcb",
autoLaunch: true
autoLaunch: true,
});
```
@@ -367,6 +390,7 @@ await callKicadScript("launch_kicad_ui", {
### IPC Mode (Coming Weeks 2-3)
When IPC backend is fully implemented:
```
KiCAD runs in background → MCP connects via IPC → Real-time updates
No file reloading needed! Changes appear instantly.
@@ -377,6 +401,7 @@ No file reloading needed! Changes appear instantly.
## 📝 Summary
**Before this feature:**
```
User manually launches KiCAD
User manually opens project
@@ -385,6 +410,7 @@ User manually reloads
```
**After this feature:**
```
User: "Design a board"
→ KiCAD auto-launches with project

View File

@@ -15,6 +15,7 @@ This document explains how to see changes made by the MCP server in the KiCAD UI
### Setup
1. **Open your project in KiCAD PCB Editor**
```bash
pcbnew /tmp/kicad_test_project/New_Project.kicad_pcb
```
@@ -77,12 +78,14 @@ Claude MCP → IPC Socket → Running KiCAD → Instant UI Update
- Restart KiCAD
2. **Install kicad-python** (Already installed ✓)
```bash
pip install kicad-python
```
3. **Configure MCP Server**
Add to your MCP config:
```json
{
"env": {
@@ -98,7 +101,7 @@ Claude MCP → IPC Socket → Running KiCAD → Instant UI Update
### Current IPC Status
| Feature | Status |
|---------|--------|
| -------------------- | ----------- |
| Connection to KiCAD | ✅ Working |
| Version checking | ✅ Working |
| Project operations | ⏳ Week 2-3 |
@@ -124,6 +127,7 @@ This will print a message each time the MCP server saves changes.
## 💡 Tips for Best Experience
### 1. Side-by-Side Windows
```
┌──────────────────┬──────────────────┐
│ Claude Code │ KiCAD PCB │
@@ -134,16 +138,20 @@ This will print a message each time the MCP server saves changes.
```
### 2. Quick Reload Workflow
- Keep KiCAD focused in one window
- Make changes via Claude in another
- Press Alt+Tab → Click "Reload" → See changes
- Repeat
### 3. Save Frequently
The MCP server auto-saves after each operation, so changes are immediately available for reload.
### 4. Verify Before Complex Operations
For complex changes (multiple components, routing, etc.):
1. Make the change
2. Reload in KiCAD
3. Verify it looks correct
@@ -167,6 +175,7 @@ For complex changes (multiple components, routing, etc.):
**Cause:** KiCAD has the file open exclusively
**Solution:**
- KiCAD should allow external modifications
- If not, close the file in KiCAD, let MCP make changes, then reopen

View File

@@ -12,6 +12,7 @@ This guide helps diagnose and fix common issues when setting up KiCAD MCP Server
```
This script will:
- Detect your KiCAD installation
- Verify all prerequisites
- Install dependencies
@@ -34,12 +35,15 @@ If the automated setup fails, continue with the manual troubleshooting below.
**Solution:**
1. **Check the log file** (this has the actual error):
```
%USERPROFILE%\.kicad-mcp\logs\kicad_interface.log
```
Open in Notepad and look at the last 50-100 lines.
2. **Test pcbnew import manually:**
```powershell
& "C:\Program Files\KiCad\9.0\bin\python.exe" -c "import pcbnew; print(pcbnew.GetBuildVersion())"
```
@@ -73,6 +77,7 @@ If the automated setup fails, continue with the manual troubleshooting below.
**Solution:**
1. **Check if KiCAD is installed:**
```powershell
Test-Path "C:\Program Files\KiCad\9.0"
```
@@ -99,6 +104,7 @@ If the automated setup fails, continue with the manual troubleshooting below.
**Solution:**
1. **Check if Node.js is installed:**
```powershell
node --version
npm --version
@@ -124,6 +130,7 @@ If the automated setup fails, continue with the manual troubleshooting below.
**Solution:**
1. **Clean and reinstall dependencies:**
```powershell
Remove-Item node_modules -Recurse -Force
Remove-Item package-lock.json -Force
@@ -132,6 +139,7 @@ If the automated setup fails, continue with the manual troubleshooting below.
```
2. **Check Node.js version:**
```powershell
node --version # Should be v18.0.0 or higher
```
@@ -152,11 +160,13 @@ If the automated setup fails, continue with the manual troubleshooting below.
**Solution:**
1. **Install with KiCAD's Python:**
```powershell
& "C:\Program Files\KiCad\9.0\bin\python.exe" -m pip install -r requirements.txt
```
2. **If pip is not available:**
```powershell
# Download get-pip.py
Invoke-WebRequest -Uri https://bootstrap.pypa.io/get-pip.py -OutFile get-pip.py
@@ -197,6 +207,7 @@ If the automated setup fails, continue with the manual troubleshooting below.
**Symptom:** Config file paths not working
**Common mistakes:**
```json
// ❌ Wrong - single backslashes
"args": ["C:\Users\Name\KiCAD-MCP-Server\dist\index.js"]
@@ -224,6 +235,7 @@ If the automated setup fails, continue with the manual troubleshooting below.
KiCAD MCP requires Python 3.10+. KiCAD 9.0 includes Python 3.11, which is perfect.
**Always use KiCAD's bundled Python:**
```json
{
"mcpServers": {
@@ -315,6 +327,7 @@ print('SUCCESS!')
```
Expected output:
```
Python version: 3.11.x ...
pcbnew version: 9.0.0
@@ -364,6 +377,7 @@ Should show successful initialization with no errors.
### Enable Verbose Logging
Add to your MCP config:
```json
{
"env": {
@@ -412,9 +426,11 @@ if ($process.HasExited) {
If none of the above solutions work:
1. **Run the diagnostic script:**
```powershell
.\setup-windows.ps1
```
Copy the entire output.
2. **Collect log files:**
@@ -437,12 +453,14 @@ If none of the above solutions work:
1. **File paths are case-insensitive** but should match actual casing for best results
2. **Long path support** may be needed for deeply nested projects:
```powershell
# Enable long paths (requires admin)
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
```
3. **Windows Defender** may slow down file operations. Add exclusion:
```
Settings → Windows Security → Virus & threat protection → Exclusions
Add: C:\Users\YourName\KiCAD-MCP-Server

View File

@@ -1,4 +1,5 @@
# Build and Test Session Summary
**Date:** October 25, 2025 (Evening)
**Status:****SUCCESS**
@@ -17,6 +18,7 @@ Complete the MCP server build and test it with various MCP clients (Claude Deskt
**Problem:** Missing TypeScript source files preventing build
**Files Created:**
- `src/tools/project.ts` (80 lines)
- Registers MCP tools: `create_project`, `open_project`, `save_project`, `get_project_info`
@@ -40,6 +42,7 @@ Complete the MCP server build and test it with various MCP clients (Claude Deskt
**Problem:** Both `component.ts` and `library.ts` registered a resource named "component_details"
**Fix Applied:**
- Renamed library resource to `library_component_details`
- Updated URI template from `kicad://component/{componentId}` to `kicad://library/component/{componentId}`
@@ -52,11 +55,13 @@ Complete the MCP server build and test it with various MCP clients (Claude Deskt
### 3. **Successful Server Startup Test** 🚀
**Test Command:**
```bash
timeout --signal=TERM 3 node dist/index.js
```
**Server Output (All Green):**
```
[INFO] Using STDIO transport for local communication
[INFO] Registering KiCAD tools, resources, and prompts...
@@ -101,6 +106,7 @@ timeout --signal=TERM 3 node dist/index.js
**File Created:** `docs/CLIENT_CONFIGURATION.md` (500+ lines)
**Contents:**
- Platform-specific configurations:
- Linux (Ubuntu/Debian, Arch)
- macOS (with KiCAD.app paths)
@@ -153,6 +159,7 @@ timeout --signal=TERM 3 node dist/index.js
**Addition:** New "Configuration for Other Clients" section after Quick Start
**Changes:**
- Added links to CLIENT_CONFIGURATION.md guide
- Listed all supported MCP clients (Claude Desktop, Cline, Claude Code)
- Highlighted that KiCAD MCP works with ANY MCP-compatible client
@@ -167,6 +174,7 @@ timeout --signal=TERM 3 node dist/index.js
### Files Created/Modified (This Session)
**New Files (5):**
```
src/tools/project.ts # 80 lines
src/tools/routing.ts # 100 lines
@@ -177,6 +185,7 @@ docs/BUILD_AND_TEST_SESSION.md # This file
```
**Modified Files (5):**
```
src/resources/library.ts # Fixed duplicate registration
config/linux-config.example.json # Updated format
@@ -194,11 +203,13 @@ README.md # Added config guide section
### Generated Files
**TypeScript Compilation:**
- 72 JavaScript files in `dist/`
- 24 declaration files (`.d.ts`)
- 24 source maps (`.js.map`)
**Directory Structure:**
```
dist/
├── index.js (entry point)
@@ -215,12 +226,14 @@ dist/
## Verification Tests
### ✅ Test 1: TypeScript Compilation
```bash
npm run build
# Result: SUCCESS (no errors)
```
### ✅ Test 2: Server Startup
```bash
timeout --signal=TERM 3 node dist/index.js
# Result: SUCCESS (exit code 0)
@@ -233,6 +246,7 @@ timeout --signal=TERM 3 node dist/index.js
```
### ✅ Test 3: Python Integration
- Python process successfully spawned: `/home/chris/MCP/KiCAD-MCP-Server/python/kicad_interface.py`
- Using system Python: `python` (resolved to Python 3.12)
- No Python import errors during startup
@@ -244,6 +258,7 @@ timeout --signal=TERM 3 node dist/index.js
### MCP Server Capabilities
**Registered Tools (20+):**
- Project: create_project, open_project, save_project, get_project_info
- Board: set_board_size, add_board_outline, get_board_properties
- Component: add_component, move_component, rotate_component, get_component_list
@@ -253,6 +268,7 @@ timeout --signal=TERM 3 node dist/index.js
- Export: export_gerber, export_pdf, export_svg, export_3d_model
**Registered Resources (15+):**
- Project info and metadata
- Board info, layers, extents
- Board 2D/3D views (PNG, SVG)
@@ -260,6 +276,7 @@ timeout --signal=TERM 3 node dist/index.js
- Statistics and analytics
**Registered Prompts (10+):**
- Component selection guidance
- Routing strategy suggestions
- Design best practices
@@ -271,6 +288,7 @@ timeout --signal=TERM 3 node dist/index.js
### Immediate Testing (Ready Now)
1. **Test with Claude Code CLI:**
```bash
# Create config
mkdir -p ~/.config/claude-code
@@ -295,6 +313,7 @@ timeout --signal=TERM 3 node dist/index.js
### Integration Testing
**Test basic workflow:**
```
1. Create new project
2. Set board size
@@ -304,6 +323,7 @@ timeout --signal=TERM 3 node dist/index.js
```
**Test resources:**
```
1. Request board info
2. View 2D board rendering
@@ -318,6 +338,7 @@ timeout --signal=TERM 3 node dist/index.js
### 1. **Modular Tool Registration**
Each tool module follows consistent pattern:
```typescript
export function registerXxxTools(server: McpServer, callKicadScript: Function) {
server.tool("tool_name", "Description", schema, async (args) => {
@@ -328,6 +349,7 @@ export function registerXxxTools(server: McpServer, callKicadScript: Function) {
```
**Benefits:**
- Easy to add new tools
- Consistent error handling
- Clean separation of concerns
@@ -335,13 +357,15 @@ export function registerXxxTools(server: McpServer, callKicadScript: Function) {
### 2. **Resource Helper Utilities**
Abstracted common response patterns:
```typescript
createJsonResponse(data, uri) // For JSON data
createBinaryResponse(data, mime) // For images/binary
createErrorResponse(error, msg) // For errors
createJsonResponse(data, uri); // For JSON data
createBinaryResponse(data, mime); // For images/binary
createErrorResponse(error, msg); // For errors
```
**Benefits:**
- DRY principle (Don't Repeat Yourself)
- Consistent response format
- Easy to modify response structure
@@ -349,6 +373,7 @@ createErrorResponse(error, msg) // For errors
### 3. **STDIO Transport**
Using standard STDIO (stdin/stdout) for MCP protocol:
- No network ports required
- Maximum security (process isolation)
- Works with all MCP clients
@@ -357,6 +382,7 @@ Using standard STDIO (stdin/stdout) for MCP protocol:
### 4. **Python Subprocess Integration**
Server spawns Python process for KiCAD operations:
- Persistent Python process (faster than per-call spawn)
- JSON-RPC communication over stdin/stdout
- Proper error propagation
@@ -367,12 +393,14 @@ Server spawns Python process for KiCAD operations:
## Achievements
### Development Infrastructure ✅
- ✅ TypeScript build pipeline working
- ✅ All source files complete
- ✅ No compilation errors
- ✅ Source maps generated for debugging
### Server Functionality ✅
- ✅ MCP protocol implementation working
- ✅ STDIO transport connected
- ✅ Python subprocess integration
@@ -380,12 +408,14 @@ Server spawns Python process for KiCAD operations:
- ✅ Graceful startup and shutdown
### Documentation ✅
- ✅ Comprehensive client configuration guide
- ✅ Platform-specific examples
- ✅ Troubleshooting section
- ✅ Advanced configuration options
### Configuration ✅
- ✅ Linux config example
- ✅ Windows config example
- ✅ macOS config example
@@ -398,7 +428,7 @@ Server spawns Python process for KiCAD operations:
**Week 1 Progress:** 100% ✅
| Category | Status |
|----------|--------|
| ---------------------- | ------------- |
| TypeScript compilation | ✅ Complete |
| Server startup | ✅ Working |
| STDIO transport | ✅ Connected |
@@ -445,6 +475,7 @@ Server spawns Python process for KiCAD operations:
## Code Quality
**Metrics:**
- TypeScript strict mode: Enabled
- ESLint compliance: Clean
- Type coverage: 100% (all exports typed)
@@ -457,12 +488,14 @@ Server spawns Python process for KiCAD operations:
## Session Impact
### Before This Session:
- TypeScript wouldn't compile (missing files)
- Server had duplicate resource registration bug
- No client configuration documentation
- Unclear how to use with different MCP clients
### After This Session:
- Complete TypeScript build working
- Server starts cleanly with all features registered
- Comprehensive 500+ line configuration guide

View File

@@ -5,12 +5,14 @@
Replace the template-based schematic workflow with dynamic symbol loading from KiCad's installed symbol libraries. This would eliminate the 13-component limitation and provide access to ALL KiCad symbols (~10,000+ symbols from standard libraries).
**Current Status (Option 1):**
- ✅ Template-based approach working
- ✅ 13 component types supported
- ❌ Limited symbol variety
- ❌ Requires manual template updates for new types
**Proposed (Option 2):**
- 🎯 Dynamic loading from `.kicad_sym` library files
- 🎯 Access to ~10,000+ KiCad symbols
- 🎯 No template maintenance required
@@ -23,6 +25,7 @@ Replace the template-based schematic workflow with dynamic symbol loading from K
### kicad-skip Library Limitation
**Core Issue:** kicad-skip **cannot create symbols from scratch**. It can only:
1. Clone existing symbols from a loaded schematic
2. Modify properties of cloned symbols
@@ -62,20 +65,24 @@ KiCad symbol libraries are S-expression files containing symbol definitions:
### Standard KiCad Library Locations
**Linux:**
- System libraries: `/usr/share/kicad/symbols/`
- User libraries: `~/.local/share/kicad/8.0/symbols/` or `~/.config/kicad/8.0/symbols/`
**Windows:**
- System libraries: `C:\Program Files\KiCad\9.0\share\kicad\symbols\`
- User libraries: `%APPDATA%\kicad\8.0\symbols\`
**macOS:**
- System libraries: `/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/`
- User libraries: `~/Library/Preferences/kicad/8.0/symbols/`
### Standard Library Files
Common libraries (each containing 50-500 symbols):
- `Device.kicad_sym` - Passives (R, C, L, D, LED, Crystal, etc.)
- `Connector.kicad_sym` - Connectors (headers, USB, etc.)
- `Connector_Generic.kicad_sym` - Generic connectors
@@ -96,6 +103,7 @@ Common libraries (each containing 50-500 symbols):
**Goal:** Build an index of all available symbols and their locations
**Implementation:**
```python
class SymbolLibraryManager:
def __init__(self):
@@ -288,16 +296,19 @@ def add_schematic_component_dynamic(params):
## Advantages Over Template Approach
### ✅ Unlimited Symbol Access
- Access to ~10,000+ standard KiCad symbols
- Support for custom user libraries
- Support for 3rd-party libraries (JLCPCB, Espressif, etc.)
### ✅ No Maintenance Required
- Template doesn't need updates for new component types
- Automatically supports new KiCad library additions
- Works with custom symbol libraries
### ✅ Better User Experience
```
User: "Add an STM32F103C8T6 microcontroller at position 100,100"
AI: *Searches symbol index*
@@ -309,6 +320,7 @@ AI: *Searches symbol index*
```
### ✅ Flexible Symbol Search
```python
# Find all resistors
symbols = lib_manager.search_symbols(query="resistor")
@@ -327,6 +339,7 @@ symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
**Problem:** Directly manipulating S-expression data is error-prone
**Mitigation:**
- Use `sexpdata` library (already a dependency)
- Create helper functions for common operations
- Add comprehensive validation and error handling
@@ -337,6 +350,7 @@ symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
**Problem:** Loading/reloading schematics after injection could be slow
**Mitigation:**
- **Cache loaded symbols**: Once injected, symbol stays in schematic
- **Batch injection**: Inject multiple symbols at once
- **Lazy loading**: Only inject symbols when first used
@@ -346,6 +360,7 @@ symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
**Problem:** Some symbols may have complex pin configurations or multiple units
**Mitigation:**
- Start with simple 2-pin passives (R, C, L)
- Gradually add support for multi-pin ICs
- Handle multi-unit symbols (gates, OpAmp sections) explicitly
@@ -356,6 +371,7 @@ symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
**Problem:** KiCad symbol format may change between versions
**Mitigation:**
- Parse KiCad version from library files
- Version-specific handling if needed
- Fallback to template approach for unsupported formats
@@ -365,6 +381,7 @@ symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
## Implementation Phases
### Phase A: Proof of Concept (1-2 weeks)
- [ ] Create `SymbolLibraryManager` class
- [ ] Implement library discovery (Linux paths only)
- [ ] Implement symbol indexing
@@ -373,6 +390,7 @@ symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
- [ ] Test end-to-end with simple components
### Phase B: Core Functionality (2-3 weeks)
- [ ] Cross-platform library discovery (Windows, macOS)
- [ ] Symbol search functionality
- [ ] Template instance creation automation
@@ -381,6 +399,7 @@ symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
- [ ] Unit tests for all operations
### Phase C: MCP Integration (1 week)
- [ ] Create `add_schematic_component_dynamic` tool
- [ ] Update `search_symbols` to use library index
- [ ] Add `list_available_symbols` tool
@@ -388,6 +407,7 @@ symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
- [ ] Documentation and examples
### Phase D: Advanced Features (2-3 weeks)
- [ ] Multi-unit symbol support (e.g., quad OpAmps)
- [ ] Custom library registration
- [ ] Symbol caching and optimization
@@ -428,18 +448,21 @@ def add_schematic_component(params):
## Success Criteria
### Must Have
- [ ] Load symbols from Device.kicad_sym (passives)
- [ ] Support R, C, L, D, LED (5 core types)
- [ ] Cross-platform library discovery
- [ ] Proper error handling
### Should Have
- [ ] Support for all Device.kicad_sym symbols (~50 symbols)
- [ ] Support for Connector.kicad_sym symbols
- [ ] Symbol search by name/keyword
- [ ] Performance: < 1 second per symbol injection
### Nice to Have
- [ ] Support for all standard libraries (~10,000 symbols)
- [ ] Multi-unit symbol support
- [ ] Custom library registration
@@ -450,7 +473,7 @@ def add_schematic_component(params):
## Risk Assessment
| Risk | Probability | Impact | Mitigation |
|------|-------------|--------|------------|
| ------------------------------- | ----------- | ------ | ------------------------------------------------ |
| S-expression parsing complexity | High | High | Use proven `sexpdata` library, extensive testing |
| Performance degradation | Medium | Medium | Implement caching, lazy loading |
| KiCad version incompatibility | Low | High | Version detection, format validation |
@@ -469,6 +492,7 @@ Dynamic library loading is **feasible and highly beneficial** for the schematic
4. **Enable true "natural language PCB design"**
**Recommendation:**
- **Keep Option 1 (expanded template) for immediate use**
- **Implement Option 2 (dynamic loading) over 6-8 weeks**
- **Maintain template fallback for compatibility**

View File

@@ -18,30 +18,35 @@ The dynamic symbol loading is now **FULLY OPERATIONAL** and accessible through t
## What's Working (Core Functionality)
### ✅ Symbol Extraction
- Parse `.kicad_sym` library files using S-expression parser
- Extract specific symbol definitions by name
- Cache parsed libraries for performance
- Tested with Device.kicad_sym (533 symbols)
### ✅ S-Expression Manipulation
- Load schematic files as S-expression trees
- Inject symbol definitions into `lib_symbols` section
- Preserve schematic structure and formatting
- Write modified schematics back to disk
### ✅ Template Instance Creation
- Create offscreen template instances at negative Y coordinates
- Generate unique UUIDs for each template
- Set proper properties (Reference, Value, Footprint, Datasheet)
- Templates marked as: `in_bom: no`, `on_board: no`, `dnp: yes`
### ✅ Component Cloning
- kicad-skip successfully clones from dynamic templates
- Components inherit symbol structure from injected definitions
- Properties can be modified after cloning
- Full integration with existing ComponentManager
### ✅ Cross-Platform Library Discovery
- Linux: `/usr/share/kicad/symbols`, `~/.local/share/kicad/*/symbols`
- Windows: `C:/Program Files/KiCad/*/share/kicad/symbols`
- macOS: `/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols`
@@ -91,6 +96,7 @@ Results:
**Class:** `DynamicSymbolLoader`
**Key Methods:**
```python
# Library Discovery
find_kicad_symbol_libraries() -> List[Path]
@@ -109,6 +115,7 @@ load_symbol_dynamically(schematic_path: Path, library: str, symbol: str) -> str
```
**Caching:**
- `library_cache`: Parsed library files (path S-expression data)
- `symbol_cache`: Extracted symbols (lib:symbol symbol definition)
@@ -117,16 +124,19 @@ load_symbol_dynamically(schematic_path: Path, library: str, symbol: str) -> str
## What's NOT Yet Done (Integration Layer)
### ⏳ MCP Tool Integration
- Need to create `add_schematic_component_dynamic` MCP tool
- Wire dynamic loader through MCP interface (has schematic path)
- Update existing `add_schematic_component` to auto-detect and use dynamic loading
### ⏳ Smart Symbol Discovery
- Automatic library detection from component type
- Search across all libraries for symbol names
- Fuzzy matching for symbol names
### ⏳ Advanced Features
- Multi-unit symbol support (e.g., quad op-amps)
- Pin configuration handling
- Custom library registration
@@ -137,21 +147,25 @@ load_symbol_dynamically(schematic_path: Path, library: str, symbol: str) -> str
## Technical Challenges Solved
### Challenge 1: S-Expression Parsing
**Problem:** KiCad files use Lisp-style S-expressions, complex to parse
**Solution:** Used `sexpdata` library (already a dependency of kicad-skip)
**Result:** Robust parsing with proper handling of nested structures
### Challenge 2: Symbol Structure Complexity
**Problem:** Symbols have complex nested structure with multiple sub-symbols
**Solution:** Extract entire symbol tree as-is, inject without modification
**Result:** Preserves all symbol details (graphics, pins, properties)
### Challenge 3: kicad-skip Integration
**Problem:** kicad-skip can only clone existing symbols, can't create from scratch
**Solution:** Inject symbol into lib_symbols, create template instance, then clone
**Result:** Seamless integration, kicad-skip unaware of dynamic loading
### Challenge 4: Schematic File Path Access
**Problem:** kicad-skip Schematic object doesn't expose file path
**Solution:** Pass schematic path explicitly at MCP interface layer
**Result:** Workaround identified, integration pending
@@ -192,7 +206,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
reference: "U1",
x: 100,
y: 100,
footprint: "Package_QFP:LQFP-48_7x7mm_P0.5mm"
footprint: "Package_QFP:LQFP-48_7x7mm_P0.5mm",
});
// The tool will:
@@ -210,7 +224,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
## Comparison: Before vs After
| Feature | Static Templates (Current) | Dynamic Loading (New) |
|---------|---------------------------|----------------------|
| --------------------- | -------------------------- | ------------------------------ |
| **Available Symbols** | 13 types | ~10,000+ types |
| **Maintenance** | Manual template updates | Zero maintenance |
| **Custom Symbols** | Not supported | Fully supported |
@@ -224,6 +238,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
## Phase Progress
### ✅ Phase A: Proof of Concept (COMPLETE)
- [x] Create `DynamicSymbolLoader` class
- [x] Implement library discovery (Linux paths)
- [x] Implement symbol indexing
@@ -235,6 +250,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
**Actual Time:** 4 hours! 🎉
### ⏳ Phase B: Core Functionality (IN PROGRESS)
- [ ] Cross-platform library discovery (Windows, macOS)
- [ ] Symbol search functionality
- [ ] Template instance creation automation
@@ -246,6 +262,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
**Progress:** 25% (cross-platform discovery done)
### ✅ Phase C: MCP Integration (COMPLETE!)
- [x] Integrate dynamic loading into `add_schematic_component` MCP handler
- [x] Implement save inject reload clone orchestration
- [x] Add schematic_path parameter throughout component chain
@@ -258,6 +275,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
**Status:** PRODUCTION READY!
**What Works Now:**
- Users can add ANY symbol from KiCad libraries via MCP interface
- Automatic detection and dynamic loading
- Seamless fallback to static templates
@@ -265,6 +283,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
- Compatible with all existing MCP clients
### ⏸️ Phase D: Advanced Features (PENDING)
- [ ] Multi-unit symbol support (e.g., quad OpAmps)
- [ ] Custom library registration
- [ ] Symbol caching and optimization
@@ -299,18 +318,21 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
## Success Metrics
### Phase A Metrics (All Achieved ✅)
- [x] Load symbols from Device.kicad_sym (passives)
- [x] Support R, C, L, D, LED (5 core types)
- [x] Cross-platform library discovery
- [x] Proper error handling
### Phase B Metrics (Target)
- [ ] Support for all Device.kicad_sym symbols (~500 symbols)
- [ ] Support for Connector.kicad_sym symbols
- [ ] Symbol search by name/keyword
- [ ] Performance: < 1 second per symbol injection
### Overall Success Criteria
- [ ] Access to all standard libraries (~10,000 symbols)
- [ ] Works on Linux, Windows, macOS
- [ ] <100ms latency for cached symbols
@@ -322,7 +344,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
## Risks & Mitigations
| Risk | Status | Mitigation |
|------|--------|------------|
| --------------------------- | -------------- | --------------------------------------- |
| S-expression complexity | RESOLVED | Used proven sexpdata library |
| Performance degradation | RESOLVED | Caching works great (<30ms cached) |
| KiCad version compatibility | TESTING | Version detection, format validation |
@@ -354,7 +376,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
### Test Components
| Component | Type | Library | Dynamic? | Result |
|-----------|------|---------|----------|--------|
| --------- | ----------------- | ------- | -------- | --------------------------- |
| R1 | Resistor | Device | Yes | Added successfully |
| C1 | Capacitor | Device | Yes | Added successfully |
| BT1 | Battery | Device | **Yes** | **Dynamic load + clone** |
@@ -375,6 +397,7 @@ await mcpServer.callTool("add_schematic_component_dynamic", {
Users can now add **ANY symbol from ~10,000 KiCad symbols** through the MCP interface!
The system automatically:
1. Detects if symbol needs dynamic loading
2. Saves current schematic
3. Injects symbol definition from library

View File

@@ -13,7 +13,7 @@ The current KiCAD MCP Server uses SWIG-based Python bindings (`import pcbnew`) w
### Why Migrate?
| SWIG API (Current) | IPC API (Future) |
|-------------------|------------------|
| -------------------------------- | ------------------------------------ |
| ❌ Deprecated | ✅ Official & Supported |
| ❌ Will be removed in KiCAD 10.0 | ✅ Long-term stability |
| ❌ Python-only | ✅ Multi-language (Python, JS, etc.) |
@@ -72,11 +72,13 @@ The current KiCAD MCP Server uses SWIG-based Python bindings (`import pcbnew`) w
### Phase 1: Research & Preparation (Days 1-2)
**Goals:**
- Understand kicad-python library
- Test IPC connection
- Document API differences
**Tasks:**
```bash
# Install kicad-python
pip install kicad-python>=0.5.0
@@ -93,6 +95,7 @@ EOF
```
**Deliverables:**
- [ ] kicad-python installed and tested
- [ ] Connection test script
- [ ] API comparison document (SWIG vs IPC)
@@ -104,6 +107,7 @@ EOF
**Goal:** Create an abstraction layer to support both APIs during transition
**File Structure:**
```
python/kicad_api/
├── __init__.py
@@ -114,6 +118,7 @@ python/kicad_api/
```
**Abstract Interface:**
```python
# python/kicad_api/base.py
from abc import ABC, abstractmethod
@@ -157,6 +162,7 @@ class KiCADBackend(ABC):
```
**IPC Implementation:**
```python
# python/kicad_api/ipc_backend.py
from kicad import KiCad
@@ -187,6 +193,7 @@ class IPCBackend(KiCADBackend):
```
**Backend Factory:**
```python
# python/kicad_api/factory.py
from typing import Optional
@@ -222,6 +229,7 @@ def create_backend(backend_type: Optional[str] = None) -> KiCADBackend:
```
**Deliverables:**
- [ ] Abstract base class defined
- [ ] IPC backend implemented
- [ ] SWIG backend (wrapper around existing code)
@@ -262,6 +270,7 @@ def create_backend(backend_type: Optional[str] = None) -> KiCADBackend:
**Total Estimated Time: 30 hours (~4 days)**
**Migration Template:**
```python
# OLD (SWIG)
import pcbnew
@@ -277,6 +286,7 @@ board_api.set_size(width, height)
```
**Deliverables:**
- [ ] project.py migrated
- [ ] board.py migrated
- [ ] component.py migrated
@@ -291,6 +301,7 @@ board_api.set_size(width, height)
**Testing Strategy:**
1. **Unit Tests**
```python
@pytest.mark.parametrize("backend_type", ["ipc", "swig"])
def test_create_project(backend_type):
@@ -311,6 +322,7 @@ board_api.set_size(width, height)
```
**Deliverables:**
- [ ] 50+ unit tests passing for IPC backend
- [ ] Side-by-side comparison tests
- [ ] Performance benchmarks documented
@@ -322,7 +334,7 @@ board_api.set_size(width, height)
### Project Operations
| Operation | SWIG | IPC |
|-----------|------|-----|
| -------------- | -------------------- | ------------------------ |
| Create project | Custom file creation | `kicad.create_project()` |
| Open project | `pcbnew.LoadBoard()` | `kicad.open_project()` |
| Save project | `board.Save()` | `board.save()` |
@@ -330,7 +342,7 @@ board_api.set_size(width, height)
### Board Operations
| Operation | SWIG | IPC |
|-----------|------|-----|
| --------- | ----------------------- | -------------------- |
| Get board | `pcbnew.LoadBoard()` | `kicad.get_board()` |
| Set size | `board.SetBoardSize()` | `board.set_size()` |
| Add layer | `board.GetLayerCount()` | `board.layers.add()` |
@@ -338,7 +350,7 @@ board_api.set_size(width, height)
### Component Operations
| Operation | SWIG | IPC |
|-----------|------|-----|
| ---------------- | --------------------- | -------------------------- |
| Place component | `pcbnew.FOOTPRINT()` | `board.add_footprint()` |
| Move component | `fp.SetPosition()` | `footprint.set_position()` |
| Rotate component | `fp.SetOrientation()` | `footprint.set_rotation()` |
@@ -346,7 +358,7 @@ board_api.set_size(width, height)
### Routing Operations
| Operation | SWIG | IPC |
|-----------|------|-----|
| ----------- | --------------------- | ------------------- |
| Add net | `board.GetNetCount()` | `board.nets.add()` |
| Route trace | `pcbnew.PCB_TRACK()` | `board.add_track()` |
| Add via | `pcbnew.PCB_VIA()` | `board.add_via()` |
@@ -378,6 +390,7 @@ export KICAD_BACKEND=ipc # or 'swig' or 'auto'
### User Migration Guide
Create `docs/MIGRATING_TO_IPC.md`:
- How to enable IPC in KiCAD
- What changes for users
- Troubleshooting IPC connection issues
@@ -409,7 +422,7 @@ If IPC migration fails:
## Timeline
| Week | Days | Tasks |
|------|------|-------|
| ---------- | ------- | ----------------------------------------------- |
| **Week 2** | Mon-Tue | Research, install kicad-python, test connection |
| | Wed-Thu | Build abstraction layer |
| | Fri | Port project.py and board.py |
@@ -449,11 +462,13 @@ If IPC migration fails:
## Next Steps (This Week)
1. **Install kicad-python**
```bash
pip install kicad-python
```
2. **Test IPC connection**
```bash
# Launch KiCAD
# Enable IPC in preferences
@@ -461,6 +476,7 @@ If IPC migration fails:
```
3. **Create abstraction layer structure**
```bash
mkdir -p python/kicad_api
touch python/kicad_api/{__init__,base,ipc_backend,swig_backend,factory}.py

View File

@@ -11,6 +11,7 @@
## Overview
Integrate JLCPCB's SMT assembly parts library (~100k+ parts) into the KiCAD MCP server, enabling:
- Component search by specifications (e.g., "10k resistor 0603 1%")
- Automatic part selection optimized for cost (prefer Basic parts)
- Real stock and pricing information
@@ -83,12 +84,14 @@ docs/
**File:** `python/commands/jlcpcb.py`
**Features:**
- Authenticate with JLCPCB API (requires user-provided key/secret)
- Download parts database (paginated, ~100k parts)
- Handle rate limiting and retries
- Save to SQLite database
**API Endpoints:**
```python
# Get auth token
POST https://jlcpcb.com/external/genToken
@@ -104,6 +107,7 @@ Body: { "lastKey": "PAGINATION_KEY" } # Optional, for next page
```
**Database Schema:**
```sql
CREATE TABLE components (
lcsc TEXT PRIMARY KEY, -- "C12345"
@@ -128,6 +132,7 @@ CREATE INDEX idx_library_type ON components(library_type);
```
**Environment Variables:**
```bash
# ~/.bashrc or .env
export JLCPCB_API_KEY="your_key_here"
@@ -135,6 +140,7 @@ export JLCPCB_API_SECRET="your_secret_here"
```
**Python Implementation Outline:**
```python
class JLCPCBClient:
def __init__(self, api_key: str, api_secret: str):
@@ -162,6 +168,7 @@ class JLCPCBClient:
**File:** `python/commands/jlcpcb_parts.py`
**Features:**
- Initialize/load SQLite database
- Parametric search (resistance, capacitance, voltage, etc.)
- Filter by library type (Basic/Extended)
@@ -169,6 +176,7 @@ class JLCPCBClient:
- Map package names to KiCAD footprints
**Python Implementation Outline:**
```python
class JLCPCBPartsManager:
def __init__(self, db_path: str = "data/jlcpcb_parts.db"):
@@ -203,6 +211,7 @@ class JLCPCBPartsManager:
```
**Package to Footprint Mapping:**
```json
{
"0402": [
@@ -215,17 +224,9 @@ class JLCPCBPartsManager:
"Capacitor_SMD:C_0603_1608Metric",
"LED_SMD:LED_0603_1608Metric"
],
"0805": [
"Resistor_SMD:R_0805_2012Metric",
"Capacitor_SMD:C_0805_2012Metric"
],
"SOT-23": [
"Package_TO_SOT_SMD:SOT-23",
"Package_TO_SOT_SMD:SOT-23-3"
],
"SOIC-8": [
"Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"
]
"0805": ["Resistor_SMD:R_0805_2012Metric", "Capacitor_SMD:C_0805_2012Metric"],
"SOT-23": ["Package_TO_SOT_SMD:SOT-23", "Package_TO_SOT_SMD:SOT-23-3"],
"SOIC-8": ["Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"]
}
```
@@ -238,6 +239,7 @@ class JLCPCBPartsManager:
**New MCP Tools:**
#### 1. `search_jlcpcb_parts`
Search JLCPCB parts library by specifications.
```typescript
@@ -280,6 +282,7 @@ Search JLCPCB parts library by specifications.
```
**Example Usage:**
```
User: "Find me a 10k resistor, 0603 package, JLCPCB basic part"
Claude: [uses search_jlcpcb_parts]
@@ -291,6 +294,7 @@ Recommended: C58972 (cheapest Basic part with high stock)
```
#### 2. `get_jlcpcb_part`
Get detailed information about a specific JLCPCB part.
```typescript
@@ -311,6 +315,7 @@ Get detailed information about a specific JLCPCB part.
```
**Returns:**
```json
{
"lcsc": "C25804",
@@ -322,18 +327,17 @@ Get detailed information about a specific JLCPCB part.
"library_type": "Basic",
"stock": 15000,
"price_breaks": [
{"qty": 1, "price": "$0.002"},
{"qty": 10, "price": "$0.0018"},
{"qty": 100, "price": "$0.0015"}
{ "qty": 1, "price": "$0.002" },
{ "qty": 10, "price": "$0.0018" },
{ "qty": 100, "price": "$0.0015" }
],
"datasheet": "https://datasheet.lcsc.com/...",
"kicad_footprints": [
"Resistor_SMD:R_0603_1608Metric"
]
"kicad_footprints": ["Resistor_SMD:R_0603_1608Metric"]
}
```
#### 3. Enhanced `place_component`
Add JLCPCB integration to existing component placement.
```typescript
@@ -347,6 +351,7 @@ Add JLCPCB integration to existing component placement.
```
**Example:**
```
User: "Place a 10k resistor at 50, 40mm using JLCPCB part C25804"
Claude: [uses place_component with jlcpcb_part="C25804"]
@@ -364,6 +369,7 @@ Claude: [uses place_component with jlcpcb_part="C25804"]
### Phase 4: Testing & Documentation (Day 4)
**Testing:**
1. Download JLCPCB database (verify ~100k parts loaded)
2. Test parametric search (resistors, capacitors, ICs)
3. Test package mapping (0603 → correct footprints)
@@ -371,6 +377,7 @@ Claude: [uses place_component with jlcpcb_part="C25804"]
5. Verify BOM export includes LCSC part numbers
**Documentation:**
- User guide: How to get JLCPCB API key
- Usage examples for each MCP tool
- Best practices (prefer Basic parts, check stock)
@@ -381,6 +388,7 @@ Claude: [uses place_component with jlcpcb_part="C25804"]
## User Workflow Examples
### Example 1: Find and Place a Resistor
```
User: "I need a 10k pull-up resistor, 0603 size, cheapest JLCPCB basic part"
@@ -409,6 +417,7 @@ Claude: [uses place_component]
```
### Example 2: Design a Cost-Optimized LED Circuit
```
User: "Design an LED circuit with current-limiting resistor, use only JLCPCB basic parts"
@@ -428,6 +437,7 @@ Claude: [searches for LED]
```
### Example 3: Check Stock Before Ordering
```
User: "I need 100 of part C25804, is there enough stock?"
@@ -453,12 +463,14 @@ Claude: [uses get_jlcpcb_part lcsc_number="C25804"]
**Configure in MCP:**
Option A: Environment variables (recommended)
```bash
export JLCPCB_API_KEY="your_app_key"
export JLCPCB_API_SECRET="your_app_secret"
```
Option B: Config file
```json
{
"jlcpcb": {
@@ -470,6 +482,7 @@ Option B: Config file
```
**Initial Setup:**
```
User: "Download the JLCPCB parts database"
@@ -543,11 +556,13 @@ This BOM can be directly uploaded to JLCPCB for assembly!
**Initial Download:** ~5-10 minutes (108k parts)
**Incremental Updates:**
- Run daily via cron/scheduled task
- Only fetch parts modified since last update
- Much faster (~30 seconds)
**Update Command:**
```python
# In Python
jlcpcb_client.update_database(db_path)
@@ -562,6 +577,7 @@ update_jlcpcb_database(force=True) # Full re-download
## Success Metrics
**Implementation Complete When:**
- ✅ Can download/cache full JLCPCB parts database
- ✅ Parametric search works (resistors, capacitors, ICs)
- ✅ Package → footprint mapping covers 90%+ of common parts
@@ -570,6 +586,7 @@ update_jlcpcb_database(force=True) # Full re-download
- ✅ Documentation complete with examples
**User Experience Goal:**
```
User: "Design a board with an ESP32, USB-C connector, and LED,
use only JLCPCB basic parts under $10 BOM"
@@ -588,6 +605,7 @@ Claude: [searches JLCPCB database]
## Future Enhancements
**Post-MVP (v2.1+):**
- LCSC API integration for extended parametric data
- Digikey/Mouser fallback for non-JLCPCB parts
- Part substitution suggestions (out of stock → alternatives)

View File

@@ -7,6 +7,7 @@
### What Was Implemented
#### 1. Tool Registry (`src/tools/registry.ts`)
- ✅ Complete tool categorization (59 tools → 7 categories)
- ✅ Direct tools list (12 high-frequency tools)
- ✅ Category lookup maps for O(1) access
@@ -14,17 +15,20 @@
- ✅ Registry statistics and metadata
#### 2. Router Tools (`src/tools/router.ts`)
-`list_tool_categories` - Browse all categories
-`get_category_tools` - View tools in a category
-`execute_tool` - Execute any routed tool
-`search_tools` - Search tools by keyword
#### 3. Server Integration (`src/server.ts`)
- ✅ Router tools registered at server startup
- ✅ All tools remain functional (backwards compatible)
- ✅ Logging added for router pattern status
#### 4. Documentation
-`TOOL_INVENTORY.md` - Complete tool catalog
-`ROUTER_ARCHITECTURE.md` - Design specification
-`ROUTER_IMPLEMENTATION_STATUS.md` - This file
@@ -36,6 +40,7 @@
**Build:** ✅ Compiles successfully (`npm run build`)
**Tool Count:**
- Total Tools: 59 (ALL still registered and visible)
- Direct Tools: 12
- Routed Tools: 47
@@ -43,6 +48,7 @@
- **Currently Visible to Claude:** 63 tools (59 + 4 router)
**Token Impact:**
- **Current:** ~42K tokens (still showing all tools)
- **Target:** ~12K tokens (Phase 2 optimization)
- **Potential Savings:** ~30K tokens (71% reduction)
@@ -50,46 +56,55 @@
## 🔄 Phase 2: Token Optimization (Next Step)
### Objective
Hide routed tools from Claude's context while keeping them accessible via `execute_tool`.
### Two Approaches
#### Option A: Registration Filtering (Recommended)
Modify tool registration to conditionally register tools based on whether they're in the direct list.
**Changes needed:**
1. Update each `register*Tools` function to check `isDirectTool()`
2. Only call `server.tool()` for direct tools
3. Routed tools remain accessible via `execute_tool` calling `callKicadScript`
**Pros:**
- Clean separation
- True token savings
- No behavior changes
**Cons:**
- Requires modifying 9 tool files
#### Option B: MCP Filter (If Supported)
If MCP SDK supports tool filtering/hiding, use that instead.
**Pros:**
- No tool file changes
- Centralized control
**Cons:**
- May not be supported by SDK
- Needs investigation
### Implementation Plan for Phase 2
1. **Create Helper Function** (`src/tools/conditional-register.ts`)
```typescript
export function registerToolConditionally(
server: McpServer,
toolName: string,
definition: ToolDefinition,
handler: Function
handler: Function,
) {
if (isDirectTool(toolName)) {
// Register with MCP (visible to Claude)
@@ -115,7 +130,7 @@ If MCP SDK supports tool filtering/hiding, use that instead.
## 📊 Categories & Distribution
| Category | Tools | Description |
|----------|-------|-------------|
| ------------- | ------ | ------------------------------------------------- |
| **board** | 9 | Board configuration, layers, zones, visualization |
| **component** | 8 | Advanced component operations |
| **export** | 8 | Manufacturing file generation |
@@ -130,6 +145,7 @@ If MCP SDK supports tool filtering/hiding, use that instead.
## 🧪 Testing the Router
### Test 1: List Categories
```
User: "What tool categories are available?"
@@ -138,6 +154,7 @@ Result: Returns 7 categories with descriptions
```
### Test 2: Browse Category
```
User: "What export tools are available?"
@@ -146,6 +163,7 @@ Result: Returns 8 export tools
```
### Test 3: Search Tools
```
User: "How do I export gerber files?"
@@ -154,6 +172,7 @@ Result: Finds export_gerber in export category
```
### Test 4: Execute Tool
```
User: "Export gerbers to ./output"

View File

@@ -19,10 +19,12 @@ This plan outlines the implementation of complete schematic wiring functionality
### What Exists ✅
**Files:**
- `python/commands/connection_schematic.py` - ConnectionManager class with wire/label methods
- MCP handlers in `kicad_interface.py` for 6 connection-related tools
**MCP Tools (Registered):**
1. `add_schematic_wire` - Add wire between two points
2. `add_schematic_connection` - Connect two component pins
3. `add_schematic_net_label` - Add net label
@@ -31,6 +33,7 @@ This plan outlines the implementation of complete schematic wiring functionality
6. `generate_netlist` - Generate netlist from schematic
**ConnectionManager Methods:**
- `add_wire(schematic, start_point, end_point)` - Add wire between coordinates
- `add_connection(schematic, source_ref, source_pin, target_ref, target_pin)` - Connect pins
- `add_net_label(schematic, net_name, position)` - Add label
@@ -42,28 +45,33 @@ This plan outlines the implementation of complete schematic wiring functionality
### What's Broken/Missing ❌
**Problem 1: kicad-skip API Uncertainty**
- Code assumes `schematic.wire.append()` exists
- Code assumes `schematic.label.append()` exists
- Code assumes pins have `.name` and `.location` attributes
- **Need to verify what kicad-skip actually supports**
**Problem 2: Pin Location Calculation**
- Current implementation tries to calculate absolute pin positions
- May not account for symbol rotation
- May not work with multi-unit symbols
- Pin numbering vs pin naming confusion
**Problem 3: No Visual Feedback**
- No way to verify wires were created correctly
- No validation of wire endpoints
- No checks for overlapping wires or junctions
**Problem 4: Limited Testing**
- No integration tests for wiring functionality
- No validation with real KiCad schematics
- User reported `add_schematic_wire` fails
**Problem 5: Missing Features**
- No junction (wire intersection) support
- No bus support (multi-bit signals)
- No no-connect flags
@@ -77,11 +85,13 @@ This plan outlines the implementation of complete schematic wiring functionality
### Challenge 1: kicad-skip Wire API
**Issue:** The kicad-skip library documentation is sparse. We need to determine:
- Does `schematic.wire` exist?
- What's the correct API to add wires?
- How are wires stored in .kicad_sch files?
**S-Expression Format (KiCad 8/9):**
```lisp
(wire (pts (xy 100 100) (xy 200 100))
(stroke (width 0) (type default))
@@ -90,6 +100,7 @@ This plan outlines the implementation of complete schematic wiring functionality
```
**Approach:**
1. Examine kicad-skip source code
2. Test wire creation manually with kicad-skip
3. Fall back to S-expression manipulation if necessary (similar to dynamic symbol loading)
@@ -102,12 +113,14 @@ This plan outlines the implementation of complete schematic wiring functionality
Pins are defined within symbol definitions in lib_symbols, with coordinates relative to symbol origin. When symbol is placed, pins move with it.
**Required Information:**
- Symbol position (x, y)
- Symbol rotation angle
- Pin offset from symbol origin
- Pin number/name mapping
**Solution:**
1. Parse symbol definition to find pin definitions
2. Apply transformation matrix (position + rotation) to pin coordinates
3. Return absolute pin position in schematic space
@@ -117,6 +130,7 @@ Pins are defined within symbol definitions in lib_symbols, with coordinates rela
**Issue:** Users don't want to manually specify every wire segment.
**Desired Behavior:**
```
User: "Connect R1 pin 1 to C1 pin 1"
System:
@@ -128,6 +142,7 @@ System:
```
**Auto-Routing Options:**
1. **Direct** - Single wire segment (diagonal if needed)
2. **Orthogonal** - Only horizontal/vertical segments (2 segments)
3. **Manhattan** - Complex path avoiding components (3+ segments)
@@ -139,11 +154,13 @@ System:
**Issue:** Labels need to attach to wires, not float in space.
**KiCad Behavior:**
- Labels must touch a wire or pin
- Labels create electrical connections at their attachment point
- Multiple labels with same name = connected net
**Implementation:**
- When adding label, find nearest wire endpoint
- Attach label to that coordinate
- Or create short wire stub for label attachment
@@ -190,6 +207,7 @@ System:
- Verify electrical connectivity
**Deliverables:**
- Working `add_schematic_wire` tool
- Working `add_schematic_connection` tool
- Pin location discovery working
@@ -197,6 +215,7 @@ System:
- Documentation updated
**Success Criteria:**
- User can connect two resistor pins with MCP command
- Wire appears in KiCad schematic viewer
- Netlist shows electrical connection
@@ -240,6 +259,7 @@ System:
- Generate and validate netlist
**Deliverables:**
- Working `add_schematic_net_label` tool
- Working `connect_to_net` tool
- Working `get_net_connections` tool
@@ -247,6 +267,7 @@ System:
- Netlist generation working
**Success Criteria:**
- User can label nets VCC, GND
- Multiple components connect to same net
- Netlist correctly shows net membership
@@ -284,12 +305,14 @@ System:
- Sheet connections
**Deliverables:**
- Junction creation
- No-connect support
- Smart orthogonal routing
- Bus and hierarchical label support
**Success Criteria:**
- Wires route cleanly around components
- Junctions appear at wire intersections
- Unused pins marked with no-connect
@@ -332,12 +355,14 @@ System:
- Add to CHANGELOG
**Deliverables:**
- ERC validation
- Comprehensive test suite
- Error handling
- Complete documentation
**Success Criteria:**
- 95%+ test pass rate
- Users can create functional circuits
- Clear error messages on failures
@@ -365,11 +390,13 @@ label = schematic.label.new(
```
**Pros:**
- Clean, maintainable code
- Follows library patterns
- Less likely to break
**Cons:**
- Depends on kicad-skip having these features
- May be limited in functionality
@@ -407,11 +434,13 @@ with open(schematic_path, 'w') as f:
```
**Pros:**
- Complete control
- Can implement any feature
- Works around library limitations
**Cons:**
- More complex
- Requires deep KiCad format knowledge
- More maintenance
@@ -447,6 +476,7 @@ Symbols are stored in `lib_symbols` section:
### Step 2: Extract Pin Information
For each pin:
- Number (e.g., "1", "2")
- Name (e.g., "GND", "VCC", "~" for unnamed)
- Position relative to symbol origin: `(at x y angle)`
@@ -462,6 +492,7 @@ From symbol instance in schematic:
```
Extract:
- Position: `(at 100 100 0)` = x=100, y=100, rotation=0°
- Reference: "R1"
@@ -518,6 +549,7 @@ R1 pin 2 C1 pin 1
```
**Algorithm:**
1. Calculate midpoint
2. Route horizontal to midpoint
3. Route vertical to target
@@ -540,6 +572,7 @@ Complex multi-segment paths avoiding components.
### Unit Tests
Test individual functions:
- `test_add_wire()` - Wire creation
- `test_get_pin_location()` - Pin discovery
- `test_add_net_label()` - Label creation
@@ -548,6 +581,7 @@ Test individual functions:
### Integration Tests
Test complete workflows:
- `test_connect_two_resistors()` - Wire R1 to R2
- `test_connect_to_vcc_net()` - Multiple components to VCC
- `test_generate_netlist()` - Netlist accuracy
@@ -565,6 +599,7 @@ Test complete workflows:
## Success Metrics
### Phase 1 Success:
- [ ] `add_schematic_wire` works (coordinates)
- [ ] `add_schematic_connection` works (pin to pin)
- [ ] Wires appear in KiCad schematic
@@ -572,6 +607,7 @@ Test complete workflows:
- [ ] 3+ integration tests passing
### Phase 2 Success:
- [ ] Net labels work (VCC, GND, etc.)
- [ ] Multiple components on same net
- [ ] `get_net_connections` returns correct results
@@ -579,12 +615,14 @@ Test complete workflows:
- [ ] 5+ integration tests passing
### Phase 3 Success:
- [ ] Junctions at wire intersections
- [ ] Orthogonal routing preferred
- [ ] No-connect flags on unused pins
- [ ] 10+ integration tests passing
### Phase 4 Success:
- [ ] ERC detects errors
- [ ] 95%+ test coverage
- [ ] Complete documentation
@@ -595,7 +633,7 @@ Test complete workflows:
## Risk Assessment
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|------------|
| ------------------------- | ----------- | ------ | -------------------------------- |
| kicad-skip lacks wire API | High | High | Use S-expression fallback |
| Pin discovery complex | Medium | Medium | Test with multiple symbol types |
| Rotation math errors | Medium | High | Extensive testing, validation |
@@ -607,11 +645,13 @@ Test complete workflows:
## Dependencies
**Required:**
- kicad-skip >= 0.1.0 (or compatible)
- sexpdata (already dependency for dynamic loading)
- Python 3.8+
**Optional:**
- KiCad CLI for validation (`kicad-cli sch export netlist`)
---
@@ -658,6 +698,7 @@ Test complete workflows:
**For Issue #26:**
Update users that:
- ✅ Component placement is DONE (with 10,000+ symbols)
- ⏳ Wire/connection tools are IN PROGRESS
- 📅 Estimated completion: 2-3 weeks for core functionality

View File

@@ -14,6 +14,7 @@ The schematic workflow was completely broken due to incorrect usage of the kicad
The kicad-skip library **does not support creating symbols from scratch**. The only way to add symbols is by **cloning existing symbol instances**.
From kicad-skip documentation:
> "symbols: these don't have a new()" because they require complex mappings to library elements, pins, and properties.
## Solution
@@ -21,6 +22,7 @@ From kicad-skip documentation:
### 1. Template-Based Approach
Created a template schematic (`python/templates/template_with_symbols.kicad_sch`) with:
- Complete `lib_symbols` section defining R, C, LED symbols
- Three template symbol instances placed off-screen at (-100, -110, -120)
- Template symbols marked as `dnp yes`, `in_bom no`, `on_board no` so they don't interfere
@@ -28,15 +30,18 @@ Created a template schematic (`python/templates/template_with_symbols.kicad_sch`
### 2. Updated Files
**python/commands/project.py:**
- Now creates both `.kicad_pcb` AND `.kicad_sch` files
- Project file includes schematic reference in `sheets` array
- Copies template schematic with cloneable symbols
**python/commands/schematic.py:**
- Uses template file instead of creating from scratch
- Proper minimal schematic structure when template unavailable
**python/commands/component_schematic.py:**
- Completely rewritten to use `clone()` API
- Maps component types to template symbols
- Proper UUID generation for each cloned symbol
@@ -78,17 +83,20 @@ SchematicManager.save_schematic(sch, result['project']['schematicPath'])
## Supported Component Types
Currently supported template symbols:
- `R` - Resistor (maps to `_TEMPLATE_R`)
- `C` - Capacitor (maps to `_TEMPLATE_C`)
- `D` or `LED` - LED (maps to `_TEMPLATE_D`)
To add more component types, update:
1. `python/templates/template_with_symbols.kicad_sch` - Add lib_symbol definition and template instance
2. `python/commands/component_schematic.py` - Add mapping in `TEMPLATE_MAP`
## Testing
Comprehensive test created at `/tmp/test_schematic_workflow.py`:
- Creates project with schematic
- Loads schematic
- Adds R, C, LED components

View File

@@ -1,4 +1,5 @@
# Week 1 - Session 1 Summary
**Date:** October 25, 2025
**Status:****EXCELLENT PROGRESS**
@@ -9,6 +10,7 @@
Resurrect the KiCAD MCP Server and transform it from a Windows-only "KiCAD automation wrapper" into a **true AI-assisted PCB design companion** for hobbyist users (novice to intermediate).
**Strategic Focus:**
- Linux-first platform support
- JLCPCB & Digikey integration
- End-to-end PCB design workflow
@@ -21,12 +23,14 @@ Resurrect the KiCAD MCP Server and transform it from a Windows-only "KiCAD autom
### 1. **Complete Project Analysis** 📊
Created comprehensive documentation:
- ✅ Full codebase exploration (6 tool categories, 9 Python command modules)
- ✅ Identified critical issues (deprecated SWIG API, Windows-only paths)
- ✅ Researched KiCAD IPC API, JLCPCB API, Digikey API
- ✅ Researched MCP best practices
**Key Findings:**
- SWIG Python bindings are DEPRECATED (will be removed in KiCAD 10.0)
- Current architecture works but is Windows-centric
- Missing core AI-assisted features (part selection, BOM management)
@@ -38,22 +42,26 @@ Created comprehensive documentation:
Designed comprehensive roadmap in 4 phases:
#### **Phase 1: Foundation & Migration (Weeks 1-4)**
- Linux compatibility
- KiCAD IPC API migration
- Performance improvements (caching, async)
#### **Phase 2: Core AI Features (Weeks 5-8)**
- JLCPCB integration (parts library + pricing)
- Digikey integration (parametric search)
- Smart BOM management
- Design pattern library
#### **Phase 3: Novice-Friendly Workflows (Weeks 9-11)**
- Guided step-by-step workflows
- Visual feedback system
- Intelligent error recovery
#### **Phase 4: Polish & Launch (Week 12)**
- Testing, documentation, community building
---
@@ -63,6 +71,7 @@ Designed comprehensive roadmap in 4 phases:
Created complete cross-platform support:
**Files Created:**
-`docs/LINUX_COMPATIBILITY_AUDIT.md` - Comprehensive audit report
-`python/utils/platform_helper.py` - Cross-platform path detection
-`config/linux-config.example.json` - Linux configuration template
@@ -70,6 +79,7 @@ Created complete cross-platform support:
-`config/macos-config.example.json` - macOS configuration template
**Platform Helper Features:**
```python
PlatformHelper.get_config_dir() # ~/.config/kicad-mcp on Linux
PlatformHelper.get_log_dir() # ~/.config/kicad-mcp/logs
@@ -86,6 +96,7 @@ Created GitHub Actions workflow:
**File:** `.github/workflows/ci.yml`
**Testing Matrix:**
- TypeScript build on Ubuntu 24.04, 22.04, Windows, macOS
- Python tests on Python 3.10, 3.11, 3.12
- Integration tests with KiCAD 9.0 installation
@@ -102,12 +113,14 @@ Created GitHub Actions workflow:
Set up comprehensive testing infrastructure:
**Files Created:**
-`pytest.ini` - Pytest configuration
-`requirements.txt` - Production dependencies
-`requirements-dev.txt` - Development dependencies
-`tests/test_platform_helper.py` - 20+ unit tests
**Test Categories:**
```python
@pytest.mark.unit # Fast, no external dependencies
@pytest.mark.integration # Requires KiCAD
@@ -116,6 +129,7 @@ Set up comprehensive testing infrastructure:
```
**Test Results:**
```
✅ Platform detection works correctly
✅ Config directories follow XDG spec on Linux
@@ -132,6 +146,7 @@ Created contributor guide:
**File:** `CONTRIBUTING.md`
**Includes:**
- Platform-specific setup instructions (Linux/Windows/macOS)
- Project structure overview
- Development workflow
@@ -144,6 +159,7 @@ Created contributor guide:
### 7. **Dependencies Management** 📦
**Production Dependencies (requirements.txt):**
```
kicad-skip>=0.1.0 # Schematic manipulation
Pillow>=9.0.0 # Image processing
@@ -154,6 +170,7 @@ python-dotenv>=1.0.0 # Config management
```
**Development Dependencies:**
```
pytest>=7.4.0 # Testing
black>=23.7.0 # Code formatting
@@ -323,6 +340,7 @@ Node: 18.x, 20.x, 22.x
## 🚀 Impact Assessment
### Before Today
- ❌ Windows-only
- ❌ No CI/CD
- ❌ No tests
@@ -330,6 +348,7 @@ Node: 18.x, 20.x, 22.x
- ❌ No developer documentation
### After Today
- ✅ Cross-platform (Linux/Windows/macOS)
- ✅ GitHub Actions CI/CD
- ✅ 20+ unit tests with pytest
@@ -346,24 +365,28 @@ Node: 18.x, 20.x, 22.x
## 💡 Key Decisions Made
### 1. **IPC API Migration: Proceed Immediately** ✅
- SWIG is deprecated, will be removed in KiCAD 10.0
- IPC API is stable, officially supported
- Better performance and cross-language support
- Decision: Migrate in Week 2-3
### 2. **Linux-First Approach** ✅
- Hobbyists often use Linux
- Better for open-source development
- Easier CI/CD with GitHub Actions
- Decision: Linux is primary development platform
### 3. **JLCPCB Integration Priority** ✅
- Hobbyists love JLCPCB for cheap assembly
- "Basic parts" filter is critical
- Better stock than Digikey for hobbyists
- Decision: JLCPCB before Digikey
### 4. **Pytest over unittest** ✅
- More Pythonic
- Better fixtures and parametrization
- Industry standard
@@ -392,15 +415,19 @@ Node: 18.x, 20.x, 22.x
## 🎉 Highlights
### Biggest Win
**Complete cross-platform infrastructure in one session**
### Most Valuable Addition
🔧 **PlatformHelper utility** - Solves path issues elegantly
### Best Decision
🎯 **Creating comprehensive plan first** - Clear roadmap for 12 weeks
### Unexpected Discovery
⚠️ **SWIG deprecation** - Would have been a nasty surprise later!
---
@@ -408,11 +435,13 @@ Node: 18.x, 20.x, 22.x
## 🤝 Collaboration Notes
### What Went Well
- Clear requirements from user
- Good research phase before coding
- Incremental progress with testing
### What to Improve
- Need actual Ubuntu 24.04 testing
- Should run pytest suite
- Need to test KiCAD 9.0 integration
@@ -422,6 +451,7 @@ Node: 18.x, 20.x, 22.x
## 📅 Schedule Status
### Week 1 Goals
- [x] Linux compatibility audit (**100% complete**)
- [x] CI/CD setup (**100% complete**)
- [x] Development infrastructure (**100% complete**)
@@ -448,16 +478,19 @@ Node: 18.x, 20.x, 22.x
## 📝 Notes for Future
### Architecture Decisions to Make
- [ ] Redis vs in-memory cache?
- [ ] Session storage approach?
- [ ] WebSocket vs STDIO for future scaling?
### Dependencies to Research
- [ ] JLCPCB API client library (exists?)
- [ ] Digikey API v3 (issus/DigiKeyApi looks good)
- [ ] kicad-python 0.5.0 compatibility
### Questions to Answer
- [ ] How to handle KiCAD running vs not running (IPC requirement)?
- [ ] Should we support both SWIG and IPC during migration?
- [ ] BOM format standardization?
@@ -467,7 +500,7 @@ Node: 18.x, 20.x, 22.x
## 🏆 Success Metrics Achieved Today
| Metric | Target | Achieved | Status |
|--------|--------|----------|--------|
| ---------------- | --------------- | -------------- | ------ |
| Platform support | Linux primary | ✅ Linux ready | ✅ |
| CI/CD pipeline | GitHub Actions | ✅ Complete | ✅ |
| Test coverage | Setup pytest | ✅ 20+ tests | ✅ |

View File

@@ -1,4 +1,5 @@
# Week 1 - Session 2 Summary
**Date:** October 25, 2025 (Afternoon)
**Status:** 🚀 **OUTSTANDING PROGRESS**
@@ -7,6 +8,7 @@
## 🎯 Session Goals
Continue Week 1 implementation while user installs KiCAD:
1. Update README with comprehensive Linux guide
2. Create installation scripts
3. Begin IPC API preparation
@@ -21,6 +23,7 @@ Continue Week 1 implementation while user installs KiCAD:
**File:** `README.md`
**Changes:**
- ✅ Updated project status to reflect v2.0 rebuild
- ✅ Added collapsible platform-specific installation sections:
- 🐧 **Linux (Ubuntu/Debian)** - Primary, detailed
@@ -39,6 +42,7 @@ Continue Week 1 implementation while user installs KiCAD:
**File:** `scripts/install-linux.sh`
**Features:**
- Fully automated Ubuntu/Debian installation
- Color-coded output (info/success/warning/error)
- Safety checks (platform detection, command validation)
@@ -51,6 +55,7 @@ Continue Week 1 implementation while user installs KiCAD:
- Helpful next-steps guidance
**Usage:**
```bash
cd kicad-mcp-server
./scripts/install-linux.sh
@@ -65,6 +70,7 @@ cd kicad-mcp-server
**File:** `.pre-commit-config.yaml`
**Hooks Added:**
- **Python:**
- Black (code formatting)
- isort (import sorting)
@@ -84,6 +90,7 @@ cd kicad-mcp-server
- Markdownlint (formatting)
**Setup:**
```bash
pip install pre-commit
pre-commit install
@@ -98,6 +105,7 @@ pre-commit install
**File:** `docs/IPC_API_MIGRATION_PLAN.md`
**Comprehensive 30-page migration guide:**
- Why migrate (SWIG deprecation analysis)
- IPC API architecture overview
- 4-phase migration strategy (10 days)
@@ -108,6 +116,7 @@ pre-commit install
- Timeline with day-by-day tasks
**Key Insights:**
- SWIG will be removed in KiCAD 10.0
- IPC is faster for some operations
- Protocol Buffers ensure API stability
@@ -156,6 +165,7 @@ pre-commit install
**Total Lines of Code:** ~800 lines
**Architecture:**
```python
from kicad_api import create_backend
@@ -173,6 +183,7 @@ if backend.connect():
```
**Key Features:**
- Abstraction allows painless migration
- Both backends can coexist during transition
- Easy testing (compare SWIG vs IPC outputs)
@@ -187,6 +198,7 @@ if backend.connect():
**File:** `package.json`
**Improvements:**
- Version bumped to `2.0.0-alpha.1`
- Better description
- Enhanced npm scripts:
@@ -211,6 +223,7 @@ if backend.connect():
### Files Created/Modified (Session 2)
**New Files (10):**
```
docs/IPC_API_MIGRATION_PLAN.md # 500+ lines
docs/WEEK1_SESSION2_SUMMARY.md # This file
@@ -224,6 +237,7 @@ python/kicad_api/swig_backend.py # 220 lines
```
**Modified Files (2):**
```
README.md # Major rewrite
package.json # Enhanced scripts
@@ -247,7 +261,7 @@ package.json # Enhanced scripts
### Progress: **95% Complete** ████████████░
| Task | Status |
|------|--------|
| --------------------- | -------------------------------- |
| Linux compatibility | ✅ Complete |
| CI/CD pipeline | ✅ Complete |
| Cross-platform paths | ✅ Complete |
@@ -270,6 +284,7 @@ package.json # Enhanced scripts
### IPC API Migration Prep ✅
Everything is in place to begin migration:
- ✅ Abstraction layer architecture defined
- ✅ Base classes and interfaces ready
- ✅ Factory pattern for backend selection
@@ -278,6 +293,7 @@ Everything is in place to begin migration:
- ✅ Comprehensive migration plan documented
**Week 2 kickoff tasks:**
1. Install `kicad-python` package
2. Test IPC connection to running KiCAD
3. Begin porting `project.py` module
@@ -288,15 +304,19 @@ Everything is in place to begin migration:
## 💡 Key Insights from Session 2
### 1. **Installation Automation**
The bash script reduces setup time from 30+ minutes to < 10 minutes with zero manual intervention.
### 2. **Pre-Commit Hooks**
Automatic code quality checks prevent bugs before they're committed. This will save hours in code review.
### 3. **Abstraction Pattern**
The backend abstraction is elegant - allows gradual migration without breaking existing functionality. Users won't notice the transition.
### 4. **Documentation Quality**
The IPC migration plan is thorough enough that another developer could execute it independently.
---
@@ -308,26 +328,31 @@ The IPC migration plan is thorough enough that another developer could execute i
You can immediately test:
**1. Platform Helper:**
```bash
python3 python/utils/platform_helper.py
```
**2. Backend Detection:**
```bash
python3 python/kicad_api/factory.py
```
**3. Installation Script:**
```bash
./scripts/install-linux.sh
```
**4. Pytest Suite:**
```bash
pytest tests/ -v
```
**5. Pre-commit Hooks:**
```bash
pre-commit run --all-files
```
@@ -337,18 +362,22 @@ pre-commit run --all-files
## 📈 Impact Assessment
### Developer Onboarding
- **Before:** 2-3 hours setup, Windows-only, manual steps
- **After:** 10 minutes automated, cross-platform, one script
### Code Quality
- **Before:** No automated checks, inconsistent style
- **After:** Pre-commit hooks, 100% type hints, Black formatting
### Future-Proofing
- **Before:** Deprecated SWIG API, no migration path
- **After:** IPC API ready, abstraction layer in place
### Documentation
- **Before:** README only, Windows-focused
- **After:** 8 comprehensive docs, Linux-primary, migration guides
@@ -357,12 +386,14 @@ pre-commit run --all-files
## 🎯 Next Actions
### Immediate (Tonight/Tomorrow)
1. Install KiCAD 9.0 on your system
2. Run `./scripts/install-linux.sh`
3. Test backend detection
4. Verify pytest suite passes
### Week 2 Start (Monday)
1. Install `kicad-python` package
2. Test IPC connection
3. Begin project.py migration
@@ -373,17 +404,20 @@ pre-commit run --all-files
## 🏆 Session 2 Achievements
### Infrastructure
- Automated Linux installation
- Pre-commit hooks for code quality
- Enhanced npm scripts
- IPC API abstraction layer (800+ lines)
### Documentation
- Updated README (Linux-primary)
- 30-page IPC migration plan
- Session summaries
### Architecture
- Backend abstraction pattern
- Factory with auto-detection
- SWIG backward compatibility
@@ -394,6 +428,7 @@ pre-commit run --all-files
## 🎉 Overall Day Summary
**Sessions 1+2 Combined:**
- **Time:** ~4-5 hours total
- 📝 **Files:** 27 created
- 💻 **Code:** ~3,000+ lines

View File

@@ -108,25 +108,25 @@ export const toolCategories: ToolCategory[] = [
properties: {
output_dir: {
type: "string",
description: "Output directory path"
description: "Output directory path",
},
layers: {
type: "array",
items: { type: "string" },
description: "Layers to export (default: all copper + silkscreen + mask)"
description: "Layers to export (default: all copper + silkscreen + mask)",
},
format: {
type: "string",
enum: ["rs274x", "x2"],
description: "Gerber format version"
}
description: "Gerber format version",
},
required: ["output_dir"]
},
required: ["output_dir"],
},
handler: async (params) => {
// Your implementation
return { success: true, files: ["..."] };
}
},
},
{
name: "export_drill",
@@ -135,24 +135,31 @@ export const toolCategories: ToolCategory[] = [
type: "object",
properties: {
output_dir: { type: "string" },
format: { type: "string", enum: ["excellon", "excellon2"] }
format: { type: "string", enum: ["excellon", "excellon2"] },
},
required: ["output_dir"]
required: ["output_dir"],
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
{
name: "export_bom",
description: "Export bill of materials as CSV or XML",
inputSchema: { /* ... */ },
handler: async (params) => { /* ... */ }
inputSchema: {
/* ... */
},
handler: async (params) => {
/* ... */
},
},
// ... more export tools
]
],
},
{
name: "drc",
description: "Design rule checking: clearance validation, electrical rules, manufacturing constraints",
description:
"Design rule checking: clearance validation, electrical rules, manufacturing constraints",
tools: [
{
name: "run_drc",
@@ -162,17 +169,21 @@ export const toolCategories: ToolCategory[] = [
properties: {
report_all: {
type: "boolean",
description: "Report all violations or stop at first"
}
}
description: "Report all violations or stop at first",
},
},
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
{
name: "get_drc_errors",
description: "Get current DRC violations without re-running check",
inputSchema: { type: "object", properties: {} },
handler: async (params) => { /* ... */ }
handler: async (params) => {
/* ... */
},
},
{
name: "set_design_rules",
@@ -183,12 +194,14 @@ export const toolCategories: ToolCategory[] = [
min_clearance: { type: "number", description: "Minimum clearance in mm" },
min_track_width: { type: "number", description: "Minimum track width in mm" },
min_via_diameter: { type: "number", description: "Minimum via diameter in mm" },
min_via_drill: { type: "number", description: "Minimum via drill size in mm" }
}
min_via_drill: { type: "number", description: "Minimum via drill size in mm" },
},
handler: async (params) => { /* ... */ }
}
]
},
handler: async (params) => {
/* ... */
},
},
],
},
{
name: "zones",
@@ -208,22 +221,26 @@ export const toolCategories: ToolCategory[] = [
type: "object",
properties: {
x: { type: "number" },
y: { type: "number" }
}
y: { type: "number" },
},
description: "Polygon vertices in mm"
},
priority: { type: "number", description: "Fill priority (higher fills first)" }
description: "Polygon vertices in mm",
},
required: ["net_name", "layer", "points"]
priority: { type: "number", description: "Fill priority (higher fills first)" },
},
required: ["net_name", "layer", "points"],
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
{
name: "fill_zones",
description: "Recalculate and fill all copper zones",
inputSchema: { type: "object", properties: {} },
handler: async (params) => { /* ... */ }
handler: async (params) => {
/* ... */
},
},
{
name: "remove_zone",
@@ -231,13 +248,15 @@ export const toolCategories: ToolCategory[] = [
inputSchema: {
type: "object",
properties: {
zone_id: { type: "string", description: "Zone identifier" }
zone_id: { type: "string", description: "Zone identifier" },
},
required: ["zone_id"]
required: ["zone_id"],
},
handler: async (params) => { /* ... */ }
}
]
handler: async (params) => {
/* ... */
},
},
],
},
// Add more categories...
];
@@ -293,7 +312,7 @@ export function searchTools(query: string): Array<{
matches.push({
category: category.name,
tool: tool.name,
description: tool.description
description: tool.description,
});
}
}
@@ -313,12 +332,7 @@ These are the tools that enable discovery and execution.
```typescript
// src/tools/router.ts
import {
getAllCategories,
getCategory,
getTool,
searchTools
} from "./registry.js";
import { getAllCategories, getCategory, getTool, searchTools } from "./registry.js";
export const routerTools = {
list_tool_categories: {
@@ -329,20 +343,20 @@ export const routerTools = {
inputSchema: {
type: "object" as const,
properties: {},
required: []
required: [],
},
handler: async () => {
const categories = getAllCategories();
return {
total_categories: categories.length,
total_tools: categories.reduce((sum, c) => sum + c.tools.length, 0),
categories: categories.map(c => ({
categories: categories.map((c) => ({
name: c.name,
description: c.description,
tool_count: c.tools.length
}))
tool_count: c.tools.length,
})),
};
}
},
},
get_category_tools: {
@@ -356,29 +370,29 @@ export const routerTools = {
properties: {
category: {
type: "string",
description: "Category name from list_tool_categories"
}
description: "Category name from list_tool_categories",
},
required: ["category"]
},
required: ["category"],
},
handler: async (params: { category: string }) => {
const category = getCategory(params.category);
if (!category) {
return {
error: `Unknown category: ${params.category}`,
available_categories: getAllCategories().map(c => c.name)
available_categories: getAllCategories().map((c) => c.name),
};
}
return {
category: category.name,
description: category.description,
tools: category.tools.map(t => ({
tools: category.tools.map((t) => ({
name: t.name,
description: t.description,
parameters: t.inputSchema
}))
parameters: t.inputSchema,
})),
};
}
},
},
execute_tool: {
@@ -391,21 +405,21 @@ export const routerTools = {
properties: {
tool_name: {
type: "string",
description: "Tool name (from get_category_tools)"
description: "Tool name (from get_category_tools)",
},
params: {
type: "object",
description: "Tool parameters (see get_category_tools for schema)"
}
description: "Tool parameters (see get_category_tools for schema)",
},
required: ["tool_name"]
},
required: ["tool_name"],
},
handler: async (input: { tool_name: string; params?: Record<string, unknown> }) => {
const entry = getTool(input.tool_name);
if (!entry) {
return {
error: `Unknown tool: ${input.tool_name}`,
hint: "Use list_tool_categories and get_category_tools to find available tools"
hint: "Use list_tool_categories and get_category_tools to find available tools",
};
}
@@ -414,16 +428,16 @@ export const routerTools = {
return {
tool: input.tool_name,
category: entry.category,
result
result,
};
} catch (err) {
return {
error: `Tool execution failed: ${(err as Error).message}`,
tool: input.tool_name,
category: entry.category
category: entry.category,
};
}
}
},
},
search_tools: {
@@ -436,20 +450,20 @@ export const routerTools = {
properties: {
query: {
type: "string",
description: "Search term (e.g., 'gerber', 'zone', 'differential', 'export')"
}
description: "Search term (e.g., 'gerber', 'zone', 'differential', 'export')",
},
required: ["query"]
},
required: ["query"],
},
handler: async (params: { query: string }) => {
const matches = searchTools(params.query);
return {
query: params.query,
count: matches.length,
matches: matches.slice(0, 20) // Limit results
matches: matches.slice(0, 20), // Limit results
};
}
}
},
},
};
```
@@ -478,15 +492,15 @@ export const directTools: ToolDefinition[] = [
template: {
type: "string",
description: "Optional template to use",
enum: ["blank", "arduino", "raspberry-pi"]
}
enum: ["blank", "arduino", "raspberry-pi"],
},
required: ["name", "path"]
},
required: ["name", "path"],
},
handler: async (params) => {
// Implementation
return { success: true, project_path: `${params.path}/${params.name}` };
}
},
},
{
name: "open_project",
@@ -494,29 +508,35 @@ export const directTools: ToolDefinition[] = [
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Path to project file or directory" }
path: { type: "string", description: "Path to project file or directory" },
},
required: ["path"]
required: ["path"],
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
{
name: "save_project",
description: "Save all project files",
inputSchema: {
type: "object",
properties: {}
properties: {},
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
{
name: "get_project_info",
description: "Get current project information: path, files, status",
inputSchema: {
type: "object",
properties: {}
properties: {},
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
// === PRIMARY OPERATIONS (your core workflow) ===
@@ -530,11 +550,13 @@ export const directTools: ToolDefinition[] = [
reference: { type: "string", description: "Reference designator (e.g., R1, U1)" },
x: { type: "number", description: "X position" },
y: { type: "number", description: "Y position" },
rotation: { type: "number", description: "Rotation in degrees", default: 0 }
rotation: { type: "number", description: "Rotation in degrees", default: 0 },
},
required: ["type", "reference", "x", "y"]
required: ["type", "reference", "x", "y"],
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
{
name: "move_component",
@@ -544,11 +566,13 @@ export const directTools: ToolDefinition[] = [
properties: {
reference: { type: "string", description: "Component reference (e.g., R1)" },
x: { type: "number", description: "New X position" },
y: { type: "number", description: "New Y position" }
y: { type: "number", description: "New Y position" },
},
required: ["reference", "x", "y"]
required: ["reference", "x", "y"],
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
{
name: "list_components",
@@ -556,10 +580,12 @@ export const directTools: ToolDefinition[] = [
inputSchema: {
type: "object",
properties: {
filter: { type: "string", description: "Optional filter (e.g., 'R*' for resistors)" }
}
filter: { type: "string", description: "Optional filter (e.g., 'R*' for resistors)" },
},
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
// === SECONDARY OPERATIONS (still common) ===
@@ -571,36 +597,42 @@ export const directTools: ToolDefinition[] = [
properties: {
start: {
type: "object",
properties: { x: { type: "number" }, y: { type: "number" } }
properties: { x: { type: "number" }, y: { type: "number" } },
},
end: {
type: "object",
properties: { x: { type: "number" }, y: { type: "number" } }
properties: { x: { type: "number" }, y: { type: "number" } },
},
net: { type: "string", description: "Net name" }
net: { type: "string", description: "Net name" },
},
required: ["start", "end"]
required: ["start", "end"],
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
{
name: "list_nets",
description: "List all nets/connections",
inputSchema: {
type: "object",
properties: {}
properties: {},
},
handler: async (params) => {
/* ... */
},
handler: async (params) => { /* ... */ }
},
{
name: "get_info",
description: "Get general information about current state",
inputSchema: {
type: "object",
properties: {}
properties: {},
},
handler: async (params) => {
/* ... */
},
},
handler: async (params) => { /* ... */ }
}
];
```
@@ -613,10 +645,7 @@ Wire everything together in your main server file.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { directTools } from "./tools/direct.js";
import { routerTools } from "./tools/router.js";
@@ -634,14 +663,11 @@ const server = new Server(
capabilities: {
tools: {},
},
}
},
);
// Combine all visible tools
const allVisibleTools = [
...directTools,
...Object.values(routerTools)
];
const allVisibleTools = [...directTools, ...Object.values(routerTools)];
// Build a handler map for quick lookup
const toolHandlers = new Map<string, (params: any) => Promise<any>>();
@@ -656,7 +682,7 @@ for (const tool of Object.values(routerTools)) {
// List tools handler - returns only direct + router tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: allVisibleTools.map(tool => ({
tools: allVisibleTools.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
@@ -676,9 +702,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
type: "text",
text: JSON.stringify({
error: `Unknown tool: ${name}`,
hint: "Use list_tool_categories and search_tools to find available tools"
})
}
hint: "Use list_tool_categories and search_tools to find available tools",
}),
},
],
isError: true,
};
@@ -690,8 +716,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
@@ -700,9 +726,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
{
type: "text",
text: JSON.stringify({
error: `Tool execution failed: ${(error as Error).message}`
})
}
error: `Tool execution failed: ${(error as Error).message}`,
}),
},
],
isError: true,
};
@@ -735,7 +761,7 @@ Include tools that are:
**Examples by domain:**
| Domain | Direct Tools |
|--------|-------------|
| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| **KiCAD** | create_project, open_project, save_project, add_component, move_component, add_track, list_components, list_nets, get_board_info |
| **IDA Pro** | open_database, save_database, get_function, list_functions, add_comment, rename, get_xrefs, decompile |
| **Git** | status, add, commit, push, pull, checkout, branch, log |
@@ -754,7 +780,7 @@ Include tools that are:
**Examples:**
| Category | Why Route It |
|----------|-------------|
| ----------------- | ---------------------------- |
| `export` | Only used at end of workflow |
| `drc/validation` | Used during review phase |
| `advanced_*` | Specialty operations |
@@ -854,52 +880,52 @@ const categories = [
{
name: "disassembly",
description: "Disassembly operations: undefine, make code/data, change types",
tools: ["make_code", "make_data", "undefine", "set_type", "make_array", "make_struct"]
tools: ["make_code", "make_data", "undefine", "set_type", "make_array", "make_struct"],
},
{
name: "functions",
description: "Function management: create, delete, modify boundaries, set types",
tools: ["create_function", "delete_function", "set_func_end", "set_func_type", "add_func_arg"]
tools: ["create_function", "delete_function", "set_func_end", "set_func_type", "add_func_arg"],
},
{
name: "types",
description: "Type system: structs, enums, typedefs, parse headers",
tools: ["create_struct", "add_struct_member", "create_enum", "parse_header", "import_types"]
tools: ["create_struct", "add_struct_member", "create_enum", "parse_header", "import_types"],
},
{
name: "patching",
description: "Binary patching: modify bytes, assemble, apply patches",
tools: ["patch_bytes", "patch_word", "patch_dword", "assemble", "apply_patches"]
tools: ["patch_bytes", "patch_word", "patch_dword", "assemble", "apply_patches"],
},
{
name: "scripting",
description: "IDAPython scripting: run scripts, evaluate expressions",
tools: ["run_script", "eval_python", "get_global", "set_global"]
tools: ["run_script", "eval_python", "get_global", "set_global"],
},
{
name: "signatures",
description: "Signatures and patterns: FLIRT, Lumina, create signatures",
tools: ["apply_flirt", "query_lumina", "create_sig", "find_pattern"]
tools: ["apply_flirt", "query_lumina", "create_sig", "find_pattern"],
},
{
name: "debugging",
description: "Debugger control: breakpoints, stepping, memory",
tools: ["set_breakpoint", "step_into", "step_over", "read_memory", "write_memory", "get_regs"]
tools: ["set_breakpoint", "step_into", "step_over", "read_memory", "write_memory", "get_regs"],
},
{
name: "export",
description: "Export: ASM listing, pseudocode, database info, reports",
tools: ["export_asm", "export_c", "export_json", "generate_report"]
tools: ["export_asm", "export_c", "export_json", "generate_report"],
},
{
name: "import",
description: "Import: symbols, types, comments from external sources",
tools: ["import_symbols", "import_pdb", "import_map", "import_comments"]
tools: ["import_symbols", "import_pdb", "import_map", "import_comments"],
},
{
name: "analysis",
description: "Analysis control: reanalyze, find patterns, auto-analysis settings",
tools: ["reanalyze", "find_crypto", "find_strings", "analyze_calls", "set_analysis_options"]
tools: ["reanalyze", "find_crypto", "find_strings", "analyze_calls", "set_analysis_options"],
},
];
```
@@ -962,19 +988,14 @@ Claude: "I've added length tuning meanders to match the trace lengths"
// tests/router.test.ts
import { describe, it, expect } from "vitest";
import {
searchTools,
getCategory,
getTool,
getAllCategories
} from "../src/tools/registry.js";
import { searchTools, getCategory, getTool, getAllCategories } from "../src/tools/registry.js";
import { routerTools } from "../src/tools/router.js";
describe("Tool Registry", () => {
it("should find tools by keyword", () => {
const results = searchTools("export");
expect(results.length).toBeGreaterThan(0);
expect(results.some(r => r.tool.includes("export"))).toBe(true);
expect(results.some((r) => r.tool.includes("export"))).toBe(true);
});
it("should return category info", () => {
@@ -998,7 +1019,7 @@ describe("Router Tools", () => {
it("get_category_tools returns tools for valid category", async () => {
const result = await routerTools.get_category_tools.handler({
category: "export"
category: "export",
});
expect(result.tools).toBeDefined();
expect(result.tools.length).toBeGreaterThan(0);
@@ -1006,7 +1027,7 @@ describe("Router Tools", () => {
it("get_category_tools returns error for invalid category", async () => {
const result = await routerTools.get_category_tools.handler({
category: "nonexistent"
category: "nonexistent",
});
expect(result.error).toBeDefined();
});
@@ -1014,7 +1035,7 @@ describe("Router Tools", () => {
it("execute_tool runs valid tool", async () => {
const result = await routerTools.execute_tool.handler({
tool_name: "export_gerber",
params: { output_dir: "/tmp/test" }
params: { output_dir: "/tmp/test" },
});
expect(result.error).toBeUndefined();
});
@@ -1022,7 +1043,7 @@ describe("Router Tools", () => {
it("execute_tool returns error for invalid tool", async () => {
const result = await routerTools.execute_tool.handler({
tool_name: "nonexistent_tool",
params: {}
params: {},
});
expect(result.error).toBeDefined();
});
@@ -1142,7 +1163,7 @@ Before shipping your router-based MCP server:
## Summary
| Before | After |
|--------|-------|
| ------------------------ | --------------------- |
| 100 tools visible | 15-18 tools visible |
| ~60K+ tokens consumed | ~10K tokens consumed |
| Tool selection confusion | Clear categories |

17
package-lock.json generated
View File

@@ -20,6 +20,7 @@
"@types/glob": "^8.1.0",
"@types/node": "^20.19.0",
"nodemon": "^3.0.1",
"prettier": "^3.8.1",
"typescript": "^5.9.3"
}
},
@@ -1150,6 +1151,22 @@
"node": ">=16.20.0"
}
},
"node_modules/prettier": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",

View File

@@ -44,6 +44,7 @@
"@types/glob": "^8.1.0",
"@types/node": "^20.19.0",
"nodemon": "^3.0.1",
"prettier": "^3.8.1",
"typescript": "^5.9.3"
}
}

View File

@@ -2,31 +2,31 @@
* Configuration handling for KiCAD MCP server
*/
import { readFile } from 'fs/promises';
import { existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { z } from 'zod';
import { logger } from './logger.js';
import { readFile } from "fs/promises";
import { existsSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { z } from "zod";
import { logger } from "./logger.js";
// Get the current directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Default config location
const DEFAULT_CONFIG_PATH = join(dirname(__dirname), 'config', 'default-config.json');
const DEFAULT_CONFIG_PATH = join(dirname(__dirname), "config", "default-config.json");
/**
* Server configuration schema
*/
const ConfigSchema = z.object({
name: z.string().default('kicad-mcp-server'),
version: z.string().default('1.0.0'),
description: z.string().default('MCP server for KiCAD PCB design operations'),
name: z.string().default("kicad-mcp-server"),
version: z.string().default("1.0.0"),
description: z.string().default("MCP server for KiCAD PCB design operations"),
pythonPath: z.string().optional(),
kicadPath: z.string().optional(),
logLevel: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
logDir: z.string().optional()
logLevel: z.enum(["error", "warn", "info", "debug"]).default("info"),
logDir: z.string().optional(),
});
/**
@@ -52,7 +52,7 @@ export async function loadConfig(configPath?: string): Promise<Config> {
}
// Read and parse configuration
const configData = await readFile(filePath, 'utf-8');
const configData = await readFile(filePath, "utf-8");
const config = JSON.parse(configData);
// Validate configuration

View File

@@ -3,11 +3,11 @@
* Main entry point
*/
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { KiCADMcpServer } from './server.js';
import { loadConfig } from './config.js';
import { logger } from './logger.js';
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { KiCADMcpServer } from "./server.js";
import { loadConfig } from "./config.js";
import { logger } from "./logger.js";
// Get the current directory
const __filename = fileURLToPath(import.meta.url);
@@ -26,13 +26,10 @@ async function main() {
const config = await loadConfig(options.configPath);
// Path to the Python script that interfaces with KiCAD
const kicadScriptPath = join(dirname(__dirname), 'python', 'kicad_interface.py');
const kicadScriptPath = join(dirname(__dirname), "python", "kicad_interface.py");
// Create the server
const server = new KiCADMcpServer(
kicadScriptPath,
config.logLevel
);
const server = new KiCADMcpServer(kicadScriptPath, config.logLevel);
// Start the server
await server.start();
@@ -40,8 +37,7 @@ async function main() {
// Setup graceful shutdown
setupGracefulShutdown(server);
logger.info('KiCAD MCP server started with STDIO transport');
logger.info("KiCAD MCP server started with STDIO transport");
} catch (error) {
logger.error(`Failed to start KiCAD MCP server: ${error}`);
process.exit(1);
@@ -55,7 +51,7 @@ function parseCommandLineArgs(args: string[]) {
let configPath = undefined;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--config' && i + 1 < args.length) {
if (args[i] === "--config" && i + 1 < args.length) {
configPath = args[i + 1];
i++;
}
@@ -69,24 +65,24 @@ function parseCommandLineArgs(args: string[]) {
*/
function setupGracefulShutdown(server: KiCADMcpServer) {
// Handle termination signals
process.on('SIGINT', async () => {
logger.info('Received SIGINT signal. Shutting down...');
process.on("SIGINT", async () => {
logger.info("Received SIGINT signal. Shutting down...");
await shutdownServer(server);
});
process.on('SIGTERM', async () => {
logger.info('Received SIGTERM signal. Shutting down...');
process.on("SIGTERM", async () => {
logger.info("Received SIGTERM signal. Shutting down...");
await shutdownServer(server);
});
// Handle uncaught exceptions
process.on('uncaughtException', async (error) => {
process.on("uncaughtException", async (error) => {
logger.error(`Uncaught exception: ${error}`);
await shutdownServer(server);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', async (reason) => {
process.on("unhandledRejection", async (reason) => {
logger.error(`Unhandled promise rejection: ${reason}`);
await shutdownServer(server);
});
@@ -97,9 +93,9 @@ function setupGracefulShutdown(server: KiCADMcpServer) {
*/
async function shutdownServer(server: KiCADMcpServer) {
try {
logger.info('Shutting down KiCAD MCP server...');
logger.info("Shutting down KiCAD MCP server...");
await server.stop();
logger.info('Server shutdown complete. Exiting...');
logger.info("Server shutdown complete. Exiting...");
process.exit(0);
} catch (error) {
logger.error(`Error during shutdown: ${error}`);

View File

@@ -1,10 +1,10 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { spawn, ChildProcess } from 'child_process';
import { existsSync } from 'fs';
import path from 'path';
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { spawn, ChildProcess } from "child_process";
import { existsSync } from "fs";
import path from "path";
// Import all tool definitions for reference
// import { registerBoardTools } from './tools/board.js';
@@ -19,13 +19,13 @@ class KiCADServer {
private server: Server;
private pythonProcess: ChildProcess | null = null;
private kicadScriptPath: string;
private requestQueue: Array<{ request: any, resolve: Function, reject: Function }> = [];
private requestQueue: Array<{ request: any; resolve: Function; reject: Function }> = [];
private processingRequest = false;
constructor() {
// Set absolute path to the Python KiCAD interface script
// Using a hardcoded path to avoid cwd() issues when running from Cline
this.kicadScriptPath = 'c:/repo/KiCAD-MCP/python/kicad_interface.py';
this.kicadScriptPath = "c:/repo/KiCAD-MCP/python/kicad_interface.py";
// Check if script exists
if (!existsSync(this.kicadScriptPath)) {
@@ -35,16 +35,16 @@ class KiCADServer {
// Initialize the server
this.server = new Server(
{
name: 'kicad-mcp-server',
version: '1.0.0'
name: "kicad-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {
// Empty object here, tools will be registered dynamically
}
}
}
},
},
},
);
// Initialize handler with direct pass-through to Python KiCAD interface
@@ -55,231 +55,234 @@ class KiCADServer {
tools: [
// Project tools
{
name: 'create_project',
description: 'Create a new KiCAD project',
name: "create_project",
description: "Create a new KiCAD project",
inputSchema: {
type: 'object',
type: "object",
properties: {
projectName: { type: 'string', description: 'Name of the new project' },
path: { type: 'string', description: 'Path where to create the project' },
template: { type: 'string', description: 'Optional template to use' }
projectName: { type: "string", description: "Name of the new project" },
path: { type: "string", description: "Path where to create the project" },
template: { type: "string", description: "Optional template to use" },
},
required: ["projectName"],
},
required: ['projectName']
}
},
{
name: 'open_project',
description: 'Open an existing KiCAD project',
name: "open_project",
description: "Open an existing KiCAD project",
inputSchema: {
type: 'object',
type: "object",
properties: {
filename: { type: 'string', description: 'Path to the project file' }
filename: { type: "string", description: "Path to the project file" },
},
required: ["filename"],
},
required: ['filename']
}
},
{
name: 'save_project',
description: 'Save the current KiCAD project',
name: "save_project",
description: "Save the current KiCAD project",
inputSchema: {
type: 'object',
type: "object",
properties: {
filename: { type: 'string', description: 'Optional path to save to' }
}
}
filename: { type: "string", description: "Optional path to save to" },
},
},
},
{
name: 'get_project_info',
description: 'Get information about the current project',
name: "get_project_info",
description: "Get information about the current project",
inputSchema: {
type: 'object',
properties: {}
}
type: "object",
properties: {},
},
},
// Board tools
{
name: 'set_board_size',
description: 'Set the size of the PCB board',
name: "set_board_size",
description: "Set the size of the PCB board",
inputSchema: {
type: 'object',
type: "object",
properties: {
width: { type: 'number', description: 'Board width' },
height: { type: 'number', description: 'Board height' },
unit: { type: 'string', description: 'Unit of measurement (mm or inch)' }
width: { type: "number", description: "Board width" },
height: { type: "number", description: "Board height" },
unit: { type: "string", description: "Unit of measurement (mm or inch)" },
},
required: ["width", "height"],
},
required: ['width', 'height']
}
},
{
name: 'add_board_outline',
description: 'Add a board outline to the PCB',
name: "add_board_outline",
description: "Add a board outline to the PCB",
inputSchema: {
type: 'object',
type: "object",
properties: {
shape: { type: 'string', description: 'Shape of outline (rectangle, circle, polygon, rounded_rectangle)' },
width: { type: 'number', description: 'Width for rectangle shapes' },
height: { type: 'number', description: 'Height for rectangle shapes' },
radius: { type: 'number', description: 'Radius for circle shapes' },
cornerRadius: { type: 'number', description: 'Corner radius for rounded rectangles' },
points: { type: 'array', description: 'Array of points for polygon shapes' },
centerX: { type: 'number', description: 'X coordinate of center' },
centerY: { type: 'number', description: 'Y coordinate of center' },
unit: { type: 'string', description: 'Unit of measurement (mm or inch)' }
}
}
shape: {
type: "string",
description: "Shape of outline (rectangle, circle, polygon, rounded_rectangle)",
},
width: { type: "number", description: "Width for rectangle shapes" },
height: { type: "number", description: "Height for rectangle shapes" },
radius: { type: "number", description: "Radius for circle shapes" },
cornerRadius: { type: "number", description: "Corner radius for rounded rectangles" },
points: { type: "array", description: "Array of points for polygon shapes" },
centerX: { type: "number", description: "X coordinate of center" },
centerY: { type: "number", description: "Y coordinate of center" },
unit: { type: "string", description: "Unit of measurement (mm or inch)" },
},
},
},
// Component tools
{
name: 'place_component',
description: 'Place a component on the PCB',
name: "place_component",
description: "Place a component on the PCB",
inputSchema: {
type: 'object',
type: "object",
properties: {
componentId: { type: 'string', description: 'Component ID/footprint to place' },
position: { type: 'object', description: 'Position coordinates' },
reference: { type: 'string', description: 'Component reference designator' },
value: { type: 'string', description: 'Component value' },
rotation: { type: 'number', description: 'Rotation angle in degrees' },
layer: { type: 'string', description: 'Layer to place component on' }
componentId: { type: "string", description: "Component ID/footprint to place" },
position: { type: "object", description: "Position coordinates" },
reference: { type: "string", description: "Component reference designator" },
value: { type: "string", description: "Component value" },
rotation: { type: "number", description: "Rotation angle in degrees" },
layer: { type: "string", description: "Layer to place component on" },
},
required: ["componentId", "position"],
},
required: ['componentId', 'position']
}
},
// Routing tools
{
name: 'add_net',
description: 'Add a new net to the PCB',
name: "add_net",
description: "Add a new net to the PCB",
inputSchema: {
type: 'object',
type: "object",
properties: {
name: { type: 'string', description: 'Net name' },
class: { type: 'string', description: 'Net class' }
name: { type: "string", description: "Net name" },
class: { type: "string", description: "Net class" },
},
required: ["name"],
},
required: ['name']
}
},
{
name: 'route_trace',
description: 'Route a trace between two points or pads',
name: "route_trace",
description: "Route a trace between two points or pads",
inputSchema: {
type: 'object',
type: "object",
properties: {
start: { type: 'object', description: 'Start point or pad' },
end: { type: 'object', description: 'End point or pad' },
layer: { type: 'string', description: 'Layer to route on' },
width: { type: 'number', description: 'Track width' },
net: { type: 'string', description: 'Net name' }
start: { type: "object", description: "Start point or pad" },
end: { type: "object", description: "End point or pad" },
layer: { type: "string", description: "Layer to route on" },
width: { type: "number", description: "Track width" },
net: { type: "string", description: "Net name" },
},
required: ["start", "end"],
},
required: ['start', 'end']
}
},
// Schematic tools
{
name: 'create_schematic',
description: 'Create a new KiCAD schematic',
name: "create_schematic",
description: "Create a new KiCAD schematic",
inputSchema: {
type: 'object',
type: "object",
properties: {
projectName: { type: 'string', description: 'Name of the schematic project' },
path: { type: 'string', description: 'Path where to create the schematic file' },
metadata: { type: 'object', description: 'Optional metadata for the schematic' }
projectName: { type: "string", description: "Name of the schematic project" },
path: { type: "string", description: "Path where to create the schematic file" },
metadata: { type: "object", description: "Optional metadata for the schematic" },
},
required: ["projectName"],
},
required: ['projectName']
}
},
{
name: 'load_schematic',
description: 'Load an existing KiCAD schematic',
name: "load_schematic",
description: "Load an existing KiCAD schematic",
inputSchema: {
type: 'object',
type: "object",
properties: {
filename: { type: 'string', description: 'Path to the schematic file to load' }
filename: { type: "string", description: "Path to the schematic file to load" },
},
required: ["filename"],
},
required: ['filename']
}
},
{
name: 'add_schematic_component',
description: 'Add a component to a KiCAD schematic',
name: "add_schematic_component",
description: "Add a component to a KiCAD schematic",
inputSchema: {
type: 'object',
type: "object",
properties: {
schematicPath: { type: 'string', description: 'Path to the schematic file' },
schematicPath: { type: "string", description: "Path to the schematic file" },
component: {
type: 'object',
description: 'Component definition',
type: "object",
description: "Component definition",
properties: {
type: { type: 'string', description: 'Component type (e.g., R, C, LED)' },
reference: { type: 'string', description: 'Reference designator (e.g., R1, C2)' },
value: { type: 'string', description: 'Component value (e.g., 10k, 0.1uF)' },
library: { type: 'string', description: 'Symbol library name' },
x: { type: 'number', description: 'X position in schematic' },
y: { type: 'number', description: 'Y position in schematic' },
rotation: { type: 'number', description: 'Rotation angle in degrees' },
properties: { type: 'object', description: 'Additional properties' }
type: { type: "string", description: "Component type (e.g., R, C, LED)" },
reference: { type: "string", description: "Reference designator (e.g., R1, C2)" },
value: { type: "string", description: "Component value (e.g., 10k, 0.1uF)" },
library: { type: "string", description: "Symbol library name" },
x: { type: "number", description: "X position in schematic" },
y: { type: "number", description: "Y position in schematic" },
rotation: { type: "number", description: "Rotation angle in degrees" },
properties: { type: "object", description: "Additional properties" },
},
required: ['type', 'reference']
}
required: ["type", "reference"],
},
},
required: ["schematicPath", "component"],
},
required: ['schematicPath', 'component']
}
},
{
name: 'add_schematic_wire',
description: 'Add a wire connection to a KiCAD schematic',
name: "add_schematic_wire",
description: "Add a wire connection to a KiCAD schematic",
inputSchema: {
type: 'object',
type: "object",
properties: {
schematicPath: { type: 'string', description: 'Path to the schematic file' },
schematicPath: { type: "string", description: "Path to the schematic file" },
startPoint: {
type: 'array',
description: 'Starting point coordinates [x, y]',
items: { type: 'number' },
type: "array",
description: "Starting point coordinates [x, y]",
items: { type: "number" },
minItems: 2,
maxItems: 2
maxItems: 2,
},
endPoint: {
type: 'array',
description: 'Ending point coordinates [x, y]',
items: { type: 'number' },
type: "array",
description: "Ending point coordinates [x, y]",
items: { type: "number" },
minItems: 2,
maxItems: 2
}
maxItems: 2,
},
},
required: ["schematicPath", "startPoint", "endPoint"],
},
required: ['schematicPath', 'startPoint', 'endPoint']
}
},
{
name: 'list_schematic_libraries',
description: 'List available KiCAD symbol libraries',
name: "list_schematic_libraries",
description: "List available KiCAD symbol libraries",
inputSchema: {
type: 'object',
type: "object",
properties: {
searchPaths: {
type: 'array',
description: 'Optional search paths for libraries',
items: { type: 'string' }
}
}
}
type: "array",
description: "Optional search paths for libraries",
items: { type: "string" },
},
},
},
},
{
name: 'export_schematic_pdf',
description: 'Export a KiCAD schematic to PDF',
name: "export_schematic_pdf",
description: "Export a KiCAD schematic to PDF",
inputSchema: {
type: 'object',
type: "object",
properties: {
schematicPath: { type: 'string', description: 'Path to the schematic file' },
outputPath: { type: 'string', description: 'Path for the output PDF file' }
schematicPath: { type: "string", description: "Path to the schematic file" },
outputPath: { type: "string", description: "Path for the output PDF file" },
},
required: ['schematicPath', 'outputPath']
}
}
]
required: ["schematicPath", "outputPath"],
},
},
],
}));
// Register tool call handler
@@ -299,35 +302,35 @@ class KiCADServer {
async start() {
try {
console.error('Starting KiCAD MCP server...');
console.error("Starting KiCAD MCP server...");
// Start the Python process for KiCAD scripting
console.error(`Starting Python process with script: ${this.kicadScriptPath}`);
const pythonExe = 'C:\\Program Files\\KiCad\\9.0\\bin\\python.exe';
const pythonExe = "C:\\Program Files\\KiCad\\9.0\\bin\\python.exe";
console.error(`Using Python executable: ${pythonExe}`);
this.pythonProcess = spawn(pythonExe, [this.kicadScriptPath], {
stdio: ['pipe', 'pipe', 'pipe'],
stdio: ["pipe", "pipe", "pipe"],
env: {
...process.env,
PYTHONPATH: 'C:/Program Files/KiCad/9.0/lib/python3/dist-packages'
}
PYTHONPATH: "C:/Program Files/KiCad/9.0/lib/python3/dist-packages",
},
});
// Listen for process exit
this.pythonProcess.on('exit', (code, signal) => {
this.pythonProcess.on("exit", (code, signal) => {
console.error(`Python process exited with code ${code} and signal ${signal}`);
this.pythonProcess = null;
});
// Listen for process errors
this.pythonProcess.on('error', (err) => {
this.pythonProcess.on("error", (err) => {
console.error(`Python process error: ${err.message}`);
});
// Set up error logging for stderr
if (this.pythonProcess.stderr) {
this.pythonProcess.stderr.on('data', (data: Buffer) => {
this.pythonProcess.stderr.on("data", (data: Buffer) => {
console.error(`Python stderr: ${data.toString()}`);
});
}
@@ -335,22 +338,21 @@ class KiCADServer {
// Connect to transport
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('KiCAD MCP server running');
console.error("KiCAD MCP server running");
// Keep the process running
process.on('SIGINT', () => {
process.on("SIGINT", () => {
if (this.pythonProcess) {
this.pythonProcess.kill();
}
this.server.close().catch(console.error);
process.exit(0);
});
} catch (error: unknown) {
if (error instanceof Error) {
console.error('Failed to start MCP server:', error.message);
console.error("Failed to start MCP server:", error.message);
} else {
console.error('Failed to start MCP server: Unknown error');
console.error("Failed to start MCP server: Unknown error");
}
process.exit(1);
}
@@ -360,7 +362,7 @@ class KiCADServer {
return new Promise((resolve, reject) => {
// Check if Python process is running
if (!this.pythonProcess) {
console.error('Python process is not running');
console.error("Python process is not running");
reject(new Error("Python process for KiCAD scripting is not running"));
return;
}
@@ -369,7 +371,7 @@ class KiCADServer {
this.requestQueue.push({
request: { command, params },
resolve,
reject
reject,
});
// Process the queue if not already processing
@@ -398,16 +400,16 @@ class KiCADServer {
const requestStr = JSON.stringify(request);
// Set up response handling
let responseData = '';
let responseData = "";
// Clear any previous listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.removeAllListeners('data');
this.pythonProcess.stdout.removeAllListeners("data");
}
// Set up new listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.on('data', (data: Buffer) => {
this.pythonProcess.stdout.on("data", (data: Buffer) => {
const chunk = data.toString();
console.error(`Received data chunk: ${chunk.length} bytes`);
responseData += chunk;
@@ -418,7 +420,9 @@ class KiCADServer {
const result = JSON.parse(responseData);
// If we get here, we have a valid JSON response
console.error(`Completed KiCAD command: ${request.command} with result: ${JSON.stringify(result)}`);
console.error(
`Completed KiCAD command: ${request.command} with result: ${JSON.stringify(result)}`,
);
// Reset processing flag
this.processingRequest = false;
@@ -428,7 +432,7 @@ class KiCADServer {
// Clear listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.removeAllListeners('data');
this.pythonProcess.stdout.removeAllListeners("data");
}
// Resolve with the expected MCP tool response format
@@ -436,20 +440,20 @@ class KiCADServer {
resolve({
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
type: "text",
text: JSON.stringify(result, null, 2),
},
],
});
} else {
resolve({
content: [
{
type: 'text',
text: result.errorDetails || result.message || 'Unknown error'
}
type: "text",
text: result.errorDetails || result.message || "Unknown error",
},
],
isError: true
isError: true,
});
}
} catch (e) {
@@ -464,7 +468,7 @@ class KiCADServer {
// Clear listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.removeAllListeners('data');
this.pythonProcess.stdout.removeAllListeners("data");
}
// Reset processing flag
@@ -479,7 +483,7 @@ class KiCADServer {
// Write the request to the Python process
console.error(`Sending request: ${requestStr}`);
this.pythonProcess?.stdin?.write(requestStr + '\n');
this.pythonProcess?.stdin?.write(requestStr + "\n");
} catch (error) {
console.error(`Error processing request: ${error}`);

View File

@@ -2,21 +2,21 @@
* Logger for KiCAD MCP server
*/
import { existsSync, mkdirSync, appendFileSync } from 'fs';
import { join } from 'path';
import * as os from 'os';
import { existsSync, mkdirSync, appendFileSync } from "fs";
import { join } from "path";
import * as os from "os";
// Log levels
type LogLevel = 'error' | 'warn' | 'info' | 'debug';
type LogLevel = "error" | "warn" | "info" | "debug";
// Default log directory
const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
const DEFAULT_LOG_DIR = join(os.homedir(), ".kicad-mcp", "logs");
/**
* Logger class for KiCAD MCP server
*/
class Logger {
private logLevel: LogLevel = 'info';
private logLevel: LogLevel = "info";
private logDir: string = DEFAULT_LOG_DIR;
/**
@@ -45,7 +45,7 @@ class Logger {
* @param message Message to log
*/
error(message: string): void {
this.log('error', message);
this.log("error", message);
}
/**
@@ -53,8 +53,8 @@ class Logger {
* @param message Message to log
*/
warn(message: string): void {
if (['error', 'warn', 'info', 'debug'].includes(this.logLevel)) {
this.log('warn', message);
if (["error", "warn", "info", "debug"].includes(this.logLevel)) {
this.log("warn", message);
}
}
@@ -63,8 +63,8 @@ class Logger {
* @param message Message to log
*/
info(message: string): void {
if (['info', 'debug'].includes(this.logLevel)) {
this.log('info', message);
if (["info", "debug"].includes(this.logLevel)) {
this.log("info", message);
}
}
@@ -73,8 +73,8 @@ class Logger {
* @param message Message to log
*/
debug(message: string): void {
if (this.logLevel === 'debug') {
this.log('debug', message);
if (this.logLevel === "debug") {
this.log("debug", message);
}
}
@@ -85,8 +85,9 @@ class Logger {
*/
private log(level: LogLevel, message: string): void {
const now = new Date();
const pad = (n: number, w = 2) => String(n).padStart(w, '0');
const timestamp = `${now.getFullYear()}-${pad(now.getMonth()+1)}-${pad(now.getDate())} ` +
const pad = (n: number, w = 2) => String(n).padStart(w, "0");
const timestamp =
`${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ` +
`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())},${pad(now.getMilliseconds(), 3)}`;
const formattedMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
@@ -101,8 +102,8 @@ class Logger {
mkdirSync(this.logDir, { recursive: true });
}
const logFile = join(this.logDir, `kicad-mcp-${new Date().toISOString().split('T')[0]}.log`);
appendFileSync(logFile, formattedMessage + '\n');
const logFile = join(this.logDir, `kicad-mcp-${new Date().toISOString().split("T")[0]}.log`);
appendFileSync(logFile, formattedMessage + "\n");
} catch (error) {
console.error(`Failed to write to log file: ${error}`);
}

View File

@@ -5,9 +5,9 @@
* in KiCAD PCB design.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
/**
* Register component prompts with the MCP server
@@ -15,7 +15,7 @@ import { logger } from '../logger.js';
* @param server MCP server instance
*/
export function registerComponentPrompts(server: McpServer): void {
logger.info('Registering component prompts');
logger.info("Registering component prompts");
// ------------------------------------------------------
// Component Selection Prompt
@@ -23,7 +23,7 @@ export function registerComponentPrompts(server: McpServer): void {
server.prompt(
"component_selection",
{
requirements: z.string().describe("Description of the circuit requirements and constraints")
requirements: z.string().describe("Description of the circuit requirements and constraints"),
},
() => ({
messages: [
@@ -44,11 +44,11 @@ Suggest appropriate components with their values, ratings, and footprints. Consi
- Thermal characteristics
- Performance specifications
For each component type, recommend specific values and provide a brief explanation of your recommendation. If appropriate, suggest alternatives with different trade-offs.`
}
}
]
})
For each component type, recommend specific values and provide a brief explanation of your recommendation. If appropriate, suggest alternatives with different trade-offs.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -57,7 +57,7 @@ For each component type, recommend specific values and provide a brief explanati
server.prompt(
"component_placement_strategy",
{
components: z.string().describe("List of components to be placed on the PCB")
components: z.string().describe("List of components to be placed on the PCB"),
},
() => ({
messages: [
@@ -91,11 +91,11 @@ Provide a strategy for optimal placement considering:
- Adequate spacing for rework
- Consider component height distribution
Group components functionally and suggest a logical arrangement. If possible, provide a rough sketch or description of component zones.`
}
}
]
})
Group components functionally and suggest a logical arrangement. If possible, provide a rough sketch or description of component zones.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -104,7 +104,9 @@ Group components functionally and suggest a logical arrangement. If possible, pr
server.prompt(
"component_replacement_analysis",
{
component_info: z.string().describe("Information about the component that needs to be replaced")
component_info: z
.string()
.describe("Information about the component that needs to be replaced"),
},
() => ({
messages: [
@@ -137,11 +139,11 @@ Consider these factors when suggesting replacements:
- Cost comparison with original part
- Lead time considerations
Suggest suitable replacement options and explain the advantages and disadvantages of each. Include any circuit modifications that might be necessary.`
}
}
]
})
Suggest suitable replacement options and explain the advantages and disadvantages of each. Include any circuit modifications that might be necessary.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -150,7 +152,9 @@ Suggest suitable replacement options and explain the advantages and disadvantage
server.prompt(
"component_troubleshooting",
{
issue_description: z.string().describe("Description of the component or circuit issue being troubleshooted")
issue_description: z
.string()
.describe("Description of the component or circuit issue being troubleshooted"),
},
() => ({
messages: [
@@ -184,11 +188,11 @@ Use the following systematic approach to diagnose the problem:
- Check for potential EMI/RFI issues
- Review mechanical stress or vibration effects
Based on the available information, suggest likely causes of the issue and recommend specific steps to diagnose and resolve the problem.`
}
}
]
})
Based on the available information, suggest likely causes of the issue and recommend specific steps to diagnose and resolve the problem.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -197,7 +201,9 @@ Based on the available information, suggest likely causes of the issue and recom
server.prompt(
"component_value_calculation",
{
circuit_requirements: z.string().describe("Description of the circuit function and performance requirements")
circuit_requirements: z
.string()
.describe("Description of the circuit function and performance requirements"),
},
() => ({
messages: [
@@ -220,12 +226,12 @@ Follow these steps to determine the optimal component values:
- Parasitic effects
- Available standard values
Present your calculations step-by-step, showing your work and explaining your reasoning. Recommend specific component values, explaining why they're appropriate for this application. If there are multiple valid approaches, discuss the trade-offs between them.`
}
}
]
})
Present your calculations step-by-step, showing your work and explaining your reasoning. Recommend specific component values, explaining why they're appropriate for this application. If there are multiple valid approaches, discuss the trade-offs between them.`,
},
},
],
}),
);
logger.info('Component prompts registered');
logger.info("Component prompts registered");
}

View File

@@ -5,9 +5,9 @@
* in KiCAD.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
/**
* Register design prompts with the MCP server
@@ -15,7 +15,7 @@ import { logger } from '../logger.js';
* @param server MCP server instance
*/
export function registerDesignPrompts(server: McpServer): void {
logger.info('Registering design prompts');
logger.info("Registering design prompts");
// ------------------------------------------------------
// PCB Layout Review Prompt
@@ -23,7 +23,11 @@ export function registerDesignPrompts(server: McpServer): void {
server.prompt(
"pcb_layout_review",
{
pcb_design_info: z.string().describe("Information about the current PCB design, including board dimensions, layer stack-up, component placement, and routing details")
pcb_design_info: z
.string()
.describe(
"Information about the current PCB design, including board dimensions, layer stack-up, component placement, and routing details",
),
},
() => ({
messages: [
@@ -71,11 +75,11 @@ When reviewing the PCB layout, consider these key areas:
- Silkscreen clarity and usefulness
- Solder mask considerations
Based on the provided information, identify potential issues and suggest specific improvements to enhance the PCB design.`
}
}
]
})
Based on the provided information, identify potential issues and suggest specific improvements to enhance the PCB design.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -84,7 +88,11 @@ Based on the provided information, identify potential issues and suggest specifi
server.prompt(
"layer_stackup_planning",
{
design_requirements: z.string().describe("Information about the PCB design requirements, including signal types, speed/frequency, power requirements, and any special considerations")
design_requirements: z
.string()
.describe(
"Information about the PCB design requirements, including signal types, speed/frequency, power requirements, and any special considerations",
),
},
() => ({
messages: [
@@ -128,11 +136,11 @@ When planning a PCB layer stack-up, consider these important factors:
- Thermal properties
- Mechanical stability
Based on the provided requirements, recommend an appropriate layer stack-up, including the number of layers, their arrangement, material specifications, and thickness parameters. Explain the rationale behind your recommendations.`
}
}
]
})
Based on the provided requirements, recommend an appropriate layer stack-up, including the number of layers, their arrangement, material specifications, and thickness parameters. Explain the rationale behind your recommendations.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -141,7 +149,11 @@ Based on the provided requirements, recommend an appropriate layer stack-up, inc
server.prompt(
"design_rule_development",
{
project_requirements: z.string().describe("Information about the PCB project requirements, including technology, speed/frequency, manufacturing capabilities, and any special considerations")
project_requirements: z
.string()
.describe(
"Information about the PCB project requirements, including technology, speed/frequency, manufacturing capabilities, and any special considerations",
),
},
() => ({
messages: [
@@ -185,11 +197,11 @@ When developing PCB design rules, consider these key areas:
- Thermal relief parameters
- Teardrop specifications
Based on the provided project requirements, recommend a comprehensive set of design rules that will ensure signal integrity, manufacturability, and reliability of the PCB. Provide specific values where appropriate and explain the rationale behind critical rules.`
}
}
]
})
Based on the provided project requirements, recommend a comprehensive set of design rules that will ensure signal integrity, manufacturability, and reliability of the PCB. Provide specific values where appropriate and explain the rationale behind critical rules.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -198,7 +210,11 @@ Based on the provided project requirements, recommend a comprehensive set of des
server.prompt(
"component_selection_guidance",
{
circuit_requirements: z.string().describe("Information about the circuit requirements, including functionality, performance needs, operating environment, and any special considerations")
circuit_requirements: z
.string()
.describe(
"Information about the circuit requirements, including functionality, performance needs, operating environment, and any special considerations",
),
},
() => ({
messages: [
@@ -246,11 +262,11 @@ When selecting components for a PCB design, consider these important factors:
- RoHS/REACH compliance
- Special handling requirements
Based on the provided circuit requirements, recommend appropriate component types, packages, and specific considerations for this design. Provide guidance on critical component selections and explain the rationale behind your recommendations.`
}
}
]
})
Based on the provided circuit requirements, recommend appropriate component types, packages, and specific considerations for this design. Provide guidance on critical component selections and explain the rationale behind your recommendations.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -259,8 +275,16 @@ Based on the provided circuit requirements, recommend appropriate component type
server.prompt(
"pcb_design_optimization",
{
design_info: z.string().describe("Information about the current PCB design, including board dimensions, layer stack-up, component placement, and routing details"),
optimization_goals: z.string().describe("Specific goals for optimization, such as performance improvement, cost reduction, size reduction, or manufacturability enhancement")
design_info: z
.string()
.describe(
"Information about the current PCB design, including board dimensions, layer stack-up, component placement, and routing details",
),
optimization_goals: z
.string()
.describe(
"Specific goals for optimization, such as performance improvement, cost reduction, size reduction, or manufacturability enhancement",
),
},
() => ({
messages: [
@@ -310,12 +334,12 @@ When optimizing a PCB design, consider these key areas based on the stated goals
- Alternative packaging approaches
- Connector and interface optimization
Based on the provided information and optimization goals, suggest specific, actionable improvements to the PCB design. Prioritize your recommendations based on their potential impact and implementation feasibility.`
}
}
]
})
Based on the provided information and optimization goals, suggest specific, actionable improvements to the PCB design. Prioritize your recommendations based on their potential impact and implementation feasibility.`,
},
},
],
}),
);
logger.info('Design prompts registered');
logger.info("Design prompts registered");
}

View File

@@ -23,10 +23,7 @@ export function registerFootprintPrompts(server: McpServer): void {
.describe(
"Component description, e.g. 'SOT-23 NPN transistor' or '2-pin JST XH 2.5mm connector'",
),
libraryPath: z
.string()
.optional()
.describe("Target .pretty library path (optional)"),
libraryPath: z.string().optional().describe("Target .pretty library path (optional)"),
},
() => ({
messages: [
@@ -107,9 +104,7 @@ Now create the footprint for: {{component}}`,
server.prompt(
"footprint_ipc_checklist",
{
footprintPath: z
.string()
.describe("Path to the .kicad_mod file to review"),
footprintPath: z.string().describe("Path to the .kicad_mod file to review"),
},
() => ({
messages: [

View File

@@ -5,9 +5,9 @@
* in KiCAD PCB design.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
/**
* Register routing prompts with the MCP server
@@ -15,7 +15,7 @@ import { logger } from '../logger.js';
* @param server MCP server instance
*/
export function registerRoutingPrompts(server: McpServer): void {
logger.info('Registering routing prompts');
logger.info("Registering routing prompts");
// ------------------------------------------------------
// Routing Strategy Prompt
@@ -23,7 +23,11 @@ export function registerRoutingPrompts(server: McpServer): void {
server.prompt(
"routing_strategy",
{
board_info: z.string().describe("Information about the PCB board, including dimensions, layer stack-up, and components")
board_info: z
.string()
.describe(
"Information about the PCB board, including dimensions, layer stack-up, and components",
),
},
() => ({
messages: [
@@ -63,11 +67,11 @@ Consider the following aspects when developing your routing strategy:
- Plan for layer transitions (vias)
- Consider impedance control requirements
Provide a comprehensive routing strategy that addresses these aspects, with specific recommendations for this particular board design.`
}
}
]
})
Provide a comprehensive routing strategy that addresses these aspects, with specific recommendations for this particular board design.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -76,7 +80,11 @@ Provide a comprehensive routing strategy that addresses these aspects, with spec
server.prompt(
"differential_pair_routing",
{
differential_pairs: z.string().describe("Information about the differential pairs to be routed, including signal names, source and destination components, and speed/frequency requirements")
differential_pairs: z
.string()
.describe(
"Information about the differential pairs to be routed, including signal names, source and destination components, and speed/frequency requirements",
),
},
() => ({
messages: [
@@ -115,11 +123,11 @@ When routing differential pairs, follow these best practices:
- Consider the need for series or parallel termination resistors
- Place termination components close to the endpoints
Based on the provided information, suggest specific routing approaches for these differential pairs, including recommended trace width, spacing, and any special considerations for this particular design.`
}
}
]
})
Based on the provided information, suggest specific routing approaches for these differential pairs, including recommended trace width, spacing, and any special considerations for this particular design.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -128,7 +136,11 @@ Based on the provided information, suggest specific routing approaches for these
server.prompt(
"high_speed_routing",
{
high_speed_signals: z.string().describe("Information about the high-speed signals to be routed, including signal names, source and destination components, and speed/frequency requirements")
high_speed_signals: z
.string()
.describe(
"Information about the high-speed signals to be routed, including signal names, source and destination components, and speed/frequency requirements",
),
},
() => ({
messages: [
@@ -168,11 +180,11 @@ When routing high-speed signals, consider these critical factors:
- Consider transmission line effects
- Account for capacitive loading from components and vias
Based on the provided information, suggest specific routing approaches for these high-speed signals, including recommended trace width, layer assignment, and any special considerations for this particular design.`
}
}
]
})
Based on the provided information, suggest specific routing approaches for these high-speed signals, including recommended trace width, layer assignment, and any special considerations for this particular design.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -181,7 +193,11 @@ Based on the provided information, suggest specific routing approaches for these
server.prompt(
"power_distribution",
{
power_requirements: z.string().describe("Information about the power requirements, including voltage rails, current needs, and components requiring power")
power_requirements: z
.string()
.describe(
"Information about the power requirements, including voltage rails, current needs, and components requiring power",
),
},
() => ({
messages: [
@@ -221,11 +237,11 @@ Consider these key aspects of power distribution network design:
- Keep power and ground planes closely coupled
- Consider filtering for noise-sensitive circuits
Based on the provided information, suggest a comprehensive power distribution strategy, including specific recommendations for plane usage, trace widths, decoupling, and any special considerations for this particular design.`
}
}
]
})
Based on the provided information, suggest a comprehensive power distribution strategy, including specific recommendations for plane usage, trace widths, decoupling, and any special considerations for this particular design.`,
},
},
],
}),
);
// ------------------------------------------------------
@@ -234,7 +250,11 @@ Based on the provided information, suggest a comprehensive power distribution st
server.prompt(
"via_usage",
{
board_info: z.string().describe("Information about the PCB board, including layer count, thickness, and design requirements")
board_info: z
.string()
.describe(
"Information about the PCB board, including layer count, thickness, and design requirements",
),
},
() => ({
messages: [
@@ -277,12 +297,12 @@ Consider these important aspects of via usage:
- Via placement to minimize trace length
- Tenting and plugging options
Based on the provided information, recommend appropriate via strategies for this PCB design, including specific via types, sizes, and placement guidelines.`
}
}
]
})
Based on the provided information, recommend appropriate via strategies for this PCB design, including specific via types, sizes, and placement guidelines.`,
},
},
],
}),
);
logger.info('Routing prompts registered');
logger.info("Routing prompts registered");
}

View File

@@ -5,10 +5,10 @@
* to the LLM, enabling better context-aware assistance.
*/
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { createJsonResponse, createBinaryResponse } from '../utils/resource-helpers.js';
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
import { createJsonResponse, createBinaryResponse } from "../utils/resource-helpers.js";
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
@@ -20,77 +20,77 @@ type CommandFunction = (command: string, params: Record<string, unknown>) => Pro
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerBoardResources(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering board resources');
logger.info("Registering board resources");
// ------------------------------------------------------
// Board Information Resource
// ------------------------------------------------------
server.resource(
"board_info",
"kicad://board/info",
async (uri) => {
logger.debug('Retrieving board information');
server.resource("board_info", "kicad://board/info", async (uri) => {
logger.debug("Retrieving board information");
const result = await callKicadScript("get_board_info", {});
if (!result.success) {
logger.error(`Failed to retrieve board information: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve board information",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug('Successfully retrieved board information');
logger.debug("Successfully retrieved board information");
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Layer List Resource
// ------------------------------------------------------
server.resource(
"layer_list",
"kicad://board/layers",
async (uri) => {
logger.debug('Retrieving layer list');
server.resource("layer_list", "kicad://board/layers", async (uri) => {
logger.debug("Retrieving layer list");
const result = await callKicadScript("get_layer_list", {});
if (!result.success) {
logger.error(`Failed to retrieve layer list: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve layer list",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved ${result.layers?.length || 0} layers`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Board Extents Resource
@@ -101,12 +101,12 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma
list: async () => ({
resources: [
{ uri: "kicad://board/extents/mm", name: "Millimeters" },
{ uri: "kicad://board/extents/inch", name: "Inches" }
]
})
{ uri: "kicad://board/extents/inch", name: "Inches" },
],
}),
}),
async (uri, params) => {
const unit = params.unit || 'mm';
const unit = params.unit || "mm";
logger.debug(`Retrieving board extents in ${unit}`);
const result = await callKicadScript("get_board_extents", { unit });
@@ -114,26 +114,30 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma
if (!result.success) {
logger.error(`Failed to retrieve board extents: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve board extents",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug('Successfully retrieved board extents');
logger.debug("Successfully retrieved board extents");
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
},
);
// ------------------------------------------------------
@@ -146,59 +150,65 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma
resources: [
{ uri: "kicad://board/2d-view/png", name: "PNG Format" },
{ uri: "kicad://board/2d-view/jpg", name: "JPEG Format" },
{ uri: "kicad://board/2d-view/svg", name: "SVG Format" }
]
})
{ uri: "kicad://board/2d-view/svg", name: "SVG Format" },
],
}),
}),
async (uri, params) => {
const format = (params.format || 'png') as 'png' | 'jpg' | 'svg';
const format = (params.format || "png") as "png" | "jpg" | "svg";
const width = params.width ? parseInt(params.width as string) : undefined;
const height = params.height ? parseInt(params.height as string) : undefined;
// Handle layers parameter - could be string or array
const layers = typeof params.layers === 'string' ? params.layers.split(',') : params.layers;
const layers = typeof params.layers === "string" ? params.layers.split(",") : params.layers;
logger.debug('Retrieving 2D board view');
logger.debug("Retrieving 2D board view");
const result = await callKicadScript("get_board_2d_view", {
layers,
width,
height,
format
format,
});
if (!result.success) {
logger.error(`Failed to retrieve 2D board view: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve 2D board view",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug('Successfully retrieved 2D board view');
logger.debug("Successfully retrieved 2D board view");
if (format === 'svg') {
if (format === "svg") {
return {
contents: [{
contents: [
{
uri: uri.href,
text: result.imageData,
mimeType: "image/svg+xml"
}]
mimeType: "image/svg+xml",
},
],
};
} else {
return {
contents: [{
contents: [
{
uri: uri.href,
blob: result.imageData,
mimeType: format === "jpg" ? "image/jpeg" : "image/png"
}]
mimeType: format === "jpg" ? "image/jpeg" : "image/png",
},
],
};
}
}
},
);
// ------------------------------------------------------
@@ -211,12 +221,12 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma
resources: [
{ uri: "kicad://board/3d-view/isometric", name: "Isometric View" },
{ uri: "kicad://board/3d-view/top", name: "Top View" },
{ uri: "kicad://board/3d-view/bottom", name: "Bottom View" }
]
})
{ uri: "kicad://board/3d-view/bottom", name: "Bottom View" },
],
}),
}),
async (uri, params) => {
const angle = params.angle || 'isometric';
const angle = params.angle || "isometric";
const width = params.width ? parseInt(params.width as string) : undefined;
const height = params.height ? parseInt(params.height as string) : undefined;
@@ -224,56 +234,59 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma
const result = await callKicadScript("get_board_3d_view", {
width,
height,
angle
angle,
});
if (!result.success) {
logger.error(`Failed to retrieve 3D board view: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve 3D board view",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug('Successfully retrieved 3D board view');
logger.debug("Successfully retrieved 3D board view");
return {
contents: [{
contents: [
{
uri: uri.href,
blob: result.imageData,
mimeType: "image/png"
}]
mimeType: "image/png",
},
],
};
}
},
);
// ------------------------------------------------------
// Board Statistics Resource
// ------------------------------------------------------
server.resource(
"board_statistics",
"kicad://board/statistics",
async (uri) => {
logger.debug('Generating board statistics');
server.resource("board_statistics", "kicad://board/statistics", async (uri) => {
logger.debug("Generating board statistics");
// Get board info
const boardResult = await callKicadScript("get_board_info", {});
if (!boardResult.success) {
logger.error(`Failed to retrieve board information: ${boardResult.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate board statistics",
details: boardResult.errorDetails
details: boardResult.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
@@ -282,14 +295,16 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma
if (!componentsResult.success) {
logger.error(`Failed to retrieve component list: ${componentsResult.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate board statistics",
details: componentsResult.errorDetails
details: componentsResult.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
@@ -298,14 +313,16 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma
if (!netsResult.success) {
logger.error(`Failed to retrieve nets list: ${netsResult.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate board statistics",
details: netsResult.errorDetails
details: netsResult.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
@@ -314,29 +331,30 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma
board: {
size: boardResult.size,
layers: boardResult.layers?.length || 0,
title: boardResult.title
title: boardResult.title,
},
components: {
count: componentsResult.components?.length || 0,
types: countComponentTypes(componentsResult.components || [])
types: countComponentTypes(componentsResult.components || []),
},
nets: {
count: netsResult.nets?.length || 0
}
count: netsResult.nets?.length || 0,
},
};
logger.debug('Successfully generated board statistics');
logger.debug("Successfully generated board statistics");
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(statistics),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
logger.info('Board resources registered');
logger.info("Board resources registered");
}
/**
@@ -346,7 +364,7 @@ function countComponentTypes(components: any[]): Record<string, number> {
const typeCounts: Record<string, number> = {};
for (const component of components) {
const type = component.value?.split(' ')[0] || 'Unknown';
const type = component.value?.split(" ")[0] || "Unknown";
typeCounts[type] = (typeCounts[type] || 0) + 1;
}

View File

@@ -5,8 +5,8 @@
* to the LLM, enabling better context-aware assistance.
*/
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { logger } from '../logger.js';
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { logger } from "../logger.js";
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
@@ -17,43 +17,46 @@ type CommandFunction = (command: string, params: Record<string, unknown>) => Pro
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerComponentResources(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering component resources');
export function registerComponentResources(
server: McpServer,
callKicadScript: CommandFunction,
): void {
logger.info("Registering component resources");
// ------------------------------------------------------
// Component List Resource
// ------------------------------------------------------
server.resource(
"component_list",
"kicad://components",
async (uri) => {
logger.debug('Retrieving component list');
server.resource("component_list", "kicad://components", async (uri) => {
logger.debug("Retrieving component list");
const result = await callKicadScript("get_component_list", {});
if (!result.success) {
logger.error(`Failed to retrieve component list: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve component list",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved ${result.components?.length || 0} components`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Component Details Resource
@@ -61,38 +64,42 @@ export function registerComponentResources(server: McpServer, callKicadScript: C
server.resource(
"component_details",
new ResourceTemplate("kicad://component/{reference}/details", {
list: undefined
list: undefined,
}),
async (uri, params) => {
const { reference } = params;
logger.debug(`Retrieving details for component: ${reference}`);
const result = await callKicadScript("get_component_properties", {
reference
reference,
});
if (!result.success) {
logger.error(`Failed to retrieve component details: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve details for component ${reference}`,
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved details for component: ${reference}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
},
);
// ------------------------------------------------------
@@ -101,109 +108,113 @@ export function registerComponentResources(server: McpServer, callKicadScript: C
server.resource(
"component_connections",
new ResourceTemplate("kicad://component/{reference}/connections", {
list: undefined
list: undefined,
}),
async (uri, params) => {
const { reference } = params;
logger.debug(`Retrieving connections for component: ${reference}`);
const result = await callKicadScript("get_component_connections", {
reference
reference,
});
if (!result.success) {
logger.error(`Failed to retrieve component connections: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve connections for component ${reference}`,
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved connections for component: ${reference}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
},
);
// ------------------------------------------------------
// Component Placement Resource
// ------------------------------------------------------
server.resource(
"component_placement",
"kicad://components/placement",
async (uri) => {
logger.debug('Retrieving component placement information');
server.resource("component_placement", "kicad://components/placement", async (uri) => {
logger.debug("Retrieving component placement information");
const result = await callKicadScript("get_component_placement", {});
if (!result.success) {
logger.error(`Failed to retrieve component placement: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve component placement information",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug('Successfully retrieved component placement information');
logger.debug("Successfully retrieved component placement information");
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Component Groups Resource
// ------------------------------------------------------
server.resource(
"component_groups",
"kicad://components/groups",
async (uri) => {
logger.debug('Retrieving component groups');
server.resource("component_groups", "kicad://components/groups", async (uri) => {
logger.debug("Retrieving component groups");
const result = await callKicadScript("get_component_groups", {});
if (!result.success) {
logger.error(`Failed to retrieve component groups: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve component groups",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved ${result.groups?.length || 0} component groups`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Component Visualization Resource
@@ -211,39 +222,43 @@ export function registerComponentResources(server: McpServer, callKicadScript: C
server.resource(
"component_visualization",
new ResourceTemplate("kicad://component/{reference}/visualization", {
list: undefined
list: undefined,
}),
async (uri, params) => {
const { reference } = params;
logger.debug(`Generating visualization for component: ${reference}`);
const result = await callKicadScript("get_component_visualization", {
reference
reference,
});
if (!result.success) {
logger.error(`Failed to generate component visualization: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: `Failed to generate visualization for component ${reference}`,
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully generated visualization for component: ${reference}`);
return {
contents: [{
contents: [
{
uri: uri.href,
blob: result.imageData, // Base64 encoded image data
mimeType: "image/png"
}]
mimeType: "image/png",
},
],
};
}
},
);
logger.info('Component resources registered');
logger.info("Component resources registered");
}

View File

@@ -4,7 +4,7 @@
* Exports all resource registration functions
*/
export { registerProjectResources } from './project.js';
export { registerBoardResources } from './board.js';
export { registerComponentResources } from './component.js';
export { registerLibraryResources } from './library.js';
export { registerProjectResources } from "./project.js";
export { registerBoardResources } from "./board.js";
export { registerComponentResources } from "./component.js";
export { registerLibraryResources } from "./library.js";

View File

@@ -5,9 +5,9 @@
* to the LLM, enabling better context-aware assistance.
*/
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
@@ -18,8 +18,11 @@ type CommandFunction = (command: string, params: Record<string, unknown>) => Pro
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerLibraryResources(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering library resources');
export function registerLibraryResources(
server: McpServer,
callKicadScript: CommandFunction,
): void {
logger.info("Registering library resources");
// ------------------------------------------------------
// Component Library Resource
@@ -28,83 +31,89 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com
"component_library",
new ResourceTemplate("kicad://components/{filter?}/{library?}", {
list: async () => ({
resources: [
{ uri: "kicad://components", name: "All Components" }
]
})
resources: [{ uri: "kicad://components", name: "All Components" }],
}),
}),
async (uri, params) => {
const filter = params.filter || '';
const library = params.library || '';
const filter = params.filter || "";
const library = params.library || "";
const limit = Number(params.limit) || undefined;
logger.debug(`Retrieving component library${filter ? ` with filter: ${filter}` : ''}${library ? ` from library: ${library}` : ''}`);
logger.debug(
`Retrieving component library${filter ? ` with filter: ${filter}` : ""}${library ? ` from library: ${library}` : ""}`,
);
const result = await callKicadScript("get_component_library", {
filter,
library,
limit
limit,
});
if (!result.success) {
logger.error(`Failed to retrieve component library: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve component library",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved ${result.components?.length || 0} components from library`);
logger.debug(
`Successfully retrieved ${result.components?.length || 0} components from library`,
);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
},
);
// ------------------------------------------------------
// Library List Resource
// ------------------------------------------------------
server.resource(
"library_list",
"kicad://libraries",
async (uri) => {
logger.debug('Retrieving library list');
server.resource("library_list", "kicad://libraries", async (uri) => {
logger.debug("Retrieving library list");
const result = await callKicadScript("get_library_list", {});
if (!result.success) {
logger.error(`Failed to retrieve library list: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve library list",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved ${result.libraries?.length || 0} libraries`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Library Component Details Resource
@@ -112,40 +121,46 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com
server.resource(
"library_component_details",
new ResourceTemplate("kicad://library/component/{componentId}/{library?}", {
list: undefined
list: undefined,
}),
async (uri, params) => {
const { componentId, library } = params;
logger.debug(`Retrieving details for component: ${componentId}${library ? ` from library: ${library}` : ''}`);
logger.debug(
`Retrieving details for component: ${componentId}${library ? ` from library: ${library}` : ""}`,
);
const result = await callKicadScript("get_component_details", {
componentId,
library
library,
});
if (!result.success) {
logger.error(`Failed to retrieve component details: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve details for component ${componentId}`,
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved details for component: ${componentId}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
},
);
// ------------------------------------------------------
@@ -154,40 +169,46 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com
server.resource(
"component_footprint",
new ResourceTemplate("kicad://footprint/{componentId}/{footprint?}", {
list: undefined
list: undefined,
}),
async (uri, params) => {
const { componentId, footprint } = params;
logger.debug(`Retrieving footprint for component: ${componentId}${footprint ? ` (${footprint})` : ''}`);
logger.debug(
`Retrieving footprint for component: ${componentId}${footprint ? ` (${footprint})` : ""}`,
);
const result = await callKicadScript("get_component_footprint", {
componentId,
footprint
footprint,
});
if (!result.success) {
logger.error(`Failed to retrieve component footprint: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve footprint for component ${componentId}`,
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved footprint for component: ${componentId}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
},
);
// ------------------------------------------------------
@@ -196,27 +217,29 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com
server.resource(
"component_symbol",
new ResourceTemplate("kicad://symbol/{componentId}", {
list: undefined
list: undefined,
}),
async (uri, params) => {
const { componentId } = params;
logger.debug(`Retrieving symbol for component: ${componentId}`);
const result = await callKicadScript("get_component_symbol", {
componentId
componentId,
});
if (!result.success) {
logger.error(`Failed to retrieve component symbol: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve symbol for component ${componentId}`,
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
@@ -225,23 +248,27 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com
// If the result includes SVG data, return it as SVG
if (result.svgData) {
return {
contents: [{
contents: [
{
uri: uri.href,
text: result.svgData,
mimeType: "image/svg+xml"
}]
mimeType: "image/svg+xml",
},
],
};
}
// Otherwise return the JSON result
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
},
);
// ------------------------------------------------------
@@ -250,41 +277,47 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com
server.resource(
"component_3d_model",
new ResourceTemplate("kicad://3d-model/{componentId}/{footprint?}", {
list: undefined
list: undefined,
}),
async (uri, params) => {
const { componentId, footprint } = params;
logger.debug(`Retrieving 3D model for component: ${componentId}${footprint ? ` (${footprint})` : ''}`);
logger.debug(
`Retrieving 3D model for component: ${componentId}${footprint ? ` (${footprint})` : ""}`,
);
const result = await callKicadScript("get_component_3d_model", {
componentId,
footprint
footprint,
});
if (!result.success) {
logger.error(`Failed to retrieve component 3D model: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve 3D model for component ${componentId}`,
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved 3D model for component: ${componentId}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
},
);
logger.info('Library resources registered');
logger.info("Library resources registered");
}

View File

@@ -5,8 +5,8 @@
* to the LLM, enabling better context-aware assistance.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { logger } from '../logger.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { logger } from "../logger.js";
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
@@ -17,171 +17,173 @@ type CommandFunction = (command: string, params: Record<string, unknown>) => Pro
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerProjectResources(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering project resources');
export function registerProjectResources(
server: McpServer,
callKicadScript: CommandFunction,
): void {
logger.info("Registering project resources");
// ------------------------------------------------------
// Project Information Resource
// ------------------------------------------------------
server.resource(
"project_info",
"kicad://project/info",
async (uri) => {
logger.debug('Retrieving project information');
server.resource("project_info", "kicad://project/info", async (uri) => {
logger.debug("Retrieving project information");
const result = await callKicadScript("get_project_info", {});
if (!result.success) {
logger.error(`Failed to retrieve project information: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve project information",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug('Successfully retrieved project information');
logger.debug("Successfully retrieved project information");
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Project Properties Resource
// ------------------------------------------------------
server.resource(
"project_properties",
"kicad://project/properties",
async (uri) => {
logger.debug('Retrieving project properties');
server.resource("project_properties", "kicad://project/properties", async (uri) => {
logger.debug("Retrieving project properties");
const result = await callKicadScript("get_project_properties", {});
if (!result.success) {
logger.error(`Failed to retrieve project properties: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve project properties",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug('Successfully retrieved project properties');
logger.debug("Successfully retrieved project properties");
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Project Files Resource
// ------------------------------------------------------
server.resource(
"project_files",
"kicad://project/files",
async (uri) => {
logger.debug('Retrieving project files');
server.resource("project_files", "kicad://project/files", async (uri) => {
logger.debug("Retrieving project files");
const result = await callKicadScript("get_project_files", {});
if (!result.success) {
logger.error(`Failed to retrieve project files: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve project files",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved ${result.files?.length || 0} project files`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Project Status Resource
// ------------------------------------------------------
server.resource(
"project_status",
"kicad://project/status",
async (uri) => {
logger.debug('Retrieving project status');
server.resource("project_status", "kicad://project/status", async (uri) => {
logger.debug("Retrieving project status");
const result = await callKicadScript("get_project_status", {});
if (!result.success) {
logger.error(`Failed to retrieve project status: ${result.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve project status",
details: result.errorDetails
details: result.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
logger.debug('Successfully retrieved project status');
logger.debug("Successfully retrieved project status");
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
// ------------------------------------------------------
// Project Summary Resource
// ------------------------------------------------------
server.resource(
"project_summary",
"kicad://project/summary",
async (uri) => {
logger.debug('Generating project summary');
server.resource("project_summary", "kicad://project/summary", async (uri) => {
logger.debug("Generating project summary");
// Get project info
const infoResult = await callKicadScript("get_project_info", {});
if (!infoResult.success) {
logger.error(`Failed to retrieve project information: ${infoResult.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate project summary",
details: infoResult.errorDetails
details: infoResult.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
@@ -190,14 +192,16 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com
if (!boardResult.success) {
logger.error(`Failed to retrieve board information: ${boardResult.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate project summary",
details: boardResult.errorDetails
details: boardResult.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
@@ -206,14 +210,16 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com
if (!componentsResult.success) {
logger.error(`Failed to retrieve component list: ${componentsResult.errorDetails}`);
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate project summary",
details: componentsResult.errorDetails
details: componentsResult.errorDetails,
}),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
@@ -223,26 +229,27 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com
board: {
size: boardResult.size,
layers: boardResult.layers?.length || 0,
title: boardResult.title
title: boardResult.title,
},
components: {
count: componentsResult.components?.length || 0,
types: countComponentTypes(componentsResult.components || [])
}
types: countComponentTypes(componentsResult.components || []),
},
};
logger.debug('Successfully generated project summary');
logger.debug("Successfully generated project summary");
return {
contents: [{
contents: [
{
uri: uri.href,
text: JSON.stringify(summary),
mimeType: "application/json"
}]
mimeType: "application/json",
},
],
};
}
);
});
logger.info('Project resources registered');
logger.info("Project resources registered");
}
/**
@@ -252,7 +259,7 @@ function countComponentTypes(components: any[]): Record<string, number> {
const typeCounts: Record<string, number> = {};
for (const component of components) {
const type = component.value?.split(' ')[0] || 'Unknown';
const type = component.value?.split(" ")[0] || "Unknown";
typeCounts[type] = (typeCounts[type] || 0) + 1;
}

View File

@@ -54,18 +54,8 @@ function findPythonExecutable(scriptPath: string): string {
// Check for virtual environment
const venvPaths = [
join(
projectRoot,
"venv",
isWindows ? "Scripts" : "bin",
isWindows ? "python.exe" : "python",
),
join(
projectRoot,
".venv",
isWindows ? "Scripts" : "bin",
isWindows ? "python.exe" : "python",
),
join(projectRoot, "venv", isWindows ? "Scripts" : "bin", isWindows ? "python.exe" : "python"),
join(projectRoot, ".venv", isWindows ? "Scripts" : "bin", isWindows ? "python.exe" : "python"),
];
for (const venvPath of venvPaths) {
@@ -77,9 +67,7 @@ function findPythonExecutable(scriptPath: string): string {
// Allow override via KICAD_PYTHON environment variable (any platform)
if (process.env.KICAD_PYTHON) {
logger.info(
`Using KICAD_PYTHON environment variable: ${process.env.KICAD_PYTHON}`,
);
logger.info(`Using KICAD_PYTHON environment variable: ${process.env.KICAD_PYTHON}`);
return process.env.KICAD_PYTHON;
}
@@ -123,9 +111,7 @@ function findPythonExecutable(scriptPath: string): string {
for (const path of homebrewPaths) {
if (existsSync(path)) {
logger.info(
`Found Homebrew Python at: ${path} (ensure pcbnew is importable)`,
);
logger.info(`Found Homebrew Python at: ${path} (ensure pcbnew is importable)`);
return path;
}
}
@@ -196,19 +182,14 @@ export class KiCADMcpServer {
* @param kicadScriptPath Path to the Python KiCAD interface script
* @param logLevel Log level for the server
*/
constructor(
kicadScriptPath: string,
logLevel: "error" | "warn" | "info" | "debug" = "info",
) {
constructor(kicadScriptPath: string, logLevel: "error" | "warn" | "info" | "debug" = "info") {
// Set up the logger
logger.setLogLevel(logLevel);
// Check if KiCAD script exists
this.kicadScriptPath = kicadScriptPath;
if (!existsSync(this.kicadScriptPath)) {
throw new Error(
`KiCAD interface script not found: ${this.kicadScriptPath}`,
);
throw new Error(`KiCAD interface script not found: ${this.kicadScriptPath}`);
}
// Initialize the MCP server
@@ -267,9 +248,7 @@ export class KiCADMcpServer {
registerFootprintPrompts(this.server);
logger.info("All KiCAD tools, resources, and prompts registered");
logger.info(
"Router pattern enabled: 4 router tools + direct tools for discovery",
);
logger.info("Router pattern enabled: 4 router tools + direct tools for discovery");
}
/**
@@ -277,15 +256,12 @@ export class KiCADMcpServer {
*/
private async validatePrerequisites(pythonExe: string): Promise<boolean> {
const isWindows = process.platform === "win32";
const isLinux =
process.platform !== "win32" && process.platform !== "darwin";
const isLinux = process.platform !== "win32" && process.platform !== "darwin";
const errors: string[] = [];
// Check if Python executable exists (for absolute paths) or is executable (for commands)
const isAbsolutePath =
pythonExe.startsWith("/") ||
pythonExe.startsWith("C:") ||
pythonExe.startsWith("\\");
pythonExe.startsWith("/") || pythonExe.startsWith("C:") || pythonExe.startsWith("\\");
if (isAbsolutePath) {
// Absolute path: use existsSync
@@ -293,16 +269,10 @@ export class KiCADMcpServer {
errors.push(`Python executable not found: ${pythonExe}`);
if (isWindows) {
errors.push(
"Windows: Install KiCAD 9.0+ from https://www.kicad.org/download/windows/",
);
errors.push(
"Or run: .\\setup-windows.ps1 for automatic configuration",
);
errors.push("Windows: Install KiCAD 9.0+ from https://www.kicad.org/download/windows/");
errors.push("Or run: .\\setup-windows.ps1 for automatic configuration");
} else if (isLinux) {
errors.push(
"Linux: Install KiCAD 9.0+ or set KICAD_PYTHON environment variable",
);
errors.push("Linux: Install KiCAD 9.0+ or set KICAD_PYTHON environment variable");
errors.push("Set KICAD_PYTHON to specify a custom Python path");
}
}
@@ -334,20 +304,14 @@ export class KiCADMcpServer {
} catch (error: any) {
errors.push(`Python executable not found in PATH: ${pythonExe}`);
errors.push(`Error: ${error.message}`);
errors.push(
"Set KICAD_PYTHON environment variable to specify full path",
);
errors.push("Set KICAD_PYTHON environment variable to specify full path");
if (isLinux) {
errors.push("");
errors.push("Linux troubleshooting:");
errors.push("1. Check if python3 is installed: which python3");
errors.push(
"2. Install KiCAD: sudo apt install kicad (Ubuntu/Debian)",
);
errors.push(
"3. Set KICAD_PYTHON=/usr/bin/python3 in your MCP config",
);
errors.push("2. Install KiCAD: sudo apt install kicad (Ubuntu/Debian)");
errors.push("3. Set KICAD_PYTHON=/usr/bin/python3 in your MCP config");
}
}
}
@@ -358,11 +322,7 @@ export class KiCADMcpServer {
}
// Check if dist/index.js exists (if running from compiled code)
const distPath = join(
dirname(dirname(this.kicadScriptPath)),
"dist",
"index.js",
);
const distPath = join(dirname(dirname(this.kicadScriptPath)), "dist", "index.js");
if (!existsSync(distPath)) {
errors.push("Project not built. Run: npm run build");
}
@@ -458,9 +418,7 @@ export class KiCADMcpServer {
logger.info("Starting KiCAD MCP server...");
// Start the Python process for KiCAD scripting
logger.info(
`Starting Python process with script: ${this.kicadScriptPath}`,
);
logger.info(`Starting Python process with script: ${this.kicadScriptPath}`);
const pythonExe = findPythonExecutable(this.kicadScriptPath);
logger.info(`Using Python executable: ${pythonExe}`);
@@ -468,25 +426,20 @@ export class KiCADMcpServer {
// Validate prerequisites
const isValid = await this.validatePrerequisites(pythonExe);
if (!isValid) {
throw new Error(
"Prerequisites validation failed. See logs above for details.",
);
throw new Error("Prerequisites validation failed. See logs above for details.");
}
this.pythonProcess = spawn(pythonExe, [this.kicadScriptPath], {
stdio: ["pipe", "pipe", "pipe"],
env: {
...process.env,
PYTHONPATH:
process.env.PYTHONPATH ||
"C:/Program Files/KiCad/9.0/lib/python3/dist-packages",
process.env.PYTHONPATH || "C:/Program Files/KiCad/9.0/lib/python3/dist-packages",
},
});
// Listen for process exit
this.pythonProcess.on("exit", (code, signal) => {
logger.warn(
`Python process exited with code ${code} and signal ${signal}`,
);
logger.warn(`Python process exited with code ${code} and signal ${signal}`);
this.pythonProcess = null;
});
@@ -563,17 +516,10 @@ export class KiCADMcpServer {
// Determine timeout based on command type
// DRC and export operations need longer timeouts for large boards
let commandTimeout = 30000; // Default 30 seconds
const longRunningCommands = [
"run_drc",
"export_gerber",
"export_pdf",
"export_3d",
];
const longRunningCommands = ["run_drc", "export_gerber", "export_pdf", "export_3d"];
if (longRunningCommands.includes(command)) {
commandTimeout = 600000; // 10 minutes for long operations
logger.info(
`Using extended timeout (${commandTimeout / 1000}s) for command: ${command}`,
);
logger.info(`Using extended timeout (${commandTimeout / 1000}s) for command: ${command}`);
}
// Add request to queue with timeout info
@@ -678,12 +624,8 @@ export class KiCADMcpServer {
// Set a timeout (use command-specific timeout or default)
const timeoutDuration = request.timeout || 30000;
const timeoutHandle = setTimeout(() => {
logger.error(
`Command timeout after ${timeoutDuration / 1000}s: ${request.command}`,
);
logger.error(
`Buffer contents: ${this.responseBuffer.substring(0, 200)}...`,
);
logger.error(`Command timeout after ${timeoutDuration / 1000}s: ${request.command}`);
logger.error(`Buffer contents: ${this.responseBuffer.substring(0, 200)}...`);
// Clear state
this.responseBuffer = "";
@@ -691,11 +633,7 @@ export class KiCADMcpServer {
this.processingRequest = false;
// Reject the promise
reject(
new Error(
`Command timeout after ${timeoutDuration / 1000}s: ${request.command}`,
),
);
reject(new Error(`Command timeout after ${timeoutDuration / 1000}s: ${request.command}`));
// Process next request
setTimeout(() => this.processNextRequest(), 0);

View File

@@ -4,9 +4,9 @@
* These tools handle board setup, layer management, and board properties
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
@@ -18,7 +18,7 @@ type CommandFunction = (command: string, params: Record<string, unknown>) => Pro
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerBoardTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering board management tools');
logger.info("Registering board management tools");
// ------------------------------------------------------
// Set Board Size Tool
@@ -28,23 +28,25 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
{
width: z.number().describe("Board width"),
height: z.number().describe("Board height"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
},
async ({ width, height, unit }) => {
logger.debug(`Setting board size to ${width}x${height} ${unit}`);
const result = await callKicadScript("set_board_size", {
width,
height,
unit
unit,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -54,13 +56,9 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
"add_layer",
{
name: z.string().describe("Layer name"),
type: z.enum([
"copper", "technical", "user", "signal"
]).describe("Layer type"),
position: z.enum([
"top", "bottom", "inner"
]).describe("Layer position"),
number: z.number().optional().describe("Layer number (for inner layers)")
type: z.enum(["copper", "technical", "user", "signal"]).describe("Layer type"),
position: z.enum(["top", "bottom", "inner"]).describe("Layer position"),
number: z.number().optional().describe("Layer number (for inner layers)"),
},
async ({ name, type, position, number }) => {
logger.debug(`Adding ${type} layer: ${name}`);
@@ -68,16 +66,18 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
name,
type,
position,
number
number,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -86,58 +86,56 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
server.tool(
"set_active_layer",
{
layer: z.string().describe("Layer name to set as active")
layer: z.string().describe("Layer name to set as active"),
},
async ({ layer }) => {
logger.debug(`Setting active layer to: ${layer}`);
const result = await callKicadScript("set_active_layer", { layer });
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
// Get Board Info Tool
// ------------------------------------------------------
server.tool(
"get_board_info",
{},
async () => {
logger.debug('Getting board information');
server.tool("get_board_info", {}, async () => {
logger.debug("Getting board information");
const result = await callKicadScript("get_board_info", {});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
);
});
// ------------------------------------------------------
// Get Layer List Tool
// ------------------------------------------------------
server.tool(
"get_layer_list",
{},
async () => {
logger.debug('Getting layer list');
server.tool("get_layer_list", {}, async () => {
logger.debug("Getting layer list");
const result = await callKicadScript("get_layer_list", {});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
);
});
// ------------------------------------------------------
// Add Board Outline Tool
@@ -145,8 +143,11 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
server.tool(
"add_board_outline",
{
shape: z.enum(["rectangle", "circle", "polygon", "rounded_rectangle"]).describe("Shape of the outline"),
params: z.object({
shape: z
.enum(["rectangle", "circle", "polygon", "rounded_rectangle"])
.describe("Shape of the outline"),
params: z
.object({
// For rectangle / rounded_rectangle
width: z.number().optional().describe("Width of rectangle"),
height: z.number().optional().describe("Height of rectangle"),
@@ -154,17 +155,21 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// For circle
radius: z.number().optional().describe("Radius of circle"),
// For polygon
points: z.array(
points: z
.array(
z.object({
x: z.number().describe("X coordinate"),
y: z.number().describe("Y coordinate")
})
).optional().describe("Points of polygon"),
y: z.number().describe("Y coordinate"),
}),
)
.optional()
.describe("Points of polygon"),
// Position: top-left corner for rectangles/rounded_rectangle, center for circle
x: z.number().describe("X coordinate of top-left corner for rectangles (default: 0)"),
y: z.number().describe("Y coordinate of top-left corner for rectangles (default: 0)"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
}).describe("Parameters for the outline shape")
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
})
.describe("Parameters for the outline shape"),
},
async ({ shape, params }) => {
logger.debug(`Adding ${shape} board outline`);
@@ -172,16 +177,18 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// and computes the center internally (center = x + width/2, y + height/2).
const result = await callKicadScript("add_board_outline", {
shape,
...params
...params,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -190,29 +197,33 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
server.tool(
"add_mounting_hole",
{
position: z.object({
position: z
.object({
x: z.number().describe("X coordinate"),
y: z.number().describe("Y coordinate"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
}).describe("Position of the mounting hole"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
})
.describe("Position of the mounting hole"),
diameter: z.number().describe("Diameter of the hole"),
padDiameter: z.number().optional().describe("Optional diameter of the pad around the hole")
padDiameter: z.number().optional().describe("Optional diameter of the pad around the hole"),
},
async ({ position, diameter, padDiameter }) => {
logger.debug(`Adding mounting hole at (${position.x},${position.y}) ${position.unit}`);
const result = await callKicadScript("add_mounting_hole", {
position,
diameter,
padDiameter
padDiameter,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -222,16 +233,18 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
"add_board_text",
{
text: z.string().describe("Text content"),
position: z.object({
position: z
.object({
x: z.number().describe("X coordinate"),
y: z.number().describe("Y coordinate"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
}).describe("Position of the text"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
})
.describe("Position of the text"),
layer: z.string().describe("Layer to place the text on"),
size: z.number().describe("Text size"),
thickness: z.number().optional().describe("Line thickness"),
rotation: z.number().optional().describe("Rotation angle in degrees"),
style: z.enum(["normal", "italic", "bold"]).optional().describe("Text style")
style: z.enum(["normal", "italic", "bold"]).optional().describe("Text style"),
},
async ({ text, position, layer, size, thickness, rotation, style }) => {
logger.debug(`Adding text "${text}" at (${position.x},${position.y}) ${position.unit}`);
@@ -242,16 +255,18 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
size,
thickness,
rotation,
style
style,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -262,16 +277,21 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
{
layer: z.string().describe("Layer for the zone"),
net: z.string().describe("Net name for the zone"),
points: z.array(
points: z
.array(
z.object({
x: z.number().describe("X coordinate"),
y: z.number().describe("Y coordinate")
})
).describe("Points defining the zone outline"),
y: z.number().describe("Y coordinate"),
}),
)
.describe("Points defining the zone outline"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
clearance: z.number().optional().describe("Clearance value"),
minWidth: z.number().optional().describe("Minimum width"),
padConnection: z.enum(["thermal", "solid", "none"]).optional().describe("Pad connection type")
padConnection: z
.enum(["thermal", "solid", "none"])
.optional()
.describe("Pad connection type"),
},
async ({ layer, net, points, unit, clearance, minWidth, padConnection }) => {
logger.debug(`Adding zone on layer ${layer} for net ${net}`);
@@ -282,16 +302,18 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
unit,
clearance,
minWidth,
padConnection
padConnection,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -300,19 +322,21 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
server.tool(
"get_board_extents",
{
unit: z.enum(["mm", "inch"]).optional().describe("Unit of measurement for the result")
unit: z.enum(["mm", "inch"]).optional().describe("Unit of measurement for the result"),
},
async ({ unit }) => {
logger.debug('Getting board extents');
logger.debug("Getting board extents");
const result = await callKicadScript("get_board_extents", { unit });
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -324,27 +348,29 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
layers: z.array(z.string()).optional().describe("Optional array of layer names to include"),
width: z.number().optional().describe("Optional width of the image in pixels"),
height: z.number().optional().describe("Optional height of the image in pixels"),
format: z.enum(["png", "jpg", "svg"]).optional().describe("Image format")
format: z.enum(["png", "jpg", "svg"]).optional().describe("Image format"),
},
async ({ layers, width, height, format }) => {
logger.debug('Getting 2D board view');
logger.debug("Getting 2D board view");
const result = await callKicadScript("get_board_2d_view", {
layers,
width,
height,
format
format,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
logger.info('Board management tools registered');
logger.info("Board management tools registered");
// Import SVG logo onto PCB layer (silkscreen)
server.tool(
@@ -355,16 +381,34 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
svgPath: z.string().describe("Path to the SVG logo file"),
x: z.number().describe("X position of the logo top-left corner in mm"),
y: z.number().describe("Y position of the logo top-left corner in mm"),
width: z.number().describe("Target width of the logo in mm (height is scaled to preserve aspect ratio)"),
layer: z.string().optional().describe("PCB layer name, e.g. F.SilkS or B.SilkS (default: F.SilkS)"),
strokeWidth: z.number().optional().describe("Outline stroke width in mm (0 = no outline, default 0)"),
width: z
.number()
.describe("Target width of the logo in mm (height is scaled to preserve aspect ratio)"),
layer: z
.string()
.optional()
.describe("PCB layer name, e.g. F.SilkS or B.SilkS (default: F.SilkS)"),
strokeWidth: z
.number()
.optional()
.describe("Outline stroke width in mm (0 = no outline, default 0)"),
filled: z.boolean().optional().describe("Fill polygons with solid colour (default true)"),
},
async (args: { pcbPath: string; svgPath: string; x: number; y: number; width: number; layer?: string; strokeWidth?: number; filled?: boolean }) => {
async (args: {
pcbPath: string;
svgPath: string;
x: number;
y: number;
width: number;
layer?: string;
strokeWidth?: number;
filled?: boolean;
}) => {
const result = await callKicadScript("import_svg_logo", args);
if (result.success) {
return {
content: [{
content: [
{
type: "text",
text: [
result.message,
@@ -372,11 +416,14 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
`Size: ${result.logo_width_mm?.toFixed(2)} × ${result.logo_height_mm?.toFixed(2)} mm`,
`Layer: ${result.layer}`,
].join("\n"),
}],
},
],
};
} else {
return {
content: [{ type: "text", text: `SVG import failed: ${result.message || "Unknown error"}` }],
content: [
{ type: "text", text: `SVG import failed: ${result.message || "Unknown error"}` },
],
};
}
},

View File

@@ -7,10 +7,7 @@ import { z } from "zod";
import { logger } from "../logger.js";
// Command function type for KiCAD script calls
type CommandFunction = (
command: string,
params: Record<string, unknown>,
) => Promise<any>;
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
/**
* Register component management tools with the MCP server
@@ -18,10 +15,7 @@ type CommandFunction = (
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerComponentTools(
server: McpServer,
callKicadScript: CommandFunction,
): void {
export function registerComponentTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info("Registering component management tools");
// ------------------------------------------------------
@@ -40,38 +34,19 @@ export function registerComponentTools(
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
})
.describe("Position coordinates and unit"),
reference: z
.string()
.optional()
.describe("Optional desired reference (e.g., 'R5')"),
value: z
.string()
.optional()
.describe("Optional component value (e.g., '10k')"),
footprint: z
.string()
.optional()
.describe("Optional specific footprint name"),
reference: z.string().optional().describe("Optional desired reference (e.g., 'R5')"),
value: z.string().optional().describe("Optional component value (e.g., '10k')"),
footprint: z.string().optional().describe("Optional specific footprint name"),
rotation: z.number().optional().describe("Optional rotation in degrees"),
layer: z
.string()
.optional()
.describe("Optional layer (e.g., 'F.Cu', 'B.SilkS')"),
layer: z.string().optional().describe("Optional layer (e.g., 'F.Cu', 'B.SilkS')"),
boardPath: z
.string()
.optional()
.describe("Path to the .kicad_pcb file required when using project-local footprint libraries"),
.describe(
"Path to the .kicad_pcb file required when using project-local footprint libraries",
),
},
async ({
componentId,
position,
reference,
value,
footprint,
rotation,
layer,
boardPath,
}) => {
async ({ componentId, position, reference, value, footprint, rotation, layer, boardPath }) => {
logger.debug(
`Placing component: ${componentId} at ${position.x},${position.y} ${position.unit}`,
);
@@ -103,9 +78,7 @@ export function registerComponentTools(
server.tool(
"move_component",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
position: z
.object({
x: z.number().describe("X coordinate"),
@@ -113,10 +86,7 @@ export function registerComponentTools(
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
})
.describe("New position coordinates and unit"),
rotation: z
.number()
.optional()
.describe("Optional new rotation in degrees"),
rotation: z.number().optional().describe("Optional new rotation in degrees"),
layer: z
.string()
.optional()
@@ -150,12 +120,8 @@ export function registerComponentTools(
server.tool(
"rotate_component",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
angle: z
.number()
.describe("Rotation angle in degrees (absolute, not relative)"),
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
angle: z.number().describe("Rotation angle in degrees (absolute, not relative)"),
},
async ({ reference, angle }) => {
logger.debug(`Rotating component: ${reference} to ${angle} degrees`);
@@ -183,9 +149,7 @@ export function registerComponentTools(
{
reference: z
.string()
.describe(
"Reference designator of the component to delete (e.g., 'R5')",
),
.describe("Reference designator of the component to delete (e.g., 'R5')"),
},
async ({ reference }) => {
logger.debug(`Deleting component: ${reference}`);
@@ -208,13 +172,8 @@ export function registerComponentTools(
server.tool(
"edit_component",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
newReference: z
.string()
.optional()
.describe("Optional new reference designator"),
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
newReference: z.string().optional().describe("Optional new reference designator"),
value: z.string().optional().describe("Optional new component value"),
footprint: z.string().optional().describe("Optional new footprint"),
},
@@ -244,10 +203,7 @@ export function registerComponentTools(
server.tool(
"find_component",
{
reference: z
.string()
.optional()
.describe("Reference designator to search for"),
reference: z.string().optional().describe("Reference designator to search for"),
value: z.string().optional().describe("Component value to search for"),
},
async ({ reference, value }) => {
@@ -276,9 +232,7 @@ export function registerComponentTools(
server.tool(
"get_component_properties",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
},
async ({ reference }) => {
logger.debug(`Getting properties for component: ${reference}`);
@@ -303,9 +257,7 @@ export function registerComponentTools(
server.tool(
"add_component_annotation",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
annotation: z.string().describe("Annotation or comment text to add"),
visible: z
.boolean()
@@ -337,15 +289,11 @@ export function registerComponentTools(
server.tool(
"group_components",
{
references: z
.array(z.string())
.describe("Reference designators of components to group"),
references: z.array(z.string()).describe("Reference designators of components to group"),
groupName: z.string().describe("Name for the component group"),
},
async ({ references, groupName }) => {
logger.debug(
`Grouping components: ${references.join(", ")} as ${groupName}`,
);
logger.debug(`Grouping components: ${references.join(", ")} as ${groupName}`);
const result = await callKicadScript("group_components", {
references,
groupName,
@@ -368,9 +316,7 @@ export function registerComponentTools(
server.tool(
"replace_component",
{
reference: z
.string()
.describe("Reference designator of the component to replace"),
reference: z.string().describe("Reference designator of the component to replace"),
newComponentId: z.string().describe("ID of the new component to use"),
newFootprint: z.string().optional().describe("Optional new footprint"),
newValue: z.string().optional().describe("Optional new component value"),
@@ -401,13 +347,8 @@ export function registerComponentTools(
server.tool(
"get_component_pads",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'U1')"),
unit: z
.enum(["mm", "inch"])
.optional()
.describe("Unit for coordinates (default: mm)"),
reference: z.string().describe("Reference designator of the component (e.g., 'U1')"),
unit: z.enum(["mm", "inch"]).optional().describe("Unit for coordinates (default: mm)"),
},
async ({ reference, unit }) => {
logger.debug(`Getting pads for component: ${reference}`);
@@ -433,10 +374,7 @@ export function registerComponentTools(
server.tool(
"get_component_list",
{
layer: z
.string()
.optional()
.describe("Filter by layer (e.g., 'F.Cu', 'B.Cu')"),
layer: z.string().optional().describe("Filter by layer (e.g., 'F.Cu', 'B.Cu')"),
boundingBox: z
.object({
x1: z.number(),
@@ -447,10 +385,7 @@ export function registerComponentTools(
})
.optional()
.describe("Filter by bounding box region"),
unit: z
.enum(["mm", "inch"])
.optional()
.describe("Unit for coordinates (default: mm)"),
unit: z.enum(["mm", "inch"]).optional().describe("Unit for coordinates (default: mm)"),
},
async ({ layer, boundingBox, unit }) => {
logger.debug("Getting component list");
@@ -477,14 +412,9 @@ export function registerComponentTools(
server.tool(
"get_pad_position",
{
reference: z
.string()
.describe("Component reference designator (e.g., 'U1')"),
reference: z.string().describe("Component reference designator (e.g., 'U1')"),
pad: z.string().describe("Pad number or name (e.g., '1', 'A1')"),
unit: z
.enum(["mm", "inch"])
.optional()
.describe("Unit for coordinates (default: mm)"),
unit: z.enum(["mm", "inch"]).optional().describe("Unit for coordinates (default: mm)"),
},
async ({ reference, pad, unit }) => {
logger.debug(`Getting pad position for ${reference} pad ${pad}`);
@@ -523,10 +453,7 @@ export function registerComponentTools(
columns: z.number().describe("Number of columns"),
rowSpacing: z.number().describe("Spacing between rows"),
columnSpacing: z.number().describe("Spacing between columns"),
startReference: z
.string()
.optional()
.describe("Starting reference (e.g., 'R1')"),
startReference: z.string().optional().describe("Starting reference (e.g., 'R1')"),
footprint: z.string().optional().describe("Footprint name"),
value: z.string().optional().describe("Component value"),
rotation: z.number().optional().describe("Rotation in degrees"),
@@ -543,9 +470,7 @@ export function registerComponentTools(
value,
rotation,
}) => {
logger.debug(
`Placing component array: ${rows}x${columns} of ${componentId}`,
);
logger.debug(`Placing component array: ${rows}x${columns} of ${componentId}`);
const result = await callKicadScript("place_component_array", {
componentId,
startPosition,
@@ -576,20 +501,10 @@ export function registerComponentTools(
server.tool(
"align_components",
{
references: z
.array(z.string())
.describe("Array of component references to align"),
alignmentType: z
.enum(["horizontal", "vertical", "grid"])
.describe("Type of alignment"),
spacing: z
.number()
.optional()
.describe("Spacing between components in mm"),
referenceComponent: z
.string()
.optional()
.describe("Reference component for alignment"),
references: z.array(z.string()).describe("Array of component references to align"),
alignmentType: z.enum(["horizontal", "vertical", "grid"]).describe("Type of alignment"),
spacing: z.number().optional().describe("Spacing between components in mm"),
referenceComponent: z.string().optional().describe("Reference component for alignment"),
},
async ({ references, alignmentType, spacing, referenceComponent }) => {
logger.debug(`Aligning components: ${references.join(", ")}`);
@@ -626,10 +541,7 @@ export function registerComponentTools(
})
.describe("Offset from original position"),
newReference: z.string().optional().describe("New reference designator"),
count: z
.number()
.optional()
.describe("Number of duplicates (default: 1)"),
count: z.number().optional().describe("Number of duplicates (default: 1)"),
},
async ({ reference, offset, newReference, count }) => {
logger.debug(`Duplicating component: ${reference}`);

View File

@@ -8,10 +8,7 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerDatasheetTools(
server: McpServer,
callKicadScript: Function,
) {
export function registerDatasheetTools(server: McpServer, callKicadScript: Function) {
// ── enrich_datasheets ──────────────────────────────────────────────────────
server.tool(
"enrich_datasheets",
@@ -30,16 +27,12 @@ No API key or internet lookup required the URL is constructed directly.
Use dry_run=true to preview changes without writing.`,
{
schematic_path: z
.string()
.describe("Path to the .kicad_sch file to enrich"),
schematic_path: z.string().describe("Path to the .kicad_sch file to enrich"),
dry_run: z
.boolean()
.optional()
.default(false)
.describe(
"If true, show what would be changed without writing to disk (default: false)",
),
.describe("If true, show what would be changed without writing to disk (default: false)"),
},
async (args: { schematic_path: string; dry_run?: boolean }) => {
const result = await callKicadScript("enrich_datasheets", args);
@@ -65,9 +58,7 @@ Use dry_run=true to preview changes without writing.`,
}
if (result.updated === 0 && !args.dry_run) {
lines.push(
"\nNo changes needed all LCSC components already have a Datasheet URL.",
);
lines.push("\nNo changes needed all LCSC components already have a Datasheet URL.");
}
return { content: [{ type: "text", text: lines.join("\n") }] };
@@ -96,9 +87,7 @@ Example: get_datasheet_url("C179739")
{
lcsc: z
.string()
.describe(
'LCSC part number, with or without "C" prefix (e.g. "C179739" or "179739")',
),
.describe('LCSC part number, with or without "C" prefix (e.g. "C179739" or "179739")'),
},
async (args: { lcsc: string }) => {
const result = await callKicadScript("get_datasheet_url", { lcsc: args.lcsc });

View File

@@ -4,9 +4,9 @@
* These tools handle design rule checking and configuration
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
@@ -18,7 +18,7 @@ type CommandFunction = (command: string, params: Record<string, unknown>) => Pro
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerDesignRuleTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering design rule tools');
logger.info("Registering design rule tools");
// ------------------------------------------------------
// Set Design Rules Tool
@@ -38,40 +38,46 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
minMicroViaDiameter: z.number().optional().describe("Minimum micro via diameter (mm)"),
minMicroViaDrill: z.number().optional().describe("Minimum micro via drill size (mm)"),
minHoleDiameter: z.number().optional().describe("Minimum hole diameter (mm)"),
requireCourtyard: z.boolean().optional().describe("Whether to require courtyards for all footprints"),
courtyardClearance: z.number().optional().describe("Minimum clearance between courtyards (mm)")
requireCourtyard: z
.boolean()
.optional()
.describe("Whether to require courtyards for all footprints"),
courtyardClearance: z
.number()
.optional()
.describe("Minimum clearance between courtyards (mm)"),
},
async (params) => {
logger.debug('Setting design rules');
logger.debug("Setting design rules");
const result = await callKicadScript("set_design_rules", params);
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
// Get Design Rules Tool
// ------------------------------------------------------
server.tool(
"get_design_rules",
{},
async () => {
logger.debug('Getting design rules');
server.tool("get_design_rules", {}, async () => {
logger.debug("Getting design rules");
const result = await callKicadScript("get_design_rules", {});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
);
});
// ------------------------------------------------------
// Run DRC Tool
@@ -79,19 +85,21 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
server.tool(
"run_drc",
{
reportPath: z.string().optional().describe("Optional path to save the DRC report")
reportPath: z.string().optional().describe("Optional path to save the DRC report"),
},
async ({ reportPath }) => {
logger.debug('Running DRC check');
logger.debug("Running DRC check");
const result = await callKicadScript("run_drc", { reportPath });
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -108,11 +116,29 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
viaDrill: z.number().describe("Via drill size for this net class (mm)"),
uvia_diameter: z.number().optional().describe("Micro via diameter for this net class (mm)"),
uvia_drill: z.number().optional().describe("Micro via drill size for this net class (mm)"),
diff_pair_width: z.number().optional().describe("Differential pair width for this net class (mm)"),
diff_pair_gap: z.number().optional().describe("Differential pair gap for this net class (mm)"),
nets: z.array(z.string()).optional().describe("Array of net names to assign to this class")
diff_pair_width: z
.number()
.optional()
.describe("Differential pair width for this net class (mm)"),
diff_pair_gap: z
.number()
.optional()
.describe("Differential pair gap for this net class (mm)"),
nets: z.array(z.string()).optional().describe("Array of net names to assign to this class"),
},
async ({ name, description, clearance, trackWidth, viaDiameter, viaDrill, uvia_diameter, uvia_drill, diff_pair_width, diff_pair_gap, nets }) => {
async ({
name,
description,
clearance,
trackWidth,
viaDiameter,
viaDrill,
uvia_diameter,
uvia_drill,
diff_pair_width,
diff_pair_gap,
nets,
}) => {
logger.debug(`Adding net class: ${name}`);
const result = await callKicadScript("add_net_class", {
name,
@@ -125,16 +151,18 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
uvia_drill,
diff_pair_width,
diff_pair_gap,
nets
nets,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -144,22 +172,24 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
"assign_net_to_class",
{
net: z.string().describe("Name of the net"),
netClass: z.string().describe("Name of the net class")
netClass: z.string().describe("Name of the net class"),
},
async ({ net, netClass }) => {
logger.debug(`Assigning net ${net} to class ${netClass}`);
const result = await callKicadScript("assign_net_to_class", {
net,
netClass
netClass,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -172,7 +202,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
minTrackWidth: z.number().optional().describe("Minimum track width for this layer (mm)"),
minClearance: z.number().optional().describe("Minimum clearance for this layer (mm)"),
minViaDiameter: z.number().optional().describe("Minimum via diameter for this layer (mm)"),
minViaDrill: z.number().optional().describe("Minimum via drill size for this layer (mm)")
minViaDrill: z.number().optional().describe("Minimum via drill size for this layer (mm)"),
},
async ({ layer, minTrackWidth, minClearance, minViaDiameter, minViaDrill }) => {
logger.debug(`Setting constraints for layer: ${layer}`);
@@ -181,16 +211,18 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
minTrackWidth,
minClearance,
minViaDiameter,
minViaDrill
minViaDrill,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -199,41 +231,57 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
server.tool(
"check_clearance",
{
item1: z.object({
type: z.enum(["track", "via", "pad", "zone", "component"]).describe("Type of the first item"),
item1: z
.object({
type: z
.enum(["track", "via", "pad", "zone", "component"])
.describe("Type of the first item"),
id: z.string().optional().describe("ID of the first item (if applicable)"),
reference: z.string().optional().describe("Reference designator (for component)"),
position: z.object({
position: z
.object({
x: z.number().optional(),
y: z.number().optional(),
unit: z.enum(["mm", "inch"]).optional()
}).optional().describe("Position to check (if ID not provided)")
}).describe("First item to check"),
item2: z.object({
type: z.enum(["track", "via", "pad", "zone", "component"]).describe("Type of the second item"),
unit: z.enum(["mm", "inch"]).optional(),
})
.optional()
.describe("Position to check (if ID not provided)"),
})
.describe("First item to check"),
item2: z
.object({
type: z
.enum(["track", "via", "pad", "zone", "component"])
.describe("Type of the second item"),
id: z.string().optional().describe("ID of the second item (if applicable)"),
reference: z.string().optional().describe("Reference designator (for component)"),
position: z.object({
position: z
.object({
x: z.number().optional(),
y: z.number().optional(),
unit: z.enum(["mm", "inch"]).optional()
}).optional().describe("Position to check (if ID not provided)")
}).describe("Second item to check")
unit: z.enum(["mm", "inch"]).optional(),
})
.optional()
.describe("Position to check (if ID not provided)"),
})
.describe("Second item to check"),
},
async ({ item1, item2 }) => {
logger.debug(`Checking clearance between ${item1.type} and ${item2.type}`);
const result = await callKicadScript("check_clearance", {
item1,
item2
item2,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -242,20 +290,25 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
server.tool(
"get_drc_violations",
{
severity: z.enum(["error", "warning", "all"]).optional().describe("Filter violations by severity")
severity: z
.enum(["error", "warning", "all"])
.optional()
.describe("Filter violations by severity"),
},
async ({ severity }) => {
logger.debug('Getting DRC violations');
logger.debug("Getting DRC violations");
const result = await callKicadScript("get_drc_violations", { severity });
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
logger.info('Design rule tools registered');
logger.info("Design rule tools registered");
}

View File

@@ -4,9 +4,9 @@
* These tools handle exporting PCB data to various formats
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
@@ -18,7 +18,7 @@ type CommandFunction = (command: string, params: Record<string, unknown>) => Pro
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerExportTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering export tools');
logger.info("Registering export tools");
// ------------------------------------------------------
// Export Gerber Tool
@@ -27,13 +27,26 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
"export_gerber",
{
outputDir: z.string().describe("Directory to save Gerber files"),
layers: z.array(z.string()).optional().describe("Optional array of layer names to export (default: all)"),
useProtelExtensions: z.boolean().optional().describe("Whether to use Protel filename extensions"),
layers: z
.array(z.string())
.optional()
.describe("Optional array of layer names to export (default: all)"),
useProtelExtensions: z
.boolean()
.optional()
.describe("Whether to use Protel filename extensions"),
generateDrillFiles: z.boolean().optional().describe("Whether to generate drill files"),
generateMapFile: z.boolean().optional().describe("Whether to generate a map file"),
useAuxOrigin: z.boolean().optional().describe("Whether to use auxiliary axis as origin")
useAuxOrigin: z.boolean().optional().describe("Whether to use auxiliary axis as origin"),
},
async ({ outputDir, layers, useProtelExtensions, generateDrillFiles, generateMapFile, useAuxOrigin }) => {
async ({
outputDir,
layers,
useProtelExtensions,
generateDrillFiles,
generateMapFile,
useAuxOrigin,
}) => {
logger.debug(`Exporting Gerber files to: ${outputDir}`);
const result = await callKicadScript("export_gerber", {
outputDir,
@@ -41,16 +54,18 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
useProtelExtensions,
generateDrillFiles,
generateMapFile,
useAuxOrigin
useAuxOrigin,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -60,10 +75,16 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
"export_pdf",
{
outputPath: z.string().describe("Path to save the PDF file"),
layers: z.array(z.string()).optional().describe("Optional array of layer names to include (default: all)"),
layers: z
.array(z.string())
.optional()
.describe("Optional array of layer names to include (default: all)"),
blackAndWhite: z.boolean().optional().describe("Whether to export in black and white"),
frameReference: z.boolean().optional().describe("Whether to include frame reference"),
pageSize: z.enum(["A4", "A3", "A2", "A1", "A0", "Letter", "Legal", "Tabloid"]).optional().describe("Page size")
pageSize: z
.enum(["A4", "A3", "A2", "A1", "A0", "Letter", "Legal", "Tabloid"])
.optional()
.describe("Page size"),
},
async ({ outputPath, layers, blackAndWhite, frameReference, pageSize }) => {
logger.debug(`Exporting PDF to: ${outputPath}`);
@@ -72,16 +93,18 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
layers,
blackAndWhite,
frameReference,
pageSize
pageSize,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -91,9 +114,12 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
"export_svg",
{
outputPath: z.string().describe("Path to save the SVG file"),
layers: z.array(z.string()).optional().describe("Optional array of layer names to include (default: all)"),
layers: z
.array(z.string())
.optional()
.describe("Optional array of layer names to include (default: all)"),
blackAndWhite: z.boolean().optional().describe("Whether to export in black and white"),
includeComponents: z.boolean().optional().describe("Whether to include component outlines")
includeComponents: z.boolean().optional().describe("Whether to include component outlines"),
},
async ({ outputPath, layers, blackAndWhite, includeComponents }) => {
logger.debug(`Exporting SVG to: ${outputPath}`);
@@ -101,16 +127,18 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
outputPath,
layers,
blackAndWhite,
includeComponents
includeComponents,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -124,9 +152,16 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
includeComponents: z.boolean().optional().describe("Whether to include 3D component models"),
includeCopper: z.boolean().optional().describe("Whether to include copper layers"),
includeSolderMask: z.boolean().optional().describe("Whether to include solder mask"),
includeSilkscreen: z.boolean().optional().describe("Whether to include silkscreen")
includeSilkscreen: z.boolean().optional().describe("Whether to include silkscreen"),
},
async ({ outputPath, format, includeComponents, includeCopper, includeSolderMask, includeSilkscreen }) => {
async ({
outputPath,
format,
includeComponents,
includeCopper,
includeSolderMask,
includeSilkscreen,
}) => {
logger.debug(`Exporting 3D model to: ${outputPath}`);
const result = await callKicadScript("export_3d", {
outputPath,
@@ -134,16 +169,18 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
includeComponents,
includeCopper,
includeSolderMask,
includeSilkscreen
includeSilkscreen,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -155,7 +192,10 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
outputPath: z.string().describe("Path to save the BOM file"),
format: z.enum(["CSV", "XML", "HTML", "JSON"]).describe("BOM file format"),
groupByValue: z.boolean().optional().describe("Whether to group components by value"),
includeAttributes: z.array(z.string()).optional().describe("Optional array of additional attributes to include")
includeAttributes: z
.array(z.string())
.optional()
.describe("Optional array of additional attributes to include"),
},
async ({ outputPath, format, groupByValue, includeAttributes }) => {
logger.debug(`Exporting BOM to: ${outputPath}`);
@@ -163,16 +203,18 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
outputPath,
format,
groupByValue,
includeAttributes
includeAttributes,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -182,22 +224,27 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
"export_netlist",
{
outputPath: z.string().describe("Path to save the netlist file"),
format: z.enum(["KiCad", "Spice", "Cadstar", "OrcadPCB2"]).optional().describe("Netlist format (default: KiCad)")
format: z
.enum(["KiCad", "Spice", "Cadstar", "OrcadPCB2"])
.optional()
.describe("Netlist format (default: KiCad)"),
},
async ({ outputPath, format }) => {
logger.debug(`Exporting netlist to: ${outputPath}`);
const result = await callKicadScript("export_netlist", {
outputPath,
format
format,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -209,7 +256,10 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
outputPath: z.string().describe("Path to save the position file"),
format: z.enum(["CSV", "ASCII"]).optional().describe("File format (default: CSV)"),
units: z.enum(["mm", "inch"]).optional().describe("Units to use (default: mm)"),
side: z.enum(["top", "bottom", "both"]).optional().describe("Which board side to include (default: both)")
side: z
.enum(["top", "bottom", "both"])
.optional()
.describe("Which board side to include (default: both)"),
},
async ({ outputPath, format, units, side }) => {
logger.debug(`Exporting position file to: ${outputPath}`);
@@ -217,16 +267,18 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
outputPath,
format,
units,
side
side,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
// ------------------------------------------------------
@@ -237,24 +289,29 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
{
outputPath: z.string().describe("Path to save the VRML file"),
includeComponents: z.boolean().optional().describe("Whether to include 3D component models"),
useRelativePaths: z.boolean().optional().describe("Whether to use relative paths for 3D models")
useRelativePaths: z
.boolean()
.optional()
.describe("Whether to use relative paths for 3D models"),
},
async ({ outputPath, includeComponents, useRelativePaths }) => {
logger.debug(`Exporting VRML to: ${outputPath}`);
const result = await callKicadScript("export_vrml", {
outputPath,
includeComponents,
useRelativePaths
useRelativePaths,
});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result)
}]
text: JSON.stringify(result),
},
],
};
}
},
);
logger.info('Export tools registered');
logger.info("Export tools registered");
}

View File

@@ -62,10 +62,7 @@ const RectSchema = z.object({
// ---- tool registration --------------------------------------------------- //
export function registerFootprintTools(
server: McpServer,
callKicadScript: Function,
) {
export function registerFootprintTools(server: McpServer, callKicadScript: Function) {
// ── create_footprint ──────────────────────────────────────────────────── //
server.tool(
"create_footprint",
@@ -80,10 +77,7 @@ export function registerFootprintTools(
),
name: z.string().describe("Footprint name, e.g. 'R_0603_Custom'"),
description: z.string().optional().describe("Human-readable description"),
tags: z
.string()
.optional()
.describe("Space-separated tag string, e.g. 'resistor SMD 0603'"),
tags: z.string().optional().describe("Space-separated tag string, e.g. 'resistor SMD 0603'"),
pads: z
.array(PadSchema)
.optional()
@@ -91,9 +85,7 @@ export function registerFootprintTools(
courtyard: RectSchema.optional().describe(
"Courtyard rectangle on F.CrtYd (recommended: 0.25 mm clearance around pads)",
),
silkscreen: RectSchema.optional().describe(
"Silkscreen rectangle on F.SilkS",
),
silkscreen: RectSchema.optional().describe("Silkscreen rectangle on F.SilkS"),
fabLayer: RectSchema.optional().describe(
"Fab-layer rectangle on F.Fab (shows component body)",
),
@@ -139,9 +131,7 @@ export function registerFootprintTools(
footprintPath: z
.string()
.describe("Full path to the .kicad_mod file, e.g. C:/MyLib.pretty/R_Custom.kicad_mod"),
padNumber: z
.union([z.string(), z.number()])
.describe("Pad number to edit, e.g. '1' or 2"),
padNumber: z.union([z.string(), z.number()]).describe("Pad number to edit, e.g. '1' or 2"),
size: PadSize.optional().describe("New pad size in mm"),
at: PadPosition.optional().describe("New pad position in mm"),
drill: z
@@ -151,10 +141,7 @@ export function registerFootprintTools(
])
.optional()
.describe("New drill size (for THT pads)"),
shape: z
.enum(["rect", "circle", "oval", "roundrect"])
.optional()
.describe("New pad shape"),
shape: z.enum(["rect", "circle", "oval", "roundrect"]).optional().describe("New pad shape"),
},
async (args: {
footprintPath: string;
@@ -177,9 +164,7 @@ export function registerFootprintTools(
"Register a .pretty footprint library in KiCAD's fp-lib-table so KiCAD can find the footprints. " +
"Run this after create_footprint when KiCAD shows 'library not found in footprint library table'.",
{
libraryPath: z
.string()
.describe("Full path to the .pretty directory to register"),
libraryPath: z.string().describe("Full path to the .pretty directory to register"),
libraryName: z
.string()
.optional()

View File

@@ -7,33 +7,21 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerFreeroutingTools(
server: McpServer,
callKicadScript: Function,
) {
export function registerFreeroutingTools(server: McpServer, callKicadScript: Function) {
// Full autoroute: export DSN -> run Freerouting -> import SES
server.tool(
"autoroute",
"Run Freerouting autorouter on the current PCB. Exports to Specctra DSN, runs Freerouting CLI, and imports the routed SES result. Requires Java 11+ and freerouting.jar (see check_freerouting).",
{
boardPath: z
.string()
.optional()
.describe("Path to .kicad_pcb file (default: current board)"),
boardPath: z.string().optional().describe("Path to .kicad_pcb file (default: current board)"),
freeroutingJar: z
.string()
.optional()
.describe(
"Path to freerouting.jar (default: ~/.kicad-mcp/freerouting.jar or FREEROUTING_JAR env)",
),
maxPasses: z
.number()
.optional()
.describe("Maximum routing passes (default: 20)"),
timeout: z
.number()
.optional()
.describe("Timeout in seconds (default: 300)"),
maxPasses: z.number().optional().describe("Maximum routing passes (default: 20)"),
timeout: z.number().optional().describe("Timeout in seconds (default: 300)"),
},
async (args: any) => {
const result = await callKicadScript("autoroute", args);
@@ -53,10 +41,7 @@ export function registerFreeroutingTools(
"export_dsn",
"Export the current PCB to Specctra DSN format. Useful for manual Freerouting workflow or external autorouters.",
{
boardPath: z
.string()
.optional()
.describe("Path to .kicad_pcb file (default: current board)"),
boardPath: z.string().optional().describe("Path to .kicad_pcb file (default: current board)"),
outputPath: z
.string()
.optional()
@@ -81,10 +66,7 @@ export function registerFreeroutingTools(
"Import a Specctra SES (session) file into the current PCB. Use after running Freerouting externally.",
{
sesPath: z.string().describe("Path to the .ses file to import"),
boardPath: z
.string()
.optional()
.describe("Path to .kicad_pcb file (default: current board)"),
boardPath: z.string().optional().describe("Path to .kicad_pcb file (default: current board)"),
},
async (args: any) => {
const result = await callKicadScript("import_ses", args);
@@ -104,10 +86,7 @@ export function registerFreeroutingTools(
"check_freerouting",
"Check if Java and Freerouting JAR are available on the system. Run this before autoroute to verify prerequisites.",
{
freeroutingJar: z
.string()
.optional()
.describe("Path to freerouting.jar to check"),
freeroutingJar: z.string().optional().describe("Path to freerouting.jar to check"),
},
async (args: any) => {
const result = await callKicadScript("check_freerouting", args);

View File

@@ -3,8 +3,8 @@
* Provides access to JLCPCB's complete parts catalog via their API
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerJLCPCBApiTools(server: McpServer, callKicadScript: Function) {
// Download JLCPCB parts database
@@ -18,32 +18,41 @@ No API credentials required - uses public JLCSearch API.
The download takes 5-10 minutes and creates a local SQLite database
for fast offline searching.`,
{
force: z.boolean().optional().default(false)
.describe("Force re-download even if database exists")
force: z
.boolean()
.optional()
.default(false)
.describe("Force re-download even if database exists"),
},
async (args: { force?: boolean }) => {
const result = await callKicadScript("download_jlcpcb_database", args);
if (result.success) {
return {
content: [{
content: [
{
type: "text",
text: `✓ Successfully downloaded JLCPCB parts database\n\n` +
text:
`✓ Successfully downloaded JLCPCB parts database\n\n` +
`Total parts: ${result.total_parts}\n` +
`Basic parts: ${result.basic_parts}\n` +
`Extended parts: ${result.extended_parts}\n` +
`Database size: ${result.db_size_mb} MB\n` +
`Database path: ${result.db_path}`
}]
`Database path: ${result.db_path}`,
},
],
};
}
return {
content: [{
content: [
{
type: "text",
text: `✗ Failed to download JLCPCB database: ${result.message || 'Unknown error'}\n\n` +
`Make sure JLCPCB_API_KEY and JLCPCB_API_SECRET environment variables are set.`
}]
text:
`✗ Failed to download JLCPCB database: ${result.message || "Unknown error"}\n\n` +
`Make sure JLCPCB_API_KEY and JLCPCB_API_SECRET environment variables are set.`,
},
],
};
}
},
);
// Search JLCPCB parts
@@ -56,58 +65,80 @@ Provides real pricing, stock info, and library type (Basic parts = free assembly
Use this to find components with exact specifications and cost optimization.`,
{
query: z.string().optional()
query: z
.string()
.optional()
.describe("Free-text search (e.g., '10k resistor 0603', 'ESP32', 'STM32F103')"),
category: z.string().optional()
category: z
.string()
.optional()
.describe("Filter by category (e.g., 'Resistors', 'Capacitors', 'Microcontrollers')"),
package: z.string().optional()
package: z
.string()
.optional()
.describe("Filter by package type (e.g., '0603', 'SOT-23', 'QFN-32')"),
library_type: z.enum(["Basic", "Extended", "Preferred", "All"]).optional().default("All")
library_type: z
.enum(["Basic", "Extended", "Preferred", "All"])
.optional()
.default("All")
.describe("Filter by library type (Basic = free assembly at JLCPCB)"),
manufacturer: z.string().optional()
.describe("Filter by manufacturer name"),
in_stock: z.boolean().optional().default(true)
manufacturer: z.string().optional().describe("Filter by manufacturer name"),
in_stock: z
.boolean()
.optional()
.default(true)
.describe("Only show parts with available stock"),
limit: z.number().optional().default(20)
.describe("Maximum number of results to return")
limit: z.number().optional().default(20).describe("Maximum number of results to return"),
},
async (args: any) => {
const result = await callKicadScript("search_jlcpcb_parts", args);
if (result.success && result.parts) {
if (result.parts.length === 0) {
return {
content: [{
content: [
{
type: "text",
text: `No JLCPCB parts found matching your criteria.\n\n` +
`Try broadening your search or check if the database is populated.`
}]
text:
`No JLCPCB parts found matching your criteria.\n\n` +
`Try broadening your search or check if the database is populated.`,
},
],
};
}
const partsList = result.parts.map((p: any) => {
const priceInfo = p.price_breaks && p.price_breaks.length > 0
const partsList = result.parts
.map((p: any) => {
const priceInfo =
p.price_breaks && p.price_breaks.length > 0
? ` - $${p.price_breaks[0].price}/ea`
: '';
const stockInfo = p.stock > 0 ? ` (${p.stock} in stock)` : ' (out of stock)';
: "";
const stockInfo = p.stock > 0 ? ` (${p.stock} in stock)` : " (out of stock)";
return `${p.lcsc}: ${p.mfr_part} - ${p.description} [${p.library_type}]${priceInfo}${stockInfo}`;
}).join('\n');
})
.join("\n");
return {
content: [{
content: [
{
type: "text",
text: `Found ${result.count} JLCPCB parts:\n\n${partsList}\n\n` +
`💡 Basic parts have free assembly. Extended parts charge $3 setup fee per unique part.`
}]
text:
`Found ${result.count} JLCPCB parts:\n\n${partsList}\n\n` +
`💡 Basic parts have free assembly. Extended parts charge $3 setup fee per unique part.`,
},
],
};
}
return {
content: [{
content: [
{
type: "text",
text: `Failed to search JLCPCB parts: ${result.message || 'Unknown error'}\n\n` +
`Make sure you've downloaded the database first using download_jlcpcb_database.`
}]
text:
`Failed to search JLCPCB parts: ${result.message || "Unknown error"}\n\n` +
`Make sure you've downloaded the database first using download_jlcpcb_database.`,
},
],
};
}
},
);
// Get JLCPCB part details
@@ -115,50 +146,55 @@ Use this to find components with exact specifications and cost optimization.`,
"get_jlcpcb_part",
"Get detailed information about a specific JLCPCB part by LCSC number",
{
lcsc_number: z.string()
.describe("LCSC part number (e.g., 'C25804', 'C2286')")
lcsc_number: z.string().describe("LCSC part number (e.g., 'C25804', 'C2286')"),
},
async (args: { lcsc_number: string }) => {
const result = await callKicadScript("get_jlcpcb_part", args);
if (result.success && result.part) {
const p = result.part;
const priceTable = p.price_breaks && p.price_breaks.length > 0
? '\n\nPrice Breaks:\n' + p.price_breaks.map((pb: any) =>
` ${pb.qty}+: $${pb.price}/ea`
).join('\n')
: '';
const priceTable =
p.price_breaks && p.price_breaks.length > 0
? "\n\nPrice Breaks:\n" +
p.price_breaks.map((pb: any) => ` ${pb.qty}+: $${pb.price}/ea`).join("\n")
: "";
const footprints = result.footprints && result.footprints.length > 0
? '\n\nSuggested KiCAD Footprints:\n' + result.footprints.map((f: string) =>
` - ${f}`
).join('\n')
: '';
const footprints =
result.footprints && result.footprints.length > 0
? "\n\nSuggested KiCAD Footprints:\n" +
result.footprints.map((f: string) => ` - ${f}`).join("\n")
: "";
return {
content: [{
content: [
{
type: "text",
text: `LCSC: ${p.lcsc}\n` +
text:
`LCSC: ${p.lcsc}\n` +
`MFR Part: ${p.mfr_part}\n` +
`Manufacturer: ${p.manufacturer}\n` +
`Category: ${p.category} / ${p.subcategory}\n` +
`Package: ${p.package}\n` +
`Description: ${p.description}\n` +
`Library Type: ${p.library_type} ${p.library_type === 'Basic' ? '(Free assembly!)' : ''}\n` +
`Library Type: ${p.library_type} ${p.library_type === "Basic" ? "(Free assembly!)" : ""}\n` +
`Stock: ${p.stock}\n` +
(p.datasheet ? `Datasheet: ${p.datasheet}\n` : '') +
(p.datasheet ? `Datasheet: ${p.datasheet}\n` : "") +
priceTable +
footprints
}]
footprints,
},
],
};
}
return {
content: [{
content: [
{
type: "text",
text: `Part not found: ${args.lcsc_number}\n\n` +
`Make sure you've downloaded the JLCPCB database first.`
}]
text:
`Part not found: ${args.lcsc_number}\n\n` +
`Make sure you've downloaded the JLCPCB database first.`,
},
],
};
}
},
);
// Get JLCPCB database statistics
@@ -171,25 +207,31 @@ Use this to find components with exact specifications and cost optimization.`,
if (result.success) {
const stats = result.stats;
return {
content: [{
content: [
{
type: "text",
text: `JLCPCB Database Statistics:\n\n` +
text:
`JLCPCB Database Statistics:\n\n` +
`Total parts: ${stats.total_parts.toLocaleString()}\n` +
`Basic parts: ${stats.basic_parts.toLocaleString()} (free assembly)\n` +
`Extended parts: ${stats.extended_parts.toLocaleString()} ($3 setup fee each)\n` +
`In stock: ${stats.in_stock.toLocaleString()}\n` +
`Database path: ${stats.db_path}`
}]
`Database path: ${stats.db_path}`,
},
],
};
}
return {
content: [{
content: [
{
type: "text",
text: `JLCPCB database not found or empty.\n\n` +
`Run download_jlcpcb_database first to populate the database.`
}]
text:
`JLCPCB database not found or empty.\n\n` +
`Run download_jlcpcb_database first to populate the database.`,
},
],
};
}
},
);
// Suggest alternative parts
@@ -200,46 +242,54 @@ Use this to find components with exact specifications and cost optimization.`,
Finds similar parts that may be cheaper, have more stock, or are Basic library type.
Useful for cost optimization and finding alternatives when parts are out of stock.`,
{
lcsc_number: z.string()
.describe("Reference LCSC part number to find alternatives for"),
limit: z.number().optional().default(5)
.describe("Maximum number of alternatives to return")
lcsc_number: z.string().describe("Reference LCSC part number to find alternatives for"),
limit: z.number().optional().default(5).describe("Maximum number of alternatives to return"),
},
async (args: { lcsc_number: string; limit?: number }) => {
const result = await callKicadScript("suggest_jlcpcb_alternatives", args);
if (result.success && result.alternatives) {
if (result.alternatives.length === 0) {
return {
content: [{
content: [
{
type: "text",
text: `No alternatives found for ${args.lcsc_number}`
}]
text: `No alternatives found for ${args.lcsc_number}`,
},
],
};
}
const altsList = result.alternatives.map((p: any, i: number) => {
const priceInfo = p.price_breaks && p.price_breaks.length > 0
const altsList = result.alternatives
.map((p: any, i: number) => {
const priceInfo =
p.price_breaks && p.price_breaks.length > 0
? ` - $${p.price_breaks[0].price}/ea`
: '';
const savings = result.reference_price && p.price_breaks && p.price_breaks.length > 0
: "";
const savings =
result.reference_price && p.price_breaks && p.price_breaks.length > 0
? ` (${((1 - p.price_breaks[0].price / result.reference_price) * 100).toFixed(0)}% cheaper)`
: '';
: "";
return `${i + 1}. ${p.lcsc}: ${p.mfr_part} [${p.library_type}]${priceInfo}${savings}\n ${p.description}\n Stock: ${p.stock}`;
}).join('\n\n');
})
.join("\n\n");
return {
content: [{
content: [
{
type: "text",
text: `Alternative parts for ${args.lcsc_number}:\n\n${altsList}`
}]
text: `Alternative parts for ${args.lcsc_number}:\n\n${altsList}`,
},
],
};
}
return {
content: [{
content: [
{
type: "text",
text: `Failed to find alternatives: ${result.message || 'Unknown error'}`
}]
text: `Failed to find alternatives: ${result.message || "Unknown error"}`,
},
],
};
}
},
);
}

View File

@@ -3,8 +3,8 @@
* Provides search/browse access to local KiCad symbol libraries
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerSymbolLibraryTools(server: McpServer, callKicadScript: Function) {
// List available symbol libraries
@@ -19,20 +19,20 @@ export function registerSymbolLibraryTools(server: McpServer, callKicadScript: F
content: [
{
type: "text",
text: `Found ${result.count} symbol libraries:\n${result.libraries.join('\n')}`
}
]
text: `Found ${result.count} symbol libraries:\n${result.libraries.join("\n")}`,
},
],
};
}
return {
content: [
{
type: "text",
text: `Failed to list symbol libraries: ${result.message || 'Unknown error'}`
}
]
text: `Failed to list symbol libraries: ${result.message || "Unknown error"}`,
},
],
};
}
},
);
// Search for symbols across all libraries
@@ -45,12 +45,12 @@ Use this to find components already in your local libraries (e.g., JLCPCB-KiCad-
Returns symbol references that can be used directly in schematics.`,
{
query: z.string()
.describe("Search query (e.g., 'ESP32', 'STM32F103', 'C8734' for LCSC ID)"),
library: z.string().optional()
query: z.string().describe("Search query (e.g., 'ESP32', 'STM32F103', 'C8734' for LCSC ID)"),
library: z
.string()
.optional()
.describe("Optional: filter to specific library name pattern (e.g., 'JLCPCB')"),
limit: z.number().optional().default(20)
.describe("Maximum number of results to return")
limit: z.number().optional().default(20).describe("Maximum number of results to return"),
},
async (args: { query: string; library?: string; limit?: number }) => {
const result = await callKicadScript("search_symbols", args);
@@ -60,38 +60,40 @@ Returns symbol references that can be used directly in schematics.`,
content: [
{
type: "text",
text: `No symbols found matching "${args.query}"${args.library ? ` in libraries matching "${args.library}"` : ''}`
}
]
text: `No symbols found matching "${args.query}"${args.library ? ` in libraries matching "${args.library}"` : ""}`,
},
],
};
}
const symbolList = result.symbols.map((s: any) => {
const symbolList = result.symbols
.map((s: any) => {
const parts = [`${s.full_ref}`];
if (s.lcsc_id) parts.push(`LCSC: ${s.lcsc_id}`);
if (s.description) parts.push(s.description);
else if (s.value) parts.push(s.value);
return parts.join(' | ');
}).join('\n');
return parts.join(" | ");
})
.join("\n");
return {
content: [
{
type: "text",
text: `Found ${result.count} symbols matching "${args.query}":\n\n${symbolList}`
}
]
text: `Found ${result.count} symbols matching "${args.query}":\n\n${symbolList}`,
},
],
};
}
return {
content: [
{
type: "text",
text: `Failed to search symbols: ${result.message || 'Unknown error'}`
}
]
text: `Failed to search symbols: ${result.message || "Unknown error"}`,
},
],
};
}
},
);
// List symbols in a specific library
@@ -99,36 +101,37 @@ Returns symbol references that can be used directly in schematics.`,
"list_library_symbols",
"List all symbols in a specific KiCAD symbol library",
{
library: z.string()
.describe("Library name (e.g., 'Device', 'PCM_JLCPCB-MCUs')")
library: z.string().describe("Library name (e.g., 'Device', 'PCM_JLCPCB-MCUs')"),
},
async (args: { library: string }) => {
const result = await callKicadScript("list_library_symbols", args);
if (result.success && result.symbols) {
const symbolList = result.symbols.map((s: any) => {
const symbolList = result.symbols
.map((s: any) => {
const parts = [` - ${s.name}`];
if (s.lcsc_id) parts.push(`(LCSC: ${s.lcsc_id})`);
return parts.join(' ');
}).join('\n');
return parts.join(" ");
})
.join("\n");
return {
content: [
{
type: "text",
text: `Library "${args.library}" contains ${result.count} symbols:\n${symbolList}`
}
]
text: `Library "${args.library}" contains ${result.count} symbols:\n${symbolList}`,
},
],
};
}
return {
content: [
{
type: "text",
text: `Failed to list symbols in library ${args.library}: ${result.message || 'Unknown error'}`
}
]
text: `Failed to list symbols in library ${args.library}: ${result.message || "Unknown error"}`,
},
],
};
}
},
);
// Get detailed information about a specific symbol
@@ -136,8 +139,9 @@ Returns symbol references that can be used directly in schematics.`,
"get_symbol_info",
"Get detailed information about a specific symbol",
{
symbol: z.string()
.describe("Symbol specification (e.g., 'Device:R' or 'PCM_JLCPCB-MCUs:STM32F103C8T6')")
symbol: z
.string()
.describe("Symbol specification (e.g., 'Device:R' or 'PCM_JLCPCB-MCUs:STM32F103C8T6')"),
},
async (args: { symbol: string }) => {
const result = await callKicadScript("get_symbol_info", args);
@@ -145,34 +149,36 @@ Returns symbol references that can be used directly in schematics.`,
const info = result.symbol_info;
const details = [
`Symbol: ${info.full_ref}`,
info.value ? `Value: ${info.value}` : '',
info.description ? `Description: ${info.description}` : '',
info.lcsc_id ? `LCSC: ${info.lcsc_id}` : '',
info.manufacturer ? `Manufacturer: ${info.manufacturer}` : '',
info.mpn ? `MPN: ${info.mpn}` : '',
info.footprint ? `Footprint: ${info.footprint}` : '',
info.category ? `Category: ${info.category}` : '',
info.lib_class ? `Class: ${info.lib_class}` : '',
info.datasheet ? `Datasheet: ${info.datasheet}` : '',
].filter(line => line).join('\n');
info.value ? `Value: ${info.value}` : "",
info.description ? `Description: ${info.description}` : "",
info.lcsc_id ? `LCSC: ${info.lcsc_id}` : "",
info.manufacturer ? `Manufacturer: ${info.manufacturer}` : "",
info.mpn ? `MPN: ${info.mpn}` : "",
info.footprint ? `Footprint: ${info.footprint}` : "",
info.category ? `Category: ${info.category}` : "",
info.lib_class ? `Class: ${info.lib_class}` : "",
info.datasheet ? `Datasheet: ${info.datasheet}` : "",
]
.filter((line) => line)
.join("\n");
return {
content: [
{
type: "text",
text: details
}
]
text: details,
},
],
};
}
return {
content: [
{
type: "text",
text: `Failed to get symbol info: ${result.message || 'Unknown error'}`
}
]
text: `Failed to get symbol info: ${result.message || "Unknown error"}`,
},
],
};
}
},
);
}

View File

@@ -6,10 +6,7 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerLibraryTools(
server: McpServer,
callKicadScript: Function,
) {
export function registerLibraryTools(server: McpServer, callKicadScript: Function) {
// List available footprint libraries
server.tool(
"list_libraries",
@@ -48,18 +45,9 @@ export function registerLibraryTools(
"search_footprints",
"Search for footprints matching a pattern across all libraries",
{
search_term: z
.string()
.describe("Search term or pattern to match footprint names"),
library: z
.string()
.optional()
.describe("Optional specific library to search in"),
limit: z
.number()
.optional()
.default(50)
.describe("Maximum number of results to return"),
search_term: z.string().describe("Search term or pattern to match footprint names"),
library: z.string().optional().describe("Optional specific library to search in"),
limit: z.number().optional().default(50).describe("Maximum number of results to return"),
},
async (args: { search_term: string; library?: string; limit?: number }) => {
const result = await callKicadScript("search_footprints", {
@@ -99,25 +87,14 @@ export function registerLibraryTools(
"list_library_footprints",
"List all footprints in a specific KiCAD library",
{
library_name: z
.string()
.describe("Name of the library to list footprints from"),
filter: z
.string()
.optional()
.describe("Optional filter pattern for footprint names"),
limit: z
.number()
.optional()
.default(100)
.describe("Maximum number of footprints to list"),
library_name: z.string().describe("Name of the library to list footprints from"),
filter: z.string().optional().describe("Optional filter pattern for footprint names"),
limit: z.number().optional().default(100).describe("Maximum number of footprints to list"),
},
async (args: { library_name: string; filter?: string; limit?: number }) => {
const result = await callKicadScript("list_library_footprints", args);
if (result.success && result.footprints) {
const footprintList = result.footprints
.map((fp: string) => ` - ${fp}`)
.join("\n");
const footprintList = result.footprints.map((fp: string) => ` - ${fp}`).join("\n");
return {
content: [
{
@@ -143,12 +120,8 @@ export function registerLibraryTools(
"get_footprint_info",
"Get detailed information about a specific footprint",
{
library_name: z
.string()
.describe("Name of the library containing the footprint"),
footprint_name: z
.string()
.describe("Name of the footprint to get information about"),
library_name: z.string().describe("Name of the library containing the footprint"),
footprint_name: z.string().describe("Name of the footprint to get information about"),
},
async (args: { library_name: string; footprint_name: string }) => {
const result = await callKicadScript("get_footprint_info", args);
@@ -156,15 +129,16 @@ export function registerLibraryTools(
const info = result.info;
// pads is a list of {number, type, shape} objects
const padsArray: Array<{ number: string; type: string; shape: string }> =
Array.isArray(info.pads) ? info.pads : [];
const padsArray: Array<{ number: string; type: string; shape: string }> = Array.isArray(
info.pads,
)
? info.pads
: [];
const padsSummary = padsArray.length
? `${padsArray.length} pads: ${padsArray.map((p) => p.number).join(", ")}`
: "";
const padsDetail = padsArray.length
? padsArray
.map((p) => ` pad ${p.number}: ${p.type} ${p.shape}`)
.join("\n")
? padsArray.map((p) => ` pad ${p.number}: ${p.type} ${p.shape}`).join("\n")
: "";
const details = [
@@ -178,9 +152,7 @@ export function registerLibraryTools(
info.courtyard
? `Courtyard size: ${info.courtyard.width}mm x ${info.courtyard.height}mm`
: "",
info.attributes
? `Attributes: ${JSON.stringify(info.attributes)}`
: "",
info.attributes ? `Attributes: ${JSON.stringify(info.attributes)}` : "",
]
.filter((line) => line)
.join("\n");

View File

@@ -2,8 +2,8 @@
* Project management tools for KiCAD MCP server
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerProjectTools(server: McpServer, callKicadScript: Function) {
// Create project tool
@@ -17,12 +17,14 @@ export function registerProjectTools(server: McpServer, callKicadScript: Functio
async (args: { path: string; name: string }) => {
const result = await callKicadScript("create_project", args);
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
},
);
// Open project tool
@@ -35,12 +37,14 @@ export function registerProjectTools(server: McpServer, callKicadScript: Functio
async (args: { filename: string }) => {
const result = await callKicadScript("open_project", args);
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
},
);
// Save project tool
@@ -53,12 +57,14 @@ export function registerProjectTools(server: McpServer, callKicadScript: Functio
async (args: { path?: string }) => {
const result = await callKicadScript("save_project", args);
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
},
);
// Get project info tool
@@ -69,12 +75,14 @@ export function registerProjectTools(server: McpServer, callKicadScript: Functio
async () => {
const result = await callKicadScript("get_project_info", {});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
},
);
// Snapshot project tool — saves a named checkpoint as PDF/image
@@ -83,17 +91,26 @@ export function registerProjectTools(server: McpServer, callKicadScript: Functio
"Save a named checkpoint snapshot of the current project state (renders board to PDF and records step label). Call after completing each major step — e.g. after Step 1 (schematic_ok) and Step 2 (layout_ok). Required by the demo workflow before waiting for user confirmation.",
{
step: z.string().describe("Step number or identifier, e.g. '1' or '2'"),
label: z.string().describe("Short label for this checkpoint, e.g. 'schematic_ok' or 'layout_ok'"),
prompt: z.string().optional().describe("Full prompt text to save as PROMPT_step{step}_{timestamp}.md alongside the snapshot"),
label: z
.string()
.describe("Short label for this checkpoint, e.g. 'schematic_ok' or 'layout_ok'"),
prompt: z
.string()
.optional()
.describe(
"Full prompt text to save as PROMPT_step{step}_{timestamp}.md alongside the snapshot",
),
},
async (args: { step: string; label: string; prompt?: string }) => {
const result = await callKicadScript("snapshot_project", args);
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
},
);
}

View File

@@ -4,7 +4,7 @@
* Centralizes all tool definitions and provides lookup/search functionality
*/
import { z } from 'zod';
import { z } from "zod";
export interface ToolDefinition {
name: string;
@@ -36,8 +36,8 @@ export const toolCategories: ToolCategory[] = [
"add_zone",
"get_board_extents",
"get_board_2d_view",
"launch_kicad_ui"
]
"launch_kicad_ui",
],
},
{
name: "component",
@@ -50,8 +50,8 @@ export const toolCategories: ToolCategory[] = [
"get_component_properties",
"add_component_annotation",
"group_components",
"replace_component"
]
"replace_component",
],
},
{
name: "export",
@@ -64,8 +64,8 @@ export const toolCategories: ToolCategory[] = [
"export_bom",
"export_netlist",
"export_position_file",
"export_vrml"
]
"export_vrml",
],
},
{
name: "drc",
@@ -78,12 +78,13 @@ export const toolCategories: ToolCategory[] = [
"assign_net_to_class",
"set_layer_constraints",
"check_clearance",
"get_drc_violations"
]
"get_drc_violations",
],
},
{
name: "schematic",
description: "Schematic operations: create, inspect, add/edit/delete components, wire connections, netlists, annotation",
description:
"Schematic operations: create, inspect, add/edit/delete components, wire connections, netlists, annotation",
tools: [
"create_schematic",
"add_schematic_component",
@@ -107,37 +108,24 @@ export const toolCategories: ToolCategory[] = [
"sync_schematic_to_board",
"get_schematic_view",
"export_schematic_svg",
"export_schematic_pdf"
]
"export_schematic_pdf",
],
},
{
name: "library",
description: "Footprint library access: search, browse, get footprint information",
tools: [
"list_libraries",
"search_footprints",
"list_library_footprints",
"get_footprint_info"
]
tools: ["list_libraries", "search_footprints", "list_library_footprints", "get_footprint_info"],
},
{
name: "routing",
description: "Advanced routing operations: vias, copper pours",
tools: [
"add_via",
"add_copper_pour"
]
tools: ["add_via", "add_copper_pour"],
},
{
name: "autoroute",
description: "Freerouting autorouter: automatic PCB routing via Specctra DSN/SES",
tools: [
"autoroute",
"export_dsn",
"import_ses",
"check_freerouting"
]
}
tools: ["autoroute", "export_dsn", "import_ses", "check_freerouting"],
},
];
/**
@@ -175,7 +163,7 @@ export const directToolNames = [
"sync_schematic_to_board",
// UI management
"check_kicad_ui"
"check_kicad_ui",
];
// Build lookup maps at module load time
@@ -260,7 +248,7 @@ export function searchTools(query: string): SearchResult[] {
matches.push({
category: "direct",
tool: toolName,
description: `${toolName} (direct tool — call directly, no execute_tool needed)`
description: `${toolName} (direct tool — call directly, no execute_tool needed)`,
});
}
}
@@ -268,15 +256,14 @@ export function searchTools(query: string): SearchResult[] {
// Search routed tools by name and category
for (const category of toolCategories) {
const categoryMatch =
category.name.toLowerCase().includes(q) ||
category.description.toLowerCase().includes(q);
category.name.toLowerCase().includes(q) || category.description.toLowerCase().includes(q);
for (const toolName of category.tools) {
if (toolName.toLowerCase().includes(q) || categoryMatch) {
matches.push({
category: category.name,
tool: toolName,
description: `${toolName} (${category.name})`
description: `${toolName} (${category.name})`,
});
}
}
@@ -297,10 +284,10 @@ export function getRegistryStats() {
total_routed_tools: routedToolCount,
total_direct_tools: directToolCount,
total_tools: routedToolCount + directToolCount,
categories: toolCategories.map(c => ({
categories: toolCategories.map((c) => ({
name: c.name,
tool_count: c.tools.length
}))
tool_count: c.tools.length,
})),
};
}

View File

@@ -4,16 +4,16 @@
* Provides discovery and execution of routed tools
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
import {
getAllCategories,
getCategory,
getToolCategory,
searchTools as registrySearchTools,
getRegistryStats
} from './registry.js';
getRegistryStats,
} from "./registry.js";
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
@@ -28,7 +28,7 @@ const toolHandlers = new Map<string, (params: any) => Promise<any>>();
*/
export function registerToolHandler(
toolName: string,
handler: (params: any) => Promise<any>
handler: (params: any) => Promise<any>,
): void {
toolHandlers.set(toolName, handler);
logger.debug(`Registered handler for routed tool: ${toolName}`);
@@ -38,7 +38,7 @@ export function registerToolHandler(
* Register all router tools with the MCP server
*/
export function registerRouterTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering router tools');
logger.info("Registering router tools");
// ============================================================================
// list_tool_categories
@@ -49,7 +49,7 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
// No parameters
},
async () => {
logger.debug('Listing tool categories');
logger.debug("Listing tool categories");
const stats = getRegistryStats();
const categories = getAllCategories();
@@ -59,20 +59,22 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
total_routed_tools: stats.total_routed_tools,
total_direct_tools: stats.total_direct_tools,
note: "Use get_category_tools to see tools in each category. Direct tools are always available.",
categories: categories.map(c => ({
categories: categories.map((c) => ({
name: c.name,
description: c.description,
tool_count: c.tools.length
}))
tool_count: c.tools.length,
})),
};
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
},
);
// ============================================================================
@@ -81,7 +83,7 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
server.tool(
"get_category_tools",
{
category: z.string().describe("Category name from list_tool_categories")
category: z.string().describe("Category name from list_tool_categories"),
},
async ({ category }) => {
logger.debug(`Getting tools for category: ${category}`);
@@ -89,15 +91,21 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
const categoryData = getCategory(category);
if (!categoryData) {
const availableCategories = getAllCategories().map(c => c.name);
const availableCategories = getAllCategories().map((c) => c.name);
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify({
text: JSON.stringify(
{
error: `Unknown category: ${category}`,
available_categories: availableCategories
}, null, 2)
}]
available_categories: availableCategories,
},
null,
2,
),
},
],
};
}
@@ -107,20 +115,22 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
category: categoryData.name,
description: categoryData.description,
tool_count: categoryData.tools.length,
tools: categoryData.tools.map(toolName => ({
tools: categoryData.tools.map((toolName) => ({
name: toolName,
description: `Use execute_tool with tool_name="${toolName}" to run this tool`
description: `Use execute_tool with tool_name="${toolName}" to run this tool`,
})),
note: "Use execute_tool to run any of these tools with appropriate parameters"
note: "Use execute_tool to run any of these tools with appropriate parameters",
};
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
},
);
// ============================================================================
@@ -130,7 +140,7 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
"execute_tool",
{
tool_name: z.string().describe("Tool name from get_category_tools"),
params: z.record(z.unknown()).optional().describe("Tool parameters (optional)")
params: z.record(z.unknown()).optional().describe("Tool parameters (optional)"),
},
async ({ tool_name, params }) => {
logger.info(`Executing routed tool: ${tool_name}`);
@@ -140,13 +150,19 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
if (!category) {
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify({
text: JSON.stringify(
{
error: `Unknown tool: ${tool_name}`,
hint: "Use list_tool_categories and get_category_tools to find available tools"
}, null, 2)
}]
hint: "Use list_tool_categories and get_category_tools to find available tools",
},
null,
2,
),
},
],
};
}
@@ -157,30 +173,44 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
// Tool is in registry but handler not registered yet
// This means the tool exists but hasn't been migrated to router pattern yet
// Fall back to calling KiCAD script directly
logger.warn(`Tool ${tool_name} in registry but no handler registered, falling back to direct call`);
logger.warn(
`Tool ${tool_name} in registry but no handler registered, falling back to direct call`,
);
try {
const result = await callKicadScript(tool_name, params || {});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify({
text: JSON.stringify(
{
tool: tool_name,
category: category,
result: result
}, null, 2)
}]
result: result,
},
null,
2,
),
},
],
};
} catch (error) {
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify({
text: JSON.stringify(
{
error: `Tool execution failed: ${(error as Error).message}`,
tool: tool_name,
category: category
}, null, 2)
}]
category: category,
},
null,
2,
),
},
],
};
}
}
@@ -192,28 +222,40 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
// The handler already returns MCP-formatted response
// Just add metadata
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify({
text: JSON.stringify(
{
tool: tool_name,
category: category,
...result
}, null, 2)
}]
...result,
},
null,
2,
),
},
],
};
} catch (error) {
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify({
text: JSON.stringify(
{
error: `Tool execution failed: ${(error as Error).message}`,
tool: tool_name,
category: category
}, null, 2)
}]
category: category,
},
null,
2,
),
},
],
};
}
}
},
);
// ============================================================================
@@ -222,7 +264,7 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
server.tool(
"search_tools",
{
query: z.string().describe("Search term (e.g., 'gerber', 'zone', 'export', 'drc')")
query: z.string().describe("Search term (e.g., 'gerber', 'zone', 'export', 'drc')"),
},
async ({ query }) => {
logger.debug(`Searching tools for: ${query}`);
@@ -233,19 +275,22 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
query: query,
count: matches.length,
matches: matches,
note: matches.length > 0
note:
matches.length > 0
? "Use execute_tool with the tool name to run it"
: "No tools found matching your query. Try list_tool_categories to browse all categories."
: "No tools found matching your query. Try list_tool_categories to browse all categories.",
};
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
},
);
logger.info('Router tools registered successfully');
logger.info("Router tools registered successfully");
}

View File

@@ -5,10 +5,7 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerRoutingTools(
server: McpServer,
callKicadScript: Function,
) {
export function registerRoutingTools(server: McpServer, callKicadScript: Function) {
// Add net tool
server.tool(
"add_net",
@@ -79,10 +76,7 @@ export function registerRoutingTools(
})
.describe("Via position"),
net: z.string().describe("Net name"),
viaType: z
.string()
.optional()
.describe("Via type (through, blind, buried)"),
viaType: z.string().optional().describe("Via type (through, blind, buried)"),
},
async (args: any) => {
const result = await callKicadScript("add_via", args);
@@ -130,10 +124,7 @@ export function registerRoutingTools(
"delete_trace",
"Delete traces from the PCB. Can delete by UUID, position, or bulk-delete all traces on a net.",
{
traceUuid: z
.string()
.optional()
.describe("UUID of a specific trace to delete"),
traceUuid: z.string().optional().describe("UUID of a specific trace to delete"),
position: z
.object({
x: z.number(),
@@ -142,18 +133,9 @@ export function registerRoutingTools(
})
.optional()
.describe("Delete trace nearest to this position"),
net: z
.string()
.optional()
.describe("Delete all traces on this net (bulk delete)"),
layer: z
.string()
.optional()
.describe("Filter by layer when using net-based deletion"),
includeVias: z
.boolean()
.optional()
.describe("Include vias in net-based deletion"),
net: z.string().optional().describe("Delete all traces on this net (bulk delete)"),
layer: z.string().optional().describe("Filter by layer when using net-based deletion"),
includeVias: z.boolean().optional().describe("Include vias in net-based deletion"),
},
async (args: any) => {
const result = await callKicadScript("delete_trace", args);
@@ -209,10 +191,7 @@ export function registerRoutingTools(
.boolean()
.optional()
.describe("Include statistics (track count, total length, etc.)"),
unit: z
.enum(["mm", "inch"])
.optional()
.describe("Unit for length measurements"),
unit: z.enum(["mm", "inch"]).optional().describe("Unit for length measurements"),
},
async (args: any) => {
const result = await callKicadScript("get_nets_list", args);
@@ -334,9 +313,13 @@ export function registerRoutingTools(
"PREFERRED tool for pad-to-pad routing. Looks up pad positions automatically, detects the net from the pad, and — critically — if the two pads are on different copper layers (e.g. J1 on F.Cu and J2 on B.Cu) automatically inserts a via at the midpoint so the connection is complete. Always use this instead of route_trace when routing between named component pads.",
{
fromRef: z.string().describe("Reference of the source component (e.g. 'U2')"),
fromPad: z.union([z.string(), z.number()]).describe("Pad number on the source component (e.g. '6' or 6)"),
fromPad: z
.union([z.string(), z.number()])
.describe("Pad number on the source component (e.g. '6' or 6)"),
toRef: z.string().describe("Reference of the target component (e.g. 'U1')"),
toPad: z.union([z.string(), z.number()]).describe("Pad number on the target component (e.g. '15' or 15)"),
toPad: z
.union([z.string(), z.number()])
.describe("Pad number on the target component (e.g. '15' or 15)"),
layer: z.string().optional().describe("PCB layer (default: F.Cu)"),
width: z.number().optional().describe("Trace width in mm (default: board default)"),
net: z.string().optional().describe("Net name override (default: auto-detected from pad)"),
@@ -362,10 +345,7 @@ export function registerRoutingTools(
.describe(
"References of the target components in same order as sourceRefs (e.g. ['U2', 'R2', 'C2'])",
),
includeVias: z
.boolean()
.optional()
.describe("Also copy vias (default: true)"),
includeVias: z.boolean().optional().describe("Also copy vias (default: true)"),
traceWidth: z
.number()
.optional()

View File

@@ -5,10 +5,7 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerSchematicTools(
server: McpServer,
callKicadScript: Function,
) {
export function registerSchematicTools(server: McpServer, callKicadScript: Function) {
// Create schematic tool
server.tool(
"create_schematic",
@@ -38,9 +35,7 @@ export function registerSchematicTools(
schematicPath: z.string().describe("Path to the schematic file"),
symbol: z
.string()
.describe(
"Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3)",
),
.describe("Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3)"),
reference: z.string().describe("Component reference (e.g., R1, U1)"),
value: z.string().optional().describe("Component value"),
footprint: z
@@ -82,10 +77,7 @@ export function registerSchematicTools(
},
};
const result = await callKicadScript(
"add_schematic_component",
transformed,
);
const result = await callKicadScript("add_schematic_component", transformed);
if (result.success) {
return {
content: [
@@ -122,9 +114,7 @@ To remove a footprint from a PCB, use delete_component instead.`,
schematicPath: z.string().describe("Path to the .kicad_sch file"),
reference: z
.string()
.describe(
"Reference designator of the component to remove (e.g. R1, U3)",
),
.describe("Reference designator of the component to remove (e.g. R1, U3)"),
},
async (args: { schematicPath: string; reference: string }) => {
const result = await callKicadScript("delete_schematic_component", args);
@@ -162,14 +152,27 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
{
schematicPath: z.string().describe("Path to the .kicad_sch file"),
reference: z.string().describe("Current reference designator of the component (e.g. R1, U3)"),
footprint: z.string().optional().describe("New KiCAD footprint string (e.g. Resistor_SMD:R_0603_1608Metric)"),
footprint: z
.string()
.optional()
.describe("New KiCAD footprint string (e.g. Resistor_SMD:R_0603_1608Metric)"),
value: z.string().optional().describe("New value string (e.g. 10k, 100nF)"),
newReference: z.string().optional().describe("Rename the reference designator (e.g. R1 → R10)"),
fieldPositions: z.record(z.object({
newReference: z
.string()
.optional()
.describe("Rename the reference designator (e.g. R1 → R10)"),
fieldPositions: z
.record(
z.object({
x: z.number(),
y: z.number(),
angle: z.number().optional().default(0),
})).optional().describe("Reposition field labels: map of field name to {x, y, angle} (e.g. {\"Reference\": {\"x\": 12.5, \"y\": 17.0}})"),
}),
)
.optional()
.describe(
'Reposition field labels: map of field name to {x, y, angle} (e.g. {"Reference": {"x": 12.5, "y": 17.0}})',
),
},
async (args: {
schematicPath: string;
@@ -220,20 +223,24 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
: "unknown";
const fieldLines = Object.entries(result.fields ?? {}).map(
([name, f]: [string, any]) =>
` ${name}: "${f.value}" @ (${f.x}, ${f.y}, angle=${f.angle}°)`
` ${name}: "${f.value}" @ (${f.x}, ${f.y}, angle=${f.angle}°)`,
);
return {
content: [{
content: [
{
type: "text",
text: `Component ${result.reference} at ${pos}\nFields:\n${fieldLines.join("\n")}`,
}],
},
],
};
}
return {
content: [{
content: [
{
type: "text",
text: `Failed to get component: ${result.message || "Unknown error"}`,
}],
},
],
};
},
);
@@ -251,13 +258,8 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
snapToPins: z
.boolean()
.optional()
.describe(
"Snap the first and last waypoints to the nearest pin (default: true)",
),
snapTolerance: z
.number()
.optional()
.describe("Maximum snap distance in mm (default: 1.0)"),
.describe("Snap the first and last waypoints to the nearest pin (default: true)"),
snapTolerance: z.number().optional().describe("Maximum snap distance in mm (default: 1.0)"),
},
async (args: {
schematicPath: string;
@@ -294,10 +296,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
"Place a junction dot at a wire intersection in the schematic. Required at T-branch and X-cross points so KiCAD recognises the electrical connection.",
{
schematicPath: z.string().describe("Path to the .kicad_sch file"),
position: z
.array(z.number())
.length(2)
.describe("Junction position [x, y] in mm"),
position: z.array(z.number()).length(2).describe("Junction position [x, y] in mm"),
},
async (args: { schematicPath: string; position: number[] }) => {
const result = await callKicadScript("add_schematic_junction", args);
@@ -329,19 +328,10 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
"Add a net label to the schematic",
{
schematicPath: z.string().describe("Path to the schematic file"),
netName: z
.string()
.describe("Name of the net (e.g., VCC, GND, SIGNAL_1)"),
position: z
.array(z.number())
.length(2)
.describe("Position [x, y] for the label"),
netName: z.string().describe("Name of the net (e.g., VCC, GND, SIGNAL_1)"),
position: z.array(z.number()).length(2).describe("Position [x, y] for the label"),
},
async (args: {
schematicPath: string;
netName: string;
position: number[];
}) => {
async (args: { schematicPath: string; netName: string; position: number[] }) => {
const result = await callKicadScript("add_schematic_net_label", args);
if (result.success) {
return {
@@ -451,9 +441,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
async (args: { schematicPath: string; x: number; y: number }) => {
const result = await callKicadScript("get_wire_connections", args);
if (result.success && result.pins) {
const pinList = result.pins
.map((p: any) => ` - ${p.component}/${p.pin}`)
.join("\n");
const pinList = result.pins.map((p: any) => ` - ${p.component}/${p.pin}`).join("\n");
const wireList = (result.wires ?? [])
.map((w: any) => ` - (${w.start.x},${w.start.y}) → (${w.end.x},${w.end.y})`)
.join("\n");
@@ -484,9 +472,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
"Returns the exact x/y coordinates of every pin on a schematic component. Use this before add_schematic_net_label to place labels correctly on pin endpoints.",
{
schematicPath: z.string().describe("Path to the schematic file"),
reference: z
.string()
.describe("Component reference designator (e.g. U1, R1, J2)"),
reference: z.string().describe("Component reference designator (e.g. U1, R1, J2)"),
},
async (args: { schematicPath: string; reference: string }) => {
const result = await callKicadScript("get_schematic_pin_locations", args);
@@ -541,23 +527,16 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
pinOffset?: number;
}) => {
const result = await callKicadScript("connect_passthrough", args);
if (
result.success !== false ||
(result.connected && result.connected.length > 0)
) {
if (result.success !== false || (result.connected && result.connected.length > 0)) {
const lines: string[] = [];
if (result.connected?.length)
lines.push(
`Connected (${result.connected.length}): ${result.connected.slice(0, 5).join(", ")}${result.connected.length > 5 ? " ..." : ""}`,
);
if (result.failed?.length)
lines.push(
`Failed (${result.failed.length}): ${result.failed.join(", ")}`,
);
lines.push(`Failed (${result.failed.length}): ${result.failed.join(", ")}`);
return {
content: [
{ type: "text", text: result.message + "\n" + lines.join("\n") },
],
content: [{ type: "text", text: result.message + "\n" + lines.join("\n") }],
};
} else {
return {
@@ -581,7 +560,10 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
filter: z
.object({
libId: z.string().optional().describe("Filter by library ID (e.g., 'Device:R')"),
referencePrefix: z.string().optional().describe("Filter by reference prefix (e.g., 'R', 'C', 'U')"),
referencePrefix: z
.string()
.optional()
.describe("Filter by reference prefix (e.g., 'R', 'C', 'U')"),
})
.optional()
.describe("Optional filters"),
@@ -640,9 +622,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
};
}
const lines = nets.map((n: any) => {
const conns = (n.connections || [])
.map((c: any) => `${c.component}/${c.pin}`)
.join(", ");
const conns = (n.connections || []).map((c: any) => `${c.component}/${c.pin}`).join(", ");
return ` ${n.name}: ${conns || "(no connections)"}`;
});
return {
@@ -680,8 +660,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
};
}
const lines = wires.map(
(w: any) =>
` (${w.start.x}, ${w.start.y}) → (${w.end.x}, ${w.end.y})`,
(w: any) => ` (${w.start.x}, ${w.start.y}) → (${w.end.x}, ${w.end.y})`,
);
return {
content: [
@@ -718,8 +697,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
};
}
const lines = labels.map(
(l: any) =>
` [${l.type}] ${l.name} at (${l.position.x}, ${l.position.y})`,
(l: any) => ` [${l.type}] ${l.name} at (${l.position.x}, ${l.position.y})`,
);
return {
content: [
@@ -789,10 +767,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
schematicPath: z.string().describe("Path to the .kicad_sch file"),
reference: z.string().describe("Reference designator (e.g., R1, U1)"),
angle: z.number().describe("Rotation angle in degrees (0, 90, 180, 270)"),
mirror: z
.enum(["x", "y"])
.optional()
.describe("Optional mirror axis"),
mirror: z.enum(["x", "y"]).optional().describe("Optional mirror axis"),
},
async (args: {
schematicPath: string;
@@ -836,14 +811,10 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
const annotated = result.annotated || [];
if (annotated.length === 0) {
return {
content: [
{ type: "text", text: "All components are already annotated." },
],
content: [{ type: "text", text: "All components are already annotated." }],
};
}
const lines = annotated.map(
(a: any) => ` ${a.oldReference}${a.newReference}`,
);
const lines = annotated.map((a: any) => ` ${a.oldReference}${a.newReference}`);
return {
content: [
{
@@ -871,12 +842,8 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
"Remove a wire from the schematic by start and end coordinates.",
{
schematicPath: z.string().describe("Path to the .kicad_sch file"),
start: z
.object({ x: z.number(), y: z.number() })
.describe("Wire start position"),
end: z
.object({ x: z.number(), y: z.number() })
.describe("Wire end position"),
start: z.object({ x: z.number(), y: z.number() }).describe("Wire start position"),
end: z.object({ x: z.number(), y: z.number() }).describe("Wire end position"),
},
async (args: {
schematicPath: string;
@@ -953,16 +920,9 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
{
schematicPath: z.string().describe("Path to the .kicad_sch file"),
outputPath: z.string().describe("Output SVG file path"),
blackAndWhite: z
.boolean()
.optional()
.describe("Export in black and white"),
blackAndWhite: z.boolean().optional().describe("Export in black and white"),
},
async (args: {
schematicPath: string;
outputPath: string;
blackAndWhite?: boolean;
}) => {
async (args: { schematicPath: string; outputPath: string; blackAndWhite?: boolean }) => {
const result = await callKicadScript("export_schematic_svg", args);
if (result.success) {
return {
@@ -993,16 +953,9 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
{
schematicPath: z.string().describe("Path to the .kicad_sch file"),
outputPath: z.string().describe("Output PDF file path"),
blackAndWhite: z
.boolean()
.optional()
.describe("Export in black and white"),
blackAndWhite: z.boolean().optional().describe("Export in black and white"),
},
async (args: {
schematicPath: string;
outputPath: string;
blackAndWhite?: boolean;
}) => {
async (args: { schematicPath: string; outputPath: string; blackAndWhite?: boolean }) => {
const result = await callKicadScript("export_schematic_pdf", args);
if (result.success) {
return {
@@ -1032,10 +985,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
"Return a rasterized image of the schematic (PNG by default, or SVG). Uses kicad-cli to export SVG, then converts to PNG via cairosvg. Use this for visual feedback after placing or wiring components.",
{
schematicPath: z.string().describe("Path to the .kicad_sch file"),
format: z
.enum(["png", "svg"])
.optional()
.describe("Output format (default: png)"),
format: z.enum(["png", "svg"]).optional().describe("Output format (default: png)"),
width: z.number().optional().describe("Image width in pixels (default: 1200)"),
height: z.number().optional().describe("Image height in pixels (default: 900)"),
},
@@ -1086,17 +1036,13 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
"run_erc",
"Runs the KiCAD Electrical Rules Check (ERC) on a schematic and returns all violations. Use after wiring to verify the schematic before generating a netlist.",
{
schematicPath: z
.string()
.describe("Path to the .kicad_sch schematic file"),
schematicPath: z.string().describe("Path to the .kicad_sch schematic file"),
},
async (args: { schematicPath: string }) => {
const result = await callKicadScript("run_erc", args);
if (result.success) {
const violations: any[] = result.violations || [];
const lines: string[] = [
`ERC result: ${violations.length} violation(s)`,
];
const lines: string[] = [`ERC result: ${violations.length} violation(s)`];
if (result.summary?.by_severity) {
const s = result.summary.by_severity;
lines.push(
@@ -1183,12 +1129,8 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
"sync_schematic_to_board",
"Import the schematic netlist into the PCB board — equivalent to pressing F8 in KiCAD (Tools → Update PCB from Schematic). MUST be called after the schematic is complete and before placing or routing components on the PCB. Without this step, the board has no footprints and no net assignments — place_component and route_pad_to_pad will produce an empty, unroutable board.",
{
schematicPath: z
.string()
.describe("Absolute path to the .kicad_sch schematic file"),
boardPath: z
.string()
.describe("Absolute path to the .kicad_pcb board file"),
schematicPath: z.string().describe("Absolute path to the .kicad_sch schematic file"),
boardPath: z.string().describe("Absolute path to the .kicad_pcb board file"),
},
async (args: { schematicPath: string; boardPath: string }) => {
const result = await callKicadScript("sync_schematic_to_board", args);
@@ -1218,8 +1160,13 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
},
async (args: {
schematicPath: string;
x1: number; y1: number; x2: number; y2: number;
format?: string; width?: number; height?: number;
x1: number;
y1: number;
x2: number;
y2: number;
format?: string;
width?: number;
height?: number;
}) => {
const result = await callKicadScript("get_schematic_view_region", args);
if (result.success && result.imageData) {
@@ -1227,11 +1174,13 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
return { content: [{ type: "text", text: result.imageData }] };
}
return {
content: [{
content: [
{
type: "image",
data: result.imageData,
mimeType: "image/png",
}],
},
],
};
}
return {
@@ -1240,14 +1189,18 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
},
);
// Find overlapping elements
server.tool(
"find_overlapping_elements",
"Detect spatially overlapping symbols, wires, and labels in the schematic. Finds duplicate power symbols at the same position, collinear overlapping wires, and labels stacked on top of each other.",
{
schematicPath: z.string().describe("Path to the .kicad_sch schematic file"),
tolerance: z.number().optional().describe("Distance threshold in mm for label proximity and wire collinearity checks. Symbol overlap uses bounding-box intersection. (default: 0.5)"),
tolerance: z
.number()
.optional()
.describe(
"Distance threshold in mm for label proximity and wire collinearity checks. Symbol overlap uses bounding-box intersection. (default: 0.5)",
),
},
async (args: { schematicPath: string; tolerance?: number }) => {
const result = await callKicadScript("find_overlapping_elements", args);
@@ -1259,7 +1212,9 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
if (syms.length) {
lines.push(`\nOverlapping symbols (${syms.length}):`);
syms.slice(0, 20).forEach((o: any) => {
lines.push(` ${o.element1.reference}${o.element2.reference} (${o.distance}mm) [${o.type}]`);
lines.push(
` ${o.element1.reference}${o.element2.reference} (${o.distance}mm) [${o.type}]`,
);
});
}
if (lbls.length) {
@@ -1271,7 +1226,9 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
if (wires.length) {
lines.push(`\nOverlapping wires (${wires.length}):`);
wires.slice(0, 20).forEach((o: any) => {
lines.push(` wire @ (${o.wire1.start.x},${o.wire1.start.y})→(${o.wire1.end.x},${o.wire1.end.y}) overlaps with another`);
lines.push(
` wire @ (${o.wire1.start.x},${o.wire1.start.y})→(${o.wire1.end.x},${o.wire1.end.y}) overlaps with another`,
);
});
}
return { content: [{ type: "text", text: lines.join("\n") }] };
@@ -1293,20 +1250,21 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
x2: z.number().describe("Right X coordinate of the region in mm"),
y2: z.number().describe("Bottom Y coordinate of the region in mm"),
},
async (args: {
schematicPath: string;
x1: number; y1: number; x2: number; y2: number;
}) => {
async (args: { schematicPath: string; x1: number; y1: number; x2: number; y2: number }) => {
const result = await callKicadScript("get_elements_in_region", args);
if (result.success) {
const c = result.counts;
const lines = [`Region (${args.x1},${args.y1})→(${args.x2},${args.y2}): ${c.symbols} symbols, ${c.wires} wires, ${c.labels} labels`];
const lines = [
`Region (${args.x1},${args.y1})→(${args.x2},${args.y2}): ${c.symbols} symbols, ${c.wires} wires, ${c.labels} labels`,
];
const syms: any[] = result.symbols || [];
if (syms.length) {
lines.push("\nSymbols:");
syms.forEach((s: any) => {
const pinCount = s.pins ? Object.keys(s.pins).length : 0;
lines.push(` ${s.reference} (${s.libId}) @ (${s.position.x}, ${s.position.y}) [${pinCount} pins]`);
lines.push(
` ${s.reference} (${s.libId}) @ (${s.position.x}, ${s.position.y}) [${pinCount} pins]`,
);
});
}
const wires: any[] = result.wires || [];
@@ -1346,7 +1304,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
const lines = [`Found ${collisions.length} wire(s) crossing symbols:`];
collisions.slice(0, 30).forEach((c: any, i: number) => {
lines.push(
` ${i + 1}. Wire (${c.wire.start.x},${c.wire.start.y})→(${c.wire.end.x},${c.wire.end.y}) crosses ${c.component.reference} (${c.component.libId})`
` ${i + 1}. Wire (${c.wire.start.x},${c.wire.start.y})→(${c.wire.end.x},${c.wire.end.y}) crosses ${c.component.reference} (${c.component.libId})`,
);
});
if (collisions.length > 30) lines.push(` ... and ${collisions.length - 30} more`);

View File

@@ -15,45 +15,67 @@ const PinSchema = z.object({
number: z.union([z.string(), z.number()]).describe("Pin number, e.g. '1', '2', 'A1'"),
type: z
.enum([
"input", "output", "bidirectional", "tri_state", "passive",
"free", "unspecified", "power_in", "power_out",
"open_collector", "open_emitter", "no_connect",
"input",
"output",
"bidirectional",
"tri_state",
"passive",
"free",
"unspecified",
"power_in",
"power_out",
"open_collector",
"open_emitter",
"no_connect",
])
.describe("Electrical pin type"),
at: z.object({
at: z
.object({
x: z.number().describe("X position in mm"),
y: z.number().describe("Y position in mm"),
angle: z.number().describe(
"Direction the pin wire extends FROM the symbol body: 0=right, 90=up, 180=left, 270=down"
angle: z
.number()
.describe(
"Direction the pin wire extends FROM the symbol body: 0=right, 90=up, 180=left, 270=down",
),
}).describe("Pin endpoint position (where the wire connects)"),
})
.describe("Pin endpoint position (where the wire connects)"),
length: z.number().optional().describe("Pin length in mm (default 2.54)"),
shape: z
.enum(["line", "inverted", "clock", "inverted_clock", "input_low",
"clock_low", "output_low", "falling_edge_clock", "non_logic"])
.enum([
"line",
"inverted",
"clock",
"inverted_clock",
"input_low",
"clock_low",
"output_low",
"falling_edge_clock",
"non_logic",
])
.optional()
.describe("Pin graphic shape (default: line)"),
});
const RectSchema = z.object({
x1: z.number(), y1: z.number(),
x2: z.number(), y2: z.number(),
x1: z.number(),
y1: z.number(),
x2: z.number(),
y2: z.number(),
width: z.number().optional().describe("Stroke width in mm (default 0.254)"),
fill: z.enum(["none", "outline", "background"]).optional()
fill: z
.enum(["none", "outline", "background"])
.optional()
.describe("Fill type (default: background)"),
});
const PolylineSchema = z.object({
points: z.array(z.object({ x: z.number(), y: z.number() }))
.describe("List of XY points in mm"),
points: z.array(z.object({ x: z.number(), y: z.number() })).describe("List of XY points in mm"),
width: z.number().optional().describe("Stroke width in mm (default 0.254)"),
fill: z.enum(["none", "outline", "background"]).optional(),
});
export function registerSymbolCreatorTools(
server: McpServer,
callKicadScript: Function,
) {
export function registerSymbolCreatorTools(server: McpServer, callKicadScript: Function) {
// ── create_symbol ────────────────────────────────────────────────────── //
server.tool(
"create_symbol",
@@ -68,14 +90,14 @@ export function registerSymbolCreatorTools(
"- Pins on bottom: at.y = body_bottom - length, angle=90 (wire goes up)\n" +
"- Standard pin length: 2.54 mm, standard grid: 2.54 mm",
{
libraryPath: z
.string()
.describe("Path to the .kicad_sym file (created if missing)"),
libraryPath: z.string().describe("Path to the .kicad_sym file (created if missing)"),
name: z.string().describe("Symbol name, e.g. 'TMC2209', 'MyOpAmp'"),
referencePrefix: z
.string()
.optional()
.describe("Schematic reference prefix: 'U' (IC), 'R' (resistor), 'J' (connector), etc. Default: 'U'"),
.describe(
"Schematic reference prefix: 'U' (IC), 'R' (resistor), 'J' (connector), etc. Default: 'U'",
),
description: z.string().optional().describe("Human-readable description"),
keywords: z.string().optional().describe("Space-separated search keywords"),
datasheet: z.string().optional().describe("Datasheet URL or '~'"),
@@ -161,9 +183,7 @@ export function registerSymbolCreatorTools(
"Register a .kicad_sym library in KiCAD's sym-lib-table so symbols can be used in schematics. " +
"Run this after create_symbol when KiCAD shows 'library not found'.",
{
libraryPath: z
.string()
.describe("Full path to the .kicad_sym file"),
libraryPath: z.string().describe("Full path to the .kicad_sym file"),
libraryName: z
.string()
.optional()

View File

@@ -2,27 +2,24 @@
* UI/Process management tools for KiCAD MCP server
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
export function registerUITools(server: McpServer, callKicadScript: Function) {
// Check if KiCAD UI is running
server.tool(
"check_kicad_ui",
"Check if KiCAD UI is currently running",
{},
async () => {
logger.info('Checking KiCAD UI status');
server.tool("check_kicad_ui", "Check if KiCAD UI is currently running", {}, async () => {
logger.info("Checking KiCAD UI status");
const result = await callKicadScript("check_kicad_ui", {});
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
);
});
// Launch KiCAD UI
server.tool(
@@ -30,19 +27,26 @@ export function registerUITools(server: McpServer, callKicadScript: Function) {
"Launch KiCAD UI, optionally with a project file",
{
projectPath: z.string().optional().describe("Optional path to .kicad_pcb file to open"),
autoLaunch: z.boolean().optional().describe("Whether to launch KiCAD if not running (default: true)")
autoLaunch: z
.boolean()
.optional()
.describe("Whether to launch KiCAD if not running (default: true)"),
},
async (args: { projectPath?: string; autoLaunch?: boolean }) => {
logger.info(`Launching KiCAD UI${args.projectPath ? ' with project: ' + args.projectPath : ''}`);
logger.info(
`Launching KiCAD UI${args.projectPath ? " with project: " + args.projectPath : ""}`,
);
const result = await callKicadScript("launch_kicad_ui", args);
return {
content: [{
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}]
text: JSON.stringify(result, null, 2),
},
],
};
}
},
);
logger.info('UI management tools registered');
logger.info("UI management tools registered");
}

View File

@@ -11,11 +11,13 @@
*/
export function createJsonResponse(data: any, uri?: string) {
return {
contents: [{
contents: [
{
uri: uri || "data:application/json",
mimeType: "application/json",
text: JSON.stringify(data, null, 2)
}]
text: JSON.stringify(data, null, 2),
},
],
};
}
@@ -28,14 +30,16 @@ export function createJsonResponse(data: any, uri?: string) {
* @returns MCP resource response object
*/
export function createBinaryResponse(data: Buffer | string, mimeType: string, uri?: string) {
const blob = typeof data === 'string' ? data : data.toString('base64');
const blob = typeof data === "string" ? data : data.toString("base64");
return {
contents: [{
contents: [
{
uri: uri || `data:${mimeType}`,
mimeType: mimeType,
blob: blob
}]
blob: blob,
},
],
};
}
@@ -49,13 +53,19 @@ export function createBinaryResponse(data: Buffer | string, mimeType: string, ur
*/
export function createErrorResponse(error: string, details?: string, uri?: string) {
return {
contents: [{
contents: [
{
uri: uri || "data:application/json",
mimeType: "application/json",
text: JSON.stringify({
text: JSON.stringify(
{
error,
details
}, null, 2)
}]
details,
},
null,
2,
),
},
],
};
}