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>
296 lines
9.7 KiB
TypeScript
296 lines
9.7 KiB
TypeScript
/**
|
|
* JLCPCB API tools for KiCAD MCP server
|
|
* Provides access to JLCPCB's complete parts catalog via their API
|
|
*/
|
|
|
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { z } from "zod";
|
|
|
|
export function registerJLCPCBApiTools(server: McpServer, callKicadScript: Function) {
|
|
// Download JLCPCB parts database
|
|
server.tool(
|
|
"download_jlcpcb_database",
|
|
`Download the complete JLCPCB parts catalog to local database.
|
|
|
|
This is a one-time setup that downloads ~2.5M+ parts from JLCSearch API.
|
|
No API credentials required - uses public JLCSearch API.
|
|
|
|
The download takes 5-10 minutes and creates a local SQLite database
|
|
for fast offline searching.`,
|
|
{
|
|
force: z
|
|
.boolean()
|
|
.optional()
|
|
.default(false)
|
|
.describe("Force re-download even if database exists"),
|
|
},
|
|
async (args: { force?: boolean }) => {
|
|
const result = await callKicadScript("download_jlcpcb_database", args);
|
|
if (result.success) {
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`✓ Successfully downloaded JLCPCB parts database\n\n` +
|
|
`Total parts: ${result.total_parts}\n` +
|
|
`Basic parts: ${result.basic_parts}\n` +
|
|
`Extended parts: ${result.extended_parts}\n` +
|
|
`Database size: ${result.db_size_mb} MB\n` +
|
|
`Database path: ${result.db_path}`,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`✗ Failed to download JLCPCB database: ${result.message || "Unknown error"}\n\n` +
|
|
`Make sure JLCPCB_API_KEY and JLCPCB_API_SECRET environment variables are set.`,
|
|
},
|
|
],
|
|
};
|
|
},
|
|
);
|
|
|
|
// Search JLCPCB parts
|
|
server.tool(
|
|
"search_jlcpcb_parts",
|
|
`Search JLCPCB parts catalog by specifications.
|
|
|
|
Searches the local JLCPCB database (must be downloaded first with download_jlcpcb_database).
|
|
Provides real pricing, stock info, and library type (Basic parts = free assembly).
|
|
|
|
Use this to find components with exact specifications and cost optimization.`,
|
|
{
|
|
query: z
|
|
.string()
|
|
.optional()
|
|
.describe("Free-text search (e.g., '10k resistor 0603', 'ESP32', 'STM32F103')"),
|
|
category: z
|
|
.string()
|
|
.optional()
|
|
.describe("Filter by category (e.g., 'Resistors', 'Capacitors', 'Microcontrollers')"),
|
|
package: z
|
|
.string()
|
|
.optional()
|
|
.describe("Filter by package type (e.g., '0603', 'SOT-23', 'QFN-32')"),
|
|
library_type: z
|
|
.enum(["Basic", "Extended", "Preferred", "All"])
|
|
.optional()
|
|
.default("All")
|
|
.describe("Filter by library type (Basic = free assembly at JLCPCB)"),
|
|
manufacturer: z.string().optional().describe("Filter by manufacturer name"),
|
|
in_stock: z
|
|
.boolean()
|
|
.optional()
|
|
.default(true)
|
|
.describe("Only show parts with available stock"),
|
|
limit: z.number().optional().default(20).describe("Maximum number of results to return"),
|
|
},
|
|
async (args: any) => {
|
|
const result = await callKicadScript("search_jlcpcb_parts", args);
|
|
if (result.success && result.parts) {
|
|
if (result.parts.length === 0) {
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`No JLCPCB parts found matching your criteria.\n\n` +
|
|
`Try broadening your search or check if the database is populated.`,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
const partsList = result.parts
|
|
.map((p: any) => {
|
|
const priceInfo =
|
|
p.price_breaks && p.price_breaks.length > 0
|
|
? ` - $${p.price_breaks[0].price}/ea`
|
|
: "";
|
|
const stockInfo = p.stock > 0 ? ` (${p.stock} in stock)` : " (out of stock)";
|
|
return `${p.lcsc}: ${p.mfr_part} - ${p.description} [${p.library_type}]${priceInfo}${stockInfo}`;
|
|
})
|
|
.join("\n");
|
|
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`Found ${result.count} JLCPCB parts:\n\n${partsList}\n\n` +
|
|
`💡 Basic parts have free assembly. Extended parts charge $3 setup fee per unique part.`,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`Failed to search JLCPCB parts: ${result.message || "Unknown error"}\n\n` +
|
|
`Make sure you've downloaded the database first using download_jlcpcb_database.`,
|
|
},
|
|
],
|
|
};
|
|
},
|
|
);
|
|
|
|
// Get JLCPCB part details
|
|
server.tool(
|
|
"get_jlcpcb_part",
|
|
"Get detailed information about a specific JLCPCB part by LCSC number",
|
|
{
|
|
lcsc_number: z.string().describe("LCSC part number (e.g., 'C25804', 'C2286')"),
|
|
},
|
|
async (args: { lcsc_number: string }) => {
|
|
const result = await callKicadScript("get_jlcpcb_part", args);
|
|
if (result.success && result.part) {
|
|
const p = result.part;
|
|
const priceTable =
|
|
p.price_breaks && p.price_breaks.length > 0
|
|
? "\n\nPrice Breaks:\n" +
|
|
p.price_breaks.map((pb: any) => ` ${pb.qty}+: $${pb.price}/ea`).join("\n")
|
|
: "";
|
|
|
|
const footprints =
|
|
result.footprints && result.footprints.length > 0
|
|
? "\n\nSuggested KiCAD Footprints:\n" +
|
|
result.footprints.map((f: string) => ` - ${f}`).join("\n")
|
|
: "";
|
|
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`LCSC: ${p.lcsc}\n` +
|
|
`MFR Part: ${p.mfr_part}\n` +
|
|
`Manufacturer: ${p.manufacturer}\n` +
|
|
`Category: ${p.category} / ${p.subcategory}\n` +
|
|
`Package: ${p.package}\n` +
|
|
`Description: ${p.description}\n` +
|
|
`Library Type: ${p.library_type} ${p.library_type === "Basic" ? "(Free assembly!)" : ""}\n` +
|
|
`Stock: ${p.stock}\n` +
|
|
(p.datasheet ? `Datasheet: ${p.datasheet}\n` : "") +
|
|
priceTable +
|
|
footprints,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`Part not found: ${args.lcsc_number}\n\n` +
|
|
`Make sure you've downloaded the JLCPCB database first.`,
|
|
},
|
|
],
|
|
};
|
|
},
|
|
);
|
|
|
|
// Get JLCPCB database statistics
|
|
server.tool(
|
|
"get_jlcpcb_database_stats",
|
|
"Get statistics about the local JLCPCB parts database",
|
|
{},
|
|
async () => {
|
|
const result = await callKicadScript("get_jlcpcb_database_stats", {});
|
|
if (result.success) {
|
|
const stats = result.stats;
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`JLCPCB Database Statistics:\n\n` +
|
|
`Total parts: ${stats.total_parts.toLocaleString()}\n` +
|
|
`Basic parts: ${stats.basic_parts.toLocaleString()} (free assembly)\n` +
|
|
`Extended parts: ${stats.extended_parts.toLocaleString()} ($3 setup fee each)\n` +
|
|
`In stock: ${stats.in_stock.toLocaleString()}\n` +
|
|
`Database path: ${stats.db_path}`,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`JLCPCB database not found or empty.\n\n` +
|
|
`Run download_jlcpcb_database first to populate the database.`,
|
|
},
|
|
],
|
|
};
|
|
},
|
|
);
|
|
|
|
// Suggest alternative parts
|
|
server.tool(
|
|
"suggest_jlcpcb_alternatives",
|
|
`Suggest alternative JLCPCB parts for a given component.
|
|
|
|
Finds similar parts that may be cheaper, have more stock, or are Basic library type.
|
|
Useful for cost optimization and finding alternatives when parts are out of stock.`,
|
|
{
|
|
lcsc_number: z.string().describe("Reference LCSC part number to find alternatives for"),
|
|
limit: z.number().optional().default(5).describe("Maximum number of alternatives to return"),
|
|
},
|
|
async (args: { lcsc_number: string; limit?: number }) => {
|
|
const result = await callKicadScript("suggest_jlcpcb_alternatives", args);
|
|
if (result.success && result.alternatives) {
|
|
if (result.alternatives.length === 0) {
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: `No alternatives found for ${args.lcsc_number}`,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
const altsList = result.alternatives
|
|
.map((p: any, i: number) => {
|
|
const priceInfo =
|
|
p.price_breaks && p.price_breaks.length > 0
|
|
? ` - $${p.price_breaks[0].price}/ea`
|
|
: "";
|
|
const savings =
|
|
result.reference_price && p.price_breaks && p.price_breaks.length > 0
|
|
? ` (${((1 - p.price_breaks[0].price / result.reference_price) * 100).toFixed(0)}% cheaper)`
|
|
: "";
|
|
return `${i + 1}. ${p.lcsc}: ${p.mfr_part} [${p.library_type}]${priceInfo}${savings}\n ${p.description}\n Stock: ${p.stock}`;
|
|
})
|
|
.join("\n\n");
|
|
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: `Alternative parts for ${args.lcsc_number}:\n\n${altsList}`,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: `Failed to find alternatives: ${result.message || "Unknown error"}`,
|
|
},
|
|
],
|
|
};
|
|
},
|
|
);
|
|
}
|