Add flexible environment configuration to support custom project paths
- Added environment variable support for configuring KiCad project search paths - Implemented auto-detection of common project directories (~/pcb, ~/Electronics, etc.) - Created .env file support for user-friendly configuration - Added utility module for environment variable loading and management - Updated documentation with detailed configuration options and troubleshooting steps - Improved logging to help diagnose path-related issues - Removed hardcoded paths to make the MCP server more generally usable
This commit is contained in:
parent
880e267423
commit
cc73df8e77
16
.env.example
Normal file
16
.env.example
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Example environment file for KiCad MCP Server
|
||||||
|
# Copy this file to .env and customize the values
|
||||||
|
|
||||||
|
# Additional directories to search for KiCad projects (comma-separated)
|
||||||
|
# KICAD_SEARCH_PATHS=~/pcb,~/Electronics,~/Projects/KiCad
|
||||||
|
|
||||||
|
# Override the default KiCad user directory
|
||||||
|
# KICAD_USER_DIR=~/Documents/KiCad
|
||||||
|
|
||||||
|
# Override the default KiCad application path
|
||||||
|
# macOS:
|
||||||
|
# KICAD_APP_PATH=/Applications/KiCad/KiCad.app
|
||||||
|
# Windows:
|
||||||
|
# KICAD_APP_PATH=C:\Program Files\KiCad
|
||||||
|
# Linux:
|
||||||
|
# KICAD_APP_PATH=/usr/share/kicad
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,6 +3,9 @@ venv/
|
|||||||
env/
|
env/
|
||||||
ENV/
|
ENV/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
|
||||||
# Python cache files
|
# Python cache files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
88
README.md
88
README.md
@ -44,11 +44,12 @@ The KiCad MCP Server is organized into a modular structure for better maintainab
|
|||||||
kicad-mcp/
|
kicad-mcp/
|
||||||
├── README.md # Project documentation
|
├── README.md # Project documentation
|
||||||
├── main.py # Entry point that runs the server
|
├── main.py # Entry point that runs the server
|
||||||
├── config.py # Configuration constants and settings
|
|
||||||
├── requirements.txt # Python dependencies
|
├── requirements.txt # Python dependencies
|
||||||
|
├── .env.example # Example environment configuration
|
||||||
├── kicad_mcp/ # Main package directory
|
├── kicad_mcp/ # Main package directory
|
||||||
│ ├── __init__.py # Package initialization
|
│ ├── __init__.py # Package initialization
|
||||||
│ ├── server.py # MCP server setup
|
│ ├── server.py # MCP server setup
|
||||||
|
│ ├── config.py # Configuration constants and settings
|
||||||
│ ├── context.py # Lifespan management and shared context
|
│ ├── context.py # Lifespan management and shared context
|
||||||
│ ├── resources/ # Resource handlers
|
│ ├── resources/ # Resource handlers
|
||||||
│ ├── tools/ # Tool handlers
|
│ ├── tools/ # Tool handlers
|
||||||
@ -78,7 +79,28 @@ source venv/bin/activate # On Windows: venv\Scripts\activate
|
|||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Run the Server
|
### 2. Configure Your Environment
|
||||||
|
|
||||||
|
Create a `.env` file to customize where the server looks for your KiCad projects:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy the example environment file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Edit the .env file
|
||||||
|
vim .env
|
||||||
|
```
|
||||||
|
|
||||||
|
In the `.env` file, add your custom project directories:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Add paths to your KiCad projects (comma-separated)
|
||||||
|
KICAD_SEARCH_PATHS=~/pcb,~/Electronics,~/Projects/KiCad
|
||||||
|
```
|
||||||
|
|
||||||
|
This will tell the server to look for KiCad projects in your custom directories in addition to the standard KiCad user directory.
|
||||||
|
|
||||||
|
### 3. Run the Server
|
||||||
|
|
||||||
Once the environment is set up, you can run the server:
|
Once the environment is set up, you can run the server:
|
||||||
|
|
||||||
@ -90,9 +112,13 @@ python -m mcp.dev main.py
|
|||||||
python main.py
|
python main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Configure an MCP Client
|
The server will automatically detect KiCad projects in:
|
||||||
|
|
||||||
The server can be used with any MCP-compatible client. Here's how to set it up with Claude Desktop as an example:
|
- The standard KiCad user directory (e.g., ~/Documents/KiCad)
|
||||||
|
- Any custom directories specified in your .env file
|
||||||
|
- Common project directories (automatically detected)
|
||||||
|
|
||||||
|
### 4. Configure an MCP Client
|
||||||
|
|
||||||
Now, let's configure Claude Desktop to use our MCP server:
|
Now, let's configure Claude Desktop to use our MCP server:
|
||||||
|
|
||||||
@ -142,7 +168,7 @@ On Windows, the configuration would look like:
|
|||||||
|
|
||||||
The configuration should be stored in `%APPDATA%\Claude\claude_desktop_config.json`.
|
The configuration should be stored in `%APPDATA%\Claude\claude_desktop_config.json`.
|
||||||
|
|
||||||
### 4. Restart Your MCP Client
|
### 5. Restart Your MCP Client
|
||||||
|
|
||||||
Close and reopen your MCP client (e.g., Claude Desktop) to load the new configuration. The KiCad server should appear in the tools dropdown menu or equivalent interface in your client.
|
Close and reopen your MCP client (e.g., Claude Desktop) to load the new configuration. The KiCad server should appear in the tools dropdown menu or equivalent interface in your client.
|
||||||
|
|
||||||
@ -328,6 +354,44 @@ Claude will generate and display a thumbnail of your PCB. Then you might ask:
|
|||||||
Let's run a full DRC check on this project to identify all the issues I need to fix.
|
Let's run a full DRC check on this project to identify all the issues I need to fix.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
The KiCad MCP Server can be configured using environment variables or a `.env` file:
|
||||||
|
|
||||||
|
### Key Configuration Options
|
||||||
|
| Environment Variable | Description | Example |
|
||||||
|
|---------------------|-------------|---------|
|
||||||
|
| `KICAD_SEARCH_PATHS` | Comma-separated list of directories to search for KiCad projects | `~/pcb,~/Electronics,~/Projects` |
|
||||||
|
| `KICAD_USER_DIR` | Override the default KiCad user directory | `~/Documents/KiCadProjects` |
|
||||||
|
| `KICAD_APP_PATH` | Override the default KiCad application path | `/Applications/KiCad7/KiCad.app` |
|
||||||
|
| `LOG_LEVEL` | Set logging verbosity | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
||||||
|
| `LOG_DIR` | Directory for log files | `logs` or `~/.kicad_mcp/logs` |
|
||||||
|
|
||||||
|
### Using Environment Variables Directly
|
||||||
|
|
||||||
|
You can set environment variables when launching the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KICAD_SEARCH_PATHS=~/pcb,~/Electronics LOG_LEVEL=DEBUG python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using a .env File (Recommended)
|
||||||
|
|
||||||
|
Create a `.env` file in the project root with your configuration by copying and renaming `.env.example`:
|
||||||
|
|
||||||
|
```
|
||||||
|
# KiCad MCP Server Configuration
|
||||||
|
|
||||||
|
# Directories to search for KiCad projects (comma-separated)
|
||||||
|
KICAD_SEARCH_PATHS=~/pcb,~/Electronics,~/Projects/KiCad
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
LOG_DIR=logs
|
||||||
|
```
|
||||||
|
|
||||||
|
The server automatically detects and loads this configuration on startup.
|
||||||
|
|
||||||
## Development Guide
|
## Development Guide
|
||||||
|
|
||||||
### Adding New Features
|
### Adding New Features
|
||||||
@ -375,16 +439,24 @@ If you encounter issues:
|
|||||||
|
|
||||||
2. **Server Errors:**
|
2. **Server Errors:**
|
||||||
- Check the terminal output when running the server in development mode
|
- Check the terminal output when running the server in development mode
|
||||||
- Look for errors in the logs at `~/.kicad_mcp/logs` or `logs/` directory
|
- Check Claude logs at:
|
||||||
|
- `~/Library/Logs/Claude/mcp-server-kicad.log` (server-specific logs)
|
||||||
|
- `~/Library/Logs/Claude/mcp.log` (general MCP logs)
|
||||||
- Make sure all required Python packages are installed
|
- Make sure all required Python packages are installed
|
||||||
- Verify that your KiCad installation is in the standard location
|
- Verify that your KiCad installation is in the standard location
|
||||||
|
|
||||||
3. **KiCad Python Modules Not Found:**
|
3. **KiCad Python Modules Not Found:**
|
||||||
- This is a common issue. The server will still work but with limited functionality
|
- This is a common issue. The server will still work but with limited functionality
|
||||||
- Ensure KiCad is installed properly
|
- Ensure KiCad is installed properly
|
||||||
- Check if the right paths are set in `config.py`
|
- Check if the right paths are set in `kicad_mcp/config.py`
|
||||||
|
|
||||||
4. **DRC History Not Saving:**
|
4. 4. **Projects Not Found:**
|
||||||
|
- Check your `.env` file to ensure your project directories are correctly specified
|
||||||
|
- Verify the paths exist and have KiCad project files (.kicad_pro)
|
||||||
|
- Use absolute paths instead of `~` if there are issues with path expansion
|
||||||
|
- Check the Claude logs mentioned above to see if there are errors when searching for projects
|
||||||
|
|
||||||
|
5. **DRC History Not Saving:**
|
||||||
- Check if the `~/.kicad_mcp/drc_history/` directory exists and is writable
|
- Check if the `~/.kicad_mcp/drc_history/` directory exists and is writable
|
||||||
- Verify that the project path used is consistent between runs
|
- Verify that the project path used is consistent between runs
|
||||||
- Check for errors in the logs related to DRC history saving
|
- Check for errors in the logs related to DRC history saving
|
||||||
|
@ -23,8 +23,36 @@ else:
|
|||||||
KICAD_USER_DIR = os.path.expanduser("~/Documents/KiCad")
|
KICAD_USER_DIR = os.path.expanduser("~/Documents/KiCad")
|
||||||
KICAD_APP_PATH = "/Applications/KiCad/KiCad.app"
|
KICAD_APP_PATH = "/Applications/KiCad/KiCad.app"
|
||||||
|
|
||||||
|
# Additional search paths from environment variable
|
||||||
|
ADDITIONAL_SEARCH_PATHS = []
|
||||||
|
env_search_paths = os.environ.get("KICAD_SEARCH_PATHS", "")
|
||||||
|
if env_search_paths:
|
||||||
|
for path in env_search_paths.split(","):
|
||||||
|
expanded_path = os.path.expanduser(path.strip())
|
||||||
|
if os.path.exists(expanded_path):
|
||||||
|
ADDITIONAL_SEARCH_PATHS.append(expanded_path)
|
||||||
|
|
||||||
|
# Try to auto-detect common project locations if not specified
|
||||||
|
DEFAULT_PROJECT_LOCATIONS = [
|
||||||
|
"~/Documents/PCB",
|
||||||
|
"~/PCB",
|
||||||
|
"~/Electronics",
|
||||||
|
"~/Projects/Electronics",
|
||||||
|
"~/Projects/PCB",
|
||||||
|
"~/Projects/KiCad"
|
||||||
|
]
|
||||||
|
|
||||||
|
for location in DEFAULT_PROJECT_LOCATIONS:
|
||||||
|
expanded_path = os.path.expanduser(location)
|
||||||
|
if os.path.exists(expanded_path) and expanded_path not in ADDITIONAL_SEARCH_PATHS:
|
||||||
|
ADDITIONAL_SEARCH_PATHS.append(expanded_path)
|
||||||
|
|
||||||
# Base path to KiCad's Python framework
|
# Base path to KiCad's Python framework
|
||||||
KICAD_PYTHON_BASE = os.path.join(KICAD_APP_PATH, "Contents/Frameworks/Python.framework/Versions")
|
if system == "Darwin": # macOS
|
||||||
|
KICAD_PYTHON_BASE = os.path.join(KICAD_APP_PATH, "Contents/Frameworks/Python.framework/Versions")
|
||||||
|
else:
|
||||||
|
KICAD_PYTHON_BASE = "" # Will be determined dynamically in python_path.py
|
||||||
|
|
||||||
|
|
||||||
# File extensions
|
# File extensions
|
||||||
KICAD_EXTENSIONS = {
|
KICAD_EXTENSIONS = {
|
||||||
|
104
kicad_mcp/utils/env.py
Normal file
104
kicad_mcp/utils/env.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
"""
|
||||||
|
Environment variable handling for KiCad MCP Server.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
def load_dotenv(env_file: str = ".env") -> Dict[str, str]:
|
||||||
|
"""Load environment variables from .env file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
env_file: Path to the .env file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary of loaded environment variables
|
||||||
|
"""
|
||||||
|
env_vars = {}
|
||||||
|
|
||||||
|
# Try to find .env file in the current directory or parent directories
|
||||||
|
env_path = find_env_file(env_file)
|
||||||
|
|
||||||
|
if not env_path:
|
||||||
|
# No .env file found, return empty dict
|
||||||
|
return env_vars
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(env_path, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Skip empty lines and comments
|
||||||
|
if not line or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Parse key-value pairs
|
||||||
|
if '=' in line:
|
||||||
|
key, value = line.split('=', 1)
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip()
|
||||||
|
|
||||||
|
# Remove quotes if present
|
||||||
|
if value.startswith('"') and value.endswith('"'):
|
||||||
|
value = value[1:-1]
|
||||||
|
elif value.startswith("'") and value.endswith("'"):
|
||||||
|
value = value[1:-1]
|
||||||
|
|
||||||
|
# Expand ~ to user's home directory
|
||||||
|
if '~' in value:
|
||||||
|
value = os.path.expanduser(value)
|
||||||
|
|
||||||
|
# Set environment variable
|
||||||
|
os.environ[key] = value
|
||||||
|
env_vars[key] = value
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"Error loading .env file: {str(e)}")
|
||||||
|
|
||||||
|
return env_vars
|
||||||
|
|
||||||
|
def find_env_file(filename: str = ".env") -> Optional[str]:
|
||||||
|
"""Find a .env file in the current directory or parent directories.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: Name of the env file to find
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the env file if found, None otherwise
|
||||||
|
"""
|
||||||
|
current_dir = os.getcwd()
|
||||||
|
max_levels = 3 # Limit how far up to search
|
||||||
|
|
||||||
|
for _ in range(max_levels):
|
||||||
|
env_path = os.path.join(current_dir, filename)
|
||||||
|
if os.path.exists(env_path):
|
||||||
|
return env_path
|
||||||
|
|
||||||
|
# Move up one directory
|
||||||
|
parent_dir = os.path.dirname(current_dir)
|
||||||
|
if parent_dir == current_dir: # We've reached the root
|
||||||
|
break
|
||||||
|
current_dir = parent_dir
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_env_list(env_var: str, default: str = "") -> list:
|
||||||
|
"""Get a list from a comma-separated environment variable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
env_var: Name of the environment variable
|
||||||
|
default: Default value if environment variable is not set
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of values
|
||||||
|
"""
|
||||||
|
value = os.environ.get(env_var, default)
|
||||||
|
if not value:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Split by comma and strip whitespace
|
||||||
|
items = [item.strip() for item in value.split(",")]
|
||||||
|
|
||||||
|
# Filter out empty items
|
||||||
|
return [item for item in items if item]
|
@ -5,8 +5,7 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
from kicad_mcp.config import KICAD_USER_DIR, KICAD_APP_PATH, KICAD_EXTENSIONS
|
from kicad_mcp.config import KICAD_USER_DIR, KICAD_APP_PATH, KICAD_EXTENSIONS, ADDITIONAL_SEARCH_PATHS
|
||||||
|
|
||||||
|
|
||||||
def find_kicad_projects() -> List[Dict[str, Any]]:
|
def find_kicad_projects() -> List[Dict[str, Any]]:
|
||||||
"""Find KiCad projects in the user's directory.
|
"""Find KiCad projects in the user's directory.
|
||||||
@ -16,13 +15,23 @@ def find_kicad_projects() -> List[Dict[str, Any]]:
|
|||||||
"""
|
"""
|
||||||
projects = []
|
projects = []
|
||||||
|
|
||||||
for root, _, files in os.walk(KICAD_USER_DIR):
|
# Search directories to look for KiCad projects
|
||||||
|
search_dirs = [KICAD_USER_DIR] + ADDITIONAL_SEARCH_PATHS
|
||||||
|
|
||||||
|
for search_dir in search_dirs:
|
||||||
|
if not os.path.exists(search_dir):
|
||||||
|
print(f"Search directory does not exist: {search_dir}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Scanning directory: {search_dir}")
|
||||||
|
for root, _, files in os.walk(search_dir):
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.endswith(KICAD_EXTENSIONS["project"]):
|
if file.endswith(KICAD_EXTENSIONS["project"]):
|
||||||
project_path = os.path.join(root, file)
|
project_path = os.path.join(root, file)
|
||||||
rel_path = os.path.relpath(project_path, KICAD_USER_DIR)
|
rel_path = os.path.relpath(project_path, search_dir)
|
||||||
project_name = get_project_name_from_path(project_path)
|
project_name = get_project_name_from_path(project_path)
|
||||||
|
|
||||||
|
print(f"Found KiCad project: {project_path}")
|
||||||
projects.append({
|
projects.append({
|
||||||
"name": project_name,
|
"name": project_name,
|
||||||
"path": project_path,
|
"path": project_path,
|
||||||
@ -30,9 +39,9 @@ def find_kicad_projects() -> List[Dict[str, Any]]:
|
|||||||
"modified": os.path.getmtime(project_path)
|
"modified": os.path.getmtime(project_path)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
print(f"Found {len(projects)} KiCad projects")
|
||||||
return projects
|
return projects
|
||||||
|
|
||||||
|
|
||||||
def get_project_name_from_path(project_path: str) -> str:
|
def get_project_name_from_path(project_path: str) -> str:
|
||||||
"""Extract the project name from a .kicad_pro file path.
|
"""Extract the project name from a .kicad_pro file path.
|
||||||
|
|
||||||
|
17
main.py
17
main.py
@ -3,14 +3,31 @@
|
|||||||
KiCad MCP Server - A Model Context Protocol server for KiCad on macOS.
|
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.
|
This server allows Claude and other MCP clients to interact with KiCad projects.
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from kicad_mcp.config import KICAD_USER_DIR, ADDITIONAL_SEARCH_PATHS
|
||||||
from kicad_mcp.server import create_server
|
from kicad_mcp.server import create_server
|
||||||
|
from kicad_mcp.utils.env import load_dotenv
|
||||||
from kicad_mcp.utils.logger import Logger
|
from kicad_mcp.utils.logger import Logger
|
||||||
|
|
||||||
|
# Load environment variables from .env file if present
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
logger = Logger()
|
logger = Logger()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
logger.info("Starting KiCad MCP server")
|
logger.info("Starting KiCad MCP server")
|
||||||
|
|
||||||
|
# Log search paths from config
|
||||||
|
logger.info(f"Using KiCad user directory: {KICAD_USER_DIR}")
|
||||||
|
if ADDITIONAL_SEARCH_PATHS:
|
||||||
|
logger.info(f"Additional search paths: {', '.join(ADDITIONAL_SEARCH_PATHS)}")
|
||||||
|
else:
|
||||||
|
logger.info("No additional search paths configured")
|
||||||
|
|
||||||
|
# Create and run server
|
||||||
server = create_server()
|
server = create_server()
|
||||||
logger.info("Running server with stdio transport")
|
logger.info("Running server with stdio transport")
|
||||||
server.run(transport='stdio')
|
server.run(transport='stdio')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user