Adding Tools
This guide covers the process of extending mckicad with new tools, resources, and prompt templates.
Adding a new tool
Section titled “Adding a new tool”1. Choose the right module
Section titled “1. Choose the right module”Look at the existing modules in tools/ and decide where your tool fits:
schematic.py— schematic creation and readingschematic_edit.py— schematic modification and removalschematic_analysis.py— connectivity, ERC, validationschematic_patterns.py— circuit building block patternsbatch.py— batch operationsproject.py— project discovery and managementdrc.py— design rule checksbom.py— BOM operationsexport.py— file export (Gerber, PDF, SVG)routing.py— autoroutinganalysis.py— board-level analysispcb.py— live PCB manipulation via IPC
If none of the existing modules fit, create a new one in tools/.
2. Write the tool function
Section titled “2. Write the tool function”Import mcp from mckicad.server and decorate with @mcp.tool():
from typing import Anyfrom mckicad.server import mcp
@mcp.tool()def my_new_tool(param: str) -> dict[str, Any]: """Clear description of what this tool does.
The docstring is shown to the LLM, so make it specific and actionable. Describe what the tool returns and when to use it. """ try: # Your implementation result = do_something(param) return {"success": True, "data": result} except Exception as e: return {"success": False, "error": str(e)}Follow the return convention: always include success: bool. On failure, include error: str. On success, include relevant data fields.
3. Register the module
Section titled “3. Register the module”If you created a new module, add an import in server.py:
# server.py - add with the other tool importsimport mckicad.tools.my_module # noqa: F401The import triggers the @mcp.tool() decorators, which register the functions with the server. No additional registration code is needed.
4. Write tests
Section titled “4. Write tests”Create a test file in tests/:
import pytestfrom mckicad.tools.my_module import my_new_tool
def test_my_new_tool_success(tmp_path): result = my_new_tool(str(tmp_path / "test.kicad_sch")) assert result["success"] is True
def test_my_new_tool_missing_file(): result = my_new_tool("/nonexistent/path.kicad_sch") assert result["success"] is False assert "error" in resultUse appropriate test markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.requires_kicad.
5. Run tests and lint
Section titled “5. Run tests and lint”make test tests/test_my_module.pymake lintAdding a new resource
Section titled “Adding a new resource”Resources provide read-only data to the LLM. Add them in resources/:
from mckicad.server import mcp
@mcp.resource("kicad://my-data/{parameter}")def my_custom_resource(parameter: str) -> str: """Description of what this resource provides.""" # Return formatted text for the LLM return f"Data about {parameter}"Import the module in server.py to trigger registration.
Adding a new prompt template
Section titled “Adding a new prompt template”Prompts are reusable conversation starters. Add them in prompts/:
from mckicad.server import mcp
@mcp.prompt()def my_workflow_prompt() -> str: """Description of what this prompt helps with.""" return """ I need help with [specific KiCad task]. Please assist me with:
1. [First aspect] 2. [Second aspect] 3. [Third aspect]
My KiCad project is located at: [Enter the full path to your .kicad_pro file here] """Best practices
Section titled “Best practices”Docstrings
Section titled “Docstrings”The tool docstring is shown directly to the calling LLM. Make it:
- Specific — describe exactly what the tool does and returns
- Actionable — explain when to use this tool vs alternatives
- Concise — LLMs work better with focused descriptions
Error handling
Section titled “Error handling”Always catch exceptions and return structured errors:
@mcp.tool()def my_tool(path: str) -> dict[str, Any]: """...""" if not path: return {"success": False, "error": "Path must be non-empty"} try: # ... return {"success": True, "data": result} except FileNotFoundError: return {"success": False, "error": f"File not found: {path}"} except Exception as e: return {"success": False, "error": str(e)}Path validation
Section titled “Path validation”Use the path validator for any file access:
from mckicad.utils.path_validator import validate_path
err = validate_path(path)if err: return {"success": False, "error": err}External commands
Section titled “External commands”Use the secure subprocess wrapper for any external command execution:
from mckicad.utils.secure_subprocess import run_command
result = run_command(["kicad-cli", "pcb", "export", "svg", pcb_path])Logging
Section titled “Logging”Use the module logger, never print():
import logginglogger = logging.getLogger(__name__)
logger.info("Processing %s", path)logger.warning("Unexpected format in %s", path)logger.error("Failed to process %s: %s", path, e)Performance
Section titled “Performance”- Use caching for expensive operations (the lifespan context provides a cache dict)
- Report progress for long-running operations via
ctx.report_progress() - Use
asynciofor concurrent operations where appropriate - Keep tool responses focused — return what the LLM needs, not everything available