Skip to main content
Glama
lamaalrajih

KiCad MCP Server

by lamaalrajih

run_drc_check

Perform Design Rule Check on KiCad PCB files to validate manufacturing compliance and identify potential layout issues before production.

Instructions

Run a Design Rule Check on a KiCad PCB file.

Args: project_path: Path to the KiCad project file (.kicad_pro) ctx: MCP context for progress reporting

Returns: Dictionary with DRC results and statistics

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_pathYes
ctxYes

Implementation Reference

  • The primary handler for the 'run_drc_check' tool. Orchestrates the DRC process by locating the PCB file, invoking the CLI helper, saving results to history, comparing with previous runs, and reporting progress.
    @mcp.tool()
    async def run_drc_check(project_path: str, ctx: Context | None) -> Dict[str, Any]:
        """Run a Design Rule Check on a KiCad PCB file.
        
        Args:
            project_path: Path to the KiCad project file (.kicad_pro)
            ctx: MCP context for progress reporting
            
        Returns:
            Dictionary with DRC results and statistics
        """
        print(f"Running DRC check for project: {project_path}")
        
        if not os.path.exists(project_path):
            print(f"Project not found: {project_path}")
            return {"success": False, "error": f"Project not found: {project_path}"}
        
        # Get PCB file from project
        files = get_project_files(project_path)
        if "pcb" not in files:
            print("PCB file not found in project")
            return {"success": False, "error": "PCB file not found in project"}
        
        pcb_file = files["pcb"]
        print(f"Found PCB file: {pcb_file}")
        
        # Report progress to user
        if ctx:
            await ctx.report_progress(10, 100)
            ctx.info(f"Starting DRC check on {os.path.basename(pcb_file)}")
        
        # Run DRC using the appropriate approach
        drc_results = None
        
        print("Using kicad-cli for DRC")
        if ctx:
            ctx.info("Using KiCad CLI for DRC check...")
        # logging.info(f"[DRC] Calling run_drc_via_cli for {pcb_file}") # <-- Remove log
        drc_results = await run_drc_via_cli(pcb_file, ctx)
        # logging.info(f"[DRC] run_drc_via_cli finished for {pcb_file}") # <-- Remove log
        
        # Process and save results if successful
        if drc_results and drc_results.get("success", False):
            # logging.info(f"[DRC] DRC check successful for {pcb_file}. Saving results.") # <-- Remove log
            # Save results to history
            save_drc_result(project_path, drc_results)
            
            # Add comparison with previous run
            comparison = compare_with_previous(project_path, drc_results)
            if comparison:
                drc_results["comparison"] = comparison
                
                if ctx:
                    if comparison["change"] < 0:
                        ctx.info(f"Great progress! You've fixed {abs(comparison['change'])} DRC violations since the last check.")
                    elif comparison["change"] > 0:
                        ctx.info(f"Found {comparison['change']} new DRC violations since the last check.")
                    else:
                        ctx.info(f"No change in the number of DRC violations since the last check.")
        elif drc_results:
             # logging.warning(f"[DRC] DRC check reported failure for {pcb_file}: {drc_results.get('error')}") # <-- Remove log
             # Pass or print a warning if needed
             pass 
        else:
            # logging.error(f"[DRC] DRC check returned None for {pcb_file}") # <-- Remove log
            # Pass or print an error if needed
            pass
        
        # Complete progress
        if ctx:
            await ctx.report_progress(100, 100)
        
        return drc_results or {
            "success": False,
            "error": "DRC check failed with an unknown error"
        }
  • Core helper function that executes the kicad-cli 'pcb drc' command with JSON output, parses the results, categorizes violations by type, and returns structured data.
    async def run_drc_via_cli(pcb_file: str, ctx: Context | None) -> 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 
                if ctx:
                    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, 'r') 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")
                if ctx:
                    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
                }
                
                if ctx:
                    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
  • Registers the DRC tools module, which includes the run_drc_check tool, with the FastMCP server instance.
    register_drc_tools(mcp)
  • Utility function to locate the kicad-cli executable across different operating systems and common installation paths.
    def find_kicad_cli() -> Optional[str]:
        """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

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lamaalrajih/kicad-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server