- Add macOS venv setup using KiCAD's bundled Python (fixes #45) - Update JLCPCB database size from 1-2 GB to 3-5 GB (accurate for 2.5M+ parts) - Update all "100k+" references to "2.5M+" for consistency - Update jlcpcb-api.ts tool description (JLCSearch API, no auth required) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
246 lines
9.2 KiB
TypeScript
246 lines
9.2 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'}`
|
|
}]
|
|
};
|
|
}
|
|
);
|
|
}
|