add logger + support for other operating systems (lower prio)
This commit is contained in:
parent
9fa890bf90
commit
047f9f6af7
21
config.py
21
config.py
@ -3,9 +3,28 @@ Configuration settings for the KiCad MCP server.
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# KiCad paths
|
import platform
|
||||||
|
|
||||||
|
# Determine operating system
|
||||||
|
system = platform.system()
|
||||||
|
|
||||||
|
# KiCad paths based on operating system
|
||||||
|
if system == "Darwin": # macOS
|
||||||
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"
|
||||||
|
elif system == "Windows":
|
||||||
|
KICAD_USER_DIR = os.path.expanduser("~/Documents/KiCad")
|
||||||
|
KICAD_APP_PATH = r"C:\Program Files\KiCad"
|
||||||
|
elif system == "Linux":
|
||||||
|
KICAD_USER_DIR = os.path.expanduser("~/kicad")
|
||||||
|
KICAD_APP_PATH = "/usr/share/kicad"
|
||||||
|
else:
|
||||||
|
# Default to macOS paths if system is unknown
|
||||||
|
KICAD_USER_DIR = os.path.expanduser("~/Documents/KiCad")
|
||||||
|
KICAD_APP_PATH = "/Applications/KiCad/KiCad.app"
|
||||||
|
|
||||||
|
# Base path to KiCad's Python framework
|
||||||
|
KICAD_PYTHON_BASE = os.path.join(KICAD_APP_PATH, "Contents/Frameworks/Python.framework/Versions")
|
||||||
|
|
||||||
# File extensions
|
# File extensions
|
||||||
KICAD_EXTENSIONS = {
|
KICAD_EXTENSIONS = {
|
||||||
|
@ -15,21 +15,43 @@ from kicad_mcp.tools.export_tools import register_export_tools
|
|||||||
# Import prompt handlers
|
# Import prompt handlers
|
||||||
from kicad_mcp.prompts.templates import register_prompts
|
from kicad_mcp.prompts.templates import register_prompts
|
||||||
|
|
||||||
|
# Import utils
|
||||||
|
from kicad_mcp.utils.logger import Logger
|
||||||
|
from kicad_mcp.utils.python_path import setup_kicad_python_path
|
||||||
|
|
||||||
|
# Create logger for this module
|
||||||
|
logger = Logger(log_dir="logs")
|
||||||
|
|
||||||
def create_server() -> FastMCP:
|
def create_server() -> FastMCP:
|
||||||
"""Create and configure the KiCad MCP server."""
|
"""Create and configure the KiCad MCP server."""
|
||||||
|
logger.info("Initializing KiCad MCP server")
|
||||||
|
|
||||||
|
# Try to set up KiCad Python path
|
||||||
|
kicad_modules_available = setup_kicad_python_path()
|
||||||
|
|
||||||
|
if kicad_modules_available:
|
||||||
|
logger.info("KiCad Python modules successfully configured")
|
||||||
|
else:
|
||||||
|
logger.warning("KiCad Python modules not available - some features will be disabled")
|
||||||
|
|
||||||
# Initialize FastMCP server
|
# Initialize FastMCP server
|
||||||
mcp = FastMCP("KiCad")
|
mcp = FastMCP("KiCad")
|
||||||
|
logger.info("Created FastMCP server instance")
|
||||||
|
|
||||||
# Register resources
|
# Register resources
|
||||||
|
logger.debug("Registering resources...")
|
||||||
register_project_resources(mcp)
|
register_project_resources(mcp)
|
||||||
register_file_resources(mcp)
|
register_file_resources(mcp)
|
||||||
|
|
||||||
# Register tools
|
# Register tools
|
||||||
|
logger.debug("Registering tools...")
|
||||||
register_project_tools(mcp)
|
register_project_tools(mcp)
|
||||||
register_analysis_tools(mcp)
|
register_analysis_tools(mcp)
|
||||||
register_export_tools(mcp)
|
register_export_tools(mcp)
|
||||||
|
|
||||||
# Register prompts
|
# Register prompts
|
||||||
|
logger.debug("Registering prompts...")
|
||||||
register_prompts(mcp)
|
register_prompts(mcp)
|
||||||
|
|
||||||
|
logger.info("Server initialization complete")
|
||||||
return mcp
|
return mcp
|
||||||
|
@ -1,68 +1,175 @@
|
|||||||
"""
|
"""
|
||||||
Export and file generation tools for KiCad projects.
|
Analysis and validation tools for KiCad projects.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from typing import Dict, Any
|
import tempfile
|
||||||
from mcp.server.fastmcp import FastMCP
|
from typing import Dict, Any, Optional
|
||||||
|
from mcp.server.fastmcp import FastMCP, Context, Image
|
||||||
|
|
||||||
from kicad_mcp.utils.file_utils import get_project_files
|
from kicad_mcp.utils.file_utils import get_project_files
|
||||||
from kicad_mcp.utils.kicad_utils import get_project_name_from_path
|
from kicad_mcp.utils.logger import Logger
|
||||||
|
|
||||||
|
# Create logger for this module
|
||||||
|
logger = Logger()
|
||||||
|
|
||||||
def register_export_tools(mcp: FastMCP) -> None:
|
def register_analysis_tools(mcp: FastMCP, kicad_modules_available: bool = False) -> None:
|
||||||
"""Register export and file generation tools with the MCP server.
|
"""Register analysis and validation tools with the MCP server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mcp: The FastMCP server instance
|
mcp: The FastMCP server instance
|
||||||
|
kicad_modules_available: Whether KiCad Python modules are available
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def extract_bom(project_path: str) -> Dict[str, Any]:
|
def validate_project(project_path: str) -> Dict[str, Any]:
|
||||||
"""Extract a Bill of Materials (BOM) from a KiCad project."""
|
"""Basic validation of a KiCad project."""
|
||||||
|
logger.info(f"Validating project: {project_path}")
|
||||||
|
|
||||||
if not os.path.exists(project_path):
|
if not os.path.exists(project_path):
|
||||||
return {"success": False, "error": f"Project not found: {project_path}"}
|
logger.error(f"Project not found: {project_path}")
|
||||||
|
return {"valid": False, "error": f"Project not found: {project_path}"}
|
||||||
|
|
||||||
project_dir = os.path.dirname(project_path)
|
issues = []
|
||||||
project_name = get_project_name_from_path(project_path)
|
files = get_project_files(project_path)
|
||||||
|
|
||||||
# Look for existing BOM files
|
# Check for essential files
|
||||||
bom_files = []
|
if "pcb" not in files:
|
||||||
for file in os.listdir(project_dir):
|
logger.warning("Missing PCB layout file")
|
||||||
if file.startswith(project_name) and file.endswith('.csv') and 'bom' in file.lower():
|
issues.append("Missing PCB layout file")
|
||||||
bom_files.append(os.path.join(project_dir, file))
|
|
||||||
|
|
||||||
if not bom_files:
|
if "schematic" not in files:
|
||||||
return {
|
logger.warning("Missing schematic file")
|
||||||
"success": False,
|
issues.append("Missing schematic file")
|
||||||
"error": "No BOM files found. You need to generate a BOM using KiCad first."
|
|
||||||
|
# Validate project file
|
||||||
|
try:
|
||||||
|
with open(project_path, 'r') as f:
|
||||||
|
import json
|
||||||
|
json.load(f)
|
||||||
|
logger.debug("Project file validated successfully")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.error("Invalid project file format (JSON parsing error)")
|
||||||
|
issues.append("Invalid project file format (JSON parsing error)")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error reading project file: {str(e)}")
|
||||||
|
issues.append(f"Error reading project file: {str(e)}")
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"valid": len(issues) == 0,
|
||||||
|
"path": project_path,
|
||||||
|
"issues": issues if issues else None,
|
||||||
|
"files_found": list(files.keys())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(f"Validation result: {'valid' if result['valid'] else 'invalid'}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
async def generate_project_thumbnail(project_path: str, ctx: Context) -> Optional[Image]:
|
||||||
|
"""Generate a thumbnail of a KiCad project's PCB layout."""
|
||||||
|
logger.info(f"Generating thumbnail for project: {project_path}")
|
||||||
|
|
||||||
|
if not os.path.exists(project_path):
|
||||||
|
logger.error(f"Project not found: {project_path}")
|
||||||
|
ctx.info(f"Project not found: {project_path}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get PCB file
|
||||||
|
files = get_project_files(project_path)
|
||||||
|
if "pcb" not in files:
|
||||||
|
logger.error("PCB file not found in project")
|
||||||
|
ctx.info("PCB file not found in project")
|
||||||
|
return None
|
||||||
|
|
||||||
|
pcb_file = files["pcb"]
|
||||||
|
logger.info(f"Found PCB file: {pcb_file}")
|
||||||
|
|
||||||
|
if not kicad_modules_available:
|
||||||
|
logger.warning("KiCad Python modules are not available - cannot generate thumbnail")
|
||||||
|
ctx.info("KiCad Python modules are not available")
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Read the first BOM file
|
# Try to import pcbnew
|
||||||
bom_path = bom_files[0]
|
import pcbnew
|
||||||
with open(bom_path, 'r') as f:
|
logger.info("Successfully imported pcbnew module")
|
||||||
bom_content = f.read()
|
|
||||||
|
|
||||||
# Parse CSV (simplified)
|
# Load the PCB file
|
||||||
lines = bom_content.strip().split('\n')
|
logger.debug(f"Loading PCB file: {pcb_file}")
|
||||||
headers = lines[0].split(',')
|
board = pcbnew.LoadBoard(pcb_file)
|
||||||
|
if not board:
|
||||||
|
logger.error("Failed to load PCB file")
|
||||||
|
ctx.info("Failed to load PCB file")
|
||||||
|
return None
|
||||||
|
|
||||||
components = []
|
# Get board dimensions
|
||||||
for line in lines[1:]:
|
board_box = board.GetBoardEdgesBoundingBox()
|
||||||
values = line.split(',')
|
width = board_box.GetWidth() / 1000000.0 # Convert to mm
|
||||||
if len(values) >= len(headers):
|
height = board_box.GetHeight() / 1000000.0
|
||||||
component = {}
|
|
||||||
for i, header in enumerate(headers):
|
|
||||||
component[header.strip()] = values[i].strip()
|
|
||||||
components.append(component)
|
|
||||||
|
|
||||||
return {
|
logger.info(f"PCB dimensions: {width:.2f}mm x {height:.2f}mm")
|
||||||
"success": True,
|
ctx.info(f"PCB dimensions: {width:.2f}mm x {height:.2f}mm")
|
||||||
"bom_file": bom_path,
|
|
||||||
"headers": headers,
|
|
||||||
"component_count": len(components),
|
|
||||||
"components": components
|
|
||||||
}
|
|
||||||
|
|
||||||
|
# Create temporary directory for output
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
logger.debug(f"Created temporary directory: {temp_dir}")
|
||||||
|
|
||||||
|
# Create PLOT_CONTROLLER for plotting
|
||||||
|
pctl = pcbnew.PLOT_CONTROLLER(board)
|
||||||
|
popt = pctl.GetPlotOptions()
|
||||||
|
|
||||||
|
# Set plot options for PNG output
|
||||||
|
popt.SetOutputDirectory(temp_dir)
|
||||||
|
popt.SetPlotFrameRef(False)
|
||||||
|
popt.SetPlotValue(True)
|
||||||
|
popt.SetPlotReference(True)
|
||||||
|
popt.SetPlotInvisibleText(False)
|
||||||
|
popt.SetPlotViaOnMaskLayer(False)
|
||||||
|
popt.SetColorMode(True) # Color mode
|
||||||
|
|
||||||
|
# Set color theme (if available in this version)
|
||||||
|
if hasattr(popt, "SetColorTheme"):
|
||||||
|
popt.SetColorTheme("default")
|
||||||
|
|
||||||
|
# Calculate a reasonable scale to fit in a thumbnail
|
||||||
|
max_size = 800 # Max pixel dimension
|
||||||
|
scale = min(max_size / width, max_size / height) * 0.8 # 80% to leave some margin
|
||||||
|
|
||||||
|
# Set plot scale if the function exists
|
||||||
|
if hasattr(popt, "SetScale"):
|
||||||
|
popt.SetScale(scale)
|
||||||
|
|
||||||
|
# Determine output filename
|
||||||
|
plot_basename = "thumbnail"
|
||||||
|
output_filename = os.path.join(temp_dir, f"{plot_basename}.png")
|
||||||
|
|
||||||
|
logger.debug(f"Plotting PCB to: {output_filename}")
|
||||||
|
|
||||||
|
# Plot PNG
|
||||||
|
pctl.OpenPlotfile(plot_basename, pcbnew.PLOT_FORMAT_PNG, "Thumbnail")
|
||||||
|
pctl.PlotLayer()
|
||||||
|
pctl.ClosePlot()
|
||||||
|
|
||||||
|
# The plot controller creates files with predictable names
|
||||||
|
plot_file = os.path.join(temp_dir, f"{plot_basename}.png")
|
||||||
|
|
||||||
|
if not os.path.exists(plot_file):
|
||||||
|
logger.error(f"Expected plot file not found: {plot_file}")
|
||||||
|
ctx.info("Failed to generate PCB image")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Read the image file
|
||||||
|
with open(plot_file, 'rb') as f:
|
||||||
|
img_data = f.read()
|
||||||
|
|
||||||
|
logger.info(f"Successfully generated thumbnail, size: {len(img_data)} bytes")
|
||||||
|
return Image(data=img_data, format="png")
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
logger.error(f"Failed to import pcbnew module: {str(e)}")
|
||||||
|
ctx.info(f"Failed to import pcbnew module: {str(e)}")
|
||||||
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"success": False, "error": str(e)}
|
logger.error(f"Error generating thumbnail: {str(e)}", exc_info=True)
|
||||||
|
ctx.info(f"Error generating thumbnail: {str(e)}")
|
||||||
|
return None
|
||||||
|
106
kicad_mcp/utils/logger.py
Normal file
106
kicad_mcp/utils/logger.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
"""
|
||||||
|
Simple logger with automatic function-level context tracking for KiCad MCP Server.
|
||||||
|
|
||||||
|
Usage examples:
|
||||||
|
# Creates logs in the "logs" directory by default
|
||||||
|
logger = Logger()
|
||||||
|
|
||||||
|
# To disable file logging completely
|
||||||
|
logger = Logger(log_dir=None)
|
||||||
|
|
||||||
|
# Or to specify a custom logs directory
|
||||||
|
logger = Logger(log_dir="custom_logs")
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import inspect
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
"""
|
||||||
|
Simple logger that automatically tracks function-level context.
|
||||||
|
"""
|
||||||
|
def __init__(self, name=None, log_dir="logs", console_level=logging.INFO, file_level=logging.DEBUG):
|
||||||
|
"""
|
||||||
|
Initialize a logger with automatic function-level context.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Logger name (defaults to calling module name)
|
||||||
|
log_dir: Directory to store log files (default: "logs" directory)
|
||||||
|
Set to None to disable file logging
|
||||||
|
console_level: Logging level for console output
|
||||||
|
file_level: Logging level for file output
|
||||||
|
"""
|
||||||
|
# If no name provided, try to determine it from the calling module
|
||||||
|
if name is None:
|
||||||
|
frame = inspect.currentframe().f_back
|
||||||
|
module = inspect.getmodule(frame)
|
||||||
|
self.name = module.__name__ if module else "kicad_mcp"
|
||||||
|
else:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
# Initialize Python's logger
|
||||||
|
self.logger = logging.getLogger(self.name)
|
||||||
|
self.logger.setLevel(logging.DEBUG) # Capture all levels, filtering at handler level
|
||||||
|
|
||||||
|
# Only configure if not already configured
|
||||||
|
if not self.logger.handlers and not logging.getLogger().handlers:
|
||||||
|
# Create formatter with detailed context
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
'%(asctime)s [%(levelname)s] %(pathname)s:%(funcName)s:%(lineno)d - %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set up console output
|
||||||
|
console_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
console_handler.setLevel(console_level)
|
||||||
|
console_handler.setFormatter(formatter)
|
||||||
|
self.logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# Set up file output by default unless explicitly disabled
|
||||||
|
if log_dir is not None:
|
||||||
|
log_dir_path = Path(log_dir)
|
||||||
|
log_dir_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||||
|
log_file = log_dir_path / f"kicad_mcp_{timestamp}.log"
|
||||||
|
|
||||||
|
file_handler = logging.FileHandler(log_file)
|
||||||
|
file_handler.setLevel(file_level)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
self.logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
self.info(f"Logging session started, log file: {log_file}")
|
||||||
|
|
||||||
|
def _get_caller_info(self):
|
||||||
|
"""Get information about the function that called the logger."""
|
||||||
|
# Skip this function, the log method, and get to the actual caller
|
||||||
|
frame = inspect.currentframe().f_back.f_back
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def debug(self, message):
|
||||||
|
"""Log a debug message with caller context."""
|
||||||
|
self.logger.debug(message)
|
||||||
|
|
||||||
|
def info(self, message):
|
||||||
|
"""Log an info message with caller context."""
|
||||||
|
self.logger.info(message)
|
||||||
|
|
||||||
|
def warning(self, message):
|
||||||
|
"""Log a warning message with caller context."""
|
||||||
|
self.logger.warning(message)
|
||||||
|
|
||||||
|
def error(self, message):
|
||||||
|
"""Log an error message with caller context."""
|
||||||
|
self.logger.error(message)
|
||||||
|
|
||||||
|
def critical(self, message):
|
||||||
|
"""Log a critical message with caller context."""
|
||||||
|
self.logger.critical(message)
|
||||||
|
|
||||||
|
def exception(self, message):
|
||||||
|
"""Log an exception message with caller context and traceback."""
|
||||||
|
self.logger.exception(message)
|
99
kicad_mcp/utils/python_path.py
Normal file
99
kicad_mcp/utils/python_path.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
"""
|
||||||
|
Python path handling for KiCad modules.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import glob
|
||||||
|
import platform
|
||||||
|
from kicad_mcp.utils.logger import Logger
|
||||||
|
|
||||||
|
# Create logger for this module
|
||||||
|
logger = Logger()
|
||||||
|
|
||||||
|
def setup_kicad_python_path():
|
||||||
|
"""
|
||||||
|
Add KiCad Python modules to the Python path by detecting the appropriate version.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
system = platform.system()
|
||||||
|
logger.info(f"Setting up KiCad Python path for {system}")
|
||||||
|
|
||||||
|
# Define search paths based on operating system
|
||||||
|
if system == "Darwin": # macOS
|
||||||
|
from kicad_mcp.config import KICAD_APP_PATH
|
||||||
|
|
||||||
|
if not os.path.exists(KICAD_APP_PATH):
|
||||||
|
logger.error(f"KiCad application not found at {KICAD_APP_PATH}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Base path to Python framework
|
||||||
|
python_base = os.path.join(KICAD_APP_PATH, "Contents/Frameworks/Python.framework/Versions")
|
||||||
|
|
||||||
|
# First try 'Current' symlink
|
||||||
|
current_path = os.path.join(python_base, "Current/lib/python*/site-packages")
|
||||||
|
site_packages = glob.glob(current_path)
|
||||||
|
|
||||||
|
# If 'Current' symlink doesn't work, find all available Python versions
|
||||||
|
if not site_packages:
|
||||||
|
logger.debug("'Current' symlink not found, searching for numbered versions")
|
||||||
|
# Look for numbered versions like 3.9, 3.10, etc.
|
||||||
|
version_dirs = glob.glob(os.path.join(python_base, "[0-9]*"))
|
||||||
|
for version_dir in version_dirs:
|
||||||
|
potential_path = os.path.join(version_dir, "lib/python*/site-packages")
|
||||||
|
site_packages.extend(glob.glob(potential_path))
|
||||||
|
|
||||||
|
elif system == "Windows":
|
||||||
|
# Windows path - typically in Program Files
|
||||||
|
kicad_app_path = r"C:\Program Files\KiCad"
|
||||||
|
python_dirs = glob.glob(os.path.join(kicad_app_path, "lib", "python*"))
|
||||||
|
site_packages = []
|
||||||
|
|
||||||
|
for python_dir in python_dirs:
|
||||||
|
potential_path = os.path.join(python_dir, "site-packages")
|
||||||
|
if os.path.exists(potential_path):
|
||||||
|
site_packages.append(potential_path)
|
||||||
|
|
||||||
|
elif system == "Linux":
|
||||||
|
# Common Linux installation paths
|
||||||
|
site_packages = [
|
||||||
|
"/usr/lib/python3/dist-packages", # Debian/Ubuntu
|
||||||
|
"/usr/lib/python3.*/site-packages", # Red Hat/Fedora
|
||||||
|
"/usr/local/lib/python3.*/site-packages" # Source install
|
||||||
|
]
|
||||||
|
|
||||||
|
# Expand glob patterns
|
||||||
|
expanded_packages = []
|
||||||
|
for pattern in site_packages:
|
||||||
|
if "*" in pattern:
|
||||||
|
expanded_packages.extend(glob.glob(pattern))
|
||||||
|
else:
|
||||||
|
expanded_packages.append(pattern)
|
||||||
|
|
||||||
|
site_packages = expanded_packages
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.error(f"Unsupported operating system: {system}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Pick the first valid path found
|
||||||
|
for path in site_packages:
|
||||||
|
if os.path.exists(path):
|
||||||
|
# Check if pcbnew module exists in this path
|
||||||
|
pcbnew_path = os.path.join(path, "pcbnew.so")
|
||||||
|
if not os.path.exists(pcbnew_path):
|
||||||
|
# On Windows it might be pcbnew.pyd instead
|
||||||
|
pcbnew_path = os.path.join(path, "pcbnew.pyd")
|
||||||
|
|
||||||
|
if os.path.exists(pcbnew_path):
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.append(path)
|
||||||
|
logger.info(f"Added KiCad Python path: {path}")
|
||||||
|
logger.info(f"Found pcbnew module at: {pcbnew_path}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.debug(f"Found site-packages at {path} but no pcbnew module")
|
||||||
|
|
||||||
|
logger.error("Could not find a valid KiCad Python site-packages directory with pcbnew module")
|
||||||
|
return False
|
9
main.py
9
main.py
@ -4,7 +4,16 @@ 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.
|
||||||
"""
|
"""
|
||||||
from kicad_mcp.server import create_server
|
from kicad_mcp.server import create_server
|
||||||
|
from kicad_mcp.utils.logger import Logger
|
||||||
|
|
||||||
|
logger = Logger(log_dir="logs")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
logger.info("Starting KiCad MCP server")
|
||||||
server = create_server()
|
server = create_server()
|
||||||
|
logger.info("Running server with stdio transport")
|
||||||
server.run(transport='stdio')
|
server.run(transport='stdio')
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Unhandled exception: {str(e)}")
|
||||||
|
raise
|
||||||
|
Loading…
x
Reference in New Issue
Block a user