Skip to main content
Glama

export_bom_csv

Generate a CSV Bill of Materials for KiCad projects to document components and quantities for manufacturing and procurement.

Instructions

Export a Bill of Materials for a KiCad project.

This tool attempts to generate a CSV BOM file for a KiCad project. It requires KiCad to be installed with the appropriate command-line tools.

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

Returns: Dictionary with export results

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_pathYes
ctxYes

Implementation Reference

  • The primary handler function for the 'export_bom_csv' tool. It orchestrates BOM export by first trying KiCad Python modules and falling back to CLI tools.
    @mcp.tool() async def export_bom_csv(project_path: str, ctx: Context | None) -> Dict[str, Any]: """Export a Bill of Materials for a KiCad project. This tool attempts to generate a CSV BOM file for a KiCad project. It requires KiCad to be installed with the appropriate command-line tools. Args: project_path: Path to the KiCad project file (.kicad_pro) ctx: MCP context for progress reporting Returns: Dictionary with export results """ print(f"Exporting BOM for project: {project_path}") if not os.path.exists(project_path): print(f"Project not found: {project_path}") if ctx: ctx.info(f"Project not found: {project_path}") return {"success": False, "error": f"Project not found: {project_path}"} # Get access to the app context app_context = ctx.request_context.lifespan_context if ctx else None kicad_modules_available = app_context.kicad_modules_available if app_context else False # Report progress if ctx: await ctx.report_progress(10, 100) # Get all project files files = get_project_files(project_path) # We need the schematic file to generate a BOM if "schematic" not in files: print("Schematic file not found in project") if ctx: ctx.info("Schematic file not found in project") return {"success": False, "error": "Schematic file not found"} schematic_file = files["schematic"] project_dir = os.path.dirname(project_path) project_name = os.path.basename(project_path)[:-10] # Remove .kicad_pro extension if ctx: await ctx.report_progress(20, 100) ctx.info(f"Found schematic file: {os.path.basename(schematic_file)}") # Try to export BOM # This will depend on KiCad's command-line tools or Python modules export_result = {"success": False} if kicad_modules_available: try: # Try to use KiCad Python modules if ctx: ctx.info("Attempting to export BOM using KiCad Python modules...") export_result = await export_bom_with_python(schematic_file, project_dir, project_name, ctx) except Exception as e: print(f"Error exporting BOM with Python modules: {str(e)}", exc_info=True) if ctx: ctx.info(f"Error using Python modules: {str(e)}") export_result = {"success": False, "error": str(e)} # If Python method failed, try command-line method if not export_result.get("success", False): try: if ctx: ctx.info("Attempting to export BOM using command-line tools...") export_result = await export_bom_with_cli(schematic_file, project_dir, project_name, ctx) except Exception as e: print(f"Error exporting BOM with CLI: {str(e)}", exc_info=True) if ctx: ctx.info(f"Error using command-line tools: {str(e)}") export_result = {"success": False, "error": str(e)} if ctx: await ctx.report_progress(100, 100) if export_result.get("success", False): if ctx: ctx.info(f"BOM exported successfully to {export_result.get('output_file', 'unknown location')}") else: if ctx: ctx.info(f"Failed to export BOM: {export_result.get('error', 'Unknown error')}") return export_result
  • Top-level registration call in the server creation that invokes register_bom_tools(mcp), which registers the export_bom_csv tool among BOM tools.
    register_bom_tools(mcp)
  • Key helper function that implements the command-line BOM export using kicad-cli, handling platform differences (Windows, macOS, Linux).
    async def export_bom_with_cli(schematic_file: str, output_dir: str, project_name: str, ctx: Context | None) -> Dict[str, Any]: """Export a BOM using KiCad command-line tools. Args: schematic_file: Path to the schematic file output_dir: Directory to save the BOM project_name: Name of the project ctx: MCP context for progress reporting Returns: Dictionary with export results """ import subprocess import platform system = platform.system() print(f"Exporting BOM using CLI tools on {system}") if ctx: await ctx.report_progress(40, 100) # Output file path output_file = os.path.join(output_dir, f"{project_name}_bom.csv") # Define the command based on operating system if system == "Darwin": # macOS from kicad_mcp.config import KICAD_APP_PATH # Path to KiCad command-line tools on macOS kicad_cli = os.path.join(KICAD_APP_PATH, "Contents/MacOS/kicad-cli") if not os.path.exists(kicad_cli): return { "success": False, "error": f"KiCad CLI tool not found at {kicad_cli}", "schematic_file": schematic_file } # Command to generate BOM cmd = [ kicad_cli, "sch", "export", "bom", "--output", output_file, schematic_file ] elif system == "Windows": from kicad_mcp.config import KICAD_APP_PATH # Path to KiCad command-line tools on Windows kicad_cli = os.path.join(KICAD_APP_PATH, "bin", "kicad-cli.exe") if not os.path.exists(kicad_cli): return { "success": False, "error": f"KiCad CLI tool not found at {kicad_cli}", "schematic_file": schematic_file } # Command to generate BOM cmd = [ kicad_cli, "sch", "export", "bom", "--output", output_file, schematic_file ] elif system == "Linux": # Assume kicad-cli is in the PATH kicad_cli = "kicad-cli" # Command to generate BOM cmd = [ kicad_cli, "sch", "export", "bom", "--output", output_file, schematic_file ] else: return { "success": False, "error": f"Unsupported operating system: {system}", "schematic_file": schematic_file } try: print(f"Running command: {' '.join(cmd)}") if ctx: await ctx.report_progress(60, 100) # Run the command process = subprocess.run(cmd, capture_output=True, text=True, timeout=30) # Check if the command was successful if process.returncode != 0: print(f"BOM export command failed with code {process.returncode}") print(f"Error output: {process.stderr}") return { "success": False, "error": f"BOM export command failed: {process.stderr}", "schematic_file": schematic_file, "command": ' '.join(cmd) } # Check if the output file was created if not os.path.exists(output_file): return { "success": False, "error": "BOM file was not created", "schematic_file": schematic_file, "output_file": output_file } if ctx: await ctx.report_progress(80, 100) # Read the first few lines of the BOM to verify it's valid with open(output_file, 'r') as f: bom_content = f.read(1024) # Read first 1KB if len(bom_content.strip()) == 0: return { "success": False, "error": "Generated BOM file is empty", "schematic_file": schematic_file, "output_file": output_file } return { "success": True, "schematic_file": schematic_file, "output_file": output_file, "file_size": os.path.getsize(output_file), "message": "BOM exported successfully" } except subprocess.TimeoutExpired: print("BOM export command timed out after 30 seconds") return { "success": False, "error": "BOM export command timed out after 30 seconds", "schematic_file": schematic_file } except Exception as e: print(f"Error exporting BOM: {str(e)}", exc_info=True) return { "success": False, "error": f"Error exporting BOM: {str(e)}", "schematic_file": schematic_file }
  • Helper function for BOM export using KiCad Python modules, called first but falls back to CLI if unavailable.
    async def export_bom_with_python(schematic_file: str, output_dir: str, project_name: str, ctx: Context | None) -> Dict[str, Any]: """Export a BOM using KiCad Python modules. Args: schematic_file: Path to the schematic file output_dir: Directory to save the BOM project_name: Name of the project ctx: MCP context for progress reporting Returns: Dictionary with export results """ print(f"Exporting BOM for schematic: {schematic_file}") if ctx: await ctx.report_progress(30, 100) try: # Try to import KiCad Python modules # This is a placeholder since exporting BOMs from schematic files # is complex and KiCad's API for this is not well-documented import kicad import kicad.pcbnew # For now, return a message indicating this method is not implemented yet print("BOM export with Python modules not fully implemented") if ctx: ctx.info("BOM export with Python modules not fully implemented yet") return { "success": False, "error": "BOM export using Python modules is not fully implemented yet. Try using the command-line method.", "schematic_file": schematic_file } except ImportError: print("Failed to import KiCad Python modules") return { "success": False, "error": "Failed to import KiCad Python modules", "schematic_file": schematic_file }
  • The register_bom_tools function that defines both BOM tools (analyze_bom and export_bom_csv) with @mcp.tool() decorators for registration.
    def register_bom_tools(mcp: FastMCP) -> None: """Register BOM-related tools with the MCP server. Args: mcp: The FastMCP server instance """ @mcp.tool() async def analyze_bom(project_path: str, ctx: Context | None) -> Dict[str, Any]: """Analyze a KiCad project's Bill of Materials. This tool will look for BOM files related to a KiCad project and provide analysis including component counts, categories, and cost estimates if available. Args: project_path: Path to the KiCad project file (.kicad_pro) ctx: MCP context for progress reporting Returns: Dictionary with BOM analysis results """ print(f"Analyzing BOM for project: {project_path}") if not os.path.exists(project_path): print(f"Project not found: {project_path}") if ctx: ctx.info(f"Project not found: {project_path}") return {"success": False, "error": f"Project not found: {project_path}"} # Report progress if ctx: await ctx.report_progress(10, 100) ctx.info(f"Looking for BOM files related to {os.path.basename(project_path)}") # Get all project files files = get_project_files(project_path) # Look for BOM files bom_files = {} for file_type, file_path in files.items(): if "bom" in file_type.lower() or file_path.lower().endswith(".csv"): bom_files[file_type] = file_path print(f"Found potential BOM file: {file_path}") if not bom_files: print("No BOM files found for project") if ctx: ctx.info("No BOM files found for project") return { "success": False, "error": "No BOM files found. Export a BOM from KiCad first.", "project_path": project_path } if ctx: await ctx.report_progress(30, 100) # Analyze each BOM file results = { "success": True, "project_path": project_path, "bom_files": {}, "component_summary": {} } total_unique_components = 0 total_components = 0 for file_type, file_path in bom_files.items(): try: if ctx: ctx.info(f"Analyzing {os.path.basename(file_path)}") # Parse the BOM file bom_data, format_info = parse_bom_file(file_path) if not bom_data or len(bom_data) == 0: print(f"Failed to parse BOM file: {file_path}") continue # Analyze the BOM data analysis = analyze_bom_data(bom_data, format_info) # Add to results results["bom_files"][file_type] = { "path": file_path, "format": format_info, "analysis": analysis } # Update totals total_unique_components += analysis["unique_component_count"] total_components += analysis["total_component_count"] print(f"Successfully analyzed BOM file: {file_path}") except Exception as e: print(f"Error analyzing BOM file {file_path}: {str(e)}", exc_info=True) results["bom_files"][file_type] = { "path": file_path, "error": str(e) } if ctx: await ctx.report_progress(70, 100) # Generate overall component summary if total_components > 0: results["component_summary"] = { "total_unique_components": total_unique_components, "total_components": total_components } # Calculate component categories across all BOMs all_categories = {} for file_type, file_info in results["bom_files"].items(): if "analysis" in file_info and "categories" in file_info["analysis"]: for category, count in file_info["analysis"]["categories"].items(): if category not in all_categories: all_categories[category] = 0 all_categories[category] += count results["component_summary"]["categories"] = all_categories # Calculate total cost if available total_cost = 0.0 cost_available = False for file_type, file_info in results["bom_files"].items(): if "analysis" in file_info and "total_cost" in file_info["analysis"]: if file_info["analysis"]["total_cost"] > 0: total_cost += file_info["analysis"]["total_cost"] cost_available = True if cost_available: results["component_summary"]["total_cost"] = round(total_cost, 2) currency = next(( file_info["analysis"].get("currency", "USD") for file_type, file_info in results["bom_files"].items() if "analysis" in file_info and "currency" in file_info["analysis"] ), "USD") results["component_summary"]["currency"] = currency if ctx: await ctx.report_progress(100, 100) ctx.info(f"BOM analysis complete: found {total_components} components") return results @mcp.tool() async def export_bom_csv(project_path: str, ctx: Context | None) -> Dict[str, Any]: """Export a Bill of Materials for a KiCad project. This tool attempts to generate a CSV BOM file for a KiCad project. It requires KiCad to be installed with the appropriate command-line tools. Args: project_path: Path to the KiCad project file (.kicad_pro) ctx: MCP context for progress reporting Returns: Dictionary with export results """ print(f"Exporting BOM for project: {project_path}") if not os.path.exists(project_path): print(f"Project not found: {project_path}") if ctx: ctx.info(f"Project not found: {project_path}") return {"success": False, "error": f"Project not found: {project_path}"} # Get access to the app context app_context = ctx.request_context.lifespan_context if ctx else None kicad_modules_available = app_context.kicad_modules_available if app_context else False # Report progress if ctx: await ctx.report_progress(10, 100) # Get all project files files = get_project_files(project_path) # We need the schematic file to generate a BOM if "schematic" not in files: print("Schematic file not found in project") if ctx: ctx.info("Schematic file not found in project") return {"success": False, "error": "Schematic file not found"} schematic_file = files["schematic"] project_dir = os.path.dirname(project_path) project_name = os.path.basename(project_path)[:-10] # Remove .kicad_pro extension if ctx: await ctx.report_progress(20, 100) ctx.info(f"Found schematic file: {os.path.basename(schematic_file)}") # Try to export BOM # This will depend on KiCad's command-line tools or Python modules export_result = {"success": False} if kicad_modules_available: try: # Try to use KiCad Python modules if ctx: ctx.info("Attempting to export BOM using KiCad Python modules...") export_result = await export_bom_with_python(schematic_file, project_dir, project_name, ctx) except Exception as e: print(f"Error exporting BOM with Python modules: {str(e)}", exc_info=True) if ctx: ctx.info(f"Error using Python modules: {str(e)}") export_result = {"success": False, "error": str(e)} # If Python method failed, try command-line method if not export_result.get("success", False): try: if ctx: ctx.info("Attempting to export BOM using command-line tools...") export_result = await export_bom_with_cli(schematic_file, project_dir, project_name, ctx) except Exception as e: print(f"Error exporting BOM with CLI: {str(e)}", exc_info=True) if ctx: ctx.info(f"Error using command-line tools: {str(e)}") export_result = {"success": False, "error": str(e)} if ctx: await ctx.report_progress(100, 100) if export_result.get("success", False): if ctx: ctx.info(f"BOM exported successfully to {export_result.get('output_file', 'unknown location')}") else: if ctx: ctx.info(f"Failed to export BOM: {export_result.get('error', 'Unknown error')}") return export_result

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