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:
Lama 2025-03-20 12:13:38 -04:00
parent 880e267423
commit cc73df8e77
7 changed files with 276 additions and 27 deletions

16
.env.example Normal file
View 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
View File

@ -3,6 +3,9 @@ venv/
env/
ENV/
# Environment files
.env
# Python cache files
__pycache__/
*.py[cod]

View File

@ -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

View File

@ -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
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
View 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]

View File

@ -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,13 +15,23 @@ def find_kicad_projects() -> List[Dict[str, Any]]:
"""
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:
if file.endswith(KICAD_EXTENSIONS["project"]):
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)
print(f"Found KiCad project: {project_path}")
projects.append({
"name": project_name,
"path": project_path,
@ -30,9 +39,9 @@ def find_kicad_projects() -> List[Dict[str, Any]]:
"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
View File

@ -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')