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/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
|
||||
# Python cache files
|
||||
__pycache__/
|
||||
*.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/
|
||||
├── README.md # Project documentation
|
||||
├── main.py # Entry point that runs the server
|
||||
├── config.py # Configuration constants and settings
|
||||
├── requirements.txt # Python dependencies
|
||||
├── .env.example # Example environment configuration
|
||||
├── kicad_mcp/ # Main package directory
|
||||
│ ├── __init__.py # Package initialization
|
||||
│ ├── server.py # MCP server setup
|
||||
│ ├── config.py # Configuration constants and settings
|
||||
│ ├── context.py # Lifespan management and shared context
|
||||
│ ├── resources/ # Resource handlers
|
||||
│ ├── tools/ # Tool handlers
|
||||
@ -78,7 +79,28 @@ source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
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:
|
||||
|
||||
@ -90,9 +112,13 @@ python -m mcp.dev 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:
|
||||
|
||||
@ -142,7 +168,7 @@ On Windows, the configuration would look like:
|
||||
|
||||
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.
|
||||
|
||||
@ -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.
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
### Adding New Features
|
||||
@ -375,16 +439,24 @@ If you encounter issues:
|
||||
|
||||
2. **Server Errors:**
|
||||
- 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
|
||||
- Verify that your KiCad installation is in the standard location
|
||||
|
||||
3. **KiCad Python Modules Not Found:**
|
||||
- This is a common issue. The server will still work but with limited functionality
|
||||
- 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
|
||||
- Verify that the project path used is consistent between runs
|
||||
- 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_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
|
||||
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
|
||||
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
|
||||
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]]:
|
||||
"""Find KiCad projects in the user's directory.
|
||||
@ -16,23 +15,33 @@ def find_kicad_projects() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
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)
|
||||
# Search directories to look for KiCad projects
|
||||
search_dirs = [KICAD_USER_DIR] + ADDITIONAL_SEARCH_PATHS
|
||||
|
||||
projects.append({
|
||||
"name": project_name,
|
||||
"path": project_path,
|
||||
"relative_path": rel_path,
|
||||
"modified": os.path.getmtime(project_path)
|
||||
})
|
||||
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:
|
||||
if file.endswith(KICAD_EXTENSIONS["project"]):
|
||||
project_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(project_path, search_dir)
|
||||
project_name = get_project_name_from_path(project_path)
|
||||
|
||||
print(f"Found KiCad project: {project_path}")
|
||||
projects.append({
|
||||
"name": project_name,
|
||||
"path": project_path,
|
||||
"relative_path": rel_path,
|
||||
"modified": os.path.getmtime(project_path)
|
||||
})
|
||||
|
||||
print(f"Found {len(projects)} KiCad projects")
|
||||
return projects
|
||||
|
||||
|
||||
def get_project_name_from_path(project_path: str) -> str:
|
||||
"""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.
|
||||
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.utils.env import load_dotenv
|
||||
from kicad_mcp.utils.logger import Logger
|
||||
|
||||
# Load environment variables from .env file if present
|
||||
load_dotenv()
|
||||
|
||||
logger = Logger()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
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()
|
||||
logger.info("Running server with stdio transport")
|
||||
server.run(transport='stdio')
|
||||
|
Loading…
x
Reference in New Issue
Block a user