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

@@ -1,194 +1,194 @@
name: CI/CD Pipeline name: CI/CD Pipeline
on: on:
push: push:
branches: [ main, develop ] branches: [main, develop]
pull_request: pull_request:
branches: [ main, develop ] branches: [main, develop]
jobs: jobs:
# TypeScript/Node.js tests # TypeScript/Node.js tests
typescript-tests: typescript-tests:
name: TypeScript Build & Test name: TypeScript Build & Test
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-24.04, ubuntu-22.04, windows-latest, macos-latest] os: [ubuntu-24.04, ubuntu-22.04, windows-latest, macos-latest]
node-version: [18.x, 20.x, 22.x] node-version: [18.x, 20.x, 22.x]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }} - name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'npm' cache: "npm"
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Run TypeScript compiler - name: Run TypeScript compiler
run: npm run build run: npm run build
- name: Run linter - name: Run linter
run: npm run lint || echo "Linter not configured yet" run: npm run lint || echo "Linter not configured yet"
- name: Run tests - name: Run tests
run: npm test || echo "Tests not configured yet" run: npm test || echo "Tests not configured yet"
# Python tests # Python tests
python-tests: python-tests:
name: Python Tests name: Python Tests
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-24.04, ubuntu-22.04] os: [ubuntu-24.04, ubuntu-22.04]
python-version: ['3.10', '3.11', '3.12'] python-version: ["3.10", "3.11", "3.12"]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python-version }} - name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
cache: 'pip' cache: "pip"
- name: Install Python dependencies - name: Install Python dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install pytest pytest-cov black mypy pylint pip install pytest pytest-cov black mypy pylint
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
- name: Run Black formatter check - name: Run Black formatter check
run: black --check python/ || echo "Black not configured yet" run: black --check python/ || echo "Black not configured yet"
- name: Run MyPy type checker - name: Run MyPy type checker
run: mypy python/ || echo "MyPy not configured yet" run: mypy python/ || echo "MyPy not configured yet"
- name: Run Pylint - name: Run Pylint
run: pylint python/ || echo "Pylint not configured yet" run: pylint python/ || echo "Pylint not configured yet"
- name: Run pytest - name: Run pytest
run: pytest python/ --cov=python --cov-report=xml || echo "Tests not configured yet" run: pytest python/ --cov=python --cov-report=xml || echo "Tests not configured yet"
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v4
with: with:
file: ./coverage.xml file: ./coverage.xml
flags: python flags: python
name: python-${{ matrix.python-version }} name: python-${{ matrix.python-version }}
if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-24.04' if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-24.04'
# Integration tests (requires KiCAD) # Integration tests (requires KiCAD)
integration-tests: integration-tests:
name: Integration Tests (Linux + KiCAD) name: Integration Tests (Linux + KiCAD)
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20.x' node-version: "20.x"
cache: 'npm' cache: "npm"
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.12' python-version: "3.12"
cache: 'pip' cache: "pip"
- name: Add KiCAD PPA and Install KiCAD 9.0 - name: Add KiCAD PPA and Install KiCAD 9.0
run: | run: |
sudo add-apt-repository --yes ppa:kicad/kicad-9.0-releases sudo add-apt-repository --yes ppa:kicad/kicad-9.0-releases
sudo apt-get update sudo apt-get update
sudo apt-get install -y kicad kicad-libraries sudo apt-get install -y kicad kicad-libraries
- name: Verify KiCAD installation - name: Verify KiCAD installation
run: | run: |
kicad-cli version || echo "kicad-cli not found" kicad-cli version || echo "kicad-cli not found"
python3 -c "import pcbnew; print(f'pcbnew version: {pcbnew.GetBuildVersion()}')" || echo "pcbnew module not found" python3 -c "import pcbnew; print(f'pcbnew version: {pcbnew.GetBuildVersion()}')" || echo "pcbnew module not found"
- name: Install dependencies - name: Install dependencies
run: | run: |
npm ci npm ci
pip install -r requirements.txt pip install -r requirements.txt
- name: Build TypeScript - name: Build TypeScript
run: npm run build run: npm run build
- name: Run integration tests - name: Run integration tests
run: | run: |
echo "Integration tests not yet configured" echo "Integration tests not yet configured"
# pytest tests/integration/ # pytest tests/integration/
# Docker build test # Docker build test
docker-build: docker-build:
name: Docker Build Test name: Docker Build Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Build Docker image - name: Build Docker image
run: | run: |
echo "Docker build not yet configured" echo "Docker build not yet configured"
# docker build -t kicad-mcp-server:test . # docker build -t kicad-mcp-server:test .
# Code quality checks # Code quality checks
code-quality: code-quality:
name: Code Quality name: Code Quality
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20.x' node-version: "20.x"
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Run ESLint - name: Run ESLint
run: npx eslint src/ || echo "ESLint not configured yet" run: npx eslint src/ || echo "ESLint not configured yet"
- name: Run Prettier check - name: Run Prettier check
run: npx prettier --check "src/**/*.ts" || echo "Prettier not configured yet" run: npx prettier --check "src/**/*.ts" || echo "Prettier not configured yet"
- name: Check for security vulnerabilities - name: Check for security vulnerabilities
run: npm audit --audit-level=moderate || echo "No critical vulnerabilities" run: npm audit --audit-level=moderate || echo "No critical vulnerabilities"
# Documentation check # Documentation check
docs-check: docs-check:
name: Documentation Check name: Documentation Check
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Check README exists - name: Check README exists
run: test -f README.md run: test -f README.md
- name: Check for broken links in docs - name: Check for broken links in docs
run: | run: |
sudo apt-get install -y linkchecker || true sudo apt-get install -y linkchecker || true
# linkchecker docs/ || echo "Link checker not configured" # linkchecker docs/ || echo "Link checker not configured"
- name: Validate JSON files - name: Validate JSON files
run: | run: |
find . -name "*.json" -not -path "./node_modules/*" -not -path "./dist/*" | xargs -I {} sh -c 'python3 -m json.tool {} > /dev/null && echo "✓ {}" || echo "✗ {}"' find . -name "*.json" -not -path "./node_modules/*" -not -path "./dist/*" | xargs -I {} sh -c 'python3 -m json.tool {} > /dev/null && echo "✓ {}" || echo "✗ {}"'

View File

@@ -7,7 +7,7 @@ repos:
- id: check-yaml - id: check-yaml
- id: check-json - id: check-json
- id: check-added-large-files - id: check-added-large-files
args: ['--maxkb=500'] args: ["--maxkb=500"]
- id: check-merge-conflict - id: check-merge-conflict
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
@@ -22,3 +22,9 @@ repos:
hooks: hooks:
- id: isort - id: isort
args: [--settings-path=pyproject.toml] 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
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,414 +1,418 @@
# Contributing to KiCAD MCP Server # Contributing to KiCAD MCP Server
Thank you for your interest in contributing to the KiCAD MCP Server! This guide will help you get started with development. Thank you for your interest in contributing to the KiCAD MCP Server! This guide will help you get started with development.
## Table of Contents ## Table of Contents
- [Development Environment Setup](#development-environment-setup) - [Development Environment Setup](#development-environment-setup)
- [Project Structure](#project-structure) - [Project Structure](#project-structure)
- [Architecture Overview](#architecture-overview) - [Architecture Overview](#architecture-overview)
- [Development Workflow](#development-workflow) - [Development Workflow](#development-workflow)
- [Testing](#testing) - [Testing](#testing)
- [Code Style](#code-style) - [Code Style](#code-style)
- [Pull Request Process](#pull-request-process) - [Pull Request Process](#pull-request-process)
- [Roadmap & Planning](#roadmap--planning) - [Roadmap & Planning](#roadmap--planning)
--- ---
## Development Environment Setup ## Development Environment Setup
### Prerequisites ### Prerequisites
- **KiCAD 9.0 or higher** - [Download here](https://www.kicad.org/download/) - **KiCAD 9.0 or higher** - [Download here](https://www.kicad.org/download/)
- **Node.js v18+** - [Download here](https://nodejs.org/) - **Node.js v18+** - [Download here](https://nodejs.org/)
- **Python 3.10+** - Should come with KiCAD, or install separately - **Python 3.10+** - Should come with KiCAD, or install separately
- **Git** - For version control - **Git** - For version control
### Platform-Specific Setup ### Platform-Specific Setup
#### Linux (Ubuntu/Debian) #### Linux (Ubuntu/Debian)
```bash ```bash
# Install KiCAD 9.0 from official PPA # Install KiCAD 9.0 from official PPA
sudo add-apt-repository --yes ppa:kicad/kicad-9.0-releases sudo add-apt-repository --yes ppa:kicad/kicad-9.0-releases
sudo apt-get update sudo apt-get update
sudo apt-get install -y kicad kicad-libraries sudo apt-get install -y kicad kicad-libraries
# Install Node.js (if not already installed) # Install Node.js (if not already installed)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs sudo apt-get install -y nodejs
# Clone the repository # Clone the repository
git clone https://github.com/yourusername/kicad-mcp-server.git git clone https://github.com/yourusername/kicad-mcp-server.git
cd kicad-mcp-server cd kicad-mcp-server
# Install Node.js dependencies # Install Node.js dependencies
npm install npm install
# Install Python dependencies # Install Python dependencies
pip3 install -r requirements-dev.txt pip3 install -r requirements-dev.txt
# Build TypeScript # Build TypeScript
npm run build npm run build
# Run tests # Run tests
npm test npm test
pytest pytest
``` ```
#### Windows #### Windows
```powershell ```powershell
# Install KiCAD 9.0 from https://www.kicad.org/download/windows/ # Install KiCAD 9.0 from https://www.kicad.org/download/windows/
# Install Node.js from https://nodejs.org/ # Install Node.js from https://nodejs.org/
# Clone the repository # Clone the repository
git clone https://github.com/yourusername/kicad-mcp-server.git git clone https://github.com/yourusername/kicad-mcp-server.git
cd kicad-mcp-server cd kicad-mcp-server
# Install Node.js dependencies # Install Node.js dependencies
npm install npm install
# Install Python dependencies # Install Python dependencies
pip install -r requirements-dev.txt pip install -r requirements-dev.txt
# Build TypeScript # Build TypeScript
npm run build npm run build
# Run tests # Run tests
npm test npm test
pytest pytest
``` ```
#### macOS #### macOS
```bash ```bash
# Install KiCAD 9.0 from https://www.kicad.org/download/macos/ # Install KiCAD 9.0 from https://www.kicad.org/download/macos/
# Install Node.js via Homebrew # Install Node.js via Homebrew
brew install node brew install node
# Clone the repository # Clone the repository
git clone https://github.com/yourusername/kicad-mcp-server.git git clone https://github.com/yourusername/kicad-mcp-server.git
cd kicad-mcp-server cd kicad-mcp-server
# Install Node.js dependencies # Install Node.js dependencies
npm install npm install
# Install Python dependencies # Install Python dependencies
pip3 install -r requirements-dev.txt pip3 install -r requirements-dev.txt
# Build TypeScript # Build TypeScript
npm run build npm run build
# Run tests # Run tests
npm test npm test
pytest pytest
``` ```
--- ---
## Project Structure ## Project Structure
``` ```
kicad-mcp-server/ kicad-mcp-server/
├── .github/ ├── .github/
│ └── workflows/ # CI/CD pipelines │ └── workflows/ # CI/CD pipelines
├── config/ # Configuration examples ├── config/ # Configuration examples
│ ├── linux-config.example.json │ ├── linux-config.example.json
│ ├── windows-config.example.json │ ├── windows-config.example.json
│ └── macos-config.example.json │ └── macos-config.example.json
├── docs/ # Documentation ├── docs/ # Documentation
├── python/ # Python interface layer ├── python/ # Python interface layer
│ ├── commands/ # KiCAD command handlers │ ├── commands/ # KiCAD command handlers
│ ├── integrations/ # External API integrations (JLCPCB, Digikey) │ ├── integrations/ # External API integrations (JLCPCB, Digikey)
│ ├── utils/ # Utility modules │ ├── utils/ # Utility modules
│ └── kicad_interface.py # Main Python entry point │ └── kicad_interface.py # Main Python entry point
├── src/ # TypeScript MCP server ├── src/ # TypeScript MCP server
│ ├── tools/ # MCP tool implementations │ ├── tools/ # MCP tool implementations
│ ├── resources/ # MCP resource implementations │ ├── resources/ # MCP resource implementations
│ ├── prompts/ # MCP prompt implementations │ ├── prompts/ # MCP prompt implementations
│ └── server.ts # Main server │ └── server.ts # Main server
├── tests/ # Test suite ├── tests/ # Test suite
│ ├── unit/ │ ├── unit/
│ ├── integration/ │ ├── integration/
│ └── fixtures/ │ └── fixtures/
├── dist/ # Compiled JavaScript (generated) ├── dist/ # Compiled JavaScript (generated)
├── node_modules/ # Node dependencies (generated) ├── node_modules/ # Node dependencies (generated)
├── package.json # Node.js configuration ├── package.json # Node.js configuration
├── tsconfig.json # TypeScript configuration ├── tsconfig.json # TypeScript configuration
├── pytest.ini # Pytest configuration ├── pytest.ini # Pytest configuration
├── requirements.txt # Python production dependencies ├── requirements.txt # Python production dependencies
└── requirements-dev.txt # Python dev dependencies └── requirements-dev.txt # Python dev dependencies
``` ```
--- ---
## Architecture Overview ## Architecture Overview
The KiCAD MCP Server is organized into several key components: The KiCAD MCP Server is organized into several key components:
- **TypeScript MCP Server** (`src/`) - Handles MCP protocol communication and tool routing - **TypeScript MCP Server** (`src/`) - Handles MCP protocol communication and tool routing
- **Python KiCAD Interface** (`python/`) - Interfaces with KiCAD's Python API (pcbnew) - **Python KiCAD Interface** (`python/`) - Interfaces with KiCAD's Python API (pcbnew)
- **Tool Router** - Organizes 122+ tools into 8 discoverable categories - **Tool Router** - Organizes 122+ tools into 8 discoverable categories
- **Resource System** - Provides dynamic project/board state information - **Resource System** - Provides dynamic project/board state information
- **Prompt System** - Offers context-aware design prompts - **Prompt System** - Offers context-aware design prompts
**Current Tool Count:** 122+ tools across 8 categories (direct + routed) **Current Tool Count:** 122+ tools across 8 categories (direct + routed)
For detailed architecture information, see `docs/ROUTER_ARCHITECTURE.md`. For detailed architecture information, see `docs/ROUTER_ARCHITECTURE.md`.
--- ---
## Development Workflow ## Development Workflow
### 1. Create a Feature Branch ### 1. Create a Feature Branch
```bash ```bash
git checkout -b feature/your-feature-name git checkout -b feature/your-feature-name
``` ```
### 2. Make Changes ### 2. Make Changes
- Edit TypeScript files in `src/` - Edit TypeScript files in `src/`
- Edit Python files in `python/` - Edit Python files in `python/`
- Add tests for new features - Add tests for new features
### 3. Build & Test ### 3. Build & Test
```bash ```bash
# Build TypeScript # Build TypeScript
npm run build npm run build
# Run TypeScript linter # Run TypeScript linter
npm run lint npm run lint
# Run Python formatter # Run Python formatter
black python/ black python/
# Run Python type checker # Run Python type checker
mypy python/ mypy python/
# Run all tests # Run all tests
npm test npm test
pytest pytest
# Run specific test file # Run specific test file
pytest tests/test_platform_helper.py -v pytest tests/test_platform_helper.py -v
# Run with coverage # Run with coverage
pytest --cov=python --cov-report=html pytest --cov=python --cov-report=html
``` ```
### 4. Commit Changes ### 4. Commit Changes
```bash ```bash
git add . git add .
git commit -m "feat: Add your feature description" git commit -m "feat: Add your feature description"
``` ```
**Commit Message Convention:** **Commit Message Convention:**
- `feat:` - New feature
- `fix:` - Bug fix - `feat:` - New feature
- `docs:` - Documentation changes - `fix:` - Bug fix
- `test:` - Adding/updating tests - `docs:` - Documentation changes
- `refactor:` - Code refactoring - `test:` - Adding/updating tests
- `chore:` - Maintenance tasks - `refactor:` - Code refactoring
- `chore:` - Maintenance tasks
### 5. Push and Create Pull Request
### 5. Push and Create Pull Request
```bash
git push origin feature/your-feature-name ```bash
``` git push origin feature/your-feature-name
```
Then create a Pull Request on GitHub.
Then create a Pull Request on GitHub.
---
---
## Testing
## Testing
### Running Tests
### Running Tests
```bash
# All tests ```bash
pytest # All tests
pytest
# Unit tests only
pytest -m unit # Unit tests only
pytest -m unit
# Integration tests (requires KiCAD installed)
pytest -m integration # Integration tests (requires KiCAD installed)
pytest -m integration
# Platform-specific tests
pytest -m linux # Linux tests only # Platform-specific tests
pytest -m windows # Windows tests only pytest -m linux # Linux tests only
pytest -m windows # Windows tests only
# With coverage report
pytest --cov=python --cov-report=term-missing # With coverage report
pytest --cov=python --cov-report=term-missing
# Verbose output
pytest -v # Verbose output
pytest -v
# Stop on first failure
pytest -x # Stop on first failure
``` pytest -x
```
### Writing Tests
### Writing Tests
Tests should be placed in `tests/` directory:
Tests should be placed in `tests/` directory:
```python
# tests/test_my_feature.py ```python
import pytest # tests/test_my_feature.py
import pytest
@pytest.mark.unit
def test_my_feature(): @pytest.mark.unit
"""Test description""" def test_my_feature():
# Arrange """Test description"""
expected = "result" # Arrange
expected = "result"
# Act
result = my_function() # Act
result = my_function()
# Assert
assert result == expected # Assert
assert result == expected
@pytest.mark.integration
@pytest.mark.linux @pytest.mark.integration
def test_linux_integration(): @pytest.mark.linux
"""Integration test for Linux""" def test_linux_integration():
# This test will only run on Linux in CI """Integration test for Linux"""
pass # This test will only run on Linux in CI
``` pass
```
---
---
## Code Style
## Code Style
### Python
### Python
We use **Black** for code formatting and **MyPy** for type checking.
We use **Black** for code formatting and **MyPy** for type checking.
```bash
# Format all Python files ```bash
black python/ # Format all Python files
black python/
# Check types
mypy python/ # Check types
mypy python/
# Run linter
pylint python/ # Run linter
``` pylint python/
```
**Python Style Guidelines:**
- Use type hints for all function signatures **Python Style Guidelines:**
- Use pathlib.Path for file paths (not os.path)
- Use descriptive variable names - Use type hints for all function signatures
- Add docstrings to all public functions/classes - Use pathlib.Path for file paths (not os.path)
- Follow PEP 8 - Use descriptive variable names
- Add docstrings to all public functions/classes
**Example:** - Follow PEP 8
```python
from pathlib import Path **Example:**
from typing import List, Optional
```python
def find_kicad_libraries(search_path: Path) -> List[Path]: from pathlib import Path
""" from typing import List, Optional
Find all KiCAD symbol libraries in the given path.
def find_kicad_libraries(search_path: Path) -> List[Path]:
Args: """
search_path: Directory to search for .kicad_sym files Find all KiCAD symbol libraries in the given path.
Returns: Args:
List of paths to found library files search_path: Directory to search for .kicad_sym files
Raises: Returns:
ValueError: If search_path doesn't exist List of paths to found library files
"""
if not search_path.exists(): Raises:
raise ValueError(f"Search path does not exist: {search_path}") ValueError: If search_path doesn't exist
"""
return list(search_path.glob("**/*.kicad_sym")) if not search_path.exists():
``` raise ValueError(f"Search path does not exist: {search_path}")
### TypeScript return list(search_path.glob("**/*.kicad_sym"))
```
We use **ESLint** and **Prettier** for TypeScript.
### TypeScript
```bash
# Format TypeScript files We use **ESLint** and **Prettier** for TypeScript.
npx prettier --write "src/**/*.ts"
```bash
# Run linter # Format TypeScript files
npx eslint src/ npx prettier --write "src/**/*.ts"
```
# Run linter
**TypeScript Style Guidelines:** npx eslint src/
- Use interfaces for data structures ```
- Use async/await for asynchronous code
- Use descriptive variable names **TypeScript Style Guidelines:**
- Add JSDoc comments to exported functions
- Use interfaces for data structures
--- - Use async/await for asynchronous code
- Use descriptive variable names
## Pull Request Process - Add JSDoc comments to exported functions
1. **Update Documentation** - If you change functionality, update docs ---
2. **Add Tests** - All new features should have tests
3. **Run CI Locally** - Ensure all tests pass before pushing ## Pull Request Process
4. **Create PR** - Use a clear, descriptive title
5. **Request Review** - Tag relevant maintainers 1. **Update Documentation** - If you change functionality, update docs
6. **Address Feedback** - Make requested changes 2. **Add Tests** - All new features should have tests
7. **Merge** - Maintainer will merge when approved 3. **Run CI Locally** - Ensure all tests pass before pushing
4. **Create PR** - Use a clear, descriptive title
### PR Checklist 5. **Request Review** - Tag relevant maintainers
6. **Address Feedback** - Make requested changes
- [ ] Code follows style guidelines 7. **Merge** - Maintainer will merge when approved
- [ ] All tests pass locally
- [ ] New tests added for new features ### PR Checklist
- [ ] Documentation updated
- [ ] Commit messages follow convention - [ ] Code follows style guidelines
- [ ] No merge conflicts - [ ] All tests pass locally
- [ ] CI/CD pipeline passes - [ ] New tests added for new features
- [ ] Documentation updated
--- - [ ] Commit messages follow convention
- [ ] No merge conflicts
## Roadmap & Planning - [ ] CI/CD pipeline passes
We track work using GitHub Projects and Issues: ---
- **GitHub Projects** - High-level roadmap and sprints ## Roadmap & Planning
- **GitHub Issues** - Specific bugs and features
- **GitHub Discussions** - Design discussions and proposals We track work using GitHub Projects and Issues:
### Current Priorities (Week 1-4) - **GitHub Projects** - High-level roadmap and sprints
- **GitHub Issues** - Specific bugs and features
1. ✅ Linux compatibility fixes - **GitHub Discussions** - Design discussions and proposals
2. ✅ Platform-agnostic path handling
3. ✅ CI/CD pipeline setup ### Current Priorities (Week 1-4)
4. 🔄 Migrate to KiCAD IPC API
5. ⏳ Add JLCPCB integration 1. ✅ Linux compatibility fixes
6. ⏳ Add Digikey integration 2. ✅ Platform-agnostic path handling
3. ✅ CI/CD pipeline setup
See [docs/REBUILD_PLAN.md](docs/REBUILD_PLAN.md) for the complete 12-week roadmap. 4. 🔄 Migrate to KiCAD IPC API
5. ⏳ Add JLCPCB integration
--- 6. ⏳ Add Digikey integration
## Getting Help See [docs/REBUILD_PLAN.md](docs/REBUILD_PLAN.md) for the complete 12-week roadmap.
- **GitHub Discussions** - Ask questions, propose ideas ---
- **GitHub Issues** - Report bugs, request features
- **Discord** - Real-time chat (link TBD) ## Getting Help
--- - **GitHub Discussions** - Ask questions, propose ideas
- **GitHub Issues** - Report bugs, request features
## License - **Discord** - Real-time chat (link TBD)
By contributing, you agree that your contributions will be licensed under the MIT License. ---
--- ## License
## Thank You! 🎉 By contributing, you agree that your contributions will be licensed under the MIT License.
Your contributions make this project better for everyone. We appreciate your time and effort! ---
## Thank You! 🎉
Your contributions make this project better for everyone. We appreciate your time and effort!

2035
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,15 @@
{ {
"mcpServers": { "mcpServers": {
"kicad": { "kicad": {
"command": "node", "command": "node",
"args": ["/home/YOUR_USERNAME/MCP/KiCAD-MCP-Server/dist/index.js"], "args": ["/home/YOUR_USERNAME/MCP/KiCAD-MCP-Server/dist/index.js"],
"env": { "env": {
"NODE_ENV": "production", "NODE_ENV": "production",
"PYTHONPATH": "/usr/share/kicad/scripting/plugins:/usr/lib/kicad/lib/python3/dist-packages", "PYTHONPATH": "/usr/share/kicad/scripting/plugins:/usr/lib/kicad/lib/python3/dist-packages",
"LOG_LEVEL": "info", "LOG_LEVEL": "info",
"KICAD_AUTO_LAUNCH": "false" "KICAD_AUTO_LAUNCH": "false"
}, },
"description": "KiCAD PCB Design Assistant - Note: PYTHONPATH auto-detected if venv exists" "description": "KiCAD PCB Design Assistant - Note: PYTHONPATH auto-detected if venv exists"
} }
} }
} }

View File

@@ -1,15 +1,15 @@
{ {
"mcpServers": { "mcpServers": {
"kicad": { "kicad": {
"command": "node", "command": "node",
"args": ["/Users/YOUR_USERNAME/MCP/KiCAD-MCP-Server/dist/index.js"], "args": ["/Users/YOUR_USERNAME/MCP/KiCAD-MCP-Server/dist/index.js"],
"env": { "env": {
"NODE_ENV": "production", "NODE_ENV": "production",
"PYTHONPATH": "/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/Current/lib/python3.11/site-packages", "PYTHONPATH": "/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/Current/lib/python3.11/site-packages",
"LOG_LEVEL": "info", "LOG_LEVEL": "info",
"KICAD_AUTO_LAUNCH": "false" "KICAD_AUTO_LAUNCH": "false"
}, },
"description": "KiCAD PCB Design Assistant - Note: PYTHONPATH auto-detected if venv exists" "description": "KiCAD PCB Design Assistant - Note: PYTHONPATH auto-detected if venv exists"
} }
} }
} }

View File

@@ -1,16 +1,16 @@
{ {
"mcpServers": { "mcpServers": {
"kicad": { "kicad": {
"command": "node", "command": "node",
"args": ["C:\\Users\\YOUR_USERNAME\\MCP\\KiCAD-MCP-Server\\dist\\index.js"], "args": ["C:\\Users\\YOUR_USERNAME\\MCP\\KiCAD-MCP-Server\\dist\\index.js"],
"env": { "env": {
"NODE_ENV": "production", "NODE_ENV": "production",
"PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\bin\\Lib\\site-packages", "PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\bin\\Lib\\site-packages",
"LOG_LEVEL": "info", "LOG_LEVEL": "info",
"KICAD_AUTO_LAUNCH": "false", "KICAD_AUTO_LAUNCH": "false",
"KICAD_MCP_DEV": "0" "KICAD_MCP_DEV": "0"
}, },
"description": "KiCAD PCB Design Assistant - Note: PYTHONPATH auto-detected if venv exists" "description": "KiCAD PCB Design Assistant - Note: PYTHONPATH auto-detected if venv exists"
} }
} }
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -12,63 +12,63 @@ 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. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | --------------- | ------- | -------- | ------------------------------------------------------------------------------------------ |
| `libraryPath` | string | Yes | Path to the .pretty library directory (created if missing). E.g. C:/MyProject/MyLib.pretty | | `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' | | `name` | string | Yes | Footprint name, e.g. 'R_0603_Custom' |
| `description` | string | No | Human-readable description | | `description` | string | No | Human-readable description |
| `tags` | string | No | Space-separated tag string, e.g. 'resistor SMD 0603' | | `tags` | string | No | Space-separated tag string, e.g. 'resistor SMD 0603' |
| `pads` | array | No | List of pad objects (see Pad Schema below). Can be empty for outlines-only footprints | | `pads` | array | No | List of pad objects (see Pad Schema below). Can be empty for outlines-only footprints |
| `courtyard` | object | No | Courtyard rectangle on F.CrtYd (recommended: 0.25 mm clearance around pads) | | `courtyard` | object | No | Courtyard rectangle on F.CrtYd (recommended: 0.25 mm clearance around pads) |
| `silkscreen` | object | No | Silkscreen rectangle on F.SilkS | | `silkscreen` | object | No | Silkscreen rectangle on F.SilkS |
| `fabLayer` | object | No | Fab-layer rectangle on F.Fab (shows component body) | | `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) | | `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) | | `overwrite` | boolean | No | Replace existing footprint file (default: false) |
#### Pad Schema #### Pad Schema
Each pad object in the `pads` array supports: Each pad object in the `pads` array supports:
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ----------------- | ---------------- | -------- | -------------------------------------------------------------------------------------------- |
| `number` | string | Yes | Pad number / name, e.g. '1', '2', 'A1' | | `number` | string | Yes | Pad number / name, e.g. '1', '2', 'A1' |
| `type` | enum | Yes | Pad type: `smd`, `thru_hole`, or `np_thru_hole` | | `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) | | `shape` | enum | No | Pad shape: `rect`, `circle`, `oval`, or `roundrect` (default: rect for SMD, circle for THT) |
| `at` | object | Yes | Pad centre position: {x: number, y: number, angle?: number} in mm | | `at` | object | Yes | Pad centre position: {x: number, y: number, angle?: number} in mm |
| `size` | object | Yes | Pad size: {w: number, h: number} in mm | | `size` | object | Yes | Pad size: {w: number, h: number} in mm |
| `drill` | number or object | No | Round drill diameter (mm) or oval drill {w: number, h: number} (required for thru_hole pads) | | `drill` | number or object | No | Round drill diameter (mm) or oval drill {w: number, h: number} (required for thru_hole pads) |
| `layers` | array | No | Override default layer list, e.g. ['F.Cu','F.Paste','F.Mask'] | | `layers` | array | No | Override default layer list, e.g. ['F.Cu','F.Paste','F.Mask'] |
| `roundrect_ratio` | number | No | Corner radius ratio for roundrect shape (0.0-0.5, default 0.25) | | `roundrect_ratio` | number | No | Corner radius ratio for roundrect shape (0.0-0.5, default 0.25) |
#### Rectangle Schema (courtyard, silkscreen, fabLayer) #### Rectangle Schema (courtyard, silkscreen, fabLayer)
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | --------- | ------ | -------- | ---------------- |
| `x1` | number | Yes | Left X in mm | | `x1` | number | Yes | Left X in mm |
| `y1` | number | Yes | Top Y in mm | | `y1` | number | Yes | Top Y in mm |
| `x2` | number | Yes | Right X in mm | | `x2` | number | Yes | Right X in mm |
| `y2` | number | Yes | Bottom Y in mm | | `y2` | number | Yes | Bottom Y in mm |
| `width` | number | No | Line width in mm | | `width` | number | No | Line width in mm |
#### Pad Types #### Pad Types
- **SMD (smd)**: Surface-mount pads for components that sit on top of the PCB. Default layers: F.Cu, F.Paste, F.Mask - **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 - **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 - **NPTH (np_thru_hole)**: Non-plated through-holes for mechanical mounting. Requires `drill` parameter. Default layers: \*.Mask
### edit_footprint_pad ### edit_footprint_pad
Edit an existing pad inside a .kicad_mod footprint file. Updates size, position, drill, or shape without recreating the whole footprint. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | --------------- | ---------------- | -------- | ---------------------------------------------------------------------------- |
| `footprintPath` | string | Yes | Full path to the .kicad_mod file, e.g. C:/MyLib.pretty/R_Custom.kicad_mod | | `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 | | `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 | | `size` | object | No | New pad size: {w: number, h: number} in mm |
| `at` | object | No | New pad position: {x: number, y: number, angle?: number} in mm | | `at` | object | No | New pad position: {x: number, y: number, angle?: number} in mm |
| `drill` | number or object | No | New drill size: number (round) or {w: number, h: number} (oval) for THT pads | | `drill` | number or object | No | New drill size: number (round) or {w: number, h: number} (oval) for THT pads |
| `shape` | enum | No | New pad shape: `rect`, `circle`, `oval`, or `roundrect` | | `shape` | enum | No | New pad shape: `rect`, `circle`, `oval`, or `roundrect` |
**When to use:** Use this tool when you need to adjust an existing footprint's pad dimensions or positions without recreating the entire footprint. Useful for fine-tuning after initial creation or adapting existing footprints. **When to use:** Use this tool when you need to adjust an existing footprint's pad dimensions or positions without recreating the entire footprint. Useful for fine-tuning after initial creation or adapting existing footprints.
@@ -76,13 +76,13 @@ 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'. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `libraryPath` | string | Yes | Full path to the .pretty directory to register | | `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) | | `libraryName` | string | No | Nickname for the library in KiCAD (default: directory name without .pretty) |
| `description` | string | No | Optional description | | `description` | string | No | Optional description |
| `scope` | enum | No | `project` = writes fp-lib-table next to the .kicad_pro file (default); `global` = writes to the user's global KiCAD config | | `scope` | enum | No | `project` = writes fp-lib-table next to the .kicad_pro file (default); `global` = writes to the user's global KiCAD config |
| `projectPath` | string | No | Path to the .kicad_pro file or its directory (required for scope=project when the library is not in the project folder) | | `projectPath` | string | No | Path to the .kicad_pro file or its directory (required for scope=project when the library is not in the project folder) |
**How fp-lib-table works:** KiCAD maintains a table mapping library nicknames to filesystem paths. Project-scope tables (fp-lib-table in the project directory) take precedence over global tables. This allows project-specific libraries without polluting the global configuration. **How fp-lib-table works:** KiCAD maintains a table mapping library nicknames to filesystem paths. Project-scope tables (fp-lib-table in the project directory) take precedence over global tables. This allows project-specific libraries without polluting the global configuration.
@@ -90,9 +90,9 @@ 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. List available .pretty footprint libraries and their contents (first 20 footprints per library). Searches KiCAD standard install paths by default.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ----- | -------- | --------------------------------------------------------------------------------------------- |
| `searchPaths` | array | No | Override default search paths. Each entry should be a directory that contains .pretty subdirs | | `searchPaths` | array | No | Override default search paths. Each entry should be a directory that contains .pretty subdirs |
### Example: Creating a Custom SOT-23 Footprint ### 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. Pin positions are where the wire connects; the symbol body is drawn between them.
**Coordinate tips:** **Coordinate tips:**
- Body rectangle typically spans ±2.54 to ±5.08 mm - 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 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) - Pins on right side: at.x = body_right + length, angle=180 (wire goes left)
@@ -179,36 +180,37 @@ Pin positions are where the wire connects; the symbol body is drawn between them
- Pins on bottom: at.y = body_bottom - length, angle=90 (wire goes up) - Pins on bottom: at.y = body_bottom - length, angle=90 (wire goes up)
- Standard pin length: 2.54 mm, standard grid: 2.54 mm - Standard pin length: 2.54 mm, standard grid: 2.54 mm
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ----------------- | ------- | -------- | ---------------------------------------------------------------------------------------- |
| `libraryPath` | string | Yes | Path to the .kicad_sym file (created if missing) | | `libraryPath` | string | Yes | Path to the .kicad_sym file (created if missing) |
| `name` | string | Yes | Symbol name, e.g. 'TMC2209', 'MyOpAmp' | | `name` | string | Yes | Symbol name, e.g. 'TMC2209', 'MyOpAmp' |
| `referencePrefix` | string | No | Schematic reference prefix: 'U' (IC), 'R' (resistor), 'J' (connector), etc. Default: 'U' | | `referencePrefix` | string | No | Schematic reference prefix: 'U' (IC), 'R' (resistor), 'J' (connector), etc. Default: 'U' |
| `description` | string | No | Human-readable description | | `description` | string | No | Human-readable description |
| `keywords` | string | No | Space-separated search keywords | | `keywords` | string | No | Space-separated search keywords |
| `datasheet` | string | No | Datasheet URL or '~' | | `datasheet` | string | No | Datasheet URL or '~' |
| `footprint` | string | No | Default footprint, e.g. 'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm' | | `footprint` | string | No | Default footprint, e.g. 'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm' |
| `inBom` | boolean | No | Include in BOM (default true) | | `inBom` | boolean | No | Include in BOM (default true) |
| `onBoard` | boolean | No | Include in netlist for PCB (default true) | | `onBoard` | boolean | No | Include in netlist for PCB (default true) |
| `pins` | array | No | List of pin objects (see Pin Schema below). Can be empty for graphical-only symbols | | `pins` | array | No | List of pin objects (see Pin Schema below). Can be empty for graphical-only symbols |
| `rectangles` | array | No | Body rectangle(s). Typically one rectangle defining the IC body | | `rectangles` | array | No | Body rectangle(s). Typically one rectangle defining the IC body |
| `polylines` | array | No | Polyline graphics for custom body shapes (op-amp triangles, etc.) | | `polylines` | array | No | Polyline graphics for custom body shapes (op-amp triangles, etc.) |
| `overwrite` | boolean | No | Replace existing symbol with same name (default false) | | `overwrite` | boolean | No | Replace existing symbol with same name (default false) |
#### Pin Schema #### Pin Schema
Each pin object in the `pins` array supports: Each pin object in the `pins` array supports:
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | --------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `name` | string | Yes | Pin name, e.g. 'VCC', 'GND', 'IN+', '~' for unnamed | | `name` | string | Yes | Pin name, e.g. 'VCC', 'GND', 'IN+', '~' for unnamed |
| `number` | string or number | Yes | Pin number, e.g. '1', '2', 'A1' | | `number` | string or number | Yes | Pin number, e.g. '1', '2', 'A1' |
| `type` | enum | Yes | Electrical pin type (see Pin Types below) | | `type` | enum | Yes | Electrical pin type (see Pin Types below) |
| `at` | object | Yes | Pin endpoint position: {x: number, y: number, angle: number} where angle is the direction the pin wire extends FROM the symbol body | | `at` | object | Yes | Pin endpoint position: {x: number, y: number, angle: number} where angle is the direction the pin wire extends FROM the symbol body |
| `length` | number | No | Pin length in mm (default 2.54) | | `length` | number | No | Pin length in mm (default 2.54) |
| `shape` | enum | No | Pin graphic shape (default: line) | | `shape` | enum | No | Pin graphic shape (default: line) |
**Pin angle conventions:** **Pin angle conventions:**
- 0 = right (wire extends to the right from the symbol body) - 0 = right (wire extends to the right from the symbol body)
- 90 = up (wire extends upward) - 90 = up (wire extends upward)
- 180 = left (wire extends to the left) - 180 = left (wire extends to the left)
@@ -216,82 +218,82 @@ Each pin object in the `pins` array supports:
#### Pin Types (Electrical) #### Pin Types (Electrical)
| Type | Description | | Type | Description |
|------|-------------| | ---------------- | ----------------------------------------- |
| `input` | Input pin | | `input` | Input pin |
| `output` | Output pin | | `output` | Output pin |
| `bidirectional` | Bidirectional I/O | | `bidirectional` | Bidirectional I/O |
| `tri_state` | Tri-state output | | `tri_state` | Tri-state output |
| `passive` | Passive component (resistors, capacitors) | | `passive` | Passive component (resistors, capacitors) |
| `free` | Free pin (no electrical rule checking) | | `free` | Free pin (no electrical rule checking) |
| `unspecified` | Unspecified type | | `unspecified` | Unspecified type |
| `power_in` | Power input (VCC, VDD) | | `power_in` | Power input (VCC, VDD) |
| `power_out` | Power output (regulators) | | `power_out` | Power output (regulators) |
| `open_collector` | Open collector output | | `open_collector` | Open collector output |
| `open_emitter` | Open emitter output | | `open_emitter` | Open emitter output |
| `no_connect` | Not connected | | `no_connect` | Not connected |
#### Pin Shapes (Graphical) #### Pin Shapes (Graphical)
| Shape | Description | | Shape | Description |
|-------|-------------| | -------------------- | -------------------------- |
| `line` | Standard pin (default) | | `line` | Standard pin (default) |
| `inverted` | Pin with inversion bubble | | `inverted` | Pin with inversion bubble |
| `clock` | Clock input (triangle) | | `clock` | Clock input (triangle) |
| `inverted_clock` | Inverted clock with bubble | | `inverted_clock` | Inverted clock with bubble |
| `input_low` | Active-low input | | `input_low` | Active-low input |
| `clock_low` | Active-low clock | | `clock_low` | Active-low clock |
| `output_low` | Active-low output | | `output_low` | Active-low output |
| `falling_edge_clock` | Falling edge triggered | | `falling_edge_clock` | Falling edge triggered |
| `non_logic` | Non-logic pin | | `non_logic` | Non-logic pin |
#### Rectangle Schema #### Rectangle Schema
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | --------- | ------ | -------- | ------------------------------------------------------------------- |
| `x1` | number | Yes | Left X in mm | | `x1` | number | Yes | Left X in mm |
| `y1` | number | Yes | Top Y in mm | | `y1` | number | Yes | Top Y in mm |
| `x2` | number | Yes | Right X in mm | | `x2` | number | Yes | Right X in mm |
| `y2` | number | Yes | Bottom Y in mm | | `y2` | number | Yes | Bottom Y in mm |
| `width` | number | No | Stroke width in mm (default 0.254) | | `width` | number | No | Stroke width in mm (default 0.254) |
| `fill` | enum | No | Fill type: `none`, `outline`, or `background` (default: background) | | `fill` | enum | No | Fill type: `none`, `outline`, or `background` (default: background) |
#### Polyline Schema #### Polyline Schema
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | --------- | ------ | -------- | ------------------------------------------------------ |
| `points` | array | Yes | List of XY points: [{x: number, y: number}, ...] in mm | | `points` | array | Yes | List of XY points: [{x: number, y: number}, ...] in mm |
| `width` | number | No | Stroke width in mm (default 0.254) | | `width` | number | No | Stroke width in mm (default 0.254) |
| `fill` | enum | No | Fill type: `none`, `outline`, or `background` | | `fill` | enum | No | Fill type: `none`, `outline`, or `background` |
### delete_symbol ### delete_symbol
Remove a symbol from a .kicad_sym library file. Remove a symbol from a .kicad_sym library file.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | --------------------------- |
| `libraryPath` | string | Yes | Path to the .kicad_sym file | | `libraryPath` | string | Yes | Path to the .kicad_sym file |
| `name` | string | Yes | Symbol name to delete | | `name` | string | Yes | Symbol name to delete |
### list_symbols_in_library ### list_symbols_in_library
List all symbol names in a .kicad_sym library file. List all symbol names in a .kicad_sym library file.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | --------------------------- |
| `libraryPath` | string | Yes | Path to the .kicad_sym file | | `libraryPath` | string | Yes | Path to the .kicad_sym file |
### register_symbol_library ### register_symbol_library
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'. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ------------------------------------------------------------------------------------- |
| `libraryPath` | string | Yes | Full path to the .kicad_sym file | | `libraryPath` | string | Yes | Full path to the .kicad_sym file |
| `libraryName` | string | No | Nickname (default: file name without extension) | | `libraryName` | string | No | Nickname (default: file name without extension) |
| `description` | string | No | Optional description | | `description` | string | No | Optional description |
| `scope` | enum | No | `project` = writes sym-lib-table next to .kicad_pro (default); `global` = user config | | `scope` | enum | No | `project` = writes sym-lib-table next to .kicad_pro (default); `global` = user config |
| `projectPath` | string | No | Path to .kicad_pro or its directory (for scope=project) | | `projectPath` | string | No | Path to .kicad_pro or its directory (for scope=project) |
### Example: Creating a Simple IC Symbol ### Example: Creating a Simple IC Symbol
@@ -351,6 +353,7 @@ This example creates a 4-pin IC symbol (VCC, GND, IN, OUT):
``` ```
**Pin positioning explained:** **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 - 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 - 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 - 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 ### Validation
After creating custom parts: After creating custom parts:
- Open KiCAD schematic editor and verify the symbol appears in the "Add Symbol" dialog - Open KiCAD schematic editor and verify the symbol appears in the "Add Symbol" dialog
- Check pin numbers, names, and electrical types in symbol properties - Check pin numbers, names, and electrical types in symbol properties
- Open KiCAD PCB editor and verify the footprint appears in the footprint browser - 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 default location is `~/.kicad-mcp/freerouting.jar`. You can override this with:
- The `freeroutingJar` parameter on any tool call - The `freeroutingJar` parameter on any tool call
- The `FREEROUTING_JAR` environment variable - 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 **Returns:** Java availability, version, Docker status, JAR location
**Example:** **Example:**
``` ```
Check if Freerouting is ready on my system. 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 | | `timeout` | number | No | 300 | Timeout in seconds |
**Example:** **Example:**
``` ```
Autoroute the current board using Freerouting with a 5-minute timeout. 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: This is useful when you want to:
- Use the Freerouting GUI for interactive routing - Use the Freerouting GUI for interactive routing
- Use a different autorouter that supports DSN/SES - Use a different autorouter that supports DSN/SES
- Route the board on a different machine - 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" ### "Java found but version < 21"
Freerouting 2.x requires Java 21+. Either: Freerouting 2.x requires Java 21+. Either:
- Upgrade your Java installation - Upgrade your Java installation
- Install Docker as a fallback - Install Docker as a fallback
### Timeout Errors ### Timeout Errors
For complex boards, increase the timeout: For complex boards, increase the timeout:
``` ```
Autoroute with timeout 600 and max passes 30. Autoroute with timeout 600 and max passes 30.
``` ```
@@ -203,6 +209,7 @@ Autoroute with timeout 600 and max passes 30.
### Routing Quality ### Routing Quality
If the autorouter does not route all connections: If the autorouter does not route all connections:
- Increase `maxPasses` (default: 20) - Increase `maxPasses` (default: 20)
- Check that your design rules allow the autorouter enough clearance - Check that your design rules allow the autorouter enough clearance
- Run DRC after autorouting to identify any violations - Run DRC after autorouting to identify any violations
@@ -211,6 +218,7 @@ If the autorouter does not route all connections:
### Docker Permission Errors ### Docker Permission Errors
If Docker reports permission errors: If Docker reports permission errors:
```bash ```bash
# Add your user to the docker group # Add your user to the docker group
sudo usermod -aG docker $USER sudo usermod -aG docker $USER

View File

@@ -8,79 +8,79 @@ KiCAD MCP Server -- AI-assisted PCB design via Model Context Protocol
## Getting Started ## Getting Started
| Document | Description | | Document | Description |
|----------|-------------| | ----------------------------------------------- | -------------------------------------------------------------- |
| [README](../README.md) | Project overview, installation, configuration, quick start | | [README](../README.md) | Project overview, installation, configuration, quick start |
| [Client Configuration](CLIENT_CONFIGURATION.md) | MCP client setup (Claude Desktop, Cline, Claude Code) | | [Client Configuration](CLIENT_CONFIGURATION.md) | MCP client setup (Claude Desktop, Cline, Claude Code) |
| [Platform Guide](PLATFORM_GUIDE.md) | Linux vs Windows vs macOS differences | | [Platform Guide](PLATFORM_GUIDE.md) | Linux vs Windows vs macOS differences |
| [PCB Design Workflow](PCB_DESIGN_WORKFLOW.md) | End-to-end design guide from project creation to manufacturing | | [PCB Design Workflow](PCB_DESIGN_WORKFLOW.md) | End-to-end design guide from project creation to manufacturing |
--- ---
## Tool References ## Tool References
| Document | Description | | Document | Description |
|----------|-------------| | ----------------------------------------------------------------------- | ----------------------------------------------------------- |
| [Tool Inventory](TOOL_INVENTORY.md) | Complete list of all 122 tools with access types | | [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 | | [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 | | [Routing Tools Reference](ROUTING_TOOLS_REFERENCE.md) | 13 routing tools -- traces, vias, differential pairs, zones |
| [Footprint and Symbol Creator Guide](FOOTPRINT_SYMBOL_CREATOR_GUIDE.md) | 8 tools for creating custom footprints and symbols | | [Footprint and Symbol Creator Guide](FOOTPRINT_SYMBOL_CREATOR_GUIDE.md) | 8 tools for creating custom footprints and symbols |
| [Freerouting Guide](FREEROUTING_GUIDE.md) | 4 autorouter tools -- setup, usage, Docker support | | [Freerouting Guide](FREEROUTING_GUIDE.md) | 4 autorouter tools -- setup, usage, Docker support |
| [SVG Import Guide](SVG_IMPORT_GUIDE.md) | Import SVG logos onto PCB layers | | [SVG Import Guide](SVG_IMPORT_GUIDE.md) | Import SVG logos onto PCB layers |
| [Datasheet Tools Guide](DATASHEET_TOOLS_GUIDE.md) | Datasheet enrichment via LCSC | | [Datasheet Tools Guide](DATASHEET_TOOLS_GUIDE.md) | Datasheet enrichment via LCSC |
--- ---
## Integration Guides ## Integration Guides
| Document | Description | | Document | Description |
|----------|-------------| | --------------------------------------------- | -------------------------------------------------- |
| [JLCPCB Integration](JLCPCB_INTEGRATION.md) | JLCPCB parts catalog, pricing, component selection | | [JLCPCB Integration](JLCPCB_INTEGRATION.md) | JLCPCB parts catalog, pricing, component selection |
| [JLCPCB Usage Guide](JLCPCB_USAGE_GUIDE.md) | Detailed JLCPCB setup and usage | | [JLCPCB Usage Guide](JLCPCB_USAGE_GUIDE.md) | Detailed JLCPCB setup and usage |
| [Library Integration](LIBRARY_INTEGRATION.md) | Footprint and symbol library setup | | [Library Integration](LIBRARY_INTEGRATION.md) | Footprint and symbol library setup |
| [IPC Backend Status](IPC_BACKEND_STATUS.md) | Real-time KiCAD UI synchronization (experimental) | | [IPC Backend Status](IPC_BACKEND_STATUS.md) | Real-time KiCAD UI synchronization (experimental) |
--- ---
## Workflows ## Workflows
| Document | Description | | Document | Description |
|----------|-------------| | --------------------------------------------- | ----------------------------------------- |
| [Realtime Workflow](REALTIME_WORKFLOW.md) | Working with IPC backend for live updates | | [Realtime Workflow](REALTIME_WORKFLOW.md) | Working with IPC backend for live updates |
| [Visual Feedback](VISUAL_FEEDBACK.md) | UI visual feedback guide | | [Visual Feedback](VISUAL_FEEDBACK.md) | UI visual feedback guide |
| [UI Auto Launch](UI_AUTO_LAUNCH.md) | Automatic KiCAD UI launch feature | | [UI Auto Launch](UI_AUTO_LAUNCH.md) | Automatic KiCAD UI launch feature |
| [Router Guide](mcp-router-guide.md) | Tool router pattern usage | | [Router Guide](mcp-router-guide.md) | Tool router pattern usage |
| [Router Architecture](ROUTER_ARCHITECTURE.md) | Router pattern design | | [Router Architecture](ROUTER_ARCHITECTURE.md) | Router pattern design |
| [Router Quick Start](ROUTER_QUICK_START.md) | Quick start for the router pattern | | [Router Quick Start](ROUTER_QUICK_START.md) | Quick start for the router pattern |
--- ---
## Troubleshooting ## Troubleshooting
| Document | Description | | Document | Description |
|----------|-------------| | --------------------------------------------------------- | ------------------------------ |
| [Known Issues](KNOWN_ISSUES.md) | Current issues and workarounds | | [Known Issues](KNOWN_ISSUES.md) | Current issues and workarounds |
| [Windows Troubleshooting](WINDOWS_TROUBLESHOOTING.md) | Windows-specific problems | | [Windows Troubleshooting](WINDOWS_TROUBLESHOOTING.md) | Windows-specific problems |
| [Linux Compatibility Audit](LINUX_COMPATIBILITY_AUDIT.md) | Linux platform details | | [Linux Compatibility Audit](LINUX_COMPATIBILITY_AUDIT.md) | Linux platform details |
--- ---
## Project Information ## Project Information
| Document | Description | | Document | Description |
|----------|-------------| | ----------------------------------- | ----------------------------------------- |
| [Status Summary](STATUS_SUMMARY.md) | Current project status and feature matrix | | [Status Summary](STATUS_SUMMARY.md) | Current project status and feature matrix |
| [Roadmap](ROADMAP.md) | Development roadmap and planned features | | [Roadmap](ROADMAP.md) | Development roadmap and planned features |
| [Changelog](../CHANGELOG.md) | Detailed release notes for all versions | | [Changelog](../CHANGELOG.md) | Detailed release notes for all versions |
--- ---
## For Contributors ## For Contributors
| Document | Description | | Document | Description |
|----------|-------------| | ---------------------------------- | ---------------------------------------- |
| [Contributing](../CONTRIBUTING.md) | How to contribute to the project | | [Contributing](../CONTRIBUTING.md) | How to contribute to the project |
| [Architecture](ARCHITECTURE.md) | System architecture and adding new tools | | [Architecture](ARCHITECTURE.md) | System architecture and adding new tools |
--- ---

View File

@@ -1,212 +1,223 @@
# KiCAD IPC Backend Implementation Status # KiCAD IPC Backend Implementation Status
**Status:** Under Active Development and Testing **Status:** Under Active Development and Testing
**Date:** 2026-03-21 **Date:** 2026-03-21
**KiCAD Version:** 9.0+ **KiCAD Version:** 9.0+
**kicad-python Version:** 0.5.0+ **kicad-python Version:** 0.5.0+
--- ---
## Overview ## Overview
The IPC backend provides real-time UI synchronization with KiCAD 9.0+ via the official IPC API. When KiCAD is running with IPC enabled, commands can update the KiCAD UI immediately without requiring manual reload. The IPC backend provides real-time UI synchronization with KiCAD 9.0+ via the official IPC API. When KiCAD is running with IPC enabled, commands can update the KiCAD UI immediately without requiring manual reload.
This feature is experimental and under active testing. The server uses a hybrid approach: IPC when available, automatic fallback to SWIG when IPC is not connected. This feature is experimental and under active testing. The server uses a hybrid approach: IPC when available, automatic fallback to SWIG when IPC is not connected.
## Key Differences ## Key Differences
| Feature | SWIG | IPC | | Feature | SWIG | IPC |
|---------|------|-----| | -------------- | ---------------------- | ------------------------ |
| UI Updates | Manual reload required | Immediate (when working) | | UI Updates | Manual reload required | Immediate (when working) |
| Undo/Redo | Not supported | Transaction support | | Undo/Redo | Not supported | Transaction support |
| API Stability | Deprecated in KiCAD 9 | Official, versioned | | API Stability | Deprecated in KiCAD 9 | Official, versioned |
| Connection | File-based | Live socket connection | | Connection | File-based | Live socket connection |
| KiCAD Required | No (file operations) | Yes (must be running) | | KiCAD Required | No (file operations) | Yes (must be running) |
## Implemented IPC Commands ## Implemented IPC Commands
The following MCP commands have IPC handlers: The following MCP commands have IPC handlers:
| Command | IPC Handler | Status | | Command | IPC Handler | Status |
|---------|-------------|--------| | -------------------------- | ------------------------------- | -------------------- |
| `route_trace` | `_ipc_route_trace` | Implemented | | `route_trace` | `_ipc_route_trace` | Implemented |
| `add_via` | `_ipc_add_via` | Implemented | | `add_via` | `_ipc_add_via` | Implemented |
| `add_net` | `_ipc_add_net` | Implemented | | `add_net` | `_ipc_add_net` | Implemented |
| `delete_trace` | `_ipc_delete_trace` | Falls back to SWIG | | `delete_trace` | `_ipc_delete_trace` | Falls back to SWIG |
| `get_nets_list` | `_ipc_get_nets_list` | Implemented | | `get_nets_list` | `_ipc_get_nets_list` | Implemented |
| `add_copper_pour` | `_ipc_add_copper_pour` | Implemented | | `add_copper_pour` | `_ipc_add_copper_pour` | Implemented |
| `refill_zones` | `_ipc_refill_zones` | Implemented | | `refill_zones` | `_ipc_refill_zones` | Implemented |
| `add_text` | `_ipc_add_text` | Implemented | | `add_text` | `_ipc_add_text` | Implemented |
| `add_board_text` | `_ipc_add_text` | Implemented | | `add_board_text` | `_ipc_add_text` | Implemented |
| `set_board_size` | `_ipc_set_board_size` | Implemented | | `set_board_size` | `_ipc_set_board_size` | Implemented |
| `get_board_info` | `_ipc_get_board_info` | Implemented | | `get_board_info` | `_ipc_get_board_info` | Implemented |
| `add_board_outline` | `_ipc_add_board_outline` | Implemented | | `add_board_outline` | `_ipc_add_board_outline` | Implemented |
| `add_mounting_hole` | `_ipc_add_mounting_hole` | Implemented | | `add_mounting_hole` | `_ipc_add_mounting_hole` | Implemented |
| `get_layer_list` | `_ipc_get_layer_list` | Implemented | | `get_layer_list` | `_ipc_get_layer_list` | Implemented |
| `place_component` | `_ipc_place_component` | Implemented (hybrid) | | `place_component` | `_ipc_place_component` | Implemented (hybrid) |
| `move_component` | `_ipc_move_component` | Implemented | | `move_component` | `_ipc_move_component` | Implemented |
| `rotate_component` | `_ipc_rotate_component` | Implemented | | `rotate_component` | `_ipc_rotate_component` | Implemented |
| `delete_component` | `_ipc_delete_component` | Implemented | | `delete_component` | `_ipc_delete_component` | Implemented |
| `get_component_list` | `_ipc_get_component_list` | Implemented | | `get_component_list` | `_ipc_get_component_list` | Implemented |
| `get_component_properties` | `_ipc_get_component_properties` | Implemented | | `get_component_properties` | `_ipc_get_component_properties` | Implemented |
| `save_project` | `_ipc_save_project` | Implemented | | `save_project` | `_ipc_save_project` | Implemented |
### Implemented Backend Features ### Implemented Backend Features
**Core Connection:** **Core Connection:**
- Connect to running KiCAD instance
- Auto-detect socket path (`/tmp/kicad/api.sock`) - Connect to running KiCAD instance
- Version checking and validation - Auto-detect socket path (`/tmp/kicad/api.sock`)
- Auto-fallback to SWIG when IPC unavailable - Version checking and validation
- Change notification callbacks - Auto-fallback to SWIG when IPC unavailable
- Change notification callbacks
**Board Operations:**
- Get board reference **Board Operations:**
- Get/Set board size
- List enabled layers - Get board reference
- Save board - Get/Set board size
- Add board outline segments - List enabled layers
- Add mounting holes - Save board
- Add board outline segments
**Component Operations:** - Add mounting holes
- List all components
- Place component (hybrid: SWIG for library loading, IPC for placement) **Component Operations:**
- Move component
- Rotate component - List all components
- Delete component - Place component (hybrid: SWIG for library loading, IPC for placement)
- Get component properties - Move component
- Rotate component
**Routing Operations:** - Delete component
- Add track - Get component properties
- Add via
- Get all tracks **Routing Operations:**
- Get all vias
- Get all nets - Add track
- Add via
**Zone Operations:** - Get all tracks
- Add copper pour zones - Get all vias
- Get zones list - Get all nets
- Refill zones
**Zone Operations:**
**UI Integration:**
- Add text to board - Add copper pour zones
- Get current selection - Get zones list
- Clear selection - Refill zones
**Transaction Support:** **UI Integration:**
- Begin transaction
- Commit transaction (with description for undo) - Add text to board
- Rollback transaction - Get current selection
- Clear selection
## Usage
**Transaction Support:**
### Prerequisites
- Begin transaction
1. **KiCAD 9.0+** must be running - Commit transaction (with description for undo)
2. **IPC API must be enabled**: `Preferences > Plugins > Enable IPC API Server` - Rollback transaction
3. A board must be open in the PCB editor
## Usage
### Installation
### Prerequisites
```bash
pip install kicad-python 1. **KiCAD 9.0+** must be running
``` 2. **IPC API must be enabled**: `Preferences > Plugins > Enable IPC API Server`
3. A board must be open in the PCB editor
### Testing
### Installation
Run the test script to verify IPC functionality:
```bash
```bash pip install kicad-python
# Make sure KiCAD is running with IPC enabled and a board open ```
./venv/bin/python python/test_ipc_backend.py
``` ### Testing
## Architecture Run the test script to verify IPC functionality:
``` ```bash
+-------------------------------------------------------------+ # Make sure KiCAD is running with IPC enabled and a board open
| MCP Server (TypeScript/Node.js) | ./venv/bin/python python/test_ipc_backend.py
+---------------------------+---------------------------------+ ```
| JSON commands
+---------------------------v---------------------------------+ ## Architecture
| Python Interface Layer |
| +--------------------------------------------------------+ | ```
| | kicad_interface.py | | +-------------------------------------------------------------+
| | - Routes commands to IPC or SWIG handlers | | | MCP Server (TypeScript/Node.js) |
| | - IPC_CAPABLE_COMMANDS dict defines routing | | +---------------------------+---------------------------------+
| +--------------------------------------------------------+ | | JSON commands
| +--------------------------------------------------------+ | +---------------------------v---------------------------------+
| | kicad_api/ipc_backend.py | | | Python Interface Layer |
| | - IPCBackend (connection management) | | | +--------------------------------------------------------+ |
| | - IPCBoardAPI (board operations) | | | | kicad_interface.py | |
| +--------------------------------------------------------+ | | | - Routes commands to IPC or SWIG handlers | |
+---------------------------+---------------------------------+ | | - IPC_CAPABLE_COMMANDS dict defines routing | |
| kicad-python (kipy) library | +--------------------------------------------------------+ |
+---------------------------v---------------------------------+ | +--------------------------------------------------------+ |
| Protocol Buffers over UNIX Sockets | | | kicad_api/ipc_backend.py | |
+---------------------------+---------------------------------+ | | - IPCBackend (connection management) | |
| | | - IPCBoardAPI (board operations) | |
+---------------------------v---------------------------------+ | +--------------------------------------------------------+ |
| KiCAD 9.0+ (IPC Server) | +---------------------------+---------------------------------+
+-------------------------------------------------------------+ | kicad-python (kipy) library
``` +---------------------------v---------------------------------+
| Protocol Buffers over UNIX Sockets |
## Known Limitations +---------------------------+---------------------------------+
|
1. **KiCAD must be running**: Unlike SWIG, IPC requires KiCAD to be open +---------------------------v---------------------------------+
2. **Project creation**: Not supported via IPC, uses file system | KiCAD 9.0+ (IPC Server) |
3. **Footprint library access**: Uses hybrid approach (SWIG loads from library, IPC places) +-------------------------------------------------------------+
4. **Delete trace**: Falls back to SWIG (IPC API doesn't support direct deletion) ```
5. **Some operations may not work as expected**: This is experimental code
## Known Limitations
## Troubleshooting
1. **KiCAD must be running**: Unlike SWIG, IPC requires KiCAD to be open
### "Connection failed" 2. **Project creation**: Not supported via IPC, uses file system
- Ensure KiCAD is running 3. **Footprint library access**: Uses hybrid approach (SWIG loads from library, IPC places)
- Enable IPC API: `Preferences > Plugins > Enable IPC API Server` 4. **Delete trace**: Falls back to SWIG (IPC API doesn't support direct deletion)
- Check if a board is open 5. **Some operations may not work as expected**: This is experimental code
### "kicad-python not found" ## Troubleshooting
```bash
pip install kicad-python ### "Connection failed"
```
- Ensure KiCAD is running
### "Version mismatch" - Enable IPC API: `Preferences > Plugins > Enable IPC API Server`
- Update kicad-python: `pip install --upgrade kicad-python` - Check if a board is open
- Ensure KiCAD 9.0+ is installed
### "kicad-python not found"
### "No board open"
- Open a board in KiCAD's PCB editor before connecting ```bash
pip install kicad-python
## File Structure ```
``` ### "Version mismatch"
python/kicad_api/
├── __init__.py # Package exports - Update kicad-python: `pip install --upgrade kicad-python`
├── base.py # Abstract base classes - Ensure KiCAD 9.0+ is installed
├── factory.py # Backend auto-detection
├── ipc_backend.py # IPC implementation ### "No board open"
└── swig_backend.py # Legacy SWIG wrapper
- Open a board in KiCAD's PCB editor before connecting
python/
└── test_ipc_backend.py # IPC test script ## File Structure
```
```
## Future Work python/kicad_api/
├── __init__.py # Package exports
1. More comprehensive testing of all IPC commands ├── base.py # Abstract base classes
2. Footprint library integration via IPC (when kipy supports it) ├── factory.py # Backend auto-detection
3. Schematic IPC support (when available in kicad-python) ├── ipc_backend.py # IPC implementation
4. Event subscriptions to react to changes made in KiCAD UI └── swig_backend.py # Legacy SWIG wrapper
5. Multi-board support
python/
## Related Documentation └── test_ipc_backend.py # IPC test script
```
- [ROADMAP.md](./ROADMAP.md) - Project roadmap
- [IPC_API_MIGRATION_PLAN.md](./IPC_API_MIGRATION_PLAN.md) - Migration details ## Future Work
- [REALTIME_WORKFLOW.md](./REALTIME_WORKFLOW.md) - Collaboration workflows
- [kicad-python docs](https://docs.kicad.org/kicad-python-main/) - Official API docs 1. More comprehensive testing of all IPC commands
2. Footprint library integration via IPC (when kipy supports it)
--- 3. Schematic IPC support (when available in kicad-python)
4. Event subscriptions to react to changes made in KiCAD UI
**Last Updated:** 2026-03-21 5. Multi-board support
## Related Documentation
- [ROADMAP.md](./ROADMAP.md) - Project roadmap
- [IPC_API_MIGRATION_PLAN.md](./IPC_API_MIGRATION_PLAN.md) - Migration details
- [REALTIME_WORKFLOW.md](./REALTIME_WORKFLOW.md) - Collaboration workflows
- [kicad-python docs](https://docs.kicad.org/kicad-python-main/) - Official API docs
---
**Last Updated:** 2026-03-21

View File

@@ -1,344 +1,374 @@
# JLCPCB Parts Integration - Complete Guide # JLCPCB Parts Integration - Complete Guide
## Overview ## Overview
The KiCAD MCP Server integrates with JLCPCB's parts library to provide intelligent component selection, cost optimization, and automated part sourcing for PCB assembly. The KiCAD MCP Server integrates with JLCPCB's parts library to provide intelligent component selection, cost optimization, and automated part sourcing for PCB assembly.
**Current Implementation**: Uses the **JLCSearch public API** (by tscircuit) for free, unauthenticated access to JLCPCB's ~100k parts catalog. **Current Implementation**: Uses the **JLCSearch public API** (by tscircuit) for free, unauthenticated access to JLCPCB's ~100k parts catalog.
## Features ## Features
**Parametric Search** - Find components by specifications (resistance, capacitance, package, etc.) **Parametric Search** - Find components by specifications (resistance, capacitance, package, etc.)
**Price Comparison** - Compare Basic vs Extended library pricing **Price Comparison** - Compare Basic vs Extended library pricing
**Alternative Suggestions** - Find cheaper or higher-stock alternatives **Alternative Suggestions** - Find cheaper or higher-stock alternatives
**Footprint Mapping** - Automatic JLCPCB package to KiCad footprint mapping **Footprint Mapping** - Automatic JLCPCB package to KiCad footprint mapping
**Stock Availability** - Real-time stock levels from JLCPCB **Stock Availability** - Real-time stock levels from JLCPCB
**No Authentication Required** - Public API, no API keys needed **No Authentication Required** - Public API, no API keys needed
## Quick Start ## Quick Start
### 1. Search for Components ### 1. Search for Components
```python ```python
from commands.jlcsearch import JLCSearchClient from commands.jlcsearch import JLCSearchClient
client = JLCSearchClient() client = JLCSearchClient()
# Search for resistors # Search for resistors
resistors = client.search_resistors( resistors = client.search_resistors(
resistance=10000, # 10kΩ resistance=10000, # 10kΩ
package="0603", package="0603",
limit=20 limit=20
) )
# Search for capacitors # Search for capacitors
capacitors = client.search_capacitors( capacitors = client.search_capacitors(
capacitance=1e-7, # 100nF capacitance=1e-7, # 100nF
package="0603", package="0603",
limit=20 limit=20
) )
# General component search # General component search
components = client.search_components( components = client.search_components(
"components", "components",
package="0603", package="0603",
limit=100 limit=100
) )
``` ```
### 2. Get Part Details ### 2. Get Part Details
```python ```python
# Get specific part by LCSC number # Get specific part by LCSC number
part = client.get_part_by_lcsc(25804) # C25804 part = client.get_part_by_lcsc(25804) # C25804
print(f"Part: {part['mfr']}") print(f"Part: {part['mfr']}")
print(f"Stock: {part['stock']}") print(f"Stock: {part['stock']}")
print(f"Price: ${part['price1']}") print(f"Price: ${part['price1']}")
print(f"Basic Library: {part['is_basic']}") print(f"Basic Library: {part['is_basic']}")
``` ```
### 3. Database Integration ### 3. Database Integration
```python ```python
from commands.jlcpcb_parts import JLCPCBPartsManager from commands.jlcpcb_parts import JLCPCBPartsManager
# Initialize database # Initialize database
db = JLCPCBPartsManager() # Uses data/jlcpcb_parts.db db = JLCPCBPartsManager() # Uses data/jlcpcb_parts.db
# Download and import parts (one-time setup) # Download and import parts (one-time setup)
client = JLCSearchClient() client = JLCSearchClient()
parts = client.download_all_components() parts = client.download_all_components()
db.import_jlcsearch_parts(parts) db.import_jlcsearch_parts(parts)
# Search imported database # Search imported database
results = db.search_parts( results = db.search_parts(
query="resistor", query="resistor",
package="0603", package="0603",
library_type="Basic", library_type="Basic",
in_stock=True, in_stock=True,
limit=20 limit=20
) )
``` ```
### 4. Footprint Mapping ### 4. Footprint Mapping
```python ```python
# Map JLCPCB package to KiCad footprints # Map JLCPCB package to KiCad footprints
footprints = db.map_package_to_footprint("0603") footprints = db.map_package_to_footprint("0603")
# Returns: # Returns:
# [ # [
# "Resistor_SMD:R_0603_1608Metric", # "Resistor_SMD:R_0603_1608Metric",
# "Capacitor_SMD:C_0603_1608Metric", # "Capacitor_SMD:C_0603_1608Metric",
# "LED_SMD:LED_0603_1608Metric" # "LED_SMD:LED_0603_1608Metric"
# ] # ]
``` ```
## API Reference ## API Reference
### JLCSearchClient ### JLCSearchClient
#### `search_resistors(resistance, package, limit)` #### `search_resistors(resistance, package, limit)`
Search for resistors by value and package.
Search for resistors by value and package.
**Parameters:**
- `resistance` (int, optional): Resistance in ohms **Parameters:**
- `package` (str, optional): Package size ("0402", "0603", "0805", etc.)
- `limit` (int): Maximum results (default: 100) - `resistance` (int, optional): Resistance in ohms
- `package` (str, optional): Package size ("0402", "0603", "0805", etc.)
**Returns:** List of resistor dicts with fields: - `limit` (int): Maximum results (default: 100)
- `lcsc`: LCSC number (integer)
- `mfr`: Manufacturer part number **Returns:** List of resistor dicts with fields:
- `package`: Package size
- `is_basic`: True if Basic library part (no assembly fee) - `lcsc`: LCSC number (integer)
- `resistance`: Resistance in ohms - `mfr`: Manufacturer part number
- `tolerance_fraction`: Tolerance (0.01 = 1%) - `package`: Package size
- `power_watts`: Power rating in mW - `is_basic`: True if Basic library part (no assembly fee)
- `stock`: Available stock - `resistance`: Resistance in ohms
- `price1`: Unit price in USD - `tolerance_fraction`: Tolerance (0.01 = 1%)
- `power_watts`: Power rating in mW
#### `search_capacitors(capacitance, package, limit)` - `stock`: Available stock
Search for capacitors by value and package. - `price1`: Unit price in USD
**Parameters:** #### `search_capacitors(capacitance, package, limit)`
- `capacitance` (float, optional): Capacitance in farads (e.g., 1e-7 for 100nF)
- `package` (str, optional): Package size Search for capacitors by value and package.
- `limit` (int): Maximum results
**Parameters:**
**Returns:** List of capacitor dicts
- `capacitance` (float, optional): Capacitance in farads (e.g., 1e-7 for 100nF)
#### `search_components(category, limit, offset, **filters)` - `package` (str, optional): Package size
General component search. - `limit` (int): Maximum results
**Parameters:** **Returns:** List of capacitor dicts
- `category` (str): "resistors", "capacitors", "components", etc.
- `limit` (int): Maximum results #### `search_components(category, limit, offset, **filters)`
- `offset` (int): Pagination offset
- `**filters`: Additional filters (package="0603", lcsc=25804, etc.) General component search.
**Returns:** List of component dicts **Parameters:**
#### `download_all_components(callback, batch_size)` - `category` (str): "resistors", "capacitors", "components", etc.
Download entire JLCPCB parts catalog. - `limit` (int): Maximum results
- `offset` (int): Pagination offset
**Parameters:** - `**filters`: Additional filters (package="0603", lcsc=25804, etc.)
- `callback` (callable, optional): Progress callback(parts_count, status_msg)
- `batch_size` (int): Parts per batch (default: 1000) **Returns:** List of component dicts
**Returns:** List of all parts (~100k components) #### `download_all_components(callback, batch_size)`
**Note:** This may take 5-10 minutes to complete. Download entire JLCPCB parts catalog.
### JLCPCBPartsManager **Parameters:**
#### `import_jlcsearch_parts(parts, progress_callback)` - `callback` (callable, optional): Progress callback(parts_count, status_msg)
Import parts from JLCSearch into local SQLite database. - `batch_size` (int): Parts per batch (default: 1000)
**Parameters:** **Returns:** List of all parts (~100k components)
- `parts` (list): List of part dicts from JLCSearchClient
- `progress_callback` (callable, optional): Progress updates **Note:** This may take 5-10 minutes to complete.
#### `search_parts(query, category, package, library_type, manufacturer, in_stock, limit)` ### JLCPCBPartsManager
Search local database with filters.
#### `import_jlcsearch_parts(parts, progress_callback)`
**Parameters:**
- `query` (str, optional): Free-text search Import parts from JLCSearch into local SQLite database.
- `category` (str, optional): Category filter
- `package` (str, optional): Package filter **Parameters:**
- `library_type` (str, optional): "Basic", "Extended", or "Preferred"
- `manufacturer` (str, optional): Manufacturer filter - `parts` (list): List of part dicts from JLCSearchClient
- `in_stock` (bool): Only in-stock parts (default: True) - `progress_callback` (callable, optional): Progress updates
- `limit` (int): Maximum results
#### `search_parts(query, category, package, library_type, manufacturer, in_stock, limit)`
**Returns:** List of matching parts
Search local database with filters.
#### `get_part_info(lcsc_number)`
Get detailed part information. **Parameters:**
**Parameters:** - `query` (str, optional): Free-text search
- `lcsc_number` (str): LCSC part number (e.g., "C25804") - `category` (str, optional): Category filter
- `package` (str, optional): Package filter
**Returns:** Part dict or None - `library_type` (str, optional): "Basic", "Extended", or "Preferred"
- `manufacturer` (str, optional): Manufacturer filter
#### `get_database_stats()` - `in_stock` (bool): Only in-stock parts (default: True)
Get database statistics. - `limit` (int): Maximum results
**Returns:** Dict with: **Returns:** List of matching parts
- `total_parts`: Total parts count
- `basic_parts`: Basic library count #### `get_part_info(lcsc_number)`
- `extended_parts`: Extended library count
- `in_stock`: Parts with stock > 0 Get detailed part information.
- `db_path`: Database file path
**Parameters:**
#### `map_package_to_footprint(package)`
Map JLCPCB package to KiCad footprints. - `lcsc_number` (str): LCSC part number (e.g., "C25804")
**Parameters:** **Returns:** Part dict or None
- `package` (str): JLCPCB package name
#### `get_database_stats()`
**Returns:** List of KiCad footprint library references
Get database statistics.
## Data Format
**Returns:** Dict with:
### JLCSearch Part Object
- `total_parts`: Total parts count
```json - `basic_parts`: Basic library count
{ - `extended_parts`: Extended library count
"lcsc": 25804, - `in_stock`: Parts with stock > 0
"mfr": "0603WAF1002T5E", - `db_path`: Database file path
"package": "0603",
"is_basic": true, #### `map_package_to_footprint(package)`
"is_preferred": false,
"resistance": 10000, Map JLCPCB package to KiCad footprints.
"tolerance_fraction": 0.01,
"power_watts": 100, **Parameters:**
"stock": 37165617,
"price1": 0.000842857 - `package` (str): JLCPCB package name
}
``` **Returns:** List of KiCad footprint library references
### Database Schema ## Data Format
```sql ### JLCSearch Part Object
CREATE TABLE components (
lcsc TEXT PRIMARY KEY, -- "C25804" ```json
category TEXT, -- "Resistors" {
subcategory TEXT, -- "Chip Resistor" "lcsc": 25804,
mfr_part TEXT, -- "0603WAF1002T5E" "mfr": "0603WAF1002T5E",
package TEXT, -- "0603" "package": "0603",
solder_joints INTEGER, "is_basic": true,
manufacturer TEXT, "is_preferred": false,
library_type TEXT, -- "Basic" or "Extended" "resistance": 10000,
description TEXT, -- "10kΩ ±1% 100mW" "tolerance_fraction": 0.01,
datasheet TEXT, "power_watts": 100,
stock INTEGER, "stock": 37165617,
price_json TEXT, -- JSON array of price breaks "price1": 0.000842857
last_updated INTEGER -- Unix timestamp }
); ```
```
### Database Schema
## Package to Footprint Mappings
```sql
| JLCPCB Package | KiCad Footprints | CREATE TABLE components (
|----------------|------------------| lcsc TEXT PRIMARY KEY, -- "C25804"
| 0402 | Resistor_SMD:R_0402_1005Metric<br>Capacitor_SMD:C_0402_1005Metric<br>LED_SMD:LED_0402_1005Metric | category TEXT, -- "Resistors"
| 0603 | Resistor_SMD:R_0603_1608Metric<br>Capacitor_SMD:C_0603_1608Metric<br>LED_SMD:LED_0603_1608Metric | subcategory TEXT, -- "Chip Resistor"
| 0805 | Resistor_SMD:R_0805_2012Metric<br>Capacitor_SMD:C_0805_2012Metric | mfr_part TEXT, -- "0603WAF1002T5E"
| 1206 | Resistor_SMD:R_1206_3216Metric<br>Capacitor_SMD:C_1206_3216Metric | package TEXT, -- "0603"
| SOT-23 | Package_TO_SOT_SMD:SOT-23<br>Package_TO_SOT_SMD:SOT-23-3 | solder_joints INTEGER,
| SOT-23-5 | Package_TO_SOT_SMD:SOT-23-5 | manufacturer TEXT,
| SOT-23-6 | Package_TO_SOT_SMD:SOT-23-6 | library_type TEXT, -- "Basic" or "Extended"
| SOT-223 | Package_TO_SOT_SMD:SOT-223 | description TEXT, -- "10kΩ ±1% 100mW"
| SOIC-8 | Package_SO:SOIC-8_3.9x4.9mm_P1.27mm | datasheet TEXT,
| QFN-20 | Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm | stock INTEGER,
price_json TEXT, -- JSON array of price breaks
## Best Practices last_updated INTEGER -- Unix timestamp
);
### 1. Always Use Basic Library Parts First ```
Basic library parts have **no assembly fee** ($0/part), while Extended parts cost **$3/part**.
## Package to Footprint Mappings
```python
# Filter for Basic parts only | JLCPCB Package | KiCad Footprints |
basic_parts = [p for p in results if p['is_basic']] | -------------- | ------------------------------------------------------------------------------------------------ |
``` | 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 |
### 2. Check Stock Availability | 0805 | Resistor_SMD:R_0805_2012Metric<br>Capacitor_SMD:C_0805_2012Metric |
Ensure sufficient stock before committing to a design. | 1206 | Resistor_SMD:R_1206_3216Metric<br>Capacitor_SMD:C_1206_3216Metric |
| SOT-23 | Package_TO_SOT_SMD:SOT-23<br>Package_TO_SOT_SMD:SOT-23-3 |
```python | SOT-23-5 | Package_TO_SOT_SMD:SOT-23-5 |
# Only use parts with >1000 stock | SOT-23-6 | Package_TO_SOT_SMD:SOT-23-6 |
high_stock = [p for p in results if p['stock'] > 1000] | SOT-223 | Package_TO_SOT_SMD:SOT-223 |
``` | SOIC-8 | Package_SO:SOIC-8_3.9x4.9mm_P1.27mm |
| QFN-20 | Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm |
### 3. Compare Prices
Even within Basic library, prices vary significantly. ## Best Practices
```python ### 1. Always Use Basic Library Parts First
# Find cheapest option
cheapest = min(results, key=lambda x: x.get('price1', 999)) Basic library parts have **no assembly fee** ($0/part), while Extended parts cost **$3/part**.
```
```python
### 4. Use Standardized Packages # Filter for Basic parts only
Stick to common packages (0402, 0603, 0805) for better availability and pricing. basic_parts = [p for p in results if p['is_basic']]
```
### 5. Cache Database Locally
Download the full parts database once and search locally for faster results. ### 2. Check Stock Availability
```python Ensure sufficient stock before committing to a design.
# Initial download (one-time, ~5-10 minutes)
if not os.path.exists("data/jlcpcb_parts.db"): ```python
parts = client.download_all_components() # Only use parts with >1000 stock
db.import_jlcsearch_parts(parts) high_stock = [p for p in results if p['stock'] > 1000]
```
# Subsequent searches use local database (instant)
results = db.search_parts(...) ### 3. Compare Prices
```
Even within Basic library, prices vary significantly.
## Troubleshooting
```python
### API Rate Limiting # Find cheapest option
JLCSearch is a community service. If you hit rate limits: cheapest = min(results, key=lambda x: x.get('price1', 999))
- 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 ### 4. Use Standardized Packages
### Missing Data Stick to common packages (0402, 0603, 0805) for better availability and pricing.
JLCSearch may not have all fields that official JLCPCB API provides:
- No datasheets (use manufacturer website) ### 5. Cache Database Locally
- Limited category information
- No solder joint count Download the full parts database once and search locally for faster results.
### Stock Discrepancies ```python
Stock levels are updated periodically but may lag real-time JLCPCB data by a few hours. # Initial download (one-time, ~5-10 minutes)
if not os.path.exists("data/jlcpcb_parts.db"):
## Official JLCPCB API (Alternative) parts = client.download_all_components()
db.import_jlcsearch_parts(parts)
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) # Subsequent searches use local database (instant)
2. APP_ID, ACCESS_KEY, and SECRET_KEY credentials results = db.search_parts(...)
3. Previous order history with JLCPCB ```
To use the official API instead of JLCSearch: ## Troubleshooting
```python ### API Rate Limiting
from commands.jlcpcb import JLCPCBClient
JLCSearch is a community service. If you hit rate limits:
# Set credentials in .env file:
# JLCPCB_APP_ID=<your_app_id> - Add delays between requests (`time.sleep(0.1)`)
# JLCPCB_API_KEY=<your_access_key> - Use the local database instead of repeated API calls
# JLCPCB_API_SECRET=<your_secret_key> - Download the full database once and work offline
client = JLCPCBClient(app_id, access_key, secret_key) ### Missing Data
data = client.fetch_parts_page()
``` JLCSearch may not have all fields that official JLCPCB API provides:
**Note:** Most users should use JLCSearch public API instead, as it's freely available and requires no authentication. - No datasheets (use manufacturer website)
- Limited category information
## Credits - No solder joint count
- **JLCSearch API**: https://jlcsearch.tscircuit.com/ (by [@tscircuit](https://github.com/tscircuit/jlcsearch)) ### Stock Discrepancies
- **JLCParts Database**: https://github.com/yaqwsx/jlcparts (by [@yaqwsx](https://github.com/yaqwsx))
- **JLCPCB**: https://jlcpcb.com/ (official parts library provider) Stock levels are updated periodically but may lag real-time JLCPCB data by a few hours.
## License ## Official JLCPCB API (Alternative)
This integration uses publicly available JLCPCB parts data via the JLCSearch community service. Users must comply with JLCPCB's terms of service when using this data for production PCB orders. 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
To use the official API instead of JLCSearch:
```python
from commands.jlcpcb import JLCPCBClient
# Set credentials in .env file:
# JLCPCB_APP_ID=<your_app_id>
# JLCPCB_API_KEY=<your_access_key>
# JLCPCB_API_SECRET=<your_secret_key>
client = JLCPCBClient(app_id, access_key, secret_key)
data = client.fetch_parts_page()
```
**Note:** Most users should use JLCSearch public API instead, as it's freely available and requires no authentication.
## Credits
- **JLCSearch API**: https://jlcsearch.tscircuit.com/ (by [@tscircuit](https://github.com/tscircuit/jlcsearch))
- **JLCParts Database**: https://github.com/yaqwsx/jlcparts (by [@yaqwsx](https://github.com/yaqwsx))
- **JLCPCB**: https://jlcpcb.com/ (official parts library provider)
## License
This integration uses publicly available JLCPCB parts data via the JLCSearch community service. Users must comply with JLCPCB's terms of service when using this data for production PCB orders.

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,364 +1,390 @@
# KiCAD Library Integration # KiCAD Library Integration
**Status:** ✅ COMPLETE **Status:** ✅ COMPLETE
**Date:** 2026-03-21 **Date:** 2026-03-21
**Version:** 2.2.3+ **Version:** 2.2.3+
## Overview ## Overview
The KiCAD MCP Server includes full library integration for both footprints and symbols, enabling: 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) - ✅ Automatic discovery of all installed KiCAD footprint libraries
-Search and browse footprints/symbols across all libraries -Automatic discovery of KiCAD symbol libraries (including project-local)
-Component placement using library footprints -Search and browse footprints/symbols across all libraries
-Symbol creation and editing with project-local library support (v2.2.2+) -Component placement using library footprints
- ✅ Support for both `Library:Footprint` and `Footprint` formats - ✅ Symbol creation and editing with project-local library support (v2.2.2+)
- ✅ Support for both `Library:Footprint` and `Footprint` formats
## How It Works
## How It Works
### Library Discovery
### Library Discovery
The library system automatically discovers both footprint and symbol libraries:
The library system automatically discovers both footprint and symbol libraries:
**Footprint Libraries** - `LibraryManager` class:
**Footprint Libraries** - `LibraryManager` class:
1. **Parsing fp-lib-table files:**
- Global: `~/.config/kicad/9.0/fp-lib-table` 1. **Parsing fp-lib-table files:**
- Project-specific: `project-dir/fp-lib-table` - Global: `~/.config/kicad/9.0/fp-lib-table`
- Project-specific: `project-dir/fp-lib-table`
**Symbol Libraries** - `DynamicSymbolLoader` class (v2.2.2+):
**Symbol Libraries** - `DynamicSymbolLoader` class (v2.2.2+):
1. **Parsing sym-lib-table files:**
- Global: `~/.config/kicad/9.0/sym-lib-table` 1. **Parsing sym-lib-table files:**
- Project-local: `project-dir/sym-lib-table` (added v2.2.2) - Global: `~/.config/kicad/9.0/sym-lib-table`
- Project-local: `project-dir/sym-lib-table` (added v2.2.2)
2. **Resolving environment variables:**
- `${KICAD9_FOOTPRINT_DIR}``/usr/share/kicad/footprints` 2. **Resolving environment variables:**
- `${K IPRJMOD}` → project directory - `${KICAD9_FOOTPRINT_DIR}``/usr/share/kicad/footprints`
- Supports custom paths - `${K IPRJMOD}` → project directory
- Supports custom paths
3. **Indexing footprints:**
- Scans `.kicad_mod` files in each library 3. **Indexing footprints:**
- Caches results for performance - Scans `.kicad_mod` files in each library
- Provides fast search capabilities - Caches results for performance
- Provides fast search capabilities
### Supported Formats
### Supported Formats
**Library:Footprint format (recommended):**
```json **Library:Footprint format (recommended):**
{
"componentId": "Resistor_SMD:R_0603_1608Metric" ```json
} {
``` "componentId": "Resistor_SMD:R_0603_1608Metric"
}
**Footprint-only format (searches all libraries):** ```
```json
{ **Footprint-only format (searches all libraries):**
"componentId": "R_0603_1608Metric"
} ```json
``` {
"componentId": "R_0603_1608Metric"
## New MCP Tools }
```
### 1. `list_libraries`
## New MCP Tools
List all available footprint libraries.
### 1. `list_libraries`
**Parameters:** None
List all available footprint libraries.
**Returns:**
```json **Parameters:** None
{
"success": true, **Returns:**
"libraries": ["Resistor_SMD", "Capacitor_SMD", "LED_SMD", ...],
"count": 153 ```json
} {
``` "success": true,
"libraries": ["Resistor_SMD", "Capacitor_SMD", "LED_SMD", ...],
### 2. `search_footprints` "count": 153
}
Search for footprints matching a pattern. ```
**Parameters:** ### 2. `search_footprints`
```json
{ Search for footprints matching a pattern.
"pattern": "*0603*", // Supports wildcards
"limit": 20 // Optional, default: 20 **Parameters:**
}
``` ```json
{
**Returns:** "pattern": "*0603*", // Supports wildcards
```json "limit": 20 // Optional, default: 20
{ }
"success": true, ```
"footprints": [
{ **Returns:**
"library": "Resistor_SMD",
"footprint": "R_0603_1608Metric", ```json
"full_name": "Resistor_SMD:R_0603_1608Metric" {
}, "success": true,
... "footprints": [
] {
} "library": "Resistor_SMD",
``` "footprint": "R_0603_1608Metric",
"full_name": "Resistor_SMD:R_0603_1608Metric"
### 3. `list_library_footprints` },
...
List all footprints in a specific library. ]
}
**Parameters:** ```
```json
{ ### 3. `list_library_footprints`
"library": "Resistor_SMD"
} List all footprints in a specific library.
```
**Parameters:**
**Returns:**
```json ```json
{ {
"success": true, "library": "Resistor_SMD"
"library": "Resistor_SMD", }
"footprints": ["R_0402_1005Metric", "R_0603_1608Metric", ...], ```
"count": 120
} **Returns:**
```
```json
### 4. `get_footprint_info` {
"success": true,
Get detailed information about a specific footprint. "library": "Resistor_SMD",
"footprints": ["R_0402_1005Metric", "R_0603_1608Metric", ...],
**Parameters:** "count": 120
```json }
{ ```
"footprint": "Resistor_SMD:R_0603_1608Metric"
} ### 4. `get_footprint_info`
```
Get detailed information about a specific footprint.
**Returns:**
```json **Parameters:**
{
"success": true, ```json
"footprint_info": { {
"library": "Resistor_SMD", "footprint": "Resistor_SMD:R_0603_1608Metric"
"footprint": "R_0603_1608Metric", }
"full_name": "Resistor_SMD:R_0603_1608Metric", ```
"library_path": "/usr/share/kicad/footprints/Resistor_SMD.pretty"
} **Returns:**
}
``` ```json
{
## Updated Component Placement "success": true,
"footprint_info": {
The `place_component` tool now uses the library system: "library": "Resistor_SMD",
"footprint": "R_0603_1608Metric",
```json "full_name": "Resistor_SMD:R_0603_1608Metric",
{ "library_path": "/usr/share/kicad/footprints/Resistor_SMD.pretty"
"componentId": "Resistor_SMD:R_0603_1608Metric", // Library:Footprint format }
"position": {"x": 50, "y": 40, "unit": "mm"}, }
"reference": "R1", ```
"value": "10k",
"rotation": 0, ## Updated Component Placement
"layer": "F.Cu"
} The `place_component` tool now uses the library system:
```
```json
**Features:** {
- ✅ Automatic footprint discovery across all libraries "componentId": "Resistor_SMD:R_0603_1608Metric", // Library:Footprint format
- ✅ Helpful error messages with suggestions "position": { "x": 50, "y": 40, "unit": "mm" },
- ✅ Supports KiCAD 9.0 API (EDA_ANGLE, GetFPIDAsString) "reference": "R1",
"value": "10k",
## Example Usage (Claude Code) "rotation": 0,
"layer": "F.Cu"
**Search for a resistor footprint:** }
``` ```
User: "Find me a 0603 resistor footprint"
**Features:**
Claude: [uses search_footprints tool with pattern "*R_0603*"]
Found: Resistor_SMD:R_0603_1608Metric - ✅ Automatic footprint discovery across all libraries
``` - ✅ Helpful error messages with suggestions
- ✅ Supports KiCAD 9.0 API (EDA_ANGLE, GetFPIDAsString)
**Place a component:**
``` ## Example Usage (Claude Code)
User: "Place a 10k 0603 resistor at 50,40mm"
**Search for a resistor footprint:**
Claude: [uses place_component with "Resistor_SMD:R_0603_1608Metric"]
✅ Placed R1: 10k at (50, 40) mm ```
``` User: "Find me a 0603 resistor footprint"
**List available capacitors:** Claude: [uses search_footprints tool with pattern "*R_0603*"]
``` Found: Resistor_SMD:R_0603_1608Metric
User: "What capacitor footprints are available?" ```
Claude: [uses list_library_footprints with "Capacitor_SMD"] **Place a component:**
Found 103 capacitor footprints including:
- C_0402_1005Metric ```
- C_0603_1608Metric User: "Place a 10k 0603 resistor at 50,40mm"
- C_0805_2012Metric
... Claude: [uses place_component with "Resistor_SMD:R_0603_1608Metric"]
``` ✅ Placed R1: 10k at (50, 40) mm
```
## Configuration
**List available capacitors:**
### Custom Library Paths
```
The system automatically detects KiCAD installations, but you can add custom libraries: User: "What capacitor footprints are available?"
1. **Via KiCAD Preferences:** Claude: [uses list_library_footprints with "Capacitor_SMD"]
- Open KiCAD → Preferences → Manage Footprint Libraries Found 103 capacitor footprints including:
- Add your custom library paths - C_0402_1005Metric
- The MCP server will automatically discover them - C_0603_1608Metric
- C_0805_2012Metric
2. **Via Project fp-lib-table:** ...
- Create `fp-lib-table` in your project directory ```
- Follow the KiCAD S-expression format
## Configuration
### Supported Platforms
### Custom Library Paths
-**Linux:** `/usr/share/kicad/footprints`, `~/.config/kicad/9.0/`
-**Windows:** `C:/Program Files/KiCAD/*/share/kicad/footprints` The system automatically detects KiCAD installations, but you can add custom libraries:
-**macOS:** `/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints`
1. **Via KiCAD Preferences:**
## KiCAD 9.0 API Compatibility - Open KiCAD → Preferences → Manage Footprint Libraries
- Add your custom library paths
The library integration includes full KiCAD 9.0 API support: - The MCP server will automatically discover them
### Fixed API Changes: 2. **Via Project fp-lib-table:**
1.`SetOrientation()` → now uses `EDA_ANGLE(degrees, DEGREES_T)` - Create `fp-lib-table` in your project directory
2.`GetOrientation()` → returns `EDA_ANGLE`, call `.AsDegrees()` - Follow the KiCAD S-expression format
3.`GetFootprintName()` → now `GetFPIDAsString()`
### Supported Platforms
### Example Fixes:
**Old (KiCAD 8.0):** -**Linux:** `/usr/share/kicad/footprints`, `~/.config/kicad/9.0/`
```python -**Windows:** `C:/Program Files/KiCAD/*/share/kicad/footprints`
module.SetOrientation(90 * 10) # Decidegrees -**macOS:** `/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints`
rotation = module.GetOrientation() / 10
``` ## KiCAD 9.0 API Compatibility
**New (KiCAD 9.0):** The library integration includes full KiCAD 9.0 API support:
```python
angle = pcbnew.EDA_ANGLE(90, pcbnew.DEGREES_T) ### Fixed API Changes:
module.SetOrientation(angle)
rotation = module.GetOrientation().AsDegrees() 1.`SetOrientation()` → now uses `EDA_ANGLE(degrees, DEGREES_T)`
``` 2.`GetOrientation()` → returns `EDA_ANGLE`, call `.AsDegrees()`
3.`GetFootprintName()` → now `GetFPIDAsString()`
## Implementation Details
### Example Fixes:
### LibraryManager Class
**Old (KiCAD 8.0):**
**Location:** `python/commands/library.py`
```python
**Key Methods:** module.SetOrientation(90 * 10) # Decidegrees
- `_load_libraries()` - Parse fp-lib-table files rotation = module.GetOrientation() / 10
- `_parse_fp_lib_table()` - S-expression parser ```
- `_resolve_uri()` - Handle environment variables
- `find_footprint()` - Locate footprint in libraries **New (KiCAD 9.0):**
- `search_footprints()` - Pattern-based search
- `list_footprints()` - List library contents ```python
angle = pcbnew.EDA_ANGLE(90, pcbnew.DEGREES_T)
**Performance:** module.SetOrientation(angle)
- Libraries loaded once at startup rotation = module.GetOrientation().AsDegrees()
- Footprint lists cached on first access ```
- Fast search using Python regex
- Minimal memory footprint ## Implementation Details
### Integration Points ### LibraryManager Class
1. **KiCADInterface (`kicad_interface.py`):** **Location:** `python/commands/library.py`
- Creates `FootprintLibraryManager` on init
- Passes to `ComponentCommands` **Key Methods:**
- Routes library commands
- `_load_libraries()` - Parse fp-lib-table files
2. **ComponentCommands (`component.py`):** - `_parse_fp_lib_table()` - S-expression parser
- Uses `LibraryManager.find_footprint()` - `_resolve_uri()` - Handle environment variables
- Provides suggestions on errors - `find_footprint()` - Locate footprint in libraries
- Supports both lookup formats - `search_footprints()` - Pattern-based search
- `list_footprints()` - List library contents
3. **MCP Tools (`src/tools/index.ts`):**
- Exposes 4 new library tools **Performance:**
- Fully typed TypeScript interfaces
- Documented parameters - Libraries loaded once at startup
- Footprint lists cached on first access
## Testing - Fast search using Python regex
- Minimal memory footprint
**Test Coverage:**
- ✅ Library path discovery (Linux/Windows/macOS) ### Integration Points
- ✅ fp-lib-table parsing
- ✅ Environment variable resolution 1. **KiCADInterface (`kicad_interface.py`):**
- ✅ Footprint search and lookup - Creates `FootprintLibraryManager` on init
- ✅ Component placement integration - Passes to `ComponentCommands`
- ✅ Error handling and suggestions - Routes library commands
**Verified With:** 2. **ComponentCommands (`component.py`):**
- KiCAD 9.0.5 on Ubuntu 24.04 - Uses `LibraryManager.find_footprint()`
- 153 standard libraries (8,000+ footprints) - Provides suggestions on errors
- pcbnew Python API - Supports both lookup formats
## Known Limitations 3. **MCP Tools (`src/tools/index.ts`):**
- Exposes 4 new library tools
1. **Library Updates:** Changes to fp-lib-table require server restart - Fully typed TypeScript interfaces
2. **Custom Libraries:** Must be added via KiCAD preferences first - Documented parameters
3. **Network Libraries:** GitHub-based libraries not yet supported
4. **Search Performance:** Linear search across all libraries (fast for <200 libs) ## Testing
## Future Enhancements **Test Coverage:**
- [ ] Watch fp-lib-table for changes (auto-reload) - ✅ Library path discovery (Linux/Windows/macOS)
- [ ] Support for GitHub library URLs - ✅ fp-lib-table parsing
- [ ] Fuzzy search for typo tolerance - ✅ Environment variable resolution
- [ ] Library metadata (descriptions, categories) - ✅ Footprint search and lookup
- [ ] Footprint previews (SVG/PNG generation) - ✅ Component placement integration
- [ ] Most-used footprints caching - ✅ Error handling and suggestions
## Troubleshooting **Verified With:**
### "No footprint libraries found" - KiCAD 9.0.5 on Ubuntu 24.04
- 153 standard libraries (8,000+ footprints)
**Cause:** fp-lib-table not found or empty - pcbnew Python API
**Solution:** ## Known Limitations
1. Verify KiCAD is installed
2. Open KiCAD and ensure libraries are configured 1. **Library Updates:** Changes to fp-lib-table require server restart
3. Check `~/.config/kicad/9.0/fp-lib-table` exists 2. **Custom Libraries:** Must be added via KiCAD preferences first
3. **Network Libraries:** GitHub-based libraries not yet supported
### "Footprint not found" 4. **Search Performance:** Linear search across all libraries (fast for <200 libs)
**Cause:** Footprint doesn't exist or library not loaded ## Future Enhancements
**Solution:** - [ ] Watch fp-lib-table for changes (auto-reload)
1. Use `search_footprints` to find similar footprints - [ ] Support for GitHub library URLs
2. Check library name is correct - [ ] Fuzzy search for typo tolerance
3. Verify library is in fp-lib-table - [ ] Library metadata (descriptions, categories)
- [ ] Footprint previews (SVG/PNG generation)
### "Failed to load footprint" - [ ] Most-used footprints caching
**Cause:** Corrupt .kicad_mod file or permissions issue ## Troubleshooting
**Solution:** ### "No footprint libraries found"
1. Check file permissions on library directories
2. Reinstall KiCAD libraries if corrupt **Cause:** fp-lib-table not found or empty
3. Check logs for detailed error
**Solution:**
## Related Documentation
1. Verify KiCAD is installed
- [ROADMAP.md](./ROADMAP.md) - Week 2 planning 2. Open KiCAD and ensure libraries are configured
- [STATUS_SUMMARY.md](./STATUS_SUMMARY.md) - Current implementation status 3. Check `~/.config/kicad/9.0/fp-lib-table` exists
- [API.md](./API.md) - Full MCP API reference
- [KiCAD Documentation](https://docs.kicad.org/9.0/en/pcbnew/pcbnew.html) - Official KiCAD docs ### "Footprint not found"
## Changelog **Cause:** Footprint doesn't exist or library not loaded
**2026-03-21 - v2.2.3+** **Solution:**
- Project-local symbol library support (v2.2.2)
- Project-local footprint library support (v2.2.2) 1. Use `search_footprints` to find similar footprints
- Implemented LibraryManager class 2. Check library name is correct
- Added 4 new MCP library tools 3. Verify library is in fp-lib-table
- Updated component placement to use libraries
- Fixed all KiCAD 9.0 API compatibility issues ### "Failed to load footprint"
- Tested end-to-end with real components
- Created comprehensive documentation **Cause:** Corrupt .kicad_mod file or permissions issue
--- **Solution:**
**Status: PRODUCTION READY** 🎉 1. Check file permissions on library directories
2. Reinstall KiCAD libraries if corrupt
The library integration is complete and fully functional. Component placement now works seamlessly with KiCAD's footprint libraries, enabling AI-driven PCB design with real, validated components. 3. Check logs for detailed error
## Related Documentation
- [ROADMAP.md](./ROADMAP.md) - Week 2 planning
- [STATUS_SUMMARY.md](./STATUS_SUMMARY.md) - Current implementation status
- [API.md](./API.md) - Full MCP API reference
- [KiCAD Documentation](https://docs.kicad.org/9.0/en/pcbnew/pcbnew.html) - Official KiCAD docs
## 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
- Added 4 new MCP library tools
- Updated component placement to use libraries
- Fixed all KiCAD 9.0 API compatibility issues
- Tested end-to-end with real components
- Created comprehensive documentation
---
**Status: PRODUCTION READY** 🎉
The library integration is complete and fully functional. Component placement now works seamlessly with KiCAD's footprint libraries, enabling AI-driven PCB design with real, validated components.

View File

@@ -1,313 +1,336 @@
# Linux Compatibility Audit Report # Linux Compatibility Audit Report
**Date:** 2025-10-25
**Target Platform:** Ubuntu 24.04 LTS (primary), Fedora, Arch (secondary) **Date:** 2025-10-25
**Current Status:** Windows-optimized, partial Linux support **Target Platform:** Ubuntu 24.04 LTS (primary), Fedora, Arch (secondary)
**Current Status:** Windows-optimized, partial Linux support
---
---
## Executive Summary
## Executive Summary
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.
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 **Overall Status:** 🟡 **PARTIAL COMPATIBILITY**
- 🟡 Python interface: Mixed (some hardcoded paths)
- ❌ Configuration: Windows-specific examples - ✅ TypeScript server: Good cross-platform support
- ❌ Documentation: Windows-only instructions - 🟡 Python interface: Mixed (some hardcoded paths)
- ❌ Configuration: Windows-specific examples
--- - ❌ Documentation: Windows-only instructions
## Critical Issues (P0 - Must Fix) ---
### 1. Hardcoded Windows Paths in Config Examples ## Critical Issues (P0 - Must Fix)
**File:** `config/claude-desktop-config.json`
```json ### 1. Hardcoded Windows Paths in Config Examples
"cwd": "c:/repo/KiCAD-MCP",
"PYTHONPATH": "C:/Program Files/KiCad/9.0/lib/python3/dist-packages" **File:** `config/claude-desktop-config.json`
```
```json
**Impact:** Config file won't work on Linux without manual editing "cwd": "c:/repo/KiCAD-MCP",
**Fix:** Create platform-specific config templates "PYTHONPATH": "C:/Program Files/KiCad/9.0/lib/python3/dist-packages"
**Priority:** P0 ```
--- **Impact:** Config file won't work on Linux without manual editing
**Fix:** Create platform-specific config templates
### 2. Library Search Paths (Mixed Approach) **Priority:** P0
**File:** `python/commands/library_schematic.py:16`
```python ---
search_paths = [
"C:/Program Files/KiCad/*/share/kicad/symbols/*.kicad_sym", # Windows ### 2. Library Search Paths (Mixed Approach)
"/usr/share/kicad/symbols/*.kicad_sym", # Linux
"/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/*.kicad_sym", # macOS **File:** `python/commands/library_schematic.py:16`
]
``` ```python
search_paths = [
**Impact:** Works but inefficient (checks all platforms) "C:/Program Files/KiCad/*/share/kicad/symbols/*.kicad_sym", # Windows
**Fix:** Auto-detect platform and use appropriate paths "/usr/share/kicad/symbols/*.kicad_sym", # Linux
**Priority:** P0 "/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/*.kicad_sym", # macOS
]
--- ```
### 3. Python Path Detection **Impact:** Works but inefficient (checks all platforms)
**File:** `python/kicad_interface.py:38-45` **Fix:** Auto-detect platform and use appropriate paths
```python **Priority:** P0
kicad_paths = [
os.path.join(os.path.dirname(sys.executable), 'Lib', 'site-packages'), ---
os.path.dirname(sys.executable)
] ### 3. Python Path Detection
```
**File:** `python/kicad_interface.py:38-45`
**Impact:** Paths use Windows convention ('Lib' is 'lib' on Linux)
**Fix:** Platform-specific path detection ```python
**Priority:** P0 kicad_paths = [
os.path.join(os.path.dirname(sys.executable), 'Lib', 'site-packages'),
--- os.path.dirname(sys.executable)
]
## High Priority Issues (P1) ```
### 4. Documentation is Windows-Only **Impact:** Paths use Windows convention ('Lib' is 'lib' on Linux)
**Files:** `README.md`, installation instructions **Fix:** Platform-specific path detection
**Priority:** P0
**Issues:**
- Installation paths reference `C:\Program Files` ---
- VSCode settings path is Windows format
- No Linux-specific troubleshooting ## High Priority Issues (P1)
**Fix:** Add Linux installation section ### 4. Documentation is Windows-Only
**Priority:** P1
**Files:** `README.md`, installation instructions
---
**Issues:**
### 5. Missing Python Dependencies Documentation
**File:** None (no requirements.txt) - Installation paths reference `C:\Program Files`
- VSCode settings path is Windows format
**Impact:** Users don't know what Python packages to install - No Linux-specific troubleshooting
**Fix:** Create `requirements.txt` and `requirements-dev.txt`
**Priority:** P1 **Fix:** Add Linux installation section
**Priority:** P1
---
---
### 6. Path Handling Uses os.path Instead of pathlib
**Files:** All Python files (11 files) ### 5. Missing Python Dependencies Documentation
**Impact:** Code is less readable and more error-prone **File:** None (no requirements.txt)
**Fix:** Migrate to `pathlib.Path` throughout
**Priority:** P1 **Impact:** Users don't know what Python packages to install
**Fix:** Create `requirements.txt` and `requirements-dev.txt`
--- **Priority:** P1
## Medium Priority Issues (P2) ---
### 7. No Linux-Specific Testing ### 6. Path Handling Uses os.path Instead of pathlib
**Impact:** Can't verify Linux compatibility
**Fix:** Add GitHub Actions with Ubuntu runner **Files:** All Python files (11 files)
**Priority:** P2
**Impact:** Code is less readable and more error-prone
--- **Fix:** Migrate to `pathlib.Path` throughout
**Priority:** P1
### 8. Log File Paths May Differ
**File:** `src/logger.ts:13` ---
```typescript
const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs'); ## Medium Priority Issues (P2)
```
### 7. No Linux-Specific Testing
**Impact:** `.kicad-mcp` is okay for Linux, but best practice is `~/.config/kicad-mcp`
**Fix:** Use XDG Base Directory spec on Linux **Impact:** Can't verify Linux compatibility
**Priority:** P2 **Fix:** Add GitHub Actions with Ubuntu runner
**Priority:** P2
---
---
### 9. No Bash/Shell Scripts for Linux
**Impact:** Manual setup is harder on Linux ### 8. Log File Paths May Differ
**Fix:** Create `install.sh` and `run.sh` scripts
**Priority:** P2 **File:** `src/logger.ts:13`
--- ```typescript
const DEFAULT_LOG_DIR = join(os.homedir(), ".kicad-mcp", "logs");
## Low Priority Issues (P3) ```
### 10. TypeScript Build Uses Windows Conventions **Impact:** `.kicad-mcp` is okay for Linux, but best practice is `~/.config/kicad-mcp`
**File:** `package.json` **Fix:** Use XDG Base Directory spec on Linux
**Priority:** P2
**Impact:** Works but could be more Linux-friendly
**Fix:** Add platform-specific build scripts ---
**Priority:** P3
### 9. No Bash/Shell Scripts for Linux
---
**Impact:** Manual setup is harder on Linux
## Positive Findings ✅ **Fix:** Create `install.sh` and `run.sh` scripts
**Priority:** P2
### What's Already Good:
---
1. **TypeScript Path Handling** - Uses `path.join()` and `os.homedir()` correctly
2. **Node.js Dependencies** - All cross-platform ## Low Priority Issues (P3)
3. **JSON Communication** - Platform-agnostic
4. **Python Base** - Python 3 works identically on all platforms ### 10. TypeScript Build Uses Windows Conventions
--- **File:** `package.json`
## Recommended Fixes - Priority Order **Impact:** Works but could be more Linux-friendly
**Fix:** Add platform-specific build scripts
### **Week 1 - Critical Fixes (P0)** **Priority:** P3
1. **Create Platform-Specific Config Templates** ---
```bash
config/ ## Positive Findings ✅
├── linux-config.example.json
├── windows-config.example.json ### What's Already Good:
└── macos-config.example.json
``` 1. **TypeScript Path Handling** - Uses `path.join()` and `os.homedir()` correctly
2. **Node.js Dependencies** - All cross-platform
2. **Fix Python Path Detection** 3. **JSON Communication** - Platform-agnostic
```python 4. **Python Base** - Python 3 works identically on all platforms
# Detect platform and set appropriate paths
import platform ---
import sys
from pathlib import Path ## Recommended Fixes - Priority Order
if platform.system() == "Windows": ### **Week 1 - Critical Fixes (P0)**
kicad_paths = [Path(sys.executable).parent / "Lib" / "site-packages"]
else: # Linux/Mac 1. **Create Platform-Specific Config Templates**
kicad_paths = [Path(sys.executable).parent / "lib" / "python3.X" / "site-packages"]
``` ```bash
config/
3. **Update Library Search Path Logic** ├── linux-config.example.json
```python ├── windows-config.example.json
def get_kicad_library_paths(): └── macos-config.example.json
"""Auto-detect KiCAD library paths based on platform""" ```
system = platform.system()
if system == "Windows": 2. **Fix Python Path Detection**
return ["C:/Program Files/KiCad/*/share/kicad/symbols/*.kicad_sym"]
elif system == "Linux": ```python
return ["/usr/share/kicad/symbols/*.kicad_sym"] # Detect platform and set appropriate paths
elif system == "Darwin": # macOS import platform
return ["/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/*.kicad_sym"] import sys
``` from pathlib import Path
### **Week 1 - High Priority (P1)** if platform.system() == "Windows":
kicad_paths = [Path(sys.executable).parent / "Lib" / "site-packages"]
4. **Create requirements.txt** else: # Linux/Mac
```txt kicad_paths = [Path(sys.executable).parent / "lib" / "python3.X" / "site-packages"]
# requirements.txt ```
kicad-skip>=0.1.0
Pillow>=9.0.0 3. **Update Library Search Path Logic**
cairosvg>=2.7.0 ```python
colorlog>=6.7.0 def get_kicad_library_paths():
``` """Auto-detect KiCAD library paths based on platform"""
system = platform.system()
5. **Add Linux Installation Documentation** if system == "Windows":
- Ubuntu/Debian instructions return ["C:/Program Files/KiCad/*/share/kicad/symbols/*.kicad_sym"]
- Fedora/RHEL instructions elif system == "Linux":
- Arch Linux instructions return ["/usr/share/kicad/symbols/*.kicad_sym"]
elif system == "Darwin": # macOS
6. **Migrate to pathlib** return ["/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/*.kicad_sym"]
- Convert all `os.path` calls to `Path` ```
- More Pythonic and readable
### **Week 1 - High Priority (P1)**
---
4. **Create requirements.txt**
## Testing Checklist
```txt
### Ubuntu 24.04 LTS Testing # requirements.txt
- [ ] Install KiCAD 9.0 from official PPA kicad-skip>=0.1.0
- [ ] Install Node.js 18+ from NodeSource Pillow>=9.0.0
- [ ] Clone repository cairosvg>=2.7.0
- [ ] Run `npm install` colorlog>=6.7.0
- [ ] Run `npm run build` ```
- [ ] Configure MCP settings (Cline)
- [ ] Test: Create project 5. **Add Linux Installation Documentation**
- [ ] Test: Place components - Ubuntu/Debian instructions
- [ ] Test: Export Gerbers - Fedora/RHEL instructions
- Arch Linux instructions
### Fedora Testing
- [ ] Install KiCAD from Fedora repos 6. **Migrate to pathlib**
- [ ] Test same workflow - Convert all `os.path` calls to `Path`
- More Pythonic and readable
### Arch Testing
- [ ] Install KiCAD from AUR ---
- [ ] Test same workflow
## Testing Checklist
---
### Ubuntu 24.04 LTS Testing
## Platform Detection Helper
- [ ] Install KiCAD 9.0 from official PPA
Create `python/utils/platform_helper.py`: - [ ] Install Node.js 18+ from NodeSource
- [ ] Clone repository
```python - [ ] Run `npm install`
"""Platform detection and path utilities""" - [ ] Run `npm run build`
import platform - [ ] Configure MCP settings (Cline)
import sys - [ ] Test: Create project
from pathlib import Path - [ ] Test: Place components
from typing import List - [ ] Test: Export Gerbers
class PlatformHelper: ### Fedora Testing
@staticmethod
def is_windows() -> bool: - [ ] Install KiCAD from Fedora repos
return platform.system() == "Windows" - [ ] Test same workflow
@staticmethod ### Arch Testing
def is_linux() -> bool:
return platform.system() == "Linux" - [ ] Install KiCAD from AUR
- [ ] Test same workflow
@staticmethod
def is_macos() -> bool: ---
return platform.system() == "Darwin"
## Platform Detection Helper
@staticmethod
def get_kicad_python_path() -> Path: Create `python/utils/platform_helper.py`:
"""Get KiCAD Python dist-packages path"""
if PlatformHelper.is_windows(): ```python
return Path("C:/Program Files/KiCad/9.0/lib/python3/dist-packages") """Platform detection and path utilities"""
elif PlatformHelper.is_linux(): import platform
# Common Linux paths import sys
candidates = [ from pathlib import Path
Path("/usr/lib/kicad/lib/python3/dist-packages"), from typing import List
Path("/usr/share/kicad/scripting/plugins"),
] class PlatformHelper:
for path in candidates: @staticmethod
if path.exists(): def is_windows() -> bool:
return path return platform.system() == "Windows"
elif PlatformHelper.is_macos():
return Path("/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/3.X/lib/python3.X/site-packages") @staticmethod
def is_linux() -> bool:
raise RuntimeError(f"Could not find KiCAD Python path for {platform.system()}") return platform.system() == "Linux"
@staticmethod @staticmethod
def get_config_dir() -> Path: def is_macos() -> bool:
"""Get appropriate config directory""" return platform.system() == "Darwin"
if PlatformHelper.is_windows():
return Path.home() / ".kicad-mcp" @staticmethod
elif PlatformHelper.is_linux(): def get_kicad_python_path() -> Path:
# Use XDG Base Directory specification """Get KiCAD Python dist-packages path"""
xdg_config = os.environ.get("XDG_CONFIG_HOME") if PlatformHelper.is_windows():
if xdg_config: return Path("C:/Program Files/KiCad/9.0/lib/python3/dist-packages")
return Path(xdg_config) / "kicad-mcp" elif PlatformHelper.is_linux():
return Path.home() / ".config" / "kicad-mcp" # Common Linux paths
elif PlatformHelper.is_macos(): candidates = [
return Path.home() / "Library" / "Application Support" / "kicad-mcp" Path("/usr/lib/kicad/lib/python3/dist-packages"),
``` Path("/usr/share/kicad/scripting/plugins"),
]
--- for path in candidates:
if path.exists():
## Success Criteria return path
elif PlatformHelper.is_macos():
✅ Server starts on Ubuntu 24.04 LTS without errors return Path("/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/3.X/lib/python3.X/site-packages")
✅ Can create and manipulate KiCAD projects
✅ CI/CD pipeline tests on Linux raise RuntimeError(f"Could not find KiCAD Python path for {platform.system()}")
✅ Documentation includes Linux setup
✅ All tests pass on Linux @staticmethod
def get_config_dir() -> Path:
--- """Get appropriate config directory"""
if PlatformHelper.is_windows():
## Next Steps return Path.home() / ".kicad-mcp"
elif PlatformHelper.is_linux():
1. Implement P0 fixes (this week) # Use XDG Base Directory specification
2. Set up GitHub Actions CI/CD xdg_config = os.environ.get("XDG_CONFIG_HOME")
3. Test on Ubuntu 24.04 LTS if xdg_config:
4. Document Linux-specific issues return Path(xdg_config) / "kicad-mcp"
5. Create installation scripts return Path.home() / ".config" / "kicad-mcp"
elif PlatformHelper.is_macos():
--- return Path.home() / "Library" / "Application Support" / "kicad-mcp"
```
**Audited by:** Claude Code
**Review Status:** ✅ Complete ---
## Success Criteria
✅ Server starts on Ubuntu 24.04 LTS without errors
✅ Can create and manipulate KiCAD projects
✅ CI/CD pipeline tests on Linux
✅ Documentation includes Linux setup
✅ All tests pass on Linux
---
## Next Steps
1. Implement P0 fixes (this week)
2. Set up GitHub Actions CI/CD
3. Test on Ubuntu 24.04 LTS
4. Document Linux-specific issues
5. Create installation scripts
---
**Audited by:** Claude Code
**Review Status:** ✅ Complete

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,416 +1,441 @@
# Real-Time Collaboration Workflow # Real-Time Collaboration Workflow
**Status:** ✅ TESTED AND WORKING **Status:** ✅ TESTED AND WORKING
**Date:** 2025-11-01 **Date:** 2025-11-01
**Version:** 2.1.0-alpha **Version:** 2.1.0-alpha
## Overview ## Overview
The KiCAD MCP Server enables **real-time paired circuit board design** between Claude Code (via MCP) and a human designer using the KiCAD UI. Both workflows have been tested and confirmed working: The KiCAD MCP Server enables **real-time paired circuit board design** between Claude Code (via MCP) and a human designer using the KiCAD UI. Both workflows have been tested and confirmed working:
-**MCP→UI**: AI places components, human sees them in KiCAD -**MCP→UI**: AI places components, human sees them in KiCAD
-**UI→MCP**: Human edits board, AI reads changes back -**UI→MCP**: Human edits board, AI reads changes back
## How It Works ## How It Works
### Architecture ### Architecture
The MCP server uses KiCAD's Python API (`pcbnew` module) to read and write `.kicad_pcb` files. The KiCAD UI and MCP both operate on the same file, enabling collaboration through the file system. The MCP server uses KiCAD's Python API (`pcbnew` module) to read and write `.kicad_pcb` files. The KiCAD UI and MCP both operate on the same file, enabling collaboration through the file system.
``` ```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ Claude Code │ │ Human Designer │ │ Claude Code │ │ Human Designer │
│ (via MCP) │ │ (KiCAD UI) │ │ (via MCP) │ │ (KiCAD UI) │
└────────┬────────┘ └────────┬─────────┘ └────────┬────────┘ └────────┬─────────┘
│ │ │ │
│ pcbnew Python API │ KiCAD UI │ pcbnew Python API │ KiCAD UI
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ project.kicad_pcb (file system) │ │ project.kicad_pcb (file system) │
└─────────────────────────────────────┘ └─────────────────────────────────────┘
``` ```
### MCP→UI Workflow (AI to Human) ### MCP→UI Workflow (AI to Human)
**Use case:** Claude places components via MCP, human sees them in KiCAD UI **Use case:** Claude places components via MCP, human sees them in KiCAD UI
1. **Claude places components** via MCP tools: 1. **Claude places components** via MCP tools:
```python
# MCP internally uses: ```python
board = pcbnew.LoadBoard('project.kicad_pcb') # MCP internally uses:
module = pcbnew.FootprintLoad(library_path, 'R_0603_1608Metric') board = pcbnew.LoadBoard('project.kicad_pcb')
module.SetPosition(pcbnew.VECTOR2I(x_nm, y_nm)) module = pcbnew.FootprintLoad(library_path, 'R_0603_1608Metric')
board.Add(module) module.SetPosition(pcbnew.VECTOR2I(x_nm, y_nm))
pcbnew.SaveBoard('project.kicad_pcb', board) board.Add(module)
``` pcbnew.SaveBoard('project.kicad_pcb', board)
```
2. **Human opens/reloads in KiCAD UI:**
- **Option A (first time):** Open the project in KiCAD 2. **Human opens/reloads in KiCAD UI:**
- **Option B (already open):** File → Revert or close and reopen the PCB editor - **Option A (first time):** Open the project in KiCAD
- Components appear instantly ✅ - **Option B (already open):** File → Revert or close and reopen the PCB editor
- Components appear instantly ✅
**Example:**
``` **Example:**
User: "Place a 10k resistor at position 30, 30mm"
Claude: [uses place_component MCP tool] ```
✅ Placed R1: 10k at (30.0, 30.0) mm User: "Place a 10k resistor at position 30, 30mm"
User: [opens KiCAD UI] Claude: [uses place_component MCP tool]
[sees R1 at the specified position] ✅ Placed R1: 10k at (30.0, 30.0) mm
``` User: [opens KiCAD UI]
[sees R1 at the specified position]
### UI→MCP Workflow (Human to AI) ```
**Use case:** Human edits board in KiCAD UI, Claude reads changes via MCP ### UI→MCP Workflow (Human to AI)
1. **Human makes changes in KiCAD UI:** **Use case:** Human edits board in KiCAD UI, Claude reads changes via MCP
- Move components
- Add new components 1. **Human makes changes in KiCAD UI:**
- Route traces - Move components
- Edit properties - Add new components
- Route traces
2. **Human saves the file:** - Edit properties
- Ctrl+S or File → Save
- KiCAD writes changes to `.kicad_pcb` file 2. **Human saves the file:**
- Ctrl+S or File → Save
3. **Claude reads changes** via MCP tools: - KiCAD writes changes to `.kicad_pcb` file
```python
# MCP internally uses: 3. **Claude reads changes** via MCP tools:
board = pcbnew.LoadBoard('project.kicad_pcb')
footprints = board.GetFootprints() ```python
# Reads all current component positions, values, etc. # MCP internally uses:
``` board = pcbnew.LoadBoard('project.kicad_pcb')
footprints = board.GetFootprints()
4. **Claude can see the updates:** # Reads all current component positions, values, etc.
- New component positions ```
- Added/removed components
- Updated values and references 4. **Claude can see the updates:**
- New traces and nets - New component positions
- Added/removed components
**Example:** - Updated values and references
``` - New traces and nets
User: "I moved R1 to a new position, can you see it?"
Claude: [uses get_board_info MCP tool] **Example:**
Yes! I can see R1 is now at (59.175, 49.0) mm
(previously it was at 30.0, 30.0 mm) ```
``` User: "I moved R1 to a new position, can you see it?"
Claude: [uses get_board_info MCP tool]
## Tested Workflows Yes! I can see R1 is now at (59.175, 49.0) mm
(previously it was at 30.0, 30.0 mm)
### Test 1: MCP→UI (Verified ✅) ```
**Setup:** ## Tested Workflows
- Created new board via MCP (100x80mm)
- Placed R1 (10k resistor) at (30, 30) mm ### Test 1: MCP→UI (Verified ✅)
- Placed D1 (RED LED) at (50, 30) mm
**Setup:**
**Result:**
- Opened KiCAD PCB editor - Created new board via MCP (100x80mm)
- Both components visible at correct positions ✅ - Placed R1 (10k resistor) at (30, 30) mm
- All properties (reference, value, rotation) correct ✅ - Placed D1 (RED LED) at (50, 30) mm
### Test 2: UI→MCP (Verified ✅) **Result:**
**Setup:** - Opened KiCAD PCB editor
- User moved R1 from (30, 30) mm to (59.175, 49.0) mm in UI - Both components visible at correct positions ✅
- User saved file (Ctrl+S) - All properties (reference, value, rotation) correct ✅
**Result:** ### Test 2: UI→MCP (Verified ✅)
- MCP read board via `get_board_info`
- New position detected correctly ✅ **Setup:**
- D1 position unchanged (as expected) ✅
- User moved R1 from (30, 30) mm to (59.175, 49.0) mm in UI
## Current Capabilities - User saved file (Ctrl+S)
### What Works **Result:**
1. **Bidirectional sync** (via file save/reload) - MCP read board via `get_board_info`
2. **Component placement** (MCP→UI) - New position detected correctly ✅
3. **Component reading** (UI→MCP) - D1 position unchanged (as expected) ✅
4. **Position/rotation updates** (both directions)
5. **Value/reference changes** (both directions) ## Current Capabilities
6. **Trace routing** (both directions)
7. **Net information** (both directions) ### What Works
8. **Board properties** (size, layers, design rules)
1. **Bidirectional sync** (via file save/reload)
### MCP Tools for Collaboration 2. **Component placement** (MCP→UI)
3. **Component reading** (UI→MCP)
**Reading board state:** 4. **Position/rotation updates** (both directions)
- `get_board_info` - Get all components and their positions 5. **Value/reference changes** (both directions)
- `get_project_info` - Get project metadata 6. **Trace routing** (both directions)
- `list_components` - List all components (if implemented) 7. **Net information** (both directions)
8. **Board properties** (size, layers, design rules)
**Modifying board:**
- `place_component` - Add new components ### MCP Tools for Collaboration
- `add_trace` - Add copper traces
- `add_via` - Add vias **Reading board state:**
- `add_copper_pour` - Add copper zones
- `add_mounting_hole` - Add mounting holes - `get_board_info` - Get all components and their positions
- `add_board_text` - Add text to board - `get_project_info` - Get project metadata
- `list_components` - List all components (if implemented)
## Limitations
**Modifying board:**
### Current Limitations
- `place_component` - Add new components
1. **Manual Save Required** - `add_trace` - Add copper traces
- UI changes require manual save (Ctrl+S) - `add_via` - Add vias
- No automatic file watching (yet) - `add_copper_pour` - Add copper zones
- Workaround: Always save before asking Claude - `add_mounting_hole` - Add mounting holes
- `add_board_text` - Add text to board
2. **Manual Reload Required**
- MCP changes require reload in UI ## Limitations
- Options: File → Revert, or close/reopen
- Future: Could implement auto-reload trigger ### Current Limitations
3. **No Live Sync** 1. **Manual Save Required**
- Changes not visible until save/reload - UI changes require manual save (Ctrl+S)
- Not truly "real-time" (more like "near-time") - No automatic file watching (yet)
- File-based sync has ~1-5 second latency - Workaround: Always save before asking Claude
4. **No Conflict Detection** 2. **Manual Reload Required**
- If both edit simultaneously, last save wins - MCP changes require reload in UI
- No merge conflict resolution - Options: File → Revert, or close/reopen
- Best practice: Take turns editing - Future: Could implement auto-reload trigger
5. **No Change Notifications** 3. **No Live Sync**
- MCP doesn't know when UI saves - Changes not visible until save/reload
- UI doesn't know when MCP saves - Not truly "real-time" (more like "near-time")
- Future: Could add file system watchers - File-based sync has ~1-5 second latency
### Known Issues 4. **No Conflict Detection**
- If both edit simultaneously, last save wins
1. **Zone Filling:** Copper pours created by MCP won't be filled (requires UI to fill) - No merge conflict resolution
2. **Undo History:** UI undo history lost after MCP changes - Best practice: Take turns editing
3. **DRC Errors:** MCP doesn't run design rule checks automatically
5. **No Change Notifications**
## Best Practices - MCP doesn't know when UI saves
- UI doesn't know when MCP saves
### For AI-Human Collaboration - Future: Could add file system watchers
1. **Establish Turn-Taking:** ### Known Issues
```
User: "I'm going to add some components, one sec" 1. **Zone Filling:** Copper pours created by MCP won't be filled (requires UI to fill)
[User edits in UI] 2. **Undo History:** UI undo history lost after MCP changes
User: "Done, saved the file" 3. **DRC Errors:** MCP doesn't run design rule checks automatically
Claude: [reads changes] "I see you added C1 and C2..."
``` ## Best Practices
2. **Always Save/Reload:** ### For AI-Human Collaboration
- Human: Save after every change (Ctrl+S)
- Human: Reload after Claude makes changes 1. **Establish Turn-Taking:**
- Claude: Always read fresh before making decisions
```
3. **Communicate Changes:** User: "I'm going to add some components, one sec"
``` [User edits in UI]
Claude: "I'm placing R1-R4 now..." User: "Done, saved the file"
[MCP places components] Claude: [reads changes] "I see you added C1 and C2..."
Claude: "Done! Reload the board to see them" ```
User: [File → Revert]
``` 2. **Always Save/Reload:**
- Human: Save after every change (Ctrl+S)
4. **Use Descriptive References:** - Human: Reload after Claude makes changes
- Good: R1, R2, C1, C2 (sequential) - Claude: Always read fresh before making decisions
- Bad: R_temp, R_test (unclear)
3. **Communicate Changes:**
### Workflow Patterns
```
**Pattern 1: AI Does Layout, Human Reviews** Claude: "I'm placing R1-R4 now..."
``` [MCP places components]
1. Claude places all components via MCP Claude: "Done! Reload the board to see them"
2. Claude routes critical traces via MCP User: [File → Revert]
3. Human opens in KiCAD UI ```
4. Human fine-tunes positions
5. Human completes routing 4. **Use Descriptive References:**
6. Saves → Claude reads final result - Good: R1, R2, C1, C2 (sequential)
``` - Bad: R_temp, R_test (unclear)
**Pattern 2: Human Sketches, AI Refines** ### Workflow Patterns
```
1. Human places major components in UI **Pattern 1: AI Does Layout, Human Reviews**
2. Saves → Claude reads layout
3. Claude suggests improvements ```
4. Claude places remaining components via MCP 1. Claude places all components via MCP
5. Human reloads and reviews 2. Claude routes critical traces via MCP
6. Iterate until satisfied 3. Human opens in KiCAD UI
``` 4. Human fine-tunes positions
5. Human completes routing
**Pattern 3: Pair Programming Style** 6. Saves → Claude reads final result
``` ```
User: "Place a 10k pull-up resistor on pin 3"
Claude: [places R1 at calculated position] **Pattern 2: Human Sketches, AI Refines**
"Done! Check position (45, 20) mm"
User: [reloads] "Looks good, now add the LED" ```
Claude: [places D1] 1. Human places major components in UI
[Continue back-and-forth] 2. Saves → Claude reads layout
``` 3. Claude suggests improvements
4. Claude places remaining components via MCP
## Future Enhancements 5. Human reloads and reviews
6. Iterate until satisfied
### Planned Improvements ```
1. **File System Watchers** (Week 4+) **Pattern 3: Pair Programming Style**
- Auto-detect when UI saves file
- Auto-reload UI when MCP saves (via IPC) ```
- Near-instant sync (<100ms) User: "Place a 10k pull-up resistor on pin 3"
Claude: [places R1 at calculated position]
2. **IPC Backend** (Week 3) "Done! Check position (45, 20) mm"
- Direct communication with KiCAD process User: [reloads] "Looks good, now add the LED"
- Live sync without file save/reload Claude: [places D1]
- True real-time collaboration [Continue back-and-forth]
```
3. **Change Notifications**
- MCP sends notification when it modifies board ## Future Enhancements
- UI shows toast: "Claude added 4 components"
- Automatic reload option ### Planned Improvements
4. **Conflict Detection** 1. **File System Watchers** (Week 4+)
- Detect when both edited same component - Auto-detect when UI saves file
- Show diff/merge UI - Auto-reload UI when MCP saves (via IPC)
- Allow choosing which changes to keep - Near-instant sync (<100ms)
5. **Collaborative Cursor** 2. **IPC Backend** (Week 3)
- Show Claude's "cursor" in UI - Direct communication with KiCAD process
- Highlight component being placed - Live sync without file save/reload
- Visual feedback for AI actions - True real-time collaboration
### Long-Term Vision 3. **Change Notifications**
- MCP sends notification when it modifies board
**Fully Real-Time Collaboration:** - UI shows toast: "Claude added 4 components"
- Both AI and human see changes instantly - Automatic reload option
- No manual save/reload required
- Conflict detection and resolution 4. **Conflict Detection**
- Visual indicators for who's editing what - Detect when both edited same component
- Chat/comment system for design discussion - Show diff/merge UI
- Allow choosing which changes to keep
**Example Future Workflow:**
``` 5. **Collaborative Cursor**
[Claude and human both have board open] - Show Claude's "cursor" in UI
Claude: [starts placing R1] - Highlight component being placed
[R1 appears in UI with "Claude is placing..." indicator] - Visual feedback for AI actions
User: [sees R1 appear in real-time]
[moves D1 to better position] ### Long-Term Vision
[Claude sees D1 move instantly]
Claude: "Good position for D1! I'll route them now" **Fully Real-Time Collaboration:**
[traces appear as Claude creates them]
``` - Both AI and human see changes instantly
- No manual save/reload required
## Technical Details - Conflict detection and resolution
- Visual indicators for who's editing what
### File Format - Chat/comment system for design discussion
KiCAD uses S-expression format (`.kicad_pcb`): **Example Future Workflow:**
```lisp
(kicad_pcb (version 20240108) (generator "pcbnew") ```
(footprint "Resistor_SMD:R_0603_1608Metric" [Claude and human both have board open]
(layer "F.Cu") Claude: [starts placing R1]
(at 30.0 30.0 0) [R1 appears in UI with "Claude is placing..." indicator]
(property "Reference" "R1") User: [sees R1 appear in real-time]
(property "Value" "10k") [moves D1 to better position]
... [Claude sees D1 move instantly]
) Claude: "Good position for D1! I'll route them now"
) [traces appear as Claude creates them]
``` ```
### Sync Mechanism ## Technical Details
**Current (File-based):** ### File Format
1. MCP: `pcbnew.SaveBoard(path, board)` → writes file
2. UI: File → Revert → reads file KiCAD uses S-expression format (`.kicad_pcb`):
3. Latency: ~1-5 seconds (manual)
```lisp
**Future (IPC-based):** (kicad_pcb (version 20240108) (generator "pcbnew")
1. MCP: `kicad.AddFootprint(...)` → sends IPC command (footprint "Resistor_SMD:R_0603_1608Metric"
2. KiCAD: Receives command → updates internal state (layer "F.Cu")
3. UI: Automatically refreshes display (at 30.0 30.0 0)
4. Latency: ~50-100ms (automatic) (property "Reference" "R1")
(property "Value" "10k")
### Python API Used ...
)
```python )
import pcbnew ```
# Load board ### Sync Mechanism
board = pcbnew.LoadBoard('project.kicad_pcb')
**Current (File-based):**
# Read components
for fp in board.GetFootprints(): 1. MCP: `pcbnew.SaveBoard(path, board)` → writes file
ref = fp.Reference().GetText() 2. UI: File → Revert → reads file
pos = fp.GetPosition() 3. Latency: ~1-5 seconds (manual)
x_mm = pos.x / 1_000_000.0
y_mm = pos.y / 1_000_000.0 **Future (IPC-based):**
# Modify board 1. MCP: `kicad.AddFootprint(...)` → sends IPC command
module = pcbnew.FootprintLoad(lib_path, 'R_0603') 2. KiCAD: Receives command → updates internal state
module.SetPosition(pcbnew.VECTOR2I(x_nm, y_nm)) 3. UI: Automatically refreshes display
board.Add(module) 4. Latency: ~50-100ms (automatic)
# Save changes ### Python API Used
pcbnew.SaveBoard('project.kicad_pcb', board)
``` ```python
import pcbnew
## Troubleshooting
# Load board
### "I don't see MCP changes in KiCAD UI" board = pcbnew.LoadBoard('project.kicad_pcb')
**Cause:** UI hasn't reloaded the file # Read components
for fp in board.GetFootprints():
**Solution:** ref = fp.Reference().GetText()
1. File → Revert (or Ctrl+R if configured) pos = fp.GetPosition()
2. Or close PCB editor and reopen x_mm = pos.x / 1_000_000.0
3. Or restart KiCAD y_mm = pos.y / 1_000_000.0
### "MCP doesn't see my UI changes" # Modify board
module = pcbnew.FootprintLoad(lib_path, 'R_0603')
**Cause:** File not saved module.SetPosition(pcbnew.VECTOR2I(x_nm, y_nm))
board.Add(module)
**Solution:**
1. Save file: Ctrl+S or File → Save # Save changes
2. Verify save: Check file modification time pcbnew.SaveBoard('project.kicad_pcb', board)
3. Ask Claude to read board again ```
### "Changes disappeared after reload" ## Troubleshooting
**Cause:** File overwritten by other party ### "I don't see MCP changes in KiCAD UI"
**Solution:** **Cause:** UI hasn't reloaded the file
1. Always save before asking MCP to make changes
2. Don't edit while MCP is working **Solution:**
3. Take turns to avoid conflicts
1. File → Revert (or Ctrl+R if configured)
### "Components appear in wrong positions" 2. Or close PCB editor and reopen
3. Or restart KiCAD
**Cause:** Unit conversion error or coordinate system mismatch
### "MCP doesn't see my UI changes"
**Solution:**
1. Check KiCAD units (View → Switch Units) **Cause:** File not saved
2. MCP uses millimeters internally
3. Report issue if positions consistently wrong **Solution:**
## Conclusion 1. Save file: Ctrl+S or File → Save
2. Verify save: Check file modification time
**The real-time collaboration workflow is WORKING and TESTED! ✅** 3. Ask Claude to read board again
The KiCAD MCP Server successfully enables paired circuit board design between AI (Claude) and human designers. While it requires manual save/reload steps, both MCP→UI and UI→MCP workflows function correctly. ### "Changes disappeared after reload"
**Current State:** "Near-real-time" collaboration (1-5 second latency) **Cause:** File overwritten by other party
**Future State:** True real-time with IPC backend (<100ms latency) **Solution:**
**Mission Accomplished:** Real-time paired circuit board design is operational and ready for use! 🎉 1. Always save before asking MCP to make changes
2. Don't edit while MCP is working
--- 3. Take turns to avoid conflicts
## Related Documentation ### "Components appear in wrong positions"
- [LIBRARY_INTEGRATION.md](./LIBRARY_INTEGRATION.md) - Component library system **Cause:** Unit conversion error or coordinate system mismatch
- [STATUS_SUMMARY.md](./STATUS_SUMMARY.md) - Current implementation status
- [ROADMAP.md](./ROADMAP.md) - Future development plans **Solution:**
- [API.md](./API.md) - Full MCP API reference
1. Check KiCAD units (View → Switch Units)
## Changelog 2. MCP uses millimeters internally
3. Report issue if positions consistently wrong
**2025-11-01 - v2.1.0-alpha**
- Tested MCPUI workflow (placing components via MCP, viewing in UI) ## Conclusion
- Tested UIMCP workflow (editing in UI, reading via MCP)
- Documented best practices and limitations **The real-time collaboration workflow is WORKING and TESTED! ✅**
- Confirmed real-time collaboration mission is met
The KiCAD MCP Server successfully enables paired circuit board design between AI (Claude) and human designers. While it requires manual save/reload steps, both MCP→UI and UI→MCP workflows function correctly.
**Current State:** "Near-real-time" collaboration (1-5 second latency)
**Future State:** True real-time with IPC backend (<100ms latency)
**Mission Accomplished:** Real-time paired circuit board design is operational and ready for use! 🎉
---
## Related Documentation
- [LIBRARY_INTEGRATION.md](./LIBRARY_INTEGRATION.md) - Component library system
- [STATUS_SUMMARY.md](./STATUS_SUMMARY.md) - Current implementation status
- [ROADMAP.md](./ROADMAP.md) - Future development plans
- [API.md](./API.md) - Full MCP API reference
## 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
- Confirmed real-time collaboration mission is met

View File

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

@@ -1,353 +1,383 @@
# Router Architecture Design # Router Architecture Design
## Overview ## Overview
This document describes the router pattern implementation for the KiCAD MCP Server. The router reduces context window consumption by organizing 122+ tools into 8 discoverable categories, keeping only the most frequently used tools directly visible. This document describes the router pattern implementation for the KiCAD MCP Server. The router reduces context window consumption by organizing 122+ tools into 8 discoverable categories, keeping only the most frequently used tools directly visible.
## Architecture Layers ## Architecture Layers
``` ```
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ MCP Client (Claude) │ │ MCP Client (Claude) │
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ KiCAD MCP Server │ │ KiCAD MCP Server │
│ ┌─────────────────────────────────────────────────────────┐│ │ ┌─────────────────────────────────────────────────────────┐│
│ │ DIRECT TOOLS (Always Visible - 12) ││ │ │ DIRECT TOOLS (Always Visible - 12) ││
│ │ • create_project • open_project • save_project ││ │ │ • create_project • open_project • save_project ││
│ │ • get_project_info • place_component • move_component ││ │ │ • get_project_info • place_component • move_component ││
│ │ • add_net • route_trace • get_board_info ││ │ │ • add_net • route_trace • get_board_info ││
│ │ • set_board_size • add_board_outline • check_kicad_ui││ │ │ • set_board_size • add_board_outline • check_kicad_ui││
│ └─────────────────────────────────────────────────────────┘│ │ └─────────────────────────────────────────────────────────┘│
│ ┌─────────────────────────────────────────────────────────┐│ │ ┌─────────────────────────────────────────────────────────┐│
│ │ ROUTER TOOLS (Discovery - 4) ││ │ │ ROUTER TOOLS (Discovery - 4) ││
│ │ • list_tool_categories • get_category_tools ││ │ │ • list_tool_categories • get_category_tools ││
│ │ • execute_tool • search_tools ││ │ │ • execute_tool • search_tools ││
│ └─────────────────────────────────────────────────────────┘│ │ └─────────────────────────────────────────────────────────┘│
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ ┌─────────────────────────────────────────────────────────┐│ │ ┌─────────────────────────────────────────────────────────┐│
│ │ ROUTED TOOLS (Hidden - 110+) ││ │ │ ROUTED TOOLS (Hidden - 110+) ││
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││
│ │ │ board │ │component │ │ export │ │ drc │ ││ │ │ │ board │ │component │ │ export │ │ drc │ ││
│ │ │ tools │ │ tools │ │ tools │ │ tools │ ││ │ │ │ tools │ │ tools │ │ tools │ │ tools │ ││
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ ││ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ ││
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││
│ │ │schematic │ │ library │ │ routing │ │footprint │ ││ │ │ │schematic │ │ library │ │ routing │ │footprint │ ││
│ │ │ tools │ │ tools │ │ tools │ │ tools │ ││ │ │ │ tools │ │ tools │ │ tools │ │ tools │ ││
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ ││ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ ││
│ └─────────────────────────────────────────────────────────┘│ │ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
``` ```
## Tool Categories ## Tool Categories
### Direct Tools (12 tools - always visible) ### Direct Tools (12 tools - always visible)
These cover the primary workflow (80%+ of use cases): These cover the primary workflow (80%+ of use cases):
1. **Project Lifecycle** (4): 1. **Project Lifecycle** (4):
- `create_project` - Create new KiCAD project - `create_project` - Create new KiCAD project
- `open_project` - Open existing project - `open_project` - Open existing project
- `save_project` - Save current project - `save_project` - Save current project
- `get_project_info` - Get project information - `get_project_info` - Get project information
2. **Core PCB Operations** (6): 2. **Core PCB Operations** (6):
- `place_component` - Place component on board - `place_component` - Place component on board
- `move_component` - Move component to new position - `move_component` - Move component to new position
- `add_net` - Create a new net - `add_net` - Create a new net
- `route_trace` - Route trace between points - `route_trace` - Route trace between points
- `get_board_info` - Get board information - `get_board_info` - Get board information
- `set_board_size` - Set board dimensions - `set_board_size` - Set board dimensions
3. **Board Setup** (1): 3. **Board Setup** (1):
- `add_board_outline` - Add board outline - `add_board_outline` - Add board outline
4. **UI Management** (1): 4. **UI Management** (1):
- `check_kicad_ui` - Check if KiCAD UI is running - `check_kicad_ui` - Check if KiCAD UI is running
### Routed Categories (8+ categories, 110+ tools) ### Routed Categories (8+ categories, 110+ tools)
#### 1. `board` - Board Configuration & Layout (9 tools) #### 1. `board` - Board Configuration & Layout (9 tools)
Setup and configuration operations.
Setup and configuration operations.
**Tools:**
- `add_layer` - Add PCB layer **Tools:**
- `set_active_layer` - Set active layer
- `get_layer_list` - List all layers - `add_layer` - Add PCB layer
- `add_mounting_hole` - Add mounting hole - `set_active_layer` - Set active layer
- `add_board_text` - Add text to board - `get_layer_list` - List all layers
- `add_zone` - Add copper zone/pour - `add_mounting_hole` - Add mounting hole
- `get_board_extents` - Get board boundaries - `add_board_text` - Add text to board
- `get_board_2d_view` - Get 2D visualization - `add_zone` - Add copper zone/pour
- `launch_kicad_ui` - Launch KiCAD UI - `get_board_extents` - Get board boundaries
- `get_board_2d_view` - Get 2D visualization
#### 2. `component` - Advanced Component Operations (8 tools) - `launch_kicad_ui` - Launch KiCAD UI
Beyond basic placement.
#### 2. `component` - Advanced Component Operations (8 tools)
**Tools:**
- `rotate_component` - Rotate component Beyond basic placement.
- `delete_component` - Delete component
- `edit_component` - Edit component properties **Tools:**
- `find_component` - Find component by reference/value
- `get_component_properties` - Get component properties - `rotate_component` - Rotate component
- `add_component_annotation` - Add component annotation - `delete_component` - Delete component
- `group_components` - Group components together - `edit_component` - Edit component properties
- `replace_component` - Replace component with another - `find_component` - Find component by reference/value
- `get_component_properties` - Get component properties
#### 3. `export` - File Export & Manufacturing (8 tools) - `add_component_annotation` - Add component annotation
Generate output files for fabrication and documentation. - `group_components` - Group components together
- `replace_component` - Replace component with another
**Tools:**
- `export_gerber` - Export Gerber files #### 3. `export` - File Export & Manufacturing (8 tools)
- `export_pdf` - Export PDF
- `export_svg` - Export SVG Generate output files for fabrication and documentation.
- `export_3d` - Export 3D model (STEP/STL/VRML/OBJ)
- `export_bom` - Export bill of materials **Tools:**
- `export_netlist` - Export netlist
- `export_position_file` - Export component positions - `export_gerber` - Export Gerber files
- `export_vrml` - Export VRML 3D model - `export_pdf` - Export PDF
- `export_svg` - Export SVG
#### 4. `drc` - Design Rules & Validation (9 tools) - `export_3d` - Export 3D model (STEP/STL/VRML/OBJ)
Design rule checking and electrical validation. - `export_bom` - Export bill of materials
- `export_netlist` - Export netlist
**Tools:** - `export_position_file` - Export component positions
- `set_design_rules` - Configure design rules - `export_vrml` - Export VRML 3D model
- `get_design_rules` - Get current rules
- `run_drc` - Run design rule check #### 4. `drc` - Design Rules & Validation (9 tools)
- `add_net_class` - Add net class
- `assign_net_to_class` - Assign net to class Design rule checking and electrical validation.
- `set_layer_constraints` - Set layer constraints
- `check_clearance` - Check clearance between items **Tools:**
- `get_drc_violations` - Get DRC violations
- `set_design_rules` - Configure design rules
#### 5. `schematic` - Schematic Operations (9 tools) - `get_design_rules` - Get current rules
Schematic editor operations. - `run_drc` - Run design rule check
- `add_net_class` - Add net class
**Tools:** - `assign_net_to_class` - Assign net to class
- `create_schematic` - Create new schematic - `set_layer_constraints` - Set layer constraints
- `add_schematic_component` - Add component to schematic - `check_clearance` - Check clearance between items
- `add_wire` - Add wire connection - `get_drc_violations` - Get DRC violations
- `add_schematic_connection` - Connect component pins
- `add_schematic_net_label` - Add net label #### 5. `schematic` - Schematic Operations (9 tools)
- `connect_to_net` - Connect pin to net
- `get_net_connections` - Get net connections Schematic editor operations.
- `generate_netlist` - Generate netlist
**Tools:**
#### 6. `library` - Footprint Library Access (4 tools)
Search and browse footprint libraries. - `create_schematic` - Create new schematic
- `add_schematic_component` - Add component to schematic
**Tools:** - `add_wire` - Add wire connection
- `list_libraries` - List available libraries - `add_schematic_connection` - Connect component pins
- `search_footprints` - Search footprints - `add_schematic_net_label` - Add net label
- `list_library_footprints` - List library footprints - `connect_to_net` - Connect pin to net
- `get_footprint_info` - Get footprint details - `get_net_connections` - Get net connections
- `generate_netlist` - Generate netlist
#### 7. `routing` - Advanced Routing (3 tools)
Advanced routing operations beyond basic trace routing. #### 6. `library` - Footprint Library Access (4 tools)
**Tools:** Search and browse footprint libraries.
- `add_via` - Add via
- `add_copper_pour` - Add copper pour **Tools:**
**Note:** `add_net` and `route_trace` are direct tools as they're core operations. - `list_libraries` - List available libraries
- `search_footprints` - Search footprints
## Router Tools - `list_library_footprints` - List library footprints
- `get_footprint_info` - Get footprint details
### 1. `list_tool_categories`
**Description:** List all available tool categories with descriptions and tool counts. #### 7. `routing` - Advanced Routing (3 tools)
**Parameters:** None Advanced routing operations beyond basic trace routing.
**Returns:** **Tools:**
```json
{ - `add_via` - Add via
"total_categories": 7, - `add_copper_pour` - Add copper pour
"total_tools": 47,
"categories": [ **Note:** `add_net` and `route_trace` are direct tools as they're core operations.
{
"name": "board", ## Router Tools
"description": "Board configuration: layers, mounting holes, zones, visualization",
"tool_count": 9 ### 1. `list_tool_categories`
},
// ... more categories **Description:** List all available tool categories with descriptions and tool counts.
]
} **Parameters:** None
```
**Returns:**
### 2. `get_category_tools`
**Description:** Get detailed information about all tools in a specific category. ```json
{
**Parameters:** "total_categories": 7,
- `category` (string) - Category name from `list_tool_categories` "total_tools": 47,
"categories": [
**Returns:** {
```json "name": "board",
{ "description": "Board configuration: layers, mounting holes, zones, visualization",
"category": "export", "tool_count": 9
"description": "File export for fabrication and documentation: Gerber, PDF, BOM, 3D models", }
"tools": [ // ... more categories
{ ]
"name": "export_gerber", }
"description": "Export Gerber files for PCB fabrication", ```
"parameters": { /* zod schema */ }
}, ### 2. `get_category_tools`
// ... more tools
] **Description:** Get detailed information about all tools in a specific category.
}
``` **Parameters:**
### 3. `execute_tool` - `category` (string) - Category name from `list_tool_categories`
**Description:** Execute a tool from any category.
**Returns:**
**Parameters:**
- `tool_name` (string) - Tool name from `get_category_tools` ```json
- `params` (object, optional) - Tool parameters {
"category": "export",
**Returns:** Tool execution result "description": "File export for fabrication and documentation: Gerber, PDF, BOM, 3D models",
"tools": [
### 4. `search_tools` {
**Description:** Search for tools by keyword across all categories. "name": "export_gerber",
"description": "Export Gerber files for PCB fabrication",
**Parameters:** "parameters": {
- `query` (string) - Search term (e.g., "gerber", "zone", "export") /* zod schema */
}
**Returns:** }
```json // ... more tools
{ ]
"query": "export", }
"count": 8, ```
"matches": [
{ ### 3. `execute_tool`
"category": "export",
"tool": "export_gerber", **Description:** Execute a tool from any category.
"description": "Export Gerber files for PCB fabrication"
}, **Parameters:**
// ... more matches
] - `tool_name` (string) - Tool name from `get_category_tools`
} - `params` (object, optional) - Tool parameters
```
**Returns:** Tool execution result
## Implementation Files
### 4. `search_tools`
### New Files to Create
**Description:** Search for tools by keyword across all categories.
1. **`src/tools/registry.ts`**
- Tool category definitions **Parameters:**
- Tool metadata storage
- Lookup maps (by name, by category) - `query` (string) - Search term (e.g., "gerber", "zone", "export")
- Search functionality
**Returns:**
2. **`src/tools/router.ts`**
- Router tool implementations ```json
- `list_tool_categories` handler {
- `get_category_tools` handler "query": "export",
- `execute_tool` handler "count": 8,
- `search_tools` handler "matches": [
{
3. **`src/tools/direct.ts`** "category": "export",
- Export direct tool definitions "tool": "export_gerber",
- Keep existing tool implementations but organized "description": "Export Gerber files for PCB fabrication"
}
### Modified Files // ... more matches
]
1. **`src/server.ts`** or **`src/kicad-server.ts`** }
- Register only direct tools + router tools ```
- Remove registration of routed tools
- Tools still callable via `execute_tool` ## Implementation Files
## Migration Strategy ### New Files to Create
### Phase 1: Create Infrastructure 1. **`src/tools/registry.ts`**
1. Create `registry.ts` with all tool definitions - Tool category definitions
2. Create `router.ts` with router tools - Tool metadata storage
3. Create `direct.ts` with direct tool list - Lookup maps (by name, by category)
- Search functionality
### Phase 2: Update Server
1. Modify server registration to use direct + router only 2. **`src/tools/router.ts`**
2. Keep all existing tool handlers intact - Router tool implementations
3. Route through `execute_tool` - `list_tool_categories` handler
- `get_category_tools` handler
### Phase 3: Testing - `execute_tool` handler
1. Test direct tools work as before - `search_tools` handler
2. Test router tools (list/get/execute/search)
3. Test routed tools via `execute_tool` 3. **`src/tools/direct.ts`**
- Export direct tool definitions
### Phase 4: Optimization (Optional) - Keep existing tool implementations but organized
1. Add caching for tool lookups
2. Add tool usage analytics ### Modified Files
3. Implement intelligent tool suggestions
1. **`src/server.ts`** or **`src/kicad-server.ts`**
## Benefits - Register only direct tools + router tools
- Remove registration of routed tools
1. **Context Efficiency**: 70% reduction in tokens (~28K saved) - Tools still callable via `execute_tool`
2. **Better Organization**: Tools grouped by function
3. **Discoverability**: Easy to find the right tool ## Migration Strategy
4. **Scalability**: Can add unlimited tools without bloating context
5. **Backwards Compatible**: Existing Python commands still work ### Phase 1: Create Infrastructure
## Usage Examples 1. Create `registry.ts` with all tool definitions
2. Create `router.ts` with router tools
### Example 1: User Wants to Export Gerbers 3. Create `direct.ts` with direct tool list
``` ### Phase 2: Update Server
User: "Export gerbers for this board"
1. Modify server registration to use direct + router only
Claude's workflow: 2. Keep all existing tool handlers intact
1. Sees "export" keyword 3. Route through `execute_tool`
2. Calls search_tools({ query: "gerber" })
→ Returns: { category: "export", tool: "export_gerber", ... } ### Phase 3: Testing
3. Calls execute_tool({
tool_name: "export_gerber", 1. Test direct tools work as before
params: { outputDir: "./gerbers" } 2. Test router tools (list/get/execute/search)
}) 3. Test routed tools via `execute_tool`
→ Returns: { success: true, files: [...] }
### Phase 4: Optimization (Optional)
Claude: "I've exported the Gerber files to ./gerbers/"
``` 1. Add caching for tool lookups
2. Add tool usage analytics
### Example 2: User Wants to Place Component 3. Implement intelligent tool suggestions
``` ## Benefits
User: "Add a 0805 resistor at position 10,20"
1. **Context Efficiency**: 70% reduction in tokens (~28K saved)
Claude's workflow: 2. **Better Organization**: Tools grouped by function
1. Sees place_component in direct tools 3. **Discoverability**: Easy to find the right tool
2. Calls place_component({ 4. **Scalability**: Can add unlimited tools without bloating context
componentId: "R_0805", 5. **Backwards Compatible**: Existing Python commands still work
position: { x: 10, y: 20, unit: "mm" }
}) ## Usage Examples
→ Returns: { success: true, reference: "R1" }
### Example 1: User Wants to Export Gerbers
Claude: "Added R1 (0805 resistor) at position (10, 20) mm"
``` ```
User: "Export gerbers for this board"
### Example 3: User Wants Unknown Operation
Claude's workflow:
``` 1. Sees "export" keyword
User: "Check the board for design rule violations" 2. Calls search_tools({ query: "gerber" })
→ Returns: { category: "export", tool: "export_gerber", ... }
Claude's workflow: 3. Calls execute_tool({
1. Uncertain which tool to use tool_name: "export_gerber",
2. Calls search_tools({ query: "design rule violations" }) params: { outputDir: "./gerbers" }
→ Returns: { category: "drc", tool: "run_drc", ...} })
3. Calls get_category_tools({ category: "drc" }) → Returns: { success: true, files: [...] }
→ Returns full DRC category tools with parameters
4. Calls execute_tool({ Claude: "I've exported the Gerber files to ./gerbers/"
tool_name: "run_drc", ```
params: {}
}) ### Example 2: User Wants to Place Component
→ Returns: DRC results
```
Claude: "I ran the design rule check. Found 3 violations: ..." User: "Add a 0805 resistor at position 10,20"
```
Claude's workflow:
## Success Metrics 1. Sees place_component in direct tools
2. Calls place_component({
- ✅ Token usage: ~12K (vs 40K before) componentId: "R_0805",
- ✅ Tool discovery time: <2 calls (search execute) position: { x: 10, y: 20, unit: "mm" }
- User experience: Unchanged (seamless) })
- Maintainability: Improved (organized categories) → Returns: { success: true, reference: "R1" }
- Scalability: Can add 100+ more tools easily
Claude: "Added R1 (0805 resistor) at position (10, 20) mm"
```
### Example 3: User Wants Unknown Operation
```
User: "Check the board for design rule violations"
Claude's workflow:
1. Uncertain which tool to use
2. Calls search_tools({ query: "design rule violations" })
→ Returns: { category: "drc", tool: "run_drc", ...}
3. Calls get_category_tools({ category: "drc" })
→ Returns full DRC category tools with parameters
4. Calls execute_tool({
tool_name: "run_drc",
params: {}
})
→ Returns: DRC results
Claude: "I ran the design rule check. Found 3 violations: ..."
```
## Success Metrics
- ✅ Token usage: ~12K (vs 40K before)
- ✅ Tool discovery time: <2 calls (search execute)
- User experience: Unchanged (seamless)
- Maintainability: Improved (organized categories)
- Scalability: Can add 100+ more tools easily

View File

@@ -1,165 +1,197 @@
# Router Quick Start Guide # Router Quick Start Guide
## What is the Router? ## What is the Router?
The KiCAD MCP Server includes an intelligent tool router that organizes 122+ tools into 8 discoverable categories. This reduces AI context usage by up to 70% while maintaining full access to all functionality. The KiCAD MCP Server includes an intelligent tool router that organizes 122+ tools into 8 discoverable categories. This reduces AI context usage by up to 70% while maintaining full access to all functionality.
## How It Works ## How It Works
Instead of loading all 59 tool schemas into every conversation, Claude now sees: 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 - **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` When you ask Claude to do something (like "export gerber files"), it will:
2. Find the `export_gerber` tool in the "export" category
3. Execute it via `execute_tool` with your parameters 1. Search for relevant tools using `search_tools`
4. Return the results 2. Find the `export_gerber` tool in the "export" category
3. Execute it via `execute_tool` with your parameters
**You don't need to change how you interact with Claude** - the discovery happens automatically! 4. Return the results
## Tool Categories **You don't need to change how you interact with Claude** - the discovery happens automatically!
The 110+ routed tools are organized into these categories: ## Tool Categories
### 1. board (9 tools) The 110+ routed tools are organized into these categories:
Board configuration: layers, mounting holes, zones, visualization
- add_layer, set_active_layer, get_layer_list ### 1. board (9 tools)
- add_mounting_hole, add_board_text
- add_zone, get_board_extents, get_board_2d_view Board configuration: layers, mounting holes, zones, visualization
- launch_kicad_ui
- add_layer, set_active_layer, get_layer_list
### 2. component (8 tools) - add_mounting_hole, add_board_text
Advanced component operations: edit, delete, search, group, annotate - add_zone, get_board_extents, get_board_2d_view
- rotate_component, delete_component, edit_component - launch_kicad_ui
- find_component, get_component_properties
- add_component_annotation, group_components, replace_component ### 2. component (8 tools)
### 3. export (8 tools) Advanced component operations: edit, delete, search, group, annotate
File export for fabrication and documentation
- export_gerber, export_pdf, export_svg, export_3d - rotate_component, delete_component, edit_component
- export_bom, export_netlist, export_position_file, export_vrml - find_component, get_component_properties
- add_component_annotation, group_components, replace_component
### 4. drc (8 tools)
Design rule checking and electrical validation ### 3. export (8 tools)
- set_design_rules, get_design_rules, run_drc
- add_net_class, assign_net_to_class, set_layer_constraints File export for fabrication and documentation
- check_clearance, get_drc_violations
- export_gerber, export_pdf, export_svg, export_3d
### 5. schematic (8 tools) - export_bom, export_netlist, export_position_file, export_vrml
Schematic operations: create, add components, wire connections
- create_schematic, add_schematic_component, add_wire ### 4. drc (8 tools)
- add_schematic_connection, add_schematic_net_label
- connect_to_net, get_net_connections, generate_netlist Design rule checking and electrical validation
### 6. library (4 tools) - set_design_rules, get_design_rules, run_drc
Footprint library access and search - add_net_class, assign_net_to_class, set_layer_constraints
- list_libraries, search_footprints - check_clearance, get_drc_violations
- list_library_footprints, get_footprint_info
### 5. schematic (8 tools)
### 7. routing (2 tools)
Advanced routing operations Schematic operations: create, add components, wire connections
- add_via, add_copper_pour
- create_schematic, add_schematic_component, add_wire
## Direct Tools (Always Available) - add_schematic_connection, add_schematic_net_label
- connect_to_net, get_net_connections, generate_netlist
These 12 tools are always visible for common operations:
### 6. library (4 tools)
**Project Lifecycle:**
- create_project, open_project, save_project, get_project_info Footprint library access and search
**Core PCB Operations:** - list_libraries, search_footprints
- place_component, move_component - list_library_footprints, get_footprint_info
- add_net, route_trace
- get_board_info, set_board_size ### 7. routing (2 tools)
- add_board_outline
Advanced routing operations
**UI Management:**
- check_kicad_ui - add_via, add_copper_pour
## Router Tools ## Direct Tools (Always Available)
### list_tool_categories These 12 tools are always visible for common operations:
Browse all available tool categories.
**Project Lifecycle:**
**Example:**
``` - create_project, open_project, save_project, get_project_info
Claude, what tool categories are available?
``` **Core PCB Operations:**
### get_category_tools - place_component, move_component
View all tools in a specific category. - add_net, route_trace
- get_board_info, set_board_size
**Example:** - add_board_outline
```
Show me all export tools available. **UI Management:**
```
- check_kicad_ui
### search_tools
Find tools by keyword. ## Router Tools
**Example:** ### list_tool_categories
```
Search for tools related to "gerber" or "mounting holes" Browse all available tool categories.
```
**Example:**
### execute_tool
Execute any routed tool with parameters. ```
Claude, what tool categories are available?
**Example:** ```
```
Execute the export_gerber tool with outputDir set to ./fabrication ### get_category_tools
```
View all tools in a specific category.
## Usage Examples
**Example:**
### Natural Interaction (Recommended)
Just ask Claude what you want - it handles discovery automatically: ```
Show me all export tools available.
``` ```
"Export gerber files to ./output"
"Add a mounting hole at x=10, y=10" ### search_tools
"Run a design rule check"
"Create a copper pour on the ground layer" Find tools by keyword.
```
**Example:**
### Manual Discovery (Optional)
You can also browse tools explicitly: ```
Search for tools related to "gerber" or "mounting holes"
``` ```
"List all tool categories"
"What export tools are available?" ### execute_tool
"Search for DRC tools"
``` Execute any routed tool with parameters.
## Benefits **Example:**
1. **Reduced Context Usage**: 70% less AI context consumed per conversation ```
2. **Organized Tools**: Logical categorization makes tools easy to find Execute the export_gerber tool with outputDir set to ./fabrication
3. **Seamless Experience**: Works transparently - no changes to how you interact ```
4. **Extensible**: Easy to add new tools and categories
5. **Backwards Compatible**: All existing tools still work ## Usage Examples
## Technical Details ### Natural Interaction (Recommended)
- **Registry**: `src/tools/registry.ts` - Tool categorization and lookup Just ask Claude what you want - it handles discovery automatically:
- **Router**: `src/tools/router.ts` - Discovery and execution implementation
- **Server Integration**: `src/server.ts` - Router tools registered at startup ```
"Export gerber files to ./output"
For implementation details, see: "Add a mounting hole at x=10, y=10"
- [ROUTER_ARCHITECTURE.md](ROUTER_ARCHITECTURE.md) - Design specification "Run a design rule check"
- [ROUTER_IMPLEMENTATION_STATUS.md](ROUTER_IMPLEMENTATION_STATUS.md) - Current status "Create a copper pour on the ground layer"
- [TOOL_INVENTORY.md](TOOL_INVENTORY.md) - Complete tool catalog ```
## Token Savings ### Manual Discovery (Optional)
**Before Router:** You can also browse tools explicitly:
- 122 tools × ~700 tokens each = ~85K tokens per conversation
```
**After Router (Current):** "List all tool categories"
- 12 direct tools + 4 router tools = 16 tools visible "What export tools are available?"
- Routed tools discovered on-demand "Search for DRC tools"
- ~12-15K tokens per conversation ```
- **~80% reduction** in context usage
## Benefits
The router pattern is complete and functional, providing efficient tool discovery while maintaining full access to all 122+ tools.
1. **Reduced Context Usage**: 70% less AI context consumed per conversation
2. **Organized Tools**: Logical categorization makes tools easy to find
3. **Seamless Experience**: Works transparently - no changes to how you interact
4. **Extensible**: Easy to add new tools and categories
5. **Backwards Compatible**: All existing tools still work
## Technical Details
- **Registry**: `src/tools/registry.ts` - Tool categorization and lookup
- **Router**: `src/tools/router.ts` - Discovery and execution implementation
- **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
## 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
- **~80% reduction** in context usage
The router pattern is complete and functional, providing efficient tool discovery while maintaining full access to all 122+ tools.

View File

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

View File

@@ -8,268 +8,296 @@ This document provides a complete reference for the 27 schematic tools in the Ki
## Component Operations (8 tools) ## Component Operations (8 tools)
### add_schematic_component ### add_schematic_component
Add a component to the schematic. Symbol format is 'Library:SymbolName' (e.g., 'Device:R', 'EDA-MCP:ESP32-C3'). Add a component to the schematic. Symbol format is 'Library:SymbolName' (e.g., 'Device:R', 'EDA-MCP:ESP32-C3').
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ---------------------------------------------------------------- |
| schematicPath | string | Yes | Path to the schematic file | | schematicPath | string | Yes | Path to the schematic file |
| symbol | string | Yes | Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3) | | symbol | string | Yes | Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3) |
| reference | string | Yes | Component reference (e.g., R1, U1) | | reference | string | Yes | Component reference (e.g., R1, U1) |
| value | string | No | Component value | | value | string | No | Component value |
| footprint | string | No | KiCAD footprint (e.g. Resistor_SMD:R_0603_1608Metric) | | footprint | string | No | KiCAD footprint (e.g. Resistor_SMD:R_0603_1608Metric) |
| position | object | No | Position on schematic with x and y coordinates | | position | object | No | Position on schematic with x and y coordinates |
**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. **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 ### 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. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ------------------------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Reference designator of the component to remove (e.g. R1, U3) | | reference | string | Yes | Reference designator of the component to remove (e.g. R1, U3) |
### edit_schematic_component ### 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. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Current reference designator of the component (e.g. R1, U3) | | 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) | | footprint | string | No | New KiCAD footprint string (e.g. Resistor_SMD:R_0603_1608Metric) |
| value | string | No | New value string (e.g. 10k, 100nF) | | value | string | No | New value string (e.g. 10k, 100nF) |
| newReference | string | No | Rename the reference designator (e.g. R1 → R10) | | newReference | string | No | Rename the reference designator (e.g. R1 → R10) |
| fieldPositions | object | No | Reposition field labels: map of field name to {x, y, angle} (e.g. {"Reference": {"x": 12.5, "y": 17.0}}) | | 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_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. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | -------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Component reference designator (e.g. R1, U1) | | reference | string | Yes | Component reference designator (e.g. R1, U1) |
### list_schematic_components ### 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. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ---------------------- | ------ | -------- | --------------------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| filter | object | No | Optional filters with libId and/or referencePrefix fields | | filter | object | No | Optional filters with libId and/or referencePrefix fields |
| filter.libId | string | No | Filter by library ID (e.g., 'Device:R') | | 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') | | filter.referencePrefix | string | No | Filter by reference prefix (e.g., 'R', 'C', 'U') |
### move_schematic_component ### move_schematic_component
Move a placed symbol to a new position in the schematic. Move a placed symbol to a new position in the schematic.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Reference designator (e.g., R1, U1) | | reference | string | Yes | Reference designator (e.g., R1, U1) |
| position | object | Yes | New position with x and y coordinates | | position | object | Yes | New position with x and y coordinates |
### rotate_schematic_component ### rotate_schematic_component
Rotate a placed symbol in the schematic. Rotate a placed symbol in the schematic.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| reference | string | Yes | Reference designator (e.g., R1, U1) | | reference | string | Yes | Reference designator (e.g., R1, U1) |
| angle | number | Yes | Rotation angle in degrees (0, 90, 180, 270) | | angle | number | Yes | Rotation angle in degrees (0, 90, 180, 270) |
| mirror | enum | No | Optional mirror axis ("x" or "y") | | mirror | enum | No | Optional mirror axis ("x" or "y") |
### annotate_schematic ### annotate_schematic
Assign reference designators to unannotated components (R? → R1, R2, ...). Must be called before tools that require known references. Assign reference designators to unannotated components (R? → R1, R2, ...). Must be called before tools that require known references.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
## Wiring and Connections (8 tools) ## Wiring and Connections (8 tools)
### add_wire ### add_wire
Add a wire connection in the schematic. Add a wire connection in the schematic.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | --------- | ------ | -------- | --------------------------------------- |
| start | object | Yes | Start position with x and y coordinates | | start | object | Yes | Start position with x and y coordinates |
| end | object | Yes | End position with x and y coordinates | | end | object | Yes | End position with x and y coordinates |
### add_schematic_connection ### 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). 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ---------------------------------------- |
| schematicPath | string | Yes | Path to the schematic file | | schematicPath | string | Yes | Path to the schematic file |
| sourceRef | string | Yes | Source component reference (e.g., R1) | | sourceRef | string | Yes | Source component reference (e.g., R1) |
| sourcePin | string | Yes | Source pin name/number (e.g., 1, 2, GND) | | sourcePin | string | Yes | Source pin name/number (e.g., 1, 2, GND) |
| targetRef | string | Yes | Target component reference (e.g., C1) | | targetRef | string | Yes | Target component reference (e.g., C1) |
| targetPin | string | Yes | Target pin name/number (e.g., 1, 2, VCC) | | targetPin | string | Yes | Target pin name/number (e.g., 1, 2, VCC) |
### add_schematic_net_label ### add_schematic_net_label
Add a net label to the schematic. Add a net label to the schematic.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ------------------------------------------ |
| schematicPath | string | Yes | Path to the schematic file | | schematicPath | string | Yes | Path to the schematic file |
| netName | string | Yes | Name of the net (e.g., VCC, GND, SIGNAL_1) | | netName | string | Yes | Name of the net (e.g., VCC, GND, SIGNAL_1) |
| position | array | Yes | Position [x, y] for the label | | position | array | Yes | Position [x, y] for the label |
### connect_to_net ### connect_to_net
Connect a component pin to a named net. Connect a component pin to a named net.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ---------------------------------- |
| schematicPath | string | Yes | Path to the schematic file | | schematicPath | string | Yes | Path to the schematic file |
| componentRef | string | Yes | Component reference (e.g., U1, R1) | | componentRef | string | Yes | Component reference (e.g., U1, R1) |
| pinName | string | Yes | Pin name/number to connect | | pinName | string | Yes | Pin name/number to connect |
| netName | string | Yes | Name of the net to connect to | | netName | string | Yes | Name of the net to connect to |
**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). **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 ### 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.
| Parameter | Type | Required | Description | 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.
|-----------|------|----------|-------------|
| schematicPath | string | Yes | Path to the schematic file | | Parameter | Type | Required | Description |
| sourceRef | string | Yes | Source connector reference (e.g. J1) | | ------------- | ------ | -------- | --------------------------------------------------------- |
| targetRef | string | Yes | Target connector reference (e.g. J2) | | schematicPath | string | Yes | Path to the schematic file |
| netPrefix | string | No | Net name prefix, e.g. 'CSI' → CSI_1, CSI_2 (default: PIN) | | sourceRef | string | Yes | Source connector reference (e.g. J1) |
| pinOffset | number | No | Add to pin number when building net name (default: 0) | | targetRef | string | Yes | Target connector reference (e.g. J2) |
| netPrefix | string | No | Net name prefix, e.g. 'CSI' → CSI_1, CSI_2 (default: PIN) |
| pinOffset | number | No | Add to pin number when building net name (default: 0) |
**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. **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 ### 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. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ------------------------------------------------ |
| schematicPath | string | Yes | Path to the schematic file | | schematicPath | string | Yes | Path to the schematic file |
| reference | string | Yes | Component reference designator (e.g. U1, R1, J2) | | reference | string | Yes | Component reference designator (e.g. U1, R1, J2) |
### delete_schematic_wire ### delete_schematic_wire
Remove a wire from the schematic by start and end coordinates. Remove a wire from the schematic by start and end coordinates.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | -------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| start | object | Yes | Wire start position with x and y coordinates | | start | object | Yes | Wire start position with x and y coordinates |
| end | object | Yes | Wire end position with x and y coordinates | | end | object | Yes | Wire end position with x and y coordinates |
### delete_schematic_net_label ### delete_schematic_net_label
Remove a net label from the schematic. Remove a net label from the schematic.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | -------------------------------------------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| netName | string | Yes | Name of the net label to remove | | 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) | | position | object | No | Position to disambiguate if multiple labels with same name (x and y coordinates) |
## Net Analysis (4 tools) ## Net Analysis (4 tools)
### get_net_connections ### get_net_connections
Get all connections for a named net. Get all connections for a named net.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | -------------------------- |
| schematicPath | string | Yes | Path to the schematic file | | schematicPath | string | Yes | Path to the schematic file |
| netName | string | Yes | Name of the net to query | | 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. **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_schematic_nets
List all nets in the schematic with their connections. List all nets in the schematic with their connections.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
### list_schematic_wires ### list_schematic_wires
List all wires in the schematic with start/end coordinates. List all wires in the schematic with start/end coordinates.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
### list_schematic_labels ### list_schematic_labels
List all net labels, global labels, and power flags in the schematic. List all net labels, global labels, and power flags in the schematic.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
## Schematic Creation and Export (5 tools) ## Schematic Creation and Export (5 tools)
### create_schematic ### create_schematic
Create a new schematic. Create a new schematic.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | --------- | ------ | -------- | -------------- |
| name | string | Yes | Schematic name | | name | string | Yes | Schematic name |
| path | string | No | Optional path | | path | string | No | Optional path |
### export_schematic_svg ### export_schematic_svg
Export schematic to SVG format using kicad-cli. Export schematic to SVG format using kicad-cli.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------- | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| outputPath | string | Yes | Output SVG file path | | outputPath | string | Yes | Output SVG file path |
| blackAndWhite | boolean | No | Export in black and white | | blackAndWhite | boolean | No | Export in black and white |
### export_schematic_pdf ### export_schematic_pdf
Export schematic to PDF format using kicad-cli. Export schematic to PDF format using kicad-cli.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------- | -------- | --------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| outputPath | string | Yes | Output PDF file path | | outputPath | string | Yes | Output PDF file path |
| blackAndWhite | boolean | No | Export in black and white | | blackAndWhite | boolean | No | Export in black and white |
### get_schematic_view ### 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. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | -------------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch file | | schematicPath | string | Yes | Path to the .kicad_sch file |
| format | enum | No | Output format ("png" or "svg", default: png) | | format | enum | No | Output format ("png" or "svg", default: png) |
| width | number | No | Image width in pixels (default: 1200) | | width | number | No | Image width in pixels (default: 1200) |
| height | number | No | Image height in pixels (default: 900) | | height | number | No | Image height in pixels (default: 900) |
### generate_netlist ### generate_netlist
Generate a netlist from the schematic. Generate a netlist from the schematic.
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | -------------------------- |
| schematicPath | string | Yes | Path to the schematic file | | 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). **Usage Notes:** Returns a complete netlist with component information (reference, value, footprint) and net connections (net name with all connected component/pin pairs).
## Validation and Synchronization (3 tools) ## Validation and Synchronization (3 tools)
### run_erc ### 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. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ------------------------------------- |
| schematicPath | string | Yes | Path to the .kicad_sch schematic file | | 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. **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 ### 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. 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 | | Parameter | Type | Required | Description |
|-----------|------|----------|-------------| | ------------- | ------ | -------- | ---------------------------------------------- |
| schematicPath | string | Yes | Absolute path to the .kicad_sch schematic file | | schematicPath | string | Yes | Absolute path to the .kicad_sch schematic file |
| boardPath | string | Yes | Absolute path to the .kicad_pcb board file | | boardPath | string | Yes | Absolute path to the .kicad_pcb board file |
**Usage Notes:** This is the F8 equivalent. It synchronizes the schematic design to the PCB, creating footprints on the board and assigning nets. This step is critical in the workflow: design in schematic → sync_schematic_to_board → place and route on PCB. **Usage Notes:** This is the F8 equivalent. It synchronizes the schematic design to the PCB, creating footprints on the board and assigning nets. This step is critical in the workflow: design in schematic → sync_schematic_to_board → place and route on PCB.
## Example Workflows ## Example Workflows
### Basic Circuit Design ### Basic Circuit Design
1. **Create project:** Use `create_schematic` to initialize a new schematic file 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. 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"` - 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 7. **Sync to PCB:** Use `sync_schematic_to_board` to transfer the design to the PCB layout
### FFC Passthrough Adapter ### FFC Passthrough Adapter
1. **Add connectors:** Place two FFC connectors using `add_schematic_component` 1. **Add connectors:** Place two FFC connectors using `add_schematic_component`
- Example: J1 and J2, both 20-pin FFC connectors - Example: J1 and J2, both 20-pin FFC connectors
2. **Connect passthrough:** Use `connect_passthrough` with `sourceRef: "J1"`, `targetRef: "J2"`, `netPrefix: "CSI"` 2. **Connect passthrough:** Use `connect_passthrough` with `sourceRef: "J1"`, `targetRef: "J2"`, `netPrefix: "CSI"`

View File

@@ -8,46 +8,47 @@
## Quick Stats ## Quick Stats
| Metric | Value | | Metric | Value |
|--------|-------| | -------------------- | --------------------------- |
| Total MCP Tools | 122 | | Total MCP Tools | 122 |
| Tool Categories | 16 | | Tool Categories | 16 |
| KiCAD 9.0 Compatible | Yes (verified) | | KiCAD 9.0 Compatible | Yes (verified) |
| Platforms | Linux, Windows, macOS | | Platforms | Linux, Windows, macOS |
| JLCPCB Parts Catalog | 2.5M+ components | | JLCPCB Parts Catalog | 2.5M+ components |
| Symbol Access | ~10,000 via dynamic loading | | Symbol Access | ~10,000 via dynamic loading |
| Footprint Libraries | 153+ auto-discovered | | Footprint Libraries | 153+ auto-discovered |
| Contributors | 10+ | | Contributors | 10+ |
| MCP Protocol Version | 2025-06-18 | | MCP Protocol Version | 2025-06-18 |
--- ---
## Feature Completion Matrix ## Feature Completion Matrix
| Feature Category | Status | Tool Count | Details | | Feature Category | Status | Tool Count | Details |
|-----------------|--------|------------|---------| | ------------------- | -------- | ---------- | ----------------------------------------------------------------------- |
| Project Management | Complete | 5 | Create, open, save, info, snapshot | | Project Management | Complete | 5 | Create, open, save, info, snapshot |
| Board Setup | Complete | 12 | Size, outline, layers, mounting holes, zones, text, 2D view, SVG import | | 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 | | Component Placement | Complete | 16 | Place, move, rotate, delete, edit, find, pads, arrays, align, duplicate |
| Routing | Complete | 13 | Traces, vias, pad-to-pad, differential pairs, netclasses, copy pattern | | Routing | Complete | 13 | Traces, vias, pad-to-pad, differential pairs, netclasses, copy pattern |
| Design Rules / DRC | Complete | 8 | Set/get rules, DRC, net classes, clearance checks | | Design Rules / DRC | Complete | 8 | Set/get rules, DRC, net classes, clearance checks |
| Export | Complete | 8 | Gerber, PDF, SVG, 3D, BOM, netlist, position file, VRML | | Export | Complete | 8 | Gerber, PDF, SVG, 3D, BOM, netlist, position file, VRML |
| Schematic | Complete | 27 | Components, wiring, net labels, connections, ERC, export, sync to board | | Schematic | Complete | 27 | Components, wiring, net labels, connections, ERC, export, sync to board |
| Footprint Libraries | Complete | 4 | List, search, browse, info | | Footprint Libraries | Complete | 4 | List, search, browse, info |
| Symbol Libraries | Complete | 4 | List, search, browse, info | | Symbol Libraries | Complete | 4 | List, search, browse, info |
| Footprint Creator | Complete | 4 | Create custom footprints, edit pads, register libraries | | Footprint Creator | Complete | 4 | Create custom footprints, edit pads, register libraries |
| Symbol Creator | Complete | 4 | Create custom symbols, register libraries | | Symbol Creator | Complete | 4 | Create custom symbols, register libraries |
| Datasheet Tools | Complete | 2 | LCSC datasheet enrichment | | Datasheet Tools | Complete | 2 | LCSC datasheet enrichment |
| JLCPCB Integration | Complete | 5 | Local DB, search, part details, stats, alternatives | | JLCPCB Integration | Complete | 5 | Local DB, search, part details, stats, alternatives |
| Freerouting | Complete | 4 | Autoroute, DSN export, SES import, availability check | | Freerouting | Complete | 4 | Autoroute, DSN export, SES import, availability check |
| UI Management | Complete | 2 | Check/launch KiCAD | | UI Management | Complete | 2 | Check/launch KiCAD |
| Router Tools | Complete | 4 | Category browsing, tool search, execute | | Router Tools | Complete | 4 | Category browsing, tool search, execute |
--- ---
## Architecture ## Architecture
### SWIG Backend (File-based) -- Default ### SWIG Backend (File-based) -- Default
- **Status:** Stable - **Status:** Stable
- Direct pcbnew API access via KiCAD's Python bindings - Direct pcbnew API access via KiCAD's Python bindings
- Requires manual KiCAD UI reload to see changes - Requires manual KiCAD UI reload to see changes
@@ -55,13 +56,16 @@
- Auto-saves after every board-modifying command - Auto-saves after every board-modifying command
### IPC Backend (Real-time) -- Experimental ### IPC Backend (Real-time) -- Experimental
- **Status:** Functional, 21 commands implemented - **Status:** Functional, 21 commands implemented
- Real-time UI synchronization with KiCAD 9+ - Real-time UI synchronization with KiCAD 9+
- Requires KiCAD running with IPC API enabled - Requires KiCAD running with IPC API enabled
- Automatic fallback to SWIG when unavailable - Automatic fallback to SWIG when unavailable
### Hybrid Approach ### Hybrid Approach
The server automatically selects the best backend: The server automatically selects the best backend:
- IPC when KiCAD is running with IPC enabled - IPC when KiCAD is running with IPC enabled
- SWIG fallback when IPC is unavailable - SWIG fallback when IPC is unavailable
- Some operations use both (e.g., footprint placement) - Some operations use both (e.g., footprint placement)
@@ -71,18 +75,21 @@ The server automatically selects the best backend:
## Platform Support ## Platform Support
### Linux -- Primary Platform ### Linux -- Primary Platform
- KiCAD 9.0 detection: Working - KiCAD 9.0 detection: Working
- Process management: Working - Process management: Working
- Library discovery: Working (153+ libraries) - Library discovery: Working (153+ libraries)
- IPC backend: Working - IPC backend: Working
### Windows -- Fully Supported ### Windows -- Fully Supported
- Automated setup script (setup-windows.ps1) - Automated setup script (setup-windows.ps1)
- Process detection via Toolhelp32 API - Process detection via Toolhelp32 API
- Library paths auto-detected - Library paths auto-detected
- Troubleshooting guide available (WINDOWS_TROUBLESHOOTING.md) - Troubleshooting guide available (WINDOWS_TROUBLESHOOTING.md)
### macOS -- Community Supported ### macOS -- Community Supported
- Configuration provided - Configuration provided
- Process detection implemented - Process detection implemented
- Library paths configured - Library paths configured
@@ -93,6 +100,7 @@ The server automatically selects the best backend:
## Recent Development Highlights ## Recent Development Highlights
### v2.2.3 (2026-03-11) ### v2.2.3 (2026-03-11)
- FFC/ribbon cable passthrough workflow (connect_passthrough, sync_schematic_to_board) - FFC/ribbon cable passthrough workflow (connect_passthrough, sync_schematic_to_board)
- Project snapshot system - Project snapshot system
- SVG logo import - SVG logo import
@@ -101,20 +109,24 @@ The server automatically selects the best backend:
- Critical B.Cu routing fixes - Critical B.Cu routing fixes
### v2.2.2-alpha (2026-03-01) ### v2.2.2-alpha (2026-03-01)
- route_pad_to_pad with auto-via insertion - route_pad_to_pad with auto-via insertion
- copy_routing_pattern for trace replication - copy_routing_pattern for trace replication
- Project-local library resolution - Project-local library resolution
### v2.2.1-alpha (2026-02-28) ### v2.2.1-alpha (2026-02-28)
- edit_schematic_component with field position support - edit_schematic_component with field position support
- Footprint and symbol creator tools - Footprint and symbol creator tools
### v2.2.0-alpha (2026-02-27) ### v2.2.0-alpha (2026-02-27)
- 13 new routing/component tools - 13 new routing/component tools
- Datasheet enrichment tools - Datasheet enrichment tools
- SWIG/UUID bug fixes - SWIG/UUID bug fixes
### v2.1.0-alpha (2026-01-10) ### v2.1.0-alpha (2026-01-10)
- Complete schematic wiring system - Complete schematic wiring system
- Dynamic symbol loading (~10,000 symbols) - Dynamic symbol loading (~10,000 symbols)
- JLCPCB parts integration - JLCPCB parts integration
@@ -124,35 +136,38 @@ The server automatically selects the best backend:
## Community Contributors ## Community Contributors
| Contributor | Key Contributions | | Contributor | Key Contributions |
|------------|-------------------| | ------------- | ------------------------------------------------------------------------------ |
| Kletternaut | Routing tools, footprint/symbol creators, passthrough workflow, template fixes | | Kletternaut | Routing tools, footprint/symbol creators, passthrough workflow, template fixes |
| Mehanik | Schematic inspection/editing tools, component field positions | | Mehanik | Schematic inspection/editing tools, component field positions |
| jflaflamme | Freerouting autorouter integration with Docker/Podman | | jflaflamme | Freerouting autorouter integration with Docker/Podman |
| l3wi | Local symbol library search, JLCPCB third-party library support | | l3wi | Local symbol library search, JLCPCB third-party library support |
| gwall-ceres | MCP protocol compliance, Windows compatibility | | gwall-ceres | MCP protocol compliance, Windows compatibility |
| fariouche | Bug fixes | | fariouche | Bug fixes |
| shuofengzhang | XDG relative path handling | | shuofengzhang | XDG relative path handling |
| sid115 | Windows setup script improvements | | sid115 | Windows setup script improvements |
| pasrom | MCP server bug fixes | | pasrom | MCP server bug fixes |
--- ---
## Getting Help ## Getting Help
**For Users:** **For Users:**
1. Check [README.md](../README.md) for installation 1. Check [README.md](../README.md) for installation
2. Review [KNOWN_ISSUES.md](KNOWN_ISSUES.md) for common problems 2. Review [KNOWN_ISSUES.md](KNOWN_ISSUES.md) for common problems
3. Check logs: `~/.kicad-mcp/logs/kicad_interface.log` 3. Check logs: `~/.kicad-mcp/logs/kicad_interface.log`
**For Contributors:** **For Contributors:**
1. Read [CONTRIBUTING.md](../CONTRIBUTING.md) for development setup 1. Read [CONTRIBUTING.md](../CONTRIBUTING.md) for development setup
2. Check [ARCHITECTURE.md](ARCHITECTURE.md) for system design 2. Check [ARCHITECTURE.md](ARCHITECTURE.md) for system design
3. Review the [Documentation Index](INDEX.md) for all available docs 3. Review the [Documentation Index](INDEX.md) for all available docs
**Issues:** **Issues:**
- Open an issue on GitHub with OS, KiCAD version, and error details - Open an issue on GitHub with OS, KiCAD version, and error details
--- ---
*Last Updated: 2026-03-21* _Last Updated: 2026-03-21_

View File

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

View File

@@ -16,325 +16,325 @@ The server uses a **router pattern** to reduce AI context usage. Tools fall into
## Project Management (5 tools) ## Project Management (5 tools)
*Source: `src/tools/project.ts`* _Source: `src/tools/project.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ------------------ | ---------------------------------------------------------------- | ------ |
| `create_project` | Create a new KiCAD project (.kicad_pro, .kicad_pcb, .kicad_sch) | Direct | | `create_project` | Create a new KiCAD project (.kicad_pro, .kicad_pcb, .kicad_sch) | Direct |
| `open_project` | Open an existing KiCAD project | Direct | | `open_project` | Open an existing KiCAD project | Direct |
| `save_project` | Save the current project | Direct | | `save_project` | Save the current project | Direct |
| `get_project_info` | Get project metadata and information | Direct | | `get_project_info` | Get project metadata and information | Direct |
| `snapshot_project` | Save a named checkpoint snapshot (renders PDF, saves step label) | Direct | | `snapshot_project` | Save a named checkpoint snapshot (renders PDF, saves step label) | Direct |
--- ---
## Board Management (12 tools) ## Board Management (12 tools)
*Source: `src/tools/board.ts`* _Source: `src/tools/board.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ------------------- | ----------------------------------------------------------------- | -------------- |
| `set_board_size` | Set PCB dimensions (width, height, unit) | Direct | | `set_board_size` | Set PCB dimensions (width, height, unit) | Direct |
| `add_board_outline` | Add board outline (rectangle, circle, polygon, rounded_rectangle) | Direct | | `add_board_outline` | Add board outline (rectangle, circle, polygon, rounded_rectangle) | Direct |
| `get_board_info` | Get board metadata and properties | Direct | | `get_board_info` | Get board metadata and properties | Direct |
| `add_layer` | Add copper/technical/signal layer | Routed (board) | | `add_layer` | Add copper/technical/signal layer | Routed (board) |
| `set_active_layer` | Change the active working layer | Routed (board) | | `set_active_layer` | Change the active working layer | Routed (board) |
| `get_layer_list` | List all layers on the board | Routed (board) | | `get_layer_list` | List all layers on the board | Routed (board) |
| `add_mounting_hole` | Add mounting hole with optional pad | Routed (board) | | `add_mounting_hole` | Add mounting hole with optional pad | Routed (board) |
| `add_board_text` | Add text annotation to board | Routed (board) | | `add_board_text` | Add text annotation to board | Routed (board) |
| `add_zone` | Add copper zone/pour with clearance settings | Routed (board) | | `add_zone` | Add copper zone/pour with clearance settings | Routed (board) |
| `get_board_extents` | Get bounding box of board | Routed (board) | | `get_board_extents` | Get bounding box of board | Routed (board) |
| `get_board_2d_view` | Render 2D board view (PNG/JPG/SVG) | Routed (board) | | `get_board_2d_view` | Render 2D board view (PNG/JPG/SVG) | Routed (board) |
| `import_svg_logo` | Import SVG file as polygons on silkscreen layer | Additional | | `import_svg_logo` | Import SVG file as polygons on silkscreen layer | Additional |
--- ---
## Component Management (16 tools) ## Component Management (16 tools)
*Source: `src/tools/component.ts`* _Source: `src/tools/component.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | -------------------------- | ------------------------------------------------------------- | ------------------ |
| `place_component` | Place footprint on PCB (position, rotation, reference, value) | Direct | | `place_component` | Place footprint on PCB (position, rotation, reference, value) | Direct |
| `move_component` | Move component to new position | Direct | | `move_component` | Move component to new position | Direct |
| `rotate_component` | Rotate component (absolute angle) | Routed (component) | | `rotate_component` | Rotate component (absolute angle) | Routed (component) |
| `delete_component` | Remove component from board | Routed (component) | | `delete_component` | Remove component from board | Routed (component) |
| `edit_component` | Edit component properties (reference, value, footprint) | Routed (component) | | `edit_component` | Edit component properties (reference, value, footprint) | Routed (component) |
| `find_component` | Search components by reference or value | Routed (component) | | `find_component` | Search components by reference or value | Routed (component) |
| `get_component_properties` | Get all properties of a component | Routed (component) | | `get_component_properties` | Get all properties of a component | Routed (component) |
| `add_component_annotation` | Add annotation/comment to component | Routed (component) | | `add_component_annotation` | Add annotation/comment to component | Routed (component) |
| `group_components` | Group multiple components together | Routed (component) | | `group_components` | Group multiple components together | Routed (component) |
| `replace_component` | Replace component with different footprint | Routed (component) | | `replace_component` | Replace component with different footprint | Routed (component) |
| `get_component_pads` | Get all pad information for a component | Additional | | `get_component_pads` | Get all pad information for a component | Additional |
| `get_component_list` | List all components with optional filters | Additional | | `get_component_list` | List all components with optional filters | Additional |
| `get_pad_position` | Get precise position of a specific pad | Additional | | `get_pad_position` | Get precise position of a specific pad | Additional |
| `place_component_array` | Place array of components (rows x columns) | Additional | | `place_component_array` | Place array of components (rows x columns) | Additional |
| `align_components` | Align components (horizontal, vertical, grid) | Additional | | `align_components` | Align components (horizontal, vertical, grid) | Additional |
| `duplicate_component` | Duplicate component with offset | Additional | | `duplicate_component` | Duplicate component with offset | Additional |
--- ---
## Routing (13 tools) ## Routing (13 tools)
*Source: `src/tools/routing.ts`* _Source: `src/tools/routing.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ------------------------- | ---------------------------------------------------- | ---------------- |
| `add_net` | Create a new net on the PCB | Direct | | `add_net` | Create a new net on the PCB | Direct |
| `route_trace` | Route trace segment between XY points (single layer) | Direct | | `route_trace` | Route trace segment between XY points (single layer) | Direct |
| `add_via` | Add via (through/blind/buried) | Routed (routing) | | `add_via` | Add via (through/blind/buried) | Routed (routing) |
| `add_copper_pour` | Add copper pour / ground plane | Routed (routing) | | `add_copper_pour` | Add copper pour / ground plane | Routed (routing) |
| `delete_trace` | Delete traces by UUID, position, or bulk by net | Additional | | `delete_trace` | Delete traces by UUID, position, or bulk by net | Additional |
| `query_traces` | Query/filter traces by net, layer, or bounding box | Additional | | `query_traces` | Query/filter traces by net, layer, or bounding box | Additional |
| `get_nets_list` | List all nets with statistics | Additional | | `get_nets_list` | List all nets with statistics | Additional |
| `modify_trace` | Modify existing trace (width, layer, net) | Additional | | `modify_trace` | Modify existing trace (width, layer, net) | Additional |
| `create_netclass` | Create net class with design rules | Additional | | `create_netclass` | Create net class with design rules | Additional |
| `route_differential_pair` | Route differential pair traces | Additional | | `route_differential_pair` | Route differential pair traces | Additional |
| `refill_zones` | Refill all copper zones | Additional | | `refill_zones` | Refill all copper zones | Additional |
| `route_pad_to_pad` | Route trace between two pads with auto-via insertion | Additional | | `route_pad_to_pad` | Route trace between two pads with auto-via insertion | Additional |
| `copy_routing_pattern` | Copy routing from source to target component groups | Additional | | `copy_routing_pattern` | Copy routing from source to target component groups | Additional |
--- ---
## Design Rules and DRC (8 tools) ## Design Rules and DRC (8 tools)
*Source: `src/tools/design-rules.ts`* _Source: `src/tools/design-rules.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ----------------------- | ----------------------------------------------------------- | ------------ |
| `set_design_rules` | Set global design rules (clearance, track width, via sizes) | Routed (drc) | | `set_design_rules` | Set global design rules (clearance, track width, via sizes) | Routed (drc) |
| `get_design_rules` | Get current design rules | Routed (drc) | | `get_design_rules` | Get current design rules | Routed (drc) |
| `run_drc` | Run design rule check | Routed (drc) | | `run_drc` | Run design rule check | Routed (drc) |
| `add_net_class` | Add net class with custom rules | Routed (drc) | | `add_net_class` | Add net class with custom rules | Routed (drc) |
| `assign_net_to_class` | Assign net to a net class | Routed (drc) | | `assign_net_to_class` | Assign net to a net class | Routed (drc) |
| `set_layer_constraints` | Set layer-specific constraints | Routed (drc) | | `set_layer_constraints` | Set layer-specific constraints | Routed (drc) |
| `check_clearance` | Check clearance between two items | Routed (drc) | | `check_clearance` | Check clearance between two items | Routed (drc) |
| `get_drc_violations` | Get DRC violation list (filter by severity) | Routed (drc) | | `get_drc_violations` | Get DRC violation list (filter by severity) | Routed (drc) |
--- ---
## Export (8 tools) ## Export (8 tools)
*Source: `src/tools/export.ts`* _Source: `src/tools/export.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ---------------------- | ------------------------------------------------- | --------------- |
| `export_gerber` | Export Gerber files for fabrication | Routed (export) | | `export_gerber` | Export Gerber files for fabrication | Routed (export) |
| `export_pdf` | Export PDF with layer selection and page size | Routed (export) | | `export_pdf` | Export PDF with layer selection and page size | Routed (export) |
| `export_svg` | Export SVG vector graphics | Routed (export) | | `export_svg` | Export SVG vector graphics | Routed (export) |
| `export_3d` | Export 3D model (STEP, STL, VRML, OBJ) | Routed (export) | | `export_3d` | Export 3D model (STEP, STL, VRML, OBJ) | Routed (export) |
| `export_bom` | Export Bill of Materials (CSV, XML, HTML, JSON) | Routed (export) | | `export_bom` | Export Bill of Materials (CSV, XML, HTML, JSON) | Routed (export) |
| `export_netlist` | Export netlist (KiCad, Spice, Cadstar, OrcadPCB2) | Routed (export) | | `export_netlist` | Export netlist (KiCad, Spice, Cadstar, OrcadPCB2) | Routed (export) |
| `export_position_file` | Export component position file for pick and place | Routed (export) | | `export_position_file` | Export component position file for pick and place | Routed (export) |
| `export_vrml` | Export VRML 3D model | Routed (export) | | `export_vrml` | Export VRML 3D model | Routed (export) |
--- ---
## Schematic (27 tools) ## Schematic (27 tools)
*Source: `src/tools/schematic.ts`* _Source: `src/tools/schematic.ts`_
### Component Operations ### Component Operations
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ---------------------------- | ------------------------------------------------------- | ------------------ |
| `add_schematic_component` | Add component to schematic (symbol from library) | Direct | | `add_schematic_component` | Add component to schematic (symbol from library) | Direct |
| `delete_schematic_component` | Remove component from schematic | Additional | | `delete_schematic_component` | Remove component from schematic | Additional |
| `edit_schematic_component` | Edit component properties (footprint, value, reference) | Additional | | `edit_schematic_component` | Edit component properties (footprint, value, reference) | Additional |
| `get_schematic_component` | Get component info with field positions | Additional | | `get_schematic_component` | Get component info with field positions | Additional |
| `list_schematic_components` | List all components in schematic | Direct | | `list_schematic_components` | List all components in schematic | Direct |
| `move_schematic_component` | Move component to new position | Routed (schematic) | | `move_schematic_component` | Move component to new position | Routed (schematic) |
| `rotate_schematic_component` | Rotate component | Routed (schematic) | | `rotate_schematic_component` | Rotate component | Routed (schematic) |
| `annotate_schematic` | Auto-annotate reference designators | Direct | | `annotate_schematic` | Auto-annotate reference designators | Direct |
### Wiring and Connections ### Wiring and Connections
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ----------------------------- | ------------------------------------------------ | ------------------ |
| `add_wire` | Add wire connection between two points | Routed (schematic) | | `add_wire` | Add wire connection between two points | Routed (schematic) |
| `delete_schematic_wire` | Delete wire segment | Routed (schematic) | | `delete_schematic_wire` | Delete wire segment | Routed (schematic) |
| `add_schematic_connection` | Connect two component pins with wire | Routed (schematic) | | `add_schematic_connection` | Connect two component pins with wire | Routed (schematic) |
| `add_schematic_net_label` | Add net label to schematic | Direct | | `add_schematic_net_label` | Add net label to schematic | Direct |
| `delete_schematic_net_label` | Delete net label | Routed (schematic) | | `delete_schematic_net_label` | Delete net label | Routed (schematic) |
| `connect_to_net` | Connect component pin to named net | Direct | | `connect_to_net` | Connect component pin to named net | Direct |
| `connect_passthrough` | Connect all matching pins between two connectors | Direct | | `connect_passthrough` | Connect all matching pins between two connectors | Direct |
| `get_schematic_pin_locations` | Get pin locations for a component | Additional | | `get_schematic_pin_locations` | Get pin locations for a component | Additional |
### Net Analysis ### Net Analysis
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ----------------------- | ----------------------------- | ------------------ |
| `get_net_connections` | Get all connections for a net | Routed (schematic) | | `get_net_connections` | Get all connections for a net | Routed (schematic) |
| `list_schematic_nets` | List all nets in schematic | Routed (schematic) | | `list_schematic_nets` | List all nets in schematic | Routed (schematic) |
| `list_schematic_wires` | List all wires in schematic | Routed (schematic) | | `list_schematic_wires` | List all wires in schematic | Routed (schematic) |
| `list_schematic_labels` | List all net labels | Routed (schematic) | | `list_schematic_labels` | List all net labels | Routed (schematic) |
### Schematic Creation and Export ### Schematic Creation and Export
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ---------------------- | -------------------------------- | ------------------ |
| `create_schematic` | Create a new schematic file | Routed (schematic) | | `create_schematic` | Create a new schematic file | Routed (schematic) |
| `get_schematic_view` | Get schematic as image (PNG/SVG) | Routed (schematic) | | `get_schematic_view` | Get schematic as image (PNG/SVG) | Routed (schematic) |
| `export_schematic_svg` | Export schematic to SVG | Routed (schematic) | | `export_schematic_svg` | Export schematic to SVG | Routed (schematic) |
| `export_schematic_pdf` | Export schematic to PDF | Routed (schematic) | | `export_schematic_pdf` | Export schematic to PDF | Routed (schematic) |
### Validation and Synchronization ### Validation and Synchronization
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ------------------------- | ----------------------------------------------------- | ------------------ |
| `run_erc` | Run electrical rule check | Additional | | `run_erc` | Run electrical rule check | Additional |
| `generate_netlist` | Generate netlist from schematic | Routed (schematic) | | `generate_netlist` | Generate netlist from schematic | Routed (schematic) |
| `sync_schematic_to_board` | Sync schematic components/nets to PCB (F8 equivalent) | Direct | | `sync_schematic_to_board` | Sync schematic components/nets to PCB (F8 equivalent) | Direct |
--- ---
## Footprint Libraries (4 tools) ## Footprint Libraries (4 tools)
*Source: `src/tools/library.ts`* _Source: `src/tools/library.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ------------------------- | ------------------------------------- | ---------------- |
| `list_libraries` | List all footprint libraries | Routed (library) | | `list_libraries` | List all footprint libraries | Routed (library) |
| `search_footprints` | Search footprints across libraries | Routed (library) | | `search_footprints` | Search footprints across libraries | Routed (library) |
| `list_library_footprints` | List footprints in a specific library | Routed (library) | | `list_library_footprints` | List footprints in a specific library | Routed (library) |
| `get_footprint_info` | Get detailed footprint information | Routed (library) | | `get_footprint_info` | Get detailed footprint information | Routed (library) |
--- ---
## Symbol Libraries (4 tools) ## Symbol Libraries (4 tools)
*Source: `src/tools/library-symbol.ts`* _Source: `src/tools/library-symbol.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ----------------------- | ----------------------------------------------- | ---------- |
| `list_symbol_libraries` | List all symbol libraries from sym-lib-table | Additional | | `list_symbol_libraries` | List all symbol libraries from sym-lib-table | Additional |
| `search_symbols` | Search symbols by name, LCSC ID, or description | Additional | | `search_symbols` | Search symbols by name, LCSC ID, or description | Additional |
| `list_library_symbols` | List symbols in a specific library | Additional | | `list_library_symbols` | List symbols in a specific library | Additional |
| `get_symbol_info` | Get detailed symbol information | Additional | | `get_symbol_info` | Get detailed symbol information | Additional |
--- ---
## Footprint Creator (4 tools) ## Footprint Creator (4 tools)
*Source: `src/tools/footprint.ts`* _Source: `src/tools/footprint.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ---------------------------- | ------------------------------------------------------------------------ | ---------- |
| `create_footprint` | Create custom .kicad_mod footprint (SMD/THT pads, courtyard, silkscreen) | Additional | | `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 | | `edit_footprint_pad` | Edit pad in existing footprint (size, position, drill, shape) | Additional |
| `register_footprint_library` | Register .pretty library in fp-lib-table | Additional | | `register_footprint_library` | Register .pretty library in fp-lib-table | Additional |
| `list_footprint_libraries` | List available .pretty libraries | Additional | | `list_footprint_libraries` | List available .pretty libraries | Additional |
--- ---
## Symbol Creator (4 tools) ## Symbol Creator (4 tools)
*Source: `src/tools/symbol-creator.ts`* _Source: `src/tools/symbol-creator.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ------------------------- | ------------------------------------------------------------- | ---------- |
| `create_symbol` | Create custom .kicad_sym symbol (pins, rectangles, polylines) | Additional | | `create_symbol` | Create custom .kicad_sym symbol (pins, rectangles, polylines) | Additional |
| `delete_symbol` | Remove symbol from library | Additional | | `delete_symbol` | Remove symbol from library | Additional |
| `list_symbols_in_library` | List all symbols in a .kicad_sym file | Additional | | `list_symbols_in_library` | List all symbols in a .kicad_sym file | Additional |
| `register_symbol_library` | Register library in sym-lib-table | Additional | | `register_symbol_library` | Register library in sym-lib-table | Additional |
--- ---
## Datasheet Tools (2 tools) ## Datasheet Tools (2 tools)
*Source: `src/tools/datasheet.ts`* _Source: `src/tools/datasheet.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ------------------- | --------------------------------------------------- | ---------- |
| `enrich_datasheets` | Fill missing datasheet URLs using LCSC part numbers | Additional | | `enrich_datasheets` | Fill missing datasheet URLs using LCSC part numbers | Additional |
| `get_datasheet_url` | Get LCSC datasheet URL for a component | Additional | | `get_datasheet_url` | Get LCSC datasheet URL for a component | Additional |
--- ---
## JLCPCB Integration (5 tools) ## JLCPCB Integration (5 tools)
*Source: `src/tools/jlcpcb-api.ts`* _Source: `src/tools/jlcpcb-api.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ----------------------------- | ------------------------------------------------------- | ---------- |
| `download_jlcpcb_database` | Download 2.5M+ parts catalog to local SQLite database | Additional | | `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 | | `search_jlcpcb_parts` | Search parts by specs (category, package, library type) | Additional |
| `get_jlcpcb_part` | Get detailed part info with pricing | Additional | | `get_jlcpcb_part` | Get detailed part info with pricing | Additional |
| `get_jlcpcb_database_stats` | Get database statistics | Additional | | `get_jlcpcb_database_stats` | Get database statistics | Additional |
| `suggest_jlcpcb_alternatives` | Find cheaper or in-stock alternatives | Additional | | `suggest_jlcpcb_alternatives` | Find cheaper or in-stock alternatives | Additional |
--- ---
## Freerouting Autorouter (4 tools) ## Freerouting Autorouter (4 tools)
*Source: `src/tools/freerouting.ts`* _Source: `src/tools/freerouting.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ------------------- | ---------------------------------------------------------- | ------------------ |
| `autoroute` | Run Freerouting autorouter (export DSN, route, import SES) | Routed (autoroute) | | `autoroute` | Run Freerouting autorouter (export DSN, route, import SES) | Routed (autoroute) |
| `export_dsn` | Export Specctra DSN file for manual routing | Routed (autoroute) | | `export_dsn` | Export Specctra DSN file for manual routing | Routed (autoroute) |
| `import_ses` | Import routed SES file back into PCB | Routed (autoroute) | | `import_ses` | Import routed SES file back into PCB | Routed (autoroute) |
| `check_freerouting` | Check Java and Freerouting JAR availability | Routed (autoroute) | | `check_freerouting` | Check Java and Freerouting JAR availability | Routed (autoroute) |
--- ---
## UI Management (2 tools) ## UI Management (2 tools)
*Source: `src/tools/ui.ts`* _Source: `src/tools/ui.ts`_
| Tool | Description | Access | | Tool | Description | Access |
|------|-------------|--------| | ----------------- | ----------------------------------------- | -------------- |
| `check_kicad_ui` | Check if KiCAD UI is running | Direct | | `check_kicad_ui` | Check if KiCAD UI is running | Direct |
| `launch_kicad_ui` | Launch KiCAD UI (optionally with project) | Routed (board) | | `launch_kicad_ui` | Launch KiCAD UI (optionally with project) | Routed (board) |
--- ---
## Router Tools (4 tools) ## Router Tools (4 tools)
*Source: `src/tools/router.ts`* _Source: `src/tools/router.ts`_
These meta-tools provide discovery and execution of routed tools: These meta-tools provide discovery and execution of routed tools:
| Tool | Description | | Tool | Description |
|------|-------------| | ---------------------- | ------------------------------------ |
| `list_tool_categories` | Browse all available tool categories | | `list_tool_categories` | Browse all available tool categories |
| `get_category_tools` | View tools in a specific category | | `get_category_tools` | View tools in a specific category |
| `search_tools` | Find tools by keyword | | `search_tools` | Find tools by keyword |
| `execute_tool` | Run any routed tool with parameters | | `execute_tool` | Run any routed tool with parameters |
--- ---
## Summary by Access Type ## Summary by Access Type
| Access Type | Count | Description | | Access Type | Count | Description |
|-------------|-------|-------------| | ----------- | ------- | --------------------------------------------------- |
| Direct | 18 | Always visible, no router needed | | Direct | 18 | Always visible, no router needed |
| Routed | 65 | Discovered via router, invoked via `execute_tool` | | Routed | 65 | Discovered via router, invoked via `execute_tool` |
| Router | 4 | Meta-tools for discovering and running routed tools | | Router | 4 | Meta-tools for discovering and running routed tools |
| Additional | 35 | Always visible, registered directly | | Additional | 35 | Always visible, registered directly |
| **Total** | **122** | | | **Total** | **122** | |
## Summary by Category ## Summary by Category
| Category | Tool Count | | Category | Tool Count |
|----------|------------| | -------------------- | ---------- |
| Project Management | 5 | | Project Management | 5 |
| Board Management | 12 | | Board Management | 12 |
| Component Management | 16 | | Component Management | 16 |
| Routing | 13 | | Routing | 13 |
| Design Rules / DRC | 8 | | Design Rules / DRC | 8 |
| Export | 8 | | Export | 8 |
| Schematic | 27 | | Schematic | 27 |
| Footprint Libraries | 4 | | Footprint Libraries | 4 |
| Symbol Libraries | 4 | | Symbol Libraries | 4 |
| Footprint Creator | 4 | | Footprint Creator | 4 |
| Symbol Creator | 4 | | Symbol Creator | 4 |
| Datasheet | 2 | | Datasheet | 2 |
| JLCPCB Integration | 5 | | JLCPCB Integration | 5 |
| Freerouting | 4 | | Freerouting | 4 |
| UI Management | 2 | | UI Management | 2 |
| Router | 4 | | Router | 4 |
| **Total** | **122** | | **Total** | **122** |
## Token Impact ## Token Impact

View File

@@ -1,399 +1,425 @@
# KiCAD UI Auto-Launch Feature # KiCAD UI Auto-Launch Feature
Automatically detect and launch KiCAD UI when needed, providing seamless visual feedback for PCB design operations. Automatically detect and launch KiCAD UI when needed, providing seamless visual feedback for PCB design operations.
--- ---
## 🎯 Overview ## 🎯 Overview
The KiCAD MCP server can now: The KiCAD MCP server can now:
- ✅ Detect if KiCAD UI is running
-Launch KiCAD automatically when needed -Detect if KiCAD UI is running
-Open projects directly in the UI -Launch KiCAD automatically when needed
-Work across Linux, macOS, and Windows -Open projects directly in the UI
- ✅ Work across Linux, macOS, and Windows
---
---
## 🚀 Quick Start
## 🚀 Quick Start
### Enable Auto-Launch
### Enable Auto-Launch
Add to your MCP configuration:
Add to your MCP configuration:
```json
{ ```json
"mcpServers": { {
"kicad": { "mcpServers": {
"command": "node", "kicad": {
"args": ["/path/to/KiCAD-MCP-Server/dist/index.js"], "command": "node",
"env": { "args": ["/path/to/KiCAD-MCP-Server/dist/index.js"],
"KICAD_AUTO_LAUNCH": "true" "env": {
} "KICAD_AUTO_LAUNCH": "true"
} }
} }
} }
``` }
```
### Manual Control (Default)
### Manual Control (Default)
Without `KICAD_AUTO_LAUNCH=true`, you manually control when KiCAD launches using the new MCP tools.
Without `KICAD_AUTO_LAUNCH=true`, you manually control when KiCAD launches using the new MCP tools.
---
---
## 🛠️ New MCP Tools
## 🛠️ New MCP Tools
### 1. `check_kicad_ui`
### 1. `check_kicad_ui`
Check if KiCAD is currently running.
Check if KiCAD is currently running.
**Parameters:** None
**Parameters:** None
**Example:**
```typescript **Example:**
{
"command": "check_kicad_ui", ```typescript
"params": {} {
} "command": "check_kicad_ui",
``` "params": {}
}
**Response:** ```
```json
{ **Response:**
"success": true,
"running": true, ```json
"processes": [ {
{ "success": true,
"pid": "12345", "running": true,
"name": "pcbnew", "processes": [
"command": "/usr/bin/pcbnew /tmp/project.kicad_pcb" {
} "pid": "12345",
], "name": "pcbnew",
"message": "KiCAD is running" "command": "/usr/bin/pcbnew /tmp/project.kicad_pcb"
} }
``` ],
"message": "KiCAD is running"
### 2. `launch_kicad_ui` }
```
Launch KiCAD UI, optionally with a project file.
### 2. `launch_kicad_ui`
**Parameters:**
- `projectPath` (optional): Path to `.kicad_pcb` file to open Launch KiCAD UI, optionally with a project file.
- `autoLaunch` (optional): Whether to launch if not running (default: true)
**Parameters:**
**Example:**
```typescript - `projectPath` (optional): Path to `.kicad_pcb` file to open
{ - `autoLaunch` (optional): Whether to launch if not running (default: true)
"command": "launch_kicad_ui",
"params": { **Example:**
"projectPath": "/tmp/mcp_demo/New_Project.kicad_pcb"
} ```typescript
} {
``` "command": "launch_kicad_ui",
"params": {
**Response:** "projectPath": "/tmp/mcp_demo/New_Project.kicad_pcb"
```json }
{ }
"success": true, ```
"running": true,
"launched": true, **Response:**
"message": "KiCAD launched successfully",
"project": "/tmp/mcp_demo/New_Project.kicad_pcb", ```json
"processes": [...] {
} "success": true,
``` "running": true,
"launched": true,
--- "message": "KiCAD launched successfully",
"project": "/tmp/mcp_demo/New_Project.kicad_pcb",
## 🔄 Workflow Examples "processes": [...]
}
### Example 1: Manual Launch ```
``` ---
User: "Check if KiCAD is running"
Claude: Uses check_kicad_ui → "KiCAD is not running" ## 🔄 Workflow Examples
User: "Launch it with the demo project" ### Example 1: Manual Launch
Claude: Uses launch_kicad_ui → KiCAD opens with project loaded!
``` ```
User: "Check if KiCAD is running"
### Example 2: Auto-Launch Mode Claude: Uses check_kicad_ui → "KiCAD is not running"
With `KICAD_AUTO_LAUNCH=true`: User: "Launch it with the demo project"
Claude: Uses launch_kicad_ui → KiCAD opens with project loaded!
``` ```
User: "Create a new Arduino shield PCB"
Claude: ### Example 2: Auto-Launch Mode
1. Creates project
2. Detects KiCAD not running With `KICAD_AUTO_LAUNCH=true`:
3. Automatically launches KiCAD with the new project
4. You see the board in real-time as it's designed! ```
``` User: "Create a new Arduino shield PCB"
Claude:
### Example 3: Side-by-Side Design 1. Creates project
2. Detects KiCAD not running
``` 3. Automatically launches KiCAD with the new project
┌────────────────────────────────────────────────────────┐ 4. You see the board in real-time as it's designed!
│ Workflow: AI-Assisted PCB Design │ ```
├────────────────────────────────────────────────────────┤
│ │ ### Example 3: Side-by-Side Design
│ 1. User: "Create a 100mm square board" │
│ → Claude creates project │ ```
│ → KiCAD auto-launches if not running │ ┌────────────────────────────────────────────────────────┐
Workflow: AI-Assisted PCB Design
│ 2. User: "Add 4 mounting holes at corners" │ ├────────────────────────────────────────────────────────┤
→ Claude adds holes
→ KiCAD detects file change, prompts to reload 1. User: "Create a 100mm square board"
│ → User clicks "Yes" → sees holes appear! │ → Claude creates project
→ KiCAD auto-launches if not running
3. User: "Perfect! Now add a circular outline..."
→ Iterative design continues... 2. User: "Add 4 mounting holes at corners"
→ Claude adds holes
└────────────────────────────────────────────────────────┘ │ → KiCAD detects file change, prompts to reload │
``` │ → User clicks "Yes" → sees holes appear! │
│ │
--- │ 3. User: "Perfect! Now add a circular outline..." │
│ → Iterative design continues... │
## ⚙️ Configuration Options │ │
└────────────────────────────────────────────────────────┘
### Environment Variables ```
| Variable | Default | Description | ---
|----------|---------|-------------|
| `KICAD_AUTO_LAUNCH` | `false` | Auto-launch KiCAD when needed | ## ⚙️ Configuration Options
| `KICAD_EXECUTABLE` | auto-detect | Override KiCAD executable path |
### Environment Variables
### Custom Executable Path
| Variable | Default | Description |
If KiCAD is installed in a non-standard location: | ------------------- | ----------- | ------------------------------ |
| `KICAD_AUTO_LAUNCH` | `false` | Auto-launch KiCAD when needed |
```json | `KICAD_EXECUTABLE` | auto-detect | Override KiCAD executable path |
{
"env": { ### Custom Executable Path
"KICAD_AUTO_LAUNCH": "true",
"KICAD_EXECUTABLE": "/opt/kicad/bin/pcbnew" If KiCAD is installed in a non-standard location:
}
} ```json
``` {
"env": {
--- "KICAD_AUTO_LAUNCH": "true",
"KICAD_EXECUTABLE": "/opt/kicad/bin/pcbnew"
## 🔍 How It Works }
}
### Process Detection ```
**Linux:** ---
```bash
pgrep -f "pcbnew|kicad" ## 🔍 How It Works
```
### Process Detection
**macOS:**
```bash **Linux:**
pgrep -f "KiCad|pcbnew"
``` ```bash
pgrep -f "pcbnew|kicad"
**Windows:** ```
```powershell
tasklist /FI "IMAGENAME eq pcbnew.exe" **macOS:**
```
```bash
### Auto-Discovery of Executable pgrep -f "KiCad|pcbnew"
```
The system searches for KiCAD in:
**Windows:**
**Linux:**
- `/usr/bin/pcbnew` ```powershell
- `/usr/local/bin/pcbnew` tasklist /FI "IMAGENAME eq pcbnew.exe"
- `/usr/bin/kicad` ```
**macOS:** ### Auto-Discovery of Executable
- `/Applications/KiCad/KiCad.app/Contents/MacOS/kicad`
- `/Applications/KiCad/pcbnew.app/Contents/MacOS/pcbnew` The system searches for KiCAD in:
**Windows:** **Linux:**
- `C:/Program Files/KiCad/9.0/bin/pcbnew.exe`
- `C:/Program Files/KiCad/8.0/bin/pcbnew.exe` - `/usr/bin/pcbnew`
- `/usr/local/bin/pcbnew`
### Launch Process - `/usr/bin/kicad`
1. Check if KiCAD is already running **macOS:**
2. If not, find executable path
3. Spawn process with optional project path - `/Applications/KiCad/KiCad.app/Contents/MacOS/kicad`
4. Wait up to 5 seconds for process to start - `/Applications/KiCad/pcbnew.app/Contents/MacOS/pcbnew`
5. Verify process is running
6. Return status to MCP client **Windows:**
--- - `C:/Program Files/KiCad/9.0/bin/pcbnew.exe`
- `C:/Program Files/KiCad/8.0/bin/pcbnew.exe`
## 💡 Use Cases
### Launch Process
### 1. Beginner-Friendly Workflow
1. Check if KiCAD is already running
User doesn't need to know how to launch KiCAD manually: 2. If not, find executable path
``` 3. Spawn process with optional project path
User: "Help me design a simple LED board" 4. Wait up to 5 seconds for process to start
Claude: [Auto-launches KiCAD, creates project, designs board] 5. Verify process is running
``` 6. Return status to MCP client
### 2. Streamlined Iteration ---
For rapid prototyping with visual feedback: ## 💡 Use Cases
```
1. Claude creates board → KiCAD opens ### 1. Beginner-Friendly Workflow
2. User sees board, requests changes
3. Claude modifies → KiCAD reloads User doesn't need to know how to launch KiCAD manually:
4. Repeat until satisfied
``` ```
User: "Help me design a simple LED board"
### 3. Batch Processing Claude: [Auto-launches KiCAD, creates project, designs board]
```
Process multiple designs without manual intervention:
```python ### 2. Streamlined Iteration
for design in designs:
create_project(design) For rapid prototyping with visual feedback:
# KiCAD auto-launches and loads each one
add_components(design) ```
route_board(design) 1. Claude creates board → KiCAD opens
export_gerbers(design) 2. User sees board, requests changes
``` 3. Claude modifies → KiCAD reloads
4. Repeat until satisfied
--- ```
## 🐛 Troubleshooting ### 3. Batch Processing
### KiCAD Doesn't Launch Process multiple designs without manual intervention:
**Check executable path:** ```python
```bash for design in designs:
# Linux/macOS create_project(design)
which pcbnew # KiCAD auto-launches and loads each one
add_components(design)
# Windows route_board(design)
where pcbnew.exe export_gerbers(design)
``` ```
**Override if needed:** ---
```json
{ ## 🐛 Troubleshooting
"env": {
"KICAD_EXECUTABLE": "/path/to/pcbnew" ### KiCAD Doesn't Launch
}
} **Check executable path:**
```
```bash
### Process Detection Fails # Linux/macOS
which pcbnew
**Manual check:**
```bash # Windows
# Linux/macOS where pcbnew.exe
ps aux | grep kicad ```
# Windows **Override if needed:**
tasklist | findstr kicad
``` ```json
{
**Verify permissions:** "env": {
- Ensure user can execute `pgrep` (Linux/macOS) "KICAD_EXECUTABLE": "/path/to/pcbnew"
- Ensure user can execute `tasklist` (Windows) }
}
### Auto-Launch Doesn't Work ```
1. Check `KICAD_AUTO_LAUNCH` is set to `"true"` (string, not boolean) ### Process Detection Fails
2. Verify KiCAD is in PATH or set `KICAD_EXECUTABLE`
3. Check MCP server logs for errors **Manual check:**
4. Try manual launch first: `launch_kicad_ui`
```bash
--- # Linux/macOS
ps aux | grep kicad
## 📊 Implementation Details
# Windows
### Files Modified/Created tasklist | findstr kicad
```
**New Files:**
- `python/utils/kicad_process.py` - Process management utilities **Verify permissions:**
- `src/tools/ui.ts` - MCP tool definitions
- `docs/UI_AUTO_LAUNCH.md` - This documentation - Ensure user can execute `pgrep` (Linux/macOS)
- Ensure user can execute `tasklist` (Windows)
**Modified Files:**
- `python/kicad_interface.py` - Added UI command handlers ### Auto-Launch Doesn't Work
- `src/server.ts` - Registered UI tools
1. Check `KICAD_AUTO_LAUNCH` is set to `"true"` (string, not boolean)
### API Reference 2. Verify KiCAD is in PATH or set `KICAD_EXECUTABLE`
3. Check MCP server logs for errors
**Python:** 4. Try manual launch first: `launch_kicad_ui`
```python
from utils.kicad_process import KiCADProcessManager, check_and_launch_kicad ---
# Check if running ## 📊 Implementation Details
manager = KiCADProcessManager()
is_running = manager.is_running() ### Files Modified/Created
# Launch KiCAD **New Files:**
success = manager.launch(project_path="/path/to/file.kicad_pcb")
- `python/utils/kicad_process.py` - Process management utilities
# Get process info - `src/tools/ui.ts` - MCP tool definitions
processes = manager.get_process_info() - `docs/UI_AUTO_LAUNCH.md` - This documentation
# High-level helper **Modified Files:**
result = check_and_launch_kicad(
project_path=Path("/path/to/file.kicad_pcb"), - `python/kicad_interface.py` - Added UI command handlers
auto_launch=True - `src/server.ts` - Registered UI tools
)
``` ### API Reference
**MCP Tools:** **Python:**
```typescript
// Check status ```python
await callKicadScript("check_kicad_ui", {}); from utils.kicad_process import KiCADProcessManager, check_and_launch_kicad
// Launch # Check if running
await callKicadScript("launch_kicad_ui", { manager = KiCADProcessManager()
projectPath: "/path/to/project.kicad_pcb", is_running = manager.is_running()
autoLaunch: true
}); # Launch KiCAD
``` success = manager.launch(project_path="/path/to/file.kicad_pcb")
--- # Get process info
processes = manager.get_process_info()
## 🔮 Future Enhancements
# High-level helper
### Planned Features result = check_and_launch_kicad(
project_path=Path("/path/to/file.kicad_pcb"),
- **Window Management:** Bring KiCAD to front, minimize/maximize auto_launch=True
- **Multi-Instance:** Handle multiple KiCAD instances )
- **IPC Integration:** Seamless integration with IPC backend ```
- **Status Notifications:** Push notifications when KiCAD state changes
- **Auto-Close:** Option to close KiCAD after operations complete **MCP Tools:**
### IPC Mode (Coming Weeks 2-3) ```typescript
// Check status
When IPC backend is fully implemented: await callKicadScript("check_kicad_ui", {});
```
KiCAD runs in background → MCP connects via IPC → Real-time updates // Launch
No file reloading needed! Changes appear instantly. await callKicadScript("launch_kicad_ui", {
``` projectPath: "/path/to/project.kicad_pcb",
autoLaunch: true,
--- });
```
## 📝 Summary
---
**Before this feature:**
``` ## 🔮 Future Enhancements
User manually launches KiCAD
User manually opens project ### Planned Features
Claude makes changes
User manually reloads - **Window Management:** Bring KiCAD to front, minimize/maximize
``` - **Multi-Instance:** Handle multiple KiCAD instances
- **IPC Integration:** Seamless integration with IPC backend
**After this feature:** - **Status Notifications:** Push notifications when KiCAD state changes
``` - **Auto-Close:** Option to close KiCAD after operations complete
User: "Design a board"
→ KiCAD auto-launches with project ### IPC Mode (Coming Weeks 2-3)
→ Changes appear (with quick reload)
→ Seamless AI-assisted design! When IPC backend is fully implemented:
```
```
--- KiCAD runs in background → MCP connects via IPC → Real-time updates
No file reloading needed! Changes appear instantly.
**Last Updated:** 2025-10-26 ```
**Version:** 2.0.0-alpha.1
**Status:** ✅ Production Ready ---
## 📝 Summary
**Before this feature:**
```
User manually launches KiCAD
User manually opens project
Claude makes changes
User manually reloads
```
**After this feature:**
```
User: "Design a board"
→ KiCAD auto-launches with project
→ Changes appear (with quick reload)
→ Seamless AI-assisted design!
```
---
**Last Updated:** 2025-10-26
**Version:** 2.0.0-alpha.1
**Status:** ✅ Production Ready

View File

@@ -1,184 +1,193 @@
# Visual Feedback: Seeing MCP Changes in KiCAD UI # Visual Feedback: Seeing MCP Changes in KiCAD UI
This document explains how to see changes made by the MCP server in the KiCAD UI in real-time or near-real-time. This document explains how to see changes made by the MCP server in the KiCAD UI in real-time or near-real-time.
## Current Status (Week 1 - SWIG Backend) ## Current Status (Week 1 - SWIG Backend)
**Active Backend:** SWIG (legacy pcbnew Python API) **Active Backend:** SWIG (legacy pcbnew Python API)
**Real-time Updates:** Not available yet **Real-time Updates:** Not available yet
**IPC Backend:** Skeleton implemented, operations coming in Weeks 2-3 **IPC Backend:** Skeleton implemented, operations coming in Weeks 2-3
--- ---
## 🎯 Best Current Workflow (SWIG + Manual Reload) ## 🎯 Best Current Workflow (SWIG + Manual Reload)
### Setup ### Setup
1. **Open your project in KiCAD PCB Editor** 1. **Open your project in KiCAD PCB Editor**
```bash
pcbnew /tmp/kicad_test_project/New_Project.kicad_pcb ```bash
``` pcbnew /tmp/kicad_test_project/New_Project.kicad_pcb
```
2. **Make changes via MCP** (Claude Code, Claude Desktop, etc.)
- Example: Add board outline, mounting holes, etc. 2. **Make changes via MCP** (Claude Code, Claude Desktop, etc.)
- Each operation saves the file automatically - Example: Add board outline, mounting holes, etc.
- Each operation saves the file automatically
3. **Reload in KiCAD UI**
- **Option A (Automatic):** KiCAD 8.0+ detects file changes and shows a reload prompt 3. **Reload in KiCAD UI**
- **Option B (Manual):** File → Revert to reload from disk - **Option A (Automatic):** KiCAD 8.0+ detects file changes and shows a reload prompt
- **Keyboard shortcut:** None by default (but you can assign one) - **Option B (Manual):** File → Revert to reload from disk
- **Keyboard shortcut:** None by default (but you can assign one)
### Workflow Example
### Workflow Example
```
┌─────────────────────────────────────────────────────────┐ ```
│ Terminal: Claude Code │ ┌─────────────────────────────────────────────────────────┐
├─────────────────────────────────────────────────────────┤ │ Terminal: Claude Code │
│ You: "Create a 100x80mm board with 4 mounting holes" │ ├─────────────────────────────────────────────────────────┤
You: "Create a 100x80mm board with 4 mounting holes"
Claude: ✓ Added board outline (100x80mm)
✓ Added mounting hole at (5,5) Claude: ✓ Added board outline (100x80mm)
│ ✓ Added mounting hole at (95,5) │ │ ✓ Added mounting hole at (5,5)
│ ✓ Added mounting hole at (95,75) │ │ ✓ Added mounting hole at (95,5)
│ ✓ Added mounting hole at (5,75) │ ✓ Added mounting hole at (95,75) │
│ ✓ Saved project │ ✓ Added mounting hole at (5,75)
└─────────────────────────────────────────────────────────┘ │ ✓ Saved project │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ KiCAD PCB Editor │ ┌─────────────────────────────────────────────────────────┐
├─────────────────────────────────────────────────────────┤ │ KiCAD PCB Editor │
│ [Reload prompt appears] │ ├─────────────────────────────────────────────────────────┤
"File has been modified. Reload?" [Reload prompt appears]
"File has been modified. Reload?"
Click "Yes" → Changes appear instantly! 🎉
└─────────────────────────────────────────────────────────┘ │ Click "Yes" → Changes appear instantly! 🎉 │
``` └─────────────────────────────────────────────────────────┘
```
---
---
## 🔮 Future: IPC Backend (Weeks 2-3)
## 🔮 Future: IPC Backend (Weeks 2-3)
When fully implemented, the IPC backend will provide **true real-time updates**:
When fully implemented, the IPC backend will provide **true real-time updates**:
### How It Will Work
### How It Will Work
```
Claude MCP → IPC Socket → Running KiCAD → Instant UI Update ```
``` Claude MCP → IPC Socket → Running KiCAD → Instant UI Update
```
**No file reloading required** - changes appear as you make them!
**No file reloading required** - changes appear as you make them!
### IPC Setup (When Available)
### IPC Setup (When Available)
1. **Enable IPC in KiCAD**
- Preferences → Advanced Preferences 1. **Enable IPC in KiCAD**
- Search for "IPC" - Preferences → Advanced Preferences
- Enable: "Enable IPC API Server" - Search for "IPC"
- Restart KiCAD - Enable: "Enable IPC API Server"
- Restart KiCAD
2. **Install kicad-python** (Already installed ✓)
```bash 2. **Install kicad-python** (Already installed ✓)
pip install kicad-python
``` ```bash
pip install kicad-python
3. **Configure MCP Server** ```
Add to your MCP config:
```json 3. **Configure MCP Server**
{ Add to your MCP config:
"env": {
"KICAD_BACKEND": "ipc" ```json
} {
} "env": {
``` "KICAD_BACKEND": "ipc"
}
4. **Start KiCAD first, then use MCP** }
- Changes will appear in real-time ```
- No manual reloading needed
4. **Start KiCAD first, then use MCP**
### Current IPC Status - Changes will appear in real-time
- No manual reloading needed
| Feature | Status |
|---------|--------| ### Current IPC Status
| Connection to KiCAD | ✅ Working |
| Version checking | ✅ Working | | Feature | Status |
| Project operations | ⏳ Week 2-3 | | -------------------- | ----------- |
| Board operations | Week 2-3 | | Connection to KiCAD | Working |
| Component operations | Week 2-3 | | Version checking | Working |
| Routing operations | ⏳ Week 2-3 | | Project operations | ⏳ Week 2-3 |
| Board operations | ⏳ Week 2-3 |
--- | Component operations | ⏳ Week 2-3 |
| Routing operations | ⏳ Week 2-3 |
## 🛠️ Monitoring Helper (Optional)
---
A helper script is available to monitor file changes:
## 🛠️ Monitoring Helper (Optional)
```bash
# Watch for changes and notify A helper script is available to monitor file changes:
./scripts/auto_refresh_kicad.sh /tmp/kicad_test_project/New_Project.kicad_pcb
``` ```bash
# Watch for changes and notify
This will print a message each time the MCP server saves changes. ./scripts/auto_refresh_kicad.sh /tmp/kicad_test_project/New_Project.kicad_pcb
```
---
This will print a message each time the MCP server saves changes.
## 💡 Tips for Best Experience
---
### 1. Side-by-Side Windows
``` ## 💡 Tips for Best Experience
┌──────────────────┬──────────────────┐
│ Claude Code │ KiCAD PCB │ ### 1. Side-by-Side Windows
│ (Terminal) │ Editor │
│ │ │ ```
│ Making changes │ Viewing results │ ┌──────────────────┬──────────────────┐
└──────────────────┴──────────────────┘ │ Claude Code │ KiCAD PCB │
``` │ (Terminal) │ Editor │
│ │ │
### 2. Quick Reload Workflow │ Making changes │ Viewing results │
- Keep KiCAD focused in one window └──────────────────┴──────────────────┘
- Make changes via Claude in another ```
- Press Alt+Tab → Click "Reload" → See changes
- Repeat ### 2. Quick Reload Workflow
### 3. Save Frequently - Keep KiCAD focused in one window
The MCP server auto-saves after each operation, so changes are immediately available for reload. - Make changes via Claude in another
- Press Alt+Tab → Click "Reload" → See changes
### 4. Verify Before Complex Operations - Repeat
For complex changes (multiple components, routing, etc.):
1. Make the change ### 3. Save Frequently
2. Reload in KiCAD
3. Verify it looks correct The MCP server auto-saves after each operation, so changes are immediately available for reload.
4. Proceed with next change
### 4. Verify Before Complex Operations
---
For complex changes (multiple components, routing, etc.):
## 🔍 Troubleshooting
1. Make the change
### KiCAD Doesn't Detect File Changes 2. Reload in KiCAD
3. Verify it looks correct
**Cause:** Some KiCAD versions or configurations don't auto-detect 4. Proceed with next change
**Solution:** Use File → Revert manually
---
### Changes Don't Appear After Reload
## 🔍 Troubleshooting
**Cause:** MCP operation may have failed
**Solution:** Check the MCP response for success: true ### KiCAD Doesn't Detect File Changes
### File is Locked **Cause:** Some KiCAD versions or configurations don't auto-detect
**Solution:** Use File → Revert manually
**Cause:** KiCAD has the file open exclusively
**Solution:** ### Changes Don't Appear After Reload
- KiCAD should allow external modifications
- If not, close the file in KiCAD, let MCP make changes, then reopen **Cause:** MCP operation may have failed
**Solution:** Check the MCP response for success: true
---
### File is Locked
## 📅 Roadmap
**Cause:** KiCAD has the file open exclusively
**Current (Week 1):** SWIG backend with manual reload **Solution:**
**Week 2-3:** IPC backend implementation
**Week 4+:** Real-time collaboration features - KiCAD should allow external modifications
- If not, close the file in KiCAD, let MCP make changes, then reopen
---
---
**Last Updated:** 2025-10-26
**Version:** 2.0.0-alpha.1 ## 📅 Roadmap
**Current (Week 1):** SWIG backend with manual reload
**Week 2-3:** IPC backend implementation
**Week 4+:** Real-time collaboration features
---
**Last Updated:** 2025-10-26
**Version:** 2.0.0-alpha.1

View File

@@ -1,475 +1,493 @@
# Windows Troubleshooting Guide # Windows Troubleshooting Guide
This guide helps diagnose and fix common issues when setting up KiCAD MCP Server on Windows. This guide helps diagnose and fix common issues when setting up KiCAD MCP Server on Windows.
## Quick Start: Automated Setup ## Quick Start: Automated Setup
**Before manually troubleshooting, try the automated setup script:** **Before manually troubleshooting, try the automated setup script:**
```powershell ```powershell
# Open PowerShell in the KiCAD-MCP-Server directory # Open PowerShell in the KiCAD-MCP-Server directory
.\setup-windows.ps1 .\setup-windows.ps1
``` ```
This script will: This script will:
- Detect your KiCAD installation
- Verify all prerequisites - Detect your KiCAD installation
- Install dependencies - Verify all prerequisites
- Build the project - Install dependencies
- Generate configuration - Build the project
- Run diagnostic tests - Generate configuration
- Run diagnostic tests
If the automated setup fails, continue with the manual troubleshooting below.
If the automated setup fails, continue with the manual troubleshooting below.
---
---
## Common Issues and Solutions
## Common Issues and Solutions
### Issue 1: Server Exits Immediately (Most Common)
### Issue 1: Server Exits Immediately (Most Common)
**Symptom:** Claude Desktop logs show "Server transport closed unexpectedly"
**Symptom:** Claude Desktop logs show "Server transport closed unexpectedly"
**Cause:** Python process crashes during startup, usually due to missing pcbnew module
**Cause:** Python process crashes during startup, usually due to missing pcbnew module
**Solution:**
**Solution:**
1. **Check the log file** (this has the actual error):
``` 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. %USERPROFILE%\.kicad-mcp\logs\kicad_interface.log
```
2. **Test pcbnew import manually:**
```powershell Open in Notepad and look at the last 50-100 lines.
& "C:\Program Files\KiCad\9.0\bin\python.exe" -c "import pcbnew; print(pcbnew.GetBuildVersion())"
``` 2. **Test pcbnew import manually:**
**Expected:** Prints KiCAD version like `9.0.0` ```powershell
& "C:\Program Files\KiCad\9.0\bin\python.exe" -c "import pcbnew; print(pcbnew.GetBuildVersion())"
**If it fails:** ```
- KiCAD's Python module isn't installed
- Reinstall KiCAD with default options **Expected:** Prints KiCAD version like `9.0.0`
- Make sure "Install Python" is checked during installation
**If it fails:**
3. **Verify PYTHONPATH in your config:** - KiCAD's Python module isn't installed
```json - Reinstall KiCAD with default options
{ - Make sure "Install Python" is checked during installation
"mcpServers": {
"kicad": { 3. **Verify PYTHONPATH in your config:**
"env": { ```json
"PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\lib\\python3\\dist-packages" {
} "mcpServers": {
} "kicad": {
} "env": {
} "PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\lib\\python3\\dist-packages"
``` }
}
--- }
}
### Issue 2: KiCAD Not Found ```
**Symptom:** Log shows "No KiCAD installations found" ---
**Solution:** ### Issue 2: KiCAD Not Found
1. **Check if KiCAD is installed:** **Symptom:** Log shows "No KiCAD installations found"
```powershell
Test-Path "C:\Program Files\KiCad\9.0" **Solution:**
```
1. **Check if KiCAD is installed:**
2. **If KiCAD is installed elsewhere:**
- Find your KiCAD installation directory ```powershell
- Update PYTHONPATH in config to match your installation Test-Path "C:\Program Files\KiCad\9.0"
- Example for version 8.0: ```
```
"PYTHONPATH": "C:\\Program Files\\KiCad\\8.0\\lib\\python3\\dist-packages" 2. **If KiCAD is installed elsewhere:**
``` - Find your KiCAD installation directory
- Update PYTHONPATH in config to match your installation
3. **If KiCAD is not installed:** - Example for version 8.0:
- Download from https://www.kicad.org/download/windows/ ```
- Install version 9.0 or higher "PYTHONPATH": "C:\\Program Files\\KiCad\\8.0\\lib\\python3\\dist-packages"
- Use default installation path ```
--- 3. **If KiCAD is not installed:**
- Download from https://www.kicad.org/download/windows/
### Issue 3: Node.js Not Found - Install version 9.0 or higher
- Use default installation path
**Symptom:** Cannot run `npm install` or `npm run build`
---
**Solution:**
### Issue 3: Node.js Not Found
1. **Check if Node.js is installed:**
```powershell **Symptom:** Cannot run `npm install` or `npm run build`
node --version
npm --version **Solution:**
```
1. **Check if Node.js is installed:**
2. **If not installed:**
- Download Node.js 18+ from https://nodejs.org/ ```powershell
- Install with default options node --version
- Restart PowerShell after installation npm --version
```
3. **If installed but not in PATH:**
```powershell 2. **If not installed:**
# Add to PATH temporarily - Download Node.js 18+ from https://nodejs.org/
$env:PATH += ";C:\Program Files\nodejs" - Install with default options
``` - Restart PowerShell after installation
--- 3. **If installed but not in PATH:**
```powershell
### Issue 4: Build Fails with TypeScript Errors # Add to PATH temporarily
$env:PATH += ";C:\Program Files\nodejs"
**Symptom:** `npm run build` shows TypeScript compilation errors ```
**Solution:** ---
1. **Clean and reinstall dependencies:** ### Issue 4: Build Fails with TypeScript Errors
```powershell
Remove-Item node_modules -Recurse -Force **Symptom:** `npm run build` shows TypeScript compilation errors
Remove-Item package-lock.json -Force
npm install **Solution:**
npm run build
``` 1. **Clean and reinstall dependencies:**
2. **Check Node.js version:** ```powershell
```powershell Remove-Item node_modules -Recurse -Force
node --version # Should be v18.0.0 or higher Remove-Item package-lock.json -Force
``` npm install
npm run build
3. **If still failing:** ```
```powershell
# Try with legacy peer deps 2. **Check Node.js version:**
npm install --legacy-peer-deps
npm run build ```powershell
``` node --version # Should be v18.0.0 or higher
```
---
3. **If still failing:**
### Issue 5: Python Dependencies Missing ```powershell
# Try with legacy peer deps
**Symptom:** Log shows errors about missing Python packages (Pillow, cairosvg, etc.) npm install --legacy-peer-deps
npm run build
**Solution:** ```
1. **Install with KiCAD's Python:** ---
```powershell
& "C:\Program Files\KiCad\9.0\bin\python.exe" -m pip install -r requirements.txt ### Issue 5: Python Dependencies Missing
```
**Symptom:** Log shows errors about missing Python packages (Pillow, cairosvg, etc.)
2. **If pip is not available:**
```powershell **Solution:**
# Download get-pip.py
Invoke-WebRequest -Uri https://bootstrap.pypa.io/get-pip.py -OutFile get-pip.py 1. **Install with KiCAD's Python:**
# Install pip ```powershell
& "C:\Program Files\KiCad\9.0\bin\python.exe" get-pip.py & "C:\Program Files\KiCad\9.0\bin\python.exe" -m pip install -r requirements.txt
```
# Then install requirements
& "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
### Issue 6: Permission Denied Errors
# Install pip
**Symptom:** Cannot write to Program Files or access certain directories & "C:\Program Files\KiCad\9.0\bin\python.exe" get-pip.py
**Solution:** # Then install requirements
& "C:\Program Files\KiCad\9.0\bin\python.exe" -m pip install -r requirements.txt
1. **Run PowerShell as Administrator:** ```
- Right-click PowerShell icon
- Select "Run as Administrator" ---
- Navigate to KiCAD-MCP-Server directory
- Run setup again ### Issue 6: Permission Denied Errors
2. **Or clone to user directory:** **Symptom:** Cannot write to Program Files or access certain directories
```powershell
cd $HOME\Documents **Solution:**
git clone https://github.com/mixelpixx/KiCAD-MCP-Server.git
cd KiCAD-MCP-Server 1. **Run PowerShell as Administrator:**
.\setup-windows.ps1 - Right-click PowerShell icon
``` - Select "Run as Administrator"
- Navigate to KiCAD-MCP-Server directory
--- - Run setup again
### Issue 7: Path Issues in Configuration 2. **Or clone to user directory:**
```powershell
**Symptom:** Config file paths not working cd $HOME\Documents
git clone https://github.com/mixelpixx/KiCAD-MCP-Server.git
**Common mistakes:** cd KiCAD-MCP-Server
```json .\setup-windows.ps1
// ❌ Wrong - single backslashes ```
"args": ["C:\Users\Name\KiCAD-MCP-Server\dist\index.js"]
---
// ❌ Wrong - mixed slashes
"args": ["C:\Users/Name\KiCAD-MCP-Server/dist\index.js"] ### Issue 7: Path Issues in Configuration
// ✅ Correct - double backslashes **Symptom:** Config file paths not working
"args": ["C:\\Users\\Name\\KiCAD-MCP-Server\\dist\\index.js"]
**Common mistakes:**
// ✅ Also correct - forward slashes
"args": ["C:/Users/Name/KiCAD-MCP-Server/dist/index.js"] ```json
``` // ❌ Wrong - single backslashes
"args": ["C:\Users\Name\KiCAD-MCP-Server\dist\index.js"]
**Solution:** Use either double backslashes `\\` or forward slashes `/` consistently.
// ❌ Wrong - mixed slashes
--- "args": ["C:\Users/Name\KiCAD-MCP-Server/dist\index.js"]
### Issue 8: Wrong Python Version // ✅ Correct - double backslashes
"args": ["C:\\Users\\Name\\KiCAD-MCP-Server\\dist\\index.js"]
**Symptom:** Errors about Python 2.7 or Python 3.6
// ✅ Also correct - forward slashes
**Solution:** "args": ["C:/Users/Name/KiCAD-MCP-Server/dist/index.js"]
```
KiCAD MCP requires Python 3.10+. KiCAD 9.0 includes Python 3.11, which is perfect.
**Solution:** Use either double backslashes `\\` or forward slashes `/` consistently.
**Always use KiCAD's bundled Python:**
```json ---
{
"mcpServers": { ### Issue 8: Wrong Python Version
"kicad": {
"command": "C:\\Program Files\\KiCad\\9.0\\bin\\python.exe", **Symptom:** Errors about Python 2.7 or Python 3.6
"args": ["C:\\Users\\YourName\\KiCAD-MCP-Server\\python\\kicad_interface.py"]
} **Solution:**
}
} KiCAD MCP requires Python 3.10+. KiCAD 9.0 includes Python 3.11, which is perfect.
```
**Always use KiCAD's bundled Python:**
This bypasses Node.js and runs Python directly.
```json
--- {
"mcpServers": {
## Configuration Examples "kicad": {
"command": "C:\\Program Files\\KiCad\\9.0\\bin\\python.exe",
### For Claude Desktop "args": ["C:\\Users\\YourName\\KiCAD-MCP-Server\\python\\kicad_interface.py"]
}
Config location: `%APPDATA%\Claude\claude_desktop_config.json` }
}
```json ```
{
"mcpServers": { This bypasses Node.js and runs Python directly.
"kicad": {
"command": "node", ---
"args": ["C:\\Users\\YourName\\KiCAD-MCP-Server\\dist\\index.js"],
"env": { ## Configuration Examples
"PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\lib\\python3\\dist-packages",
"NODE_ENV": "production", ### For Claude Desktop
"LOG_LEVEL": "info"
} Config location: `%APPDATA%\Claude\claude_desktop_config.json`
}
} ```json
} {
``` "mcpServers": {
"kicad": {
### For Cline (VSCode) "command": "node",
"args": ["C:\\Users\\YourName\\KiCAD-MCP-Server\\dist\\index.js"],
Config location: `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json` "env": {
"PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\lib\\python3\\dist-packages",
```json "NODE_ENV": "production",
{ "LOG_LEVEL": "info"
"mcpServers": { }
"kicad": { }
"command": "node", }
"args": ["C:\\Users\\YourName\\KiCAD-MCP-Server\\dist\\index.js"], }
"env": { ```
"PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\lib\\python3\\dist-packages"
}, ### For Cline (VSCode)
"description": "KiCAD PCB Design Assistant"
} Config location: `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`
}
} ```json
``` {
"mcpServers": {
### Alternative: Python Direct Mode "kicad": {
"command": "node",
If Node.js issues persist, run Python directly: "args": ["C:\\Users\\YourName\\KiCAD-MCP-Server\\dist\\index.js"],
"env": {
```json "PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\lib\\python3\\dist-packages"
{ },
"mcpServers": { "description": "KiCAD PCB Design Assistant"
"kicad": { }
"command": "C:\\Program Files\\KiCad\\9.0\\bin\\python.exe", }
"args": ["C:\\Users\\YourName\\KiCAD-MCP-Server\\python\\kicad_interface.py"], }
"env": { ```
"PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\lib\\python3\\dist-packages"
} ### Alternative: Python Direct Mode
}
} If Node.js issues persist, run Python directly:
}
``` ```json
{
--- "mcpServers": {
"kicad": {
## Manual Testing Steps "command": "C:\\Program Files\\KiCad\\9.0\\bin\\python.exe",
"args": ["C:\\Users\\YourName\\KiCAD-MCP-Server\\python\\kicad_interface.py"],
### Test 1: Verify KiCAD Python "env": {
"PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\lib\\python3\\dist-packages"
```powershell }
& "C:\Program Files\KiCad\9.0\bin\python.exe" -c @" }
import sys }
print(f'Python version: {sys.version}') }
import pcbnew ```
print(f'pcbnew version: {pcbnew.GetBuildVersion()}')
print('SUCCESS!') ---
"@
``` ## Manual Testing Steps
Expected output: ### Test 1: Verify KiCAD Python
```
Python version: 3.11.x ... ```powershell
pcbnew version: 9.0.0 & "C:\Program Files\KiCad\9.0\bin\python.exe" -c @"
SUCCESS! import sys
``` print(f'Python version: {sys.version}')
import pcbnew
### Test 2: Verify Node.js print(f'pcbnew version: {pcbnew.GetBuildVersion()}')
print('SUCCESS!')
```powershell "@
node --version # Should be v18.0.0+ ```
npm --version # Should be 9.0.0+
``` Expected output:
### Test 3: Build Project ```
Python version: 3.11.x ...
```powershell pcbnew version: 9.0.0
cd C:\Users\YourName\KiCAD-MCP-Server SUCCESS!
npm install ```
npm run build
Test-Path .\dist\index.js # Should output: True ### Test 2: Verify Node.js
```
```powershell
### Test 4: Run Server Manually node --version # Should be v18.0.0+
npm --version # Should be 9.0.0+
```powershell ```
$env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages"
node .\dist\index.js ### Test 3: Build Project
```
```powershell
Expected: Server should start and wait for input (doesn't exit immediately) cd C:\Users\YourName\KiCAD-MCP-Server
npm install
**To stop:** Press Ctrl+C npm run build
Test-Path .\dist\index.js # Should output: True
### Test 5: Check Log File ```
```powershell ### Test 4: Run Server Manually
# View log file
Get-Content "$env:USERPROFILE\.kicad-mcp\logs\kicad_interface.log" -Tail 50 ```powershell
``` $env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages"
node .\dist\index.js
Should show successful initialization with no errors. ```
--- Expected: Server should start and wait for input (doesn't exit immediately)
## Advanced Diagnostics **To stop:** Press Ctrl+C
### Enable Verbose Logging ### Test 5: Check Log File
Add to your MCP config: ```powershell
```json # View log file
{ Get-Content "$env:USERPROFILE\.kicad-mcp\logs\kicad_interface.log" -Tail 50
"env": { ```
"LOG_LEVEL": "debug",
"PYTHONUNBUFFERED": "1" Should show successful initialization with no errors.
}
} ---
```
## Advanced Diagnostics
### Check Python sys.path
### Enable Verbose Logging
```powershell
& "C:\Program Files\KiCad\9.0\bin\python.exe" -c @" Add to your MCP config:
import sys
for path in sys.path: ```json
print(path) {
"@ "env": {
``` "LOG_LEVEL": "debug",
"PYTHONUNBUFFERED": "1"
Should include: `C:\Program Files\KiCad\9.0\lib\python3\dist-packages` }
}
### Test MCP Communication ```
```powershell ### Check Python sys.path
# Start server
$env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages" ```powershell
$process = Start-Process -FilePath "node" -ArgumentList ".\dist\index.js" -NoNewWindow -PassThru & "C:\Program Files\KiCad\9.0\bin\python.exe" -c @"
import sys
# Wait 3 seconds for path in sys.path:
Start-Sleep -Seconds 3 print(path)
"@
# Check if still running ```
if ($process.HasExited) {
Write-Host "Server crashed!" -ForegroundColor Red Should include: `C:\Program Files\KiCad\9.0\lib\python3\dist-packages`
Write-Host "Exit code: $($process.ExitCode)"
} else { ### Test MCP Communication
Write-Host "Server is running!" -ForegroundColor Green
Stop-Process -Id $process.Id ```powershell
} # Start server
``` $env:PYTHONPATH = "C:\Program Files\KiCad\9.0\lib\python3\dist-packages"
$process = Start-Process -FilePath "node" -ArgumentList ".\dist\index.js" -NoNewWindow -PassThru
---
# Wait 3 seconds
## Getting Help Start-Sleep -Seconds 3
If none of the above solutions work: # Check if still running
if ($process.HasExited) {
1. **Run the diagnostic script:** Write-Host "Server crashed!" -ForegroundColor Red
```powershell Write-Host "Exit code: $($process.ExitCode)"
.\setup-windows.ps1 } else {
``` Write-Host "Server is running!" -ForegroundColor Green
Copy the entire output. Stop-Process -Id $process.Id
}
2. **Collect log files:** ```
- MCP log: `%USERPROFILE%\.kicad-mcp\logs\kicad_interface.log`
- Claude Desktop log: `%APPDATA%\Claude\logs\mcp*.log` ---
3. **Open a GitHub issue:** ## Getting Help
- Go to: https://github.com/mixelpixx/KiCAD-MCP-Server/issues
- Title: "Windows Setup Issue: [brief description]" If none of the above solutions work:
- Include:
- Windows version (10 or 11) 1. **Run the diagnostic script:**
- Output from setup script
- Log file contents ```powershell
- Output from manual tests above .\setup-windows.ps1
```
---
Copy the entire output.
## Known Limitations on Windows
2. **Collect log files:**
1. **File paths are case-insensitive** but should match actual casing for best results - MCP log: `%USERPROFILE%\.kicad-mcp\logs\kicad_interface.log`
- Claude Desktop log: `%APPDATA%\Claude\logs\mcp*.log`
2. **Long path support** may be needed for deeply nested projects:
```powershell 3. **Open a GitHub issue:**
# Enable long paths (requires admin) - Go to: https://github.com/mixelpixx/KiCAD-MCP-Server/issues
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force - Title: "Windows Setup Issue: [brief description]"
``` - Include:
- Windows version (10 or 11)
3. **Windows Defender** may slow down file operations. Add exclusion: - Output from setup script
``` - Log file contents
Settings → Windows Security → Virus & threat protection → Exclusions - Output from manual tests above
Add: C:\Users\YourName\KiCAD-MCP-Server
``` ---
4. **Antivirus software** may block Python/Node processes. Temporarily disable for testing. ## Known Limitations on Windows
--- 1. **File paths are case-insensitive** but should match actual casing for best results
## Success Checklist 2. **Long path support** may be needed for deeply nested projects:
When everything works, you should have: ```powershell
# Enable long paths (requires admin)
- [ ] KiCAD 9.0+ installed at `C:\Program Files\KiCad\9.0` New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
- [ ] Node.js 18+ installed and in PATH ```
- [ ] Python can import pcbnew successfully
- [ ] `npm run build` completes without errors 3. **Windows Defender** may slow down file operations. Add exclusion:
- [ ] `dist\index.js` file exists
- [ ] MCP config file created with correct paths ```
- [ ] Server starts without immediate crash Settings → Windows Security → Virus & threat protection → Exclusions
- [ ] Log file shows successful initialization Add: C:\Users\YourName\KiCAD-MCP-Server
- [ ] Claude Desktop/Cline recognizes the MCP server ```
- [ ] Can execute: "Create a new KiCAD project"
4. **Antivirus software** may block Python/Node processes. Temporarily disable for testing.
---
---
**Last Updated:** 2025-11-05
**Maintained by:** KiCAD MCP Team ## Success Checklist
For the latest updates, see: https://github.com/mixelpixx/KiCAD-MCP-Server When everything works, you should have:
- [ ] KiCAD 9.0+ installed at `C:\Program Files\KiCad\9.0`
- [ ] Node.js 18+ installed and in PATH
- [ ] Python can import pcbnew successfully
- [ ] `npm run build` completes without errors
- [ ] `dist\index.js` file exists
- [ ] MCP config file created with correct paths
- [ ] Server starts without immediate crash
- [ ] Log file shows successful initialization
- [ ] Claude Desktop/Cline recognizes the MCP server
- [ ] Can execute: "Create a new KiCAD project"
---
**Last Updated:** 2025-11-05
**Maintained by:** KiCAD MCP Team
For the latest updates, see: https://github.com/mixelpixx/KiCAD-MCP-Server

File diff suppressed because it is too large Load Diff

View File

@@ -1,485 +1,509 @@
# Option 2: Dynamic Library Loading Plan # Option 2: Dynamic Library Loading Plan
## Executive Summary ## Executive Summary
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). 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):** **Current Status (Option 1):**
- ✅ Template-based approach working
-13 component types supported -Template-based approach working
- ❌ Limited symbol variety - ✅ 13 component types supported
-Requires manual template updates for new types -Limited symbol variety
- ❌ Requires manual template updates for new types
**Proposed (Option 2):**
- 🎯 Dynamic loading from `.kicad_sym` library files **Proposed (Option 2):**
- 🎯 Access to ~10,000+ KiCad symbols
- 🎯 No template maintenance required - 🎯 Dynamic loading from `.kicad_sym` library files
- 🎯 User can specify any library/symbol combination - 🎯 Access to ~10,000+ KiCad symbols
- 🎯 No template maintenance required
--- - 🎯 User can specify any library/symbol combination
## Problem Analysis ---
### kicad-skip Library Limitation ## Problem Analysis
**Core Issue:** kicad-skip **cannot create symbols from scratch**. It can only: ### kicad-skip Library Limitation
1. Clone existing symbols from a loaded schematic
2. Modify properties of cloned symbols **Core Issue:** kicad-skip **cannot create symbols from scratch**. It can only:
**Current Workaround:** Pre-load template symbols in schematic file 1. Clone existing symbols from a loaded schematic
2. Modify properties of cloned symbols
**Proposed Solution:** Load symbols from KiCad's `.kicad_sym` library files, inject them into the schematic's `lib_symbols` section, then clone from there.
**Current Workaround:** Pre-load template symbols in schematic file
---
**Proposed Solution:** Load symbols from KiCad's `.kicad_sym` library files, inject them into the schematic's `lib_symbols` section, then clone from there.
## KiCad Symbol Library Architecture
---
### Symbol Library File Format (`.kicad_sym`)
## KiCad Symbol Library Architecture
KiCad symbol libraries are S-expression files containing symbol definitions:
### Symbol Library File Format (`.kicad_sym`)
```lisp
(kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor) KiCad symbol libraries are S-expression files containing symbol definitions:
(symbol "Device:R"
(pin_numbers hide) ```lisp
(pin_names (offset 0)) (kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor)
(in_bom yes) (symbol "Device:R"
(on_board yes) (pin_numbers hide)
(property "Reference" "R" ...) (pin_names (offset 0))
(property "Value" "R" ...) (in_bom yes)
;; Graphics definitions (on_board yes)
(symbol "R_0_1" ...) (property "Reference" "R" ...)
(symbol "R_1_1" (property "Value" "R" ...)
(pin passive line ...) ;; Graphics definitions
) (symbol "R_0_1" ...)
) (symbol "R_1_1"
(symbol "Device:C" ...) (pin passive line ...)
(symbol "Device:L" ...) )
;; ... thousands more )
) (symbol "Device:C" ...)
``` (symbol "Device:L" ...)
;; ... thousands more
### Standard KiCad Library Locations )
```
**Linux:**
- System libraries: `/usr/share/kicad/symbols/` ### Standard KiCad Library Locations
- User libraries: `~/.local/share/kicad/8.0/symbols/` or `~/.config/kicad/8.0/symbols/`
**Linux:**
**Windows:**
- System libraries: `C:\Program Files\KiCad\9.0\share\kicad\symbols\` - System libraries: `/usr/share/kicad/symbols/`
- User libraries: `%APPDATA%\kicad\8.0\symbols\` - User libraries: `~/.local/share/kicad/8.0/symbols/` or `~/.config/kicad/8.0/symbols/`
**macOS:** **Windows:**
- System libraries: `/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/`
- User libraries: `~/Library/Preferences/kicad/8.0/symbols/` - System libraries: `C:\Program Files\KiCad\9.0\share\kicad\symbols\`
- User libraries: `%APPDATA%\kicad\8.0\symbols\`
### Standard Library Files
**macOS:**
Common libraries (each containing 50-500 symbols):
- `Device.kicad_sym` - Passives (R, C, L, D, LED, Crystal, etc.) - System libraries: `/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/`
- `Connector.kicad_sym` - Connectors (headers, USB, etc.) - User libraries: `~/Library/Preferences/kicad/8.0/symbols/`
- `Connector_Generic.kicad_sym` - Generic connectors
- `Transistor_BJT.kicad_sym` - Bipolar transistors ### Standard Library Files
- `Transistor_FET.kicad_sym` - MOSFETs
- `Amplifier_Operational.kicad_sym` - Op-amps Common libraries (each containing 50-500 symbols):
- `Regulator_Linear.kicad_sym` - Voltage regulators
- `MCU_*.kicad_sym` - Microcontrollers - `Device.kicad_sym` - Passives (R, C, L, D, LED, Crystal, etc.)
- `Interface_*.kicad_sym` - Interface ICs - `Connector.kicad_sym` - Connectors (headers, USB, etc.)
- ... 100+ more libraries - `Connector_Generic.kicad_sym` - Generic connectors
- `Transistor_BJT.kicad_sym` - Bipolar transistors
--- - `Transistor_FET.kicad_sym` - MOSFETs
- `Amplifier_Operational.kicad_sym` - Op-amps
## Implementation Strategy - `Regulator_Linear.kicad_sym` - Voltage regulators
- `MCU_*.kicad_sym` - Microcontrollers
### Phase 1: Library Discovery & Indexing - `Interface_*.kicad_sym` - Interface ICs
- ... 100+ more libraries
**Goal:** Build an index of all available symbols and their locations
---
**Implementation:**
```python ## Implementation Strategy
class SymbolLibraryManager:
def __init__(self): ### Phase 1: Library Discovery & Indexing
self.library_paths = []
self.symbol_index = {} # {"Device:R": "/path/to/Device.kicad_sym", ...} **Goal:** Build an index of all available symbols and their locations
def discover_libraries(self): **Implementation:**
"""Find all KiCad symbol libraries on the system"""
search_paths = [ ```python
"/usr/share/kicad/symbols/", class SymbolLibraryManager:
os.path.expanduser("~/.local/share/kicad/8.0/symbols/"), def __init__(self):
os.path.expanduser("~/.config/kicad/8.0/symbols/"), self.library_paths = []
] self.symbol_index = {} # {"Device:R": "/path/to/Device.kicad_sym", ...}
for search_path in search_paths: def discover_libraries(self):
if os.path.exists(search_path): """Find all KiCad symbol libraries on the system"""
for lib_file in os.listdir(search_path): search_paths = [
if lib_file.endswith('.kicad_sym'): "/usr/share/kicad/symbols/",
self.library_paths.append(os.path.join(search_path, lib_file)) os.path.expanduser("~/.local/share/kicad/8.0/symbols/"),
os.path.expanduser("~/.config/kicad/8.0/symbols/"),
def index_symbols(self): ]
"""Parse all libraries and build symbol index"""
for lib_path in self.library_paths: for search_path in search_paths:
lib_name = os.path.basename(lib_path).replace('.kicad_sym', '') if os.path.exists(search_path):
symbols = self._parse_library(lib_path) for lib_file in os.listdir(search_path):
if lib_file.endswith('.kicad_sym'):
for symbol_name in symbols: self.library_paths.append(os.path.join(search_path, lib_file))
full_name = f"{lib_name}:{symbol_name}"
self.symbol_index[full_name] = { def index_symbols(self):
'library': lib_name, """Parse all libraries and build symbol index"""
'library_path': lib_path, for lib_path in self.library_paths:
'symbol_name': symbol_name lib_name = os.path.basename(lib_path).replace('.kicad_sym', '')
} symbols = self._parse_library(lib_path)
def _parse_library(self, lib_path): for symbol_name in symbols:
"""Parse .kicad_sym file and extract symbol names""" full_name = f"{lib_name}:{symbol_name}"
# Use sexpdata (already a dependency of kicad-skip) self.symbol_index[full_name] = {
import sexpdata 'library': lib_name,
'library_path': lib_path,
with open(lib_path, 'r') as f: 'symbol_name': symbol_name
data = sexpdata.load(f) }
symbols = [] def _parse_library(self, lib_path):
for item in data[2:]: # Skip header """Parse .kicad_sym file and extract symbol names"""
if isinstance(item, list) and item[0] == Symbol('symbol'): # Use sexpdata (already a dependency of kicad-skip)
symbol_name = item[1] # e.g., "Device:R" import sexpdata
# Extract just the symbol part after ':'
if ':' in symbol_name: with open(lib_path, 'r') as f:
symbol_name = symbol_name.split(':')[1] data = sexpdata.load(f)
symbols.append(symbol_name)
symbols = []
return symbols for item in data[2:]: # Skip header
``` if isinstance(item, list) and item[0] == Symbol('symbol'):
symbol_name = item[1] # e.g., "Device:R"
### Phase 2: Dynamic Symbol Injection # Extract just the symbol part after ':'
if ':' in symbol_name:
**Goal:** Load symbol definition from library file and inject into schematic symbol_name = symbol_name.split(':')[1]
symbols.append(symbol_name)
**Challenge:** kicad-skip works with loaded schematics, but we need to dynamically add symbols to the `lib_symbols` section.
return symbols
**Solution:** Modify the schematic's S-expression data directly before loading with kicad-skip: ```
```python ### Phase 2: Dynamic Symbol Injection
def inject_symbol_into_schematic(schematic_path, library_path, symbol_name):
""" **Goal:** Load symbol definition from library file and inject into schematic
1. Read schematic S-expression
2. Read library S-expression **Challenge:** kicad-skip works with loaded schematics, but we need to dynamically add symbols to the `lib_symbols` section.
3. Extract symbol definition from library
4. Inject into schematic's lib_symbols section **Solution:** Modify the schematic's S-expression data directly before loading with kicad-skip:
5. Save modified schematic
6. Reload with kicad-skip ```python
""" def inject_symbol_into_schematic(schematic_path, library_path, symbol_name):
import sexpdata """
1. Read schematic S-expression
# Load schematic 2. Read library S-expression
with open(schematic_path, 'r') as f: 3. Extract symbol definition from library
sch_data = sexpdata.load(f) 4. Inject into schematic's lib_symbols section
5. Save modified schematic
# Load library 6. Reload with kicad-skip
with open(library_path, 'r') as f: """
lib_data = sexpdata.load(f) import sexpdata
# Find symbol definition in library # Load schematic
symbol_def = None with open(schematic_path, 'r') as f:
for item in lib_data[2:]: sch_data = sexpdata.load(f)
if isinstance(item, list) and item[0] == Symbol('symbol'):
if symbol_name in str(item[1]): # Load library
symbol_def = item with open(library_path, 'r') as f:
break lib_data = sexpdata.load(f)
if not symbol_def: # Find symbol definition in library
raise ValueError(f"Symbol {symbol_name} not found in {library_path}") symbol_def = None
for item in lib_data[2:]:
# Find lib_symbols section in schematic if isinstance(item, list) and item[0] == Symbol('symbol'):
lib_symbols_index = None if symbol_name in str(item[1]):
for i, item in enumerate(sch_data): symbol_def = item
if isinstance(item, list) and item[0] == Symbol('lib_symbols'): break
lib_symbols_index = i
break if not symbol_def:
raise ValueError(f"Symbol {symbol_name} not found in {library_path}")
# Inject symbol definition
if lib_symbols_index: # Find lib_symbols section in schematic
sch_data[lib_symbols_index].append(symbol_def) lib_symbols_index = None
for i, item in enumerate(sch_data):
# Save modified schematic if isinstance(item, list) and item[0] == Symbol('lib_symbols'):
with open(schematic_path, 'w') as f: lib_symbols_index = i
sexpdata.dump(sch_data, f) break
# Reload with kicad-skip # Inject symbol definition
return Schematic(schematic_path) if lib_symbols_index:
``` sch_data[lib_symbols_index].append(symbol_def)
### Phase 3: Template Instance Creation # Save modified schematic
with open(schematic_path, 'w') as f:
**Goal:** Create offscreen template instances that can be cloned sexpdata.dump(sch_data, f)
**After injection:** Symbol definition is in `lib_symbols`, but we need an instance to clone from: # Reload with kicad-skip
return Schematic(schematic_path)
```python ```
def create_template_instance(schematic, library_name, symbol_name):
""" ### Phase 3: Template Instance Creation
Create an offscreen template instance that can be cloned
Similar to our current _TEMPLATE_R approach **Goal:** Create offscreen template instances that can be cloned
"""
# This requires directly manipulating the S-expression **After injection:** Symbol definition is in `lib_symbols`, but we need an instance to clone from:
# Add a symbol instance at offscreen position with special reference
```python
template_ref = f"_TEMPLATE_{library_name}_{symbol_name}" def create_template_instance(schematic, library_name, symbol_name):
"""
# Create symbol instance (S-expression) Create an offscreen template instance that can be cloned
symbol_instance = [ Similar to our current _TEMPLATE_R approach
Symbol('symbol'), """
[Symbol('lib_id'), f"{library_name}:{symbol_name}"], # This requires directly manipulating the S-expression
[Symbol('at'), -100, -100 - (len(schematic.symbol) * 10), 0], # Add a symbol instance at offscreen position with special reference
[Symbol('unit'), 1],
[Symbol('in_bom'), Symbol('no')], template_ref = f"_TEMPLATE_{library_name}_{symbol_name}"
[Symbol('on_board'), Symbol('no')],
[Symbol('dnp'), Symbol('yes')], # Create symbol instance (S-expression)
[Symbol('uuid'), str(uuid.uuid4())], symbol_instance = [
[Symbol('property'), "Reference", template_ref, ...], Symbol('symbol'),
# ... more properties [Symbol('lib_id'), f"{library_name}:{symbol_name}"],
] [Symbol('at'), -100, -100 - (len(schematic.symbol) * 10), 0],
[Symbol('unit'), 1],
# Inject into schematic and reload [Symbol('in_bom'), Symbol('no')],
# ... (similar to inject_symbol_into_schematic) [Symbol('on_board'), Symbol('no')],
[Symbol('dnp'), Symbol('yes')],
return template_ref [Symbol('uuid'), str(uuid.uuid4())],
``` [Symbol('property'), "Reference", template_ref, ...],
# ... more properties
### Phase 4: User-Facing API ]
**Goal:** Simple interface for users to add any KiCad symbol # Inject into schematic and reload
# ... (similar to inject_symbol_into_schematic)
**New MCP Tool: `add_schematic_component_dynamic`**
return template_ref
```python ```
def add_schematic_component_dynamic(params):
""" ### Phase 4: User-Facing API
Add component by library:symbol notation
**Goal:** Simple interface for users to add any KiCad symbol
Example:
{ **New MCP Tool: `add_schematic_component_dynamic`**
"library": "Device",
"symbol": "R", ```python
"reference": "R1", def add_schematic_component_dynamic(params):
"value": "10k", """
"x": 100, Add component by library:symbol notation
"y": 100
} Example:
{
OR using full notation: "library": "Device",
{ "symbol": "R",
"lib_symbol": "Device:R", # Full notation "reference": "R1",
"reference": "R1", "value": "10k",
... "x": 100,
} "y": 100
""" }
lib_symbol = params.get('lib_symbol') or f"{params['library']}:{params['symbol']}"
OR using full notation:
# 1. Check if symbol is already in schematic's lib_symbols {
# 2. If not, inject it from library file "lib_symbol": "Device:R", # Full notation
# 3. Create template instance if needed "reference": "R1",
# 4. Clone template and set properties ...
}
return {"success": True, "reference": params['reference']} """
``` lib_symbol = params.get('lib_symbol') or f"{params['library']}:{params['symbol']}"
--- # 1. Check if symbol is already in schematic's lib_symbols
# 2. If not, inject it from library file
## Advantages Over Template Approach # 3. Create template instance if needed
# 4. Clone template and set properties
### ✅ Unlimited Symbol Access
- Access to ~10,000+ standard KiCad symbols return {"success": True, "reference": params['reference']}
- 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 ## Advantages Over Template Approach
- Automatically supports new KiCad library additions
- Works with custom symbol libraries ### ✅ Unlimited Symbol Access
### ✅ Better User Experience - Access to ~10,000+ standard KiCad symbols
``` - Support for custom user libraries
User: "Add an STM32F103C8T6 microcontroller at position 100,100" - Support for 3rd-party libraries (JLCPCB, Espressif, etc.)
AI: *Searches symbol index*
*Finds MCU_ST_STM32F1:STM32F103C8Tx* ### ✅ No Maintenance Required
*Loads from library*
*Injects into schematic* - Template doesn't need updates for new component types
*Places component* - Automatically supports new KiCad library additions
✓ Done! - Works with custom symbol libraries
```
### ✅ Better User Experience
### ✅ Flexible Symbol Search
```python ```
# Find all resistors User: "Add an STM32F103C8T6 microcontroller at position 100,100"
symbols = lib_manager.search_symbols(query="resistor") AI: *Searches symbol index*
# Returns: ["Device:R", "Device:R_Small", "Device:R_Network", ...] *Finds MCU_ST_STM32F1:STM32F103C8Tx*
*Loads from library*
# Find all STM32 MCUs *Injects into schematic*
symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1") *Places component*
``` ✓ Done!
```
---
### ✅ Flexible Symbol Search
## Challenges & Mitigations
```python
### Challenge 1: S-expression Manipulation Complexity # Find all resistors
symbols = lib_manager.search_symbols(query="resistor")
**Problem:** Directly manipulating S-expression data is error-prone # Returns: ["Device:R", "Device:R_Small", "Device:R_Network", ...]
**Mitigation:** # Find all STM32 MCUs
- Use `sexpdata` library (already a dependency) symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
- Create helper functions for common operations ```
- Add comprehensive validation and error handling
- Extensive testing with various symbol types ---
### Challenge 2: Performance ## Challenges & Mitigations
**Problem:** Loading/reloading schematics after injection could be slow ### Challenge 1: S-expression Manipulation Complexity
**Mitigation:** **Problem:** Directly manipulating S-expression data is error-prone
- **Cache loaded symbols**: Once injected, symbol stays in schematic
- **Batch injection**: Inject multiple symbols at once **Mitigation:**
- **Lazy loading**: Only inject symbols when first used
- Use `sexpdata` library (already a dependency)
### Challenge 3: Symbol Compatibility - Create helper functions for common operations
- Add comprehensive validation and error handling
**Problem:** Some symbols may have complex pin configurations or multiple units - Extensive testing with various symbol types
**Mitigation:** ### Challenge 2: Performance
- Start with simple 2-pin passives (R, C, L)
- Gradually add support for multi-pin ICs **Problem:** Loading/reloading schematics after injection could be slow
- Handle multi-unit symbols (gates, OpAmp sections) explicitly
- Document supported symbol types **Mitigation:**
### Challenge 4: Library Version Compatibility - **Cache loaded symbols**: Once injected, symbol stays in schematic
- **Batch injection**: Inject multiple symbols at once
**Problem:** KiCad symbol format may change between versions - **Lazy loading**: Only inject symbols when first used
**Mitigation:** ### Challenge 3: Symbol Compatibility
- Parse KiCad version from library files
- Version-specific handling if needed **Problem:** Some symbols may have complex pin configurations or multiple units
- Fallback to template approach for unsupported formats
**Mitigation:**
---
- Start with simple 2-pin passives (R, C, L)
## Implementation Phases - Gradually add support for multi-pin ICs
- Handle multi-unit symbols (gates, OpAmp sections) explicitly
### Phase A: Proof of Concept (1-2 weeks) - Document supported symbol types
- [ ] Create `SymbolLibraryManager` class
- [ ] Implement library discovery (Linux paths only) ### Challenge 4: Library Version Compatibility
- [ ] Implement symbol indexing
- [ ] Test with Device.kicad_sym (R, C, L) **Problem:** KiCad symbol format may change between versions
- [ ] Implement basic S-expression injection
- [ ] Test end-to-end with simple components **Mitigation:**
### Phase B: Core Functionality (2-3 weeks) - Parse KiCad version from library files
- [ ] Cross-platform library discovery (Windows, macOS) - Version-specific handling if needed
- [ ] Symbol search functionality - Fallback to template approach for unsupported formats
- [ ] Template instance creation automation
- [ ] Multi-pin component support ---
- [ ] Error handling and validation
- [ ] Unit tests for all operations ## Implementation Phases
### Phase C: MCP Integration (1 week) ### Phase A: Proof of Concept (1-2 weeks)
- [ ] Create `add_schematic_component_dynamic` tool
- [ ] Update `search_symbols` to use library index - [ ] Create `SymbolLibraryManager` class
- [ ] Add `list_available_symbols` tool - [ ] Implement library discovery (Linux paths only)
- [ ] Add `list_symbol_libraries` tool - [ ] Implement symbol indexing
- [ ] Documentation and examples - [ ] Test with Device.kicad_sym (R, C, L)
- [ ] Implement basic S-expression injection
### Phase D: Advanced Features (2-3 weeks) - [ ] Test end-to-end with simple components
- [ ] Multi-unit symbol support (e.g., quad OpAmps)
- [ ] Custom library registration ### Phase B: Core Functionality (2-3 weeks)
- [ ] Symbol caching and optimization
- [ ] 3rd-party library support (JLCPCB, etc.) - [ ] Cross-platform library discovery (Windows, macOS)
- [ ] Symbol preview generation - [ ] Symbol search functionality
- [ ] Template instance creation automation
--- - [ ] Multi-pin component support
- [ ] Error handling and validation
## Migration Strategy - [ ] Unit tests for all operations
### Backward Compatibility ### Phase C: MCP Integration (1 week)
Keep template-based approach as fallback: - [ ] Create `add_schematic_component_dynamic` tool
- [ ] Update `search_symbols` to use library index
```python - [ ] Add `list_available_symbols` tool
def add_schematic_component(params): - [ ] Add `list_symbol_libraries` tool
"""Smart component addition with fallback""" - [ ] Documentation and examples
# Try dynamic loading first
try: ### Phase D: Advanced Features (2-3 weeks)
if 'library' in params or 'lib_symbol' in params:
return add_schematic_component_dynamic(params) - [ ] Multi-unit symbol support (e.g., quad OpAmps)
except Exception as e: - [ ] Custom library registration
logger.warning(f"Dynamic loading failed: {e}, falling back to template") - [ ] Symbol caching and optimization
- [ ] 3rd-party library support (JLCPCB, etc.)
# Fallback to template-based - [ ] Symbol preview generation
return add_schematic_component_template(params)
``` ---
### Gradual Rollout ## Migration Strategy
1. **Week 1-2:** Implement basic dynamic loading ### Backward Compatibility
2. **Week 3-4:** Test with power users, gather feedback
3. **Week 5-6:** Make dynamic loading the default Keep template-based approach as fallback:
4. **Week 7+:** Deprecate template-only approach (keep as fallback)
```python
--- def add_schematic_component(params):
"""Smart component addition with fallback"""
## Success Criteria # Try dynamic loading first
try:
### Must Have if 'library' in params or 'lib_symbol' in params:
- [ ] Load symbols from Device.kicad_sym (passives) return add_schematic_component_dynamic(params)
- [ ] Support R, C, L, D, LED (5 core types) except Exception as e:
- [ ] Cross-platform library discovery logger.warning(f"Dynamic loading failed: {e}, falling back to template")
- [ ] Proper error handling
# Fallback to template-based
### Should Have return add_schematic_component_template(params)
- [ ] Support for all Device.kicad_sym symbols (~50 symbols) ```
- [ ] Support for Connector.kicad_sym symbols
- [ ] Symbol search by name/keyword ### Gradual Rollout
- [ ] Performance: < 1 second per symbol injection
1. **Week 1-2:** Implement basic dynamic loading
### Nice to Have 2. **Week 3-4:** Test with power users, gather feedback
- [ ] Support for all standard libraries (~10,000 symbols) 3. **Week 5-6:** Make dynamic loading the default
- [ ] Multi-unit symbol support 4. **Week 7+:** Deprecate template-only approach (keep as fallback)
- [ ] Custom library registration
- [ ] Symbol preview/documentation ---
--- ## Success Criteria
## Risk Assessment ### Must Have
| Risk | Probability | Impact | Mitigation | - [ ] Load symbols from Device.kicad_sym (passives)
|------|-------------|--------|------------| - [ ] Support R, C, L, D, LED (5 core types)
| S-expression parsing complexity | High | High | Use proven `sexpdata` library, extensive testing | - [ ] Cross-platform library discovery
| Performance degradation | Medium | Medium | Implement caching, lazy loading | - [ ] Proper error handling
| KiCad version incompatibility | Low | High | Version detection, format validation |
| Template fallback breaks | Low | Medium | Maintain template approach in parallel | ### Should Have
| User confusion | Medium | Low | Clear documentation, gradual rollout |
- [ ] Support for all Device.kicad_sym symbols (~50 symbols)
--- - [ ] Support for Connector.kicad_sym symbols
- [ ] Symbol search by name/keyword
## Conclusion - [ ] Performance: < 1 second per symbol injection
Dynamic library loading is **feasible and highly beneficial** for the schematic workflow. While the template-based approach (Option 1) provides immediate value with 13 component types, Option 2 would: ### Nice to Have
1. **Eliminate the 13-component limitation** - [ ] Support for all standard libraries (~10,000 symbols)
2. **Provide access to 10,000+ KiCad symbols** - [ ] Multi-unit symbol support
3. **Remove manual template maintenance** - [ ] Custom library registration
4. **Enable true "natural language PCB design"** - [ ] Symbol preview/documentation
**Recommendation:** ---
- **Keep Option 1 (expanded template) for immediate use**
- **Implement Option 2 (dynamic loading) over 6-8 weeks** ## Risk Assessment
- **Maintain template fallback for compatibility**
| Risk | Probability | Impact | Mitigation |
This gives users immediate value while we build the robust long-term solution. | ------------------------------- | ----------- | ------ | ------------------------------------------------ |
| 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 |
## References | Template fallback breaks | Low | Medium | Maintain template approach in parallel |
| User confusion | Medium | Low | Clear documentation, gradual rollout |
- [KiCad File Formats Documentation](https://dev-docs.kicad.org/en/file-formats/)
- [kicad-skip GitHub](https://github.com/mvnmgrx/kicad-skip) ---
- [sexpdata Python Library](https://github.com/jd-boyd/sexpdata)
- [KiCad Symbol Library Format Spec](https://dev-docs.kicad.org/en/file-formats/sexpr-intro/) ## Conclusion
Dynamic library loading is **feasible and highly beneficial** for the schematic workflow. While the template-based approach (Option 1) provides immediate value with 13 component types, Option 2 would:
1. **Eliminate the 13-component limitation**
2. **Provide access to 10,000+ KiCad symbols**
3. **Remove manual template maintenance**
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**
This gives users immediate value while we build the robust long-term solution.
---
## References
- [KiCad File Formats Documentation](https://dev-docs.kicad.org/en/file-formats/)
- [kicad-skip GitHub](https://github.com/mvnmgrx/kicad-skip)
- [sexpdata Python Library](https://github.com/jd-boyd/sexpdata)
- [KiCad Symbol Library Format Spec](https://dev-docs.kicad.org/en/file-formats/sexpr-intro/)

View File

@@ -1,390 +1,413 @@
# Dynamic Symbol Loading - Implementation Status # Dynamic Symbol Loading - Implementation Status
**Date:** 2026-01-10 **Date:** 2026-01-10
**Status:** Phase A-C - ✅ **COMPLETE AND PRODUCTION-READY!** **Status:** Phase A-C - ✅ **COMPLETE AND PRODUCTION-READY!**
## 🚀 BREAKTHROUGH: Full MCP Integration Complete! ## 🚀 BREAKTHROUGH: Full MCP Integration Complete!
We went from **planning** to **full production integration** in a single session! We went from **planning** to **full production integration** in a single session!
**Phase A** (Proof of Concept): ✅ Complete - Core dynamic loading works **Phase A** (Proof of Concept): ✅ Complete - Core dynamic loading works
**Phase B** (Core Functionality): ✅ ~60% Complete - Cross-platform, caching working **Phase B** (Core Functionality): ✅ ~60% Complete - Cross-platform, caching working
**Phase C** (MCP Integration): ✅ **COMPLETE!** - Fully integrated through MCP interface **Phase C** (MCP Integration): ✅ **COMPLETE!** - Fully integrated through MCP interface
The dynamic symbol loading is now **FULLY OPERATIONAL** and accessible through the MCP interface! The dynamic symbol loading is now **FULLY OPERATIONAL** and accessible through the MCP interface!
--- ---
## What's Working (Core Functionality) ## What's Working (Core Functionality)
### ✅ Symbol Extraction ### ✅ Symbol Extraction
- Parse `.kicad_sym` library files using S-expression parser
- Extract specific symbol definitions by name - Parse `.kicad_sym` library files using S-expression parser
- Cache parsed libraries for performance - Extract specific symbol definitions by name
- Tested with Device.kicad_sym (533 symbols) - Cache parsed libraries for performance
- Tested with Device.kicad_sym (533 symbols)
### ✅ S-Expression Manipulation
- Load schematic files as S-expression trees ### ✅ S-Expression Manipulation
- Inject symbol definitions into `lib_symbols` section
- Preserve schematic structure and formatting - Load schematic files as S-expression trees
- Write modified schematics back to disk - Inject symbol definitions into `lib_symbols` section
- Preserve schematic structure and formatting
### ✅ Template Instance Creation - Write modified schematics back to disk
- Create offscreen template instances at negative Y coordinates
- Generate unique UUIDs for each template ### ✅ Template Instance Creation
- Set proper properties (Reference, Value, Footprint, Datasheet)
- Templates marked as: `in_bom: no`, `on_board: no`, `dnp: yes` - Create offscreen template instances at negative Y coordinates
- Generate unique UUIDs for each template
### ✅ Component Cloning - Set proper properties (Reference, Value, Footprint, Datasheet)
- kicad-skip successfully clones from dynamic templates - Templates marked as: `in_bom: no`, `on_board: no`, `dnp: yes`
- Components inherit symbol structure from injected definitions
- Properties can be modified after cloning ### ✅ Component Cloning
- Full integration with existing ComponentManager
- kicad-skip successfully clones from dynamic templates
### ✅ Cross-Platform Library Discovery - Components inherit symbol structure from injected definitions
- Linux: `/usr/share/kicad/symbols`, `~/.local/share/kicad/*/symbols` - Properties can be modified after cloning
- Windows: `C:/Program Files/KiCad/*/share/kicad/symbols` - Full integration with existing ComponentManager
- macOS: `/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols`
- Environment variable support: `KICAD9_SYMBOL_DIR`, etc. ### ✅ Cross-Platform Library Discovery
--- - Linux: `/usr/share/kicad/symbols`, `~/.local/share/kicad/*/symbols`
- Windows: `C:/Program Files/KiCad/*/share/kicad/symbols`
## Test Results - macOS: `/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols`
- Environment variable support: `KICAD9_SYMBOL_DIR`, etc.
### End-to-End Test (Successful)
---
**Test:** Load 5 symbols dynamically and create components
## Test Results
```python
Symbols Tested: ### End-to-End Test (Successful)
- Device:R Injected, template created, cloned successfully
- Device:C Injected, template created, cloned successfully **Test:** Load 5 symbols dynamically and create components
- Device:LED Injected, template created, cloned successfully
- Device:L Injected, template created, cloned successfully ```python
- Device:D Injected, template created, cloned successfully Symbols Tested:
- Device:R Injected, template created, cloned successfully
Results: - Device:C Injected, template created, cloned successfully
All 5 symbols extracted from Device.kicad_sym - Device:LED Injected, template created, cloned successfully
All 5 symbol definitions injected into schematic - Device:L Injected, template created, cloned successfully
All 5 template instances created - Device:D Injected, template created, cloned successfully
kicad-skip loaded modified schematic without errors
Components successfully cloned from dynamic templates Results:
``` All 5 symbols extracted from Device.kicad_sym
All 5 symbol definitions injected into schematic
### Performance Metrics All 5 template instances created
kicad-skip loaded modified schematic without errors
- **Library parsing:** ~0.3s for Device.kicad_sym (first time) Components successfully cloned from dynamic templates
- **Library parsing:** ~0.001s (cached) ```
- **Symbol extraction:** <0.01s
- **Symbol injection:** ~0.05s ### Performance Metrics
- **Template creation:** ~0.02s
- **Total per symbol:** ~0.08s (first time), ~0.03s (cached) - **Library parsing:** ~0.3s for Device.kicad_sym (first time)
- **Library parsing:** ~0.001s (cached)
**Conclusion:** Fast enough for real-time use! - **Symbol extraction:** <0.01s
- **Symbol injection:** ~0.05s
--- - **Template creation:** ~0.02s
- **Total per symbol:** ~0.08s (first time), ~0.03s (cached)
## Code Structure
**Conclusion:** Fast enough for real-time use!
### New File: `python/commands/dynamic_symbol_loader.py`
---
**Class:** `DynamicSymbolLoader`
## Code Structure
**Key Methods:**
```python ### New File: `python/commands/dynamic_symbol_loader.py`
# Library Discovery
find_kicad_symbol_libraries() -> List[Path] **Class:** `DynamicSymbolLoader`
find_library_file(library_name: str) -> Optional[Path]
**Key Methods:**
# Parsing & Extraction
parse_library_file(library_path: Path) -> List # Returns S-expression ```python
extract_symbol_definition(library_path: Path, symbol_name: str) -> Optional[List] # Library Discovery
find_kicad_symbol_libraries() -> List[Path]
# Injection & Template Creation find_library_file(library_name: str) -> Optional[Path]
inject_symbol_into_schematic(schematic_path: Path, library: str, symbol: str) -> bool
create_template_instance(schematic_path: Path, library: str, symbol: str) -> str # Parsing & Extraction
parse_library_file(library_path: Path) -> List # Returns S-expression
# Complete Workflow extract_symbol_definition(library_path: Path, symbol_name: str) -> Optional[List]
load_symbol_dynamically(schematic_path: Path, library: str, symbol: str) -> str
``` # Injection & Template Creation
inject_symbol_into_schematic(schematic_path: Path, library: str, symbol: str) -> bool
**Caching:** create_template_instance(schematic_path: Path, library: str, symbol: str) -> str
- `library_cache`: Parsed library files (path S-expression data)
- `symbol_cache`: Extracted symbols (lib:symbol symbol definition) # Complete Workflow
load_symbol_dynamically(schematic_path: Path, library: str, symbol: str) -> str
--- ```
## What's NOT Yet Done (Integration Layer) **Caching:**
### ⏳ MCP Tool Integration - `library_cache`: Parsed library files (path S-expression data)
- Need to create `add_schematic_component_dynamic` MCP tool - `symbol_cache`: Extracted symbols (lib:symbol symbol definition)
- Wire dynamic loader through MCP interface (has schematic path)
- Update existing `add_schematic_component` to auto-detect and use dynamic loading ---
### ⏳ Smart Symbol Discovery ## What's NOT Yet Done (Integration Layer)
- Automatic library detection from component type
- Search across all libraries for symbol names ### ⏳ MCP Tool Integration
- Fuzzy matching for symbol names
- Need to create `add_schematic_component_dynamic` MCP tool
### ⏳ Advanced Features - Wire dynamic loader through MCP interface (has schematic path)
- Multi-unit symbol support (e.g., quad op-amps) - Update existing `add_schematic_component` to auto-detect and use dynamic loading
- Pin configuration handling
- Custom library registration ### ⏳ Smart Symbol Discovery
- Symbol preview generation
- Automatic library detection from component type
--- - Search across all libraries for symbol names
- Fuzzy matching for symbol names
## Technical Challenges Solved
### ⏳ Advanced Features
### Challenge 1: S-Expression Parsing
**Problem:** KiCad files use Lisp-style S-expressions, complex to parse - Multi-unit symbol support (e.g., quad op-amps)
**Solution:** Used `sexpdata` library (already a dependency of kicad-skip) - Pin configuration handling
**Result:** Robust parsing with proper handling of nested structures - Custom library registration
- Symbol preview generation
### 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) ## Technical Challenges Solved
### Challenge 3: kicad-skip Integration ### Challenge 1: S-Expression Parsing
**Problem:** kicad-skip can only clone existing symbols, can't create from scratch
**Solution:** Inject symbol into lib_symbols, create template instance, then clone **Problem:** KiCad files use Lisp-style S-expressions, complex to parse
**Result:** Seamless integration, kicad-skip unaware of dynamic loading **Solution:** Used `sexpdata` library (already a dependency of kicad-skip)
**Result:** Robust parsing with proper handling of nested structures
### Challenge 4: Schematic File Path Access
**Problem:** kicad-skip Schematic object doesn't expose file path ### Challenge 2: Symbol Structure Complexity
**Solution:** Pass schematic path explicitly at MCP interface layer
**Result:** Workaround identified, integration pending **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)
## Example Usage (Current) ### Challenge 3: kicad-skip Integration
### Direct Python Usage **Problem:** kicad-skip can only clone existing symbols, can't create from scratch
**Solution:** Inject symbol into lib_symbols, create template instance, then clone
```python **Result:** Seamless integration, kicad-skip unaware of dynamic loading
from commands.dynamic_symbol_loader import DynamicSymbolLoader
from pathlib import Path ### Challenge 4: Schematic File Path Access
# Initialize loader **Problem:** kicad-skip Schematic object doesn't expose file path
loader = DynamicSymbolLoader() **Solution:** Pass schematic path explicitly at MCP interface layer
**Result:** Workaround identified, integration pending
# Load a symbol dynamically
schematic_path = Path("/path/to/project.kicad_sch") ---
template_ref = loader.load_symbol_dynamically(
schematic_path, ## Example Usage (Current)
library_name="Device",
symbol_name="R" ### Direct Python Usage
)
```python
# Now use template_ref with kicad-skip to clone components from commands.dynamic_symbol_loader import DynamicSymbolLoader
# template_ref will be something like "_TEMPLATE_Device_R" from pathlib import Path
```
# Initialize loader
### Future MCP Tool Usage loader = DynamicSymbolLoader()
```typescript # Load a symbol dynamically
// This is what it WILL look like after integration: schematic_path = Path("/path/to/project.kicad_sch")
template_ref = loader.load_symbol_dynamically(
await mcpServer.callTool("add_schematic_component_dynamic", { schematic_path,
library: "MCU_ST_STM32F1", library_name="Device",
symbol: "STM32F103C8Tx", symbol_name="R"
reference: "U1", )
x: 100,
y: 100, # Now use template_ref with kicad-skip to clone components
footprint: "Package_QFP:LQFP-48_7x7mm_P0.5mm" # template_ref will be something like "_TEMPLATE_Device_R"
}); ```
// The tool will: ### Future MCP Tool Usage
// 1. Check if symbol exists in static templates (no)
// 2. Dynamically load from MCU_ST_STM32F1.kicad_sym ```typescript
// 3. Inject symbol definition // This is what it WILL look like after integration:
// 4. Create template instance
// 5. Clone to create actual component await mcpServer.callTool("add_schematic_component_dynamic", {
// 6. Set properties (reference, position, footprint) library: "MCU_ST_STM32F1",
// All of this happens AUTOMATICALLY! symbol: "STM32F103C8Tx",
``` reference: "U1",
x: 100,
--- y: 100,
footprint: "Package_QFP:LQFP-48_7x7mm_P0.5mm",
## Comparison: Before vs After });
| Feature | Static Templates (Current) | Dynamic Loading (New) | // The tool will:
|---------|---------------------------|----------------------| // 1. Check if symbol exists in static templates (no)
| **Available Symbols** | 13 types | ~10,000+ types | // 2. Dynamically load from MCU_ST_STM32F1.kicad_sym
| **Maintenance** | Manual template updates | Zero maintenance | // 3. Inject symbol definition
| **Custom Symbols** | Not supported | Fully supported | // 4. Create template instance
| **3rd Party Libs** | Not supported | Fully supported | // 5. Clone to create actual component
| **Setup Time** | Pre-created templates | On-demand loading | // 6. Set properties (reference, position, footprint)
| **Performance** | Instant (pre-loaded) | ~80ms first time, ~30ms cached | // All of this happens AUTOMATICALLY!
| **Flexibility** | Limited to template list | Any .kicad_sym file | ```
--- ---
## Phase Progress ## Comparison: Before vs After
### ✅ Phase A: Proof of Concept (COMPLETE) | Feature | Static Templates (Current) | Dynamic Loading (New) |
- [x] Create `DynamicSymbolLoader` class | --------------------- | -------------------------- | ------------------------------ |
- [x] Implement library discovery (Linux paths) | **Available Symbols** | 13 types | ~10,000+ types |
- [x] Implement symbol indexing | **Maintenance** | Manual template updates | Zero maintenance |
- [x] Test with Device.kicad_sym (R, C, L) | **Custom Symbols** | Not supported | Fully supported |
- [x] Implement basic S-expression injection | **3rd Party Libs** | Not supported | Fully supported |
- [x] Test end-to-end with simple components | **Setup Time** | Pre-created templates | On-demand loading |
| **Performance** | Instant (pre-loaded) | ~80ms first time, ~30ms cached |
**Time Estimate:** 1-2 weeks | **Flexibility** | Limited to template list | Any .kicad_sym file |
**Actual Time:** 4 hours! 🎉
---
### ⏳ Phase B: Core Functionality (IN PROGRESS)
- [ ] Cross-platform library discovery (Windows, macOS) ## Phase Progress
- [ ] Symbol search functionality
- [ ] Template instance creation automation ### ✅ Phase A: Proof of Concept (COMPLETE)
- [ ] Multi-pin component support
- [ ] Error handling and validation - [x] Create `DynamicSymbolLoader` class
- [ ] Unit tests for all operations - [x] Implement library discovery (Linux paths)
- [x] Implement symbol indexing
**Time Estimate:** 2-3 weeks - [x] Test with Device.kicad_sym (R, C, L)
**Progress:** 25% (cross-platform discovery done) - [x] Implement basic S-expression injection
- [x] Test end-to-end with simple components
### ✅ Phase C: MCP Integration (COMPLETE!)
- [x] Integrate dynamic loading into `add_schematic_component` MCP handler **Time Estimate:** 1-2 weeks
- [x] Implement save inject reload clone orchestration **Actual Time:** 4 hours! 🎉
- [x] Add schematic_path parameter throughout component chain
- [x] Smart detection of when dynamic loading is needed ### ⏳ Phase B: Core Functionality (IN PROGRESS)
- [x] Proper error handling and fallback to static templates
- [x] End-to-end integration testing (100% passing!) - [ ] Cross-platform library discovery (Windows, macOS)
- [ ] Symbol search functionality
**Time Estimate:** 1 week - [ ] Template instance creation automation
**Actual Time:** 2 hours! 🎉 - [ ] Multi-pin component support
**Status:** PRODUCTION READY! - [ ] Error handling and validation
- [ ] Unit tests for all operations
**What Works Now:**
- Users can add ANY symbol from KiCad libraries via MCP interface **Time Estimate:** 2-3 weeks
- Automatic detection and dynamic loading **Progress:** 25% (cross-platform discovery done)
- Seamless fallback to static templates
- Response includes dynamic_loading_used flag and symbol_source info ### ✅ Phase C: MCP Integration (COMPLETE!)
- Compatible with all existing MCP clients
- [x] Integrate dynamic loading into `add_schematic_component` MCP handler
### ⏸️ Phase D: Advanced Features (PENDING) - [x] Implement save inject reload clone orchestration
- [ ] Multi-unit symbol support (e.g., quad OpAmps) - [x] Add schematic_path parameter throughout component chain
- [ ] Custom library registration - [x] Smart detection of when dynamic loading is needed
- [ ] Symbol caching and optimization - [x] Proper error handling and fallback to static templates
- [ ] 3rd-party library support (JLCPCB, etc.) - [x] End-to-end integration testing (100% passing!)
- [ ] Symbol preview generation
**Time Estimate:** 1 week
**Time Estimate:** 2-3 weeks **Actual Time:** 2 hours! 🎉
**Status:** PRODUCTION READY!
---
**What Works Now:**
## Next Immediate Steps
- Users can add ANY symbol from KiCad libraries via MCP interface
1. **Wire Through MCP Interface** (2-3 hours) - Automatic detection and dynamic loading
- Update `python/kicad_interface.py` to pass schematic path - Seamless fallback to static templates
- Create wrapper function that combines dynamic loading + cloning - Response includes dynamic_loading_used flag and symbol_source info
- Test with MCP client - Compatible with all existing MCP clients
2. **Create MCP Tool** (1-2 hours) ### ⏸️ Phase D: Advanced Features (PENDING)
- Define `add_schematic_component_dynamic` tool schema
- Register in tool registry - [ ] Multi-unit symbol support (e.g., quad OpAmps)
- Add to documentation - [ ] Custom library registration
- [ ] Symbol caching and optimization
3. **Integration Testing** (1-2 hours) - [ ] 3rd-party library support (JLCPCB, etc.)
- Test with Claude Desktop/Cline - [ ] Symbol preview generation
- Test with complex symbols (ICs, connectors)
- Verify error handling **Time Estimate:** 2-3 weeks
**Total Time to Full Integration:** ~6 hours ---
--- ## Next Immediate Steps
## Success Metrics 1. **Wire Through MCP Interface** (2-3 hours)
- Update `python/kicad_interface.py` to pass schematic path
### Phase A Metrics (All Achieved ✅) - Create wrapper function that combines dynamic loading + cloning
- [x] Load symbols from Device.kicad_sym (passives) - Test with MCP client
- [x] Support R, C, L, D, LED (5 core types)
- [x] Cross-platform library discovery 2. **Create MCP Tool** (1-2 hours)
- [x] Proper error handling - Define `add_schematic_component_dynamic` tool schema
- Register in tool registry
### Phase B Metrics (Target) - Add to documentation
- [ ] Support for all Device.kicad_sym symbols (~500 symbols)
- [ ] Support for Connector.kicad_sym symbols 3. **Integration Testing** (1-2 hours)
- [ ] Symbol search by name/keyword - Test with Claude Desktop/Cline
- [ ] Performance: < 1 second per symbol injection - Test with complex symbols (ICs, connectors)
- Verify error handling
### Overall Success Criteria
- [ ] Access to all standard libraries (~10,000 symbols) **Total Time to Full Integration:** ~6 hours
- [ ] Works on Linux, Windows, macOS
- [ ] <100ms latency for cached symbols ---
- [ ] Zero template maintenance required
- [ ] Backward compatible with static templates ## Success Metrics
--- ### Phase A Metrics (All Achieved ✅)
## Risks & Mitigations - [x] Load symbols from Device.kicad_sym (passives)
- [x] Support R, C, L, D, LED (5 core types)
| Risk | Status | Mitigation | - [x] Cross-platform library discovery
|------|--------|------------| - [x] Proper error handling
| S-expression complexity | RESOLVED | Used proven sexpdata library |
| Performance degradation | RESOLVED | Caching works great (<30ms cached) | ### Phase B Metrics (Target)
| KiCad version compatibility | TESTING | Version detection, format validation |
| Template fallback breaks | PREVENTED | Maintained static templates in parallel | - [ ] Support for all Device.kicad_sym symbols (~500 symbols)
| Integration complexity | IN PROGRESS | Clean separation of concerns | - [ ] Support for Connector.kicad_sym symbols
- [ ] Symbol search by name/keyword
--- - [ ] Performance: < 1 second per symbol injection
## Conclusion ### Overall Success Criteria
**We did it!** The core dynamic symbol loading is **fully functional**. This is a game-changer for the KiCAD MCP Server: - [ ] Access to all standard libraries (~10,000 symbols)
- [ ] Works on Linux, Windows, macOS
- No more 13-component limitation - [ ] <100ms latency for cached symbols
- Access to thousands of symbols - [ ] Zero template maintenance required
- Zero template maintenance - [ ] Backward compatible with static templates
- Production-ready performance
---
**The hardest part is DONE.** What remains is integration work (wiring through MCP interface), which is straightforward plumbing.
## Risks & Mitigations
**Estimated time to full production deployment:** 6-8 hours of integration work.
| Risk | Status | Mitigation |
--- | --------------------------- | -------------- | --------------------------------------- |
| S-expression complexity | RESOLVED | Used proven sexpdata library |
## 🎯 MCP Integration Test Results (2026-01-10) | Performance degradation | RESOLVED | Caching works great (<30ms cached) |
| KiCad version compatibility | TESTING | Version detection, format validation |
**Test:** Full MCP interface with dynamic symbol loading | Template fallback breaks | PREVENTED | Maintained static templates in parallel |
**Status:** **100% PASSING** | Integration complexity | IN PROGRESS | Clean separation of concerns |
### Test Components ---
| Component | Type | Library | Dynamic? | Result | ## Conclusion
|-----------|------|---------|----------|--------|
| R1 | Resistor | Device | Yes | Added successfully | **We did it!** The core dynamic symbol loading is **fully functional**. This is a game-changer for the KiCAD MCP Server:
| C1 | Capacitor | Device | Yes | Added successfully |
| BT1 | Battery | Device | **Yes** | **Dynamic load + clone** | - No more 13-component limitation
| F1 | Fuse | Device | **Yes** | **Dynamic load + clone** | - Access to thousands of symbols
| T1 | Transformer_1P_1S | Device | **Yes** | **Dynamic load + clone** | - Zero template maintenance
- Production-ready performance
### Results Summary
**The hardest part is DONE.** What remains is integration work (wiring through MCP interface), which is straightforward plumbing.
- **Static templates:** 2/2 successful (R, C)
- **Dynamic loading:** 3/3 successful (Battery, Fuse, Transformer) **Estimated time to full production deployment:** 6-8 hours of integration work.
- **Total success rate:** 5/5 (100%)
- **Templates created:** 5 (all persisted correctly) ---
- **Reload orchestration:** Working perfectly
- **Error handling:** No failures, all fallbacks untested (no errors!) ## 🎯 MCP Integration Test Results (2026-01-10)
### What This Means **Test:** Full MCP interface with dynamic symbol loading
**Status:** **100% PASSING**
Users can now add **ANY symbol from ~10,000 KiCad symbols** through the MCP interface!
### Test Components
The system automatically:
1. Detects if symbol needs dynamic loading | Component | Type | Library | Dynamic? | Result |
2. Saves current schematic | --------- | ----------------- | ------- | -------- | --------------------------- |
3. Injects symbol definition from library | R1 | Resistor | Device | Yes | Added successfully |
4. Creates template instance | C1 | Capacitor | Device | Yes | Added successfully |
5. Reloads schematic | BT1 | Battery | Device | **Yes** | **Dynamic load + clone** |
6. Clones template to create component | F1 | Fuse | Device | **Yes** | **Dynamic load + clone** |
7. Saves final result | T1 | Transformer_1P_1S | Device | **Yes** | **Dynamic load + clone** |
**Zero configuration required** - just specify library and symbol name! ### Results Summary
--- - **Static templates:** 2/2 successful (R, C)
- **Dynamic loading:** 3/3 successful (Battery, Fuse, Transformer)
**Amazing progress! From planning to full production in one session!** 🚀 🎉 - **Total success rate:** 5/5 (100%)
- **Templates created:** 5 (all persisted correctly)
- **Reload orchestration:** Working perfectly
- **Error handling:** No failures, all fallbacks untested (no errors!)
### What This Means
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
4. Creates template instance
5. Reloads schematic
6. Clones template to create component
7. Saves final result
**Zero configuration required** - just specify library and symbol name!
---
**Amazing progress! From planning to full production in one session!** 🚀 🎉

View File

@@ -1,477 +1,493 @@
# KiCAD IPC API Migration Plan # KiCAD IPC API Migration Plan
**Status:** 📋 Planning **Status:** 📋 Planning
**Target Completion:** Week 2-3 (November 1-8, 2025) **Target Completion:** Week 2-3 (November 1-8, 2025)
**Priority:** 🔴 **CRITICAL** - Current SWIG API deprecated **Priority:** 🔴 **CRITICAL** - Current SWIG API deprecated
--- ---
## Executive Summary ## Executive Summary
The current KiCAD MCP Server uses SWIG-based Python bindings (`import pcbnew`) which are **deprecated as of KiCAD 9.0** and will be **removed in KiCAD 10.0**. We must migrate to the official **KiCAD IPC API** to future-proof the project. The current KiCAD MCP Server uses SWIG-based Python bindings (`import pcbnew`) which are **deprecated as of KiCAD 9.0** and will be **removed in KiCAD 10.0**. We must migrate to the official **KiCAD IPC API** to future-proof the project.
### Why Migrate? ### Why Migrate?
| SWIG API (Current) | IPC API (Future) | | SWIG API (Current) | IPC API (Future) |
|-------------------|------------------| | -------------------------------- | ------------------------------------ |
| ❌ Deprecated | ✅ Official & Supported | | ❌ Deprecated | ✅ Official & Supported |
| ❌ Will be removed in KiCAD 10.0 | ✅ Long-term stability | | ❌ Will be removed in KiCAD 10.0 | ✅ Long-term stability |
| ❌ Python-only | ✅ Multi-language (Python, JS, etc.) | | ❌ Python-only | ✅ Multi-language (Python, JS, etc.) |
| ❌ Direct linking | ✅ Inter-process communication | | ❌ Direct linking | ✅ Inter-process communication |
| ⚠️ Synchronous only | ✅ Async support | | ⚠️ Synchronous only | ✅ Async support |
| ⚠️ No versioning | ✅ Protocol Buffers versioning | | ⚠️ No versioning | ✅ Protocol Buffers versioning |
**Decision: Migrate immediately to avoid technical debt** **Decision: Migrate immediately to avoid technical debt**
--- ---
## IPC API Overview ## IPC API Overview
### Architecture ### Architecture
``` ```
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ TypeScript MCP Server (Node.js) │ │ TypeScript MCP Server (Node.js) │
└──────────────────────┬──────────────────────────────────────┘ └──────────────────────┬──────────────────────────────────────┘
│ JSON over stdin/stdout │ JSON over stdin/stdout
┌──────────────────────▼──────────────────────────────────────┐ ┌──────────────────────▼──────────────────────────────────────┐
│ Python Interface Layer │ │ Python Interface Layer │
│ ┌────────────────────────────────────────────────────────┐ │ │ ┌────────────────────────────────────────────────────────┐ │
│ │ KiCAD API Abstraction (NEW) │ │ │ │ KiCAD API Abstraction (NEW) │ │
│ └────────────────────────────────────────────────────────┘ │ │ └────────────────────────────────────────────────────────┘ │
└──────────────────────┬──────────────────────────────────────┘ └──────────────────────┬──────────────────────────────────────┘
│ kicad-python library │ kicad-python library
┌──────────────────────▼──────────────────────────────────────┐ ┌──────────────────────▼──────────────────────────────────────┐
│ KiCAD IPC Server (Protocol Buffers) │ │ KiCAD IPC Server (Protocol Buffers) │
│ Running inside KiCAD Process │ │ Running inside KiCAD Process │
└──────────────────────┬──────────────────────────────────────┘ └──────────────────────┬──────────────────────────────────────┘
│ UNIX Sockets / Named Pipes │ UNIX Sockets / Named Pipes
┌──────────────────────▼──────────────────────────────────────┐ ┌──────────────────────▼──────────────────────────────────────┐
│ KiCAD 9.0+ Application │ │ KiCAD 9.0+ Application │
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
``` ```
### Key Differences ### Key Differences
1. **KiCAD Must Be Running** 1. **KiCAD Must Be Running**
- SWIG: Can run headless, no KiCAD GUI needed - SWIG: Can run headless, no KiCAD GUI needed
- IPC: Requires KiCAD running with IPC server enabled - IPC: Requires KiCAD running with IPC server enabled
2. **Communication Method** 2. **Communication Method**
- SWIG: Direct Python module import - SWIG: Direct Python module import
- IPC: Socket-based RPC (Remote Procedure Call) - IPC: Socket-based RPC (Remote Procedure Call)
3. **API Structure** 3. **API Structure**
- SWIG: `board.SetSize(width, height)` - SWIG: `board.SetSize(width, height)`
- IPC: `kicad.get_board().set_size(width, height)` - IPC: `kicad.get_board().set_size(width, height)`
--- ---
## Migration Strategy ## Migration Strategy
### Phase 1: Research & Preparation (Days 1-2) ### Phase 1: Research & Preparation (Days 1-2)
**Goals:** **Goals:**
- Understand kicad-python library
- Test IPC connection - Understand kicad-python library
- Document API differences - Test IPC connection
- Document API differences
**Tasks:**
```bash **Tasks:**
# Install kicad-python
pip install kicad-python>=0.5.0 ```bash
# Install kicad-python
# Test basic connection pip install kicad-python>=0.5.0
python3 << EOF
from kicad import KiCad # Test basic connection
kicad = KiCad() python3 << EOF
print(f"Connected to KiCAD: {kicad.check_version()}") from kicad import KiCad
EOF kicad = KiCad()
print(f"Connected to KiCAD: {kicad.check_version()}")
# Read official documentation EOF
# https://docs.kicad.org/kicad-python-main
``` # Read official documentation
# https://docs.kicad.org/kicad-python-main
**Deliverables:** ```
- [ ] kicad-python installed and tested
- [ ] Connection test script **Deliverables:**
- [ ] API comparison document (SWIG vs IPC)
- [ ] kicad-python installed and tested
--- - [ ] Connection test script
- [ ] API comparison document (SWIG vs IPC)
### Phase 2: Abstraction Layer (Days 3-4)
---
**Goal:** Create an abstraction layer to support both APIs during transition
### Phase 2: Abstraction Layer (Days 3-4)
**File Structure:**
``` **Goal:** Create an abstraction layer to support both APIs during transition
python/kicad_api/
├── __init__.py **File Structure:**
├── base.py # Abstract base class
├── ipc_backend.py # NEW: IPC API implementation ```
├── swig_backend.py # Legacy SWIG implementation python/kicad_api/
── factory.py # Backend selector ── __init__.py
``` ├── base.py # Abstract base class
├── ipc_backend.py # NEW: IPC API implementation
**Abstract Interface:** ├── swig_backend.py # Legacy SWIG implementation
```python └── factory.py # Backend selector
# python/kicad_api/base.py ```
from abc import ABC, abstractmethod
from typing import Optional **Abstract Interface:**
from pathlib import Path
```python
class KiCADBackend(ABC): # python/kicad_api/base.py
"""Abstract base class for KiCAD API backends""" from abc import ABC, abstractmethod
from typing import Optional
@abstractmethod from pathlib import Path
def connect(self) -> bool:
"""Connect to KiCAD""" class KiCADBackend(ABC):
pass """Abstract base class for KiCAD API backends"""
@abstractmethod @abstractmethod
def disconnect(self) -> None: def connect(self) -> bool:
"""Disconnect from KiCAD""" """Connect to KiCAD"""
pass pass
@abstractmethod @abstractmethod
def is_connected(self) -> bool: def disconnect(self) -> None:
"""Check if connected""" """Disconnect from KiCAD"""
pass pass
@abstractmethod @abstractmethod
def create_project(self, path: Path, name: str) -> dict: def is_connected(self) -> bool:
"""Create a new KiCAD project""" """Check if connected"""
pass pass
@abstractmethod @abstractmethod
def open_project(self, path: Path) -> dict: def create_project(self, path: Path, name: str) -> dict:
"""Open existing project""" """Create a new KiCAD project"""
pass pass
@abstractmethod @abstractmethod
def get_board(self) -> 'BoardAPI': def open_project(self, path: Path) -> dict:
"""Get board API""" """Open existing project"""
pass pass
# ... more abstract methods @abstractmethod
``` def get_board(self) -> 'BoardAPI':
"""Get board API"""
**IPC Implementation:** pass
```python
# python/kicad_api/ipc_backend.py # ... more abstract methods
from kicad import KiCad ```
from kicad_api.base import KiCADBackend
**IPC Implementation:**
class IPCBackend(KiCADBackend):
"""KiCAD IPC API backend""" ```python
# python/kicad_api/ipc_backend.py
def __init__(self): from kicad import KiCad
self.kicad = None from kicad_api.base import KiCADBackend
def connect(self) -> bool: class IPCBackend(KiCADBackend):
"""Connect to running KiCAD instance""" """KiCAD IPC API backend"""
try:
self.kicad = KiCad() def __init__(self):
# Verify connection self.kicad = None
version = self.kicad.check_version()
logger.info(f"Connected to KiCAD via IPC: {version}") def connect(self) -> bool:
return True """Connect to running KiCAD instance"""
except Exception as e: try:
logger.error(f"Failed to connect via IPC: {e}") self.kicad = KiCad()
return False # Verify connection
version = self.kicad.check_version()
def create_project(self, path: Path, name: str) -> dict: logger.info(f"Connected to KiCAD via IPC: {version}")
"""Create project using IPC API""" return True
# Implementation here except Exception as e:
pass logger.error(f"Failed to connect via IPC: {e}")
``` return False
**Backend Factory:** def create_project(self, path: Path, name: str) -> dict:
```python """Create project using IPC API"""
# python/kicad_api/factory.py # Implementation here
from typing import Optional pass
from kicad_api.base import KiCADBackend ```
from kicad_api.ipc_backend import IPCBackend
from kicad_api.swig_backend import SWIGBackend **Backend Factory:**
def create_backend(backend_type: Optional[str] = None) -> KiCADBackend: ```python
""" # python/kicad_api/factory.py
Create appropriate KiCAD backend from typing import Optional
from kicad_api.base import KiCADBackend
Args: from kicad_api.ipc_backend import IPCBackend
backend_type: 'ipc', 'swig', or None for auto-detect from kicad_api.swig_backend import SWIGBackend
Returns: def create_backend(backend_type: Optional[str] = None) -> KiCADBackend:
KiCADBackend instance """
""" Create appropriate KiCAD backend
if backend_type == 'ipc':
return IPCBackend() Args:
elif backend_type == 'swig': backend_type: 'ipc', 'swig', or None for auto-detect
return SWIGBackend()
else: Returns:
# Auto-detect: Try IPC first, fall back to SWIG KiCADBackend instance
try: """
backend = IPCBackend() if backend_type == 'ipc':
if backend.connect(): return IPCBackend()
return backend elif backend_type == 'swig':
except ImportError: return SWIGBackend()
pass else:
# Auto-detect: Try IPC first, fall back to SWIG
# Fall back to SWIG try:
return SWIGBackend() backend = IPCBackend()
``` if backend.connect():
return backend
**Deliverables:** except ImportError:
- [ ] Abstract base class defined pass
- [ ] IPC backend implemented
- [ ] SWIG backend (wrapper around existing code) # Fall back to SWIG
- [ ] Factory with auto-detection return SWIGBackend()
```
---
**Deliverables:**
### Phase 3: Port Core Modules (Days 5-8)
- [ ] Abstract base class defined
**Migration Order** (by complexity): - [ ] IPC backend implemented
- [ ] SWIG backend (wrapper around existing code)
1. **project.py** (Simple - good starting point) - [ ] Factory with auto-detection
- Create, open, save projects
- Estimated: 2 hours ---
2. **board.py** (Medium - board properties) ### Phase 3: Port Core Modules (Days 5-8)
- Set size, layers, outline
- Estimated: 4 hours **Migration Order** (by complexity):
3. **component.py** (Complex - many operations) 1. **project.py** (Simple - good starting point)
- Place, move, rotate, delete - Create, open, save projects
- Component arrays and alignment - Estimated: 2 hours
- Estimated: 8 hours
2. **board.py** (Medium - board properties)
4. **routing.py** (Complex - trace routing) - Set size, layers, outline
- Nets, traces, vias - Estimated: 4 hours
- Copper pours, differential pairs
- Estimated: 8 hours 3. **component.py** (Complex - many operations)
- Place, move, rotate, delete
5. **design_rules.py** (Medium - DRC) - Component arrays and alignment
- Set rules, run DRC - Estimated: 8 hours
- Estimated: 4 hours
4. **routing.py** (Complex - trace routing)
6. **export.py** (Medium - file exports) - Nets, traces, vias
- Gerber, PDF, SVG, 3D - Copper pours, differential pairs
- Estimated: 4 hours - Estimated: 8 hours
**Total Estimated Time: 30 hours (~4 days)** 5. **design_rules.py** (Medium - DRC)
- Set rules, run DRC
**Migration Template:** - Estimated: 4 hours
```python
# OLD (SWIG) 6. **export.py** (Medium - file exports)
import pcbnew - Gerber, PDF, SVG, 3D
board = pcbnew.LoadBoard(filename) - Estimated: 4 hours
board.SetBoardSize(width, height)
**Total Estimated Time: 30 hours (~4 days)**
# NEW (IPC via abstraction)
from kicad_api import create_backend **Migration Template:**
backend = create_backend('ipc')
backend.connect() ```python
board_api = backend.get_board() # OLD (SWIG)
board_api.set_size(width, height) import pcbnew
``` board = pcbnew.LoadBoard(filename)
board.SetBoardSize(width, height)
**Deliverables:**
- [ ] project.py migrated # NEW (IPC via abstraction)
- [ ] board.py migrated from kicad_api import create_backend
- [ ] component.py migrated backend = create_backend('ipc')
- [ ] routing.py migrated backend.connect()
- [ ] design_rules.py migrated board_api = backend.get_board()
- [ ] export.py migrated board_api.set_size(width, height)
```
---
**Deliverables:**
### Phase 4: Testing & Validation (Days 9-10)
- [ ] project.py migrated
**Testing Strategy:** - [ ] board.py migrated
- [ ] component.py migrated
1. **Unit Tests** - [ ] routing.py migrated
```python - [ ] design_rules.py migrated
@pytest.mark.parametrize("backend_type", ["ipc", "swig"]) - [ ] export.py migrated
def test_create_project(backend_type):
backend = create_backend(backend_type) ---
result = backend.create_project(Path("/tmp/test"), "TestProject")
assert result["success"] is True ### Phase 4: Testing & Validation (Days 9-10)
```
**Testing Strategy:**
2. **Integration Tests**
- Run side-by-side: IPC vs SWIG 1. **Unit Tests**
- Compare outputs for identical operations
- Verify file compatibility ```python
@pytest.mark.parametrize("backend_type", ["ipc", "swig"])
3. **Performance Benchmarks** def test_create_project(backend_type):
```python backend = create_backend(backend_type)
# Measure: operations/second for each backend result = backend.create_project(Path("/tmp/test"), "TestProject")
# Expected: IPC slightly slower due to IPC overhead assert result["success"] is True
``` ```
**Deliverables:** 2. **Integration Tests**
- [ ] 50+ unit tests passing for IPC backend - Run side-by-side: IPC vs SWIG
- [ ] Side-by-side comparison tests - Compare outputs for identical operations
- [ ] Performance benchmarks documented - Verify file compatibility
--- 3. **Performance Benchmarks**
```python
## API Comparison Reference # Measure: operations/second for each backend
# Expected: IPC slightly slower due to IPC overhead
### Project Operations ```
| Operation | SWIG | IPC | **Deliverables:**
|-----------|------|-----|
| Create project | Custom file creation | `kicad.create_project()` | - [ ] 50+ unit tests passing for IPC backend
| Open project | `pcbnew.LoadBoard()` | `kicad.open_project()` | - [ ] Side-by-side comparison tests
| Save project | `board.Save()` | `board.save()` | - [ ] Performance benchmarks documented
### Board Operations ---
| Operation | SWIG | IPC | ## API Comparison Reference
|-----------|------|-----|
| Get board | `pcbnew.LoadBoard()` | `kicad.get_board()` | ### Project Operations
| Set size | `board.SetBoardSize()` | `board.set_size()` |
| Add layer | `board.GetLayerCount()` | `board.layers.add()` | | Operation | SWIG | IPC |
| -------------- | -------------------- | ------------------------ |
### Component Operations | Create project | Custom file creation | `kicad.create_project()` |
| Open project | `pcbnew.LoadBoard()` | `kicad.open_project()` |
| Operation | SWIG | IPC | | Save project | `board.Save()` | `board.save()` |
|-----------|------|-----|
| Place component | `pcbnew.FOOTPRINT()` | `board.add_footprint()` | ### Board Operations
| Move component | `fp.SetPosition()` | `footprint.set_position()` |
| Rotate component | `fp.SetOrientation()` | `footprint.set_rotation()` | | Operation | SWIG | IPC |
| --------- | ----------------------- | -------------------- |
### Routing Operations | Get board | `pcbnew.LoadBoard()` | `kicad.get_board()` |
| Set size | `board.SetBoardSize()` | `board.set_size()` |
| Operation | SWIG | IPC | | Add layer | `board.GetLayerCount()` | `board.layers.add()` |
|-----------|------|-----|
| Add net | `board.GetNetCount()` | `board.nets.add()` | ### Component Operations
| Route trace | `pcbnew.PCB_TRACK()` | `board.add_track()` |
| Add via | `pcbnew.PCB_VIA()` | `board.add_via()` | | Operation | SWIG | IPC |
| ---------------- | --------------------- | -------------------------- |
--- | Place component | `pcbnew.FOOTPRINT()` | `board.add_footprint()` |
| Move component | `fp.SetPosition()` | `footprint.set_position()` |
## Configuration Changes | Rotate component | `fp.SetOrientation()` | `footprint.set_rotation()` |
### Update requirements.txt ### Routing Operations
```diff | Operation | SWIG | IPC |
+ # KiCAD IPC API (official Python bindings) | ----------- | --------------------- | ------------------- |
+ kicad-python>=0.5.0 | Add net | `board.GetNetCount()` | `board.nets.add()` |
| Route trace | `pcbnew.PCB_TRACK()` | `board.add_track()` |
# Legacy SWIG support (for backward compatibility) | Add via | `pcbnew.PCB_VIA()` | `board.add_via()` |
kicad-skip>=0.1.0
``` ---
### Environment Variables ## Configuration Changes
```bash ### Update requirements.txt
# Enable IPC API in KiCAD preferences
# Preferences > Plugins > Enable IPC API Server ```diff
+ # KiCAD IPC API (official Python bindings)
# Set backend preference (optional) + kicad-python>=0.5.0
export KICAD_BACKEND=ipc # or 'swig' or 'auto'
``` # Legacy SWIG support (for backward compatibility)
kicad-skip>=0.1.0
### User Migration Guide ```
Create `docs/MIGRATING_TO_IPC.md`: ### Environment Variables
- How to enable IPC in KiCAD
- What changes for users ```bash
- Troubleshooting IPC connection issues # Enable IPC API in KiCAD preferences
# Preferences > Plugins > Enable IPC API Server
---
# Set backend preference (optional)
## Rollback Plan export KICAD_BACKEND=ipc # or 'swig' or 'auto'
```
If IPC migration fails:
### User Migration Guide
1. **Keep SWIG backend** - Already abstracted
2. **Default to SWIG** - Change factory auto-detection Create `docs/MIGRATING_TO_IPC.md`:
3. **Document limitations** - Note that SWIG will be removed eventually
4. **Plan retry** - Schedule IPC migration for later - How to enable IPC in KiCAD
- What changes for users
--- - Troubleshooting IPC connection issues
## Success Criteria ---
- [ ] ✅ All existing functionality works with IPC backend ## Rollback Plan
- [ ] ✅ Tests pass with both IPC and SWIG backends
- [ ] ✅ Performance acceptable (< 20% slowdown vs SWIG) If IPC migration fails:
- [ ] ✅ Documentation updated
- [ ] ✅ Migration guide created 1. **Keep SWIG backend** - Already abstracted
- [ ] ✅ User-facing tools work without changes 2. **Default to SWIG** - Change factory auto-detection
3. **Document limitations** - Note that SWIG will be removed eventually
--- 4. **Plan retry** - Schedule IPC migration for later
## Timeline ---
| Week | Days | Tasks | ## Success Criteria
|------|------|-------|
| **Week 2** | Mon-Tue | Research, install kicad-python, test connection | - [ ] ✅ All existing functionality works with IPC backend
| | Wed-Thu | Build abstraction layer | - [ ] ✅ Tests pass with both IPC and SWIG backends
| | Fri | Port project.py and board.py | - [ ] ✅ Performance acceptable (< 20% slowdown vs SWIG)
| **Week 3** | Mon-Tue | Port component.py and routing.py | - [ ] ✅ Documentation updated
| | Wed | Port design_rules.py and export.py | - [ ] ✅ Migration guide created
| | Thu-Fri | Testing, validation, documentation | - [ ] ✅ User-facing tools work without changes
--- ---
## Resources ## Timeline
- **Official Docs:** https://docs.kicad.org/kicad-python-main | Week | Days | Tasks |
- **kicad-python PyPI:** https://pypi.org/project/kicad-python/ | ---------- | ------- | ----------------------------------------------- |
- **IPC API Spec:** https://dev-docs.kicad.org/en/apis-and-binding/ipc-api/ | **Week 2** | Mon-Tue | Research, install kicad-python, test connection |
- **Protocol Buffers:** Used by IPC for message format | | Wed-Thu | Build abstraction layer |
| | Fri | Port project.py and board.py |
--- | **Week 3** | Mon-Tue | Port component.py and routing.py |
| | Wed | Port design_rules.py and export.py |
## Open Questions | | Thu-Fri | Testing, validation, documentation |
1. **How to handle KiCAD not running?** ---
- Option A: Auto-launch KiCAD in background
- Option B: Require user to launch KiCAD first ## Resources
- Option C: Fall back to SWIG if IPC unavailable
- **Decision: Option C for now, A later** - **Official Docs:** https://docs.kicad.org/kicad-python-main
- **kicad-python PyPI:** https://pypi.org/project/kicad-python/
2. **Connection management** - **IPC API Spec:** https://dev-docs.kicad.org/en/apis-and-binding/ipc-api/
- Should we keep connection open or connect per-operation? - **Protocol Buffers:** Used by IPC for message format
- **Decision: Keep alive with reconnect logic**
---
3. **Performance vs reliability**
- IPC has overhead but more stable ## Open Questions
- **Decision: Reliability > performance**
1. **How to handle KiCAD not running?**
--- - Option A: Auto-launch KiCAD in background
- Option B: Require user to launch KiCAD first
## Next Steps (This Week) - Option C: Fall back to SWIG if IPC unavailable
- **Decision: Option C for now, A later**
1. **Install kicad-python**
```bash 2. **Connection management**
pip install kicad-python - Should we keep connection open or connect per-operation?
``` - **Decision: Keep alive with reconnect logic**
2. **Test IPC connection** 3. **Performance vs reliability**
```bash - IPC has overhead but more stable
# Launch KiCAD - **Decision: Reliability > performance**
# Enable IPC in preferences
python3 -c "from kicad import KiCad; k=KiCad(); print(k.check_version())" ---
```
## Next Steps (This Week)
3. **Create abstraction layer structure**
```bash 1. **Install kicad-python**
mkdir -p python/kicad_api
touch python/kicad_api/{__init__,base,ipc_backend,swig_backend,factory}.py ```bash
``` pip install kicad-python
```
4. **Begin project.py migration**
- Start with simplest module 2. **Test IPC connection**
- Establish patterns for others
```bash
--- # Launch KiCAD
# Enable IPC in preferences
**Prepared by:** Claude Code python3 -c "from kicad import KiCad; k=KiCad(); print(k.check_version())"
**Last Updated:** October 25, 2025 ```
**Status:** 📋 Ready to execute
3. **Create abstraction layer structure**
```bash
mkdir -p python/kicad_api
touch python/kicad_api/{__init__,base,ipc_backend,swig_backend,factory}.py
```
4. **Begin project.py migration**
- Start with simplest module
- Establish patterns for others
---
**Prepared by:** Claude Code
**Last Updated:** October 25, 2025
**Status:** 📋 Ready to execute

File diff suppressed because it is too large Load Diff

View File

@@ -1,222 +1,241 @@
# Router Implementation Status # Router Implementation Status
## ✅ Phase 1 Complete: Foundation & Infrastructure ## ✅ Phase 1 Complete: Foundation & Infrastructure
**Date:** December 28, 2025 **Date:** December 28, 2025
### What Was Implemented ### What Was Implemented
#### 1. Tool Registry (`src/tools/registry.ts`) #### 1. Tool Registry (`src/tools/registry.ts`)
- ✅ Complete tool categorization (59 tools → 7 categories)
-Direct tools list (12 high-frequency tools) -Complete tool categorization (59 tools → 7 categories)
-Category lookup maps for O(1) access -Direct tools list (12 high-frequency tools)
-Tool search functionality -Category lookup maps for O(1) access
-Registry statistics and metadata -Tool search functionality
- ✅ Registry statistics and metadata
#### 2. Router Tools (`src/tools/router.ts`)
-`list_tool_categories` - Browse all categories #### 2. Router Tools (`src/tools/router.ts`)
-`get_category_tools` - View tools in a category
-`execute_tool` - Execute any routed tool -`list_tool_categories` - Browse all categories
-`search_tools` - Search tools by keyword -`get_category_tools` - View tools in a category
-`execute_tool` - Execute any routed tool
#### 3. Server Integration (`src/server.ts`) -`search_tools` - Search tools by keyword
- ✅ Router tools registered at server startup
- ✅ All tools remain functional (backwards compatible) #### 3. Server Integration (`src/server.ts`)
- ✅ Logging added for router pattern status
- ✅ Router tools registered at server startup
#### 4. Documentation - ✅ All tools remain functional (backwards compatible)
-`TOOL_INVENTORY.md` - Complete tool catalog -Logging added for router pattern status
-`ROUTER_ARCHITECTURE.md` - Design specification
-`ROUTER_IMPLEMENTATION_STATUS.md` - This file #### 4. Documentation
### Current State -`TOOL_INVENTORY.md` - Complete tool catalog
-`ROUTER_ARCHITECTURE.md` - Design specification
**Status:****Router Infrastructure Complete** -`ROUTER_IMPLEMENTATION_STATUS.md` - This file
**Build:** ✅ Compiles successfully (`npm run build`) ### Current State
**Tool Count:** **Status:****Router Infrastructure Complete**
- Total Tools: 59 (ALL still registered and visible)
- Direct Tools: 12 **Build:** ✅ Compiles successfully (`npm run build`)
- Routed Tools: 47
- Router Tools: 4 **Tool Count:**
- **Currently Visible to Claude:** 63 tools (59 + 4 router)
- Total Tools: 59 (ALL still registered and visible)
**Token Impact:** - Direct Tools: 12
- **Current:** ~42K tokens (still showing all tools) - Routed Tools: 47
- **Target:** ~12K tokens (Phase 2 optimization) - Router Tools: 4
- **Potential Savings:** ~30K tokens (71% reduction) - **Currently Visible to Claude:** 63 tools (59 + 4 router)
## 🔄 Phase 2: Token Optimization (Next Step) **Token Impact:**
### Objective - **Current:** ~42K tokens (still showing all tools)
Hide routed tools from Claude's context while keeping them accessible via `execute_tool`. - **Target:** ~12K tokens (Phase 2 optimization)
- **Potential Savings:** ~30K tokens (71% reduction)
### Two Approaches
## 🔄 Phase 2: Token Optimization (Next Step)
#### Option A: Registration Filtering (Recommended)
Modify tool registration to conditionally register tools based on whether they're in the direct list. ### Objective
**Changes needed:** Hide routed tools from Claude's context while keeping them accessible via `execute_tool`.
1. Update each `register*Tools` function to check `isDirectTool()`
2. Only call `server.tool()` for direct tools ### Two Approaches
3. Routed tools remain accessible via `execute_tool` calling `callKicadScript`
#### Option A: Registration Filtering (Recommended)
**Pros:**
- Clean separation Modify tool registration to conditionally register tools based on whether they're in the direct list.
- True token savings
- No behavior changes **Changes needed:**
**Cons:** 1. Update each `register*Tools` function to check `isDirectTool()`
- Requires modifying 9 tool files 2. Only call `server.tool()` for direct tools
3. Routed tools remain accessible via `execute_tool` calling `callKicadScript`
#### Option B: MCP Filter (If Supported)
If MCP SDK supports tool filtering/hiding, use that instead. **Pros:**
**Pros:** - Clean separation
- No tool file changes - True token savings
- Centralized control - No behavior changes
**Cons:** **Cons:**
- May not be supported by SDK
- Needs investigation - Requires modifying 9 tool files
### Implementation Plan for Phase 2 #### Option B: MCP Filter (If Supported)
1. **Create Helper Function** (`src/tools/conditional-register.ts`) If MCP SDK supports tool filtering/hiding, use that instead.
```typescript
export function registerToolConditionally( **Pros:**
server: McpServer,
toolName: string, - No tool file changes
definition: ToolDefinition, - Centralized control
handler: Function
) { **Cons:**
if (isDirectTool(toolName)) {
// Register with MCP (visible to Claude) - May not be supported by SDK
server.tool(toolName, definition, handler); - Needs investigation
} else {
// Register handler for execute_tool (hidden from Claude) ### Implementation Plan for Phase 2
registerToolHandler(toolName, handler);
} 1. **Create Helper Function** (`src/tools/conditional-register.ts`)
}
``` ```typescript
export function registerToolConditionally(
2. **Update Tool Registration Functions** server: McpServer,
Modify each `register*Tools` function to use conditional registration. toolName: string,
definition: ToolDefinition,
3. **Test** handler: Function,
- Verify direct tools work normally ) {
- Verify routed tools work via `execute_tool` if (isDirectTool(toolName)) {
- Verify token count reduction // Register with MCP (visible to Claude)
server.tool(toolName, definition, handler);
4. **Measure Impact** } else {
Count tools visible to Claude before/after. // Register handler for execute_tool (hidden from Claude)
registerToolHandler(toolName, handler);
## 📊 Categories & Distribution }
}
| Category | Tools | Description | ```
|----------|-------|-------------|
| **board** | 9 | Board configuration, layers, zones, visualization | 2. **Update Tool Registration Functions**
| **component** | 8 | Advanced component operations | Modify each `register*Tools` function to use conditional registration.
| **export** | 8 | Manufacturing file generation |
| **drc** | 9 | Design rule checking & validation | 3. **Test**
| **schematic** | 9 | Schematic editor operations | - Verify direct tools work normally
| **library** | 4 | Footprint library access | - Verify routed tools work via `execute_tool`
| **routing** | 3 | Advanced routing (vias, copper pours) | - Verify token count reduction
| **TOTAL** | **47** | **Routed tools** |
| **direct** | **12** | **Always visible tools** | 4. **Measure Impact**
| **router** | **4** | **Discovery tools** | Count tools visible to Claude before/after.
## 🧪 Testing the Router ## 📊 Categories & Distribution
### Test 1: List Categories | Category | Tools | Description |
``` | ------------- | ------ | ------------------------------------------------- |
User: "What tool categories are available?" | **board** | 9 | Board configuration, layers, zones, visualization |
| **component** | 8 | Advanced component operations |
Expected: Claude calls list_tool_categories | **export** | 8 | Manufacturing file generation |
Result: Returns 7 categories with descriptions | **drc** | 9 | Design rule checking & validation |
``` | **schematic** | 9 | Schematic editor operations |
| **library** | 4 | Footprint library access |
### Test 2: Browse Category | **routing** | 3 | Advanced routing (vias, copper pours) |
``` | **TOTAL** | **47** | **Routed tools** |
User: "What export tools are available?" | **direct** | **12** | **Always visible tools** |
| **router** | **4** | **Discovery tools** |
Expected: Claude calls get_category_tools({ category: "export" })
Result: Returns 8 export tools ## 🧪 Testing the Router
```
### Test 1: List Categories
### Test 3: Search Tools
``` ```
User: "How do I export gerber files?" User: "What tool categories are available?"
Expected: Claude calls search_tools({ query: "gerber" }) Expected: Claude calls list_tool_categories
Result: Finds export_gerber in export category Result: Returns 7 categories with descriptions
``` ```
### Test 4: Execute Tool ### Test 2: Browse Category
```
User: "Export gerbers to ./output" ```
User: "What export tools are available?"
Expected: Claude calls execute_tool({
tool_name: "export_gerber", Expected: Claude calls get_category_tools({ category: "export" })
params: { outputDir: "./output" } Result: Returns 8 export tools
}) ```
Result: Executes via router, returns gerber export result
``` ### Test 3: Search Tools
## 📝 Benefits Achieved (Phase 1) ```
User: "How do I export gerber files?"
1. ✅ **Foundation Ready**: All infrastructure in place
2. ✅ **Organized**: 59 tools categorized into logical groups Expected: Claude calls search_tools({ query: "gerber" })
3. ✅ **Discoverable**: Tools easily found via search/browse Result: Finds export_gerber in export category
4. ✅ **Backwards Compatible**: All existing tools still work ```
5. ✅ **Extensible**: Easy to add new tools and categories
6. ✅ **Documented**: Complete architecture and usage docs ### Test 4: Execute Tool
## 🚀 Next Actions ```
User: "Export gerbers to ./output"
1. **Optional: Complete Phase 2** (Token Optimization)
- Implement conditional registration Expected: Claude calls execute_tool({
- Hide routed tools from context tool_name: "export_gerber",
- Achieve 71% token reduction params: { outputDir: "./output" }
})
2. **Or: Ship Phase 1 As-Is** Result: Executes via router, returns gerber export result
- Router tools work perfectly now ```
- Users can discover and execute tools
- Optimization can be done later ## 📝 Benefits Achieved (Phase 1)
- No breaking changes
1. ✅ **Foundation Ready**: All infrastructure in place
## 📚 Related Files 2. ✅ **Organized**: 59 tools categorized into logical groups
3. ✅ **Discoverable**: Tools easily found via search/browse
- `src/tools/registry.ts` - Tool registry and categories 4. ✅ **Backwards Compatible**: All existing tools still work
- `src/tools/router.ts` - Router tool implementations 5. ✅ **Extensible**: Easy to add new tools and categories
- `src/server.ts` - Server integration 6. ✅ **Documented**: Complete architecture and usage docs
- `docs/TOOL_INVENTORY.md` - Complete tool list
- `docs/ROUTER_ARCHITECTURE.md` - Design specification ## 🚀 Next Actions
- `docs/mcp-router-guide.md` - Original implementation guide
1. **Optional: Complete Phase 2** (Token Optimization)
## 💡 Usage Example - Implement conditional registration
- Hide routed tools from context
```typescript - Achieve 71% token reduction
// User: "I need to export gerber files"
2. **Or: Ship Phase 1 As-Is**
// Claude's interaction: - Router tools work perfectly now
// 1. Sees "export" and "gerber" keywords - Users can discover and execute tools
// 2. Calls search_tools({ query: "gerber" }) - Optimization can be done later
// → Returns: { category: "export", tool: "export_gerber", ... } - No breaking changes
// 3. Calls execute_tool({
// tool_name: "export_gerber", ## 📚 Related Files
// params: { outputDir: "./gerbers" }
// }) - `src/tools/registry.ts` - Tool registry and categories
// → Executes and returns result - `src/tools/router.ts` - Router tool implementations
// 4. "I've exported your Gerber files to ./gerbers/" - `src/server.ts` - Server integration
``` - `docs/TOOL_INVENTORY.md` - Complete tool list
- `docs/ROUTER_ARCHITECTURE.md` - Design specification
## Status Summary - `docs/mcp-router-guide.md` - Original implementation guide
**Router Pattern: IMPLEMENTED** ## 💡 Usage Example
**Build: PASSING**
**Backwards Compatible: YES** ```typescript
**Token Optimization: PENDING (Phase 2)** // User: "I need to export gerber files"
The router infrastructure is complete and functional. The system now supports tool discovery and organized access to all 59 tools. Phase 2 optimization (hiding routed tools) can be implemented when ready for maximum token savings. // Claude's interaction:
// 1. Sees "export" and "gerber" keywords
// 2. Calls search_tools({ query: "gerber" })
// → Returns: { category: "export", tool: "export_gerber", ... }
// 3. Calls execute_tool({
// tool_name: "export_gerber",
// params: { outputDir: "./gerbers" }
// })
// → Executes and returns result
// 4. "I've exported your Gerber files to ./gerbers/"
```
## Status Summary
**Router Pattern: IMPLEMENTED**
**Build: PASSING**
**Backwards Compatible: YES**
**Token Optimization: PENDING (Phase 2)**
The router infrastructure is complete and functional. The system now supports tool discovery and organized access to all 59 tools. Phase 2 optimization (hiding routed tools) can be implemented when ready for maximum token savings.

File diff suppressed because it is too large Load Diff

View File

@@ -1,125 +1,133 @@
# Schematic Workflow Fix - Issue #26 # Schematic Workflow Fix - Issue #26
## Problem Summary ## Problem Summary
The schematic workflow was completely broken due to incorrect usage of the kicad-skip library: The schematic workflow was completely broken due to incorrect usage of the kicad-skip library:
1. **`create_project`** only created PCB files, no schematic 1. **`create_project`** only created PCB files, no schematic
2. **`create_schematic`** created orphaned schematic files not linked to projects 2. **`create_schematic`** created orphaned schematic files not linked to projects
3. **`add_schematic_component`** called non-existent `schematic.add_symbol()` method 3. **`add_schematic_component`** called non-existent `schematic.add_symbol()` method
4. Project files didn't reference schematics in their structure 4. Project files didn't reference schematics in their structure
## Root Cause ## Root Cause
The kicad-skip library **does not support creating symbols from scratch**. The only way to add symbols is by **cloning existing symbol instances**. 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: From kicad-skip documentation:
> "symbols: these don't have a new()" because they require complex mappings to library elements, pins, and properties.
> "symbols: these don't have a new()" because they require complex mappings to library elements, pins, and properties.
## Solution
## Solution
### 1. Template-Based Approach
### 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 Created a template schematic (`python/templates/template_with_symbols.kicad_sch`) with:
- 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 - Complete `lib_symbols` section defining R, C, LED symbols
- Three template symbol instances placed off-screen at (-100, -110, -120)
### 2. Updated Files - Template symbols marked as `dnp yes`, `in_bom no`, `on_board no` so they don't interfere
**python/commands/project.py:** ### 2. Updated Files
- Now creates both `.kicad_pcb` AND `.kicad_sch` files
- Project file includes schematic reference in `sheets` array **python/commands/project.py:**
- Copies template schematic with cloneable symbols
- Now creates both `.kicad_pcb` AND `.kicad_sch` files
**python/commands/schematic.py:** - Project file includes schematic reference in `sheets` array
- Uses template file instead of creating from scratch - Copies template schematic with cloneable symbols
- Proper minimal schematic structure when template unavailable
**python/commands/schematic.py:**
**python/commands/component_schematic.py:**
- Completely rewritten to use `clone()` API - Uses template file instead of creating from scratch
- Maps component types to template symbols - Proper minimal schematic structure when template unavailable
- Proper UUID generation for each cloned symbol
- Correct position setting: `symbol.at.value = [x, y, rotation]` **python/commands/component_schematic.py:**
### 3. Correct Workflow - Completely rewritten to use `clone()` API
- Maps component types to template symbols
```python - Proper UUID generation for each cloned symbol
from commands.project import ProjectCommands - Correct position setting: `symbol.at.value = [x, y, rotation]`
from commands.schematic import SchematicManager
from commands.component_schematic import ComponentManager ### 3. Correct Workflow
# Step 1: Create project (creates both PCB and schematic) ```python
project_cmd = ProjectCommands() from commands.project import ProjectCommands
result = project_cmd.create_project({ from commands.schematic import SchematicManager
"name": "MyProject", from commands.component_schematic import ComponentManager
"path": "/path/to/project"
}) # Step 1: Create project (creates both PCB and schematic)
project_cmd = ProjectCommands()
# Step 2: Load the schematic result = project_cmd.create_project({
sch = SchematicManager.load_schematic(result['project']['schematicPath']) "name": "MyProject",
"path": "/path/to/project"
# Step 3: Add components by cloning templates })
component_def = {
"type": "R", # Maps to _TEMPLATE_R # Step 2: Load the schematic
"reference": "R1", # Component reference sch = SchematicManager.load_schematic(result['project']['schematicPath'])
"value": "10k", # Component value
"footprint": "Resistor_SMD:R_0603_1608Metric", # Step 3: Add components by cloning templates
"x": 50.8, # Position in mm component_def = {
"y": 50.8, # Position in mm "type": "R", # Maps to _TEMPLATE_R
"rotation": 0 # Rotation in degrees "reference": "R1", # Component reference
} "value": "10k", # Component value
symbol = ComponentManager.add_component(sch, component_def) "footprint": "Resistor_SMD:R_0603_1608Metric",
"x": 50.8, # Position in mm
# Step 4: Save the schematic "y": 50.8, # Position in mm
SchematicManager.save_schematic(sch, result['project']['schematicPath']) "rotation": 0 # Rotation in degrees
``` }
symbol = ComponentManager.add_component(sch, component_def)
## Supported Component Types
# Step 4: Save the schematic
Currently supported template symbols: SchematicManager.save_schematic(sch, result['project']['schematicPath'])
- `R` - Resistor (maps to `_TEMPLATE_R`) ```
- `C` - Capacitor (maps to `_TEMPLATE_C`)
- `D` or `LED` - LED (maps to `_TEMPLATE_D`) ## Supported Component Types
To add more component types, update: Currently supported template symbols:
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` - `R` - Resistor (maps to `_TEMPLATE_R`)
- `C` - Capacitor (maps to `_TEMPLATE_C`)
## Testing - `D` or `LED` - LED (maps to `_TEMPLATE_D`)
Comprehensive test created at `/tmp/test_schematic_workflow.py`: To add more component types, update:
- Creates project with schematic
- Loads schematic 1. `python/templates/template_with_symbols.kicad_sch` - Add lib_symbol definition and template instance
- Adds R, C, LED components 2. `python/commands/component_schematic.py` - Add mapping in `TEMPLATE_MAP`
- Saves schematic
- Validates with `kicad-cli sch export pdf` ## Testing
All tests passing ✓ Comprehensive test created at `/tmp/test_schematic_workflow.py`:
## Files Modified - Creates project with schematic
- Loads schematic
- `python/commands/project.py` - Added schematic creation - Adds R, C, LED components
- `python/commands/schematic.py` - Fixed template usage - Saves schematic
- `python/commands/component_schematic.py` - Rewritten to use clone() API - Validates with `kicad-cli sch export pdf`
- `python/templates/empty.kicad_sch` - Minimal template (created)
- `python/templates/template_with_symbols.kicad_sch` - Template with cloneable symbols (created) All tests passing ✓
## Limitations ## Files Modified
1. Can only add components that have templates defined - `python/commands/project.py` - Added schematic creation
2. Template symbols remain in schematic (but marked as DNP/not in BOM) - `python/commands/schematic.py` - Fixed template usage
3. Complex symbols (multi-unit, hierarchical) may need custom templates - `python/commands/component_schematic.py` - Rewritten to use clone() API
- `python/templates/empty.kicad_sch` - Minimal template (created)
## Future Improvements - `python/templates/template_with_symbols.kicad_sch` - Template with cloneable symbols (created)
1. Add more component templates (transistors, connectors, ICs) ## Limitations
2. Dynamic template generation from KiCad symbol libraries
3. Auto-hide template symbols in schematic 1. Can only add components that have templates defined
4. Support for custom user templates 2. Template symbols remain in schematic (but marked as DNP/not in BOM)
3. Complex symbols (multi-unit, hierarchical) may need custom templates
## References
## Future Improvements
- GitHub Issue: #26
- kicad-skip documentation: https://github.com/psychogenic/kicad-skip 1. Add more component templates (transistors, connectors, ICs)
- Test results: `/tmp/test_schematic_workflow/` 2. Dynamic template generation from KiCad symbol libraries
3. Auto-hide template symbols in schematic
4. Support for custom user templates
## References
- GitHub Issue: #26
- kicad-skip documentation: https://github.com/psychogenic/kicad-skip
- Test results: `/tmp/test_schematic_workflow/`

File diff suppressed because it is too large Load Diff

View File

@@ -1,422 +1,457 @@
# Week 1 - Session 2 Summary # Week 1 - Session 2 Summary
**Date:** October 25, 2025 (Afternoon)
**Status:** 🚀 **OUTSTANDING PROGRESS** **Date:** October 25, 2025 (Afternoon)
**Status:** 🚀 **OUTSTANDING PROGRESS**
---
---
## 🎯 Session Goals
## 🎯 Session Goals
Continue Week 1 implementation while user installs KiCAD:
1. Update README with comprehensive Linux guide Continue Week 1 implementation while user installs KiCAD:
2. Create installation scripts
3. Begin IPC API preparation 1. Update README with comprehensive Linux guide
4. Set up development infrastructure 2. Create installation scripts
3. Begin IPC API preparation
--- 4. Set up development infrastructure
## ✅ Completed Work ---
### 1. **README.md Major Update** 📚 ## ✅ Completed Work
**File:** `README.md` ### 1. **README.md Major Update** 📚
**Changes:** **File:** `README.md`
- ✅ Updated project status to reflect v2.0 rebuild
- ✅ Added collapsible platform-specific installation sections: **Changes:**
- 🐧 **Linux (Ubuntu/Debian)** - Primary, detailed
- 🪟 **Windows 10/11** - Fully supported - ✅ Updated project status to reflect v2.0 rebuild
- 🍎 **macOS** - Experimental - ✅ Added collapsible platform-specific installation sections:
- ✅ Updated system requirements (Linux primary platform) - 🐧 **Linux (Ubuntu/Debian)** - Primary, detailed
- ✅ Added Quick Start section with test commands - 🪟 **Windows 10/11** - Fully supported
- ✅ Better visual organization with emojis and status indicators - 🍎 **macOS** - Experimental
- ✅ Updated system requirements (Linux primary platform)
**Impact:** New users can now install on Linux in < 10 minutes! - ✅ Added Quick Start section with test commands
- ✅ Better visual organization with emojis and status indicators
---
**Impact:** New users can now install on Linux in < 10 minutes!
### 2. **Linux Installation Script** 🛠️
---
**File:** `scripts/install-linux.sh`
### 2. **Linux Installation Script** 🛠️
**Features:**
- Fully automated Ubuntu/Debian installation **File:** `scripts/install-linux.sh`
- Color-coded output (info/success/warning/error)
- Safety checks (platform detection, command validation) **Features:**
- Installs:
- KiCAD 9.0 from PPA - Fully automated Ubuntu/Debian installation
- Node.js 20.x - Color-coded output (info/success/warning/error)
- Python dependencies - Safety checks (platform detection, command validation)
- Builds TypeScript - Installs:
- Verification checks after installation - KiCAD 9.0 from PPA
- Helpful next-steps guidance - Node.js 20.x
- Python dependencies
**Usage:** - Builds TypeScript
```bash - Verification checks after installation
cd kicad-mcp-server - Helpful next-steps guidance
./scripts/install-linux.sh
``` **Usage:**
**Lines of Code:** ~200 lines of robust shell script ```bash
cd kicad-mcp-server
--- ./scripts/install-linux.sh
```
### 3. **Pre-Commit Hooks Configuration** 🔧
**Lines of Code:** ~200 lines of robust shell script
**File:** `.pre-commit-config.yaml`
---
**Hooks Added:**
- **Python:** ### 3. **Pre-Commit Hooks Configuration** 🔧
- Black (code formatting)
- isort (import sorting) **File:** `.pre-commit-config.yaml`
- MyPy (type checking)
- Flake8 (linting) **Hooks Added:**
- Bandit (security checks)
- **TypeScript/JavaScript:** - **Python:**
- Prettier (formatting) - Black (code formatting)
- **General:** - isort (import sorting)
- Trailing whitespace removal - MyPy (type checking)
- End-of-file fixer - Flake8 (linting)
- YAML/JSON validation - Bandit (security checks)
- Large file detection - **TypeScript/JavaScript:**
- Merge conflict detection - Prettier (formatting)
- Private key detection - **General:**
- **Markdown:** - Trailing whitespace removal
- Markdownlint (formatting) - End-of-file fixer
- YAML/JSON validation
**Setup:** - Large file detection
```bash - Merge conflict detection
pip install pre-commit - Private key detection
pre-commit install - **Markdown:**
``` - Markdownlint (formatting)
**Impact:** Automatic code quality enforcement on every commit! **Setup:**
--- ```bash
pip install pre-commit
### 4. **IPC API Migration Plan** 📋 pre-commit install
```
**File:** `docs/IPC_API_MIGRATION_PLAN.md`
**Impact:** Automatic code quality enforcement on every commit!
**Comprehensive 30-page migration guide:**
- Why migrate (SWIG deprecation analysis) ---
- IPC API architecture overview
- 4-phase migration strategy (10 days) ### 4. **IPC API Migration Plan** 📋
- API comparison tables (SWIG vs IPC)
- Testing strategy **File:** `docs/IPC_API_MIGRATION_PLAN.md`
- Rollback plan
- Success criteria **Comprehensive 30-page migration guide:**
- Timeline with day-by-day tasks
- Why migrate (SWIG deprecation analysis)
**Key Insights:** - IPC API architecture overview
- SWIG will be removed in KiCAD 10.0 - 4-phase migration strategy (10 days)
- IPC is faster for some operations - API comparison tables (SWIG vs IPC)
- Protocol Buffers ensure API stability - Testing strategy
- Multi-language support opens future possibilities - Rollback plan
- Success criteria
--- - Timeline with day-by-day tasks
### 5. **IPC API Abstraction Layer** 🏗️ **Key Insights:**
**New Module:** `python/kicad_api/` - SWIG will be removed in KiCAD 10.0
- IPC is faster for some operations
**Files Created (5):** - Protocol Buffers ensure API stability
- Multi-language support opens future possibilities
1. **`__init__.py`** (20 lines)
- Package exports ---
- Version info
- Usage examples ### 5. **IPC API Abstraction Layer** 🏗️
2. **`base.py`** (180 lines) **New Module:** `python/kicad_api/`
- `KiCADBackend` abstract base class
- `BoardAPI` abstract interface **Files Created (5):**
- Custom exceptions (`BackendError`, `ConnectionError`, etc.)
- Defines contract for all backends 1. **`__init__.py`** (20 lines)
- Package exports
3. **`factory.py`** (160 lines) - Version info
- `create_backend()` - Smart backend selection - Usage examples
- Auto-detection (try IPC, fall back to SWIG)
- Environment variable support (`KICAD_BACKEND`) 2. **`base.py`** (180 lines)
- `get_available_backends()` - Diagnostic function - `KiCADBackend` abstract base class
- Comprehensive error handling - `BoardAPI` abstract interface
- Custom exceptions (`BackendError`, `ConnectionError`, etc.)
4. **`ipc_backend.py`** (210 lines) - Defines contract for all backends
- `IPCBackend` class (kicad-python wrapper)
- `IPCBoardAPI` class 3. **`factory.py`** (160 lines)
- Connection management - `create_backend()` - Smart backend selection
- Skeleton methods (to be implemented in Week 2-3) - Auto-detection (try IPC, fall back to SWIG)
- Clear TODO markers for migration - Environment variable support (`KICAD_BACKEND`)
- `get_available_backends()` - Diagnostic function
5. **`swig_backend.py`** (220 lines) - Comprehensive error handling
- `SWIGBackend` class (wraps existing code)
- `SWIGBoardAPI` class 4. **`ipc_backend.py`** (210 lines)
- Backward compatibility layer - `IPCBackend` class (kicad-python wrapper)
- Deprecation warnings - `IPCBoardAPI` class
- Bridges old commands to new interface - Connection management
- Skeleton methods (to be implemented in Week 2-3)
**Total Lines of Code:** ~800 lines - Clear TODO markers for migration
**Architecture:** 5. **`swig_backend.py`** (220 lines)
```python - `SWIGBackend` class (wraps existing code)
from kicad_api import create_backend - `SWIGBoardAPI` class
- Backward compatibility layer
# Auto-detect best backend - Deprecation warnings
backend = create_backend() - Bridges old commands to new interface
# Or specify explicitly **Total Lines of Code:** ~800 lines
backend = create_backend('ipc') # Use IPC
backend = create_backend('swig') # Use SWIG (deprecated) **Architecture:**
# Use unified interface ```python
if backend.connect(): from kicad_api import create_backend
board = backend.get_board()
board.set_size(100, 80) # Auto-detect best backend
``` backend = create_backend()
**Key Features:** # Or specify explicitly
- Abstraction allows painless migration backend = create_backend('ipc') # Use IPC
- Both backends can coexist during transition backend = create_backend('swig') # Use SWIG (deprecated)
- Easy testing (compare SWIG vs IPC outputs)
- Future-proof (add new backends easily) # Use unified interface
- Type hints throughout if backend.connect():
- Comprehensive error handling board = backend.get_board()
board.set_size(100, 80)
--- ```
### 6. **Enhanced package.json** 📦 **Key Features:**
**File:** `package.json` - Abstraction allows painless migration
- Both backends can coexist during transition
**Improvements:** - Easy testing (compare SWIG vs IPC outputs)
- Version bumped to `2.0.0-alpha.1` - Future-proof (add new backends easily)
- Better description - Type hints throughout
- Enhanced npm scripts: - Comprehensive error handling
```json
"build:watch": "tsc --watch" ---
"clean": "rm -rf dist"
"rebuild": "npm run clean && npm run build" ### 6. **Enhanced package.json** 📦
"test": "npm run test:ts && npm run test:py"
"test:py": "pytest tests/ -v" **File:** `package.json`
"test:coverage": "pytest with coverage"
"lint": "npm run lint:ts && npm run lint:py" **Improvements:**
"lint:py": "black + mypy + flake8"
"format": "prettier + black" - Version bumped to `2.0.0-alpha.1`
``` - Better description
- Enhanced npm scripts:
**Impact:** Better developer experience, easier workflows ```json
"build:watch": "tsc --watch"
--- "clean": "rm -rf dist"
"rebuild": "npm run clean && npm run build"
## 📊 Statistics "test": "npm run test:ts && npm run test:py"
"test:py": "pytest tests/ -v"
### Files Created/Modified (Session 2) "test:coverage": "pytest with coverage"
"lint": "npm run lint:ts && npm run lint:py"
**New Files (10):** "lint:py": "black + mypy + flake8"
``` "format": "prettier + black"
docs/IPC_API_MIGRATION_PLAN.md # 500+ lines ```
docs/WEEK1_SESSION2_SUMMARY.md # This file
scripts/install-linux.sh # 200 lines **Impact:** Better developer experience, easier workflows
.pre-commit-config.yaml # 60 lines
python/kicad_api/__init__.py # 20 lines ---
python/kicad_api/base.py # 180 lines
python/kicad_api/factory.py # 160 lines ## 📊 Statistics
python/kicad_api/ipc_backend.py # 210 lines
python/kicad_api/swig_backend.py # 220 lines ### Files Created/Modified (Session 2)
```
**New Files (10):**
**Modified Files (2):**
``` ```
README.md # Major rewrite docs/IPC_API_MIGRATION_PLAN.md # 500+ lines
package.json # Enhanced scripts docs/WEEK1_SESSION2_SUMMARY.md # This file
``` scripts/install-linux.sh # 200 lines
.pre-commit-config.yaml # 60 lines
**Total New Lines:** ~1,600+ lines of code/documentation python/kicad_api/__init__.py # 20 lines
python/kicad_api/base.py # 180 lines
--- python/kicad_api/factory.py # 160 lines
python/kicad_api/ipc_backend.py # 210 lines
### Combined Sessions 1+2 Today python/kicad_api/swig_backend.py # 220 lines
```
**Files Created:** 27
**Lines Written:** ~3,000+ **Modified Files (2):**
**Documentation Pages:** 8
**Tests Created:** 20+ ```
README.md # Major rewrite
--- package.json # Enhanced scripts
```
## 🎯 Week 1 Status
**Total New Lines:** ~1,600+ lines of code/documentation
### Progress: **95% Complete** ████████████░
---
| Task | Status |
|------|--------| ### Combined Sessions 1+2 Today
| Linux compatibility | ✅ Complete |
| CI/CD pipeline | ✅ Complete | **Files Created:** 27
| Cross-platform paths | ✅ Complete | **Lines Written:** ~3,000+
| Developer docs | ✅ Complete | **Documentation Pages:** 8
| pytest framework | ✅ Complete | **Tests Created:** 20+
| Config templates | ✅ Complete |
| Installation scripts | ✅ Complete | ---
| Pre-commit hooks | ✅ Complete |
| IPC migration plan | ✅ Complete | ## 🎯 Week 1 Status
| IPC abstraction layer | ✅ Complete |
| README updates | ✅ Complete | ### Progress: **95% Complete** ████████████░
| Testing on Ubuntu | ⏳ Pending (needs KiCAD install) |
| Task | Status |
**Only Remaining:** Test with actual KiCAD 9.0 installation! | --------------------- | -------------------------------- |
| Linux compatibility | ✅ Complete |
--- | CI/CD pipeline | ✅ Complete |
| Cross-platform paths | ✅ Complete |
## 🚀 Ready for Week 2 | Developer docs | ✅ Complete |
| pytest framework | ✅ Complete |
### IPC API Migration Prep ✅ | Config templates | ✅ Complete |
| Installation scripts | ✅ Complete |
Everything is in place to begin migration: | Pre-commit hooks | ✅ Complete |
- ✅ Abstraction layer architecture defined | IPC migration plan | ✅ Complete |
- ✅ Base classes and interfaces ready | IPC abstraction layer | ✅ Complete |
- ✅ Factory pattern for backend selection | README updates | ✅ Complete |
- ✅ SWIG wrapper for backward compatibility | Testing on Ubuntu | ⏳ Pending (needs KiCAD install) |
- ✅ IPC skeleton awaiting implementation
- ✅ Comprehensive migration plan documented **Only Remaining:** Test with actual KiCAD 9.0 installation!
**Week 2 kickoff tasks:** ---
1. Install `kicad-python` package
2. Test IPC connection to running KiCAD ## 🚀 Ready for Week 2
3. Begin porting `project.py` module
4. Create side-by-side tests (SWIG vs IPC) ### IPC API Migration Prep ✅
--- Everything is in place to begin migration:
## 💡 Key Insights from Session 2 - ✅ Abstraction layer architecture defined
- ✅ Base classes and interfaces ready
### 1. **Installation Automation** - ✅ Factory pattern for backend selection
The bash script reduces setup time from 30+ minutes to < 10 minutes with zero manual intervention. - ✅ SWIG wrapper for backward compatibility
- ✅ IPC skeleton awaiting implementation
### 2. **Pre-Commit Hooks** - ✅ Comprehensive migration plan documented
Automatic code quality checks prevent bugs before they're committed. This will save hours in code review.
**Week 2 kickoff tasks:**
### 3. **Abstraction Pattern**
The backend abstraction is elegant - allows gradual migration without breaking existing functionality. Users won't notice the transition. 1. Install `kicad-python` package
2. Test IPC connection to running KiCAD
### 4. **Documentation Quality** 3. Begin porting `project.py` module
The IPC migration plan is thorough enough that another developer could execute it independently. 4. Create side-by-side tests (SWIG vs IPC)
--- ---
## 🧪 Testing Readiness ## 💡 Key Insights from Session 2
### When KiCAD is Installed ### 1. **Installation Automation**
You can immediately test: The bash script reduces setup time from 30+ minutes to < 10 minutes with zero manual intervention.
**1. Platform Helper:** ### 2. **Pre-Commit Hooks**
```bash
python3 python/utils/platform_helper.py Automatic code quality checks prevent bugs before they're committed. This will save hours in code review.
```
### 3. **Abstraction Pattern**
**2. Backend Detection:**
```bash The backend abstraction is elegant - allows gradual migration without breaking existing functionality. Users won't notice the transition.
python3 python/kicad_api/factory.py
``` ### 4. **Documentation Quality**
**3. Installation Script:** The IPC migration plan is thorough enough that another developer could execute it independently.
```bash
./scripts/install-linux.sh ---
```
## 🧪 Testing Readiness
**4. Pytest Suite:**
```bash ### When KiCAD is Installed
pytest tests/ -v
``` You can immediately test:
**5. Pre-commit Hooks:** **1. Platform Helper:**
```bash
pre-commit run --all-files ```bash
``` python3 python/utils/platform_helper.py
```
---
**2. Backend Detection:**
## 📈 Impact Assessment
```bash
### Developer Onboarding python3 python/kicad_api/factory.py
- **Before:** 2-3 hours setup, Windows-only, manual steps ```
- **After:** 10 minutes automated, cross-platform, one script
**3. Installation Script:**
### Code Quality
- **Before:** No automated checks, inconsistent style ```bash
- **After:** Pre-commit hooks, 100% type hints, Black formatting ./scripts/install-linux.sh
```
### Future-Proofing
- **Before:** Deprecated SWIG API, no migration path **4. Pytest Suite:**
- **After:** IPC API ready, abstraction layer in place
```bash
### Documentation pytest tests/ -v
- **Before:** README only, Windows-focused ```
- **After:** 8 comprehensive docs, Linux-primary, migration guides
**5. Pre-commit Hooks:**
---
```bash
## 🎯 Next Actions pre-commit run --all-files
```
### Immediate (Tonight/Tomorrow)
1. Install KiCAD 9.0 on your system ---
2. Run `./scripts/install-linux.sh`
3. Test backend detection ## 📈 Impact Assessment
4. Verify pytest suite passes
### Developer Onboarding
### Week 2 Start (Monday)
1. Install `kicad-python` package - **Before:** 2-3 hours setup, Windows-only, manual steps
2. Test IPC connection - **After:** 10 minutes automated, cross-platform, one script
3. Begin project.py migration
4. Create first IPC API tests ### Code Quality
--- - **Before:** No automated checks, inconsistent style
- **After:** Pre-commit hooks, 100% type hints, Black formatting
## 🏆 Session 2 Achievements
### Future-Proofing
### Infrastructure
- Automated Linux installation - **Before:** Deprecated SWIG API, no migration path
- Pre-commit hooks for code quality - **After:** IPC API ready, abstraction layer in place
- Enhanced npm scripts
- IPC API abstraction layer (800+ lines) ### Documentation
### Documentation - **Before:** README only, Windows-focused
- Updated README (Linux-primary) - **After:** 8 comprehensive docs, Linux-primary, migration guides
- 30-page IPC migration plan
- Session summaries ---
### Architecture ## 🎯 Next Actions
- Backend abstraction pattern
- Factory with auto-detection ### Immediate (Tonight/Tomorrow)
- SWIG backward compatibility
- IPC skeleton ready for implementation 1. Install KiCAD 9.0 on your system
2. Run `./scripts/install-linux.sh`
--- 3. Test backend detection
4. Verify pytest suite passes
## 🎉 Overall Day Summary
### Week 2 Start (Monday)
**Sessions 1+2 Combined:**
- **Time:** ~4-5 hours total 1. Install `kicad-python` package
- 📝 **Files:** 27 created 2. Test IPC connection
- 💻 **Code:** ~3,000+ lines 3. Begin project.py migration
- 📚 **Docs:** 8 comprehensive pages 4. Create first IPC API tests
- 🧪 **Tests:** 20+ unit tests
- **Week 1:** 95% complete ---
**Status:** 🟢 **AHEAD OF SCHEDULE** ## 🏆 Session 2 Achievements
--- ### Infrastructure
## 🚀 Momentum Check - Automated Linux installation
- Pre-commit hooks for code quality
**Energy Level:** 🔋🔋🔋🔋🔋 (Maximum) - Enhanced npm scripts
**Code Quality:** ⭐⭐⭐⭐⭐ (Excellent) - IPC API abstraction layer (800+ lines)
**Documentation:** 📖📖📖📖📖 (Comprehensive)
**Architecture:** 🏗🏗🏗🏗🏗 (Solid) ### Documentation
**Ready for Week 2 IPC Migration:** YES! - Updated README (Linux-primary)
- 30-page IPC migration plan
--- - Session summaries
**End of Session 2** ### Architecture
**Next:** KiCAD installation + testing + Week 2 kickoff
- Backend abstraction pattern
Let's keep this incredible momentum going! 🎉🚀 - Factory with auto-detection
- SWIG backward compatibility
- IPC skeleton ready for implementation
---
## 🎉 Overall Day Summary
**Sessions 1+2 Combined:**
- **Time:** ~4-5 hours total
- 📝 **Files:** 27 created
- 💻 **Code:** ~3,000+ lines
- 📚 **Docs:** 8 comprehensive pages
- 🧪 **Tests:** 20+ unit tests
- **Week 1:** 95% complete
**Status:** 🟢 **AHEAD OF SCHEDULE**
---
## 🚀 Momentum Check
**Energy Level:** 🔋🔋🔋🔋🔋 (Maximum)
**Code Quality:** ⭐⭐⭐⭐⭐ (Excellent)
**Documentation:** 📖📖📖📖📖 (Comprehensive)
**Architecture:** 🏗🏗🏗🏗🏗 (Solid)
**Ready for Week 2 IPC Migration:** YES!
---
**End of Session 2**
**Next:** KiCAD installation + testing + Week 2 kickoff
Let's keep this incredible momentum going! 🎉🚀

View File

@@ -108,25 +108,25 @@ export const toolCategories: ToolCategory[] = [
properties: { properties: {
output_dir: { output_dir: {
type: "string", type: "string",
description: "Output directory path" description: "Output directory path",
}, },
layers: { layers: {
type: "array", type: "array",
items: { type: "string" }, items: { type: "string" },
description: "Layers to export (default: all copper + silkscreen + mask)" description: "Layers to export (default: all copper + silkscreen + mask)",
}, },
format: { format: {
type: "string", type: "string",
enum: ["rs274x", "x2"], enum: ["rs274x", "x2"],
description: "Gerber format version" description: "Gerber format version",
} },
}, },
required: ["output_dir"] required: ["output_dir"],
}, },
handler: async (params) => { handler: async (params) => {
// Your implementation // Your implementation
return { success: true, files: ["..."] }; return { success: true, files: ["..."] };
} },
}, },
{ {
name: "export_drill", name: "export_drill",
@@ -135,24 +135,31 @@ export const toolCategories: ToolCategory[] = [
type: "object", type: "object",
properties: { properties: {
output_dir: { type: "string" }, 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", name: "export_bom",
description: "Export bill of materials as CSV or XML", description: "Export bill of materials as CSV or XML",
inputSchema: { /* ... */ }, inputSchema: {
handler: async (params) => { /* ... */ } /* ... */
},
handler: async (params) => {
/* ... */
},
}, },
// ... more export tools // ... more export tools
] ],
}, },
{ {
name: "drc", name: "drc",
description: "Design rule checking: clearance validation, electrical rules, manufacturing constraints", description:
"Design rule checking: clearance validation, electrical rules, manufacturing constraints",
tools: [ tools: [
{ {
name: "run_drc", name: "run_drc",
@@ -162,17 +169,21 @@ export const toolCategories: ToolCategory[] = [
properties: { properties: {
report_all: { report_all: {
type: "boolean", 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", name: "get_drc_errors",
description: "Get current DRC violations without re-running check", description: "Get current DRC violations without re-running check",
inputSchema: { type: "object", properties: {} }, inputSchema: { type: "object", properties: {} },
handler: async (params) => { /* ... */ } handler: async (params) => {
/* ... */
},
}, },
{ {
name: "set_design_rules", name: "set_design_rules",
@@ -183,12 +194,14 @@ export const toolCategories: ToolCategory[] = [
min_clearance: { type: "number", description: "Minimum clearance in mm" }, min_clearance: { type: "number", description: "Minimum clearance in mm" },
min_track_width: { type: "number", description: "Minimum track width 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_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", name: "zones",
@@ -208,22 +221,26 @@ export const toolCategories: ToolCategory[] = [
type: "object", type: "object",
properties: { properties: {
x: { type: "number" }, x: { type: "number" },
y: { type: "number" } y: { type: "number" },
} },
}, },
description: "Polygon vertices in mm" description: "Polygon vertices in mm",
}, },
priority: { type: "number", description: "Fill priority (higher fills first)" } priority: { type: "number", description: "Fill priority (higher fills first)" },
}, },
required: ["net_name", "layer", "points"] required: ["net_name", "layer", "points"],
},
handler: async (params) => {
/* ... */
}, },
handler: async (params) => { /* ... */ }
}, },
{ {
name: "fill_zones", name: "fill_zones",
description: "Recalculate and fill all copper zones", description: "Recalculate and fill all copper zones",
inputSchema: { type: "object", properties: {} }, inputSchema: { type: "object", properties: {} },
handler: async (params) => { /* ... */ } handler: async (params) => {
/* ... */
},
}, },
{ {
name: "remove_zone", name: "remove_zone",
@@ -231,13 +248,15 @@ export const toolCategories: ToolCategory[] = [
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: { 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... // Add more categories...
]; ];
@@ -293,7 +312,7 @@ export function searchTools(query: string): Array<{
matches.push({ matches.push({
category: category.name, category: category.name,
tool: tool.name, tool: tool.name,
description: tool.description description: tool.description,
}); });
} }
} }
@@ -313,12 +332,7 @@ These are the tools that enable discovery and execution.
```typescript ```typescript
// src/tools/router.ts // src/tools/router.ts
import { import { getAllCategories, getCategory, getTool, searchTools } from "./registry.js";
getAllCategories,
getCategory,
getTool,
searchTools
} from "./registry.js";
export const routerTools = { export const routerTools = {
list_tool_categories: { list_tool_categories: {
@@ -329,20 +343,20 @@ export const routerTools = {
inputSchema: { inputSchema: {
type: "object" as const, type: "object" as const,
properties: {}, properties: {},
required: [] required: [],
}, },
handler: async () => { handler: async () => {
const categories = getAllCategories(); const categories = getAllCategories();
return { return {
total_categories: categories.length, total_categories: categories.length,
total_tools: categories.reduce((sum, c) => sum + c.tools.length, 0), total_tools: categories.reduce((sum, c) => sum + c.tools.length, 0),
categories: categories.map(c => ({ categories: categories.map((c) => ({
name: c.name, name: c.name,
description: c.description, description: c.description,
tool_count: c.tools.length tool_count: c.tools.length,
})) })),
}; };
} },
}, },
get_category_tools: { get_category_tools: {
@@ -356,29 +370,29 @@ export const routerTools = {
properties: { properties: {
category: { category: {
type: "string", 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 }) => { handler: async (params: { category: string }) => {
const category = getCategory(params.category); const category = getCategory(params.category);
if (!category) { if (!category) {
return { return {
error: `Unknown category: ${params.category}`, error: `Unknown category: ${params.category}`,
available_categories: getAllCategories().map(c => c.name) available_categories: getAllCategories().map((c) => c.name),
}; };
} }
return { return {
category: category.name, category: category.name,
description: category.description, description: category.description,
tools: category.tools.map(t => ({ tools: category.tools.map((t) => ({
name: t.name, name: t.name,
description: t.description, description: t.description,
parameters: t.inputSchema parameters: t.inputSchema,
})) })),
}; };
} },
}, },
execute_tool: { execute_tool: {
@@ -391,21 +405,21 @@ export const routerTools = {
properties: { properties: {
tool_name: { tool_name: {
type: "string", type: "string",
description: "Tool name (from get_category_tools)" description: "Tool name (from get_category_tools)",
}, },
params: { params: {
type: "object", 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> }) => { handler: async (input: { tool_name: string; params?: Record<string, unknown> }) => {
const entry = getTool(input.tool_name); const entry = getTool(input.tool_name);
if (!entry) { if (!entry) {
return { return {
error: `Unknown tool: ${input.tool_name}`, 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 { return {
tool: input.tool_name, tool: input.tool_name,
category: entry.category, category: entry.category,
result result,
}; };
} catch (err) { } catch (err) {
return { return {
error: `Tool execution failed: ${(err as Error).message}`, error: `Tool execution failed: ${(err as Error).message}`,
tool: input.tool_name, tool: input.tool_name,
category: entry.category category: entry.category,
}; };
} }
} },
}, },
search_tools: { search_tools: {
@@ -436,20 +450,20 @@ export const routerTools = {
properties: { properties: {
query: { query: {
type: "string", 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 }) => { handler: async (params: { query: string }) => {
const matches = searchTools(params.query); const matches = searchTools(params.query);
return { return {
query: params.query, query: params.query,
count: matches.length, 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: { template: {
type: "string", type: "string",
description: "Optional template to use", description: "Optional template to use",
enum: ["blank", "arduino", "raspberry-pi"] enum: ["blank", "arduino", "raspberry-pi"],
} },
}, },
required: ["name", "path"] required: ["name", "path"],
}, },
handler: async (params) => { handler: async (params) => {
// Implementation // Implementation
return { success: true, project_path: `${params.path}/${params.name}` }; return { success: true, project_path: `${params.path}/${params.name}` };
} },
}, },
{ {
name: "open_project", name: "open_project",
@@ -494,29 +508,35 @@ export const directTools: ToolDefinition[] = [
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: { 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", name: "save_project",
description: "Save all project files", description: "Save all project files",
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: {} properties: {},
},
handler: async (params) => {
/* ... */
}, },
handler: async (params) => { /* ... */ }
}, },
{ {
name: "get_project_info", name: "get_project_info",
description: "Get current project information: path, files, status", description: "Get current project information: path, files, status",
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: {} properties: {},
},
handler: async (params) => {
/* ... */
}, },
handler: async (params) => { /* ... */ }
}, },
// === PRIMARY OPERATIONS (your core workflow) === // === PRIMARY OPERATIONS (your core workflow) ===
@@ -530,11 +550,13 @@ export const directTools: ToolDefinition[] = [
reference: { type: "string", description: "Reference designator (e.g., R1, U1)" }, reference: { type: "string", description: "Reference designator (e.g., R1, U1)" },
x: { type: "number", description: "X position" }, x: { type: "number", description: "X position" },
y: { type: "number", description: "Y 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", name: "move_component",
@@ -544,11 +566,13 @@ export const directTools: ToolDefinition[] = [
properties: { properties: {
reference: { type: "string", description: "Component reference (e.g., R1)" }, reference: { type: "string", description: "Component reference (e.g., R1)" },
x: { type: "number", description: "New X position" }, 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", name: "list_components",
@@ -556,10 +580,12 @@ export const directTools: ToolDefinition[] = [
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: { 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) === // === SECONDARY OPERATIONS (still common) ===
@@ -571,36 +597,42 @@ export const directTools: ToolDefinition[] = [
properties: { properties: {
start: { start: {
type: "object", type: "object",
properties: { x: { type: "number" }, y: { type: "number" } } properties: { x: { type: "number" }, y: { type: "number" } },
}, },
end: { end: {
type: "object", 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", name: "list_nets",
description: "List all nets/connections", description: "List all nets/connections",
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: {} properties: {},
},
handler: async (params) => {
/* ... */
}, },
handler: async (params) => { /* ... */ }
}, },
{ {
name: "get_info", name: "get_info",
description: "Get general information about current state", description: "Get general information about current state",
inputSchema: { inputSchema: {
type: "object", 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 { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { directTools } from "./tools/direct.js"; import { directTools } from "./tools/direct.js";
import { routerTools } from "./tools/router.js"; import { routerTools } from "./tools/router.js";
@@ -634,14 +663,11 @@ const server = new Server(
capabilities: { capabilities: {
tools: {}, tools: {},
}, },
} },
); );
// Combine all visible tools // Combine all visible tools
const allVisibleTools = [ const allVisibleTools = [...directTools, ...Object.values(routerTools)];
...directTools,
...Object.values(routerTools)
];
// Build a handler map for quick lookup // Build a handler map for quick lookup
const toolHandlers = new Map<string, (params: any) => Promise<any>>(); 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 // List tools handler - returns only direct + router tools
server.setRequestHandler(ListToolsRequestSchema, async () => { server.setRequestHandler(ListToolsRequestSchema, async () => {
return { return {
tools: allVisibleTools.map(tool => ({ tools: allVisibleTools.map((tool) => ({
name: tool.name, name: tool.name,
description: tool.description, description: tool.description,
inputSchema: tool.inputSchema, inputSchema: tool.inputSchema,
@@ -676,9 +702,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
type: "text", type: "text",
text: JSON.stringify({ text: JSON.stringify({
error: `Unknown tool: ${name}`, 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, isError: true,
}; };
@@ -690,8 +716,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
content: [ content: [
{ {
type: "text", type: "text",
text: JSON.stringify(result, null, 2) text: JSON.stringify(result, null, 2),
} },
], ],
}; };
} catch (error) { } catch (error) {
@@ -700,9 +726,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
{ {
type: "text", type: "text",
text: JSON.stringify({ text: JSON.stringify({
error: `Tool execution failed: ${(error as Error).message}` error: `Tool execution failed: ${(error as Error).message}`,
}) }),
} },
], ],
isError: true, isError: true,
}; };
@@ -734,12 +760,12 @@ Include tools that are:
**Examples by domain:** **Examples by domain:**
| Domain | Direct Tools | | Domain | Direct Tools |
|--------|-------------| | ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| **KiCAD** | create_project, open_project, save_project, add_component, move_component, add_track, list_components, list_nets, get_board_info | | **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 | | **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 | | **Git** | status, add, commit, push, pull, checkout, branch, log |
| **Database** | connect, query, list_tables, describe_table | | **Database** | connect, query, list_tables, describe_table |
### Routed Tools (Hidden Behind Router) ### Routed Tools (Hidden Behind Router)
@@ -753,13 +779,13 @@ Include tools that are:
**Examples:** **Examples:**
| Category | Why Route It | | Category | Why Route It |
|----------|-------------| | ----------------- | ---------------------------- |
| `export` | Only used at end of workflow | | `export` | Only used at end of workflow |
| `drc/validation` | Used during review phase | | `drc/validation` | Used during review phase |
| `advanced_*` | Specialty operations | | `advanced_*` | Specialty operations |
| `bulk_*` | Batch operations | | `bulk_*` | Batch operations |
| `config/settings` | One-time setup | | `config/settings` | One-time setup |
--- ---
@@ -832,18 +858,18 @@ For an IDA Pro MCP server with 100+ tools:
```typescript ```typescript
const directTools = [ const directTools = [
"open_database", // Load IDB "open_database", // Load IDB
"save_database", // Save IDB "save_database", // Save IDB
"get_function", // Get function by address/name "get_function", // Get function by address/name
"list_functions", // List all functions "list_functions", // List all functions
"decompile", // Decompile function (Hex-Rays) "decompile", // Decompile function (Hex-Rays)
"add_comment", // Add comment at address "add_comment", // Add comment at address
"rename", // Rename address/function "rename", // Rename address/function
"get_xrefs_to", // Get cross-references to address "get_xrefs_to", // Get cross-references to address
"get_xrefs_from", // Get cross-references from address "get_xrefs_from", // Get cross-references from address
"get_strings", // List strings "get_strings", // List strings
"search_bytes", // Search for byte pattern "search_bytes", // Search for byte pattern
"get_segments", // List segments "get_segments", // List segments
]; ];
``` ```
@@ -854,52 +880,52 @@ const categories = [
{ {
name: "disassembly", name: "disassembly",
description: "Disassembly operations: undefine, make code/data, change types", 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", name: "functions",
description: "Function management: create, delete, modify boundaries, set types", 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", name: "types",
description: "Type system: structs, enums, typedefs, parse headers", 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", name: "patching",
description: "Binary patching: modify bytes, assemble, apply patches", 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", name: "scripting",
description: "IDAPython scripting: run scripts, evaluate expressions", 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", name: "signatures",
description: "Signatures and patterns: FLIRT, Lumina, create 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", name: "debugging",
description: "Debugger control: breakpoints, stepping, memory", 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", name: "export",
description: "Export: ASM listing, pseudocode, database info, reports", 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", name: "import",
description: "Import: symbols, types, comments from external sources", 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", name: "analysis",
description: "Analysis control: reanalyze, find patterns, auto-analysis settings", 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 // tests/router.test.ts
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import { import { searchTools, getCategory, getTool, getAllCategories } from "../src/tools/registry.js";
searchTools,
getCategory,
getTool,
getAllCategories
} from "../src/tools/registry.js";
import { routerTools } from "../src/tools/router.js"; import { routerTools } from "../src/tools/router.js";
describe("Tool Registry", () => { describe("Tool Registry", () => {
it("should find tools by keyword", () => { it("should find tools by keyword", () => {
const results = searchTools("export"); const results = searchTools("export");
expect(results.length).toBeGreaterThan(0); 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", () => { it("should return category info", () => {
@@ -998,7 +1019,7 @@ describe("Router Tools", () => {
it("get_category_tools returns tools for valid category", async () => { it("get_category_tools returns tools for valid category", async () => {
const result = await routerTools.get_category_tools.handler({ const result = await routerTools.get_category_tools.handler({
category: "export" category: "export",
}); });
expect(result.tools).toBeDefined(); expect(result.tools).toBeDefined();
expect(result.tools.length).toBeGreaterThan(0); expect(result.tools.length).toBeGreaterThan(0);
@@ -1006,7 +1027,7 @@ describe("Router Tools", () => {
it("get_category_tools returns error for invalid category", async () => { it("get_category_tools returns error for invalid category", async () => {
const result = await routerTools.get_category_tools.handler({ const result = await routerTools.get_category_tools.handler({
category: "nonexistent" category: "nonexistent",
}); });
expect(result.error).toBeDefined(); expect(result.error).toBeDefined();
}); });
@@ -1014,7 +1035,7 @@ describe("Router Tools", () => {
it("execute_tool runs valid tool", async () => { it("execute_tool runs valid tool", async () => {
const result = await routerTools.execute_tool.handler({ const result = await routerTools.execute_tool.handler({
tool_name: "export_gerber", tool_name: "export_gerber",
params: { output_dir: "/tmp/test" } params: { output_dir: "/tmp/test" },
}); });
expect(result.error).toBeUndefined(); expect(result.error).toBeUndefined();
}); });
@@ -1022,7 +1043,7 @@ describe("Router Tools", () => {
it("execute_tool returns error for invalid tool", async () => { it("execute_tool returns error for invalid tool", async () => {
const result = await routerTools.execute_tool.handler({ const result = await routerTools.execute_tool.handler({
tool_name: "nonexistent_tool", tool_name: "nonexistent_tool",
params: {} params: {},
}); });
expect(result.error).toBeDefined(); expect(result.error).toBeDefined();
}); });
@@ -1141,11 +1162,11 @@ Before shipping your router-based MCP server:
## Summary ## Summary
| Before | After | | Before | After |
|--------|-------| | ------------------------ | --------------------- |
| 100 tools visible | 15-18 tools visible | | 100 tools visible | 15-18 tools visible |
| ~60K+ tokens consumed | ~10K tokens consumed | | ~60K+ tokens consumed | ~10K tokens consumed |
| Tool selection confusion | Clear categories | | Tool selection confusion | Clear categories |
| Context starvation | Room for conversation | | Context starvation | Room for conversation |
The router pattern gives you unlimited tool capacity while keeping Claude focused and efficient. The router pattern gives you unlimited tool capacity while keeping Claude focused and efficient.

17
package-lock.json generated
View File

@@ -20,6 +20,7 @@
"@types/glob": "^8.1.0", "@types/glob": "^8.1.0",
"@types/node": "^20.19.0", "@types/node": "^20.19.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"prettier": "^3.8.1",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
}, },
@@ -1150,6 +1151,22 @@
"node": ">=16.20.0" "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": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",

View File

@@ -1,49 +1,50 @@
{ {
"name": "kicad-mcp", "name": "kicad-mcp",
"version": "2.1.0-alpha", "version": "2.1.0-alpha",
"description": "AI-assisted PCB design with KiCAD via Model Context Protocol", "description": "AI-assisted PCB design with KiCAD via Model Context Protocol",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"build:watch": "tsc --watch", "build:watch": "tsc --watch",
"start": "node dist/index.js", "start": "node dist/index.js",
"dev": "npm run build:watch & nodemon dist/index.js", "dev": "npm run build:watch & nodemon dist/index.js",
"clean": "rm -rf dist", "clean": "rm -rf dist",
"rebuild": "npm run clean && npm run build", "rebuild": "npm run clean && npm run build",
"test": "npm run test:ts && npm run test:py", "test": "npm run test:ts && npm run test:py",
"test:ts": "echo 'TypeScript tests not yet configured'", "test:ts": "echo 'TypeScript tests not yet configured'",
"test:py": "pytest tests/ -v", "test:py": "pytest tests/ -v",
"test:coverage": "pytest tests/ --cov=python --cov-report=html --cov-report=term", "test:coverage": "pytest tests/ --cov=python --cov-report=html --cov-report=term",
"lint": "npm run lint:ts && npm run lint:py", "lint": "npm run lint:ts && npm run lint:py",
"lint:ts": "eslint src/ || echo 'ESLint not configured'", "lint:ts": "eslint src/ || echo 'ESLint not configured'",
"lint:py": "cd python && black . && mypy . && flake8 .", "lint:py": "cd python && black . && mypy . && flake8 .",
"format": "prettier --write 'src/**/*.ts' && black python/", "format": "prettier --write 'src/**/*.ts' && black python/",
"prepare": "npm run build", "prepare": "npm run build",
"pretest": "npm run build" "pretest": "npm run build"
}, },
"keywords": [ "keywords": [
"kicad", "kicad",
"mcp", "mcp",
"model-context-protocol", "model-context-protocol",
"pcb-design", "pcb-design",
"ai", "ai",
"claude" "claude"
], ],
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.21.0", "@modelcontextprotocol/sdk": "^1.21.0",
"dotenv": "^17.0.0", "dotenv": "^17.0.0",
"express": "^5.1.0", "express": "^5.1.0",
"zod": "^3.25.0" "zod": "^3.25.0"
}, },
"devDependencies": { "devDependencies": {
"@cfworker/json-schema": "^4.1.1", "@cfworker/json-schema": "^4.1.1",
"@types/express": "^5.0.5", "@types/express": "^5.0.5",
"@types/glob": "^8.1.0", "@types/glob": "^8.1.0",
"@types/node": "^20.19.0", "@types/node": "^20.19.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"typescript": "^5.9.3" "prettier": "^3.8.1",
} "typescript": "^5.9.3"
} }
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,231 +1,237 @@
/** /**
* Component prompts for KiCAD MCP server * Component prompts for KiCAD MCP server
* *
* These prompts guide the LLM in providing assistance with component-related tasks * These prompts guide the LLM in providing assistance with component-related tasks
* in KiCAD PCB design. * in KiCAD PCB design.
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
/** /**
* Register component prompts with the MCP server * Register component prompts with the MCP server
* *
* @param server MCP server instance * @param server MCP server instance
*/ */
export function registerComponentPrompts(server: McpServer): void { export function registerComponentPrompts(server: McpServer): void {
logger.info('Registering component prompts'); logger.info("Registering component prompts");
// ------------------------------------------------------ // ------------------------------------------------------
// Component Selection Prompt // Component Selection Prompt
// ------------------------------------------------------ // ------------------------------------------------------
server.prompt( server.prompt(
"component_selection", "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: [ messages: [
{ {
role: "user", role: "user",
content: { content: {
type: "text", type: "text",
text: `You're helping to select components for a circuit design. Given the following requirements: text: `You're helping to select components for a circuit design. Given the following requirements:
{{requirements}} {{requirements}}
Suggest appropriate components with their values, ratings, and footprints. Consider factors like: Suggest appropriate components with their values, ratings, and footprints. Consider factors like:
- Power and voltage ratings - Power and voltage ratings
- Current handling capabilities - Current handling capabilities
- Tolerance requirements - Tolerance requirements
- Physical size constraints and package types - Physical size constraints and package types
- Availability and cost considerations - Availability and cost considerations
- Thermal characteristics - Thermal characteristics
- Performance specifications - 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.`,
} },
} },
] ],
}) }),
); );
// ------------------------------------------------------ // ------------------------------------------------------
// Component Placement Strategy Prompt // Component Placement Strategy Prompt
// ------------------------------------------------------ // ------------------------------------------------------
server.prompt( server.prompt(
"component_placement_strategy", "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: [ messages: [
{ {
role: "user", role: "user",
content: { content: {
type: "text", type: "text",
text: `You're helping with component placement for a PCB layout. Here are the components to place: text: `You're helping with component placement for a PCB layout. Here are the components to place:
{{components}} {{components}}
Provide a strategy for optimal placement considering: Provide a strategy for optimal placement considering:
1. Signal Integrity: 1. Signal Integrity:
- Group related components to minimize signal path length - Group related components to minimize signal path length
- Keep sensitive signals away from noisy components - Keep sensitive signals away from noisy components
- Consider appropriate placement for bypass/decoupling capacitors - Consider appropriate placement for bypass/decoupling capacitors
2. Thermal Management: 2. Thermal Management:
- Distribute heat-generating components - Distribute heat-generating components
- Ensure adequate spacing for cooling - Ensure adequate spacing for cooling
- Placement near heat sinks or vias for thermal dissipation - Placement near heat sinks or vias for thermal dissipation
3. EMI/EMC Concerns: 3. EMI/EMC Concerns:
- Separate digital and analog sections - Separate digital and analog sections
- Consider ground plane partitioning - Consider ground plane partitioning
- Shield sensitive components - Shield sensitive components
4. Manufacturing and Assembly: 4. Manufacturing and Assembly:
- Component orientation for automated assembly - Component orientation for automated assembly
- Adequate spacing for rework - Adequate spacing for rework
- Consider component height distribution - 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.`,
} },
} },
] ],
}) }),
); );
// ------------------------------------------------------ // ------------------------------------------------------
// Component Replacement Analysis Prompt // Component Replacement Analysis Prompt
// ------------------------------------------------------ // ------------------------------------------------------
server.prompt( server.prompt(
"component_replacement_analysis", "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: [ },
{ () => ({
role: "user", messages: [
content: { {
type: "text", role: "user",
text: `You're helping to find a replacement for a component that is unavailable or needs to be updated. Here's the original component information: content: {
type: "text",
{{component_info}} text: `You're helping to find a replacement for a component that is unavailable or needs to be updated. Here's the original component information:
Consider these factors when suggesting replacements: {{component_info}}
1. Electrical Compatibility: Consider these factors when suggesting replacements:
- Match or exceed key electrical specifications
- Ensure voltage/current/power ratings are compatible 1. Electrical Compatibility:
- Consider parametric equivalents - Match or exceed key electrical specifications
- Ensure voltage/current/power ratings are compatible
2. Physical Compatibility: - Consider parametric equivalents
- Footprint compatibility or adaptation requirements
- Package differences and mounting considerations 2. Physical Compatibility:
- Size and clearance requirements - Footprint compatibility or adaptation requirements
- Package differences and mounting considerations
3. Performance Impact: - Size and clearance requirements
- How the replacement might affect circuit performance
- Potential need for circuit adjustments 3. Performance Impact:
- How the replacement might affect circuit performance
4. Availability and Cost: - Potential need for circuit adjustments
- Current market availability
- Cost comparison with original part 4. Availability and Cost:
- Lead time considerations - Current market availability
- Cost comparison with original part
Suggest suitable replacement options and explain the advantages and disadvantages of each. Include any circuit modifications that might be necessary.` - Lead time considerations
}
} Suggest suitable replacement options and explain the advantages and disadvantages of each. Include any circuit modifications that might be necessary.`,
] },
}) },
); ],
}),
// ------------------------------------------------------ );
// Component Troubleshooting Prompt
// ------------------------------------------------------ // ------------------------------------------------------
server.prompt( // Component Troubleshooting Prompt
"component_troubleshooting", // ------------------------------------------------------
{ server.prompt(
issue_description: z.string().describe("Description of the component or circuit issue being troubleshooted") "component_troubleshooting",
}, {
() => ({ issue_description: z
messages: [ .string()
{ .describe("Description of the component or circuit issue being troubleshooted"),
role: "user", },
content: { () => ({
type: "text", messages: [
text: `You're helping to troubleshoot an issue with a component or circuit section in a PCB design. Here's the issue description: {
role: "user",
{{issue_description}} content: {
type: "text",
Use the following systematic approach to diagnose the problem: text: `You're helping to troubleshoot an issue with a component or circuit section in a PCB design. Here's the issue description:
1. Component Verification: {{issue_description}}
- Check component values, footprints, and orientation
- Verify correct part numbers and specifications Use the following systematic approach to diagnose the problem:
- Examine for potential manufacturing defects
1. Component Verification:
2. Circuit Analysis: - Check component values, footprints, and orientation
- Review the schematic for design errors - Verify correct part numbers and specifications
- Check for proper connections and signal paths - Examine for potential manufacturing defects
- Verify power and ground connections
2. Circuit Analysis:
3. Layout Review: - Review the schematic for design errors
- Examine component placement and orientation - Check for proper connections and signal paths
- Check for adequate clearances - Verify power and ground connections
- Review trace routing and potential interference
3. Layout Review:
4. Environmental Factors: - Examine component placement and orientation
- Consider temperature, humidity, and other environmental impacts - Check for adequate clearances
- Check for potential EMI/RFI issues - Review trace routing and potential interference
- Review mechanical stress or vibration effects
4. Environmental Factors:
Based on the available information, suggest likely causes of the issue and recommend specific steps to diagnose and resolve the problem.` - Consider temperature, humidity, and other environmental impacts
} - 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.`,
); },
},
// ------------------------------------------------------ ],
// Component Value Calculation Prompt }),
// ------------------------------------------------------ );
server.prompt(
"component_value_calculation", // ------------------------------------------------------
{ // Component Value Calculation Prompt
circuit_requirements: z.string().describe("Description of the circuit function and performance requirements") // ------------------------------------------------------
}, server.prompt(
() => ({ "component_value_calculation",
messages: [ {
{ circuit_requirements: z
role: "user", .string()
content: { .describe("Description of the circuit function and performance requirements"),
type: "text", },
text: `You're helping to calculate appropriate component values for a specific circuit function. Here's the circuit description and requirements: () => ({
messages: [
{{circuit_requirements}} {
role: "user",
Follow these steps to determine the optimal component values: content: {
type: "text",
1. Identify the relevant circuit equations and design formulas text: `You're helping to calculate appropriate component values for a specific circuit function. Here's the circuit description and requirements:
2. Consider the design constraints and performance requirements
3. Calculate initial component values based on ideal behavior {{circuit_requirements}}
4. Adjust for real-world factors:
- Component tolerances Follow these steps to determine the optimal component values:
- Temperature coefficients
- Parasitic effects 1. Identify the relevant circuit equations and design formulas
- Available standard values 2. Consider the design constraints and performance requirements
3. Calculate initial component values based on ideal behavior
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.` 4. Adjust for real-world factors:
} - Component tolerances
} - Temperature coefficients
] - 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.`,
logger.info('Component prompts registered'); },
} },
],
}),
);
logger.info("Component prompts registered");
}

View File

@@ -1,321 +1,345 @@
/** /**
* Design prompts for KiCAD MCP server * Design prompts for KiCAD MCP server
* *
* These prompts guide the LLM in providing assistance with general PCB design tasks * These prompts guide the LLM in providing assistance with general PCB design tasks
* in KiCAD. * in KiCAD.
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
/** /**
* Register design prompts with the MCP server * Register design prompts with the MCP server
* *
* @param server MCP server instance * @param server MCP server instance
*/ */
export function registerDesignPrompts(server: McpServer): void { export function registerDesignPrompts(server: McpServer): void {
logger.info('Registering design prompts'); logger.info("Registering design prompts");
// ------------------------------------------------------ // ------------------------------------------------------
// PCB Layout Review Prompt // PCB Layout Review Prompt
// ------------------------------------------------------ // ------------------------------------------------------
server.prompt( server.prompt(
"pcb_layout_review", "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(
messages: [ "Information about the current PCB design, including board dimensions, layer stack-up, component placement, and routing details",
{ ),
role: "user", },
content: { () => ({
type: "text", messages: [
text: `You're helping to review a PCB layout for potential issues and improvements. Here's information about the current PCB design: {
role: "user",
{{pcb_design_info}} content: {
type: "text",
When reviewing the PCB layout, consider these key areas: text: `You're helping to review a PCB layout for potential issues and improvements. Here's information about the current PCB design:
1. Component Placement: {{pcb_design_info}}
- Logical grouping of related components
- Orientation for efficient routing When reviewing the PCB layout, consider these key areas:
- Thermal considerations for heat-generating components
- Mechanical constraints (mounting holes, connectors at edges) 1. Component Placement:
- Accessibility for testing and rework - Logical grouping of related components
- Orientation for efficient routing
2. Signal Integrity: - Thermal considerations for heat-generating components
- Trace lengths for critical signals - Mechanical constraints (mounting holes, connectors at edges)
- Differential pair routing quality - Accessibility for testing and rework
- Potential crosstalk issues
- Return path continuity 2. Signal Integrity:
- Decoupling capacitor placement - Trace lengths for critical signals
- Differential pair routing quality
3. Power Distribution: - Potential crosstalk issues
- Adequate copper for power rails - Return path continuity
- Power plane design and continuity - Decoupling capacitor placement
- Decoupling strategy effectiveness
- Voltage regulator thermal management 3. Power Distribution:
- Adequate copper for power rails
4. EMI/EMC Considerations: - Power plane design and continuity
- Ground plane integrity - Decoupling strategy effectiveness
- Potential antenna effects - Voltage regulator thermal management
- Shielding requirements
- Loop area minimization 4. EMI/EMC Considerations:
- Edge radiation control - Ground plane integrity
- Potential antenna effects
5. Manufacturing and Assembly: - Shielding requirements
- DFM (Design for Manufacturing) issues - Loop area minimization
- DFA (Design for Assembly) considerations - Edge radiation control
- Testability features
- Silkscreen clarity and usefulness 5. Manufacturing and Assembly:
- Solder mask considerations - DFM (Design for Manufacturing) issues
- DFA (Design for Assembly) considerations
Based on the provided information, identify potential issues and suggest specific improvements to enhance the PCB design.` - Testability features
} - Silkscreen clarity and usefulness
} - Solder mask considerations
]
}) Based on the provided information, identify potential issues and suggest specific improvements to enhance the PCB design.`,
); },
},
// ------------------------------------------------------ ],
// Layer Stack-up Planning Prompt }),
// ------------------------------------------------------ );
server.prompt(
"layer_stackup_planning", // ------------------------------------------------------
{ // Layer Stack-up Planning Prompt
design_requirements: z.string().describe("Information about the PCB design requirements, including signal types, speed/frequency, power requirements, and any special considerations") // ------------------------------------------------------
}, server.prompt(
() => ({ "layer_stackup_planning",
messages: [ {
{ design_requirements: z
role: "user", .string()
content: { .describe(
type: "text", "Information about the PCB design requirements, including signal types, speed/frequency, power requirements, and any special considerations",
text: `You're helping to plan an appropriate layer stack-up for a PCB design. Here's information about the design requirements: ),
},
{{design_requirements}} () => ({
messages: [
When planning a PCB layer stack-up, consider these important factors: {
role: "user",
1. Signal Integrity Requirements: content: {
- Controlled impedance needs type: "text",
- High-speed signal routing text: `You're helping to plan an appropriate layer stack-up for a PCB design. Here's information about the design requirements:
- EMI/EMC considerations
- Crosstalk mitigation {{design_requirements}}
2. Power Distribution Needs: When planning a PCB layer stack-up, consider these important factors:
- Current requirements for power rails
- Power integrity considerations 1. Signal Integrity Requirements:
- Decoupling effectiveness - Controlled impedance needs
- Thermal management - High-speed signal routing
- EMI/EMC considerations
3. Manufacturing Constraints: - Crosstalk mitigation
- Fabrication capabilities and limitations
- Cost considerations 2. Power Distribution Needs:
- Available materials and their properties - Current requirements for power rails
- Standard vs. specialized processes - Power integrity considerations
- Decoupling effectiveness
4. Layer Types and Arrangement: - Thermal management
- Signal layers
- Power and ground planes 3. Manufacturing Constraints:
- Mixed signal/plane layers - Fabrication capabilities and limitations
- Microstrip vs. stripline configurations - Cost considerations
- Available materials and their properties
5. Material Selection: - Standard vs. specialized processes
- Dielectric constant (Er) requirements
- Loss tangent considerations for high-speed 4. Layer Types and Arrangement:
- Thermal properties - Signal layers
- Mechanical stability - Power and ground planes
- Mixed signal/plane layers
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.` - Microstrip vs. stripline configurations
}
} 5. Material Selection:
] - Dielectric constant (Er) requirements
}) - Loss tangent considerations for high-speed
); - Thermal properties
- Mechanical stability
// ------------------------------------------------------
// Design Rule Development Prompt 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.`,
// ------------------------------------------------------ },
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") );
},
() => ({ // ------------------------------------------------------
messages: [ // Design Rule Development Prompt
{ // ------------------------------------------------------
role: "user", server.prompt(
content: { "design_rule_development",
type: "text", {
text: `You're helping to develop appropriate design rules for a PCB project. Here's information about the project requirements: project_requirements: z
.string()
{{project_requirements}} .describe(
"Information about the PCB project requirements, including technology, speed/frequency, manufacturing capabilities, and any special considerations",
When developing PCB design rules, consider these key areas: ),
},
1. Clearance Rules: () => ({
- Minimum spacing between copper features messages: [
- Different clearance requirements for different net classes {
- High-voltage clearance requirements role: "user",
- Polygon pour clearances content: {
type: "text",
2. Width Rules: text: `You're helping to develop appropriate design rules for a PCB project. Here's information about the project requirements:
- Minimum trace widths for signal nets
- Power trace width requirements based on current {{project_requirements}}
- Differential pair width and spacing
- Net class-specific width rules When developing PCB design rules, consider these key areas:
3. Via Rules: 1. Clearance Rules:
- Minimum via size and drill diameter - Minimum spacing between copper features
- Via annular ring requirements - Different clearance requirements for different net classes
- Microvias and buried/blind via specifications - High-voltage clearance requirements
- Via-in-pad rules - Polygon pour clearances
4. Manufacturing Constraints: 2. Width Rules:
- Minimum hole size - Minimum trace widths for signal nets
- Aspect ratio limitations - Power trace width requirements based on current
- Soldermask and silkscreen constraints - Differential pair width and spacing
- Edge clearances - Net class-specific width rules
5. Special Requirements: 3. Via Rules:
- Impedance control specifications - Minimum via size and drill diameter
- High-speed routing constraints - Via annular ring requirements
- Thermal relief parameters - Microvias and buried/blind via specifications
- Teardrop specifications - Via-in-pad 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.` 4. Manufacturing Constraints:
} - Minimum hole size
} - Aspect ratio limitations
] - Soldermask and silkscreen constraints
}) - Edge clearances
);
5. Special Requirements:
// ------------------------------------------------------ - Impedance control specifications
// Component Selection Guidance Prompt - High-speed routing constraints
// ------------------------------------------------------ - Thermal relief parameters
server.prompt( - Teardrop specifications
"component_selection_guidance",
{ 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.`,
circuit_requirements: z.string().describe("Information about the circuit requirements, including functionality, performance needs, operating environment, and any special considerations") },
}, },
() => ({ ],
messages: [ }),
{ );
role: "user",
content: { // ------------------------------------------------------
type: "text", // Component Selection Guidance Prompt
text: `You're helping with component selection for a PCB design. Here's information about the circuit requirements: // ------------------------------------------------------
server.prompt(
{{circuit_requirements}} "component_selection_guidance",
{
When selecting components for a PCB design, consider these important factors: circuit_requirements: z
.string()
1. Electrical Specifications: .describe(
- Voltage and current ratings "Information about the circuit requirements, including functionality, performance needs, operating environment, and any special considerations",
- Power handling capabilities ),
- Speed/frequency requirements },
- Noise and precision considerations () => ({
- Operating temperature range messages: [
{
2. Package and Footprint: role: "user",
- Space constraints on the PCB content: {
- Thermal dissipation requirements type: "text",
- Manual vs. automated assembly text: `You're helping with component selection for a PCB design. Here's information about the circuit requirements:
- Inspection and rework considerations
- Available footprint libraries {{circuit_requirements}}
3. Availability and Sourcing: When selecting components for a PCB design, consider these important factors:
- Multiple source options
- Lead time considerations 1. Electrical Specifications:
- Lifecycle status (new, mature, end-of-life) - Voltage and current ratings
- Cost considerations - Power handling capabilities
- Minimum order quantities - Speed/frequency requirements
- Noise and precision considerations
4. Reliability and Quality: - Operating temperature range
- Industrial vs. commercial vs. automotive grade
- Expected lifetime of the product 2. Package and Footprint:
- Environmental conditions - Space constraints on the PCB
- Compliance with relevant standards - Thermal dissipation requirements
- Manual vs. automated assembly
5. Special Considerations: - Inspection and rework considerations
- EMI/EMC performance - Available footprint libraries
- Thermal characteristics
- Moisture sensitivity 3. Availability and Sourcing:
- RoHS/REACH compliance - Multiple source options
- Special handling requirements - Lead time considerations
- Lifecycle status (new, mature, end-of-life)
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.` - Cost considerations
} - Minimum order quantities
}
] 4. Reliability and Quality:
}) - Industrial vs. commercial vs. automotive grade
); - Expected lifetime of the product
- Environmental conditions
// ------------------------------------------------------ - Compliance with relevant standards
// PCB Design Optimization Prompt
// ------------------------------------------------------ 5. Special Considerations:
server.prompt( - EMI/EMC performance
"pcb_design_optimization", - Thermal characteristics
{ - Moisture sensitivity
design_info: z.string().describe("Information about the current PCB design, including board dimensions, layer stack-up, component placement, and routing details"), - RoHS/REACH compliance
optimization_goals: z.string().describe("Specific goals for optimization, such as performance improvement, cost reduction, size reduction, or manufacturability enhancement") - 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.`,
messages: [ },
{ },
role: "user", ],
content: { }),
type: "text", );
text: `You're helping to optimize a PCB design. Here's information about the current design and optimization goals:
// ------------------------------------------------------
{{design_info}} // PCB Design Optimization Prompt
{{optimization_goals}} // ------------------------------------------------------
server.prompt(
When optimizing a PCB design, consider these key areas based on the stated goals: "pcb_design_optimization",
{
1. Performance Optimization: design_info: z
- Critical signal path length reduction .string()
- Impedance control improvement .describe(
- Decoupling strategy enhancement "Information about the current PCB design, including board dimensions, layer stack-up, component placement, and routing details",
- Thermal management improvement ),
- EMI/EMC reduction techniques optimization_goals: z
.string()
2. Manufacturability Optimization: .describe(
- DFM rule compliance "Specific goals for optimization, such as performance improvement, cost reduction, size reduction, or manufacturability enhancement",
- Testability improvements ),
- Assembly process simplification },
- Yield improvement opportunities () => ({
- Tolerance and variation management messages: [
{
3. Cost Optimization: role: "user",
- Board size reduction opportunities content: {
- Layer count optimization type: "text",
- Component consolidation text: `You're helping to optimize a PCB design. Here's information about the current design and optimization goals:
- Alternative component options
- Panelization efficiency {{design_info}}
{{optimization_goals}}
4. Reliability Optimization:
- Stress point identification and mitigation When optimizing a PCB design, consider these key areas based on the stated goals:
- Environmental robustness improvements
- Failure mode mitigation 1. Performance Optimization:
- Margin analysis and improvement - Critical signal path length reduction
- Redundancy considerations - Impedance control improvement
- Decoupling strategy enhancement
5. Space/Size Optimization: - Thermal management improvement
- Component placement density - EMI/EMC reduction techniques
- 3D space utilization
- Flex and rigid-flex opportunities 2. Manufacturability Optimization:
- Alternative packaging approaches - DFM rule compliance
- Connector and interface optimization - Testability improvements
- Assembly process simplification
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.` - Yield improvement opportunities
} - Tolerance and variation management
}
] 3. Cost Optimization:
}) - Board size reduction opportunities
); - Layer count optimization
- Component consolidation
logger.info('Design prompts registered'); - Alternative component options
} - Panelization efficiency
4. Reliability Optimization:
- Stress point identification and mitigation
- Environmental robustness improvements
- Failure mode mitigation
- Margin analysis and improvement
- Redundancy considerations
5. Space/Size Optimization:
- Component placement density
- 3D space utilization
- Flex and rigid-flex opportunities
- 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.`,
},
},
],
}),
);
logger.info("Design prompts registered");
}

View File

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

View File

@@ -1,288 +1,308 @@
/** /**
* Routing prompts for KiCAD MCP server * Routing prompts for KiCAD MCP server
* *
* These prompts guide the LLM in providing assistance with routing-related tasks * These prompts guide the LLM in providing assistance with routing-related tasks
* in KiCAD PCB design. * in KiCAD PCB design.
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
/** /**
* Register routing prompts with the MCP server * Register routing prompts with the MCP server
* *
* @param server MCP server instance * @param server MCP server instance
*/ */
export function registerRoutingPrompts(server: McpServer): void { export function registerRoutingPrompts(server: McpServer): void {
logger.info('Registering routing prompts'); logger.info("Registering routing prompts");
// ------------------------------------------------------ // ------------------------------------------------------
// Routing Strategy Prompt // Routing Strategy Prompt
// ------------------------------------------------------ // ------------------------------------------------------
server.prompt( server.prompt(
"routing_strategy", "routing_strategy",
{ {
board_info: z.string().describe("Information about the PCB board, including dimensions, layer stack-up, and components") board_info: z
}, .string()
() => ({ .describe(
messages: [ "Information about the PCB board, including dimensions, layer stack-up, and components",
{ ),
role: "user", },
content: { () => ({
type: "text", messages: [
text: `You're helping to develop a routing strategy for a PCB design. Here's information about the board: {
role: "user",
{{board_info}} content: {
type: "text",
Consider the following aspects when developing your routing strategy: text: `You're helping to develop a routing strategy for a PCB design. Here's information about the board:
1. Signal Integrity: {{board_info}}
- Group related signals and keep them close
- Minimize trace length for high-speed signals Consider the following aspects when developing your routing strategy:
- Consider differential pair routing for appropriate signals
- Avoid right-angle bends in traces 1. Signal Integrity:
- Group related signals and keep them close
2. Power Distribution: - Minimize trace length for high-speed signals
- Use appropriate trace widths for power and ground - Consider differential pair routing for appropriate signals
- Consider using power planes for better distribution - Avoid right-angle bends in traces
- Place decoupling capacitors close to ICs
2. Power Distribution:
3. EMI/EMC Considerations: - Use appropriate trace widths for power and ground
- Keep digital and analog sections separated - Consider using power planes for better distribution
- Consider ground plane partitioning - Place decoupling capacitors close to ICs
- Minimize loop areas for sensitive signals
3. EMI/EMC Considerations:
4. Manufacturing Constraints: - Keep digital and analog sections separated
- Adhere to minimum trace width and spacing requirements - Consider ground plane partitioning
- Consider via size and placement restrictions - Minimize loop areas for sensitive signals
- Account for soldermask and silkscreen limitations
4. Manufacturing Constraints:
5. Layer Stack-up Utilization: - Adhere to minimum trace width and spacing requirements
- Determine which signals go on which layers - Consider via size and placement restrictions
- Plan for layer transitions (vias) - Account for soldermask and silkscreen limitations
- Consider impedance control requirements
5. Layer Stack-up Utilization:
Provide a comprehensive routing strategy that addresses these aspects, with specific recommendations for this particular board design.` - Determine which signals go on which layers
} - 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.`,
); },
},
// ------------------------------------------------------ ],
// Differential Pair Routing Prompt }),
// ------------------------------------------------------ );
server.prompt(
"differential_pair_routing", // ------------------------------------------------------
{ // Differential Pair Routing Prompt
differential_pairs: z.string().describe("Information about the differential pairs to be routed, including signal names, source and destination components, and speed/frequency requirements") // ------------------------------------------------------
}, server.prompt(
() => ({ "differential_pair_routing",
messages: [ {
{ differential_pairs: z
role: "user", .string()
content: { .describe(
type: "text", "Information about the differential pairs to be routed, including signal names, source and destination components, and speed/frequency requirements",
text: `You're helping with routing differential pairs on a PCB. Here's information about the differential pairs: ),
},
{{differential_pairs}} () => ({
messages: [
When routing differential pairs, follow these best practices: {
role: "user",
1. Length Matching: content: {
- Keep both traces in each pair the same length type: "text",
- Maintain consistent spacing between the traces text: `You're helping with routing differential pairs on a PCB. Here's information about the differential pairs:
- Use serpentine routing (meanders) for length matching when necessary
{{differential_pairs}}
2. Impedance Control:
- Maintain consistent trace width and spacing to control impedance When routing differential pairs, follow these best practices:
- Consider the layer stack-up and dielectric properties
- Avoid changing layers if possible; when necessary, use symmetrical via pairs 1. Length Matching:
- Keep both traces in each pair the same length
3. Coupling and Crosstalk: - Maintain consistent spacing between the traces
- Keep differential pairs tightly coupled to each other - Use serpentine routing (meanders) for length matching when necessary
- Maintain adequate spacing between different differential pairs
- Route away from single-ended signals that could cause interference 2. Impedance Control:
- Maintain consistent trace width and spacing to control impedance
4. Reference Planes: - Consider the layer stack-up and dielectric properties
- Route over continuous reference planes - Avoid changing layers if possible; when necessary, use symmetrical via pairs
- Avoid splits in reference planes under differential pairs
- Consider the return path for the signals 3. Coupling and Crosstalk:
- Keep differential pairs tightly coupled to each other
5. Termination: - Maintain adequate spacing between different differential pairs
- Plan for proper termination at the ends of the pairs - Route away from single-ended signals that could cause interference
- Consider the need for series or parallel termination resistors
- Place termination components close to the endpoints 4. Reference Planes:
- Route over continuous reference planes
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.` - Avoid splits in reference planes under differential pairs
} - Consider the return path for the signals
}
] 5. Termination:
}) - Plan for proper termination at the ends of the pairs
); - Consider the need for series or parallel termination resistors
- Place termination components close to the endpoints
// ------------------------------------------------------
// High-Speed Routing Prompt 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.`,
// ------------------------------------------------------ },
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") );
},
() => ({ // ------------------------------------------------------
messages: [ // High-Speed Routing Prompt
{ // ------------------------------------------------------
role: "user", server.prompt(
content: { "high_speed_routing",
type: "text", {
text: `You're helping with routing high-speed signals on a PCB. Here's information about the high-speed signals: high_speed_signals: z
.string()
{{high_speed_signals}} .describe(
"Information about the high-speed signals to be routed, including signal names, source and destination components, and speed/frequency requirements",
When routing high-speed signals, consider these critical factors: ),
},
1. Impedance Control: () => ({
- Maintain consistent trace width to control impedance messages: [
- Use controlled impedance calculations based on layer stack-up {
- Consider microstrip vs. stripline routing depending on signal requirements role: "user",
content: {
2. Signal Integrity: type: "text",
- Minimize trace length to reduce propagation delay text: `You're helping with routing high-speed signals on a PCB. Here's information about the high-speed signals:
- Avoid sharp corners (use 45° angles or curves)
- Minimize vias to reduce discontinuities {{high_speed_signals}}
- Consider using teardrops at pad connections
When routing high-speed signals, consider these critical factors:
3. Crosstalk Mitigation:
- Maintain adequate spacing between high-speed traces 1. Impedance Control:
- Use ground traces or planes for isolation - Maintain consistent trace width to control impedance
- Cross traces at 90° when traces must cross on adjacent layers - Use controlled impedance calculations based on layer stack-up
- Consider microstrip vs. stripline routing depending on signal requirements
4. Return Path Management:
- Ensure continuous return path under the signal 2. Signal Integrity:
- Avoid reference plane splits under high-speed signals - Minimize trace length to reduce propagation delay
- Use ground vias near signal vias for return path continuity - Avoid sharp corners (use 45° angles or curves)
- Minimize vias to reduce discontinuities
5. Termination and Loading: - Consider using teardrops at pad connections
- Plan for proper termination (series, parallel, AC, etc.)
- Consider transmission line effects 3. Crosstalk Mitigation:
- Account for capacitive loading from components and vias - Maintain adequate spacing between high-speed traces
- Use ground traces or planes for isolation
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.` - Cross traces at 90° when traces must cross on adjacent layers
}
} 4. Return Path Management:
] - Ensure continuous return path under the signal
}) - Avoid reference plane splits under high-speed signals
); - Use ground vias near signal vias for return path continuity
// ------------------------------------------------------ 5. Termination and Loading:
// Power Distribution Prompt - Plan for proper termination (series, parallel, AC, etc.)
// ------------------------------------------------------ - Consider transmission line effects
server.prompt( - Account for capacitive loading from components and vias
"power_distribution",
{ 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.`,
power_requirements: z.string().describe("Information about the power requirements, including voltage rails, current needs, and components requiring power") },
}, },
() => ({ ],
messages: [ }),
{ );
role: "user",
content: { // ------------------------------------------------------
type: "text", // Power Distribution Prompt
text: `You're helping with designing the power distribution network for a PCB. Here's information about the power requirements: // ------------------------------------------------------
server.prompt(
{{power_requirements}} "power_distribution",
{
Consider these key aspects of power distribution network design: power_requirements: z
.string()
1. Power Planes vs. Traces: .describe(
- Determine when to use power planes versus wide traces "Information about the power requirements, including voltage rails, current needs, and components requiring power",
- Consider current requirements and voltage drop ),
- Plan the layer stack-up to accommodate power distribution },
() => ({
2. Decoupling Strategy: messages: [
- Place decoupling capacitors close to ICs {
- Use appropriate capacitor values and types role: "user",
- Consider high-frequency and bulk decoupling needs content: {
- Plan for power entry filtering type: "text",
text: `You're helping with designing the power distribution network for a PCB. Here's information about the power requirements:
3. Current Capacity:
- Calculate trace widths based on current requirements {{power_requirements}}
- Consider thermal issues and heat dissipation
- Plan for current return paths Consider these key aspects of power distribution network design:
4. Voltage Regulation: 1. Power Planes vs. Traces:
- Place regulators strategically - Determine when to use power planes versus wide traces
- Consider thermal management for regulators - Consider current requirements and voltage drop
- Plan feedback paths for regulators - Plan the layer stack-up to accommodate power distribution
5. EMI/EMC Considerations: 2. Decoupling Strategy:
- Minimize loop areas - Place decoupling capacitors close to ICs
- Keep power and ground planes closely coupled - Use appropriate capacitor values and types
- Consider filtering for noise-sensitive circuits - Consider high-frequency and bulk decoupling needs
- Plan for power entry filtering
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.`
} 3. Current Capacity:
} - Calculate trace widths based on current requirements
] - Consider thermal issues and heat dissipation
}) - Plan for current return paths
);
4. Voltage Regulation:
// ------------------------------------------------------ - Place regulators strategically
// Via Usage Prompt - Consider thermal management for regulators
// ------------------------------------------------------ - Plan feedback paths for regulators
server.prompt(
"via_usage", 5. EMI/EMC Considerations:
{ - Minimize loop areas
board_info: z.string().describe("Information about the PCB board, including layer count, thickness, and design requirements") - Keep power and ground planes closely coupled
}, - Consider filtering for noise-sensitive circuits
() => ({
messages: [ 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.`,
{ },
role: "user", },
content: { ],
type: "text", }),
text: `You're helping with planning via usage in a PCB design. Here's information about the board: );
{{board_info}} // ------------------------------------------------------
// Via Usage Prompt
Consider these important aspects of via usage: // ------------------------------------------------------
server.prompt(
1. Via Types: "via_usage",
- Through-hole vias (span all layers) {
- Blind vias (connect outer layer to inner layer) board_info: z
- Buried vias (connect inner layers only) .string()
- Microvias (small diameter vias for HDI designs) .describe(
"Information about the PCB board, including layer count, thickness, and design requirements",
2. Manufacturing Constraints: ),
- Minimum via diameter and drill size },
- Aspect ratio limitations (board thickness to hole diameter) () => ({
- Annular ring requirements messages: [
- Via-in-pad considerations and special processing {
role: "user",
3. Signal Integrity Impact: content: {
- Capacitive loading effects of vias type: "text",
- Impedance discontinuities text: `You're helping with planning via usage in a PCB design. Here's information about the board:
- Stub effects in through-hole vias
- Strategies to minimize via impact on high-speed signals {{board_info}}
4. Thermal Considerations: Consider these important aspects of via usage:
- Using vias for thermal relief
- Via patterns for heat dissipation 1. Via Types:
- Thermal via sizing and spacing - Through-hole vias (span all layers)
- Blind vias (connect outer layer to inner layer)
5. Design Optimization: - Buried vias (connect inner layers only)
- Via fanout strategies - Microvias (small diameter vias for HDI designs)
- Sharing vias between signals vs. dedicated vias
- Via placement to minimize trace length 2. Manufacturing Constraints:
- Tenting and plugging options - Minimum via diameter and drill size
- Aspect ratio limitations (board thickness to hole diameter)
Based on the provided information, recommend appropriate via strategies for this PCB design, including specific via types, sizes, and placement guidelines.` - Annular ring requirements
} - Via-in-pad considerations and special processing
}
] 3. Signal Integrity Impact:
}) - Capacitive loading effects of vias
); - Impedance discontinuities
- Stub effects in through-hole vias
logger.info('Routing prompts registered'); - Strategies to minimize via impact on high-speed signals
}
4. Thermal Considerations:
- Using vias for thermal relief
- Via patterns for heat dissipation
- Thermal via sizing and spacing
5. Design Optimization:
- Via fanout strategies
- Sharing vias between signals vs. dedicated vias
- 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.`,
},
},
],
}),
);
logger.info("Routing prompts registered");
}

View File

@@ -1,354 +1,372 @@
/** /**
* Board resources for KiCAD MCP server * Board resources for KiCAD MCP server
* *
* These resources provide information about the PCB board * These resources provide information about the PCB board
* to the LLM, enabling better context-aware assistance. * to the LLM, enabling better context-aware assistance.
*/ */
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
import { createJsonResponse, createBinaryResponse } from '../utils/resource-helpers.js'; import { createJsonResponse, createBinaryResponse } from "../utils/resource-helpers.js";
// Command function type for KiCAD script calls // 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 board resources with the MCP server * Register board resources with the MCP server
* *
* @param server MCP server instance * @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands * @param callKicadScript Function to call KiCAD script commands
*/ */
export function registerBoardResources(server: McpServer, callKicadScript: CommandFunction): void { export function registerBoardResources(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering board resources'); logger.info("Registering board resources");
// ------------------------------------------------------ // ------------------------------------------------------
// Board Information Resource // Board Information Resource
// ------------------------------------------------------ // ------------------------------------------------------
server.resource( server.resource("board_info", "kicad://board/info", async (uri) => {
"board_info", logger.debug("Retrieving board information");
"kicad://board/info", const result = await callKicadScript("get_board_info", {});
async (uri) => {
logger.debug('Retrieving board information'); if (!result.success) {
const result = await callKicadScript("get_board_info", {}); logger.error(`Failed to retrieve board information: ${result.errorDetails}`);
return {
if (!result.success) { contents: [
logger.error(`Failed to retrieve board information: ${result.errorDetails}`); {
return { uri: uri.href,
contents: [{ text: JSON.stringify({
uri: uri.href, error: "Failed to retrieve board information",
text: JSON.stringify({ details: result.errorDetails,
error: "Failed to retrieve board information", }),
details: result.errorDetails mimeType: "application/json",
}), },
mimeType: "application/json" ],
}] };
}; }
}
logger.debug("Successfully retrieved board information");
logger.debug('Successfully retrieved board information'); return {
return { contents: [
contents: [{ {
uri: uri.href, uri: uri.href,
text: JSON.stringify(result), text: JSON.stringify(result),
mimeType: "application/json" mimeType: "application/json",
}] },
}; ],
} };
); });
// ------------------------------------------------------ // ------------------------------------------------------
// Layer List Resource // Layer List Resource
// ------------------------------------------------------ // ------------------------------------------------------
server.resource( server.resource("layer_list", "kicad://board/layers", async (uri) => {
"layer_list", logger.debug("Retrieving layer list");
"kicad://board/layers", const result = await callKicadScript("get_layer_list", {});
async (uri) => {
logger.debug('Retrieving layer list'); if (!result.success) {
const result = await callKicadScript("get_layer_list", {}); logger.error(`Failed to retrieve layer list: ${result.errorDetails}`);
return {
if (!result.success) { contents: [
logger.error(`Failed to retrieve layer list: ${result.errorDetails}`); {
return { uri: uri.href,
contents: [{ text: JSON.stringify({
uri: uri.href, error: "Failed to retrieve layer list",
text: JSON.stringify({ details: result.errorDetails,
error: "Failed to retrieve layer list", }),
details: result.errorDetails mimeType: "application/json",
}), },
mimeType: "application/json" ],
}] };
}; }
}
logger.debug(`Successfully retrieved ${result.layers?.length || 0} layers`);
logger.debug(`Successfully retrieved ${result.layers?.length || 0} layers`); return {
return { contents: [
contents: [{ {
uri: uri.href, uri: uri.href,
text: JSON.stringify(result), text: JSON.stringify(result),
mimeType: "application/json" mimeType: "application/json",
}] },
}; ],
} };
); });
// ------------------------------------------------------ // ------------------------------------------------------
// Board Extents Resource // Board Extents Resource
// ------------------------------------------------------ // ------------------------------------------------------
server.resource( server.resource(
"board_extents", "board_extents",
new ResourceTemplate("kicad://board/extents/{unit?}", { new ResourceTemplate("kicad://board/extents/{unit?}", {
list: async () => ({ list: async () => ({
resources: [ resources: [
{ uri: "kicad://board/extents/mm", name: "Millimeters" }, { uri: "kicad://board/extents/mm", name: "Millimeters" },
{ uri: "kicad://board/extents/inch", name: "Inches" } { uri: "kicad://board/extents/inch", name: "Inches" },
] ],
}) }),
}), }),
async (uri, params) => { async (uri, params) => {
const unit = params.unit || 'mm'; const unit = params.unit || "mm";
logger.debug(`Retrieving board extents in ${unit}`); logger.debug(`Retrieving board extents in ${unit}`);
const result = await callKicadScript("get_board_extents", { unit }); const result = await callKicadScript("get_board_extents", { unit });
if (!result.success) { if (!result.success) {
logger.error(`Failed to retrieve board extents: ${result.errorDetails}`); logger.error(`Failed to retrieve board extents: ${result.errorDetails}`);
return { return {
contents: [{ contents: [
uri: uri.href, {
text: JSON.stringify({ uri: uri.href,
error: "Failed to retrieve board extents", text: JSON.stringify({
details: result.errorDetails error: "Failed to retrieve board extents",
}), details: result.errorDetails,
mimeType: "application/json" }),
}] mimeType: "application/json",
}; },
} ],
};
logger.debug('Successfully retrieved board extents'); }
return {
contents: [{ logger.debug("Successfully retrieved board extents");
uri: uri.href, return {
text: JSON.stringify(result), contents: [
mimeType: "application/json" {
}] uri: uri.href,
}; text: JSON.stringify(result),
} mimeType: "application/json",
); },
],
// ------------------------------------------------------ };
// Board 2D View Resource },
// ------------------------------------------------------ );
server.resource(
"board_2d_view", // ------------------------------------------------------
new ResourceTemplate("kicad://board/2d-view/{format?}", { // Board 2D View Resource
list: async () => ({ // ------------------------------------------------------
resources: [ server.resource(
{ uri: "kicad://board/2d-view/png", name: "PNG Format" }, "board_2d_view",
{ uri: "kicad://board/2d-view/jpg", name: "JPEG Format" }, new ResourceTemplate("kicad://board/2d-view/{format?}", {
{ uri: "kicad://board/2d-view/svg", name: "SVG Format" } list: async () => ({
] resources: [
}) { uri: "kicad://board/2d-view/png", name: "PNG Format" },
}), { uri: "kicad://board/2d-view/jpg", name: "JPEG Format" },
async (uri, params) => { { uri: "kicad://board/2d-view/svg", name: "SVG Format" },
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 async (uri, params) => {
const layers = typeof params.layers === 'string' ? params.layers.split(',') : params.layers; const format = (params.format || "png") as "png" | "jpg" | "svg";
const width = params.width ? parseInt(params.width as string) : undefined;
logger.debug('Retrieving 2D board view'); const height = params.height ? parseInt(params.height as string) : undefined;
const result = await callKicadScript("get_board_2d_view", { // Handle layers parameter - could be string or array
layers, const layers = typeof params.layers === "string" ? params.layers.split(",") : params.layers;
width,
height, logger.debug("Retrieving 2D board view");
format const result = await callKicadScript("get_board_2d_view", {
}); layers,
width,
if (!result.success) { height,
logger.error(`Failed to retrieve 2D board view: ${result.errorDetails}`); format,
return { });
contents: [{
uri: uri.href, if (!result.success) {
text: JSON.stringify({ logger.error(`Failed to retrieve 2D board view: ${result.errorDetails}`);
error: "Failed to retrieve 2D board view", return {
details: result.errorDetails contents: [
}), {
mimeType: "application/json" uri: uri.href,
}] text: JSON.stringify({
}; error: "Failed to retrieve 2D board view",
} details: result.errorDetails,
}),
logger.debug('Successfully retrieved 2D board view'); mimeType: "application/json",
},
if (format === 'svg') { ],
return { };
contents: [{ }
uri: uri.href,
text: result.imageData, logger.debug("Successfully retrieved 2D board view");
mimeType: "image/svg+xml"
}] if (format === "svg") {
}; return {
} else { contents: [
return { {
contents: [{ uri: uri.href,
uri: uri.href, text: result.imageData,
blob: result.imageData, mimeType: "image/svg+xml",
mimeType: format === "jpg" ? "image/jpeg" : "image/png" },
}] ],
}; };
} } else {
} return {
); contents: [
{
// ------------------------------------------------------ uri: uri.href,
// Board 3D View Resource blob: result.imageData,
// ------------------------------------------------------ mimeType: format === "jpg" ? "image/jpeg" : "image/png",
server.resource( },
"board_3d_view", ],
new ResourceTemplate("kicad://board/3d-view/{angle?}", { };
list: async () => ({ }
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" } // ------------------------------------------------------
] // Board 3D View Resource
}) // ------------------------------------------------------
}), server.resource(
async (uri, params) => { "board_3d_view",
const angle = params.angle || 'isometric'; new ResourceTemplate("kicad://board/3d-view/{angle?}", {
const width = params.width ? parseInt(params.width as string) : undefined; list: async () => ({
const height = params.height ? parseInt(params.height as string) : undefined; resources: [
{ uri: "kicad://board/3d-view/isometric", name: "Isometric View" },
logger.debug(`Retrieving 3D board view from ${angle} angle`); { uri: "kicad://board/3d-view/top", name: "Top View" },
const result = await callKicadScript("get_board_3d_view", { { uri: "kicad://board/3d-view/bottom", name: "Bottom View" },
width, ],
height, }),
angle }),
}); async (uri, params) => {
const angle = params.angle || "isometric";
if (!result.success) { const width = params.width ? parseInt(params.width as string) : undefined;
logger.error(`Failed to retrieve 3D board view: ${result.errorDetails}`); const height = params.height ? parseInt(params.height as string) : undefined;
return {
contents: [{ logger.debug(`Retrieving 3D board view from ${angle} angle`);
uri: uri.href, const result = await callKicadScript("get_board_3d_view", {
text: JSON.stringify({ width,
error: "Failed to retrieve 3D board view", height,
details: result.errorDetails angle,
}), });
mimeType: "application/json"
}] if (!result.success) {
}; logger.error(`Failed to retrieve 3D board view: ${result.errorDetails}`);
} return {
contents: [
logger.debug('Successfully retrieved 3D board view'); {
return { uri: uri.href,
contents: [{ text: JSON.stringify({
uri: uri.href, error: "Failed to retrieve 3D board view",
blob: result.imageData, details: result.errorDetails,
mimeType: "image/png" }),
}] mimeType: "application/json",
}; },
} ],
); };
}
// ------------------------------------------------------
// Board Statistics Resource logger.debug("Successfully retrieved 3D board view");
// ------------------------------------------------------ return {
server.resource( contents: [
"board_statistics", {
"kicad://board/statistics", uri: uri.href,
async (uri) => { blob: result.imageData,
logger.debug('Generating board statistics'); mimeType: "image/png",
},
// Get board info ],
const boardResult = await callKicadScript("get_board_info", {}); };
if (!boardResult.success) { },
logger.error(`Failed to retrieve board information: ${boardResult.errorDetails}`); );
return {
contents: [{ // ------------------------------------------------------
uri: uri.href, // Board Statistics Resource
text: JSON.stringify({ // ------------------------------------------------------
error: "Failed to generate board statistics", server.resource("board_statistics", "kicad://board/statistics", async (uri) => {
details: boardResult.errorDetails logger.debug("Generating board statistics");
}),
mimeType: "application/json" // Get board info
}] const boardResult = await callKicadScript("get_board_info", {});
}; if (!boardResult.success) {
} logger.error(`Failed to retrieve board information: ${boardResult.errorDetails}`);
return {
// Get component list contents: [
const componentsResult = await callKicadScript("get_component_list", {}); {
if (!componentsResult.success) { uri: uri.href,
logger.error(`Failed to retrieve component list: ${componentsResult.errorDetails}`); text: JSON.stringify({
return { error: "Failed to generate board statistics",
contents: [{ details: boardResult.errorDetails,
uri: uri.href, }),
text: JSON.stringify({ mimeType: "application/json",
error: "Failed to generate board statistics", },
details: componentsResult.errorDetails ],
}), };
mimeType: "application/json" }
}]
}; // Get component list
} const componentsResult = await callKicadScript("get_component_list", {});
if (!componentsResult.success) {
// Get nets list logger.error(`Failed to retrieve component list: ${componentsResult.errorDetails}`);
const netsResult = await callKicadScript("get_nets_list", {}); return {
if (!netsResult.success) { contents: [
logger.error(`Failed to retrieve nets list: ${netsResult.errorDetails}`); {
return { uri: uri.href,
contents: [{ text: JSON.stringify({
uri: uri.href, error: "Failed to generate board statistics",
text: JSON.stringify({ details: componentsResult.errorDetails,
error: "Failed to generate board statistics", }),
details: netsResult.errorDetails mimeType: "application/json",
}), },
mimeType: "application/json" ],
}] };
}; }
}
// Get nets list
// Combine all information into statistics const netsResult = await callKicadScript("get_nets_list", {});
const statistics = { if (!netsResult.success) {
board: { logger.error(`Failed to retrieve nets list: ${netsResult.errorDetails}`);
size: boardResult.size, return {
layers: boardResult.layers?.length || 0, contents: [
title: boardResult.title {
}, uri: uri.href,
components: { text: JSON.stringify({
count: componentsResult.components?.length || 0, error: "Failed to generate board statistics",
types: countComponentTypes(componentsResult.components || []) details: netsResult.errorDetails,
}, }),
nets: { mimeType: "application/json",
count: netsResult.nets?.length || 0 },
} ],
}; };
}
logger.debug('Successfully generated board statistics');
return { // Combine all information into statistics
contents: [{ const statistics = {
uri: uri.href, board: {
text: JSON.stringify(statistics), size: boardResult.size,
mimeType: "application/json" layers: boardResult.layers?.length || 0,
}] title: boardResult.title,
}; },
} components: {
); count: componentsResult.components?.length || 0,
types: countComponentTypes(componentsResult.components || []),
logger.info('Board resources registered'); },
} nets: {
count: netsResult.nets?.length || 0,
/** },
* Helper function to count component types };
*/
function countComponentTypes(components: any[]): Record<string, number> { logger.debug("Successfully generated board statistics");
const typeCounts: Record<string, number> = {}; return {
contents: [
for (const component of components) { {
const type = component.value?.split(' ')[0] || 'Unknown'; uri: uri.href,
typeCounts[type] = (typeCounts[type] || 0) + 1; text: JSON.stringify(statistics),
} mimeType: "application/json",
},
return typeCounts; ],
} };
});
logger.info("Board resources registered");
}
/**
* Helper function to count component types
*/
function countComponentTypes(components: any[]): Record<string, number> {
const typeCounts: Record<string, number> = {};
for (const component of components) {
const type = component.value?.split(" ")[0] || "Unknown";
typeCounts[type] = (typeCounts[type] || 0) + 1;
}
return typeCounts;
}

View File

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

View File

@@ -1,10 +1,10 @@
/** /**
* Resources index for KiCAD MCP server * Resources index for KiCAD MCP server
* *
* Exports all resource registration functions * Exports all resource registration functions
*/ */
export { registerProjectResources } from './project.js'; export { registerProjectResources } from "./project.js";
export { registerBoardResources } from './board.js'; export { registerBoardResources } from "./board.js";
export { registerComponentResources } from './component.js'; export { registerComponentResources } from "./component.js";
export { registerLibraryResources } from './library.js'; export { registerLibraryResources } from "./library.js";

View File

@@ -1,290 +1,323 @@
/** /**
* Library resources for KiCAD MCP server * Library resources for KiCAD MCP server
* *
* These resources provide information about KiCAD component libraries * These resources provide information about KiCAD component libraries
* to the LLM, enabling better context-aware assistance. * to the LLM, enabling better context-aware assistance.
*/ */
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
// Command function type for KiCAD script calls // 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 library resources with the MCP server * Register library resources with the MCP server
* *
* @param server MCP server instance * @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands * @param callKicadScript Function to call KiCAD script commands
*/ */
export function registerLibraryResources(server: McpServer, callKicadScript: CommandFunction): void { export function registerLibraryResources(
logger.info('Registering library resources'); server: McpServer,
callKicadScript: CommandFunction,
// ------------------------------------------------------ ): void {
// Component Library Resource logger.info("Registering library resources");
// ------------------------------------------------------
server.resource( // ------------------------------------------------------
"component_library", // Component Library Resource
new ResourceTemplate("kicad://components/{filter?}/{library?}", { // ------------------------------------------------------
list: async () => ({ server.resource(
resources: [ "component_library",
{ uri: "kicad://components", name: "All Components" } new ResourceTemplate("kicad://components/{filter?}/{library?}", {
] list: async () => ({
}) resources: [{ uri: "kicad://components", name: "All Components" }],
}), }),
async (uri, params) => { }),
const filter = params.filter || ''; async (uri, params) => {
const library = params.library || ''; const filter = params.filter || "";
const limit = Number(params.limit) || undefined; 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(
const result = await callKicadScript("get_component_library", { `Retrieving component library${filter ? ` with filter: ${filter}` : ""}${library ? ` from library: ${library}` : ""}`,
filter, );
library,
limit const result = await callKicadScript("get_component_library", {
}); filter,
library,
if (!result.success) { limit,
logger.error(`Failed to retrieve component library: ${result.errorDetails}`); });
return {
contents: [{ if (!result.success) {
uri: uri.href, logger.error(`Failed to retrieve component library: ${result.errorDetails}`);
text: JSON.stringify({ return {
error: "Failed to retrieve component library", contents: [
details: result.errorDetails {
}), uri: uri.href,
mimeType: "application/json" text: JSON.stringify({
}] error: "Failed to retrieve component library",
}; details: result.errorDetails,
} }),
mimeType: "application/json",
logger.debug(`Successfully retrieved ${result.components?.length || 0} components from library`); },
return { ],
contents: [{ };
uri: uri.href, }
text: JSON.stringify(result),
mimeType: "application/json" logger.debug(
}] `Successfully retrieved ${result.components?.length || 0} components from library`,
}; );
} return {
); contents: [
{
// ------------------------------------------------------ uri: uri.href,
// Library List Resource text: JSON.stringify(result),
// ------------------------------------------------------ mimeType: "application/json",
server.resource( },
"library_list", ],
"kicad://libraries", };
async (uri) => { },
logger.debug('Retrieving library list'); );
const result = await callKicadScript("get_library_list", {});
// ------------------------------------------------------
if (!result.success) { // Library List Resource
logger.error(`Failed to retrieve library list: ${result.errorDetails}`); // ------------------------------------------------------
return { server.resource("library_list", "kicad://libraries", async (uri) => {
contents: [{ logger.debug("Retrieving library list");
uri: uri.href, const result = await callKicadScript("get_library_list", {});
text: JSON.stringify({
error: "Failed to retrieve library list", if (!result.success) {
details: result.errorDetails logger.error(`Failed to retrieve library list: ${result.errorDetails}`);
}), return {
mimeType: "application/json" contents: [
}] {
}; uri: uri.href,
} text: JSON.stringify({
error: "Failed to retrieve library list",
logger.debug(`Successfully retrieved ${result.libraries?.length || 0} libraries`); details: result.errorDetails,
return { }),
contents: [{ mimeType: "application/json",
uri: uri.href, },
text: JSON.stringify(result), ],
mimeType: "application/json" };
}] }
};
} logger.debug(`Successfully retrieved ${result.libraries?.length || 0} libraries`);
); return {
contents: [
// ------------------------------------------------------ {
// Library Component Details Resource uri: uri.href,
// ------------------------------------------------------ text: JSON.stringify(result),
server.resource( mimeType: "application/json",
"library_component_details", },
new ResourceTemplate("kicad://library/component/{componentId}/{library?}", { ],
list: undefined };
}), });
async (uri, params) => {
const { componentId, library } = params; // ------------------------------------------------------
logger.debug(`Retrieving details for component: ${componentId}${library ? ` from library: ${library}` : ''}`); // Library Component Details Resource
// ------------------------------------------------------
const result = await callKicadScript("get_component_details", { server.resource(
componentId, "library_component_details",
library new ResourceTemplate("kicad://library/component/{componentId}/{library?}", {
}); list: undefined,
}),
if (!result.success) { async (uri, params) => {
logger.error(`Failed to retrieve component details: ${result.errorDetails}`); const { componentId, library } = params;
return { logger.debug(
contents: [{ `Retrieving details for component: ${componentId}${library ? ` from library: ${library}` : ""}`,
uri: uri.href, );
text: JSON.stringify({
error: `Failed to retrieve details for component ${componentId}`, const result = await callKicadScript("get_component_details", {
details: result.errorDetails componentId,
}), library,
mimeType: "application/json" });
}]
}; if (!result.success) {
} logger.error(`Failed to retrieve component details: ${result.errorDetails}`);
return {
logger.debug(`Successfully retrieved details for component: ${componentId}`); contents: [
return { {
contents: [{ uri: uri.href,
uri: uri.href, text: JSON.stringify({
text: JSON.stringify(result), error: `Failed to retrieve details for component ${componentId}`,
mimeType: "application/json" details: result.errorDetails,
}] }),
}; mimeType: "application/json",
} },
); ],
};
// ------------------------------------------------------ }
// Component Footprint Resource
// ------------------------------------------------------ logger.debug(`Successfully retrieved details for component: ${componentId}`);
server.resource( return {
"component_footprint", contents: [
new ResourceTemplate("kicad://footprint/{componentId}/{footprint?}", { {
list: undefined uri: uri.href,
}), text: JSON.stringify(result),
async (uri, params) => { mimeType: "application/json",
const { componentId, footprint } = params; },
logger.debug(`Retrieving footprint for component: ${componentId}${footprint ? ` (${footprint})` : ''}`); ],
};
const result = await callKicadScript("get_component_footprint", { },
componentId, );
footprint
}); // ------------------------------------------------------
// Component Footprint Resource
if (!result.success) { // ------------------------------------------------------
logger.error(`Failed to retrieve component footprint: ${result.errorDetails}`); server.resource(
return { "component_footprint",
contents: [{ new ResourceTemplate("kicad://footprint/{componentId}/{footprint?}", {
uri: uri.href, list: undefined,
text: JSON.stringify({ }),
error: `Failed to retrieve footprint for component ${componentId}`, async (uri, params) => {
details: result.errorDetails const { componentId, footprint } = params;
}), logger.debug(
mimeType: "application/json" `Retrieving footprint for component: ${componentId}${footprint ? ` (${footprint})` : ""}`,
}] );
};
} const result = await callKicadScript("get_component_footprint", {
componentId,
logger.debug(`Successfully retrieved footprint for component: ${componentId}`); footprint,
return { });
contents: [{
uri: uri.href, if (!result.success) {
text: JSON.stringify(result), logger.error(`Failed to retrieve component footprint: ${result.errorDetails}`);
mimeType: "application/json" return {
}] contents: [
}; {
} uri: uri.href,
); text: JSON.stringify({
error: `Failed to retrieve footprint for component ${componentId}`,
// ------------------------------------------------------ details: result.errorDetails,
// Component Symbol Resource }),
// ------------------------------------------------------ mimeType: "application/json",
server.resource( },
"component_symbol", ],
new ResourceTemplate("kicad://symbol/{componentId}", { };
list: undefined }
}),
async (uri, params) => { logger.debug(`Successfully retrieved footprint for component: ${componentId}`);
const { componentId } = params; return {
logger.debug(`Retrieving symbol for component: ${componentId}`); contents: [
{
const result = await callKicadScript("get_component_symbol", { uri: uri.href,
componentId text: JSON.stringify(result),
}); mimeType: "application/json",
},
if (!result.success) { ],
logger.error(`Failed to retrieve component symbol: ${result.errorDetails}`); };
return { },
contents: [{ );
uri: uri.href,
text: JSON.stringify({ // ------------------------------------------------------
error: `Failed to retrieve symbol for component ${componentId}`, // Component Symbol Resource
details: result.errorDetails // ------------------------------------------------------
}), server.resource(
mimeType: "application/json" "component_symbol",
}] new ResourceTemplate("kicad://symbol/{componentId}", {
}; list: undefined,
} }),
async (uri, params) => {
logger.debug(`Successfully retrieved symbol for component: ${componentId}`); const { componentId } = params;
logger.debug(`Retrieving symbol for component: ${componentId}`);
// If the result includes SVG data, return it as SVG
if (result.svgData) { const result = await callKicadScript("get_component_symbol", {
return { componentId,
contents: [{ });
uri: uri.href,
text: result.svgData, if (!result.success) {
mimeType: "image/svg+xml" logger.error(`Failed to retrieve component symbol: ${result.errorDetails}`);
}] return {
}; contents: [
} {
uri: uri.href,
// Otherwise return the JSON result text: JSON.stringify({
return { error: `Failed to retrieve symbol for component ${componentId}`,
contents: [{ details: result.errorDetails,
uri: uri.href, }),
text: JSON.stringify(result), mimeType: "application/json",
mimeType: "application/json" },
}] ],
}; };
} }
);
logger.debug(`Successfully retrieved symbol for component: ${componentId}`);
// ------------------------------------------------------
// Component 3D Model Resource // If the result includes SVG data, return it as SVG
// ------------------------------------------------------ if (result.svgData) {
server.resource( return {
"component_3d_model", contents: [
new ResourceTemplate("kicad://3d-model/{componentId}/{footprint?}", { {
list: undefined uri: uri.href,
}), text: result.svgData,
async (uri, params) => { mimeType: "image/svg+xml",
const { componentId, footprint } = params; },
logger.debug(`Retrieving 3D model for component: ${componentId}${footprint ? ` (${footprint})` : ''}`); ],
};
const result = await callKicadScript("get_component_3d_model", { }
componentId,
footprint // Otherwise return the JSON result
}); return {
contents: [
if (!result.success) { {
logger.error(`Failed to retrieve component 3D model: ${result.errorDetails}`); uri: uri.href,
return { text: JSON.stringify(result),
contents: [{ mimeType: "application/json",
uri: uri.href, },
text: JSON.stringify({ ],
error: `Failed to retrieve 3D model for component ${componentId}`, };
details: result.errorDetails },
}), );
mimeType: "application/json"
}] // ------------------------------------------------------
}; // Component 3D Model Resource
} // ------------------------------------------------------
server.resource(
logger.debug(`Successfully retrieved 3D model for component: ${componentId}`); "component_3d_model",
return { new ResourceTemplate("kicad://3d-model/{componentId}/{footprint?}", {
contents: [{ list: undefined,
uri: uri.href, }),
text: JSON.stringify(result), async (uri, params) => {
mimeType: "application/json" const { componentId, footprint } = params;
}] logger.debug(
}; `Retrieving 3D model for component: ${componentId}${footprint ? ` (${footprint})` : ""}`,
} );
);
const result = await callKicadScript("get_component_3d_model", {
logger.info('Library resources registered'); componentId,
} footprint,
});
if (!result.success) {
logger.error(`Failed to retrieve component 3D model: ${result.errorDetails}`);
return {
contents: [
{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve 3D model for component ${componentId}`,
details: result.errorDetails,
}),
mimeType: "application/json",
},
],
};
}
logger.debug(`Successfully retrieved 3D model for component: ${componentId}`);
return {
contents: [
{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json",
},
],
};
},
);
logger.info("Library resources registered");
}

View File

@@ -1,260 +1,267 @@
/** /**
* Project resources for KiCAD MCP server * Project resources for KiCAD MCP server
* *
* These resources provide information about the KiCAD project * These resources provide information about the KiCAD project
* to the LLM, enabling better context-aware assistance. * to the LLM, enabling better context-aware assistance.
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
// Command function type for KiCAD script calls // 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 project resources with the MCP server * Register project resources with the MCP server
* *
* @param server MCP server instance * @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands * @param callKicadScript Function to call KiCAD script commands
*/ */
export function registerProjectResources(server: McpServer, callKicadScript: CommandFunction): void { export function registerProjectResources(
logger.info('Registering project resources'); server: McpServer,
callKicadScript: CommandFunction,
// ------------------------------------------------------ ): void {
// Project Information Resource logger.info("Registering project resources");
// ------------------------------------------------------
server.resource( // ------------------------------------------------------
"project_info", // Project Information Resource
"kicad://project/info", // ------------------------------------------------------
async (uri) => { server.resource("project_info", "kicad://project/info", async (uri) => {
logger.debug('Retrieving project information'); logger.debug("Retrieving project information");
const result = await callKicadScript("get_project_info", {}); const result = await callKicadScript("get_project_info", {});
if (!result.success) { if (!result.success) {
logger.error(`Failed to retrieve project information: ${result.errorDetails}`); logger.error(`Failed to retrieve project information: ${result.errorDetails}`);
return { return {
contents: [{ contents: [
uri: uri.href, {
text: JSON.stringify({ uri: uri.href,
error: "Failed to retrieve project information", text: JSON.stringify({
details: result.errorDetails error: "Failed to retrieve project information",
}), details: result.errorDetails,
mimeType: "application/json" }),
}] mimeType: "application/json",
}; },
} ],
};
logger.debug('Successfully retrieved project information'); }
return {
contents: [{ logger.debug("Successfully retrieved project information");
uri: uri.href, return {
text: JSON.stringify(result), contents: [
mimeType: "application/json" {
}] uri: uri.href,
}; text: JSON.stringify(result),
} mimeType: "application/json",
); },
],
// ------------------------------------------------------ };
// Project Properties Resource });
// ------------------------------------------------------
server.resource( // ------------------------------------------------------
"project_properties", // Project Properties Resource
"kicad://project/properties", // ------------------------------------------------------
async (uri) => { server.resource("project_properties", "kicad://project/properties", async (uri) => {
logger.debug('Retrieving project properties'); logger.debug("Retrieving project properties");
const result = await callKicadScript("get_project_properties", {}); const result = await callKicadScript("get_project_properties", {});
if (!result.success) { if (!result.success) {
logger.error(`Failed to retrieve project properties: ${result.errorDetails}`); logger.error(`Failed to retrieve project properties: ${result.errorDetails}`);
return { return {
contents: [{ contents: [
uri: uri.href, {
text: JSON.stringify({ uri: uri.href,
error: "Failed to retrieve project properties", text: JSON.stringify({
details: result.errorDetails error: "Failed to retrieve project properties",
}), details: result.errorDetails,
mimeType: "application/json" }),
}] mimeType: "application/json",
}; },
} ],
};
logger.debug('Successfully retrieved project properties'); }
return {
contents: [{ logger.debug("Successfully retrieved project properties");
uri: uri.href, return {
text: JSON.stringify(result), contents: [
mimeType: "application/json" {
}] uri: uri.href,
}; text: JSON.stringify(result),
} mimeType: "application/json",
); },
],
// ------------------------------------------------------ };
// Project Files Resource });
// ------------------------------------------------------
server.resource( // ------------------------------------------------------
"project_files", // Project Files Resource
"kicad://project/files", // ------------------------------------------------------
async (uri) => { server.resource("project_files", "kicad://project/files", async (uri) => {
logger.debug('Retrieving project files'); logger.debug("Retrieving project files");
const result = await callKicadScript("get_project_files", {}); const result = await callKicadScript("get_project_files", {});
if (!result.success) { if (!result.success) {
logger.error(`Failed to retrieve project files: ${result.errorDetails}`); logger.error(`Failed to retrieve project files: ${result.errorDetails}`);
return { return {
contents: [{ contents: [
uri: uri.href, {
text: JSON.stringify({ uri: uri.href,
error: "Failed to retrieve project files", text: JSON.stringify({
details: result.errorDetails error: "Failed to retrieve project files",
}), details: result.errorDetails,
mimeType: "application/json" }),
}] mimeType: "application/json",
}; },
} ],
};
logger.debug(`Successfully retrieved ${result.files?.length || 0} project files`); }
return {
contents: [{ logger.debug(`Successfully retrieved ${result.files?.length || 0} project files`);
uri: uri.href, return {
text: JSON.stringify(result), contents: [
mimeType: "application/json" {
}] uri: uri.href,
}; text: JSON.stringify(result),
} mimeType: "application/json",
); },
],
// ------------------------------------------------------ };
// Project Status Resource });
// ------------------------------------------------------
server.resource( // ------------------------------------------------------
"project_status", // Project Status Resource
"kicad://project/status", // ------------------------------------------------------
async (uri) => { server.resource("project_status", "kicad://project/status", async (uri) => {
logger.debug('Retrieving project status'); logger.debug("Retrieving project status");
const result = await callKicadScript("get_project_status", {}); const result = await callKicadScript("get_project_status", {});
if (!result.success) { if (!result.success) {
logger.error(`Failed to retrieve project status: ${result.errorDetails}`); logger.error(`Failed to retrieve project status: ${result.errorDetails}`);
return { return {
contents: [{ contents: [
uri: uri.href, {
text: JSON.stringify({ uri: uri.href,
error: "Failed to retrieve project status", text: JSON.stringify({
details: result.errorDetails error: "Failed to retrieve project status",
}), details: result.errorDetails,
mimeType: "application/json" }),
}] mimeType: "application/json",
}; },
} ],
};
logger.debug('Successfully retrieved project status'); }
return {
contents: [{ logger.debug("Successfully retrieved project status");
uri: uri.href, return {
text: JSON.stringify(result), contents: [
mimeType: "application/json" {
}] uri: uri.href,
}; text: JSON.stringify(result),
} mimeType: "application/json",
); },
],
// ------------------------------------------------------ };
// Project Summary Resource });
// ------------------------------------------------------
server.resource( // ------------------------------------------------------
"project_summary", // Project Summary Resource
"kicad://project/summary", // ------------------------------------------------------
async (uri) => { server.resource("project_summary", "kicad://project/summary", async (uri) => {
logger.debug('Generating project summary'); logger.debug("Generating project summary");
// Get project info // Get project info
const infoResult = await callKicadScript("get_project_info", {}); const infoResult = await callKicadScript("get_project_info", {});
if (!infoResult.success) { if (!infoResult.success) {
logger.error(`Failed to retrieve project information: ${infoResult.errorDetails}`); logger.error(`Failed to retrieve project information: ${infoResult.errorDetails}`);
return { return {
contents: [{ contents: [
uri: uri.href, {
text: JSON.stringify({ uri: uri.href,
error: "Failed to generate project summary", text: JSON.stringify({
details: infoResult.errorDetails error: "Failed to generate project summary",
}), details: infoResult.errorDetails,
mimeType: "application/json" }),
}] mimeType: "application/json",
}; },
} ],
};
// Get board info }
const boardResult = await callKicadScript("get_board_info", {});
if (!boardResult.success) { // Get board info
logger.error(`Failed to retrieve board information: ${boardResult.errorDetails}`); const boardResult = await callKicadScript("get_board_info", {});
return { if (!boardResult.success) {
contents: [{ logger.error(`Failed to retrieve board information: ${boardResult.errorDetails}`);
uri: uri.href, return {
text: JSON.stringify({ contents: [
error: "Failed to generate project summary", {
details: boardResult.errorDetails uri: uri.href,
}), text: JSON.stringify({
mimeType: "application/json" error: "Failed to generate project summary",
}] details: boardResult.errorDetails,
}; }),
} mimeType: "application/json",
},
// Get component list ],
const componentsResult = await callKicadScript("get_component_list", {}); };
if (!componentsResult.success) { }
logger.error(`Failed to retrieve component list: ${componentsResult.errorDetails}`);
return { // Get component list
contents: [{ const componentsResult = await callKicadScript("get_component_list", {});
uri: uri.href, if (!componentsResult.success) {
text: JSON.stringify({ logger.error(`Failed to retrieve component list: ${componentsResult.errorDetails}`);
error: "Failed to generate project summary", return {
details: componentsResult.errorDetails contents: [
}), {
mimeType: "application/json" uri: uri.href,
}] text: JSON.stringify({
}; error: "Failed to generate project summary",
} details: componentsResult.errorDetails,
}),
// Combine all information into a summary mimeType: "application/json",
const summary = { },
project: infoResult.project, ],
board: { };
size: boardResult.size, }
layers: boardResult.layers?.length || 0,
title: boardResult.title // Combine all information into a summary
}, const summary = {
components: { project: infoResult.project,
count: componentsResult.components?.length || 0, board: {
types: countComponentTypes(componentsResult.components || []) size: boardResult.size,
} layers: boardResult.layers?.length || 0,
}; title: boardResult.title,
},
logger.debug('Successfully generated project summary'); components: {
return { count: componentsResult.components?.length || 0,
contents: [{ types: countComponentTypes(componentsResult.components || []),
uri: uri.href, },
text: JSON.stringify(summary), };
mimeType: "application/json"
}] logger.debug("Successfully generated project summary");
}; return {
} contents: [
); {
uri: uri.href,
logger.info('Project resources registered'); text: JSON.stringify(summary),
} mimeType: "application/json",
},
/** ],
* Helper function to count component types };
*/ });
function countComponentTypes(components: any[]): Record<string, number> {
const typeCounts: Record<string, number> = {}; logger.info("Project resources registered");
}
for (const component of components) {
const type = component.value?.split(' ')[0] || 'Unknown'; /**
typeCounts[type] = (typeCounts[type] || 0) + 1; * Helper function to count component types
} */
function countComponentTypes(components: any[]): Record<string, number> {
return typeCounts; const typeCounts: Record<string, number> = {};
}
for (const component of components) {
const type = component.value?.split(" ")[0] || "Unknown";
typeCounts[type] = (typeCounts[type] || 0) + 1;
}
return typeCounts;
}

View File

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

View File

@@ -1,384 +1,431 @@
/** /**
* Board management tools for KiCAD MCP server * Board management tools for KiCAD MCP server
* *
* These tools handle board setup, layer management, and board properties * These tools handle board setup, layer management, and board properties
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
// Command function type for KiCAD script calls // 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 board management tools with the MCP server * Register board management tools with the MCP server
* *
* @param server MCP server instance * @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands * @param callKicadScript Function to call KiCAD script commands
*/ */
export function registerBoardTools(server: McpServer, callKicadScript: CommandFunction): void { export function registerBoardTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering board management tools'); logger.info("Registering board management tools");
// ------------------------------------------------------ // ------------------------------------------------------
// Set Board Size Tool // Set Board Size Tool
// ------------------------------------------------------ // ------------------------------------------------------
server.tool( server.tool(
"set_board_size", "set_board_size",
{ {
width: z.number().describe("Board width"), width: z.number().describe("Board width"),
height: z.number().describe("Board height"), 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 }) => { async ({ width, height, unit }) => {
logger.debug(`Setting board size to ${width}x${height} ${unit}`); logger.debug(`Setting board size to ${width}x${height} ${unit}`);
const result = await callKicadScript("set_board_size", { const result = await callKicadScript("set_board_size", {
width, width,
height, height,
unit unit,
}); });
return { return {
content: [{ content: [
type: "text", {
text: JSON.stringify(result) type: "text",
}] text: JSON.stringify(result),
}; },
} ],
); };
},
// ------------------------------------------------------ );
// Add Layer Tool
// ------------------------------------------------------ // ------------------------------------------------------
server.tool( // Add Layer Tool
"add_layer", // ------------------------------------------------------
{ server.tool(
name: z.string().describe("Layer name"), "add_layer",
type: z.enum([ {
"copper", "technical", "user", "signal" name: z.string().describe("Layer name"),
]).describe("Layer type"), type: z.enum(["copper", "technical", "user", "signal"]).describe("Layer type"),
position: z.enum([ position: z.enum(["top", "bottom", "inner"]).describe("Layer position"),
"top", "bottom", "inner" number: z.number().optional().describe("Layer number (for inner layers)"),
]).describe("Layer position"), },
number: z.number().optional().describe("Layer number (for inner layers)") async ({ name, type, position, number }) => {
}, logger.debug(`Adding ${type} layer: ${name}`);
async ({ name, type, position, number }) => { const result = await callKicadScript("add_layer", {
logger.debug(`Adding ${type} layer: ${name}`); name,
const result = await callKicadScript("add_layer", { type,
name, position,
type, number,
position, });
number
}); return {
content: [
return { {
content: [{ type: "text",
type: "text", text: JSON.stringify(result),
text: JSON.stringify(result) },
}] ],
}; };
} },
); );
// ------------------------------------------------------ // ------------------------------------------------------
// Set Active Layer Tool // Set Active Layer Tool
// ------------------------------------------------------ // ------------------------------------------------------
server.tool( server.tool(
"set_active_layer", "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 }) => { async ({ layer }) => {
logger.debug(`Setting active layer to: ${layer}`); logger.debug(`Setting active layer to: ${layer}`);
const result = await callKicadScript("set_active_layer", { layer }); const result = await callKicadScript("set_active_layer", { layer });
return { return {
content: [{ content: [
type: "text", {
text: JSON.stringify(result) type: "text",
}] text: JSON.stringify(result),
}; },
} ],
); };
},
// ------------------------------------------------------ );
// Get Board Info Tool
// ------------------------------------------------------ // ------------------------------------------------------
server.tool( // Get Board Info Tool
"get_board_info", // ------------------------------------------------------
{}, server.tool("get_board_info", {}, async () => {
async () => { logger.debug("Getting board information");
logger.debug('Getting board information'); const result = await callKicadScript("get_board_info", {});
const result = await callKicadScript("get_board_info", {});
return {
return { content: [
content: [{ {
type: "text", type: "text",
text: JSON.stringify(result) text: JSON.stringify(result),
}] },
}; ],
} };
); });
// ------------------------------------------------------ // ------------------------------------------------------
// Get Layer List Tool // Get Layer List Tool
// ------------------------------------------------------ // ------------------------------------------------------
server.tool( server.tool("get_layer_list", {}, async () => {
"get_layer_list", logger.debug("Getting layer list");
{}, const result = await callKicadScript("get_layer_list", {});
async () => {
logger.debug('Getting layer list'); return {
const result = await callKicadScript("get_layer_list", {}); content: [
{
return { type: "text",
content: [{ text: JSON.stringify(result),
type: "text", },
text: JSON.stringify(result) ],
}] };
}; });
}
); // ------------------------------------------------------
// Add Board Outline Tool
// ------------------------------------------------------ // ------------------------------------------------------
// Add Board Outline Tool server.tool(
// ------------------------------------------------------ "add_board_outline",
server.tool( {
"add_board_outline", shape: z
{ .enum(["rectangle", "circle", "polygon", "rounded_rectangle"])
shape: z.enum(["rectangle", "circle", "polygon", "rounded_rectangle"]).describe("Shape of the outline"), .describe("Shape of the outline"),
params: z.object({ params: z
// For rectangle / rounded_rectangle .object({
width: z.number().optional().describe("Width of rectangle"), // For rectangle / rounded_rectangle
height: z.number().optional().describe("Height of rectangle"), width: z.number().optional().describe("Width of rectangle"),
cornerRadius: z.number().optional().describe("Corner radius for rounded_rectangle (mm)"), height: z.number().optional().describe("Height of rectangle"),
// For circle cornerRadius: z.number().optional().describe("Corner radius for rounded_rectangle (mm)"),
radius: z.number().optional().describe("Radius of circle"), // For circle
// For polygon radius: z.number().optional().describe("Radius of circle"),
points: z.array( // For polygon
z.object({ points: z
x: z.number().describe("X coordinate"), .array(
y: z.number().describe("Y coordinate") z.object({
}) x: z.number().describe("X coordinate"),
).optional().describe("Points of polygon"), y: z.number().describe("Y coordinate"),
// 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)"), .optional()
unit: z.enum(["mm", "inch"]).describe("Unit of measurement") .describe("Points of polygon"),
}).describe("Parameters for the outline shape") // 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)"),
async ({ shape, params }) => { y: z.number().describe("Y coordinate of top-left corner for rectangles (default: 0)"),
logger.debug(`Adding ${shape} board outline`); unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
// Pass x/y as-is to Python; outline.py treats them as top-left corner })
// and computes the center internally (center = x + width/2, y + height/2). .describe("Parameters for the outline shape"),
const result = await callKicadScript("add_board_outline", { },
shape, async ({ shape, params }) => {
...params logger.debug(`Adding ${shape} board outline`);
}); // Pass x/y as-is to Python; outline.py treats them as top-left corner
// and computes the center internally (center = x + width/2, y + height/2).
return { const result = await callKicadScript("add_board_outline", {
content: [{ shape,
type: "text", ...params,
text: JSON.stringify(result) });
}]
}; return {
} content: [
); {
type: "text",
// ------------------------------------------------------ text: JSON.stringify(result),
// Add Mounting Hole Tool },
// ------------------------------------------------------ ],
server.tool( };
"add_mounting_hole", },
{ );
position: z.object({
x: z.number().describe("X coordinate"), // ------------------------------------------------------
y: z.number().describe("Y coordinate"), // Add Mounting Hole Tool
unit: z.enum(["mm", "inch"]).describe("Unit of measurement") // ------------------------------------------------------
}).describe("Position of the mounting hole"), server.tool(
diameter: z.number().describe("Diameter of the hole"), "add_mounting_hole",
padDiameter: z.number().optional().describe("Optional diameter of the pad around the hole") {
}, position: z
async ({ position, diameter, padDiameter }) => { .object({
logger.debug(`Adding mounting hole at (${position.x},${position.y}) ${position.unit}`); x: z.number().describe("X coordinate"),
const result = await callKicadScript("add_mounting_hole", { y: z.number().describe("Y coordinate"),
position, unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
diameter, })
padDiameter .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"),
return { },
content: [{ async ({ position, diameter, padDiameter }) => {
type: "text", logger.debug(`Adding mounting hole at (${position.x},${position.y}) ${position.unit}`);
text: JSON.stringify(result) const result = await callKicadScript("add_mounting_hole", {
}] position,
}; diameter,
} padDiameter,
); });
// ------------------------------------------------------ return {
// Add Text Tool content: [
// ------------------------------------------------------ {
server.tool( type: "text",
"add_board_text", text: JSON.stringify(result),
{ },
text: z.string().describe("Text content"), ],
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"), // ------------------------------------------------------
layer: z.string().describe("Layer to place the text on"), // Add Text Tool
size: z.number().describe("Text size"), // ------------------------------------------------------
thickness: z.number().optional().describe("Line thickness"), server.tool(
rotation: z.number().optional().describe("Rotation angle in degrees"), "add_board_text",
style: z.enum(["normal", "italic", "bold"]).optional().describe("Text style") {
}, text: z.string().describe("Text content"),
async ({ text, position, layer, size, thickness, rotation, style }) => { position: z
logger.debug(`Adding text "${text}" at (${position.x},${position.y}) ${position.unit}`); .object({
const result = await callKicadScript("add_board_text", { x: z.number().describe("X coordinate"),
text, y: z.number().describe("Y coordinate"),
position, unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
layer, })
size, .describe("Position of the text"),
thickness, layer: z.string().describe("Layer to place the text on"),
rotation, size: z.number().describe("Text size"),
style 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"),
return { },
content: [{ async ({ text, position, layer, size, thickness, rotation, style }) => {
type: "text", logger.debug(`Adding text "${text}" at (${position.x},${position.y}) ${position.unit}`);
text: JSON.stringify(result) const result = await callKicadScript("add_board_text", {
}] text,
}; position,
} layer,
); size,
thickness,
// ------------------------------------------------------ rotation,
// Add Zone Tool style,
// ------------------------------------------------------ });
server.tool(
"add_zone", return {
{ content: [
layer: z.string().describe("Layer for the zone"), {
net: z.string().describe("Net name for the zone"), type: "text",
points: z.array( text: JSON.stringify(result),
z.object({ },
x: z.number().describe("X coordinate"), ],
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"), // Add Zone Tool
padConnection: z.enum(["thermal", "solid", "none"]).optional().describe("Pad connection type") // ------------------------------------------------------
}, server.tool(
async ({ layer, net, points, unit, clearance, minWidth, padConnection }) => { "add_zone",
logger.debug(`Adding zone on layer ${layer} for net ${net}`); {
const result = await callKicadScript("add_zone", { layer: z.string().describe("Layer for the zone"),
layer, net: z.string().describe("Net name for the zone"),
net, points: z
points, .array(
unit, z.object({
clearance, x: z.number().describe("X coordinate"),
minWidth, y: z.number().describe("Y coordinate"),
padConnection }),
}); )
.describe("Points defining the zone outline"),
return { unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
content: [{ clearance: z.number().optional().describe("Clearance value"),
type: "text", minWidth: z.number().optional().describe("Minimum width"),
text: JSON.stringify(result) 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}`);
// Get Board Extents Tool const result = await callKicadScript("add_zone", {
// ------------------------------------------------------ layer,
server.tool( net,
"get_board_extents", points,
{ unit,
unit: z.enum(["mm", "inch"]).optional().describe("Unit of measurement for the result") clearance,
}, minWidth,
async ({ unit }) => { padConnection,
logger.debug('Getting board extents'); });
const result = await callKicadScript("get_board_extents", { unit });
return {
return { content: [
content: [{ {
type: "text", type: "text",
text: JSON.stringify(result) text: JSON.stringify(result),
}] },
}; ],
} };
); },
);
// ------------------------------------------------------
// Get Board 2D View Tool // ------------------------------------------------------
// ------------------------------------------------------ // Get Board Extents Tool
server.tool( // ------------------------------------------------------
"get_board_2d_view", server.tool(
{ "get_board_extents",
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"), unit: z.enum(["mm", "inch"]).optional().describe("Unit of measurement for the result"),
height: z.number().optional().describe("Optional height of the image in pixels"), },
format: z.enum(["png", "jpg", "svg"]).optional().describe("Image format") async ({ unit }) => {
}, logger.debug("Getting board extents");
async ({ layers, width, height, format }) => { const result = await callKicadScript("get_board_extents", { unit });
logger.debug('Getting 2D board view');
const result = await callKicadScript("get_board_2d_view", { return {
layers, content: [
width, {
height, type: "text",
format text: JSON.stringify(result),
}); },
],
return { };
content: [{ },
type: "text", );
text: JSON.stringify(result)
}] // ------------------------------------------------------
}; // Get Board 2D View Tool
} // ------------------------------------------------------
); server.tool(
"get_board_2d_view",
logger.info('Board management tools registered'); {
layers: z.array(z.string()).optional().describe("Optional array of layer names to include"),
// Import SVG logo onto PCB layer (silkscreen) width: z.number().optional().describe("Optional width of the image in pixels"),
server.tool( height: z.number().optional().describe("Optional height of the image in pixels"),
"import_svg_logo", format: z.enum(["png", "jpg", "svg"]).optional().describe("Image format"),
"Imports an SVG file as filled graphic polygons onto a KiCAD PCB layer (default F.SilkS / front silkscreen). Curves are linearised automatically. Ideal for placing a company or project logo on the board.", },
{ async ({ layers, width, height, format }) => {
pcbPath: z.string().describe("Path to the .kicad_pcb file"), logger.debug("Getting 2D board view");
svgPath: z.string().describe("Path to the SVG logo file"), const result = await callKicadScript("get_board_2d_view", {
x: z.number().describe("X position of the logo top-left corner in mm"), layers,
y: z.number().describe("Y position of the logo top-left corner in mm"), width,
width: z.number().describe("Target width of the logo in mm (height is scaled to preserve aspect ratio)"), height,
layer: z.string().optional().describe("PCB layer name, e.g. F.SilkS or B.SilkS (default: F.SilkS)"), format,
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)"),
}, return {
async (args: { pcbPath: string; svgPath: string; x: number; y: number; width: number; layer?: string; strokeWidth?: number; filled?: boolean }) => { content: [
const result = await callKicadScript("import_svg_logo", args); {
if (result.success) { type: "text",
return { text: JSON.stringify(result),
content: [{ },
type: "text", ],
text: [ };
result.message, },
`Polygons: ${result.polygon_count}`, );
`Size: ${result.logo_width_mm?.toFixed(2)} × ${result.logo_height_mm?.toFixed(2)} mm`,
`Layer: ${result.layer}`, logger.info("Board management tools registered");
].join("\n"),
}], // Import SVG logo onto PCB layer (silkscreen)
}; server.tool(
} else { "import_svg_logo",
return { "Imports an SVG file as filled graphic polygons onto a KiCAD PCB layer (default F.SilkS / front silkscreen). Curves are linearised automatically. Ideal for placing a company or project logo on the board.",
content: [{ type: "text", text: `SVG import failed: ${result.message || "Unknown error"}` }], {
}; pcbPath: z.string().describe("Path to the .kicad_pcb file"),
} 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)"),
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;
}) => {
const result = await callKicadScript("import_svg_logo", args);
if (result.success) {
return {
content: [
{
type: "text",
text: [
result.message,
`Polygons: ${result.polygon_count}`,
`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"}` },
],
};
}
},
);
}

View File

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

View File

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

View File

@@ -1,261 +1,314 @@
/** /**
* Design rules tools for KiCAD MCP server * Design rules tools for KiCAD MCP server
* *
* These tools handle design rule checking and configuration * These tools handle design rule checking and configuration
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
// Command function type for KiCAD script calls // 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 design rule tools with the MCP server * Register design rule tools with the MCP server
* *
* @param server MCP server instance * @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands * @param callKicadScript Function to call KiCAD script commands
*/ */
export function registerDesignRuleTools(server: McpServer, callKicadScript: CommandFunction): void { export function registerDesignRuleTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering design rule tools'); logger.info("Registering design rule tools");
// ------------------------------------------------------ // ------------------------------------------------------
// Set Design Rules Tool // Set Design Rules Tool
// ------------------------------------------------------ // ------------------------------------------------------
server.tool( server.tool(
"set_design_rules", "set_design_rules",
{ {
clearance: z.number().optional().describe("Minimum clearance between copper items (mm)"), clearance: z.number().optional().describe("Minimum clearance between copper items (mm)"),
trackWidth: z.number().optional().describe("Default track width (mm)"), trackWidth: z.number().optional().describe("Default track width (mm)"),
viaDiameter: z.number().optional().describe("Default via diameter (mm)"), viaDiameter: z.number().optional().describe("Default via diameter (mm)"),
viaDrill: z.number().optional().describe("Default via drill size (mm)"), viaDrill: z.number().optional().describe("Default via drill size (mm)"),
microViaDiameter: z.number().optional().describe("Default micro via diameter (mm)"), microViaDiameter: z.number().optional().describe("Default micro via diameter (mm)"),
microViaDrill: z.number().optional().describe("Default micro via drill size (mm)"), microViaDrill: z.number().optional().describe("Default micro via drill size (mm)"),
minTrackWidth: z.number().optional().describe("Minimum track width (mm)"), minTrackWidth: z.number().optional().describe("Minimum track width (mm)"),
minViaDiameter: z.number().optional().describe("Minimum via diameter (mm)"), minViaDiameter: z.number().optional().describe("Minimum via diameter (mm)"),
minViaDrill: z.number().optional().describe("Minimum via drill size (mm)"), minViaDrill: z.number().optional().describe("Minimum via drill size (mm)"),
minMicroViaDiameter: z.number().optional().describe("Minimum micro via diameter (mm)"), minMicroViaDiameter: z.number().optional().describe("Minimum micro via diameter (mm)"),
minMicroViaDrill: z.number().optional().describe("Minimum micro via drill size (mm)"), minMicroViaDrill: z.number().optional().describe("Minimum micro via drill size (mm)"),
minHoleDiameter: z.number().optional().describe("Minimum hole diameter (mm)"), minHoleDiameter: z.number().optional().describe("Minimum hole diameter (mm)"),
requireCourtyard: z.boolean().optional().describe("Whether to require courtyards for all footprints"), requireCourtyard: z
courtyardClearance: z.number().optional().describe("Minimum clearance between courtyards (mm)") .boolean()
}, .optional()
async (params) => { .describe("Whether to require courtyards for all footprints"),
logger.debug('Setting design rules'); courtyardClearance: z
const result = await callKicadScript("set_design_rules", params); .number()
.optional()
return { .describe("Minimum clearance between courtyards (mm)"),
content: [{ },
type: "text", async (params) => {
text: JSON.stringify(result) logger.debug("Setting design rules");
}] const result = await callKicadScript("set_design_rules", params);
};
} return {
); content: [
{
// ------------------------------------------------------ type: "text",
// Get Design Rules Tool text: JSON.stringify(result),
// ------------------------------------------------------ },
server.tool( ],
"get_design_rules", };
{}, },
async () => { );
logger.debug('Getting design rules');
const result = await callKicadScript("get_design_rules", {}); // ------------------------------------------------------
// Get Design Rules Tool
return { // ------------------------------------------------------
content: [{ server.tool("get_design_rules", {}, async () => {
type: "text", logger.debug("Getting design rules");
text: JSON.stringify(result) const result = await callKicadScript("get_design_rules", {});
}]
}; return {
} content: [
); {
type: "text",
// ------------------------------------------------------ text: JSON.stringify(result),
// Run DRC Tool },
// ------------------------------------------------------ ],
server.tool( };
"run_drc", });
{
reportPath: z.string().optional().describe("Optional path to save the DRC report") // ------------------------------------------------------
}, // Run DRC Tool
async ({ reportPath }) => { // ------------------------------------------------------
logger.debug('Running DRC check'); server.tool(
const result = await callKicadScript("run_drc", { reportPath }); "run_drc",
{
return { reportPath: z.string().optional().describe("Optional path to save the DRC report"),
content: [{ },
type: "text", async ({ reportPath }) => {
text: JSON.stringify(result) logger.debug("Running DRC check");
}] const result = await callKicadScript("run_drc", { reportPath });
};
} return {
); content: [
{
// ------------------------------------------------------ type: "text",
// Add Net Class Tool text: JSON.stringify(result),
// ------------------------------------------------------ },
server.tool( ],
"add_net_class", };
{ },
name: z.string().describe("Name of the net class"), );
description: z.string().optional().describe("Optional description of the net class"),
clearance: z.number().describe("Clearance for this net class (mm)"), // ------------------------------------------------------
trackWidth: z.number().describe("Track width for this net class (mm)"), // Add Net Class Tool
viaDiameter: z.number().describe("Via diameter for this net class (mm)"), // ------------------------------------------------------
viaDrill: z.number().describe("Via drill size for this net class (mm)"), server.tool(
uvia_diameter: z.number().optional().describe("Micro via diameter for this net class (mm)"), "add_net_class",
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)"), name: z.string().describe("Name of the net class"),
diff_pair_gap: z.number().optional().describe("Differential pair gap for this net class (mm)"), description: z.string().optional().describe("Optional description of the net class"),
nets: z.array(z.string()).optional().describe("Array of net names to assign to this class") clearance: z.number().describe("Clearance for this net class (mm)"),
}, trackWidth: z.number().describe("Track width for this net class (mm)"),
async ({ name, description, clearance, trackWidth, viaDiameter, viaDrill, uvia_diameter, uvia_drill, diff_pair_width, diff_pair_gap, nets }) => { viaDiameter: z.number().describe("Via diameter for this net class (mm)"),
logger.debug(`Adding net class: ${name}`); viaDrill: z.number().describe("Via drill size for this net class (mm)"),
const result = await callKicadScript("add_net_class", { uvia_diameter: z.number().optional().describe("Micro via diameter for this net class (mm)"),
name, uvia_drill: z.number().optional().describe("Micro via drill size for this net class (mm)"),
description, diff_pair_width: z
clearance, .number()
trackWidth, .optional()
viaDiameter, .describe("Differential pair width for this net class (mm)"),
viaDrill, diff_pair_gap: z
uvia_diameter, .number()
uvia_drill, .optional()
diff_pair_width, .describe("Differential pair gap for this net class (mm)"),
diff_pair_gap, nets: z.array(z.string()).optional().describe("Array of net names to assign to this class"),
nets },
}); async ({
name,
return { description,
content: [{ clearance,
type: "text", trackWidth,
text: JSON.stringify(result) viaDiameter,
}] viaDrill,
}; uvia_diameter,
} uvia_drill,
); diff_pair_width,
diff_pair_gap,
// ------------------------------------------------------ nets,
// Assign Net to Class Tool }) => {
// ------------------------------------------------------ logger.debug(`Adding net class: ${name}`);
server.tool( const result = await callKicadScript("add_net_class", {
"assign_net_to_class", name,
{ description,
net: z.string().describe("Name of the net"), clearance,
netClass: z.string().describe("Name of the net class") trackWidth,
}, viaDiameter,
async ({ net, netClass }) => { viaDrill,
logger.debug(`Assigning net ${net} to class ${netClass}`); uvia_diameter,
const result = await callKicadScript("assign_net_to_class", { uvia_drill,
net, diff_pair_width,
netClass diff_pair_gap,
}); nets,
});
return {
content: [{ return {
type: "text", content: [
text: JSON.stringify(result) {
}] type: "text",
}; text: JSON.stringify(result),
} },
); ],
};
// ------------------------------------------------------ },
// Set Layer Constraints Tool );
// ------------------------------------------------------
server.tool( // ------------------------------------------------------
"set_layer_constraints", // Assign Net to Class Tool
{ // ------------------------------------------------------
layer: z.string().describe("Layer name (e.g., 'F.Cu')"), server.tool(
minTrackWidth: z.number().optional().describe("Minimum track width for this layer (mm)"), "assign_net_to_class",
minClearance: z.number().optional().describe("Minimum clearance for this layer (mm)"), {
minViaDiameter: z.number().optional().describe("Minimum via diameter for this layer (mm)"), net: z.string().describe("Name of the net"),
minViaDrill: z.number().optional().describe("Minimum via drill size for this layer (mm)") netClass: z.string().describe("Name of the net class"),
}, },
async ({ layer, minTrackWidth, minClearance, minViaDiameter, minViaDrill }) => { async ({ net, netClass }) => {
logger.debug(`Setting constraints for layer: ${layer}`); logger.debug(`Assigning net ${net} to class ${netClass}`);
const result = await callKicadScript("set_layer_constraints", { const result = await callKicadScript("assign_net_to_class", {
layer, net,
minTrackWidth, netClass,
minClearance, });
minViaDiameter,
minViaDrill return {
}); content: [
{
return { type: "text",
content: [{ text: JSON.stringify(result),
type: "text", },
text: JSON.stringify(result) ],
}] };
}; },
} );
);
// ------------------------------------------------------
// ------------------------------------------------------ // Set Layer Constraints Tool
// Check Clearance Tool // ------------------------------------------------------
// ------------------------------------------------------ server.tool(
server.tool( "set_layer_constraints",
"check_clearance", {
{ layer: z.string().describe("Layer name (e.g., 'F.Cu')"),
item1: z.object({ minTrackWidth: z.number().optional().describe("Minimum track width for this layer (mm)"),
type: z.enum(["track", "via", "pad", "zone", "component"]).describe("Type of the first item"), minClearance: z.number().optional().describe("Minimum clearance for this layer (mm)"),
id: z.string().optional().describe("ID of the first item (if applicable)"), minViaDiameter: z.number().optional().describe("Minimum via diameter for this layer (mm)"),
reference: z.string().optional().describe("Reference designator (for component)"), minViaDrill: z.number().optional().describe("Minimum via drill size for this layer (mm)"),
position: z.object({ },
x: z.number().optional(), async ({ layer, minTrackWidth, minClearance, minViaDiameter, minViaDrill }) => {
y: z.number().optional(), logger.debug(`Setting constraints for layer: ${layer}`);
unit: z.enum(["mm", "inch"]).optional() const result = await callKicadScript("set_layer_constraints", {
}).optional().describe("Position to check (if ID not provided)") layer,
}).describe("First item to check"), minTrackWidth,
item2: z.object({ minClearance,
type: z.enum(["track", "via", "pad", "zone", "component"]).describe("Type of the second item"), minViaDiameter,
id: z.string().optional().describe("ID of the second item (if applicable)"), minViaDrill,
reference: z.string().optional().describe("Reference designator (for component)"), });
position: z.object({
x: z.number().optional(), return {
y: z.number().optional(), content: [
unit: z.enum(["mm", "inch"]).optional() {
}).optional().describe("Position to check (if ID not provided)") type: "text",
}).describe("Second item to check") text: JSON.stringify(result),
}, },
async ({ item1, item2 }) => { ],
logger.debug(`Checking clearance between ${item1.type} and ${item2.type}`); };
const result = await callKicadScript("check_clearance", { },
item1, );
item2
}); // ------------------------------------------------------
// Check Clearance Tool
return { // ------------------------------------------------------
content: [{ server.tool(
type: "text", "check_clearance",
text: JSON.stringify(result) {
}] 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)"),
// Get DRC Violations Tool reference: z.string().optional().describe("Reference designator (for component)"),
// ------------------------------------------------------ position: z
server.tool( .object({
"get_drc_violations", x: z.number().optional(),
{ y: z.number().optional(),
severity: z.enum(["error", "warning", "all"]).optional().describe("Filter violations by severity") unit: z.enum(["mm", "inch"]).optional(),
}, })
async ({ severity }) => { .optional()
logger.debug('Getting DRC violations'); .describe("Position to check (if ID not provided)"),
const result = await callKicadScript("get_drc_violations", { severity }); })
.describe("First item to check"),
return { item2: z
content: [{ .object({
type: "text", type: z
text: JSON.stringify(result) .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({
logger.info('Design rule tools registered'); 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"),
},
async ({ item1, item2 }) => {
logger.debug(`Checking clearance between ${item1.type} and ${item2.type}`);
const result = await callKicadScript("check_clearance", {
item1,
item2,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Get DRC Violations Tool
// ------------------------------------------------------
server.tool(
"get_drc_violations",
{
severity: z
.enum(["error", "warning", "all"])
.optional()
.describe("Filter violations by severity"),
},
async ({ severity }) => {
logger.debug("Getting DRC violations");
const result = await callKicadScript("get_drc_violations", { severity });
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
logger.info("Design rule tools registered");
}

View File

@@ -1,260 +1,317 @@
/** /**
* Export tools for KiCAD MCP server * Export tools for KiCAD MCP server
* *
* These tools handle exporting PCB data to various formats * These tools handle exporting PCB data to various formats
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
// Command function type for KiCAD script calls // 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 export tools with the MCP server * Register export tools with the MCP server
* *
* @param server MCP server instance * @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands * @param callKicadScript Function to call KiCAD script commands
*/ */
export function registerExportTools(server: McpServer, callKicadScript: CommandFunction): void { export function registerExportTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering export tools'); logger.info("Registering export tools");
// ------------------------------------------------------ // ------------------------------------------------------
// Export Gerber Tool // Export Gerber Tool
// ------------------------------------------------------ // ------------------------------------------------------
server.tool( server.tool(
"export_gerber", "export_gerber",
{ {
outputDir: z.string().describe("Directory to save Gerber files"), 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)"), layers: z
useProtelExtensions: z.boolean().optional().describe("Whether to use Protel filename extensions"), .array(z.string())
generateDrillFiles: z.boolean().optional().describe("Whether to generate drill files"), .optional()
generateMapFile: z.boolean().optional().describe("Whether to generate a map file"), .describe("Optional array of layer names to export (default: all)"),
useAuxOrigin: z.boolean().optional().describe("Whether to use auxiliary axis as origin") useProtelExtensions: z
}, .boolean()
async ({ outputDir, layers, useProtelExtensions, generateDrillFiles, generateMapFile, useAuxOrigin }) => { .optional()
logger.debug(`Exporting Gerber files to: ${outputDir}`); .describe("Whether to use Protel filename extensions"),
const result = await callKicadScript("export_gerber", { generateDrillFiles: z.boolean().optional().describe("Whether to generate drill files"),
outputDir, generateMapFile: z.boolean().optional().describe("Whether to generate a map file"),
layers, useAuxOrigin: z.boolean().optional().describe("Whether to use auxiliary axis as origin"),
useProtelExtensions, },
generateDrillFiles, async ({
generateMapFile, outputDir,
useAuxOrigin layers,
}); useProtelExtensions,
generateDrillFiles,
return { generateMapFile,
content: [{ useAuxOrigin,
type: "text", }) => {
text: JSON.stringify(result) logger.debug(`Exporting Gerber files to: ${outputDir}`);
}] const result = await callKicadScript("export_gerber", {
}; outputDir,
} layers,
); useProtelExtensions,
generateDrillFiles,
// ------------------------------------------------------ generateMapFile,
// Export PDF Tool useAuxOrigin,
// ------------------------------------------------------ });
server.tool(
"export_pdf", return {
{ content: [
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)"), type: "text",
blackAndWhite: z.boolean().optional().describe("Whether to export in black and white"), text: JSON.stringify(result),
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") ],
}, };
async ({ outputPath, layers, blackAndWhite, frameReference, pageSize }) => { },
logger.debug(`Exporting PDF to: ${outputPath}`); );
const result = await callKicadScript("export_pdf", {
outputPath, // ------------------------------------------------------
layers, // Export PDF Tool
blackAndWhite, // ------------------------------------------------------
frameReference, server.tool(
pageSize "export_pdf",
}); {
outputPath: z.string().describe("Path to save the PDF file"),
return { layers: z
content: [{ .array(z.string())
type: "text", .optional()
text: JSON.stringify(result) .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"),
// Export SVG Tool },
// ------------------------------------------------------ async ({ outputPath, layers, blackAndWhite, frameReference, pageSize }) => {
server.tool( logger.debug(`Exporting PDF to: ${outputPath}`);
"export_svg", const result = await callKicadScript("export_pdf", {
{ outputPath,
outputPath: z.string().describe("Path to save the SVG file"), layers,
layers: z.array(z.string()).optional().describe("Optional array of layer names to include (default: all)"), blackAndWhite,
blackAndWhite: z.boolean().optional().describe("Whether to export in black and white"), frameReference,
includeComponents: z.boolean().optional().describe("Whether to include component outlines") pageSize,
}, });
async ({ outputPath, layers, blackAndWhite, includeComponents }) => {
logger.debug(`Exporting SVG to: ${outputPath}`); return {
const result = await callKicadScript("export_svg", { content: [
outputPath, {
layers, type: "text",
blackAndWhite, text: JSON.stringify(result),
includeComponents },
}); ],
};
return { },
content: [{ );
type: "text",
text: JSON.stringify(result) // ------------------------------------------------------
}] // Export SVG Tool
}; // ------------------------------------------------------
} server.tool(
); "export_svg",
{
// ------------------------------------------------------ outputPath: z.string().describe("Path to save the SVG file"),
// Export 3D Model Tool layers: z
// ------------------------------------------------------ .array(z.string())
server.tool( .optional()
"export_3d", .describe("Optional array of layer names to include (default: all)"),
{ blackAndWhite: z.boolean().optional().describe("Whether to export in black and white"),
outputPath: z.string().describe("Path to save the 3D model file"), includeComponents: z.boolean().optional().describe("Whether to include component outlines"),
format: z.enum(["STEP", "STL", "VRML", "OBJ"]).describe("3D model format"), },
includeComponents: z.boolean().optional().describe("Whether to include 3D component models"), async ({ outputPath, layers, blackAndWhite, includeComponents }) => {
includeCopper: z.boolean().optional().describe("Whether to include copper layers"), logger.debug(`Exporting SVG to: ${outputPath}`);
includeSolderMask: z.boolean().optional().describe("Whether to include solder mask"), const result = await callKicadScript("export_svg", {
includeSilkscreen: z.boolean().optional().describe("Whether to include silkscreen") outputPath,
}, layers,
async ({ outputPath, format, includeComponents, includeCopper, includeSolderMask, includeSilkscreen }) => { blackAndWhite,
logger.debug(`Exporting 3D model to: ${outputPath}`); includeComponents,
const result = await callKicadScript("export_3d", { });
outputPath,
format, return {
includeComponents, content: [
includeCopper, {
includeSolderMask, type: "text",
includeSilkscreen text: JSON.stringify(result),
}); },
],
return { };
content: [{ },
type: "text", );
text: JSON.stringify(result)
}] // ------------------------------------------------------
}; // Export 3D Model Tool
} // ------------------------------------------------------
); server.tool(
"export_3d",
// ------------------------------------------------------ {
// Export BOM Tool outputPath: z.string().describe("Path to save the 3D model file"),
// ------------------------------------------------------ format: z.enum(["STEP", "STL", "VRML", "OBJ"]).describe("3D model format"),
server.tool( includeComponents: z.boolean().optional().describe("Whether to include 3D component models"),
"export_bom", includeCopper: z.boolean().optional().describe("Whether to include copper layers"),
{ includeSolderMask: z.boolean().optional().describe("Whether to include solder mask"),
outputPath: z.string().describe("Path to save the BOM file"), includeSilkscreen: z.boolean().optional().describe("Whether to include silkscreen"),
format: z.enum(["CSV", "XML", "HTML", "JSON"]).describe("BOM file format"), },
groupByValue: z.boolean().optional().describe("Whether to group components by value"), async ({
includeAttributes: z.array(z.string()).optional().describe("Optional array of additional attributes to include") outputPath,
}, format,
async ({ outputPath, format, groupByValue, includeAttributes }) => { includeComponents,
logger.debug(`Exporting BOM to: ${outputPath}`); includeCopper,
const result = await callKicadScript("export_bom", { includeSolderMask,
outputPath, includeSilkscreen,
format, }) => {
groupByValue, logger.debug(`Exporting 3D model to: ${outputPath}`);
includeAttributes const result = await callKicadScript("export_3d", {
}); outputPath,
format,
return { includeComponents,
content: [{ includeCopper,
type: "text", includeSolderMask,
text: JSON.stringify(result) includeSilkscreen,
}] });
};
} return {
); content: [
{
// ------------------------------------------------------ type: "text",
// Export Netlist Tool text: JSON.stringify(result),
// ------------------------------------------------------ },
server.tool( ],
"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)")
}, // ------------------------------------------------------
async ({ outputPath, format }) => { // Export BOM Tool
logger.debug(`Exporting netlist to: ${outputPath}`); // ------------------------------------------------------
const result = await callKicadScript("export_netlist", { server.tool(
outputPath, "export_bom",
format {
}); outputPath: z.string().describe("Path to save the BOM file"),
format: z.enum(["CSV", "XML", "HTML", "JSON"]).describe("BOM file format"),
return { groupByValue: z.boolean().optional().describe("Whether to group components by value"),
content: [{ includeAttributes: z
type: "text", .array(z.string())
text: JSON.stringify(result) .optional()
}] .describe("Optional array of additional attributes to include"),
}; },
} async ({ outputPath, format, groupByValue, includeAttributes }) => {
); logger.debug(`Exporting BOM to: ${outputPath}`);
const result = await callKicadScript("export_bom", {
// ------------------------------------------------------ outputPath,
// Export Position File Tool format,
// ------------------------------------------------------ groupByValue,
server.tool( includeAttributes,
"export_position_file", });
{
outputPath: z.string().describe("Path to save the position file"), return {
format: z.enum(["CSV", "ASCII"]).optional().describe("File format (default: CSV)"), content: [
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)") type: "text",
}, text: JSON.stringify(result),
async ({ outputPath, format, units, side }) => { },
logger.debug(`Exporting position file to: ${outputPath}`); ],
const result = await callKicadScript("export_position_file", { };
outputPath, },
format, );
units,
side // ------------------------------------------------------
}); // Export Netlist Tool
// ------------------------------------------------------
return { server.tool(
content: [{ "export_netlist",
type: "text", {
text: JSON.stringify(result) outputPath: z.string().describe("Path to save the netlist file"),
}] format: z
}; .enum(["KiCad", "Spice", "Cadstar", "OrcadPCB2"])
} .optional()
); .describe("Netlist format (default: KiCad)"),
},
// ------------------------------------------------------ async ({ outputPath, format }) => {
// Export VRML Tool logger.debug(`Exporting netlist to: ${outputPath}`);
// ------------------------------------------------------ const result = await callKicadScript("export_netlist", {
server.tool( outputPath,
"export_vrml", format,
{ });
outputPath: z.string().describe("Path to save the VRML file"),
includeComponents: z.boolean().optional().describe("Whether to include 3D component models"), return {
useRelativePaths: z.boolean().optional().describe("Whether to use relative paths for 3D models") content: [
}, {
async ({ outputPath, includeComponents, useRelativePaths }) => { type: "text",
logger.debug(`Exporting VRML to: ${outputPath}`); text: JSON.stringify(result),
const result = await callKicadScript("export_vrml", { },
outputPath, ],
includeComponents, };
useRelativePaths },
}); );
return { // ------------------------------------------------------
content: [{ // Export Position File Tool
type: "text", // ------------------------------------------------------
text: JSON.stringify(result) server.tool(
}] "export_position_file",
}; {
} 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)"),
logger.info('Export tools registered'); 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}`);
const result = await callKicadScript("export_position_file", {
outputPath,
format,
units,
side,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Export VRML Tool
// ------------------------------------------------------
server.tool(
"export_vrml",
{
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"),
},
async ({ outputPath, includeComponents, useRelativePaths }) => {
logger.debug(`Exporting VRML to: ${outputPath}`);
const result = await callKicadScript("export_vrml", {
outputPath,
includeComponents,
useRelativePaths,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
logger.info("Export tools registered");
}

View File

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

View File

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

View File

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

View File

@@ -3,8 +3,8 @@
* Provides search/browse access to local KiCad symbol libraries * Provides search/browse access to local KiCad symbol libraries
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
export function registerSymbolLibraryTools(server: McpServer, callKicadScript: Function) { export function registerSymbolLibraryTools(server: McpServer, callKicadScript: Function) {
// List available symbol libraries // List available symbol libraries
@@ -19,20 +19,20 @@ export function registerSymbolLibraryTools(server: McpServer, callKicadScript: F
content: [ content: [
{ {
type: "text", 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 { return {
content: [ content: [
{ {
type: "text", 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 // 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.`, Returns symbol references that can be used directly in schematics.`,
{ {
query: z.string() query: z.string().describe("Search query (e.g., 'ESP32', 'STM32F103', 'C8734' for LCSC ID)"),
.describe("Search query (e.g., 'ESP32', 'STM32F103', 'C8734' for LCSC ID)"), library: z
library: z.string().optional() .string()
.optional()
.describe("Optional: filter to specific library name pattern (e.g., 'JLCPCB')"), .describe("Optional: filter to specific library name pattern (e.g., 'JLCPCB')"),
limit: z.number().optional().default(20) limit: z.number().optional().default(20).describe("Maximum number of results to return"),
.describe("Maximum number of results to return")
}, },
async (args: { query: string; library?: string; limit?: number }) => { async (args: { query: string; library?: string; limit?: number }) => {
const result = await callKicadScript("search_symbols", args); const result = await callKicadScript("search_symbols", args);
@@ -60,38 +60,40 @@ Returns symbol references that can be used directly in schematics.`,
content: [ content: [
{ {
type: "text", 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
const parts = [`${s.full_ref}`]; .map((s: any) => {
if (s.lcsc_id) parts.push(`LCSC: ${s.lcsc_id}`); const parts = [`${s.full_ref}`];
if (s.description) parts.push(s.description); if (s.lcsc_id) parts.push(`LCSC: ${s.lcsc_id}`);
else if (s.value) parts.push(s.value); if (s.description) parts.push(s.description);
return parts.join(' | '); else if (s.value) parts.push(s.value);
}).join('\n'); return parts.join(" | ");
})
.join("\n");
return { return {
content: [ content: [
{ {
type: "text", 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 { return {
content: [ content: [
{ {
type: "text", 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 // List symbols in a specific library
@@ -99,36 +101,37 @@ Returns symbol references that can be used directly in schematics.`,
"list_library_symbols", "list_library_symbols",
"List all symbols in a specific KiCAD symbol library", "List all symbols in a specific KiCAD symbol library",
{ {
library: z.string() library: z.string().describe("Library name (e.g., 'Device', 'PCM_JLCPCB-MCUs')"),
.describe("Library name (e.g., 'Device', 'PCM_JLCPCB-MCUs')")
}, },
async (args: { library: string }) => { async (args: { library: string }) => {
const result = await callKicadScript("list_library_symbols", args); const result = await callKicadScript("list_library_symbols", args);
if (result.success && result.symbols) { if (result.success && result.symbols) {
const symbolList = result.symbols.map((s: any) => { const symbolList = result.symbols
const parts = [` - ${s.name}`]; .map((s: any) => {
if (s.lcsc_id) parts.push(`(LCSC: ${s.lcsc_id})`); const parts = [` - ${s.name}`];
return parts.join(' '); if (s.lcsc_id) parts.push(`(LCSC: ${s.lcsc_id})`);
}).join('\n'); return parts.join(" ");
})
.join("\n");
return { return {
content: [ content: [
{ {
type: "text", type: "text",
text: `Library "${args.library}" contains ${result.count} symbols:\n${symbolList}` text: `Library "${args.library}" contains ${result.count} symbols:\n${symbolList}`,
} },
] ],
}; };
} }
return { return {
content: [ content: [
{ {
type: "text", 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 // 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_symbol_info",
"Get detailed information about a specific symbol", "Get detailed information about a specific symbol",
{ {
symbol: z.string() symbol: z
.describe("Symbol specification (e.g., 'Device:R' or 'PCM_JLCPCB-MCUs:STM32F103C8T6')") .string()
.describe("Symbol specification (e.g., 'Device:R' or 'PCM_JLCPCB-MCUs:STM32F103C8T6')"),
}, },
async (args: { symbol: string }) => { async (args: { symbol: string }) => {
const result = await callKicadScript("get_symbol_info", args); 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 info = result.symbol_info;
const details = [ const details = [
`Symbol: ${info.full_ref}`, `Symbol: ${info.full_ref}`,
info.value ? `Value: ${info.value}` : '', info.value ? `Value: ${info.value}` : "",
info.description ? `Description: ${info.description}` : '', info.description ? `Description: ${info.description}` : "",
info.lcsc_id ? `LCSC: ${info.lcsc_id}` : '', info.lcsc_id ? `LCSC: ${info.lcsc_id}` : "",
info.manufacturer ? `Manufacturer: ${info.manufacturer}` : '', info.manufacturer ? `Manufacturer: ${info.manufacturer}` : "",
info.mpn ? `MPN: ${info.mpn}` : '', info.mpn ? `MPN: ${info.mpn}` : "",
info.footprint ? `Footprint: ${info.footprint}` : '', info.footprint ? `Footprint: ${info.footprint}` : "",
info.category ? `Category: ${info.category}` : '', info.category ? `Category: ${info.category}` : "",
info.lib_class ? `Class: ${info.lib_class}` : '', info.lib_class ? `Class: ${info.lib_class}` : "",
info.datasheet ? `Datasheet: ${info.datasheet}` : '', info.datasheet ? `Datasheet: ${info.datasheet}` : "",
].filter(line => line).join('\n'); ]
.filter((line) => line)
.join("\n");
return { return {
content: [ content: [
{ {
type: "text", type: "text",
text: details text: details,
} },
] ],
}; };
} }
return { return {
content: [ content: [
{ {
type: "text", 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 { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod"; import { z } from "zod";
export function registerLibraryTools( export function registerLibraryTools(server: McpServer, callKicadScript: Function) {
server: McpServer,
callKicadScript: Function,
) {
// List available footprint libraries // List available footprint libraries
server.tool( server.tool(
"list_libraries", "list_libraries",
@@ -48,18 +45,9 @@ export function registerLibraryTools(
"search_footprints", "search_footprints",
"Search for footprints matching a pattern across all libraries", "Search for footprints matching a pattern across all libraries",
{ {
search_term: z search_term: z.string().describe("Search term or pattern to match footprint names"),
.string() library: z.string().optional().describe("Optional specific library to search in"),
.describe("Search term or pattern to match footprint names"), limit: z.number().optional().default(50).describe("Maximum number of results to return"),
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 }) => { async (args: { search_term: string; library?: string; limit?: number }) => {
const result = await callKicadScript("search_footprints", { const result = await callKicadScript("search_footprints", {
@@ -99,25 +87,14 @@ export function registerLibraryTools(
"list_library_footprints", "list_library_footprints",
"List all footprints in a specific KiCAD library", "List all footprints in a specific KiCAD library",
{ {
library_name: z library_name: z.string().describe("Name of the library to list footprints from"),
.string() filter: z.string().optional().describe("Optional filter pattern for footprint names"),
.describe("Name of the library to list footprints from"), limit: z.number().optional().default(100).describe("Maximum number of footprints to list"),
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 }) => { async (args: { library_name: string; filter?: string; limit?: number }) => {
const result = await callKicadScript("list_library_footprints", args); const result = await callKicadScript("list_library_footprints", args);
if (result.success && result.footprints) { if (result.success && result.footprints) {
const footprintList = result.footprints const footprintList = result.footprints.map((fp: string) => ` - ${fp}`).join("\n");
.map((fp: string) => ` - ${fp}`)
.join("\n");
return { return {
content: [ content: [
{ {
@@ -143,12 +120,8 @@ export function registerLibraryTools(
"get_footprint_info", "get_footprint_info",
"Get detailed information about a specific footprint", "Get detailed information about a specific footprint",
{ {
library_name: z library_name: z.string().describe("Name of the library containing the footprint"),
.string() footprint_name: z.string().describe("Name of the footprint to get information about"),
.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 }) => { async (args: { library_name: string; footprint_name: string }) => {
const result = await callKicadScript("get_footprint_info", args); const result = await callKicadScript("get_footprint_info", args);
@@ -156,15 +129,16 @@ export function registerLibraryTools(
const info = result.info; const info = result.info;
// pads is a list of {number, type, shape} objects // pads is a list of {number, type, shape} objects
const padsArray: Array<{ number: string; type: string; shape: string }> = const padsArray: Array<{ number: string; type: string; shape: string }> = Array.isArray(
Array.isArray(info.pads) ? info.pads : []; info.pads,
)
? info.pads
: [];
const padsSummary = padsArray.length const padsSummary = padsArray.length
? `${padsArray.length} pads: ${padsArray.map((p) => p.number).join(", ")}` ? `${padsArray.length} pads: ${padsArray.map((p) => p.number).join(", ")}`
: ""; : "";
const padsDetail = padsArray.length const padsDetail = padsArray.length
? padsArray ? padsArray.map((p) => ` pad ${p.number}: ${p.type} ${p.shape}`).join("\n")
.map((p) => ` pad ${p.number}: ${p.type} ${p.shape}`)
.join("\n")
: ""; : "";
const details = [ const details = [
@@ -178,9 +152,7 @@ export function registerLibraryTools(
info.courtyard info.courtyard
? `Courtyard size: ${info.courtyard.width}mm x ${info.courtyard.height}mm` ? `Courtyard size: ${info.courtyard.width}mm x ${info.courtyard.height}mm`
: "", : "",
info.attributes info.attributes ? `Attributes: ${JSON.stringify(info.attributes)}` : "",
? `Attributes: ${JSON.stringify(info.attributes)}`
: "",
] ]
.filter((line) => line) .filter((line) => line)
.join("\n"); .join("\n");

View File

@@ -1,99 +1,116 @@
/** /**
* Project management tools for KiCAD MCP server * Project management tools for KiCAD MCP server
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
export function registerProjectTools(server: McpServer, callKicadScript: Function) { export function registerProjectTools(server: McpServer, callKicadScript: Function) {
// Create project tool // Create project tool
server.tool( server.tool(
"create_project", "create_project",
"Create a new KiCAD project", "Create a new KiCAD project",
{ {
path: z.string().describe("Project directory path"), path: z.string().describe("Project directory path"),
name: z.string().describe("Project name"), name: z.string().describe("Project name"),
}, },
async (args: { path: string; name: string }) => { async (args: { path: string; name: string }) => {
const result = await callKicadScript("create_project", args); const result = await callKicadScript("create_project", args);
return { return {
content: [{ content: [
type: "text", {
text: JSON.stringify(result, null, 2) type: "text",
}] text: JSON.stringify(result, null, 2),
}; },
} ],
); };
},
// Open project tool );
server.tool(
"open_project", // Open project tool
"Open an existing KiCAD project", server.tool(
{ "open_project",
filename: z.string().describe("Path to .kicad_pro or .kicad_pcb file"), "Open an existing KiCAD project",
}, {
async (args: { filename: string }) => { filename: z.string().describe("Path to .kicad_pro or .kicad_pcb file"),
const result = await callKicadScript("open_project", args); },
return { async (args: { filename: string }) => {
content: [{ const result = await callKicadScript("open_project", args);
type: "text", return {
text: JSON.stringify(result, null, 2) content: [
}] {
}; type: "text",
} text: JSON.stringify(result, null, 2),
); },
],
// Save project tool };
server.tool( },
"save_project", );
"Save the current KiCAD project",
{ // Save project tool
path: z.string().optional().describe("Optional new path to save to"), server.tool(
}, "save_project",
async (args: { path?: string }) => { "Save the current KiCAD project",
const result = await callKicadScript("save_project", args); {
return { path: z.string().optional().describe("Optional new path to save to"),
content: [{ },
type: "text", async (args: { path?: string }) => {
text: JSON.stringify(result, null, 2) const result = await callKicadScript("save_project", args);
}] return {
}; content: [
} {
); type: "text",
text: JSON.stringify(result, null, 2),
// Get project info tool },
server.tool( ],
"get_project_info", };
"Get information about the current KiCAD project", },
{}, );
async () => {
const result = await callKicadScript("get_project_info", {}); // Get project info tool
return { server.tool(
content: [{ "get_project_info",
type: "text", "Get information about the current KiCAD project",
text: JSON.stringify(result, null, 2) {},
}] async () => {
}; const result = await callKicadScript("get_project_info", {});
} return {
); content: [
{
// Snapshot project tool — saves a named checkpoint as PDF/image type: "text",
server.tool( text: JSON.stringify(result, null, 2),
"snapshot_project", },
"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"),
}, // Snapshot project tool — saves a named checkpoint as PDF/image
async (args: { step: string; label: string; prompt?: string }) => { server.tool(
const result = await callKicadScript("snapshot_project", args); "snapshot_project",
return { "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.",
content: [{ {
type: "text", step: z.string().describe("Step number or identifier, e.g. '1' or '2'"),
text: JSON.stringify(result, null, 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",
),
},
async (args: { step: string; label: string; prompt?: string }) => {
const result = await callKicadScript("snapshot_project", args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
},
);
}

View File

@@ -1,308 +1,295 @@
/** /**
* Tool Registry for KiCAD MCP Server * Tool Registry for KiCAD MCP Server
* *
* Centralizes all tool definitions and provides lookup/search functionality * Centralizes all tool definitions and provides lookup/search functionality
*/ */
import { z } from 'zod'; import { z } from "zod";
export interface ToolDefinition { export interface ToolDefinition {
name: string; name: string;
description: string; description: string;
inputSchema: z.ZodObject<any> | z.ZodType<any>; inputSchema: z.ZodObject<any> | z.ZodType<any>;
// Handler will be registered separately in the existing tool files // Handler will be registered separately in the existing tool files
} }
export interface ToolCategory { export interface ToolCategory {
name: string; name: string;
description: string; description: string;
tools: string[]; // Tool names in this category tools: string[]; // Tool names in this category
} }
/** /**
* Tool category definitions * Tool category definitions
* Each category groups related tools for better organization * Each category groups related tools for better organization
*/ */
export const toolCategories: ToolCategory[] = [ export const toolCategories: ToolCategory[] = [
{ {
name: "board", name: "board",
description: "Board configuration: layers, mounting holes, zones, visualization", description: "Board configuration: layers, mounting holes, zones, visualization",
tools: [ tools: [
"add_layer", "add_layer",
"set_active_layer", "set_active_layer",
"get_layer_list", "get_layer_list",
"add_mounting_hole", "add_mounting_hole",
"add_board_text", "add_board_text",
"add_zone", "add_zone",
"get_board_extents", "get_board_extents",
"get_board_2d_view", "get_board_2d_view",
"launch_kicad_ui" "launch_kicad_ui",
] ],
}, },
{ {
name: "component", name: "component",
description: "Advanced component operations: edit, delete, search, group, annotate", description: "Advanced component operations: edit, delete, search, group, annotate",
tools: [ tools: [
"rotate_component", "rotate_component",
"delete_component", "delete_component",
"edit_component", "edit_component",
"find_component", "find_component",
"get_component_properties", "get_component_properties",
"add_component_annotation", "add_component_annotation",
"group_components", "group_components",
"replace_component" "replace_component",
] ],
}, },
{ {
name: "export", name: "export",
description: "File export for fabrication and documentation: Gerber, PDF, BOM, 3D models", description: "File export for fabrication and documentation: Gerber, PDF, BOM, 3D models",
tools: [ tools: [
"export_gerber", "export_gerber",
"export_pdf", "export_pdf",
"export_svg", "export_svg",
"export_3d", "export_3d",
"export_bom", "export_bom",
"export_netlist", "export_netlist",
"export_position_file", "export_position_file",
"export_vrml" "export_vrml",
] ],
}, },
{ {
name: "drc", name: "drc",
description: "Design rule checking and electrical validation: DRC, net classes, clearances", description: "Design rule checking and electrical validation: DRC, net classes, clearances",
tools: [ tools: [
"set_design_rules", "set_design_rules",
"get_design_rules", "get_design_rules",
"run_drc", "run_drc",
"add_net_class", "add_net_class",
"assign_net_to_class", "assign_net_to_class",
"set_layer_constraints", "set_layer_constraints",
"check_clearance", "check_clearance",
"get_drc_violations" "get_drc_violations",
] ],
}, },
{ {
name: "schematic", name: "schematic",
description: "Schematic operations: create, inspect, add/edit/delete components, wire connections, netlists, annotation", description:
tools: [ "Schematic operations: create, inspect, add/edit/delete components, wire connections, netlists, annotation",
"create_schematic", tools: [
"add_schematic_component", "create_schematic",
"list_schematic_components", "add_schematic_component",
"move_schematic_component", "list_schematic_components",
"rotate_schematic_component", "move_schematic_component",
"annotate_schematic", "rotate_schematic_component",
"add_schematic_wire", "annotate_schematic",
"delete_schematic_wire", "add_schematic_wire",
"add_schematic_junction", "delete_schematic_wire",
"add_schematic_net_label", "add_schematic_junction",
"delete_schematic_net_label", "add_schematic_net_label",
"connect_to_net", "delete_schematic_net_label",
"connect_passthrough", "connect_to_net",
"get_net_connections", "connect_passthrough",
"list_schematic_nets", "get_net_connections",
"list_schematic_wires", "list_schematic_nets",
"list_schematic_labels", "list_schematic_wires",
"get_wire_connections", "list_schematic_labels",
"generate_netlist", "get_wire_connections",
"sync_schematic_to_board", "generate_netlist",
"get_schematic_view", "sync_schematic_to_board",
"export_schematic_svg", "get_schematic_view",
"export_schematic_pdf" "export_schematic_svg",
] "export_schematic_pdf",
}, ],
{ },
name: "library", {
description: "Footprint library access: search, browse, get footprint information", name: "library",
tools: [ description: "Footprint library access: search, browse, get footprint information",
"list_libraries", tools: ["list_libraries", "search_footprints", "list_library_footprints", "get_footprint_info"],
"search_footprints", },
"list_library_footprints", {
"get_footprint_info" name: "routing",
] description: "Advanced routing operations: vias, copper pours",
}, tools: ["add_via", "add_copper_pour"],
{ },
name: "routing", {
description: "Advanced routing operations: vias, copper pours", name: "autoroute",
tools: [ description: "Freerouting autorouter: automatic PCB routing via Specctra DSN/SES",
"add_via", tools: ["autoroute", "export_dsn", "import_ses", "check_freerouting"],
"add_copper_pour" },
] ];
},
{ /**
name: "autoroute", * Direct tools that are always visible (not routed)
description: "Freerouting autorouter: automatic PCB routing via Specctra DSN/SES", * These are the most frequently used tools
tools: [ */
"autoroute", export const directToolNames = [
"export_dsn", // Project lifecycle
"import_ses", "create_project",
"check_freerouting" "open_project",
] "save_project",
} "snapshot_project",
]; "get_project_info",
/** // Core PCB operations
* Direct tools that are always visible (not routed) "place_component",
* These are the most frequently used tools "move_component",
*/ "add_net",
export const directToolNames = [ "route_trace",
// Project lifecycle "get_board_info",
"create_project", "set_board_size",
"open_project",
"save_project", // Board setup
"snapshot_project", "add_board_outline",
"get_project_info",
// Schematic essentials (always visible so AI uses them correctly)
// Core PCB operations "add_schematic_component",
"place_component", "list_schematic_components",
"move_component", "annotate_schematic",
"add_net", "connect_passthrough",
"route_trace", "connect_to_net",
"get_board_info", "add_schematic_net_label",
"set_board_size",
// Schematic <-> PCB sync (F8 equivalent)
// Board setup "sync_schematic_to_board",
"add_board_outline",
// UI management
// Schematic essentials (always visible so AI uses them correctly) "check_kicad_ui",
"add_schematic_component", ];
"list_schematic_components",
"annotate_schematic", // Build lookup maps at module load time
"connect_passthrough", const categoryMap = new Map<string, ToolCategory>();
"connect_to_net", const toolCategoryMap = new Map<string, string>();
"add_schematic_net_label",
export function initializeRegistry() {
// Schematic <-> PCB sync (F8 equivalent) // Build category map
"sync_schematic_to_board", for (const category of toolCategories) {
categoryMap.set(category.name, category);
// UI management
"check_kicad_ui" // Build tool -> category map
]; for (const toolName of category.tools) {
toolCategoryMap.set(toolName, category.name);
// Build lookup maps at module load time }
const categoryMap = new Map<string, ToolCategory>(); }
const toolCategoryMap = new Map<string, string>(); }
export function initializeRegistry() { /**
// Build category map * Get a category by name
for (const category of toolCategories) { */
categoryMap.set(category.name, category); export function getCategory(name: string): ToolCategory | undefined {
return categoryMap.get(name);
// Build tool -> category map }
for (const toolName of category.tools) {
toolCategoryMap.set(toolName, category.name); /**
} * Get the category name for a tool
} */
} export function getToolCategory(toolName: string): string | undefined {
return toolCategoryMap.get(toolName);
/** }
* Get a category by name
*/ /**
export function getCategory(name: string): ToolCategory | undefined { * Get all categories
return categoryMap.get(name); */
} export function getAllCategories(): ToolCategory[] {
return toolCategories;
/** }
* Get the category name for a tool
*/ /**
export function getToolCategory(toolName: string): string | undefined { * Get all routed tool names (excludes direct tools)
return toolCategoryMap.get(toolName); */
} export function getRoutedToolNames(): string[] {
const allRoutedTools: string[] = [];
/** for (const category of toolCategories) {
* Get all categories allRoutedTools.push(...category.tools);
*/ }
export function getAllCategories(): ToolCategory[] { return allRoutedTools;
return toolCategories; }
}
/**
/** * Check if a tool is a direct tool
* Get all routed tool names (excludes direct tools) */
*/ export function isDirectTool(toolName: string): boolean {
export function getRoutedToolNames(): string[] { return directToolNames.includes(toolName);
const allRoutedTools: string[] = []; }
for (const category of toolCategories) {
allRoutedTools.push(...category.tools); /**
} * Check if a tool is a routed tool
return allRoutedTools; */
} export function isRoutedTool(toolName: string): boolean {
return toolCategoryMap.has(toolName);
/** }
* Check if a tool is a direct tool
*/ /**
export function isDirectTool(toolName: string): boolean { * Search for tools by keyword
return directToolNames.includes(toolName); * Searches tool names, descriptions, and category names
} */
export interface SearchResult {
/** category: string;
* Check if a tool is a routed tool tool: string;
*/ description: string;
export function isRoutedTool(toolName: string): boolean { }
return toolCategoryMap.has(toolName);
} export function searchTools(query: string): SearchResult[] {
const q = query.toLowerCase();
/** const matches: SearchResult[] = [];
* Search for tools by keyword
* Searches tool names, descriptions, and category names // Search direct tools first
*/ for (const toolName of directToolNames) {
export interface SearchResult { if (toolName.toLowerCase().includes(q)) {
category: string; matches.push({
tool: string; category: "direct",
description: string; tool: toolName,
} description: `${toolName} (direct tool — call directly, no execute_tool needed)`,
});
export function searchTools(query: string): SearchResult[] { }
const q = query.toLowerCase(); }
const matches: SearchResult[] = [];
// Search routed tools by name and category
// Search direct tools first for (const category of toolCategories) {
for (const toolName of directToolNames) { const categoryMatch =
if (toolName.toLowerCase().includes(q)) { category.name.toLowerCase().includes(q) || category.description.toLowerCase().includes(q);
matches.push({
category: "direct", for (const toolName of category.tools) {
tool: toolName, if (toolName.toLowerCase().includes(q) || categoryMatch) {
description: `${toolName} (direct tool — call directly, no execute_tool needed)` matches.push({
}); category: category.name,
} tool: toolName,
} description: `${toolName} (${category.name})`,
});
// Search routed tools by name and category }
for (const category of toolCategories) { }
const categoryMatch = }
category.name.toLowerCase().includes(q) ||
category.description.toLowerCase().includes(q); return matches.slice(0, 20); // Limit results
}
for (const toolName of category.tools) {
if (toolName.toLowerCase().includes(q) || categoryMatch) { /**
matches.push({ * Get statistics about the tool registry
category: category.name, */
tool: toolName, export function getRegistryStats() {
description: `${toolName} (${category.name})` const routedToolCount = getRoutedToolNames().length;
}); const directToolCount = directToolNames.length;
}
} return {
} total_categories: toolCategories.length,
total_routed_tools: routedToolCount,
return matches.slice(0, 20); // Limit results total_direct_tools: directToolCount,
} total_tools: routedToolCount + directToolCount,
categories: toolCategories.map((c) => ({
/** name: c.name,
* Get statistics about the tool registry tool_count: c.tools.length,
*/ })),
export function getRegistryStats() { };
const routedToolCount = getRoutedToolNames().length; }
const directToolCount = directToolNames.length;
// Initialize on module load
return { initializeRegistry();
total_categories: toolCategories.length,
total_routed_tools: routedToolCount,
total_direct_tools: directToolCount,
total_tools: routedToolCount + directToolCount,
categories: toolCategories.map(c => ({
name: c.name,
tool_count: c.tools.length
}))
};
}
// Initialize on module load
initializeRegistry();

View File

@@ -1,251 +1,296 @@
/** /**
* Router Tools for KiCAD MCP Server * Router Tools for KiCAD MCP Server
* *
* Provides discovery and execution of routed tools * Provides discovery and execution of routed tools
*/ */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod'; import { z } from "zod";
import { logger } from '../logger.js'; import { logger } from "../logger.js";
import { import {
getAllCategories, getAllCategories,
getCategory, getCategory,
getToolCategory, getToolCategory,
searchTools as registrySearchTools, searchTools as registrySearchTools,
getRegistryStats getRegistryStats,
} from './registry.js'; } from "./registry.js";
// Command function type for KiCAD script calls // 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>;
// Map to store tool execution handlers // Map to store tool execution handlers
// This will be populated by registerToolHandler() // This will be populated by registerToolHandler()
const toolHandlers = new Map<string, (params: any) => Promise<any>>(); const toolHandlers = new Map<string, (params: any) => Promise<any>>();
/** /**
* Register a tool handler for execution via execute_tool * Register a tool handler for execution via execute_tool
* This should be called by each tool registration function * This should be called by each tool registration function
*/ */
export function registerToolHandler( export function registerToolHandler(
toolName: string, toolName: string,
handler: (params: any) => Promise<any> handler: (params: any) => Promise<any>,
): void { ): void {
toolHandlers.set(toolName, handler); toolHandlers.set(toolName, handler);
logger.debug(`Registered handler for routed tool: ${toolName}`); logger.debug(`Registered handler for routed tool: ${toolName}`);
} }
/** /**
* Register all router tools with the MCP server * Register all router tools with the MCP server
*/ */
export function registerRouterTools(server: McpServer, callKicadScript: CommandFunction): void { export function registerRouterTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering router tools'); logger.info("Registering router tools");
// ============================================================================ // ============================================================================
// list_tool_categories // list_tool_categories
// ============================================================================ // ============================================================================
server.tool( server.tool(
"list_tool_categories", "list_tool_categories",
{ {
// No parameters // No parameters
}, },
async () => { async () => {
logger.debug('Listing tool categories'); logger.debug("Listing tool categories");
const stats = getRegistryStats(); const stats = getRegistryStats();
const categories = getAllCategories(); const categories = getAllCategories();
const result = { const result = {
total_categories: stats.total_categories, total_categories: stats.total_categories,
total_routed_tools: stats.total_routed_tools, total_routed_tools: stats.total_routed_tools,
total_direct_tools: stats.total_direct_tools, total_direct_tools: stats.total_direct_tools,
note: "Use get_category_tools to see tools in each category. Direct tools are always available.", 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, name: c.name,
description: c.description, description: c.description,
tool_count: c.tools.length tool_count: c.tools.length,
})) })),
}; };
return { return {
content: [{ content: [
type: "text", {
text: JSON.stringify(result, null, 2) type: "text",
}] text: JSON.stringify(result, null, 2),
}; },
} ],
); };
},
// ============================================================================ );
// get_category_tools
// ============================================================================ // ============================================================================
server.tool( // get_category_tools
"get_category_tools", // ============================================================================
{ server.tool(
category: z.string().describe("Category name from list_tool_categories") "get_category_tools",
}, {
async ({ category }) => { category: z.string().describe("Category name from list_tool_categories"),
logger.debug(`Getting tools for category: ${category}`); },
async ({ category }) => {
const categoryData = getCategory(category); logger.debug(`Getting tools for category: ${category}`);
if (!categoryData) { const categoryData = getCategory(category);
const availableCategories = getAllCategories().map(c => c.name);
return { if (!categoryData) {
content: [{ const availableCategories = getAllCategories().map((c) => c.name);
type: "text", return {
text: JSON.stringify({ content: [
error: `Unknown category: ${category}`, {
available_categories: availableCategories type: "text",
}, null, 2) text: JSON.stringify(
}] {
}; error: `Unknown category: ${category}`,
} available_categories: availableCategories,
},
// Return tool names and basic info null,
// Full schema is available via tool introspection once tool is called 2,
const result = { ),
category: categoryData.name, },
description: categoryData.description, ],
tool_count: categoryData.tools.length, };
tools: categoryData.tools.map(toolName => ({ }
name: toolName,
description: `Use execute_tool with tool_name="${toolName}" to run this tool` // Return tool names and basic info
})), // Full schema is available via tool introspection once tool is called
note: "Use execute_tool to run any of these tools with appropriate parameters" const result = {
}; category: categoryData.name,
description: categoryData.description,
return { tool_count: categoryData.tools.length,
content: [{ tools: categoryData.tools.map((toolName) => ({
type: "text", name: toolName,
text: JSON.stringify(result, null, 2) 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",
} };
);
return {
// ============================================================================ content: [
// execute_tool {
// ============================================================================ type: "text",
server.tool( text: JSON.stringify(result, null, 2),
"execute_tool", },
{ ],
tool_name: z.string().describe("Tool name from get_category_tools"), };
params: z.record(z.unknown()).optional().describe("Tool parameters (optional)") },
}, );
async ({ tool_name, params }) => {
logger.info(`Executing routed tool: ${tool_name}`); // ============================================================================
// execute_tool
// Check if tool exists in registry // ============================================================================
const category = getToolCategory(tool_name); server.tool(
"execute_tool",
if (!category) { {
return { tool_name: z.string().describe("Tool name from get_category_tools"),
content: [{ params: z.record(z.unknown()).optional().describe("Tool parameters (optional)"),
type: "text", },
text: JSON.stringify({ async ({ tool_name, params }) => {
error: `Unknown tool: ${tool_name}`, logger.info(`Executing routed tool: ${tool_name}`);
hint: "Use list_tool_categories and get_category_tools to find available tools"
}, null, 2) // Check if tool exists in registry
}] const category = getToolCategory(tool_name);
};
} if (!category) {
return {
// Get the handler content: [
const handler = toolHandlers.get(tool_name); {
type: "text",
if (!handler) { text: JSON.stringify(
// Tool is in registry but handler not registered yet {
// This means the tool exists but hasn't been migrated to router pattern yet error: `Unknown tool: ${tool_name}`,
// Fall back to calling KiCAD script directly hint: "Use list_tool_categories and get_category_tools to find available tools",
logger.warn(`Tool ${tool_name} in registry but no handler registered, falling back to direct call`); },
null,
try { 2,
const result = await callKicadScript(tool_name, params || {}); ),
return { },
content: [{ ],
type: "text", };
text: JSON.stringify({ }
tool: tool_name,
category: category, // Get the handler
result: result const handler = toolHandlers.get(tool_name);
}, null, 2)
}] if (!handler) {
}; // Tool is in registry but handler not registered yet
} catch (error) { // This means the tool exists but hasn't been migrated to router pattern yet
return { // Fall back to calling KiCAD script directly
content: [{ logger.warn(
type: "text", `Tool ${tool_name} in registry but no handler registered, falling back to direct call`,
text: JSON.stringify({ );
error: `Tool execution failed: ${(error as Error).message}`,
tool: tool_name, try {
category: category const result = await callKicadScript(tool_name, params || {});
}, null, 2) return {
}] content: [
}; {
} type: "text",
} text: JSON.stringify(
{
// Execute the tool via its handler tool: tool_name,
try { category: category,
const result = await handler(params || {}); result: result,
},
// The handler already returns MCP-formatted response null,
// Just add metadata 2,
return { ),
content: [{ },
type: "text", ],
text: JSON.stringify({ };
tool: tool_name, } catch (error) {
category: category, return {
...result content: [
}, null, 2) {
}] type: "text",
}; text: JSON.stringify(
} catch (error) { {
return { error: `Tool execution failed: ${(error as Error).message}`,
content: [{ tool: tool_name,
type: "text", category: category,
text: JSON.stringify({ },
error: `Tool execution failed: ${(error as Error).message}`, null,
tool: tool_name, 2,
category: category ),
}, null, 2) },
}] ],
}; };
} }
} }
);
// Execute the tool via its handler
// ============================================================================ try {
// search_tools const result = await handler(params || {});
// ============================================================================
server.tool( // The handler already returns MCP-formatted response
"search_tools", // Just add metadata
{ return {
query: z.string().describe("Search term (e.g., 'gerber', 'zone', 'export', 'drc')") content: [
}, {
async ({ query }) => { type: "text",
logger.debug(`Searching tools for: ${query}`); text: JSON.stringify(
{
const matches = registrySearchTools(query); tool: tool_name,
category: category,
const result = { ...result,
query: query, },
count: matches.length, null,
matches: matches, 2,
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." ],
}; };
} catch (error) {
return { return {
content: [{ content: [
type: "text", {
text: JSON.stringify(result, null, 2) type: "text",
}] text: JSON.stringify(
}; {
} error: `Tool execution failed: ${(error as Error).message}`,
); tool: tool_name,
category: category,
logger.info('Router tools registered successfully'); },
} null,
2,
),
},
],
};
}
},
);
// ============================================================================
// search_tools
// ============================================================================
server.tool(
"search_tools",
{
query: z.string().describe("Search term (e.g., 'gerber', 'zone', 'export', 'drc')"),
},
async ({ query }) => {
logger.debug(`Searching tools for: ${query}`);
const matches = registrySearchTools(query);
const result = {
query: query,
count: matches.length,
matches: matches,
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.",
};
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
},
);
logger.info("Router tools registered successfully");
}

View File

@@ -5,10 +5,7 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod"; import { z } from "zod";
export function registerRoutingTools( export function registerRoutingTools(server: McpServer, callKicadScript: Function) {
server: McpServer,
callKicadScript: Function,
) {
// Add net tool // Add net tool
server.tool( server.tool(
"add_net", "add_net",
@@ -79,10 +76,7 @@ export function registerRoutingTools(
}) })
.describe("Via position"), .describe("Via position"),
net: z.string().describe("Net name"), net: z.string().describe("Net name"),
viaType: z viaType: z.string().optional().describe("Via type (through, blind, buried)"),
.string()
.optional()
.describe("Via type (through, blind, buried)"),
}, },
async (args: any) => { async (args: any) => {
const result = await callKicadScript("add_via", args); const result = await callKicadScript("add_via", args);
@@ -130,10 +124,7 @@ export function registerRoutingTools(
"delete_trace", "delete_trace",
"Delete traces from the PCB. Can delete by UUID, position, or bulk-delete all traces on a net.", "Delete traces from the PCB. Can delete by UUID, position, or bulk-delete all traces on a net.",
{ {
traceUuid: z traceUuid: z.string().optional().describe("UUID of a specific trace to delete"),
.string()
.optional()
.describe("UUID of a specific trace to delete"),
position: z position: z
.object({ .object({
x: z.number(), x: z.number(),
@@ -142,18 +133,9 @@ export function registerRoutingTools(
}) })
.optional() .optional()
.describe("Delete trace nearest to this position"), .describe("Delete trace nearest to this position"),
net: z net: z.string().optional().describe("Delete all traces on this net (bulk delete)"),
.string() layer: z.string().optional().describe("Filter by layer when using net-based deletion"),
.optional() includeVias: z.boolean().optional().describe("Include vias in net-based deletion"),
.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) => { async (args: any) => {
const result = await callKicadScript("delete_trace", args); const result = await callKicadScript("delete_trace", args);
@@ -209,10 +191,7 @@ export function registerRoutingTools(
.boolean() .boolean()
.optional() .optional()
.describe("Include statistics (track count, total length, etc.)"), .describe("Include statistics (track count, total length, etc.)"),
unit: z unit: z.enum(["mm", "inch"]).optional().describe("Unit for length measurements"),
.enum(["mm", "inch"])
.optional()
.describe("Unit for length measurements"),
}, },
async (args: any) => { async (args: any) => {
const result = await callKicadScript("get_nets_list", args); 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.", "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')"), 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')"), 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)"), layer: z.string().optional().describe("PCB layer (default: F.Cu)"),
width: z.number().optional().describe("Trace width in mm (default: board default)"), 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)"), net: z.string().optional().describe("Net name override (default: auto-detected from pad)"),
@@ -362,10 +345,7 @@ export function registerRoutingTools(
.describe( .describe(
"References of the target components in same order as sourceRefs (e.g. ['U2', 'R2', 'C2'])", "References of the target components in same order as sourceRefs (e.g. ['U2', 'R2', 'C2'])",
), ),
includeVias: z includeVias: z.boolean().optional().describe("Also copy vias (default: true)"),
.boolean()
.optional()
.describe("Also copy vias (default: true)"),
traceWidth: z traceWidth: z
.number() .number()
.optional() .optional()

View File

@@ -5,10 +5,7 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod"; import { z } from "zod";
export function registerSchematicTools( export function registerSchematicTools(server: McpServer, callKicadScript: Function) {
server: McpServer,
callKicadScript: Function,
) {
// Create schematic tool // Create schematic tool
server.tool( server.tool(
"create_schematic", "create_schematic",
@@ -38,9 +35,7 @@ export function registerSchematicTools(
schematicPath: z.string().describe("Path to the schematic file"), schematicPath: z.string().describe("Path to the schematic file"),
symbol: z symbol: z
.string() .string()
.describe( .describe("Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3)"),
"Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3)",
),
reference: z.string().describe("Component reference (e.g., R1, U1)"), reference: z.string().describe("Component reference (e.g., R1, U1)"),
value: z.string().optional().describe("Component value"), value: z.string().optional().describe("Component value"),
footprint: z footprint: z
@@ -82,10 +77,7 @@ export function registerSchematicTools(
}, },
}; };
const result = await callKicadScript( const result = await callKicadScript("add_schematic_component", transformed);
"add_schematic_component",
transformed,
);
if (result.success) { if (result.success) {
return { return {
content: [ 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"), schematicPath: z.string().describe("Path to the .kicad_sch file"),
reference: z reference: z
.string() .string()
.describe( .describe("Reference designator of the component to remove (e.g. R1, U3)"),
"Reference designator of the component to remove (e.g. R1, U3)",
),
}, },
async (args: { schematicPath: string; reference: string }) => { async (args: { schematicPath: string; reference: string }) => {
const result = await callKicadScript("delete_schematic_component", args); 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"), schematicPath: z.string().describe("Path to the .kicad_sch file"),
reference: z.string().describe("Current reference designator of the component (e.g. R1, U3)"), 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)"), 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)"), newReference: z
fieldPositions: z.record(z.object({ .string()
x: z.number(), .optional()
y: z.number(), .describe("Rename the reference designator (e.g. R1 → R10)"),
angle: z.number().optional().default(0), fieldPositions: z
})).optional().describe("Reposition field labels: map of field name to {x, y, angle} (e.g. {\"Reference\": {\"x\": 12.5, \"y\": 17.0}})"), .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}})',
),
}, },
async (args: { async (args: {
schematicPath: string; schematicPath: string;
@@ -220,20 +223,24 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
: "unknown"; : "unknown";
const fieldLines = Object.entries(result.fields ?? {}).map( const fieldLines = Object.entries(result.fields ?? {}).map(
([name, f]: [string, any]) => ([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 { return {
content: [{ content: [
type: "text", {
text: `Component ${result.reference} at ${pos}\nFields:\n${fieldLines.join("\n")}`, type: "text",
}], text: `Component ${result.reference} at ${pos}\nFields:\n${fieldLines.join("\n")}`,
},
],
}; };
} }
return { return {
content: [{ content: [
type: "text", {
text: `Failed to get component: ${result.message || "Unknown error"}`, 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 snapToPins: z
.boolean() .boolean()
.optional() .optional()
.describe( .describe("Snap the first and last waypoints to the nearest pin (default: true)"),
"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)"),
),
snapTolerance: z
.number()
.optional()
.describe("Maximum snap distance in mm (default: 1.0)"),
}, },
async (args: { async (args: {
schematicPath: string; 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.", "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"), schematicPath: z.string().describe("Path to the .kicad_sch file"),
position: z position: z.array(z.number()).length(2).describe("Junction position [x, y] in mm"),
.array(z.number())
.length(2)
.describe("Junction position [x, y] in mm"),
}, },
async (args: { schematicPath: string; position: number[] }) => { async (args: { schematicPath: string; position: number[] }) => {
const result = await callKicadScript("add_schematic_junction", args); 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", "Add a net label to the schematic",
{ {
schematicPath: z.string().describe("Path to the schematic file"), schematicPath: z.string().describe("Path to the schematic file"),
netName: z netName: z.string().describe("Name of the net (e.g., VCC, GND, SIGNAL_1)"),
.string() position: z.array(z.number()).length(2).describe("Position [x, y] for the label"),
.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: { async (args: { schematicPath: string; netName: string; position: number[] }) => {
schematicPath: string;
netName: string;
position: number[];
}) => {
const result = await callKicadScript("add_schematic_net_label", args); const result = await callKicadScript("add_schematic_net_label", args);
if (result.success) { if (result.success) {
return { 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 }) => { async (args: { schematicPath: string; x: number; y: number }) => {
const result = await callKicadScript("get_wire_connections", args); const result = await callKicadScript("get_wire_connections", args);
if (result.success && result.pins) { if (result.success && result.pins) {
const pinList = result.pins const pinList = result.pins.map((p: any) => ` - ${p.component}/${p.pin}`).join("\n");
.map((p: any) => ` - ${p.component}/${p.pin}`)
.join("\n");
const wireList = (result.wires ?? []) const wireList = (result.wires ?? [])
.map((w: any) => ` - (${w.start.x},${w.start.y}) → (${w.end.x},${w.end.y})`) .map((w: any) => ` - (${w.start.x},${w.start.y}) → (${w.end.x},${w.end.y})`)
.join("\n"); .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.", "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"), schematicPath: z.string().describe("Path to the schematic file"),
reference: z reference: z.string().describe("Component reference designator (e.g. U1, R1, J2)"),
.string()
.describe("Component reference designator (e.g. U1, R1, J2)"),
}, },
async (args: { schematicPath: string; reference: string }) => { async (args: { schematicPath: string; reference: string }) => {
const result = await callKicadScript("get_schematic_pin_locations", args); 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; pinOffset?: number;
}) => { }) => {
const result = await callKicadScript("connect_passthrough", args); const result = await callKicadScript("connect_passthrough", args);
if ( if (result.success !== false || (result.connected && result.connected.length > 0)) {
result.success !== false ||
(result.connected && result.connected.length > 0)
) {
const lines: string[] = []; const lines: string[] = [];
if (result.connected?.length) if (result.connected?.length)
lines.push( lines.push(
`Connected (${result.connected.length}): ${result.connected.slice(0, 5).join(", ")}${result.connected.length > 5 ? " ..." : ""}`, `Connected (${result.connected.length}): ${result.connected.slice(0, 5).join(", ")}${result.connected.length > 5 ? " ..." : ""}`,
); );
if (result.failed?.length) if (result.failed?.length)
lines.push( lines.push(`Failed (${result.failed.length}): ${result.failed.join(", ")}`);
`Failed (${result.failed.length}): ${result.failed.join(", ")}`,
);
return { return {
content: [ content: [{ type: "text", text: result.message + "\n" + lines.join("\n") }],
{ type: "text", text: result.message + "\n" + lines.join("\n") },
],
}; };
} else { } else {
return { return {
@@ -581,7 +560,10 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
filter: z filter: z
.object({ .object({
libId: z.string().optional().describe("Filter by library ID (e.g., 'Device:R')"), 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() .optional()
.describe("Optional filters"), .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 lines = nets.map((n: any) => {
const conns = (n.connections || []) const conns = (n.connections || []).map((c: any) => `${c.component}/${c.pin}`).join(", ");
.map((c: any) => `${c.component}/${c.pin}`)
.join(", ");
return ` ${n.name}: ${conns || "(no connections)"}`; return ` ${n.name}: ${conns || "(no connections)"}`;
}); });
return { return {
@@ -680,8 +660,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
}; };
} }
const lines = wires.map( const lines = wires.map(
(w: any) => (w: any) => ` (${w.start.x}, ${w.start.y}) → (${w.end.x}, ${w.end.y})`,
` (${w.start.x}, ${w.start.y}) → (${w.end.x}, ${w.end.y})`,
); );
return { return {
content: [ content: [
@@ -718,8 +697,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
}; };
} }
const lines = labels.map( const lines = labels.map(
(l: any) => (l: any) => ` [${l.type}] ${l.name} at (${l.position.x}, ${l.position.y})`,
` [${l.type}] ${l.name} at (${l.position.x}, ${l.position.y})`,
); );
return { return {
content: [ 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"), schematicPath: z.string().describe("Path to the .kicad_sch file"),
reference: z.string().describe("Reference designator (e.g., R1, U1)"), reference: z.string().describe("Reference designator (e.g., R1, U1)"),
angle: z.number().describe("Rotation angle in degrees (0, 90, 180, 270)"), angle: z.number().describe("Rotation angle in degrees (0, 90, 180, 270)"),
mirror: z mirror: z.enum(["x", "y"]).optional().describe("Optional mirror axis"),
.enum(["x", "y"])
.optional()
.describe("Optional mirror axis"),
}, },
async (args: { async (args: {
schematicPath: string; 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 || []; const annotated = result.annotated || [];
if (annotated.length === 0) { if (annotated.length === 0) {
return { return {
content: [ content: [{ type: "text", text: "All components are already annotated." }],
{ type: "text", text: "All components are already annotated." },
],
}; };
} }
const lines = annotated.map( const lines = annotated.map((a: any) => ` ${a.oldReference}${a.newReference}`);
(a: any) => ` ${a.oldReference}${a.newReference}`,
);
return { return {
content: [ 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.", "Remove a wire from the schematic by start and end coordinates.",
{ {
schematicPath: z.string().describe("Path to the .kicad_sch file"), schematicPath: z.string().describe("Path to the .kicad_sch file"),
start: z start: z.object({ x: z.number(), y: z.number() }).describe("Wire start position"),
.object({ x: z.number(), y: z.number() }) end: z.object({ x: z.number(), y: z.number() }).describe("Wire end position"),
.describe("Wire start position"),
end: z
.object({ x: z.number(), y: z.number() })
.describe("Wire end position"),
}, },
async (args: { async (args: {
schematicPath: string; 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"), schematicPath: z.string().describe("Path to the .kicad_sch file"),
outputPath: z.string().describe("Output SVG file path"), outputPath: z.string().describe("Output SVG file path"),
blackAndWhite: z blackAndWhite: z.boolean().optional().describe("Export in black and white"),
.boolean()
.optional()
.describe("Export in black and white"),
}, },
async (args: { async (args: { schematicPath: string; outputPath: string; blackAndWhite?: boolean }) => {
schematicPath: string;
outputPath: string;
blackAndWhite?: boolean;
}) => {
const result = await callKicadScript("export_schematic_svg", args); const result = await callKicadScript("export_schematic_svg", args);
if (result.success) { if (result.success) {
return { 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"), schematicPath: z.string().describe("Path to the .kicad_sch file"),
outputPath: z.string().describe("Output PDF file path"), outputPath: z.string().describe("Output PDF file path"),
blackAndWhite: z blackAndWhite: z.boolean().optional().describe("Export in black and white"),
.boolean()
.optional()
.describe("Export in black and white"),
}, },
async (args: { async (args: { schematicPath: string; outputPath: string; blackAndWhite?: boolean }) => {
schematicPath: string;
outputPath: string;
blackAndWhite?: boolean;
}) => {
const result = await callKicadScript("export_schematic_pdf", args); const result = await callKicadScript("export_schematic_pdf", args);
if (result.success) { if (result.success) {
return { 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.", "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"), schematicPath: z.string().describe("Path to the .kicad_sch file"),
format: z format: z.enum(["png", "svg"]).optional().describe("Output format (default: png)"),
.enum(["png", "svg"])
.optional()
.describe("Output format (default: png)"),
width: z.number().optional().describe("Image width in pixels (default: 1200)"), width: z.number().optional().describe("Image width in pixels (default: 1200)"),
height: z.number().optional().describe("Image height in pixels (default: 900)"), 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", "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.", "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 schematicPath: z.string().describe("Path to the .kicad_sch schematic file"),
.string()
.describe("Path to the .kicad_sch schematic file"),
}, },
async (args: { schematicPath: string }) => { async (args: { schematicPath: string }) => {
const result = await callKicadScript("run_erc", args); const result = await callKicadScript("run_erc", args);
if (result.success) { if (result.success) {
const violations: any[] = result.violations || []; const violations: any[] = result.violations || [];
const lines: string[] = [ const lines: string[] = [`ERC result: ${violations.length} violation(s)`];
`ERC result: ${violations.length} violation(s)`,
];
if (result.summary?.by_severity) { if (result.summary?.by_severity) {
const s = result.summary.by_severity; const s = result.summary.by_severity;
lines.push( 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", "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.", "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 schematicPath: z.string().describe("Absolute path to the .kicad_sch schematic file"),
.string() boardPath: z.string().describe("Absolute path to the .kicad_pcb board file"),
.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 }) => { async (args: { schematicPath: string; boardPath: string }) => {
const result = await callKicadScript("sync_schematic_to_board", args); 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: { async (args: {
schematicPath: string; schematicPath: string;
x1: number; y1: number; x2: number; y2: number; x1: number;
format?: string; width?: number; height?: number; y1: number;
x2: number;
y2: number;
format?: string;
width?: number;
height?: number;
}) => { }) => {
const result = await callKicadScript("get_schematic_view_region", args); const result = await callKicadScript("get_schematic_view_region", args);
if (result.success && result.imageData) { 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: [{ type: "text", text: result.imageData }] };
} }
return { return {
content: [{ content: [
type: "image", {
data: result.imageData, type: "image",
mimeType: "image/png", data: result.imageData,
}], mimeType: "image/png",
},
],
}; };
} }
return { return {
@@ -1240,14 +1189,18 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
}, },
); );
// Find overlapping elements // Find overlapping elements
server.tool( server.tool(
"find_overlapping_elements", "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.", "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"), 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 }) => { async (args: { schematicPath: string; tolerance?: number }) => {
const result = await callKicadScript("find_overlapping_elements", args); 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) { if (syms.length) {
lines.push(`\nOverlapping symbols (${syms.length}):`); lines.push(`\nOverlapping symbols (${syms.length}):`);
syms.slice(0, 20).forEach((o: any) => { 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) { 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) { if (wires.length) {
lines.push(`\nOverlapping wires (${wires.length}):`); lines.push(`\nOverlapping wires (${wires.length}):`);
wires.slice(0, 20).forEach((o: any) => { 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") }] }; 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"), x2: z.number().describe("Right X coordinate of the region in mm"),
y2: z.number().describe("Bottom Y coordinate of the region in mm"), y2: z.number().describe("Bottom Y coordinate of the region in mm"),
}, },
async (args: { async (args: { schematicPath: string; x1: number; y1: number; x2: number; y2: number }) => {
schematicPath: string;
x1: number; y1: number; x2: number; y2: number;
}) => {
const result = await callKicadScript("get_elements_in_region", args); const result = await callKicadScript("get_elements_in_region", args);
if (result.success) { if (result.success) {
const c = result.counts; 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 || []; const syms: any[] = result.symbols || [];
if (syms.length) { if (syms.length) {
lines.push("\nSymbols:"); lines.push("\nSymbols:");
syms.forEach((s: any) => { syms.forEach((s: any) => {
const pinCount = s.pins ? Object.keys(s.pins).length : 0; 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 || []; 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:`]; const lines = [`Found ${collisions.length} wire(s) crossing symbols:`];
collisions.slice(0, 30).forEach((c: any, i: number) => { collisions.slice(0, 30).forEach((c: any, i: number) => {
lines.push( 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`); 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'"), number: z.union([z.string(), z.number()]).describe("Pin number, e.g. '1', '2', 'A1'"),
type: z type: z
.enum([ .enum([
"input", "output", "bidirectional", "tri_state", "passive", "input",
"free", "unspecified", "power_in", "power_out", "output",
"open_collector", "open_emitter", "no_connect", "bidirectional",
"tri_state",
"passive",
"free",
"unspecified",
"power_in",
"power_out",
"open_collector",
"open_emitter",
"no_connect",
]) ])
.describe("Electrical pin type"), .describe("Electrical pin type"),
at: z.object({ at: z
x: z.number().describe("X position in mm"), .object({
y: z.number().describe("Y position in mm"), x: z.number().describe("X position in mm"),
angle: z.number().describe( y: z.number().describe("Y position in mm"),
"Direction the pin wire extends FROM the symbol body: 0=right, 90=up, 180=left, 270=down" angle: z
), .number()
}).describe("Pin endpoint position (where the wire connects)"), .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)"),
length: z.number().optional().describe("Pin length in mm (default 2.54)"), length: z.number().optional().describe("Pin length in mm (default 2.54)"),
shape: z shape: z
.enum(["line", "inverted", "clock", "inverted_clock", "input_low", .enum([
"clock_low", "output_low", "falling_edge_clock", "non_logic"]) "line",
"inverted",
"clock",
"inverted_clock",
"input_low",
"clock_low",
"output_low",
"falling_edge_clock",
"non_logic",
])
.optional() .optional()
.describe("Pin graphic shape (default: line)"), .describe("Pin graphic shape (default: line)"),
}); });
const RectSchema = z.object({ const RectSchema = z.object({
x1: z.number(), y1: z.number(), x1: z.number(),
x2: z.number(), y2: z.number(), y1: z.number(),
x2: z.number(),
y2: z.number(),
width: z.number().optional().describe("Stroke width in mm (default 0.254)"), 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)"), .describe("Fill type (default: background)"),
}); });
const PolylineSchema = z.object({ const PolylineSchema = z.object({
points: z.array(z.object({ x: z.number(), y: z.number() })) points: z.array(z.object({ x: z.number(), y: z.number() })).describe("List of XY points in mm"),
.describe("List of XY points in mm"),
width: z.number().optional().describe("Stroke width in mm (default 0.254)"), 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(),
}); });
export function registerSymbolCreatorTools( export function registerSymbolCreatorTools(server: McpServer, callKicadScript: Function) {
server: McpServer,
callKicadScript: Function,
) {
// ── create_symbol ────────────────────────────────────────────────────── // // ── create_symbol ────────────────────────────────────────────────────── //
server.tool( server.tool(
"create_symbol", "create_symbol",
@@ -68,14 +90,14 @@ export function registerSymbolCreatorTools(
"- Pins on bottom: at.y = body_bottom - length, angle=90 (wire goes up)\n" + "- 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", "- Standard pin length: 2.54 mm, standard grid: 2.54 mm",
{ {
libraryPath: z libraryPath: z.string().describe("Path to the .kicad_sym file (created if missing)"),
.string()
.describe("Path to the .kicad_sym file (created if missing)"),
name: z.string().describe("Symbol name, e.g. 'TMC2209', 'MyOpAmp'"), name: z.string().describe("Symbol name, e.g. 'TMC2209', 'MyOpAmp'"),
referencePrefix: z referencePrefix: z
.string() .string()
.optional() .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"), description: z.string().optional().describe("Human-readable description"),
keywords: z.string().optional().describe("Space-separated search keywords"), keywords: z.string().optional().describe("Space-separated search keywords"),
datasheet: z.string().optional().describe("Datasheet URL or '~'"), 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. " + "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'.", "Run this after create_symbol when KiCAD shows 'library not found'.",
{ {
libraryPath: z libraryPath: z.string().describe("Full path to the .kicad_sym file"),
.string()
.describe("Full path to the .kicad_sym file"),
libraryName: z libraryName: z
.string() .string()
.optional() .optional()

View File

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

View File

@@ -1,61 +1,71 @@
/** /**
* Resource helper utilities for MCP resources * Resource helper utilities for MCP resources
*/ */
/** /**
* Create a JSON response for MCP resources * Create a JSON response for MCP resources
* *
* @param data Data to serialize as JSON * @param data Data to serialize as JSON
* @param uri Optional URI for the resource * @param uri Optional URI for the resource
* @returns MCP resource response object * @returns MCP resource response object
*/ */
export function createJsonResponse(data: any, uri?: string) { export function createJsonResponse(data: any, uri?: string) {
return { return {
contents: [{ contents: [
uri: uri || "data:application/json", {
mimeType: "application/json", uri: uri || "data:application/json",
text: JSON.stringify(data, null, 2) mimeType: "application/json",
}] text: JSON.stringify(data, null, 2),
}; },
} ],
};
/** }
* Create a binary response for MCP resources
* /**
* @param data Binary data (Buffer or base64 string) * Create a binary response for MCP resources
* @param mimeType MIME type of the binary data *
* @param uri Optional URI for the resource * @param data Binary data (Buffer or base64 string)
* @returns MCP resource response object * @param mimeType MIME type of the binary data
*/ * @param uri Optional URI for the resource
export function createBinaryResponse(data: Buffer | string, mimeType: string, uri?: string) { * @returns MCP resource response object
const blob = typeof data === 'string' ? data : data.toString('base64'); */
export function createBinaryResponse(data: Buffer | string, mimeType: string, uri?: string) {
return { const blob = typeof data === "string" ? data : data.toString("base64");
contents: [{
uri: uri || `data:${mimeType}`, return {
mimeType: mimeType, contents: [
blob: blob {
}] uri: uri || `data:${mimeType}`,
}; mimeType: mimeType,
} blob: blob,
},
/** ],
* Create an error response for MCP resources };
* }
* @param error Error message
* @param details Optional error details /**
* @param uri Optional URI for the resource * Create an error response for MCP resources
* @returns MCP resource error response *
*/ * @param error Error message
export function createErrorResponse(error: string, details?: string, uri?: string) { * @param details Optional error details
return { * @param uri Optional URI for the resource
contents: [{ * @returns MCP resource error response
uri: uri || "data:application/json", */
mimeType: "application/json", export function createErrorResponse(error: string, details?: string, uri?: string) {
text: JSON.stringify({ return {
error, contents: [
details {
}, null, 2) uri: uri || "data:application/json",
}] mimeType: "application/json",
}; text: JSON.stringify(
} {
error,
details,
},
null,
2,
),
},
],
};
}

View File

@@ -1,14 +1,14 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"esModuleInterop": true, "esModuleInterop": true,
"strict": true, "strict": true,
"outDir": "dist", "outDir": "dist",
"declaration": true, "declaration": true,
"sourceMap": true "sourceMap": true
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }