From 9fa890bf90c98b5348ed08ac63599620fa7e80bb Mon Sep 17 00:00:00 2001 From: Lama Date: Thu, 20 Mar 2025 02:01:30 -0400 Subject: [PATCH] a lot --- README.md | 247 +++++++++++++++--------------- config.py | 26 ++++ kicad_mcp/__init__.py | 4 + kicad_mcp/prompts/__init__.py | 3 + kicad_mcp/prompts/templates.py | 59 +++++++ kicad_mcp/resources/__init__.py | 3 + kicad_mcp/resources/files.py | 46 ++++++ kicad_mcp/resources/projects.py | 67 ++++++++ kicad_mcp/server.py | 35 +++++ kicad_mcp/tools/__init__.py | 3 + kicad_mcp/tools/analysis_tools.py | 50 ++++++ kicad_mcp/tools/export_tools.py | 68 ++++++++ kicad_mcp/tools/project_tools.py | 53 +++++++ kicad_mcp/utils/__init__.py | 3 + kicad_mcp/utils/file_utils.py | 66 ++++++++ kicad_mcp/utils/kicad_utils.py | 74 +++++++++ main.py | 10 ++ requirements.txt | 2 + 18 files changed, 695 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index b6eb194..2c33455 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# KiCad MCP Server - Setup Guide +# KiCad MCP Server 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. @@ -9,11 +9,31 @@ This guide will help you set up a Model Context Protocol (MCP) server for KiCad - Claude Desktop (or another MCP client) - Basic familiarity with the terminal +## Project Structure + +The KiCad MCP Server is organized into a modular structure for better maintainability: + +``` +kicad-mcp/ +├── README.md # Project documentation +├── main.py # Entry point that runs the server +├── config.py # Configuration constants and settings +├── requirements.txt # Python dependencies +├── kicad_mcp/ # Main package directory +│ ├── __init__.py # Package initialization +│ ├── server.py # MCP server setup +│ ├── resources/ # Resource handlers +│ ├── tools/ # Tool handlers +│ ├── prompts/ # Prompt templates +│ └── utils/ # Utility functions +└── tests/ # Unit tests +``` + ## Installation Steps ### 1. Set Up Your Python Environment -First, let's install `uv` (a fast Python package installer) and set up our environment: +First, let's install `pip` and set up our environment: ```bash # Create a new directory for our project @@ -24,34 +44,23 @@ cd ~/Projects/kicad-mcp python3 -m venv venv source venv/bin/activate -# Install the MCP SDK -pip install "mcp[cli]" +# Install the MCP SDK and other dependencies +pip install -r requirements.txt ``` -### 2. Save the KiCad MCP Server Script +### 2. Run the Server -Create a new file called `kicad_mcp.py` in your project directory and paste the contents of the KiCad MCP Server script. +Once the environment is set up, you can run the server: ```bash -# Make the file executable (optional, but helpful) -chmod +x kicad_mcp.py +# Run in development mode +python -m mcp.dev main.py -# Run the server in development mode -python -m mcp.dev kicad_mcp.py +# Or run directly +python main.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 +### 3. Configure Claude Desktop Now, let's configure Claude Desktop to use our MCP server: @@ -73,7 +82,7 @@ nano ~/Library/Application\ Support/Claude/claude_desktop_config.json "kicad": { "command": "/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/venv/bin/python", "args": [ - "/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/kicad_mcp.py" + "/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/main.py" ] } } @@ -84,24 +93,89 @@ Replace `/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp` with the actual path to your 3. Save the file and exit the editor. -### 5. Restart Claude Desktop +### 4. Restart Claude Desktop Close and reopen Claude Desktop to load the new configuration. -## Usage +## Understanding MCP Components -Once the server is properly configured, you can interact with KiCad through Claude Desktop: +The Model Context Protocol (MCP) defines three primary ways to provide capabilities: -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 +### Resources vs Tools vs Prompts -Here are some example prompts you can use: +**Resources** are read-only data sources that LLMs can reference: +- Similar to GET endpoints in REST APIs +- Provide data without performing significant computation +- Used when the LLM needs to read information +- Typically accessed programmatically by the client application +- Example: `kicad://projects` returns a list of all KiCad projects -- "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]" +**Tools** are functions that perform actions or computations: +- Similar to POST/PUT endpoints in REST APIs +- Can have side effects (like opening applications or generating files) +- Used when the LLM needs to perform actions in the world +- Typically invoked directly by the LLM (with user approval) +- Example: `open_project()` launches KiCad with a specific project + +**Prompts** are reusable templates for common interactions: +- Pre-defined conversation starters or instructions +- Help users articulate common questions or tasks +- Invoked by user choice (typically from a menu) +- Example: The `debug_pcb_issues` prompt helps users troubleshoot PCB problems + +## Available Features + +The KiCad MCP Server provides several capabilities: + +### Resources +- `kicad://projects` - List all KiCad projects +- `kicad://project/{path}` - Get details about a specific project +- `kicad://schematic/{path}` - Extract information from a schematic file + +### Tools +- Project management tools (find projects, get structure, open in KiCad) +- Analysis tools (validate projects, generate thumbnails) +- Export tools (extract bill of materials) + +### Prompts +- Create new component guide +- Debug PCB issues guide +- PCB manufacturing checklist + +## Development Guide + +### Adding New Features + +To add new features to the KiCad MCP Server, follow these steps: + +1. Identify the category for your feature (resource, tool, or prompt) +2. Add your implementation to the appropriate module +3. Register your feature in the corresponding register function +4. Test your changes with the development tools + +Example for adding a new tool: + +```python +# kicad_mcp/tools/analysis_tools.py + +@mcp.tool() +def new_analysis_tool(project_path: str) -> Dict[str, Any]: + """Description of your new tool.""" + # Implementation goes here + return {"result": "success"} +``` + +### Running Tests + +This project uses pytest for testing: + +```bash +# Install development dependencies +pip install pytest + +# Run tests +pytest +``` ## Troubleshooting @@ -110,108 +184,33 @@ 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) + - Ensure Python can access the `mcp` package 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 +1. Fork the repository +2. Create a feature branch +3. Add your changes +4. Submit a pull request -The project currently consists of just two files: +## Future Development Ideas -- kicad_mcp.py - The main MCP server implementation +Interested in contributing? Here are some ideas for future development: -- This setup guide +1. Add Design Rule Check (DRC) report generation and parsing +2. Implement 3D model visualization tools +3. Create PCB review tools with annotations +4. Add support for generating manufacturing files +5. Implement component search tools +6. Add tests! -If you want to make improvements: +## License -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 +This project is open source under the MIT license. diff --git a/config.py b/config.py index e69de29..20ade21 100644 --- a/config.py +++ b/config.py @@ -0,0 +1,26 @@ +""" +Configuration settings for the KiCad MCP server. +""" +import os + +# KiCad paths +KICAD_USER_DIR = os.path.expanduser("~/Documents/KiCad") +KICAD_APP_PATH = "/Applications/KiCad/KiCad.app" + +# File extensions +KICAD_EXTENSIONS = { + "project": ".kicad_pro", + "pcb": ".kicad_pcb", + "schematic": ".kicad_sch", + "design_rules": ".kicad_dru", + "worksheet": ".kicad_wks", + "footprint": ".kicad_mod", + "netlist": "_netlist.net", + "kibot_config": ".kibot.yaml", +} + +# Recognized data files +DATA_EXTENSIONS = [ + ".csv", # BOM or other data + ".pos", # Component position file +] diff --git a/kicad_mcp/__init__.py b/kicad_mcp/__init__.py index e69de29..8a17880 100644 --- a/kicad_mcp/__init__.py +++ b/kicad_mcp/__init__.py @@ -0,0 +1,4 @@ +""" +KiCad MCP Server - A Model Context Protocol server for KiCad. +""" +__version__ = "0.1.0" diff --git a/kicad_mcp/prompts/__init__.py b/kicad_mcp/prompts/__init__.py index e69de29..0125234 100644 --- a/kicad_mcp/prompts/__init__.py +++ b/kicad_mcp/prompts/__init__.py @@ -0,0 +1,3 @@ +""" +Prompt templates for KiCad MCP Server. +""" diff --git a/kicad_mcp/prompts/templates.py b/kicad_mcp/prompts/templates.py index e69de29..989b333 100644 --- a/kicad_mcp/prompts/templates.py +++ b/kicad_mcp/prompts/templates.py @@ -0,0 +1,59 @@ +""" +Prompt templates for KiCad interactions. +""" +from mcp.server.fastmcp import FastMCP + + +def register_prompts(mcp: FastMCP) -> None: + """Register prompt templates with the MCP server. + + Args: + mcp: The FastMCP server instance + """ + + @mcp.prompt() + def create_new_component() -> str: + """Prompt for creating a new KiCad component.""" + prompt = """ + 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. + """ + + return prompt + + @mcp.prompt() + def debug_pcb_issues() -> str: + """Prompt for debugging common PCB issues.""" + prompt = """ + 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. + """ + + return prompt + + @mcp.prompt() + def pcb_manufacturing_checklist() -> str: + """Prompt for PCB manufacturing preparation checklist.""" + prompt = """ + I'm preparing to send my KiCad PCB design for manufacturing. Please help me with a comprehensive checklist of things to verify before submitting my design, including: + + 1. Design rule compliance + 2. Layer stack configuration + 3. Manufacturing notes and specifications + 4. Required output files (Gerber, drill, etc.) + 5. Component placement considerations + + Please provide a detailed checklist I can follow to ensure my design is ready for manufacturing. + """ diff --git a/kicad_mcp/resources/__init__.py b/kicad_mcp/resources/__init__.py index e69de29..a37420b 100644 --- a/kicad_mcp/resources/__init__.py +++ b/kicad_mcp/resources/__init__.py @@ -0,0 +1,3 @@ +""" +Resource handlers for KiCad MCP Server. +""" diff --git a/kicad_mcp/resources/files.py b/kicad_mcp/resources/files.py index e69de29..4669eaa 100644 --- a/kicad_mcp/resources/files.py +++ b/kicad_mcp/resources/files.py @@ -0,0 +1,46 @@ +""" +File content resources for KiCad files. +""" +import os +from mcp.server.fastmcp import FastMCP + + +def register_file_resources(mcp: FastMCP) -> None: + """Register file-related resources with the MCP server. + + Args: + mcp: The FastMCP server instance + """ + + @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)}" diff --git a/kicad_mcp/resources/projects.py b/kicad_mcp/resources/projects.py index e69de29..5735aa4 100644 --- a/kicad_mcp/resources/projects.py +++ b/kicad_mcp/resources/projects.py @@ -0,0 +1,67 @@ +""" +Project listing and information resources. +""" +import os +from mcp.server.fastmcp import FastMCP + +from kicad_mcp.utils.kicad_utils import find_kicad_projects +from kicad_mcp.utils.file_utils import get_project_files, load_project_json + + +def register_project_resources(mcp: FastMCP) -> None: + """Register project-related resources with the MCP server. + + Args: + mcp: The FastMCP server instance + """ + + @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 + project_data = load_project_json(project_path) + if not project_data: + return f"Error reading project file: {project_path}" + + # 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)}" diff --git a/kicad_mcp/server.py b/kicad_mcp/server.py index e69de29..375b2c6 100644 --- a/kicad_mcp/server.py +++ b/kicad_mcp/server.py @@ -0,0 +1,35 @@ +""" +MCP server creation and configuration. +""" +from mcp.server.fastmcp import FastMCP + +# Import resource handlers +from kicad_mcp.resources.projects import register_project_resources +from kicad_mcp.resources.files import register_file_resources + +# Import tool handlers +from kicad_mcp.tools.project_tools import register_project_tools +from kicad_mcp.tools.analysis_tools import register_analysis_tools +from kicad_mcp.tools.export_tools import register_export_tools + +# Import prompt handlers +from kicad_mcp.prompts.templates import register_prompts + +def create_server() -> FastMCP: + """Create and configure the KiCad MCP server.""" + # Initialize FastMCP server + mcp = FastMCP("KiCad") + + # Register resources + register_project_resources(mcp) + register_file_resources(mcp) + + # Register tools + register_project_tools(mcp) + register_analysis_tools(mcp) + register_export_tools(mcp) + + # Register prompts + register_prompts(mcp) + + return mcp diff --git a/kicad_mcp/tools/__init__.py b/kicad_mcp/tools/__init__.py index e69de29..16917aa 100644 --- a/kicad_mcp/tools/__init__.py +++ b/kicad_mcp/tools/__init__.py @@ -0,0 +1,3 @@ +""" +Tool handlers for KiCad MCP Server. +""" diff --git a/kicad_mcp/tools/analysis_tools.py b/kicad_mcp/tools/analysis_tools.py index e69de29..9c72d11 100644 --- a/kicad_mcp/tools/analysis_tools.py +++ b/kicad_mcp/tools/analysis_tools.py @@ -0,0 +1,50 @@ +""" +Analysis and validation tools for KiCad projects. +""" +import os +from typing import Dict, Any, Optional +from mcp.server.fastmcp import FastMCP, Context, Image + +from kicad_mcp.utils.file_utils import get_project_files + + +def register_analysis_tools(mcp: FastMCP) -> None: + """Register analysis and validation tools with the MCP server. + + Args: + mcp: The FastMCP server instance + """ + + @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 "pcb" not in files: + issues.append("Missing PCB layout file") + + if "schematic" not in files: + issues.append("Missing schematic file") + + # Validate project file + try: + with open(project_path, 'r') as f: + import json + 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()) + } + diff --git a/kicad_mcp/tools/export_tools.py b/kicad_mcp/tools/export_tools.py index e69de29..c6bb5a1 100644 --- a/kicad_mcp/tools/export_tools.py +++ b/kicad_mcp/tools/export_tools.py @@ -0,0 +1,68 @@ +""" +Export and file generation tools for KiCad projects. +""" +import os +from typing import Dict, Any +from mcp.server.fastmcp import FastMCP + +from kicad_mcp.utils.file_utils import get_project_files +from kicad_mcp.utils.kicad_utils import get_project_name_from_path + + +def register_export_tools(mcp: FastMCP) -> None: + """Register export and file generation tools with the MCP server. + + Args: + mcp: The FastMCP server instance + """ + + @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 = get_project_name_from_path(project_path) + + # 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)} diff --git a/kicad_mcp/tools/project_tools.py b/kicad_mcp/tools/project_tools.py index e69de29..a216234 100644 --- a/kicad_mcp/tools/project_tools.py +++ b/kicad_mcp/tools/project_tools.py @@ -0,0 +1,53 @@ +""" +Project management tools for KiCad. +""" +import os +from typing import Dict, List, Any +from mcp.server.fastmcp import FastMCP + +from kicad_mcp.utils.kicad_utils import find_kicad_projects, open_kicad_project +from kicad_mcp.utils.file_utils import get_project_files, load_project_json + + +def register_project_tools(mcp: FastMCP) -> None: + """Register project management tools with the MCP server. + + Args: + mcp: The FastMCP server instance + """ + + @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 = {} + project_data = load_project_json(project_path) + if project_data and "metadata" in project_data: + metadata = project_data["metadata"] + + return { + "name": project_name, + "path": project_path, + "directory": project_dir, + "files": files, + "metadata": metadata + } + + @mcp.tool() + def open_project(project_path: str) -> Dict[str, Any]: + """Open a KiCad project in KiCad.""" + return open_kicad_project(project_path) diff --git a/kicad_mcp/utils/__init__.py b/kicad_mcp/utils/__init__.py index e69de29..3bfb64a 100644 --- a/kicad_mcp/utils/__init__.py +++ b/kicad_mcp/utils/__init__.py @@ -0,0 +1,3 @@ +""" +Utility functions for KiCad MCP Server. +""" diff --git a/kicad_mcp/utils/file_utils.py b/kicad_mcp/utils/file_utils.py index e69de29..674d3a6 100644 --- a/kicad_mcp/utils/file_utils.py +++ b/kicad_mcp/utils/file_utils.py @@ -0,0 +1,66 @@ +""" +File handling utilities for KiCad MCP Server. +""" +import os +import json +from typing import Dict, List, Any, Optional + +from kicad_mcp.utils.kicad_utils import get_project_name_from_path + + +def get_project_files(project_path: str) -> Dict[str, str]: + """Get all files related to a KiCad project. + + Args: + project_path: Path to the .kicad_pro file + + Returns: + Dictionary mapping file types to file paths + """ + from kicad_mcp.config import KICAD_EXTENSIONS, DATA_EXTENSIONS + + project_dir = os.path.dirname(project_path) + project_name = get_project_name_from_path(project_path) + + files = {} + + # Check for standard KiCad files + for file_type, extension in KICAD_EXTENSIONS.items(): + if file_type == "project": + # We already have the project file + files[file_type] = project_path + continue + + file_path = os.path.join(project_dir, f"{project_name}{extension}") + if os.path.exists(file_path): + files[file_type] = file_path + + # Check for data files + for ext in DATA_EXTENSIONS: + for file in os.listdir(project_dir): + if file.startswith(project_name) and file.endswith(ext): + # Extract the type from filename (e.g., project_name-bom.csv -> bom) + file_type = file[len(project_name):].strip('-_') + file_type = file_type.split('.')[0] + if not file_type: + file_type = ext[1:] # Use extension if no specific type + + files[file_type] = os.path.join(project_dir, file) + + return files + + +def load_project_json(project_path: str) -> Optional[Dict[str, Any]]: + """Load and parse a KiCad project file. + + Args: + project_path: Path to the .kicad_pro file + + Returns: + Parsed JSON data or None if parsing failed + """ + try: + with open(project_path, 'r') as f: + return json.load(f) + except Exception: + return None diff --git a/kicad_mcp/utils/kicad_utils.py b/kicad_mcp/utils/kicad_utils.py index e69de29..bd57887 100644 --- a/kicad_mcp/utils/kicad_utils.py +++ b/kicad_mcp/utils/kicad_utils.py @@ -0,0 +1,74 @@ +""" +KiCad-specific utility functions. +""" +import os +import subprocess +from typing import Dict, List, Any + +from kicad_mcp.config import KICAD_USER_DIR, KICAD_APP_PATH, KICAD_EXTENSIONS + + +def find_kicad_projects() -> List[Dict[str, Any]]: + """Find KiCad projects in the user's directory. + + Returns: + List of dictionaries with project information + """ + projects = [] + + for root, _, files in os.walk(KICAD_USER_DIR): + for file in files: + if file.endswith(KICAD_EXTENSIONS["project"]): + project_path = os.path.join(root, file) + rel_path = os.path.relpath(project_path, KICAD_USER_DIR) + project_name = get_project_name_from_path(project_path) + + projects.append({ + "name": project_name, + "path": project_path, + "relative_path": rel_path, + "modified": os.path.getmtime(project_path) + }) + + return projects + + +def get_project_name_from_path(project_path: str) -> str: + """Extract the project name from a .kicad_pro file path. + + Args: + project_path: Path to the .kicad_pro file + + Returns: + Project name without extension + """ + basename = os.path.basename(project_path) + return basename[:-len(KICAD_EXTENSIONS["project"])] + + +def open_kicad_project(project_path: str) -> Dict[str, Any]: + """Open a KiCad project using the KiCad application. + + Args: + project_path: Path to the .kicad_pro file + + Returns: + Dictionary with result information + """ + 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)} diff --git a/main.py b/main.py index e69de29..e7e2b8e 100644 --- a/main.py +++ b/main.py @@ -0,0 +1,10 @@ +#!/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 kicad_mcp.server import create_server + +if __name__ == "__main__": + server = create_server() + server.run(transport='stdio') diff --git a/requirements.txt b/requirements.txt index e69de29..c5a99e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,2 @@ +mcp[cli] +httpx