@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.
- package/CHANGELOG.md +15 -0
- package/README.md +241 -0
- package/debug-text.ts +24 -0
- package/dist/assets/search.html +528 -0
- package/dist/index.js +32364 -0
- package/dist/src/index.js +28521 -0
- package/package.json +49 -0
- package/scripts/build-search-page.ts +68 -0
- package/src/assets/search-built.html +528 -0
- package/src/assets/search.html +458 -0
- package/src/browser/index.ts +381 -0
- package/src/browser/kicad-renderer.ts +646 -0
- package/src/browser/sexpr-parser.ts +321 -0
- package/src/http/routes.ts +253 -0
- package/src/http/server.ts +74 -0
- package/src/index.ts +117 -0
- package/src/tools/details.ts +66 -0
- package/src/tools/easyeda.ts +582 -0
- package/src/tools/index.ts +98 -0
- package/src/tools/library-fix.ts +414 -0
- package/src/tools/library-update.ts +412 -0
- package/src/tools/library.ts +263 -0
- package/src/tools/search.ts +58 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library Update Tool
|
|
3
|
+
* Parses existing JLC-MCP-* libraries and regenerates all components
|
|
4
|
+
* with latest data, normalization, and hybrid footprint logic
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { existsSync, readdirSync } from 'fs';
|
|
10
|
+
import { readFile } from 'fs/promises';
|
|
11
|
+
import { homedir, platform } from 'os';
|
|
12
|
+
import {
|
|
13
|
+
easyedaClient,
|
|
14
|
+
symbolConverter,
|
|
15
|
+
footprintConverter,
|
|
16
|
+
getLibraryCategory,
|
|
17
|
+
getLibraryFilename,
|
|
18
|
+
getFootprintDirName,
|
|
19
|
+
get3DModelsDirName,
|
|
20
|
+
getAllCategories,
|
|
21
|
+
type LibraryCategory,
|
|
22
|
+
ensureDir,
|
|
23
|
+
writeText,
|
|
24
|
+
} from '@jlcpcb/core';
|
|
25
|
+
import { join } from 'path';
|
|
26
|
+
|
|
27
|
+
// KiCad versions to check (newest first)
|
|
28
|
+
const KICAD_VERSIONS = ['9.0', '8.0'];
|
|
29
|
+
|
|
30
|
+
// 3rd party library namespace
|
|
31
|
+
const LIBRARY_NAMESPACE = 'jlc_mcp';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detect KiCad major version from existing user directories
|
|
35
|
+
*/
|
|
36
|
+
function detectKicadVersion(): string {
|
|
37
|
+
const home = homedir();
|
|
38
|
+
const baseDir = join(home, 'Documents', 'KiCad');
|
|
39
|
+
|
|
40
|
+
for (const version of KICAD_VERSIONS) {
|
|
41
|
+
if (existsSync(join(baseDir, version))) {
|
|
42
|
+
return version;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return '9.0'; // Default
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get library paths for update operation
|
|
50
|
+
* Platform-specific paths matching where ${KICAD9_3RD_PARTY} resolves:
|
|
51
|
+
* - macOS/Windows: ~/Documents/KiCad/{version}/3rdparty/jlc_mcp/
|
|
52
|
+
* - Linux: ~/.local/share/kicad/{version}/3rdparty/jlc_mcp/
|
|
53
|
+
*/
|
|
54
|
+
function getLibraryPaths(projectPath?: string): {
|
|
55
|
+
symbolsDir: string;
|
|
56
|
+
footprintDir: string;
|
|
57
|
+
models3dDir: string;
|
|
58
|
+
} {
|
|
59
|
+
if (projectPath) {
|
|
60
|
+
const librariesDir = join(projectPath, 'libraries');
|
|
61
|
+
return {
|
|
62
|
+
symbolsDir: join(librariesDir, 'symbols'),
|
|
63
|
+
footprintDir: join(librariesDir, 'footprints', getFootprintDirName()),
|
|
64
|
+
models3dDir: join(librariesDir, '3dmodels', get3DModelsDirName()),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const home = homedir();
|
|
69
|
+
const version = detectKicadVersion();
|
|
70
|
+
const plat = platform();
|
|
71
|
+
|
|
72
|
+
let base: string;
|
|
73
|
+
if (plat === 'linux') {
|
|
74
|
+
base = join(home, '.local', 'share', 'kicad', version, '3rdparty', LIBRARY_NAMESPACE);
|
|
75
|
+
} else {
|
|
76
|
+
base = join(home, 'Documents', 'KiCad', version, '3rdparty', LIBRARY_NAMESPACE);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
symbolsDir: join(base, 'symbols'),
|
|
81
|
+
footprintDir: join(base, 'footprints', getFootprintDirName()),
|
|
82
|
+
models3dDir: join(base, '3dmodels', get3DModelsDirName()),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Extract LCSC IDs from a symbol library file
|
|
88
|
+
*/
|
|
89
|
+
function extractLcscIdsFromLibrary(content: string): string[] {
|
|
90
|
+
const pattern = /\(property\s+"LCSC"\s+"(C\d+)"/g;
|
|
91
|
+
const ids: string[] = [];
|
|
92
|
+
let match;
|
|
93
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
94
|
+
ids.push(match[1]);
|
|
95
|
+
}
|
|
96
|
+
return ids;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate standard KiCad symbol library header
|
|
101
|
+
*/
|
|
102
|
+
function generateEmptyLibrary(): string {
|
|
103
|
+
return `(kicad_symbol_lib
|
|
104
|
+
\t(version 20241209)
|
|
105
|
+
\t(generator "jlc-mcp")
|
|
106
|
+
\t(generator_version "9.0")
|
|
107
|
+
)\n`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Find all JLC-MCP-*.kicad_sym files in a directory
|
|
112
|
+
*/
|
|
113
|
+
function findJlcLibraries(symbolsDir: string): string[] {
|
|
114
|
+
if (!existsSync(symbolsDir)) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const files = readdirSync(symbolsDir);
|
|
120
|
+
return files
|
|
121
|
+
.filter((f) => f.startsWith('JLC-MCP-') && f.endsWith('.kicad_sym'))
|
|
122
|
+
.map((f) => join(symbolsDir, f));
|
|
123
|
+
} catch {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const updateLibraryTool: Tool = {
|
|
129
|
+
name: 'library_update',
|
|
130
|
+
description: `Parse existing JLC-MCP-* libraries and regenerate all components with latest data.
|
|
131
|
+
|
|
132
|
+
If no JLC-MCP-* libraries exist, initializes empty library files for all categories:
|
|
133
|
+
- JLC-MCP-Resistors, JLC-MCP-Capacitors, JLC-MCP-Inductors, JLC-MCP-Diodes
|
|
134
|
+
- JLC-MCP-Transistors, JLC-MCP-ICs, JLC-MCP-Connectors, JLC-MCP-Misc
|
|
135
|
+
|
|
136
|
+
Also creates footprint directory (JLC-MCP.pretty) and 3D models directory (JLC-MCP.3dshapes).
|
|
137
|
+
|
|
138
|
+
When libraries exist, this tool:
|
|
139
|
+
1. Finds all JLC-MCP-*.kicad_sym files in the symbols directory
|
|
140
|
+
2. Extracts LCSC IDs from each symbol's properties
|
|
141
|
+
3. Fetches fresh data from EasyEDA for each component
|
|
142
|
+
4. Regenerates symbols with value normalization
|
|
143
|
+
5. Applies hybrid footprint logic (KiCad standard vs generated)
|
|
144
|
+
6. Rebuilds category-based libraries
|
|
145
|
+
|
|
146
|
+
Use dry_run=true to preview changes without writing files.`,
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: 'object',
|
|
149
|
+
properties: {
|
|
150
|
+
project_path: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: 'Optional: Project path. If omitted, uses global KiCad library.',
|
|
153
|
+
},
|
|
154
|
+
dry_run: {
|
|
155
|
+
type: 'boolean',
|
|
156
|
+
description: 'Preview changes without writing files (default: false)',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const UpdateLibraryParamsSchema = z.object({
|
|
163
|
+
project_path: z.string().min(1).optional(),
|
|
164
|
+
dry_run: z.boolean().default(false),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
interface UpdateResult {
|
|
168
|
+
lcscId: string;
|
|
169
|
+
category: LibraryCategory;
|
|
170
|
+
symbolName: string;
|
|
171
|
+
footprintType: 'reference' | 'generated';
|
|
172
|
+
footprintRef: string;
|
|
173
|
+
status: 'updated' | 'failed';
|
|
174
|
+
error?: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function handleUpdateLibrary(args: unknown) {
|
|
178
|
+
const params = UpdateLibraryParamsSchema.parse(args);
|
|
179
|
+
const paths = getLibraryPaths(params.project_path);
|
|
180
|
+
|
|
181
|
+
// Find all existing JLC libraries
|
|
182
|
+
const libraryFiles = findJlcLibraries(paths.symbolsDir);
|
|
183
|
+
|
|
184
|
+
if (libraryFiles.length === 0) {
|
|
185
|
+
// No existing libraries - initialize empty libraries for all categories
|
|
186
|
+
const allCategories = getAllCategories();
|
|
187
|
+
|
|
188
|
+
if (params.dry_run) {
|
|
189
|
+
return {
|
|
190
|
+
content: [{
|
|
191
|
+
type: 'text' as const,
|
|
192
|
+
text: JSON.stringify({
|
|
193
|
+
success: true,
|
|
194
|
+
action: 'would_initialize',
|
|
195
|
+
dry_run: true,
|
|
196
|
+
summary: {
|
|
197
|
+
categories_to_create: allCategories.length,
|
|
198
|
+
directories_to_create: [
|
|
199
|
+
paths.symbolsDir,
|
|
200
|
+
paths.footprintDir,
|
|
201
|
+
paths.models3dDir,
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
libraries_to_create: allCategories.map((cat) => ({
|
|
205
|
+
category: cat,
|
|
206
|
+
filename: getLibraryFilename(cat),
|
|
207
|
+
path: join(paths.symbolsDir, getLibraryFilename(cat)),
|
|
208
|
+
})),
|
|
209
|
+
}, null, 2),
|
|
210
|
+
}],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Create directories
|
|
215
|
+
await ensureDir(paths.symbolsDir);
|
|
216
|
+
await ensureDir(paths.footprintDir);
|
|
217
|
+
await ensureDir(paths.models3dDir);
|
|
218
|
+
|
|
219
|
+
// Create empty library files for all categories
|
|
220
|
+
const emptyContent = generateEmptyLibrary();
|
|
221
|
+
const createdLibraries: string[] = [];
|
|
222
|
+
|
|
223
|
+
for (const category of allCategories) {
|
|
224
|
+
const filename = getLibraryFilename(category);
|
|
225
|
+
const filepath = join(paths.symbolsDir, filename);
|
|
226
|
+
await writeText(filepath, emptyContent);
|
|
227
|
+
createdLibraries.push(filepath);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
content: [{
|
|
232
|
+
type: 'text' as const,
|
|
233
|
+
text: JSON.stringify({
|
|
234
|
+
success: true,
|
|
235
|
+
action: 'initialized',
|
|
236
|
+
dry_run: false,
|
|
237
|
+
summary: {
|
|
238
|
+
categories_initialized: allCategories.length,
|
|
239
|
+
directories_created: [
|
|
240
|
+
paths.symbolsDir,
|
|
241
|
+
paths.footprintDir,
|
|
242
|
+
paths.models3dDir,
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
libraries_created: createdLibraries,
|
|
246
|
+
next_steps: [
|
|
247
|
+
'Use library_fetch to add components to your libraries',
|
|
248
|
+
'Run library_update again after adding components to regenerate with latest data',
|
|
249
|
+
],
|
|
250
|
+
}, null, 2),
|
|
251
|
+
}],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Extract all LCSC IDs from existing libraries
|
|
256
|
+
const allLcscIds: Set<string> = new Set();
|
|
257
|
+
const libraryInfo: { file: string; ids: string[] }[] = [];
|
|
258
|
+
|
|
259
|
+
for (const file of libraryFiles) {
|
|
260
|
+
const content = await readFile(file, 'utf-8');
|
|
261
|
+
const ids = extractLcscIdsFromLibrary(content);
|
|
262
|
+
libraryInfo.push({ file, ids });
|
|
263
|
+
ids.forEach((id) => allLcscIds.add(id));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (allLcscIds.size === 0) {
|
|
267
|
+
return {
|
|
268
|
+
content: [{
|
|
269
|
+
type: 'text' as const,
|
|
270
|
+
text: JSON.stringify({
|
|
271
|
+
success: false,
|
|
272
|
+
error: 'No LCSC IDs found in existing libraries',
|
|
273
|
+
libraries_scanned: libraryFiles.length,
|
|
274
|
+
}),
|
|
275
|
+
}],
|
|
276
|
+
isError: true,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Process each component
|
|
281
|
+
const results: UpdateResult[] = [];
|
|
282
|
+
const categorySymbols: Map<LibraryCategory, string[]> = new Map();
|
|
283
|
+
|
|
284
|
+
for (const lcscId of allLcscIds) {
|
|
285
|
+
try {
|
|
286
|
+
// Fetch fresh component data
|
|
287
|
+
const component = await easyedaClient.getComponentData(lcscId);
|
|
288
|
+
|
|
289
|
+
if (!component) {
|
|
290
|
+
results.push({
|
|
291
|
+
lcscId,
|
|
292
|
+
category: 'Misc',
|
|
293
|
+
symbolName: '',
|
|
294
|
+
footprintType: 'generated',
|
|
295
|
+
footprintRef: '',
|
|
296
|
+
status: 'failed',
|
|
297
|
+
error: 'Component not found',
|
|
298
|
+
});
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Determine category
|
|
303
|
+
const category = getLibraryCategory(
|
|
304
|
+
component.info.prefix,
|
|
305
|
+
component.info.category,
|
|
306
|
+
component.info.description
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Generate symbol entry (without library wrapper)
|
|
310
|
+
const symbolEntry = symbolConverter.convertToSymbolEntry(component);
|
|
311
|
+
const symbolName = symbolConverter.getSymbolName(component);
|
|
312
|
+
|
|
313
|
+
// Collect symbols by category
|
|
314
|
+
if (!categorySymbols.has(category)) {
|
|
315
|
+
categorySymbols.set(category, []);
|
|
316
|
+
}
|
|
317
|
+
categorySymbols.get(category)!.push(symbolEntry);
|
|
318
|
+
|
|
319
|
+
// Handle footprint
|
|
320
|
+
const footprintResult = footprintConverter.getFootprint(component);
|
|
321
|
+
|
|
322
|
+
// Generate custom footprint if needed (not in dry run)
|
|
323
|
+
if (!params.dry_run && footprintResult.type === 'generated') {
|
|
324
|
+
await ensureDir(paths.footprintDir);
|
|
325
|
+
const footprintName = footprintResult.name + '_' + lcscId;
|
|
326
|
+
const footprintPath = join(paths.footprintDir, `${footprintName}.kicad_mod`);
|
|
327
|
+
await writeText(footprintPath, footprintResult.content!);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
results.push({
|
|
331
|
+
lcscId,
|
|
332
|
+
category,
|
|
333
|
+
symbolName,
|
|
334
|
+
footprintType: footprintResult.type,
|
|
335
|
+
footprintRef: footprintResult.type === 'reference'
|
|
336
|
+
? footprintResult.reference!
|
|
337
|
+
: `JLC:${footprintResult.name}_${lcscId}`,
|
|
338
|
+
status: 'updated',
|
|
339
|
+
});
|
|
340
|
+
} catch (error) {
|
|
341
|
+
results.push({
|
|
342
|
+
lcscId,
|
|
343
|
+
category: 'Misc',
|
|
344
|
+
symbolName: '',
|
|
345
|
+
footprintType: 'generated',
|
|
346
|
+
footprintRef: '',
|
|
347
|
+
status: 'failed',
|
|
348
|
+
error: error instanceof Error ? error.message : String(error),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Write category-based libraries (not in dry run)
|
|
354
|
+
if (!params.dry_run) {
|
|
355
|
+
await ensureDir(paths.symbolsDir);
|
|
356
|
+
|
|
357
|
+
for (const [category, entries] of categorySymbols) {
|
|
358
|
+
const filename = getLibraryFilename(category);
|
|
359
|
+
const filepath = join(paths.symbolsDir, filename);
|
|
360
|
+
|
|
361
|
+
// Create library file with all symbols
|
|
362
|
+
const header = `(kicad_symbol_lib
|
|
363
|
+
\t(version 20241209)
|
|
364
|
+
\t(generator "jlc-mcp")
|
|
365
|
+
\t(generator_version "9.0")
|
|
366
|
+
`;
|
|
367
|
+
const content = header + entries.join('') + ')\n';
|
|
368
|
+
await writeText(filepath, content);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Summarize results
|
|
373
|
+
const successful = results.filter((r) => r.status === 'updated');
|
|
374
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
375
|
+
const byCategory = new Map<LibraryCategory, number>();
|
|
376
|
+
|
|
377
|
+
for (const r of successful) {
|
|
378
|
+
byCategory.set(r.category, (byCategory.get(r.category) || 0) + 1);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const footprintStats = {
|
|
382
|
+
kicad_standard: successful.filter((r) => r.footprintType === 'reference').length,
|
|
383
|
+
custom_generated: successful.filter((r) => r.footprintType === 'generated').length,
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
content: [{
|
|
388
|
+
type: 'text' as const,
|
|
389
|
+
text: JSON.stringify({
|
|
390
|
+
success: true,
|
|
391
|
+
dry_run: params.dry_run,
|
|
392
|
+
summary: {
|
|
393
|
+
total_components: allLcscIds.size,
|
|
394
|
+
updated: successful.length,
|
|
395
|
+
failed: failed.length,
|
|
396
|
+
libraries_generated: categorySymbols.size,
|
|
397
|
+
},
|
|
398
|
+
by_category: Object.fromEntries(byCategory),
|
|
399
|
+
footprint_stats: footprintStats,
|
|
400
|
+
libraries_written: params.dry_run
|
|
401
|
+
? []
|
|
402
|
+
: Array.from(categorySymbols.keys()).map((cat) =>
|
|
403
|
+
join(paths.symbolsDir, getLibraryFilename(cat))
|
|
404
|
+
),
|
|
405
|
+
failed_components: failed.map((f) => ({
|
|
406
|
+
lcsc_id: f.lcscId,
|
|
407
|
+
error: f.error,
|
|
408
|
+
})),
|
|
409
|
+
}, null, 2),
|
|
410
|
+
}],
|
|
411
|
+
};
|
|
412
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library fetching and conversion tools for MCP
|
|
3
|
+
* Uses jlc-core for all business logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import {
|
|
9
|
+
createLibraryService,
|
|
10
|
+
createComponentService,
|
|
11
|
+
easyedaClient,
|
|
12
|
+
symbolConverter,
|
|
13
|
+
footprintConverter,
|
|
14
|
+
} from '@jlcpcb/core';
|
|
15
|
+
|
|
16
|
+
const libraryService = createLibraryService();
|
|
17
|
+
const componentService = createComponentService();
|
|
18
|
+
|
|
19
|
+
export const getSymbolKicadTool: Tool = {
|
|
20
|
+
name: 'library_get_symbol',
|
|
21
|
+
description: 'Get a KiCad-compatible symbol definition by LCSC part number. Returns the symbol in .kicad_sym format. LCSC is JLC PCB\'s preferred supplier for assembly.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
lcsc_id: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'LCSC part number (e.g., C2040)',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ['lcsc_id'],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const getFootprintKicadTool: Tool = {
|
|
35
|
+
name: 'library_get_footprint',
|
|
36
|
+
description: 'Get a KiCad-compatible footprint definition by LCSC part number. Returns the footprint in .kicad_mod format. LCSC is JLC PCB\'s preferred supplier for assembly.',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
lcsc_id: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'LCSC part number (e.g., C2040)',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
required: ['lcsc_id'],
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const fetchLibraryTool: Tool = {
|
|
50
|
+
name: 'library_fetch',
|
|
51
|
+
description: `Fetch a component and add it to KiCad libraries.
|
|
52
|
+
|
|
53
|
+
Accepts:
|
|
54
|
+
- LCSC part numbers (e.g., C2040) → global JLC-MCP libraries
|
|
55
|
+
- EasyEDA UUIDs (e.g., 8007c710c0b9406db963b55df6990340) → project-local EasyEDA library (requires project_path)
|
|
56
|
+
|
|
57
|
+
LCSC components are routed to category-based global libraries:
|
|
58
|
+
- JLC-MCP-Resistors.kicad_sym, JLC-MCP-Capacitors.kicad_sym, JLC-MCP-ICs.kicad_sym, etc.
|
|
59
|
+
- Stored at ~/Documents/KiCad/{version}/3rdparty/jlc_mcp/
|
|
60
|
+
|
|
61
|
+
EasyEDA community components are stored project-locally:
|
|
62
|
+
- <project>/libraries/symbols/EasyEDA.kicad_sym
|
|
63
|
+
- <project>/libraries/footprints/EasyEDA.pretty/
|
|
64
|
+
|
|
65
|
+
Returns symbol_ref and footprint_ref for immediate use with add_schematic_component.`,
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
id: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'LCSC part number (e.g., C2040) or EasyEDA community UUID',
|
|
72
|
+
},
|
|
73
|
+
project_path: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'Project path (required for EasyEDA UUIDs, optional for LCSC IDs)',
|
|
76
|
+
},
|
|
77
|
+
include_3d: {
|
|
78
|
+
type: 'boolean',
|
|
79
|
+
description: 'Include 3D model if available (default: false for LCSC, true for EasyEDA)',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
required: ['id'],
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const get3DModelTool: Tool = {
|
|
87
|
+
name: 'library_get_3d_model',
|
|
88
|
+
description: 'Download a 3D model for a component. Requires the model UUID from component_get. Returns the model as base64-encoded STEP data.',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
uuid: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
description: '3D model UUID from component_get result',
|
|
95
|
+
},
|
|
96
|
+
format: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
enum: ['step', 'obj'],
|
|
99
|
+
description: 'Model format: "step" or "obj" (default: step)',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
required: ['uuid'],
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const LibraryParamsSchema = z.object({
|
|
107
|
+
lcsc_id: z.string().regex(/^C\d+$/, 'Invalid LCSC part number'),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const FetchLibraryParamsSchema = z.object({
|
|
111
|
+
id: z.string().min(1),
|
|
112
|
+
project_path: z.string().min(1).optional(),
|
|
113
|
+
include_3d: z.boolean().optional(),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export const Model3DParamsSchema = z.object({
|
|
117
|
+
uuid: z.string().min(1),
|
|
118
|
+
format: z.enum(['step', 'obj']).default('step'),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export async function handleGetSymbolKicad(args: unknown) {
|
|
122
|
+
const params = LibraryParamsSchema.parse(args);
|
|
123
|
+
|
|
124
|
+
const component = await easyedaClient.getComponentData(params.lcsc_id);
|
|
125
|
+
|
|
126
|
+
if (!component) {
|
|
127
|
+
return {
|
|
128
|
+
content: [{
|
|
129
|
+
type: 'text' as const,
|
|
130
|
+
text: `Component ${params.lcsc_id} not found`,
|
|
131
|
+
}],
|
|
132
|
+
isError: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const symbol = symbolConverter.convert(component);
|
|
137
|
+
return {
|
|
138
|
+
content: [{
|
|
139
|
+
type: 'text' as const,
|
|
140
|
+
text: symbol,
|
|
141
|
+
}],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function handleGetFootprintKicad(args: unknown) {
|
|
146
|
+
const params = LibraryParamsSchema.parse(args);
|
|
147
|
+
|
|
148
|
+
const component = await easyedaClient.getComponentData(params.lcsc_id);
|
|
149
|
+
|
|
150
|
+
if (!component) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{
|
|
153
|
+
type: 'text' as const,
|
|
154
|
+
text: `Component ${params.lcsc_id} not found`,
|
|
155
|
+
}],
|
|
156
|
+
isError: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const footprint = footprintConverter.convert(component);
|
|
161
|
+
return {
|
|
162
|
+
content: [{
|
|
163
|
+
type: 'text' as const,
|
|
164
|
+
text: footprint,
|
|
165
|
+
}],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if ID is an LCSC part number (C followed by digits)
|
|
171
|
+
*/
|
|
172
|
+
function isLcscId(id: string): boolean {
|
|
173
|
+
return /^C\d+$/.test(id);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function handleFetchLibrary(args: unknown) {
|
|
177
|
+
const params = FetchLibraryParamsSchema.parse(args);
|
|
178
|
+
const isCommunityComponent = !isLcscId(params.id);
|
|
179
|
+
|
|
180
|
+
// Community components require project_path
|
|
181
|
+
if (isCommunityComponent && !params.project_path) {
|
|
182
|
+
return {
|
|
183
|
+
content: [{
|
|
184
|
+
type: 'text' as const,
|
|
185
|
+
text: JSON.stringify({
|
|
186
|
+
success: false,
|
|
187
|
+
error: 'EasyEDA community components require project_path for local storage',
|
|
188
|
+
id: params.id,
|
|
189
|
+
hint: 'Provide project_path to store in <project>/libraries/EasyEDA.*',
|
|
190
|
+
}),
|
|
191
|
+
}],
|
|
192
|
+
isError: true,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const result = await libraryService.install(params.id, {
|
|
198
|
+
projectPath: params.project_path,
|
|
199
|
+
include3d: params.include_3d,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
content: [{
|
|
204
|
+
type: 'text' as const,
|
|
205
|
+
text: JSON.stringify({
|
|
206
|
+
success: true,
|
|
207
|
+
id: params.id,
|
|
208
|
+
source: result.source,
|
|
209
|
+
storage_mode: result.storageMode,
|
|
210
|
+
category: result.category,
|
|
211
|
+
symbol_name: result.symbolName,
|
|
212
|
+
symbol_ref: result.symbolRef,
|
|
213
|
+
footprint_ref: result.footprintRef,
|
|
214
|
+
footprint_type: result.footprintType,
|
|
215
|
+
datasheet: result.datasheet,
|
|
216
|
+
files: {
|
|
217
|
+
symbol_library: result.files.symbolLibrary,
|
|
218
|
+
footprint: result.files.footprint,
|
|
219
|
+
model_3d: result.files.model3d,
|
|
220
|
+
},
|
|
221
|
+
symbol_action: result.symbolAction,
|
|
222
|
+
validation_data: result.validationData,
|
|
223
|
+
}, null, 2),
|
|
224
|
+
}],
|
|
225
|
+
};
|
|
226
|
+
} catch (error) {
|
|
227
|
+
return {
|
|
228
|
+
content: [{
|
|
229
|
+
type: 'text' as const,
|
|
230
|
+
text: JSON.stringify({
|
|
231
|
+
success: false,
|
|
232
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
233
|
+
id: params.id,
|
|
234
|
+
source: isCommunityComponent ? 'easyeda_community' : 'lcsc',
|
|
235
|
+
}),
|
|
236
|
+
}],
|
|
237
|
+
isError: true,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function handleGet3DModel(args: unknown) {
|
|
243
|
+
const params = Model3DParamsSchema.parse(args);
|
|
244
|
+
|
|
245
|
+
const model = await easyedaClient.get3DModel(params.uuid, params.format);
|
|
246
|
+
|
|
247
|
+
if (!model) {
|
|
248
|
+
return {
|
|
249
|
+
content: [{
|
|
250
|
+
type: 'text' as const,
|
|
251
|
+
text: `3D model ${params.uuid} not found`,
|
|
252
|
+
}],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
content: [{
|
|
259
|
+
type: 'text' as const,
|
|
260
|
+
text: `3D model downloaded (${model.length} bytes, ${params.format.toUpperCase()} format)\n\nBase64 data:\n${model.toString('base64').slice(0, 500)}...`,
|
|
261
|
+
}],
|
|
262
|
+
};
|
|
263
|
+
}
|