Skip to main content
Glama
lamaalrajih

KiCad MCP Server

by lamaalrajih

generate_project_thumbnail

Create a thumbnail image of a KiCad PCB layout to visualize the project's design for documentation or sharing purposes.

Instructions

Generate a thumbnail of a KiCad project's PCB layout (Alias for generate_pcb_thumbnail).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_pathYes
ctxYes

Implementation Reference

  • Handler function for the generate_project_thumbnail tool. It is registered with @mcp.tool() and redirects execution to the generate_pcb_thumbnail function for the actual thumbnail generation logic.
    @mcp.tool()
    async def generate_project_thumbnail(project_path: str, ctx: Context | None):
        """Generate a thumbnail of a KiCad project's PCB layout (Alias for generate_pcb_thumbnail)."""
        # This function now just calls the main CLI-based thumbnail generator
        print(f"generate_project_thumbnail called, redirecting to generate_pcb_thumbnail for {project_path}")
        return await generate_pcb_thumbnail(project_path, ctx)
  • Primary handler for PCB thumbnail generation (generate_pcb_thumbnail), invoked by generate_project_thumbnail. Handles project file validation, caching, progress reporting, and delegates to CLI helper.
    @mcp.tool()
    async def generate_pcb_thumbnail(project_path: str, ctx: Context | None):
        """Generate a thumbnail image of a KiCad PCB layout using kicad-cli.
    
        Args:
            project_path: Path to the KiCad project file (.kicad_pro)
            ctx: Context for MCP communication
    
        Returns:
            Thumbnail image of the PCB or None if generation failed
        """
        try:
            # Access the context (with null check)
            app_context = None
            if ctx:
                app_context = ctx.request_context.lifespan_context
            # Removed check for kicad_modules_available as we now use CLI
            
            print(f"Generating thumbnail via CLI for project: {project_path}")
    
            if not os.path.exists(project_path):
                print(f"Project not found: {project_path}")
                if ctx:
                    await ctx.info(f"Project not found: {project_path}")
                return None
    
            # Get PCB file from project
            files = get_project_files(project_path)
            if "pcb" not in files:
                print("PCB file not found in project")
                if ctx:
                    await ctx.info("PCB file not found in project")
                return None
    
            pcb_file = files["pcb"]
            print(f"Found PCB file: {pcb_file}")
    
            # Check cache
            cache_key = f"thumbnail_cli_{pcb_file}_{os.path.getmtime(pcb_file)}"
            if app_context and hasattr(app_context, 'cache') and cache_key in app_context.cache:
                print(f"Using cached CLI thumbnail for {pcb_file}")
                return app_context.cache[cache_key]
    
            if ctx:
                await ctx.report_progress(10, 100)
                await ctx.info(f"Generating thumbnail for {os.path.basename(pcb_file)} using kicad-cli")
    
            # Use command-line tools
            try:
                thumbnail = await generate_thumbnail_with_cli(pcb_file, ctx)
                if thumbnail:
                    # Cache the result if possible
                    if app_context and hasattr(app_context, 'cache'):
                        app_context.cache[cache_key] = thumbnail
                    print("Thumbnail generated successfully via CLI.")
                    return thumbnail
                else:
                     print("generate_thumbnail_with_cli returned None")
                     if ctx:
                         await ctx.info("Failed to generate thumbnail using kicad-cli.")
                     return None
            except Exception as e:
                print(f"Error calling generate_thumbnail_with_cli: {str(e)}", exc_info=True)
                if ctx:
                    await ctx.info(f"Error generating thumbnail with kicad-cli: {str(e)}")
                return None
            
        except asyncio.CancelledError:
            print("Thumbnail generation cancelled")
            raise  # Re-raise to let MCP know the task was cancelled
        except Exception as e:
            print(f"Unexpected error in thumbnail generation: {str(e)}")
            if ctx:
                await ctx.info(f"Error: {str(e)}")
            return None
  • Core utility function that executes kicad-cli to generate SVG thumbnail from PCB file, handling OS-specific paths, subprocess execution, error handling, and image return.
    async def generate_thumbnail_with_cli(pcb_file: str, ctx: Context | None):
        """Generate PCB thumbnail using command line tools.
        This is a fallback method when the kicad Python module is not available or fails.
    
        Args:
            pcb_file: Path to the PCB file (.kicad_pcb)
            ctx: MCP context for progress reporting
    
        Returns:
            Image object containing the PCB thumbnail or None if generation failed
        """
        try:
            print("Attempting to generate thumbnail using KiCad CLI tools")
            if ctx:
                await ctx.report_progress(20, 100)
    
            # --- Determine Output Path --- 
            project_dir = os.path.dirname(pcb_file)
            project_name = os.path.splitext(os.path.basename(pcb_file))[0]
            output_file = os.path.join(project_dir, f"{project_name}_thumbnail.svg")
            # --------------------------- 
    
            # Check for required command-line tools based on OS
            kicad_cli = None
            if system == "Darwin":  # macOS
                kicad_cli_path = os.path.join(KICAD_APP_PATH, "Contents/MacOS/kicad-cli")
                if os.path.exists(kicad_cli_path):
                     kicad_cli = kicad_cli_path
                elif shutil.which("kicad-cli") is not None:
                    kicad_cli = "kicad-cli"  # Try to use from PATH
                else:
                    print(f"kicad-cli not found at {kicad_cli_path} or in PATH")
                    return None
            elif system == "Windows":
                kicad_cli_path = os.path.join(KICAD_APP_PATH, "bin", "kicad-cli.exe")
                if os.path.exists(kicad_cli_path):
                     kicad_cli = kicad_cli_path
                elif shutil.which("kicad-cli.exe") is not None:
                     kicad_cli = "kicad-cli.exe"
                elif shutil.which("kicad-cli") is not None:
                    kicad_cli = "kicad-cli"  # Try to use from PATH (without .exe)
                else:
                    print(f"kicad-cli not found at {kicad_cli_path} or in PATH")
                    return None
            elif system == "Linux":
                kicad_cli = shutil.which("kicad-cli")
                if not kicad_cli:
                    print("kicad-cli not found in PATH")
                    return None
            else:
                print(f"Unsupported operating system: {system}")
                return None
    
            if ctx:
                await ctx.report_progress(30, 100)
                await ctx.info("Using KiCad command line tools for thumbnail generation")        # Build command for generating SVG from PCB using kicad-cli (changed from PNG)
            cmd = [
                kicad_cli,
                "pcb",
                "export",
                "svg", # <-- Changed format to svg
                "--output", output_file,
                "--layers", "F.Cu,B.Cu,F.SilkS,B.SilkS,F.Mask,B.Mask,Edge.Cuts",  # Keep relevant layers
                # Consider adding options like --black-and-white if needed
                pcb_file
            ]
    
            print(f"Running command: {' '.join(cmd)}")
            if ctx:
                await ctx.report_progress(50, 100)
    
            # Run the command
            try:
                process = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=30)
                print(f"Command successful: {process.stdout}")
    
                if ctx:
                    await ctx.report_progress(70, 100)
    
                # Check if the output file was created
                if not os.path.exists(output_file):
                    print(f"Output file not created: {output_file}")
                    return None
    
                # Read the image file
                with open(output_file, 'rb') as f:
                    img_data = f.read()
    
                print(f"Successfully generated thumbnail with CLI, size: {len(img_data)} bytes")
                if ctx:
                    await ctx.report_progress(90, 100)
                    # Inform user about the saved file
                    await ctx.info(f"Thumbnail saved to: {output_file}")
                return Image(data=img_data, format="svg") # <-- Changed format to svg
    
            except subprocess.CalledProcessError as e:
                print(f"Command '{' '.join(e.cmd)}' failed with code {e.returncode}")
                print(f"Stderr: {e.stderr}")
                print(f"Stdout: {e.stdout}")
                if ctx:
                    await ctx.info(f"KiCad CLI command failed: {e.stderr or e.stdout}")
                return None
            except subprocess.TimeoutExpired:
                print(f"Command timed out after 30 seconds: {' '.join(cmd)}")
                if ctx:
                    await ctx.info("KiCad CLI command timed out")
                return None
            except Exception as e:
                print(f"Error running CLI command: {str(e)}", exc_info=True)
                if ctx:
                    await ctx.info(f"Error running KiCad CLI: {str(e)}")
                return None
                    
        except asyncio.CancelledError:
            print("CLI thumbnail generation cancelled")
            raise
        except Exception as e:
            print(f"Unexpected error in CLI thumbnail generation: {str(e)}")
            if ctx:
                await ctx.info(f"Unexpected error: {str(e)}")
            return None
  • Registers the export tools, including generate_project_thumbnail, by calling the register_export_tools function during server initialization.
    register_export_tools(mcp)
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It states the tool generates a thumbnail but doesn't disclose behavioral traits such as what format the thumbnail is in (image type, dimensions), whether it's saved to disk or returned as data, performance characteristics, error conditions, or any side effects. The description is minimal and lacks necessary operational context for a mutation tool (generation implies creation).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is very concise—a single sentence that efficiently states the tool's purpose and alias relationship. It's front-loaded with the core functionality. However, the brevity comes at the cost of completeness, as it omits important details needed for effective use.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (generation implies mutation), lack of annotations, no output schema, and 0% schema description coverage, the description is incomplete. It doesn't cover what the tool returns (e.g., image data, file path), error handling, or usage constraints. For a tool with two required parameters and no structured documentation, this leaves significant gaps for an AI agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the schema provides no parameter documentation. The description mentions 'project_path' implicitly through 'KiCad project' but doesn't explain what this path should be (file path, project name, etc.) or the purpose of 'ctx'. It adds minimal semantic value beyond what's inferable from the tool name, failing to compensate for the lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Generate a thumbnail of a KiCad project's PCB layout.' It specifies the verb ('generate'), resource ('thumbnail'), and domain context ('KiCad project's PCB layout'). However, it doesn't distinguish this tool from its sibling 'generate_pcb_thumbnail' beyond noting it's an alias, which is helpful but not a true functional differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context by specifying 'KiCad project's PCB layout' and mentions it's an 'Alias for generate_pcb_thumbnail,' which provides some guidance about tool relationships. However, it doesn't explicitly state when to use this tool versus alternatives (e.g., other thumbnail generation methods or when to use the aliased tool directly), nor does it mention prerequisites or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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