initial commit

This commit is contained in:
Lama 2025-03-20 00:59:09 -04:00
parent 5148a39a3e
commit 09ca184da6
2 changed files with 566 additions and 2 deletions

219
README.md
View File

@ -1,2 +1,217 @@
# kicad-mcp # KiCad MCP Server - Setup Guide
Model Context Protocol server for KiCad on Mac
This guide will help you set up a Model Context Protocol (MCP) server for KiCad on macOS, allowing you to interact with KiCad projects through Claude Desktop or other MCP-compatible clients.
## Prerequisites
- macOS with KiCad installed
- Python 3.10 or higher
- Claude Desktop (or another MCP client)
- Basic familiarity with the terminal
## Installation Steps
### 1. Set Up Your Python Environment
First, let's install `uv` (a fast Python package installer) and set up our environment:
```bash
# Create a new directory for our project
mkdir -p ~/Projects/kicad-mcp
cd ~/Projects/kicad-mcp
# Create a virtual environment and activate it
python3 -m venv venv
source venv/bin/activate
# Install the MCP SDK
pip install "mcp[cli]"
```
### 2. Save the KiCad MCP Server Script
Create a new file called `kicad_mcp.py` in your project directory and paste the contents of the KiCad MCP Server script.
```bash
# Make the file executable (optional, but helpful)
chmod +x kicad_mcp.py
# Run the server in development mode
python -m mcp.dev kicad_mcp.py
```
### 3. Test Your Server
Let's make sure your server works correctly before integrating it with Claude Desktop:
```bash
# Check if the file exists and has content
cat kicad_mcp.py | head -n 5
```
You should see the server start up and display information about the available tools and resources.
### 4. Configure Claude Desktop
Now, let's configure Claude Desktop to use our MCP server:
1. Create or edit the Claude Desktop configuration file:
```bash
# Create the directory if it doesn't exist
mkdir -p ~/Library/Application\ Support/Claude
# Edit the configuration file (or create it if it doesn't exist)
nano ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
2. Add the KiCad MCP server to the configuration:
```json
{
"mcpServers": {
"kicad": {
"command": "/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/venv/bin/python",
"args": [
"/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/kicad_mcp.py"
]
}
}
}
```
Replace `/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp` with the actual path to your project directory (e.g., `/Users/yourusername/Projects/kicad-mcp`).
3. Save the file and exit the editor.
### 5. Restart Claude Desktop
Close and reopen Claude Desktop to load the new configuration.
## Usage
Once the server is properly configured, you can interact with KiCad through Claude Desktop:
1. Open Claude Desktop
2. Look for the tools icon (hammer symbol) in the Claude interface
3. You should see the KiCad MCP tools available in the menu
Here are some example prompts you can use:
- "What KiCad projects do I have on my Mac?"
- "Can you help me open my latest KiCad project?"
- "Extract the bill of materials from my project at [path]"
- "Validate my KiCad project at [path]"
## Troubleshooting
If you encounter issues:
1. **Server Not Appearing in Claude Desktop:**
- Check your `claude_desktop_config.json` file for errors
- Make sure the path to your project and Python interpreter is correct
- Ensure Python can access the `mcp` package (check by running `python -c "import mcp; print(mcp.__version__)"` in your venv)
2. **Server Errors:**
- Check the terminal output when running the server in development mode
- Make sure all required Python packages are installed
- Verify that your KiCad installation is in the standard location
- Run `pip install -U "mcp[cli]"` to ensure you have the latest version
3. **Permission Issues:**
- Make sure the script is executable (`chmod +x kicad_mcp.py`)
- Check if Claude Desktop has permission to run the script
- If you get permission errors, try using the full path to your Python interpreter in the configuration
## Extending the Server
The provided MCP server implements basic KiCad functionality. To extend it:
1. Add new tools using the `@mcp.tool()` decorator
2. Add new resources using the `@mcp.resource()` decorator
3. Add new prompts using the `@mcp.prompt()` decorator
The MCP SDK provides a command-line interface for development and deployment. With your virtual environment activated, you can use commands like:
```bash
# Test your server in development mode
python -m mcp.dev kicad_mcp.py
# Install your server for use with Claude Desktop
python -m mcp.install kicad_mcp.py --name "KiCad"
```
## Additional Resources
- [MCP Documentation](https://modelcontextprotocol.io/introduction)
- [KiCad Python API Documentation](https://docs.kicad.org/doxygen-python/namespacepcbnew.html)
## Contributing
Want to contribute to the KiCad MCP Server? Here's how you can help improve this project:
### Getting Started
The project currently consists of just two files:
- kicad_mcp.py - The main MCP server implementation
- This setup guide
If you want to make improvements:
1. Set up your environment as described in the installation steps
2. Make your changes to the kicad_mcp.py file
3. Test your changes with Claude Desktop
### How to Contribute
#### Improving the Existing Server
You can improve the existing server by:
- Adding more tools and resources for KiCad
- Fixing bugs in the current implementation
- Improving error handling and user experience
- Adding support for more KiCad features
#### Testing Your Changes
Before sharing your changes, test them thoroughly:
```bash
# Run the server in development mode to check for errors
python -m mcp.dev kicad_mcp.py
# Test your changes with Claude Desktop
```
### Future Development Ideas
If you're looking for ways to improve the server, consider:
1. Adding support for KiCad's Python API (`pcbnew`) for deeper integration
2. Creating more specialized tools for PCB design review
3. Adding visualization capabilities for schematics and layouts
4. Improving project organization as the codebase grows
### Best Practices
- Keep the code simple and focused
- Document your functions with clear docstrings
- Handle errors gracefully with informative messages
- Test with different KiCad project structures

349
kicad_mcp.py Normal file
View File

@ -0,0 +1,349 @@
#!/usr/bin/env python3
"""
KiCad MCP Server - A Model Context Protocol server for KiCad on macOS.
This server allows Claude and other MCP clients to interact with KiCad projects.
"""
from typing import Dict, List, Any, Tuple, Optional
import os
import json
import subprocess
import asyncio
from pathlib import Path
from mcp.server.fastmcp import FastMCP, Context, Image
# Initialize FastMCP server
mcp = FastMCP("KiCad")
# Constants
KICAD_USER_DIR = os.path.expanduser("~/Documents/KiCad")
KICAD_APP_PATH = "/Applications/KiCad/KiCad.app"
# Helper functions
def find_kicad_projects() -> List[Dict[str, Any]]:
"""Find KiCad projects in the user's directory."""
projects = []
for root, _, files in os.walk(KICAD_USER_DIR):
for file in files:
if file.endswith(".kicad_pro"):
project_path = os.path.join(root, file)
rel_path = os.path.relpath(project_path, KICAD_USER_DIR)
project_name = file[:-10] # Remove .kicad_pro extension
projects.append({
"name": project_name,
"path": project_path,
"relative_path": rel_path,
"modified": os.path.getmtime(project_path)
})
return projects
def get_project_files(project_path: str) -> Dict[str, str]:
"""Get all files related to a KiCad project."""
project_dir = os.path.dirname(project_path)
project_name = os.path.basename(project_path)[:-10] # Remove .kicad_pro
files = {}
extensions = [
".kicad_pcb", # PCB layout
".kicad_sch", # Schematic
".kicad_dru", # Design rules
".kibot.yaml", # KiBot configuration
".kicad_wks", # Worksheet template
".kicad_mod", # Footprint module
"_netlist.net", # Netlist
".csv", # BOM or other data
".pos", # Component position file
]
for ext in extensions:
file_path = os.path.join(project_dir, f"{project_name}{ext}")
if os.path.exists(file_path):
files[ext[1:]] = file_path # Remove leading dot from extension
return files
# Resources
@mcp.resource("kicad://projects")
def list_projects_resource() -> str:
"""List all KiCad projects as a formatted resource."""
projects = find_kicad_projects()
if not projects:
return "No KiCad projects found in your Documents/KiCad directory."
result = "# KiCad Projects\n\n"
for project in sorted(projects, key=lambda p: p["modified"], reverse=True):
result += f"## {project['name']}\n"
result += f"- **Path**: {project['path']}\n"
result += f"- **Last Modified**: {os.path.getmtime(project['path'])}\n\n"
return result
@mcp.resource("kicad://project/{project_path}")
def get_project_details(project_path: str) -> str:
"""Get details about a specific KiCad project."""
if not os.path.exists(project_path):
return f"Project not found: {project_path}"
try:
# Load project file
with open(project_path, 'r') as f:
project_data = json.load(f)
# Get related files
files = get_project_files(project_path)
# Format project details
result = f"# Project: {os.path.basename(project_path)[:-10]}\n\n"
result += "## Project Files\n"
for file_type, file_path in files.items():
result += f"- **{file_type}**: {file_path}\n"
result += "\n## Project Settings\n"
# Extract metadata
if "metadata" in project_data:
metadata = project_data["metadata"]
for key, value in metadata.items():
result += f"- **{key}**: {value}\n"
return result
except Exception as e:
return f"Error reading project file: {str(e)}"
@mcp.resource("kicad://schematic/{schematic_path}")
def get_schematic_info(schematic_path: str) -> str:
"""Extract information from a KiCad schematic file."""
if not os.path.exists(schematic_path):
return f"Schematic file not found: {schematic_path}"
# KiCad schematic files are in S-expression format (not JSON)
# This is a basic extraction of text-based information
try:
with open(schematic_path, 'r') as f:
content = f.read()
# Basic extraction of components
components = []
for line in content.split('\n'):
if '(symbol ' in line and 'lib_id' in line:
components.append(line.strip())
result = f"# Schematic: {os.path.basename(schematic_path)}\n\n"
result += f"## Components (Estimated Count: {len(components)})\n\n"
# Extract a sample of components
for i, comp in enumerate(components[:10]):
result += f"{comp}\n"
if len(components) > 10:
result += f"\n... and {len(components) - 10} more components\n"
return result
except Exception as e:
return f"Error reading schematic file: {str(e)}"
# Tools
@mcp.tool()
def find_projects() -> List[Dict[str, Any]]:
"""Find all KiCad projects on this system."""
return find_kicad_projects()
@mcp.tool()
def get_project_structure(project_path: str) -> Dict[str, Any]:
"""Get the structure and files of a KiCad project."""
if not os.path.exists(project_path):
return {"error": f"Project not found: {project_path}"}
project_dir = os.path.dirname(project_path)
project_name = os.path.basename(project_path)[:-10] # Remove .kicad_pro extension
# Get related files
files = get_project_files(project_path)
# Get project metadata
metadata = {}
try:
with open(project_path, 'r') as f:
project_data = json.load(f)
if "metadata" in project_data:
metadata = project_data["metadata"]
except Exception as e:
metadata = {"error": str(e)}
return {
"name": project_name,
"path": project_path,
"directory": project_dir,
"files": files,
"metadata": metadata
}
@mcp.tool()
def open_kicad_project(project_path: str) -> Dict[str, Any]:
"""Open a KiCad project in KiCad."""
if not os.path.exists(project_path):
return {"success": False, "error": f"Project not found: {project_path}"}
try:
# On MacOS, use the 'open' command to open the project in KiCad
cmd = ["open", "-a", KICAD_APP_PATH, project_path]
result = subprocess.run(cmd, capture_output=True, text=True)
return {
"success": result.returncode == 0,
"command": " ".join(cmd),
"output": result.stdout,
"error": result.stderr if result.returncode != 0 else None
}
except Exception as e:
return {"success": False, "error": str(e)}
@mcp.tool()
def extract_bom(project_path: str) -> Dict[str, Any]:
"""Extract a Bill of Materials (BOM) from a KiCad project."""
if not os.path.exists(project_path):
return {"success": False, "error": f"Project not found: {project_path}"}
project_dir = os.path.dirname(project_path)
project_name = os.path.basename(project_path)[:-10]
# Look for existing BOM files
bom_files = []
for file in os.listdir(project_dir):
if file.startswith(project_name) and file.endswith('.csv') and 'bom' in file.lower():
bom_files.append(os.path.join(project_dir, file))
if not bom_files:
return {
"success": False,
"error": "No BOM files found. You need to generate a BOM using KiCad first."
}
try:
# Read the first BOM file
bom_path = bom_files[0]
with open(bom_path, 'r') as f:
bom_content = f.read()
# Parse CSV (simplified)
lines = bom_content.strip().split('\n')
headers = lines[0].split(',')
components = []
for line in lines[1:]:
values = line.split(',')
if len(values) >= len(headers):
component = {}
for i, header in enumerate(headers):
component[header.strip()] = values[i].strip()
components.append(component)
return {
"success": True,
"bom_file": bom_path,
"headers": headers,
"component_count": len(components),
"components": components
}
except Exception as e:
return {"success": False, "error": str(e)}
@mcp.tool()
def validate_project(project_path: str) -> Dict[str, Any]:
"""Basic validation of a KiCad project."""
if not os.path.exists(project_path):
return {"valid": False, "error": f"Project not found: {project_path}"}
issues = []
files = get_project_files(project_path)
# Check for essential files
if "kicad_pcb" not in files:
issues.append("Missing PCB layout file")
if "kicad_sch" not in files:
issues.append("Missing schematic file")
# Validate project file
try:
with open(project_path, 'r') as f:
project_data = json.load(f)
except json.JSONDecodeError:
issues.append("Invalid project file format (JSON parsing error)")
except Exception as e:
issues.append(f"Error reading project file: {str(e)}")
return {
"valid": len(issues) == 0,
"path": project_path,
"issues": issues if issues else None,
"files_found": list(files.keys())
}
@mcp.tool()
async def generate_project_thumbnail(project_path: str, ctx: Context) -> Optional[Image]:
"""Generate a thumbnail of a KiCad project's PCB layout."""
# This would normally use KiCad's Python API (pcbnew) to render a PCB image
# However, since this is a simulation, we'll return a message instead
if not os.path.exists(project_path):
ctx.info(f"Project not found: {project_path}")
return None
# Get PCB file
files = get_project_files(project_path)
if "kicad_pcb" not in files:
ctx.info("PCB file not found in project")
return None
ctx.info("In a real implementation, this would generate a PCB thumbnail image")
ctx.info("This requires pcbnew Python module from KiCad to render PCB layouts")
# Placeholder for actual implementation
# In a real implementation, you would:
# 1. Load the PCB file using pcbnew
# 2. Render it to an image
# 3. Return the image
return None
# Prompts
@mcp.prompt()
def create_new_component() -> str:
"""Prompt for creating a new KiCad component."""
return """
I want to create a new component in KiCad for my PCB design. I need help with:
1. Deciding on the correct component package/footprint
2. Creating the schematic symbol
3. Connecting the schematic symbol to the footprint
4. Adding the component to my design
Please provide step-by-step instructions on how to create a new component in KiCad.
"""
@mcp.prompt()
def debug_pcb_issues() -> str:
"""Prompt for debugging common PCB issues."""
return """
I'm having issues with my KiCad PCB design. Can you help me troubleshoot the following problems:
1. Design rule check (DRC) errors
2. Electrical rule check (ERC) errors
3. Footprint mismatches
4. Routing challenges
Please provide a systematic approach to identifying and fixing these issues in KiCad.
"""
# Run the server
if __name__ == "__main__":
mcp.run(transport='stdio')