feat: datasheet tools + fix missing delete_schematic_component
New tools - datasheet: - get_datasheet_url: construct LCSC datasheet PDF URL + product page URL without any API key (URL schema: https://www.lcsc.com/datasheet/<C#>.pdf) - enrich_datasheets: scan .kicad_sch, write LCSC datasheet URL into every symbol that has an LCSC property but an empty Datasheet field; supports dry_run=true for preview; text-based implementation (no skip writes) Implementation: python/commands/datasheet_manager.py New tool - schematic: - delete_schematic_component: remove a placed symbol from a .kicad_sch file by reference designator (e.g. R1, U3) Bug fix - delete_schematic_component (two separate root causes): 1. No MCP tool named delete_schematic_component was registered at all. Any delete-symbol request fell through to the PCB-only delete_component tool which searches pcbnew.BOARD and always returned 'Component not found' for schematic symbols. 2. component_schematic.py::remove_component() still used skip for writes. PR #40 rewrote DynamicSymbolLoader (add path) to avoid skip-induced schematic corruption, but the delete path was not touched by that PR. Fix: _handle_delete_schematic_component in kicad_interface.py uses direct text manipulation with parenthesis-depth tracking (same technique as PR #40), bypassing component_schematic.py entirely. Error message explicitly guides users: 'use delete_component for PCB footprints'. Files changed: - python/commands/datasheet_manager.py (new) - src/tools/datasheet.ts (new) - python/kicad_interface.py: 3 new handlers + dispatch entries - src/tools/schematic.ts: delete_schematic_component tool - src/server.ts: registerDatasheetTools import + call - src/tools/index.ts: export registerDatasheetTools - CHANGELOG.md: document all above
This commit is contained in:
123
src/tools/datasheet.ts
Normal file
123
src/tools/datasheet.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Datasheet tools for KiCAD MCP server
|
||||
*
|
||||
* Enriches KiCAD schematic symbols with LCSC datasheet URLs.
|
||||
* URL schema: https://www.lcsc.com/datasheet/<LCSC#>.pdf (no API key required)
|
||||
*/
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerDatasheetTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
// ── enrich_datasheets ──────────────────────────────────────────────────────
|
||||
server.tool(
|
||||
"enrich_datasheets",
|
||||
`Fill in missing Datasheet URLs in a KiCAD schematic using LCSC part numbers.
|
||||
|
||||
For every placed symbol that has:
|
||||
• (property "LCSC" "C123456") set
|
||||
• (property "Datasheet" "~") or empty
|
||||
|
||||
Sets the Datasheet field to:
|
||||
https://www.lcsc.com/datasheet/C123456.pdf
|
||||
|
||||
The URL is then visible in KiCAD's footprint browser, symbol properties dialog,
|
||||
and any tool that reads the standard KiCAD Datasheet field.
|
||||
No API key or internet lookup required – the URL is constructed directly.
|
||||
|
||||
Use dry_run=true to preview changes without writing.`,
|
||||
{
|
||||
schematic_path: z
|
||||
.string()
|
||||
.describe("Path to the .kicad_sch file to enrich"),
|
||||
dry_run: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe(
|
||||
"If true, show what would be changed without writing to disk (default: false)",
|
||||
),
|
||||
},
|
||||
async (args: { schematic_path: string; dry_run?: boolean }) => {
|
||||
const result = await callKicadScript("enrich_datasheets", args);
|
||||
if (result.success) {
|
||||
const lines: string[] = [];
|
||||
|
||||
if (args.dry_run) {
|
||||
lines.push(`[DRY RUN] Schematic: ${result.schematic}\n`);
|
||||
} else {
|
||||
lines.push(`Schematic: ${result.schematic}\n`);
|
||||
}
|
||||
|
||||
lines.push(`✓ Updated: ${result.updated}`);
|
||||
lines.push(` Already set: ${result.already_set}`);
|
||||
lines.push(` No LCSC number: ${result.no_lcsc}`);
|
||||
lines.push(` No field: ${result.no_datasheet_field}`);
|
||||
|
||||
if (result.details && result.details.length > 0) {
|
||||
lines.push("\nComponents updated:");
|
||||
for (const d of result.details) {
|
||||
lines.push(` ${d.reference.padEnd(6)} ${d.lcsc.padEnd(12)} → ${d.url}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.updated === 0 && !args.dry_run) {
|
||||
lines.push(
|
||||
"\nNo changes needed – all LCSC components already have a Datasheet URL.",
|
||||
);
|
||||
}
|
||||
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to enrich datasheets: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ── get_datasheet_url ──────────────────────────────────────────────────────
|
||||
server.tool(
|
||||
"get_datasheet_url",
|
||||
`Get the LCSC datasheet URL for a component by LCSC number.
|
||||
|
||||
Returns the direct PDF URL and product page URL.
|
||||
No network request – URL is constructed from the LCSC number alone.
|
||||
|
||||
Example: get_datasheet_url("C179739")
|
||||
→ https://www.lcsc.com/datasheet/C179739.pdf`,
|
||||
{
|
||||
lcsc: z
|
||||
.string()
|
||||
.describe(
|
||||
'LCSC part number, with or without "C" prefix (e.g. "C179739" or "179739")',
|
||||
),
|
||||
},
|
||||
async (args: { lcsc: string }) => {
|
||||
const result = await callKicadScript("get_datasheet_url", { lcsc: args.lcsc });
|
||||
if (result.success) {
|
||||
const lines = [
|
||||
`LCSC: ${result.lcsc}`,
|
||||
`Datasheet PDF: ${result.datasheet_url}`,
|
||||
`Product page: ${result.product_url}`,
|
||||
];
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Invalid LCSC number: ${args.lcsc}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
/**
|
||||
* Tools index for KiCAD MCP server
|
||||
*
|
||||
* Exports all tool registration functions
|
||||
*/
|
||||
|
||||
export { registerProjectTools } from './project.js';
|
||||
export { registerBoardTools } from './board.js';
|
||||
export { registerComponentTools } from './component.js';
|
||||
export { registerRoutingTools } from './routing.js';
|
||||
export { registerDesignRuleTools } from './design-rules.js';
|
||||
export { registerExportTools } from './export.js';
|
||||
export { registerSchematicTools } from './schematic.js';
|
||||
export { registerLibraryTools } from './library.js';
|
||||
export { registerUITools } from './ui.js';
|
||||
/**
|
||||
* Tools index for KiCAD MCP server
|
||||
*
|
||||
* Exports all tool registration functions
|
||||
*/
|
||||
|
||||
export { registerProjectTools } from "./project.js";
|
||||
export { registerBoardTools } from "./board.js";
|
||||
export { registerComponentTools } from "./component.js";
|
||||
export { registerRoutingTools } from "./routing.js";
|
||||
export { registerDesignRuleTools } from "./design-rules.js";
|
||||
export { registerExportTools } from "./export.js";
|
||||
export { registerSchematicTools } from "./schematic.js";
|
||||
export { registerLibraryTools } from "./library.js";
|
||||
export { registerUITools } from "./ui.js";
|
||||
export { registerDatasheetTools } from "./datasheet.js";
|
||||
|
||||
@@ -1,268 +1,383 @@
|
||||
/**
|
||||
* Schematic tools for KiCAD MCP server
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
export function registerSchematicTools(server: McpServer, callKicadScript: Function) {
|
||||
// Create schematic tool
|
||||
server.tool(
|
||||
"create_schematic",
|
||||
"Create a new schematic",
|
||||
{
|
||||
name: z.string().describe("Schematic name"),
|
||||
path: z.string().optional().describe("Optional path"),
|
||||
},
|
||||
async (args: { name: string; path?: string }) => {
|
||||
const result = await callKicadScript("create_schematic", args);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Add component to schematic
|
||||
server.tool(
|
||||
"add_schematic_component",
|
||||
"Add a component to the schematic. Symbol format is 'Library:SymbolName' (e.g., 'Device:R', 'EDA-MCP:ESP32-C3')",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
symbol: z.string().describe("Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3)"),
|
||||
reference: z.string().describe("Component reference (e.g., R1, U1)"),
|
||||
value: z.string().optional().describe("Component value"),
|
||||
position: z.object({
|
||||
x: z.number(),
|
||||
y: z.number()
|
||||
}).optional().describe("Position on schematic"),
|
||||
},
|
||||
async (args: { schematicPath: string; symbol: string; reference: string; value?: string; position?: { x: number; y: number } }) => {
|
||||
// Transform to what Python backend expects
|
||||
const [library, symbolName] = args.symbol.includes(':')
|
||||
? args.symbol.split(':')
|
||||
: ['Device', args.symbol];
|
||||
|
||||
const transformed = {
|
||||
schematicPath: args.schematicPath,
|
||||
component: {
|
||||
library,
|
||||
type: symbolName,
|
||||
reference: args.reference,
|
||||
value: args.value,
|
||||
// Python expects flat x, y not nested position
|
||||
x: args.position?.x ?? 0,
|
||||
y: args.position?.y ?? 0
|
||||
}
|
||||
};
|
||||
|
||||
const result = await callKicadScript("add_schematic_component", transformed);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully added ${args.reference} (${args.symbol}) to schematic`
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to add component: ${result.message || JSON.stringify(result)}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Connect components with wire
|
||||
server.tool(
|
||||
"add_wire",
|
||||
"Add a wire connection in the schematic",
|
||||
{
|
||||
start: z.object({
|
||||
x: z.number(),
|
||||
y: z.number()
|
||||
}).describe("Start position"),
|
||||
end: z.object({
|
||||
x: z.number(),
|
||||
y: z.number()
|
||||
}).describe("End position"),
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("add_wire", args);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Add pin-to-pin connection
|
||||
server.tool(
|
||||
"add_schematic_connection",
|
||||
"Connect two component pins with a wire",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
sourceRef: z.string().describe("Source component reference (e.g., R1)"),
|
||||
sourcePin: z.string().describe("Source pin name/number (e.g., 1, 2, GND)"),
|
||||
targetRef: z.string().describe("Target component reference (e.g., C1)"),
|
||||
targetPin: z.string().describe("Target pin name/number (e.g., 1, 2, VCC)")
|
||||
},
|
||||
async (args: { schematicPath: string; sourceRef: string; sourcePin: string; targetRef: string; targetPin: string }) => {
|
||||
const result = await callKicadScript("add_schematic_connection", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully connected ${args.sourceRef}/${args.sourcePin} to ${args.targetRef}/${args.targetPin}`
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to add connection: ${result.message || 'Unknown error'}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Add net label
|
||||
server.tool(
|
||||
"add_schematic_net_label",
|
||||
"Add a net label to the schematic",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
netName: z.string().describe("Name of the net (e.g., VCC, GND, SIGNAL_1)"),
|
||||
position: z.array(z.number()).length(2).describe("Position [x, y] for the label")
|
||||
},
|
||||
async (args: { schematicPath: string; netName: string; position: number[] }) => {
|
||||
const result = await callKicadScript("add_schematic_net_label", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully added net label '${args.netName}' at position [${args.position}]`
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to add net label: ${result.message || 'Unknown error'}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Connect pin to net
|
||||
server.tool(
|
||||
"connect_to_net",
|
||||
"Connect a component pin to a named net",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
componentRef: z.string().describe("Component reference (e.g., U1, R1)"),
|
||||
pinName: z.string().describe("Pin name/number to connect"),
|
||||
netName: z.string().describe("Name of the net to connect to")
|
||||
},
|
||||
async (args: { schematicPath: string; componentRef: string; pinName: string; netName: string }) => {
|
||||
const result = await callKicadScript("connect_to_net", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully connected ${args.componentRef}/${args.pinName} to net '${args.netName}'`
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to connect to net: ${result.message || 'Unknown error'}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Get net connections
|
||||
server.tool(
|
||||
"get_net_connections",
|
||||
"Get all connections for a named net",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
netName: z.string().describe("Name of the net to query")
|
||||
},
|
||||
async (args: { schematicPath: string; netName: string }) => {
|
||||
const result = await callKicadScript("get_net_connections", args);
|
||||
if (result.success && result.connections) {
|
||||
const connectionList = result.connections.map((conn: any) =>
|
||||
` - ${conn.component}/${conn.pin}`
|
||||
).join('\n');
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Net '${args.netName}' connections:\n${connectionList}`
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to get net connections: ${result.message || 'Unknown error'}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Generate netlist
|
||||
server.tool(
|
||||
"generate_netlist",
|
||||
"Generate a netlist from the schematic",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file")
|
||||
},
|
||||
async (args: { schematicPath: string }) => {
|
||||
const result = await callKicadScript("generate_netlist", args);
|
||||
if (result.success && result.netlist) {
|
||||
const netlist = result.netlist;
|
||||
const output = [
|
||||
`=== Netlist for ${args.schematicPath} ===`,
|
||||
`\nComponents (${netlist.components.length}):`,
|
||||
...netlist.components.map((comp: any) =>
|
||||
` ${comp.reference}: ${comp.value} (${comp.footprint || 'No footprint'})`
|
||||
),
|
||||
`\nNets (${netlist.nets.length}):`,
|
||||
...netlist.nets.map((net: any) => {
|
||||
const connections = net.connections.map((conn: any) =>
|
||||
`${conn.component}/${conn.pin}`
|
||||
).join(', ');
|
||||
return ` ${net.name}: ${connections}`;
|
||||
})
|
||||
].join('\n');
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: output
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to generate netlist: ${result.message || 'Unknown error'}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Schematic tools for KiCAD MCP server
|
||||
*/
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerSchematicTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
// Create schematic tool
|
||||
server.tool(
|
||||
"create_schematic",
|
||||
"Create a new schematic",
|
||||
{
|
||||
name: z.string().describe("Schematic name"),
|
||||
path: z.string().optional().describe("Optional path"),
|
||||
},
|
||||
async (args: { name: string; path?: string }) => {
|
||||
const result = await callKicadScript("create_schematic", args);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Add component to schematic
|
||||
server.tool(
|
||||
"add_schematic_component",
|
||||
"Add a component to the schematic. Symbol format is 'Library:SymbolName' (e.g., 'Device:R', 'EDA-MCP:ESP32-C3')",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
symbol: z
|
||||
.string()
|
||||
.describe(
|
||||
"Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3)",
|
||||
),
|
||||
reference: z.string().describe("Component reference (e.g., R1, U1)"),
|
||||
value: z.string().optional().describe("Component value"),
|
||||
position: z
|
||||
.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
})
|
||||
.optional()
|
||||
.describe("Position on schematic"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
symbol: string;
|
||||
reference: string;
|
||||
value?: string;
|
||||
position?: { x: number; y: number };
|
||||
}) => {
|
||||
// Transform to what Python backend expects
|
||||
const [library, symbolName] = args.symbol.includes(":")
|
||||
? args.symbol.split(":")
|
||||
: ["Device", args.symbol];
|
||||
|
||||
const transformed = {
|
||||
schematicPath: args.schematicPath,
|
||||
component: {
|
||||
library,
|
||||
type: symbolName,
|
||||
reference: args.reference,
|
||||
value: args.value,
|
||||
// Python expects flat x, y not nested position
|
||||
x: args.position?.x ?? 0,
|
||||
y: args.position?.y ?? 0,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await callKicadScript(
|
||||
"add_schematic_component",
|
||||
transformed,
|
||||
);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Successfully added ${args.reference} (${args.symbol}) to schematic`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to add component: ${result.message || JSON.stringify(result)}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Delete component from schematic
|
||||
server.tool(
|
||||
"delete_schematic_component",
|
||||
`Remove a placed symbol from a KiCAD schematic (.kicad_sch).
|
||||
|
||||
This removes the symbol instance (the placed component) from the schematic.
|
||||
It does NOT remove the symbol definition from lib_symbols.
|
||||
|
||||
Note: This tool operates on schematic files (.kicad_sch).
|
||||
To remove a footprint from a PCB, use delete_component instead.`,
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch file"),
|
||||
reference: z
|
||||
.string()
|
||||
.describe("Reference designator of the component to remove (e.g. R1, U3)"),
|
||||
},
|
||||
async (args: { schematicPath: string; reference: string }) => {
|
||||
const result = await callKicadScript("delete_schematic_component", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Successfully removed ${args.reference} from schematic`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to remove component: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Connect components with wire
|
||||
server.tool(
|
||||
"add_wire",
|
||||
"Add a wire connection in the schematic",
|
||||
{
|
||||
start: z
|
||||
.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
})
|
||||
.describe("Start position"),
|
||||
end: z
|
||||
.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
})
|
||||
.describe("End position"),
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("add_wire", args);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Add pin-to-pin connection
|
||||
server.tool(
|
||||
"add_schematic_connection",
|
||||
"Connect two component pins with a wire",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
sourceRef: z.string().describe("Source component reference (e.g., R1)"),
|
||||
sourcePin: z
|
||||
.string()
|
||||
.describe("Source pin name/number (e.g., 1, 2, GND)"),
|
||||
targetRef: z.string().describe("Target component reference (e.g., C1)"),
|
||||
targetPin: z
|
||||
.string()
|
||||
.describe("Target pin name/number (e.g., 1, 2, VCC)"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
sourceRef: string;
|
||||
sourcePin: string;
|
||||
targetRef: string;
|
||||
targetPin: string;
|
||||
}) => {
|
||||
const result = await callKicadScript("add_schematic_connection", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Successfully connected ${args.sourceRef}/${args.sourcePin} to ${args.targetRef}/${args.targetPin}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to add connection: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Add net label
|
||||
server.tool(
|
||||
"add_schematic_net_label",
|
||||
"Add a net label to the schematic",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
netName: z
|
||||
.string()
|
||||
.describe("Name of the net (e.g., VCC, GND, SIGNAL_1)"),
|
||||
position: z
|
||||
.array(z.number())
|
||||
.length(2)
|
||||
.describe("Position [x, y] for the label"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
netName: string;
|
||||
position: number[];
|
||||
}) => {
|
||||
const result = await callKicadScript("add_schematic_net_label", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Successfully added net label '${args.netName}' at position [${args.position}]`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to add net label: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Connect pin to net
|
||||
server.tool(
|
||||
"connect_to_net",
|
||||
"Connect a component pin to a named net",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
componentRef: z.string().describe("Component reference (e.g., U1, R1)"),
|
||||
pinName: z.string().describe("Pin name/number to connect"),
|
||||
netName: z.string().describe("Name of the net to connect to"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
componentRef: string;
|
||||
pinName: string;
|
||||
netName: string;
|
||||
}) => {
|
||||
const result = await callKicadScript("connect_to_net", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Successfully connected ${args.componentRef}/${args.pinName} to net '${args.netName}'`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to connect to net: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Get net connections
|
||||
server.tool(
|
||||
"get_net_connections",
|
||||
"Get all connections for a named net",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
netName: z.string().describe("Name of the net to query"),
|
||||
},
|
||||
async (args: { schematicPath: string; netName: string }) => {
|
||||
const result = await callKicadScript("get_net_connections", args);
|
||||
if (result.success && result.connections) {
|
||||
const connectionList = result.connections
|
||||
.map((conn: any) => ` - ${conn.component}/${conn.pin}`)
|
||||
.join("\n");
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Net '${args.netName}' connections:\n${connectionList}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to get net connections: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Generate netlist
|
||||
server.tool(
|
||||
"generate_netlist",
|
||||
"Generate a netlist from the schematic",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
},
|
||||
async (args: { schematicPath: string }) => {
|
||||
const result = await callKicadScript("generate_netlist", args);
|
||||
if (result.success && result.netlist) {
|
||||
const netlist = result.netlist;
|
||||
const output = [
|
||||
`=== Netlist for ${args.schematicPath} ===`,
|
||||
`\nComponents (${netlist.components.length}):`,
|
||||
...netlist.components.map(
|
||||
(comp: any) =>
|
||||
` ${comp.reference}: ${comp.value} (${comp.footprint || "No footprint"})`,
|
||||
),
|
||||
`\nNets (${netlist.nets.length}):`,
|
||||
...netlist.nets.map((net: any) => {
|
||||
const connections = net.connections
|
||||
.map((conn: any) => `${conn.component}/${conn.pin}`)
|
||||
.join(", ");
|
||||
return ` ${net.name}: ${connections}`;
|
||||
}),
|
||||
].join("\n");
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: output,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to generate netlist: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user