kicad-mcp/src/mckicad/tools/project.py
Ryan Malloy 4ae38fed59 Rebuild on FastMCP 3 with src-layout and kicad-sch-api integration
Migrate from FastMCP 2.14.5 to 3.1.0 with complete architectural
overhaul. Adopt src-layout packaging, lazy config functions to
eliminate .env race condition, and decorator-based tool registration.

Consolidate 14 tool modules into 8 focused modules (33 tools total).
Add 9 new schematic tools via kicad-sch-api for creating and
manipulating .kicad_sch files. Drop pandas dependency (BOM uses
stdlib csv). Remove ~17k lines of stubs, over-engineering, and
dead code.

All checks pass: ruff clean, mypy 0 errors, 17/17 tests green.
2026-03-03 18:26:54 -07:00

132 lines
4.0 KiB
Python

"""
Project management tools for KiCad MCP server.
Provides tools for discovering, inspecting, and opening KiCad projects
on the local filesystem.
"""
import logging
import os
from typing import Any
from mckicad.server import mcp
from mckicad.utils.file_utils import get_project_files, load_project_json
from mckicad.utils.kicad_utils import find_kicad_projects, open_kicad_project
logger = logging.getLogger(__name__)
@mcp.tool()
def list_projects() -> dict[str, Any]:
"""Find and list all KiCad projects in configured search paths.
Scans KICAD_SEARCH_PATHS and default project directories for
.kicad_pro files. Returns project name, path, relative path,
and last-modified timestamp for each discovered project.
Returns:
Dictionary with success status, project list, and count.
"""
logger.info("Scanning for KiCad projects")
try:
projects = find_kicad_projects()
logger.info("Found %d KiCad project(s)", len(projects))
return {
"success": True,
"data": projects,
"count": len(projects),
"error": None,
}
except Exception as e:
logger.error("Failed to list projects: %s", e)
return {
"success": False,
"data": [],
"count": 0,
"error": str(e),
}
@mcp.tool()
def get_project_structure(project_path: str) -> dict[str, Any]:
"""Get the file structure and metadata of a KiCad project.
Enumerates all files associated with a .kicad_pro project file
(schematic, PCB, netlist, BOM exports, etc.) and loads project
metadata from the JSON project file.
Args:
project_path: Absolute path to the .kicad_pro file.
Returns:
Dictionary with project name, directory, file map, and metadata.
"""
logger.info("Getting project structure for: %s", project_path)
if not os.path.exists(project_path):
logger.warning("Project file not found: %s", project_path)
return {
"success": False,
"data": None,
"error": f"Project not found: {project_path}",
}
try:
project_dir = os.path.dirname(project_path)
# Strip .kicad_pro extension to get the project name
basename = os.path.basename(project_path)
project_name = basename.rsplit(".kicad_pro", 1)[0] if basename.endswith(".kicad_pro") else basename
files = get_project_files(project_path)
metadata = {}
project_data = load_project_json(project_path)
if project_data and "metadata" in project_data:
metadata = project_data["metadata"]
logger.info(
"Project '%s' has %d associated file(s)", project_name, len(files)
)
return {
"success": True,
"data": {
"name": project_name,
"path": project_path,
"directory": project_dir,
"files": files,
"metadata": metadata,
},
"error": None,
}
except Exception as e:
logger.error("Failed to get project structure for %s: %s", project_path, e)
return {
"success": False,
"data": None,
"error": str(e),
}
@mcp.tool()
def open_project(project_path: str) -> dict[str, Any]:
"""Open a KiCad project in the KiCad application.
Launches KiCad (or the system default handler) with the specified
.kicad_pro file. Uses platform-appropriate open commands (open on
macOS, xdg-open on Linux).
Args:
project_path: Absolute path to the .kicad_pro file.
Returns:
Dictionary with success status and any error output.
"""
logger.info("Opening project: %s", project_path)
result = open_kicad_project(project_path)
if result.get("success"):
logger.info("Project opened successfully: %s", project_path)
else:
logger.warning("Failed to open project: %s", result.get("error"))
return result