jlcpcb-mcp-parts-finder
Version:
MCP server for searching JLCPCB electronic components database
129 lines • 3.85 kB
JavaScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import Database from "better-sqlite3";
import { z } from "zod";
import { homedir } from "os";
import { join } from "path";
import { existsSync } from "fs";
// Create server instance
const server = new McpServer({
name: "jlcpcb-mcp-parts-finder",
version: "1.0.0",
});
// Database path - check multiple locations
const dbLocations = [
process.env.JLCPCB_DB_PATH,
join(homedir(), ".jlcpcb-mcp", "cache.sqlite3"),
join(process.cwd(), "vendor", "jlcpcb-parts-mcp", "cache.sqlite3"),
"/opt/jlcpcb-mcp/cache.sqlite3",
].filter(Boolean);
let dbPath;
for (const path of dbLocations) {
if (path && existsSync(path)) {
dbPath = path;
break;
}
}
if (!dbPath) {
// Database not found - exit silently for MCP compatibility
process.exit(1);
}
// Connect to database
let db;
try {
db = new Database(dbPath, { readonly: true });
// Connected to database successfully
}
catch (error) {
// Failed to open database
process.exit(1);
}
// Define the search_parts tool
server.tool("search_parts", "Search JLCPCB parts by category and keyword", {
category_id: z
.number()
.describe("Category ID (e.g., 208 for Audio Connectors)"),
keyword: z
.string()
.optional()
.describe("Search keyword"),
limit: z
.number()
.optional()
.default(20)
.describe("Max results (default: 20)"),
}, async ({ category_id, keyword = "", limit = 20 }) => {
try {
const query = keyword
? `SELECT lcsc, mfr, description, package, stock
FROM components
WHERE category_id = ? AND (mfr LIKE ? OR description LIKE ?)
ORDER BY stock DESC LIMIT ?`
: `SELECT lcsc, mfr, description, package, stock
FROM components
WHERE category_id = ?
ORDER BY stock DESC LIMIT ?`;
const params = keyword
? [category_id, `%${keyword}%`, `%${keyword}%`, limit]
: [category_id, limit];
const stmt = db.prepare(query);
const results = stmt.all(...params);
const text = results
.map((r) => `C${r.lcsc}: ${r.mfr} - ${r.description || "No description"} (${r.package}, Stock: ${r.stock})`)
.join("\n");
return {
content: [{ type: "text", text: text || "No results found" }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
};
}
});
// Define the list_categories tool
server.tool("list_categories", "List all JLCPCB component categories", {}, async () => {
try {
const categories = db
.prepare("SELECT id, category, subcategory FROM categories ORDER BY category, subcategory")
.all();
const text = categories
.map((c) => `${c.id}: ${c.category} > ${c.subcategory}`)
.join("\n");
return {
content: [{ type: "text", text }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
};
}
});
// Handle graceful shutdown
process.on("SIGINT", () => {
// Shutting down
if (db) {
db.close();
}
process.exit(0);
});
process.on("SIGTERM", () => {
// Shutting down
if (db) {
db.close();
}
process.exit(0);
});
// Start the server
async function run() {
const transport = new StdioServerTransport();
await server.connect(transport);
// Server started successfully
}
run().catch((error) => {
// Failed to start server
process.exit(1);
});
//# sourceMappingURL=index.js.map