@jlcpcb/mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,582 @@
1
+ /**
2
+ * EasyEDA Community Library MCP Tools
3
+ * Search and 3D model download for community-contributed components
4
+ */
5
+
6
+ import { z } from 'zod'
7
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js'
8
+ import { easyedaCommunityClient } from '@jlcpcb/core'
9
+ import { join } from 'path'
10
+ import { execSync } from 'child_process'
11
+ import { tmpdir } from 'os'
12
+
13
+ // Tool Definitions
14
+
15
+ export const easyedaSearchTool: Tool = {
16
+ name: 'easyeda_search',
17
+ description:
18
+ 'Search EasyEDA community library for user-contributed symbols and footprints. Use this for parts not in LCSC official library (e.g., XIAO, Arduino modules, custom breakouts). Returns results with UUIDs and optionally opens an HTML preview.',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ query: {
23
+ type: 'string',
24
+ description: 'Search term (e.g., "XIAO RP2040", "ESP32-C3 module")',
25
+ },
26
+ source: {
27
+ type: 'string',
28
+ enum: ['user', 'lcsc', 'easyeda', 'all'],
29
+ description:
30
+ 'Filter by source. "user" for community-contributed (default)',
31
+ },
32
+ limit: {
33
+ type: 'number',
34
+ description: 'Max results to return (default: 20)',
35
+ },
36
+ open_preview: {
37
+ type: 'boolean',
38
+ description: 'Generate and open HTML preview in browser',
39
+ default: true,
40
+ },
41
+ },
42
+ required: ['query'],
43
+ },
44
+ }
45
+
46
+ export const easyedaGet3DModelTool: Tool = {
47
+ name: 'easyeda_get_3d_model',
48
+ description:
49
+ 'Download 3D model for an EasyEDA community component. Requires the model UUID from easyeda_get.',
50
+ inputSchema: {
51
+ type: 'object',
52
+ properties: {
53
+ uuid: {
54
+ type: 'string',
55
+ description: '3D model UUID from easyeda_get result',
56
+ },
57
+ format: {
58
+ type: 'string',
59
+ enum: ['step', 'obj'],
60
+ description: 'Model format: "step" or "obj" (default: step)',
61
+ },
62
+ },
63
+ required: ['uuid'],
64
+ },
65
+ }
66
+
67
+ // Zod Schemas
68
+
69
+ const EasyedaSearchParamsSchema = z.object({
70
+ query: z.string().min(1),
71
+ source: z.enum(['user', 'lcsc', 'easyeda', 'all']).optional(),
72
+ limit: z.number().min(1).max(100).optional(),
73
+ open_preview: z.boolean().optional(),
74
+ })
75
+
76
+ const EasyedaGet3DModelParamsSchema = z.object({
77
+ uuid: z.string().min(1),
78
+ format: z.enum(['step', 'obj']).default('step'),
79
+ })
80
+
81
+ // Tool Handlers
82
+
83
+ export async function handleEasyedaSearch(args: unknown) {
84
+ const params = EasyedaSearchParamsSchema.parse(args)
85
+ const openPreview = params.open_preview ?? true
86
+
87
+ const results = await easyedaCommunityClient.search({
88
+ query: params.query,
89
+ source: params.source,
90
+ limit: params.limit || 20,
91
+ })
92
+
93
+ if (results.length === 0) {
94
+ return {
95
+ content: [
96
+ {
97
+ type: 'text' as const,
98
+ text: `No results found for "${params.query}"`,
99
+ },
100
+ ],
101
+ }
102
+ }
103
+
104
+ // Generate text output
105
+ let output = `Found ${results.length} results for "${params.query}":\n\n`
106
+ output += '| # | Title | Package | Owner | UUID |\n'
107
+ output += '|---|-------|---------|-------|------|\n'
108
+
109
+ for (let i = 0; i < results.length; i++) {
110
+ const r = results[i]
111
+ output += `| ${i + 1} | ${r.title} | ${r.package} | ${r.owner.nickname || r.owner.username} | ${r.uuid} |\n`
112
+ }
113
+
114
+ output += '\nUse `library_fetch` with the UUID to add component to global JLC-MCP libraries.'
115
+ output += '\nUse `easyeda_fetch` with the UUID to add to project-local EasyEDA library.'
116
+
117
+ // Generate HTML preview
118
+ if (openPreview) {
119
+ const { filepath, browserOpened } = await generateHtmlPreview(params.query, results)
120
+ if (browserOpened) {
121
+ output += `\n\nHTML preview opened in browser.`
122
+ } else {
123
+ output += `\n\nCould not open browser automatically.`
124
+ }
125
+ output += `\nPreview file: ${filepath}`
126
+ }
127
+
128
+ return {
129
+ content: [
130
+ {
131
+ type: 'text' as const,
132
+ text: output,
133
+ },
134
+ ],
135
+ }
136
+ }
137
+
138
+ export async function handleEasyedaGet3DModel(args: unknown) {
139
+ const params = EasyedaGet3DModelParamsSchema.parse(args)
140
+
141
+ const model = await easyedaCommunityClient.get3DModel(
142
+ params.uuid,
143
+ params.format
144
+ )
145
+
146
+ if (!model) {
147
+ return {
148
+ content: [
149
+ {
150
+ type: 'text' as const,
151
+ text: `3D model ${params.uuid} not found`,
152
+ },
153
+ ],
154
+ isError: true,
155
+ }
156
+ }
157
+
158
+ return {
159
+ content: [
160
+ {
161
+ type: 'text' as const,
162
+ text: `3D model downloaded (${model.length} bytes, ${params.format.toUpperCase()} format)\n\nBase64 data:\n${model.toString('base64').slice(0, 500)}...`,
163
+ },
164
+ ],
165
+ }
166
+ }
167
+
168
+ // Helper Functions
169
+
170
+ /**
171
+ * Generate HTML preview file and open it in browser
172
+ * Fetches component details to generate SVG previews for both symbol and footprint
173
+ * Returns the filepath and whether the browser was successfully opened
174
+ */
175
+ async function generateHtmlPreview(
176
+ query: string,
177
+ results: Awaited<ReturnType<typeof easyedaCommunityClient.search>>
178
+ ): Promise<{ filepath: string; browserOpened: boolean }> {
179
+ const timestamp = Date.now()
180
+ const filename = `easyeda-search-${timestamp}.html`
181
+ const filepath = join(tmpdir(), filename)
182
+
183
+ // No-image placeholder SVG
184
+ const noImageSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 150" style="background:#2a2a2a"><text x="100" y="75" text-anchor="middle" fill="#666" font-size="12">No Preview</text></svg>`
185
+ const noImageDataUri = `data:image/svg+xml,${encodeURIComponent(noImageSvg)}`
186
+
187
+ // Generate cards with symbol thumbnail URL and footprint SVG
188
+ const cardsPromises = results.slice(0, 10).map(async (r) => {
189
+ // Symbol image: use EasyEDA's thumbnail URL
190
+ const symbolImageUrl = `https://image.easyeda.com/components/${r.uuid}.png`
191
+
192
+ // Footprint: generate SVG from shape data (need to fetch component details)
193
+ let footprintSvg = ''
194
+ try {
195
+ const component = await easyedaCommunityClient.getComponent(r.uuid)
196
+ if (component) {
197
+ const rawData = component.rawData as any
198
+ // For docType 2: footprint in packageDetail.dataStr
199
+ // For docType 4: footprint in top-level dataStr
200
+ const fpDataStr = rawData?.packageDetail?.dataStr || rawData?.dataStr
201
+ if (fpDataStr?.shape) {
202
+ footprintSvg = generateFootprintSvg(fpDataStr)
203
+ }
204
+ }
205
+ } catch {
206
+ // Ignore fetch errors, show placeholder
207
+ }
208
+
209
+ const footprintDataUri = footprintSvg ? `data:image/svg+xml,${encodeURIComponent(footprintSvg)}` : noImageDataUri
210
+
211
+ return `
212
+ <div class="card">
213
+ <div class="images">
214
+ <div class="image-box">
215
+ <div class="image-label">Symbol</div>
216
+ <img src="${symbolImageUrl}" alt="Symbol" onerror="this.src='${noImageDataUri}'">
217
+ </div>
218
+ <div class="image-box">
219
+ <div class="image-label">Footprint</div>
220
+ <img src="${footprintDataUri}" alt="Footprint">
221
+ </div>
222
+ </div>
223
+ <h3>${escapeHtml(r.title)}</h3>
224
+ <div class="meta">
225
+ <div><strong>Package:</strong> ${escapeHtml(r.package || 'N/A')}</div>
226
+ <div><strong>Owner:</strong> ${escapeHtml(r.owner.nickname || r.owner.username)}</div>
227
+ ${r.manufacturer ? `<div><strong>Mfr:</strong> ${escapeHtml(r.manufacturer)}</div>` : ''}
228
+ </div>
229
+ <div class="uuid" onclick="navigator.clipboard.writeText('${r.uuid}'); this.classList.add('copied'); setTimeout(() => this.classList.remove('copied'), 1000);">
230
+ ${r.uuid}
231
+ </div>
232
+ </div>`
233
+ })
234
+
235
+ const cards = (await Promise.all(cardsPromises)).join('\n')
236
+
237
+ const html = `<!DOCTYPE html>
238
+ <html lang="en">
239
+ <head>
240
+ <meta charset="UTF-8">
241
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
242
+ <title>EasyEDA Search: ${escapeHtml(query)}</title>
243
+ <style>
244
+ * { box-sizing: border-box; }
245
+ body {
246
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
247
+ max-width: 1400px;
248
+ margin: 0 auto;
249
+ padding: 20px;
250
+ background: #f9f9f9;
251
+ color: #333;
252
+ }
253
+ h1 { margin-bottom: 8px; }
254
+ .subtitle { color: #666; margin-bottom: 20px; }
255
+ .grid {
256
+ display: grid;
257
+ grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
258
+ gap: 16px;
259
+ }
260
+ .card {
261
+ background: white;
262
+ border: 1px solid #ddd;
263
+ border-radius: 8px;
264
+ padding: 16px;
265
+ transition: box-shadow 0.2s;
266
+ }
267
+ .card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
268
+ .card .images {
269
+ display: flex;
270
+ gap: 8px;
271
+ margin-bottom: 12px;
272
+ }
273
+ .card .image-box {
274
+ flex: 1;
275
+ min-width: 0;
276
+ }
277
+ .card .image-label {
278
+ font-size: 10px;
279
+ color: #888;
280
+ text-transform: uppercase;
281
+ text-align: center;
282
+ margin-bottom: 4px;
283
+ }
284
+ .card img {
285
+ width: 100%;
286
+ height: 120px;
287
+ object-fit: contain;
288
+ border-radius: 4px;
289
+ border: 1px solid #eee;
290
+ }
291
+ .card h3 {
292
+ margin: 0 0 8px;
293
+ font-size: 15px;
294
+ line-height: 1.3;
295
+ overflow: hidden;
296
+ text-overflow: ellipsis;
297
+ white-space: nowrap;
298
+ }
299
+ .card .meta {
300
+ color: #666;
301
+ font-size: 12px;
302
+ line-height: 1.6;
303
+ }
304
+ .card .meta div { margin-bottom: 2px; }
305
+ .card .uuid {
306
+ font-family: 'SF Mono', Monaco, 'Courier New', monospace;
307
+ font-size: 10px;
308
+ color: #888;
309
+ background: #f5f5f5;
310
+ padding: 6px 8px;
311
+ border-radius: 4px;
312
+ margin-top: 12px;
313
+ cursor: pointer;
314
+ word-break: break-all;
315
+ transition: background 0.2s;
316
+ }
317
+ .card .uuid:hover { background: #e8e8e8; }
318
+ .card .uuid.copied { background: #d4edda; color: #155724; }
319
+ .instructions {
320
+ background: #e8f4fd;
321
+ border: 1px solid #b8daff;
322
+ border-radius: 8px;
323
+ padding: 16px;
324
+ margin-bottom: 20px;
325
+ font-size: 14px;
326
+ }
327
+ .instructions code {
328
+ background: #fff;
329
+ padding: 2px 6px;
330
+ border-radius: 4px;
331
+ font-family: 'SF Mono', Monaco, monospace;
332
+ }
333
+ </style>
334
+ </head>
335
+ <body>
336
+ <h1>EasyEDA Search: "${escapeHtml(query)}"</h1>
337
+ <p class="subtitle">Found ${results.length} results. Click UUID to copy to clipboard.</p>
338
+
339
+ <div class="instructions">
340
+ <strong>How to use:</strong><br>
341
+ 1. Click on a UUID to copy it<br>
342
+ 2. Use <code>library_fetch</code> with the UUID to add to global JLC-MCP libraries<br>
343
+ 3. Or use <code>easyeda_fetch</code> for project-local EasyEDA library
344
+ </div>
345
+
346
+ <div class="grid">
347
+ ${cards}
348
+ </div>
349
+ </body>
350
+ </html>`
351
+
352
+ // Write HTML file
353
+ require('fs').writeFileSync(filepath, html, 'utf-8')
354
+
355
+ // Open in default browser (cross-platform)
356
+ const browserOpened = openInBrowser(filepath)
357
+
358
+ return { filepath, browserOpened }
359
+ }
360
+
361
+ /**
362
+ * Open a file in the default browser (cross-platform)
363
+ * Returns true if browser was successfully opened, false otherwise
364
+ */
365
+ function openInBrowser(filepath: string): boolean {
366
+ const platform = process.platform
367
+
368
+ try {
369
+ switch (platform) {
370
+ case 'darwin':
371
+ // macOS
372
+ execSync(`open "${filepath}"`, { stdio: 'ignore' })
373
+ return true
374
+ case 'win32':
375
+ // Windows - use start command with empty title
376
+ execSync(`start "" "${filepath}"`, { stdio: 'ignore', shell: 'cmd.exe' })
377
+ return true
378
+ case 'linux':
379
+ default:
380
+ // Linux and other Unix-like systems
381
+ execSync(`xdg-open "${filepath}"`, { stdio: 'ignore' })
382
+ return true
383
+ }
384
+ } catch {
385
+ // If the platform-specific command fails, try alternatives
386
+ const fallbacks = ['xdg-open', 'sensible-browser', 'x-www-browser', 'gnome-open']
387
+ for (const cmd of fallbacks) {
388
+ try {
389
+ execSync(`${cmd} "${filepath}"`, { stdio: 'ignore' })
390
+ return true
391
+ } catch {
392
+ // Try next fallback
393
+ }
394
+ }
395
+ // All attempts failed
396
+ return false
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Escape HTML special characters
402
+ */
403
+ function escapeHtml(str: string): string {
404
+ return str
405
+ .replace(/&/g, '&amp;')
406
+ .replace(/</g, '&lt;')
407
+ .replace(/>/g, '&gt;')
408
+ .replace(/"/g, '&quot;')
409
+ .replace(/'/g, '&#039;')
410
+ }
411
+
412
+ /**
413
+ * Generate SVG from EasyEDA footprint dataStr
414
+ * Renders shapes with proper z-ordering: regions → tracks → pads → holes → text
415
+ */
416
+ function generateFootprintSvg(dataStr: {
417
+ shape?: string[]
418
+ BBox?: { x: number; y: number; width: number; height: number }
419
+ head?: { x?: number; y?: number }
420
+ }): string {
421
+ if (!dataStr.shape || dataStr.shape.length === 0) {
422
+ return ''
423
+ }
424
+
425
+ // Get bounding box or calculate from origin
426
+ const bbox = dataStr.BBox || { x: 0, y: 0, width: 100, height: 100 }
427
+ const padding = 5
428
+ const viewBox = `${bbox.x - padding} ${bbox.y - padding} ${bbox.width + padding * 2} ${bbox.height + padding * 2}`
429
+
430
+ // Separate shapes by type for proper z-ordering
431
+ const regions: string[] = []
432
+ const tracks: string[] = []
433
+ const pads: string[] = []
434
+ const holes: string[] = []
435
+ const texts: string[] = []
436
+
437
+ for (const shape of dataStr.shape) {
438
+ if (typeof shape !== 'string') continue
439
+
440
+ if (shape.startsWith('SOLIDREGION~')) {
441
+ const svg = renderSolidRegion(shape)
442
+ if (svg) regions.push(svg)
443
+ } else if (shape.startsWith('TRACK~')) {
444
+ const svg = renderTrackShape(shape)
445
+ if (svg) tracks.push(svg)
446
+ } else if (shape.startsWith('PAD~')) {
447
+ const result = renderPadShape(shape)
448
+ if (result) {
449
+ pads.push(result.pad)
450
+ if (result.hole) holes.push(result.hole)
451
+ }
452
+ } else if (shape.startsWith('TEXT~')) {
453
+ const svg = renderTextShape(shape)
454
+ if (svg) texts.push(svg)
455
+ }
456
+ }
457
+
458
+ const allElements = [...regions, ...tracks, ...pads, ...holes, ...texts]
459
+ if (allElements.length === 0) {
460
+ return ''
461
+ }
462
+
463
+ // KiCAD-style colors: black bg, red pads, grey holes, yellow outlines/text
464
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" style="background:#000000">
465
+ <style>
466
+ .pad { fill: #CC0000; stroke: none; }
467
+ .pad-hole { fill: #666666; }
468
+ .track { fill: none; stroke: #FFFF00; stroke-linecap: round; stroke-linejoin: round; }
469
+ .region { fill: #CC0000; opacity: 0.6; }
470
+ .text-path { fill: none; stroke: #FFFF00; stroke-width: 0.4; stroke-linecap: round; stroke-linejoin: round; }
471
+ </style>
472
+ ${allElements.join('\n ')}
473
+ </svg>`
474
+ }
475
+
476
+ /**
477
+ * Render PAD shape to SVG - returns pad and hole separately for z-ordering
478
+ * Format: PAD~shapeType~cx~cy~width~height~layer~~pinNum~holeDia~points~rot~id~...
479
+ */
480
+ function renderPadShape(padData: string): { pad: string; hole: string | null } | null {
481
+ const fields = padData.split('~')
482
+ const shapeType = fields[1]
483
+ const cx = parseFloat(fields[2]) || 0
484
+ const cy = parseFloat(fields[3]) || 0
485
+
486
+ if (shapeType === 'POLYGON') {
487
+ // PAD~POLYGON~cx~cy~width~height~layer~~pinNum~holeDia~points...
488
+ // Field indices: 0=PAD, 1=POLYGON, 2=cx, 3=cy, 4=width, 5=height, 6=layer, 7=empty, 8=pinNum, 9=holeDia, 10=points
489
+ const holeDia = parseFloat(fields[9]) || 0
490
+ const pointsStr = fields[10] || ''
491
+ if (!pointsStr) return null
492
+
493
+ // Parse polygon points (space-separated x y pairs)
494
+ const coords = pointsStr.split(' ').map(Number)
495
+ if (coords.length < 4) return null
496
+
497
+ let pathD = `M ${coords[0]} ${coords[1]}`
498
+ for (let i = 2; i < coords.length; i += 2) {
499
+ pathD += ` L ${coords[i]} ${coords[i + 1]}`
500
+ }
501
+ pathD += ' Z'
502
+
503
+ return {
504
+ pad: `<path class="pad" d="${pathD}"/>`,
505
+ hole: holeDia > 0 ? `<circle class="pad-hole" cx="${cx}" cy="${cy}" r="${holeDia}"/>` : null,
506
+ }
507
+ }
508
+
509
+ // Standard pads: ELLIPSE, OVAL, RECT, ROUND
510
+ const width = parseFloat(fields[4]) || 0
511
+ const height = parseFloat(fields[5]) || 0
512
+ const holeDia = parseFloat(fields[9]) || 0
513
+
514
+ let padSvg = ''
515
+
516
+ if (shapeType === 'ELLIPSE' || shapeType === 'OVAL' || shapeType === 'ROUND') {
517
+ const rx = width / 2
518
+ const ry = height / 2
519
+ padSvg = `<ellipse class="pad" cx="${cx}" cy="${cy}" rx="${rx}" ry="${ry}"/>`
520
+ } else {
521
+ // RECT or default
522
+ const rectX = cx - width / 2
523
+ const rectY = cy - height / 2
524
+ padSvg = `<rect class="pad" x="${rectX}" y="${rectY}" width="${width}" height="${height}"/>`
525
+ }
526
+
527
+ return {
528
+ pad: padSvg,
529
+ hole: holeDia > 0 ? `<circle class="pad-hole" cx="${cx}" cy="${cy}" r="${holeDia}"/>` : null,
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Render TRACK shape to SVG
535
+ * Format: TRACK~width~layer~~points
536
+ */
537
+ function renderTrackShape(trackData: string): string | null {
538
+ const fields = trackData.split('~')
539
+ const strokeWidth = parseFloat(fields[1]) || 0.5
540
+ const pointsStr = fields[4] || ''
541
+
542
+ if (!pointsStr) return null
543
+
544
+ const coords = pointsStr.split(' ').map(Number)
545
+ if (coords.length < 4) return null
546
+
547
+ let pathD = `M ${coords[0]} ${coords[1]}`
548
+ for (let i = 2; i < coords.length; i += 2) {
549
+ pathD += ` L ${coords[i]} ${coords[i + 1]}`
550
+ }
551
+
552
+ return `<path class="track" d="${pathD}" stroke-width="${strokeWidth}"/>`
553
+ }
554
+
555
+ /**
556
+ * Render SOLIDREGION shape to SVG
557
+ * Format: SOLIDREGION~layer~~path~fill~id~...
558
+ * Field indices: 0=SOLIDREGION, 1=layer, 2=empty, 3=path, 4=fill
559
+ */
560
+ function renderSolidRegion(regionData: string): string | null {
561
+ const fields = regionData.split('~')
562
+ const pathD = fields[3] || ''
563
+
564
+ if (!pathD || !pathD.startsWith('M')) return null
565
+
566
+ return `<path class="region" d="${pathD}"/>`
567
+ }
568
+
569
+ /**
570
+ * Render TEXT shape to SVG using pre-rendered path
571
+ * Format: TEXT~align~x~y~strokeWidth~rot~?~layer~~fontSize~content~svgPath~id~~flag~type
572
+ * Field indices: 7=layer, 10=content, 11=svgPath
573
+ */
574
+ function renderTextShape(textData: string): string | null {
575
+ const fields = textData.split('~')
576
+ const svgPath = fields[11] || ''
577
+
578
+ if (!svgPath || !svgPath.startsWith('M')) return null
579
+
580
+ // Text is pre-rendered as SVG path commands - just use them directly
581
+ return `<path class="text-path" d="${svgPath}"/>`
582
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * MCP tool definitions and handlers for LCSC MCP server
3
+ */
4
+
5
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
6
+
7
+ // Import LCSC tools
8
+ import { searchComponentsTool, handleSearchComponents } from './search.js';
9
+ import { getComponentTool, handleGetComponent } from './details.js';
10
+ import {
11
+ getSymbolKicadTool,
12
+ getFootprintKicadTool,
13
+ fetchLibraryTool,
14
+ get3DModelTool,
15
+ handleGetSymbolKicad,
16
+ handleGetFootprintKicad,
17
+ handleFetchLibrary,
18
+ handleGet3DModel,
19
+ } from './library.js';
20
+ import {
21
+ updateLibraryTool,
22
+ handleUpdateLibrary,
23
+ } from './library-update.js';
24
+ import {
25
+ fixLibraryTool,
26
+ handleFixLibrary,
27
+ } from './library-fix.js';
28
+
29
+ // Import EasyEDA community tools
30
+ import {
31
+ easyedaSearchTool,
32
+ easyedaGet3DModelTool,
33
+ handleEasyedaSearch,
34
+ handleEasyedaGet3DModel,
35
+ } from './easyeda.js';
36
+
37
+ // Export all tool definitions
38
+ export const tools: Tool[] = [
39
+ // LCSC/JLCPCB official library
40
+ searchComponentsTool,
41
+ getComponentTool,
42
+ getSymbolKicadTool,
43
+ getFootprintKicadTool,
44
+ fetchLibraryTool,
45
+ updateLibraryTool,
46
+ fixLibraryTool,
47
+ get3DModelTool,
48
+ // EasyEDA community library
49
+ easyedaSearchTool,
50
+ easyedaGet3DModelTool,
51
+ ];
52
+
53
+ // Tool handler map
54
+ export const toolHandlers: Record<string, (args: unknown) => Promise<{
55
+ content: Array<{ type: 'text'; text: string }>;
56
+ isError?: boolean;
57
+ }>> = {
58
+ // LCSC/JLCPCB official library
59
+ component_search: handleSearchComponents,
60
+ component_get: handleGetComponent,
61
+ library_get_symbol: handleGetSymbolKicad,
62
+ library_get_footprint: handleGetFootprintKicad,
63
+ library_fetch: handleFetchLibrary,
64
+ library_update: handleUpdateLibrary,
65
+ library_fix: handleFixLibrary,
66
+ library_get_3d_model: handleGet3DModel,
67
+ // EasyEDA community library
68
+ easyeda_search: handleEasyedaSearch,
69
+ easyeda_get_3d_model: handleEasyedaGet3DModel,
70
+ };
71
+
72
+ // Re-export individual tools
73
+ export { searchComponentsTool, handleSearchComponents } from './search.js';
74
+ export { getComponentTool, handleGetComponent } from './details.js';
75
+ export {
76
+ getSymbolKicadTool,
77
+ getFootprintKicadTool,
78
+ fetchLibraryTool,
79
+ get3DModelTool,
80
+ handleGetSymbolKicad,
81
+ handleGetFootprintKicad,
82
+ handleFetchLibrary,
83
+ handleGet3DModel,
84
+ } from './library.js';
85
+ export {
86
+ updateLibraryTool,
87
+ handleUpdateLibrary,
88
+ } from './library-update.js';
89
+ export {
90
+ fixLibraryTool,
91
+ handleFixLibrary,
92
+ } from './library-fix.js';
93
+ export {
94
+ easyedaSearchTool,
95
+ easyedaGet3DModelTool,
96
+ handleEasyedaSearch,
97
+ handleEasyedaGet3DModel,
98
+ } from './easyeda.js';