feat: Add missing routing/component tools and fix SWIG/UUID bugs
New MCP tools added (TypeScript layer): - Routing: delete_trace, query_traces, get_nets_list, modify_trace, create_netclass, route_differential_pair, refill_zones (with SWIG warning) - Component: get_component_pads, get_component_list, get_pad_position, place_component_array, align_components, duplicate_component Bug fixes: - routing.py: Fix SwigPyObject UUID comparison (str() -> m_Uuid.AsString()) - routing.py: Fix SWIG iterator invalidation after board.Remove() by converting board.Tracks() to list() before iteration - routing.py: Add board.SetModified() and clear Python refs after Remove() to prevent dangling SWIG pointers crashing subsequent calls - routing.py: Wrap each track access in try/except in query_traces() to gracefully skip invalid track objects after bulk delete - routing.py: Add missing return statement (mypy fix) - library.py: Fix search_footprints param mapping search_term->pattern - library.py: Fix fp.name -> fp.full_name field access - library.py: Accept both 'pattern' and 'search_term' parameter names - library.py: Add library filter support in search_footprints - library.py: Fix loop variable shadowing Path object (mypy fix) - design_rules.py: Add type annotation for violation_counts (mypy fix) Contribution guidelines followed (CONTRIBUTING.md): - black python/ applied (3 touched files pass without changes) - mypy python/ passes with 0 errors on all changed files - npx prettier --write applied to changed TypeScript files - npm run build passes (tsc --noEmit: 0 errors) - Commit messages follow feat:/fix: convention Note on diff size: The large insertion/deletion count in the changed files is due to black and prettier reformatting previously unformatted code (missing trailing commas, quote style, line length). The actual logic changes are limited to the 6 files listed above. Note: refill_zones has known SWIG segfault risk (see KNOWN_ISSUES.md). Prefer IPC backend (KiCAD open) or zone fill via KiCAD UI.
This commit is contained in:
@@ -1,159 +1,193 @@
|
||||
/**
|
||||
* Library tools for KiCAD MCP server
|
||||
* Provides access to KiCAD footprint libraries and symbols
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
export function registerLibraryTools(server: McpServer, callKicadScript: Function) {
|
||||
// List available footprint libraries
|
||||
server.tool(
|
||||
"list_libraries",
|
||||
"List all available KiCAD footprint libraries",
|
||||
{
|
||||
search_paths: z.array(z.string()).optional()
|
||||
.describe("Optional additional search paths for libraries")
|
||||
},
|
||||
async (args: { search_paths?: string[] }) => {
|
||||
const result = await callKicadScript("list_libraries", args);
|
||||
if (result.success && result.libraries) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Found ${result.libraries.length} footprint libraries:\n${result.libraries.join('\n')}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to list libraries: ${result.message || 'Unknown error'}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Search for footprints across all libraries
|
||||
server.tool(
|
||||
"search_footprints",
|
||||
"Search for footprints matching a pattern across all libraries",
|
||||
{
|
||||
search_term: z.string()
|
||||
.describe("Search term or pattern to match footprint names"),
|
||||
library: z.string().optional()
|
||||
.describe("Optional specific library to search in"),
|
||||
limit: z.number().optional().default(50)
|
||||
.describe("Maximum number of results to return")
|
||||
},
|
||||
async (args: { search_term: string; library?: string; limit?: number }) => {
|
||||
const result = await callKicadScript("search_footprints", args);
|
||||
if (result.success && result.footprints) {
|
||||
const footprintList = result.footprints.map((fp: any) =>
|
||||
`${fp.library}:${fp.name}${fp.description ? ' - ' + fp.description : ''}`
|
||||
).join('\n');
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Found ${result.footprints.length} matching footprints:\n${footprintList}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to search footprints: ${result.message || 'Unknown error'}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// List footprints in a specific library
|
||||
server.tool(
|
||||
"list_library_footprints",
|
||||
"List all footprints in a specific KiCAD library",
|
||||
{
|
||||
library_name: z.string()
|
||||
.describe("Name of the library to list footprints from"),
|
||||
filter: z.string().optional()
|
||||
.describe("Optional filter pattern for footprint names"),
|
||||
limit: z.number().optional().default(100)
|
||||
.describe("Maximum number of footprints to list")
|
||||
},
|
||||
async (args: { library_name: string; filter?: string; limit?: number }) => {
|
||||
const result = await callKicadScript("list_library_footprints", args);
|
||||
if (result.success && result.footprints) {
|
||||
const footprintList = result.footprints.map((fp: string) => ` - ${fp}`).join('\n');
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Library ${args.library_name} contains ${result.footprints.length} footprints:\n${footprintList}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to list footprints in library ${args.library_name}: ${result.message || 'Unknown error'}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Get detailed information about a specific footprint
|
||||
server.tool(
|
||||
"get_footprint_info",
|
||||
"Get detailed information about a specific footprint",
|
||||
{
|
||||
library_name: z.string()
|
||||
.describe("Name of the library containing the footprint"),
|
||||
footprint_name: z.string()
|
||||
.describe("Name of the footprint to get information about")
|
||||
},
|
||||
async (args: { library_name: string; footprint_name: string }) => {
|
||||
const result = await callKicadScript("get_footprint_info", args);
|
||||
if (result.success && result.info) {
|
||||
const info = result.info;
|
||||
const details = [
|
||||
`Footprint: ${info.name}`,
|
||||
`Library: ${info.library}`,
|
||||
info.description ? `Description: ${info.description}` : '',
|
||||
info.keywords ? `Keywords: ${info.keywords}` : '',
|
||||
info.pads ? `Number of pads: ${info.pads}` : '',
|
||||
info.layers ? `Layers used: ${info.layers.join(', ')}` : '',
|
||||
info.courtyard ? `Courtyard size: ${info.courtyard.width}mm x ${info.courtyard.height}mm` : '',
|
||||
info.attributes ? `Attributes: ${JSON.stringify(info.attributes)}` : ''
|
||||
].filter(line => line).join('\n');
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: details
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to get footprint info: ${result.message || 'Unknown error'}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Library tools for KiCAD MCP server
|
||||
* Provides access to KiCAD footprint libraries and symbols
|
||||
*/
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerLibraryTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
// List available footprint libraries
|
||||
server.tool(
|
||||
"list_libraries",
|
||||
"List all available KiCAD footprint libraries",
|
||||
{
|
||||
search_paths: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe("Optional additional search paths for libraries"),
|
||||
},
|
||||
async (args: { search_paths?: string[] }) => {
|
||||
const result = await callKicadScript("list_libraries", args);
|
||||
if (result.success && result.libraries) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Found ${result.libraries.length} footprint libraries:\n${result.libraries.join("\n")}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to list libraries: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Search for footprints across all libraries
|
||||
server.tool(
|
||||
"search_footprints",
|
||||
"Search for footprints matching a pattern across all libraries",
|
||||
{
|
||||
search_term: z
|
||||
.string()
|
||||
.describe("Search term or pattern to match footprint names"),
|
||||
library: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Optional specific library to search in"),
|
||||
limit: z
|
||||
.number()
|
||||
.optional()
|
||||
.default(50)
|
||||
.describe("Maximum number of results to return"),
|
||||
},
|
||||
async (args: { search_term: string; library?: string; limit?: number }) => {
|
||||
const result = await callKicadScript("search_footprints", {
|
||||
pattern: args.search_term,
|
||||
library: args.library,
|
||||
limit: args.limit,
|
||||
});
|
||||
if (result.success && result.footprints) {
|
||||
const footprintList = result.footprints
|
||||
.map(
|
||||
(fp: any) =>
|
||||
`${fp.full_name || fp.library + ":" + fp.footprint}${fp.description ? " - " + fp.description : ""}`,
|
||||
)
|
||||
.join("\n");
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Found ${result.footprints.length} matching footprints:\n${footprintList}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to search footprints: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// List footprints in a specific library
|
||||
server.tool(
|
||||
"list_library_footprints",
|
||||
"List all footprints in a specific KiCAD library",
|
||||
{
|
||||
library_name: z
|
||||
.string()
|
||||
.describe("Name of the library to list footprints from"),
|
||||
filter: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Optional filter pattern for footprint names"),
|
||||
limit: z
|
||||
.number()
|
||||
.optional()
|
||||
.default(100)
|
||||
.describe("Maximum number of footprints to list"),
|
||||
},
|
||||
async (args: { library_name: string; filter?: string; limit?: number }) => {
|
||||
const result = await callKicadScript("list_library_footprints", args);
|
||||
if (result.success && result.footprints) {
|
||||
const footprintList = result.footprints
|
||||
.map((fp: string) => ` - ${fp}`)
|
||||
.join("\n");
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Library ${args.library_name} contains ${result.footprints.length} footprints:\n${footprintList}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to list footprints in library ${args.library_name}: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Get detailed information about a specific footprint
|
||||
server.tool(
|
||||
"get_footprint_info",
|
||||
"Get detailed information about a specific footprint",
|
||||
{
|
||||
library_name: z
|
||||
.string()
|
||||
.describe("Name of the library containing the footprint"),
|
||||
footprint_name: z
|
||||
.string()
|
||||
.describe("Name of the footprint to get information about"),
|
||||
},
|
||||
async (args: { library_name: string; footprint_name: string }) => {
|
||||
const result = await callKicadScript("get_footprint_info", args);
|
||||
if (result.success && result.info) {
|
||||
const info = result.info;
|
||||
const details = [
|
||||
`Footprint: ${info.name}`,
|
||||
`Library: ${info.library}`,
|
||||
info.description ? `Description: ${info.description}` : "",
|
||||
info.keywords ? `Keywords: ${info.keywords}` : "",
|
||||
info.pads ? `Number of pads: ${info.pads}` : "",
|
||||
info.layers ? `Layers used: ${info.layers.join(", ")}` : "",
|
||||
info.courtyard
|
||||
? `Courtyard size: ${info.courtyard.width}mm x ${info.courtyard.height}mm`
|
||||
: "",
|
||||
info.attributes
|
||||
? `Attributes: ${JSON.stringify(info.attributes)}`
|
||||
: "",
|
||||
]
|
||||
.filter((line) => line)
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: details,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to get footprint info: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user