Ryan Malloy e8bad34660 Enhance MCP tools with improved FastMCP integration and IPC client
🔧 Tool Enhancements:
- Update all MCP tools to use FastMCP instead of legacy Context
- Improve IPC client with proper kicad-python integration
- Streamline function signatures for better performance
- Remove unnecessary Context dependencies from pattern recognition

 Performance Improvements:
- Simplified function calls for faster execution
- Better error handling and logging
- Enhanced IPC connection management with socket path support
- Optimized pattern recognition without blocking operations

🛠️ Technical Updates:
- BOM tools: Remove Context dependency for cleaner API
- DRC tools: Streamline CLI integration
- Export tools: Update thumbnail generation with FastMCP
- Netlist tools: Enhance extraction performance
- Pattern tools: Non-blocking circuit pattern recognition
- IPC client: Add proper kicad-python socket connection

These improvements make the MCP tools more reliable and performant
for real-time KiCad automation workflows.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-13 05:09:20 -06:00

160 lines
5.4 KiB
Python

"""
Design Rule Check (DRC) implementation using KiCad command-line interface.
"""
import json
import os
import subprocess
import tempfile
from typing import Any
from mcp.server.fastmcp import Context
from kicad_mcp.config import system
async def run_drc_via_cli(pcb_file: str) -> dict[str, Any]:
"""Run DRC using KiCad command line tools.
Args:
pcb_file: Path to the PCB file (.kicad_pcb)
ctx: MCP context for progress reporting
Returns:
Dictionary with DRC results
"""
results = {"success": False, "method": "cli", "pcb_file": pcb_file}
try:
# Create a temporary directory for the output
with tempfile.TemporaryDirectory() as temp_dir:
# Output file for DRC report
output_file = os.path.join(temp_dir, "drc_report.json")
# Find kicad-cli executable
kicad_cli = find_kicad_cli()
if not kicad_cli:
print("kicad-cli not found in PATH or common installation locations")
results["error"] = (
"kicad-cli not found. Please ensure KiCad 9.0+ is installed and kicad-cli is available."
)
return results
# Report progress
await ctx.report_progress(50, 100)
ctx.info("Running DRC using KiCad CLI...")
# Build the DRC command
cmd = [kicad_cli, "pcb", "drc", "--format", "json", "--output", output_file, pcb_file]
print(f"Running command: {' '.join(cmd)}")
process = subprocess.run(cmd, capture_output=True, text=True)
# Check if the command was successful
if process.returncode != 0:
print(f"DRC command failed with code {process.returncode}")
print(f"Error output: {process.stderr}")
results["error"] = f"DRC command failed: {process.stderr}"
return results
# Check if the output file was created
if not os.path.exists(output_file):
print("DRC report file not created")
results["error"] = "DRC report file not created"
return results
# Read the DRC report
with open(output_file) as f:
try:
drc_report = json.load(f)
except json.JSONDecodeError:
print("Failed to parse DRC report JSON")
results["error"] = "Failed to parse DRC report JSON"
return results
# Process the DRC report
violations = drc_report.get("violations", [])
violation_count = len(violations)
print(f"DRC completed with {violation_count} violations")
await ctx.report_progress(70, 100)
ctx.info(f"DRC completed with {violation_count} violations")
# Categorize violations by type
error_types = {}
for violation in violations:
error_type = violation.get("message", "Unknown")
if error_type not in error_types:
error_types[error_type] = 0
error_types[error_type] += 1
# Create success response
results = {
"success": True,
"method": "cli",
"pcb_file": pcb_file,
"total_violations": violation_count,
"violation_categories": error_types,
"violations": violations,
}
await ctx.report_progress(90, 100)
return results
except Exception as e:
print(f"Error in CLI DRC: {str(e)}", exc_info=True)
results["error"] = f"Error in CLI DRC: {str(e)}"
return results
def find_kicad_cli() -> str | None:
"""Find the kicad-cli executable in the system PATH.
Returns:
Path to kicad-cli if found, None otherwise
"""
# Check if kicad-cli is in PATH
try:
if system == "Windows":
# On Windows, check for kicad-cli.exe
result = subprocess.run(["where", "kicad-cli.exe"], capture_output=True, text=True)
if result.returncode == 0:
return result.stdout.strip().split("\n")[0]
else:
# On Unix-like systems, use which
result = subprocess.run(["which", "kicad-cli"], capture_output=True, text=True)
if result.returncode == 0:
return result.stdout.strip()
except Exception as e:
print(f"Error finding kicad-cli: {str(e)}")
# If we get here, kicad-cli is not in PATH
# Try common installation locations
if system == "Windows":
# Common Windows installation path
potential_paths = [
r"C:\Program Files\KiCad\bin\kicad-cli.exe",
r"C:\Program Files (x86)\KiCad\bin\kicad-cli.exe",
]
elif system == "Darwin": # macOS
# Common macOS installation paths
potential_paths = [
"/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli",
"/Applications/KiCad/kicad-cli",
]
else: # Linux and other Unix-like systems
# Common Linux installation paths
potential_paths = [
"/usr/bin/kicad-cli",
"/usr/local/bin/kicad-cli",
"/opt/kicad/bin/kicad-cli",
]
# Check each potential path
for path in potential_paths:
if os.path.exists(path) and os.access(path, os.X_OK):
return path
# If still not found, return None
return None