@jlcpcb/mcp 0.1.1 → 0.3.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 +72 -0
- package/README.md +19 -29
- package/dist/index.js +1271 -1199
- package/package.json +2 -3
- package/src/index.ts +6 -3
- package/src/schemas.ts +14 -0
- package/src/services.ts +34 -0
- package/src/tools/batch.ts +123 -0
- package/src/tools/index.ts +36 -63
- package/src/tools/library-fix.ts +1 -18
- package/src/tools/library-update.ts +8 -27
- package/src/tools/library.ts +108 -149
- package/src/tools/search.ts +68 -10
- package/dist/assets/search.html +0 -528
- package/scripts/build-search-page.ts +0 -68
- package/src/assets/search-built.html +0 -528
- package/src/assets/search.html +0 -458
- package/src/browser/index.ts +0 -381
- package/src/browser/kicad-renderer.ts +0 -646
- package/src/browser/sexpr-parser.ts +0 -321
- package/src/http/routes.ts +0 -253
- package/src/http/server.ts +0 -74
- package/src/tools/details.ts +0 -66
- package/src/tools/easyeda.ts +0 -582
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jlcpcb/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "MCP server for JLC/EasyEDA component sourcing, library fetching, and conversion to KiCad format",
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
"main": "./dist/index.js",
|
|
19
19
|
"types": "./dist/index.d.ts",
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target node
|
|
22
|
-
"build:search": "bun run scripts/build-search-page.ts",
|
|
21
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target node",
|
|
23
22
|
"start": "bun run ./src/index.ts",
|
|
24
23
|
"dev": "bun --watch ./src/index.ts",
|
|
25
24
|
"typecheck": "tsc --noEmit",
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* EasyEDA (owned by JLC/LCSC) provides the symbol and footprint data.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { createRequire } from 'module';
|
|
14
15
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
15
16
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
16
17
|
import {
|
|
@@ -19,8 +20,10 @@ import {
|
|
|
19
20
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
20
21
|
|
|
21
22
|
import { tools, toolHandlers } from './tools/index.js';
|
|
22
|
-
import { createLogger, ensureGlobalLibraryTables } from '@jlcpcb/core';
|
|
23
|
-
|
|
23
|
+
import { createLogger, ensureGlobalLibraryTables, startHttpServer } from '@jlcpcb/core';
|
|
24
|
+
|
|
25
|
+
const require = createRequire(import.meta.url);
|
|
26
|
+
const { version } = require('../package.json');
|
|
24
27
|
|
|
25
28
|
const logger = createLogger('jlc-mcp');
|
|
26
29
|
|
|
@@ -28,7 +31,7 @@ const logger = createLogger('jlc-mcp');
|
|
|
28
31
|
const server = new Server(
|
|
29
32
|
{
|
|
30
33
|
name: 'jlc-mcp',
|
|
31
|
-
version
|
|
34
|
+
version,
|
|
32
35
|
},
|
|
33
36
|
{
|
|
34
37
|
capabilities: {
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared validation schemas for MCP tools
|
|
3
|
+
* Re-exports from @jlcpcb/core for consistency across packages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
LCSCPartNumberSchema,
|
|
8
|
+
LCSCPartNumberSchema as LcscIdSchema, // Alias for backwards compatibility
|
|
9
|
+
EasyEDAUuidSchema,
|
|
10
|
+
ComponentIdSchema,
|
|
11
|
+
SafePathSchema,
|
|
12
|
+
isLcscId,
|
|
13
|
+
isEasyEDAUuid,
|
|
14
|
+
} from '@jlcpcb/core';
|
package/src/services.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared service instances for MCP tools
|
|
3
|
+
* Provides singleton access to core services to avoid multiple instantiations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createComponentService,
|
|
8
|
+
createLibraryService,
|
|
9
|
+
type ComponentService,
|
|
10
|
+
type LibraryService,
|
|
11
|
+
} from '@jlcpcb/core';
|
|
12
|
+
|
|
13
|
+
let componentService: ComponentService | null = null;
|
|
14
|
+
let libraryService: LibraryService | null = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the shared ComponentService instance
|
|
18
|
+
*/
|
|
19
|
+
export function getComponentService(): ComponentService {
|
|
20
|
+
if (!componentService) {
|
|
21
|
+
componentService = createComponentService();
|
|
22
|
+
}
|
|
23
|
+
return componentService;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the shared LibraryService instance
|
|
28
|
+
*/
|
|
29
|
+
export function getLibraryService(): LibraryService {
|
|
30
|
+
if (!libraryService) {
|
|
31
|
+
libraryService = createLibraryService();
|
|
32
|
+
}
|
|
33
|
+
return libraryService;
|
|
34
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch operations for MCP
|
|
3
|
+
* Install multiple components in a single call
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { getLibraryService } from '../services.js';
|
|
9
|
+
import { LcscIdSchema } from '../schemas.js';
|
|
10
|
+
|
|
11
|
+
export const libraryBatchInstallTool: Tool = {
|
|
12
|
+
name: 'library_batch_install',
|
|
13
|
+
description: `Install multiple components to KiCad libraries in a single call.
|
|
14
|
+
|
|
15
|
+
Accepts up to 10 LCSC part numbers. Components are installed in parallel.
|
|
16
|
+
Returns a summary of installed, skipped (already installed), and failed components.
|
|
17
|
+
|
|
18
|
+
Use this when you need to install a bill of materials or multiple components at once.`,
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
ids: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
items: { type: 'string' },
|
|
25
|
+
maxItems: 10,
|
|
26
|
+
description: 'Array of LCSC part numbers (e.g., ["C2040", "C5446", "C14663"])',
|
|
27
|
+
},
|
|
28
|
+
force: {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
description: 'Reinstall even if already exists (default: false)',
|
|
31
|
+
},
|
|
32
|
+
include_3d: {
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
description: 'Include 3D models if available (default: false)',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ['ids'],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const BatchInstallParamsSchema = z.object({
|
|
42
|
+
ids: z.array(LcscIdSchema).min(1).max(10),
|
|
43
|
+
force: z.boolean().optional(),
|
|
44
|
+
include_3d: z.boolean().optional(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
interface BatchResult {
|
|
48
|
+
id: string;
|
|
49
|
+
status: 'installed' | 'skipped' | 'failed';
|
|
50
|
+
symbol_ref?: string;
|
|
51
|
+
footprint_ref?: string;
|
|
52
|
+
reason?: string;
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function handleLibraryBatchInstall(args: unknown) {
|
|
57
|
+
const params = BatchInstallParamsSchema.parse(args);
|
|
58
|
+
|
|
59
|
+
const results: BatchResult[] = [];
|
|
60
|
+
let installed = 0;
|
|
61
|
+
let skipped = 0;
|
|
62
|
+
let failed = 0;
|
|
63
|
+
|
|
64
|
+
// Install components in parallel (max 10)
|
|
65
|
+
const installPromises = params.ids.map(async (id): Promise<BatchResult> => {
|
|
66
|
+
try {
|
|
67
|
+
const result = await getLibraryService().install(id, {
|
|
68
|
+
include3d: params.include_3d,
|
|
69
|
+
force: params.force,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Check if it was actually installed or already existed
|
|
73
|
+
if (result.symbolAction === 'exists') {
|
|
74
|
+
return {
|
|
75
|
+
id,
|
|
76
|
+
status: 'skipped',
|
|
77
|
+
symbol_ref: result.symbolRef,
|
|
78
|
+
footprint_ref: result.footprintRef,
|
|
79
|
+
reason: 'already installed',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
id,
|
|
85
|
+
status: 'installed',
|
|
86
|
+
symbol_ref: result.symbolRef,
|
|
87
|
+
footprint_ref: result.footprintRef,
|
|
88
|
+
};
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
id,
|
|
92
|
+
status: 'failed',
|
|
93
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const installResults = await Promise.all(installPromises);
|
|
99
|
+
|
|
100
|
+
// Aggregate results
|
|
101
|
+
for (const result of installResults) {
|
|
102
|
+
results.push(result);
|
|
103
|
+
if (result.status === 'installed') installed++;
|
|
104
|
+
else if (result.status === 'skipped') skipped++;
|
|
105
|
+
else failed++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
content: [{
|
|
110
|
+
type: 'text' as const,
|
|
111
|
+
text: JSON.stringify({
|
|
112
|
+
success: failed < params.ids.length, // At least one succeeded
|
|
113
|
+
summary: {
|
|
114
|
+
total: params.ids.length,
|
|
115
|
+
installed,
|
|
116
|
+
skipped,
|
|
117
|
+
failed,
|
|
118
|
+
},
|
|
119
|
+
results,
|
|
120
|
+
}),
|
|
121
|
+
}],
|
|
122
|
+
};
|
|
123
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP tool definitions and handlers for
|
|
2
|
+
* MCP tool definitions and handlers for JLC-MCP server
|
|
3
|
+
* Streamlined version with 6 consolidated tools
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
6
7
|
|
|
7
|
-
// Import
|
|
8
|
-
import {
|
|
9
|
-
import { getComponentTool, handleGetComponent } from './details.js';
|
|
8
|
+
// Import tools
|
|
9
|
+
import { componentSearchTool, handleComponentSearch } from './search.js';
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
handleGetSymbolKicad,
|
|
16
|
-
handleGetFootprintKicad,
|
|
17
|
-
handleFetchLibrary,
|
|
18
|
-
handleGet3DModel,
|
|
11
|
+
libraryInstallTool,
|
|
12
|
+
libraryGetComponentTool,
|
|
13
|
+
handleLibraryInstall,
|
|
14
|
+
handleLibraryGetComponent,
|
|
19
15
|
} from './library.js';
|
|
16
|
+
import {
|
|
17
|
+
libraryBatchInstallTool,
|
|
18
|
+
handleLibraryBatchInstall,
|
|
19
|
+
} from './batch.js';
|
|
20
20
|
import {
|
|
21
21
|
updateLibraryTool,
|
|
22
22
|
handleUpdateLibrary,
|
|
@@ -26,62 +26,41 @@ import {
|
|
|
26
26
|
handleFixLibrary,
|
|
27
27
|
} from './library-fix.js';
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
import {
|
|
31
|
-
easyedaSearchTool,
|
|
32
|
-
easyedaGet3DModelTool,
|
|
33
|
-
handleEasyedaSearch,
|
|
34
|
-
handleEasyedaGet3DModel,
|
|
35
|
-
} from './easyeda.js';
|
|
36
|
-
|
|
37
|
-
// Export all tool definitions
|
|
29
|
+
// Export all tool definitions (6 tools total)
|
|
38
30
|
export const tools: Tool[] = [
|
|
39
|
-
// LCSC
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
updateLibraryTool,
|
|
46
|
-
fixLibraryTool,
|
|
47
|
-
get3DModelTool,
|
|
48
|
-
// EasyEDA community library
|
|
49
|
-
easyedaSearchTool,
|
|
50
|
-
easyedaGet3DModelTool,
|
|
31
|
+
componentSearchTool, // Search LCSC or EasyEDA community
|
|
32
|
+
libraryInstallTool, // Install single component
|
|
33
|
+
libraryBatchInstallTool, // Install up to 10 components
|
|
34
|
+
libraryGetComponentTool, // Get installed component metadata
|
|
35
|
+
updateLibraryTool, // Regenerate all components
|
|
36
|
+
fixLibraryTool, // Apply pin corrections
|
|
51
37
|
];
|
|
52
38
|
|
|
53
|
-
// Tool handler map
|
|
39
|
+
// Tool handler map - derived from tool definitions to prevent name mismatches
|
|
54
40
|
export const toolHandlers: Record<string, (args: unknown) => Promise<{
|
|
55
41
|
content: Array<{ type: 'text'; text: string }>;
|
|
56
42
|
isError?: boolean;
|
|
57
43
|
}>> = {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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,
|
|
44
|
+
[componentSearchTool.name]: handleComponentSearch,
|
|
45
|
+
[libraryInstallTool.name]: handleLibraryInstall,
|
|
46
|
+
[libraryBatchInstallTool.name]: handleLibraryBatchInstall,
|
|
47
|
+
[libraryGetComponentTool.name]: handleLibraryGetComponent,
|
|
48
|
+
[updateLibraryTool.name]: handleUpdateLibrary,
|
|
49
|
+
[fixLibraryTool.name]: handleFixLibrary,
|
|
70
50
|
};
|
|
71
51
|
|
|
72
|
-
// Re-export
|
|
73
|
-
export {
|
|
74
|
-
export { getComponentTool, handleGetComponent } from './details.js';
|
|
52
|
+
// Re-export for direct imports
|
|
53
|
+
export { componentSearchTool, handleComponentSearch } from './search.js';
|
|
75
54
|
export {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
handleGetSymbolKicad,
|
|
81
|
-
handleGetFootprintKicad,
|
|
82
|
-
handleFetchLibrary,
|
|
83
|
-
handleGet3DModel,
|
|
55
|
+
libraryInstallTool,
|
|
56
|
+
libraryGetComponentTool,
|
|
57
|
+
handleLibraryInstall,
|
|
58
|
+
handleLibraryGetComponent,
|
|
84
59
|
} from './library.js';
|
|
60
|
+
export {
|
|
61
|
+
libraryBatchInstallTool,
|
|
62
|
+
handleLibraryBatchInstall,
|
|
63
|
+
} from './batch.js';
|
|
85
64
|
export {
|
|
86
65
|
updateLibraryTool,
|
|
87
66
|
handleUpdateLibrary,
|
|
@@ -90,9 +69,3 @@ export {
|
|
|
90
69
|
fixLibraryTool,
|
|
91
70
|
handleFixLibrary,
|
|
92
71
|
} from './library-fix.js';
|
|
93
|
-
export {
|
|
94
|
-
easyedaSearchTool,
|
|
95
|
-
easyedaGet3DModelTool,
|
|
96
|
-
handleEasyedaSearch,
|
|
97
|
-
handleEasyedaGet3DModel,
|
|
98
|
-
} from './easyeda.js';
|
package/src/tools/library-fix.ts
CHANGED
|
@@ -19,28 +19,11 @@ import {
|
|
|
19
19
|
getFootprintReference as getCategoryFootprintRef,
|
|
20
20
|
ensureDir,
|
|
21
21
|
writeText,
|
|
22
|
+
detectKicadVersion,
|
|
22
23
|
type EasyEDAPin,
|
|
23
24
|
} from '@jlcpcb/core';
|
|
24
25
|
import { join } from 'path';
|
|
25
26
|
|
|
26
|
-
// KiCad versions to check (newest first)
|
|
27
|
-
const KICAD_VERSIONS = ['9.0', '8.0'];
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Detect KiCad major version from existing user directories
|
|
31
|
-
*/
|
|
32
|
-
function detectKicadVersion(): string {
|
|
33
|
-
const home = homedir();
|
|
34
|
-
const baseDir = join(home, 'Documents', 'KiCad');
|
|
35
|
-
|
|
36
|
-
for (const version of KICAD_VERSIONS) {
|
|
37
|
-
if (existsSync(join(baseDir, version))) {
|
|
38
|
-
return version;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return '9.0'; // Default
|
|
42
|
-
}
|
|
43
|
-
|
|
44
27
|
/**
|
|
45
28
|
* Get library paths for fix operation
|
|
46
29
|
*/
|
|
@@ -21,30 +21,13 @@ import {
|
|
|
21
21
|
type LibraryCategory,
|
|
22
22
|
ensureDir,
|
|
23
23
|
writeText,
|
|
24
|
+
detectKicadVersion,
|
|
24
25
|
} from '@jlcpcb/core';
|
|
25
26
|
import { join } from 'path';
|
|
26
27
|
|
|
27
|
-
// KiCad versions to check (newest first)
|
|
28
|
-
const KICAD_VERSIONS = ['9.0', '8.0'];
|
|
29
|
-
|
|
30
28
|
// 3rd party library namespace
|
|
31
29
|
const LIBRARY_NAMESPACE = 'jlc_mcp';
|
|
32
30
|
|
|
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
31
|
/**
|
|
49
32
|
* Get library paths for update operation
|
|
50
33
|
* Platform-specific paths matching where ${KICAD9_3RD_PARTY} resolves:
|
|
@@ -383,6 +366,8 @@ export async function handleUpdateLibrary(args: unknown) {
|
|
|
383
366
|
custom_generated: successful.filter((r) => r.footprintType === 'generated').length,
|
|
384
367
|
};
|
|
385
368
|
|
|
369
|
+
// Compact response - summary only, no detailed failures
|
|
370
|
+
// (failures can be retrieved separately if needed)
|
|
386
371
|
return {
|
|
387
372
|
content: [{
|
|
388
373
|
type: 'text' as const,
|
|
@@ -390,23 +375,19 @@ export async function handleUpdateLibrary(args: unknown) {
|
|
|
390
375
|
success: true,
|
|
391
376
|
dry_run: params.dry_run,
|
|
392
377
|
summary: {
|
|
393
|
-
|
|
378
|
+
total: allLcscIds.size,
|
|
394
379
|
updated: successful.length,
|
|
395
380
|
failed: failed.length,
|
|
396
|
-
libraries_generated: categorySymbols.size,
|
|
397
381
|
},
|
|
398
382
|
by_category: Object.fromEntries(byCategory),
|
|
399
383
|
footprint_stats: footprintStats,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
: Array.from(categorySymbols.keys()).map((cat) =>
|
|
403
|
-
join(paths.symbolsDir, getLibraryFilename(cat))
|
|
404
|
-
),
|
|
405
|
-
failed_components: failed.map((f) => ({
|
|
384
|
+
// Only include first 5 failures to avoid massive responses
|
|
385
|
+
failed_sample: failed.slice(0, 5).map((f) => ({
|
|
406
386
|
lcsc_id: f.lcscId,
|
|
407
387
|
error: f.error,
|
|
408
388
|
})),
|
|
409
|
-
|
|
389
|
+
has_more_failures: failed.length > 5,
|
|
390
|
+
}),
|
|
410
391
|
}],
|
|
411
392
|
};
|
|
412
393
|
}
|