Add Prettier as a dev dependency with .prettierrc.json config and .prettierignore. Hook added via mirrors-prettier in pre-commit config. All TypeScript, JSON, Markdown, and YAML files auto-formatted. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
215 lines
7.8 KiB
TypeScript
215 lines
7.8 KiB
TypeScript
/**
|
||
* Symbol creator tools for KiCAD MCP server
|
||
*
|
||
* create_symbol – add a new symbol to a .kicad_sym library
|
||
* delete_symbol – remove a symbol from a library
|
||
* list_symbols_in_library – list all symbols in a .kicad_sym file
|
||
* register_symbol_library – add library to sym-lib-table
|
||
*/
|
||
|
||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||
import { z } from "zod";
|
||
|
||
const PinSchema = z.object({
|
||
name: z.string().describe("Pin name, e.g. 'VCC', 'GND', 'IN+', '~' for unnamed"),
|
||
number: z.union([z.string(), z.number()]).describe("Pin number, e.g. '1', '2', 'A1'"),
|
||
type: z
|
||
.enum([
|
||
"input",
|
||
"output",
|
||
"bidirectional",
|
||
"tri_state",
|
||
"passive",
|
||
"free",
|
||
"unspecified",
|
||
"power_in",
|
||
"power_out",
|
||
"open_collector",
|
||
"open_emitter",
|
||
"no_connect",
|
||
])
|
||
.describe("Electrical pin type"),
|
||
at: z
|
||
.object({
|
||
x: z.number().describe("X position in mm"),
|
||
y: z.number().describe("Y position in mm"),
|
||
angle: z
|
||
.number()
|
||
.describe(
|
||
"Direction the pin wire extends FROM the symbol body: 0=right, 90=up, 180=left, 270=down",
|
||
),
|
||
})
|
||
.describe("Pin endpoint position (where the wire connects)"),
|
||
length: z.number().optional().describe("Pin length in mm (default 2.54)"),
|
||
shape: z
|
||
.enum([
|
||
"line",
|
||
"inverted",
|
||
"clock",
|
||
"inverted_clock",
|
||
"input_low",
|
||
"clock_low",
|
||
"output_low",
|
||
"falling_edge_clock",
|
||
"non_logic",
|
||
])
|
||
.optional()
|
||
.describe("Pin graphic shape (default: line)"),
|
||
});
|
||
|
||
const RectSchema = z.object({
|
||
x1: z.number(),
|
||
y1: z.number(),
|
||
x2: z.number(),
|
||
y2: z.number(),
|
||
width: z.number().optional().describe("Stroke width in mm (default 0.254)"),
|
||
fill: z
|
||
.enum(["none", "outline", "background"])
|
||
.optional()
|
||
.describe("Fill type (default: background)"),
|
||
});
|
||
|
||
const PolylineSchema = z.object({
|
||
points: z.array(z.object({ x: z.number(), y: z.number() })).describe("List of XY points in mm"),
|
||
width: z.number().optional().describe("Stroke width in mm (default 0.254)"),
|
||
fill: z.enum(["none", "outline", "background"]).optional(),
|
||
});
|
||
|
||
export function registerSymbolCreatorTools(server: McpServer, callKicadScript: Function) {
|
||
// ── create_symbol ────────────────────────────────────────────────────── //
|
||
server.tool(
|
||
"create_symbol",
|
||
"Create a new schematic symbol in a .kicad_sym library file (created if missing). " +
|
||
"After creation, use register_symbol_library so KiCAD finds it. " +
|
||
"Pin positions are where the wire connects; the symbol body is drawn between them.\n\n" +
|
||
"Coordinate tips:\n" +
|
||
"- Body rectangle typically spans ±2.54 to ±5.08 mm\n" +
|
||
"- Pins on left side: at.x = body_left - length, angle=0 (wire goes right)\n" +
|
||
"- Pins on right side: at.x = body_right + length, angle=180 (wire goes left)\n" +
|
||
"- Pins on top: at.y = body_top + length, angle=270 (wire goes down)\n" +
|
||
"- Pins on bottom: at.y = body_bottom - length, angle=90 (wire goes up)\n" +
|
||
"- Standard pin length: 2.54 mm, standard grid: 2.54 mm",
|
||
{
|
||
libraryPath: z.string().describe("Path to the .kicad_sym file (created if missing)"),
|
||
name: z.string().describe("Symbol name, e.g. 'TMC2209', 'MyOpAmp'"),
|
||
referencePrefix: z
|
||
.string()
|
||
.optional()
|
||
.describe(
|
||
"Schematic reference prefix: 'U' (IC), 'R' (resistor), 'J' (connector), etc. Default: 'U'",
|
||
),
|
||
description: z.string().optional().describe("Human-readable description"),
|
||
keywords: z.string().optional().describe("Space-separated search keywords"),
|
||
datasheet: z.string().optional().describe("Datasheet URL or '~'"),
|
||
footprint: z
|
||
.string()
|
||
.optional()
|
||
.describe("Default footprint, e.g. 'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm'"),
|
||
inBom: z.boolean().optional().describe("Include in BOM (default true)"),
|
||
onBoard: z.boolean().optional().describe("Include in netlist for PCB (default true)"),
|
||
pins: z
|
||
.array(PinSchema)
|
||
.optional()
|
||
.describe("List of pins (can be empty for graphical-only symbols)"),
|
||
rectangles: z
|
||
.array(RectSchema)
|
||
.optional()
|
||
.describe("Body rectangle(s). Typically one rectangle defining the IC body."),
|
||
polylines: z
|
||
.array(PolylineSchema)
|
||
.optional()
|
||
.describe("Polyline graphics for custom body shapes (op-amp triangles, etc.)"),
|
||
overwrite: z
|
||
.boolean()
|
||
.optional()
|
||
.describe("Replace existing symbol with same name (default false)"),
|
||
},
|
||
async (args: {
|
||
libraryPath: string;
|
||
name: string;
|
||
referencePrefix?: string;
|
||
description?: string;
|
||
keywords?: string;
|
||
datasheet?: string;
|
||
footprint?: string;
|
||
inBom?: boolean;
|
||
onBoard?: boolean;
|
||
pins?: z.infer<typeof PinSchema>[];
|
||
rectangles?: z.infer<typeof RectSchema>[];
|
||
polylines?: z.infer<typeof PolylineSchema>[];
|
||
overwrite?: boolean;
|
||
}) => {
|
||
const result = await callKicadScript("create_symbol", args);
|
||
return {
|
||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||
};
|
||
},
|
||
);
|
||
|
||
// ── delete_symbol ────────────────────────────────────────────────────── //
|
||
server.tool(
|
||
"delete_symbol",
|
||
"Remove a symbol from a .kicad_sym library file.",
|
||
{
|
||
libraryPath: z.string().describe("Path to the .kicad_sym file"),
|
||
name: z.string().describe("Symbol name to delete"),
|
||
},
|
||
async (args: { libraryPath: string; name: string }) => {
|
||
const result = await callKicadScript("delete_symbol", args);
|
||
return {
|
||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||
};
|
||
},
|
||
);
|
||
|
||
// ── list_symbols_in_library ──────────────────────────────────────────── //
|
||
server.tool(
|
||
"list_symbols_in_library",
|
||
"List all symbol names in a .kicad_sym library file.",
|
||
{
|
||
libraryPath: z.string().describe("Path to the .kicad_sym file"),
|
||
},
|
||
async (args: { libraryPath: string }) => {
|
||
const result = await callKicadScript("list_symbols_in_library", args);
|
||
return {
|
||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||
};
|
||
},
|
||
);
|
||
|
||
// ── register_symbol_library ──────────────────────────────────────────── //
|
||
server.tool(
|
||
"register_symbol_library",
|
||
"Register a .kicad_sym library in KiCAD's sym-lib-table so symbols can be used in schematics. " +
|
||
"Run this after create_symbol when KiCAD shows 'library not found'.",
|
||
{
|
||
libraryPath: z.string().describe("Full path to the .kicad_sym file"),
|
||
libraryName: z
|
||
.string()
|
||
.optional()
|
||
.describe("Nickname (default: file name without extension)"),
|
||
description: z.string().optional(),
|
||
scope: z
|
||
.enum(["project", "global"])
|
||
.optional()
|
||
.describe("project = writes sym-lib-table next to .kicad_pro; global = user config"),
|
||
projectPath: z
|
||
.string()
|
||
.optional()
|
||
.describe("Path to .kicad_pro or its directory (for scope=project)"),
|
||
},
|
||
async (args: {
|
||
libraryPath: string;
|
||
libraryName?: string;
|
||
description?: string;
|
||
scope?: "project" | "global";
|
||
projectPath?: string;
|
||
}) => {
|
||
const result = await callKicadScript("register_symbol_library", args);
|
||
return {
|
||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||
};
|
||
},
|
||
);
|
||
}
|