@jlcpcb/mcp 0.2.0 → 0.3.1

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.
@@ -1,54 +1,18 @@
1
1
  /**
2
- * Library fetching and conversion tools for MCP
3
- * Uses jlc-core for all business logic
2
+ * Library management tools for MCP
3
+ * Streamlined version using LibraryService from core
4
4
  */
5
5
 
6
6
  import { z } from 'zod';
7
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';
8
+ import { getLibraryService } from '../services.js';
9
+ import { LcscIdSchema, SafePathSchema, isLcscId } from '../schemas.js';
15
10
 
16
- const libraryService = createLibraryService();
17
- const componentService = createComponentService();
11
+ // Tool Definitions
18
12
 
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.
13
+ export const libraryInstallTool: Tool = {
14
+ name: 'library_install',
15
+ description: `Install a component to KiCad libraries.
52
16
 
53
17
  Accepts:
54
18
  - LCSC part numbers (e.g., C2040) → global JLC-MCP libraries
@@ -58,11 +22,7 @@ LCSC components are routed to category-based global libraries:
58
22
  - JLC-MCP-Resistors.kicad_sym, JLC-MCP-Capacitors.kicad_sym, JLC-MCP-ICs.kicad_sym, etc.
59
23
  - Stored at ~/Documents/KiCad/{version}/3rdparty/jlc_mcp/
60
24
 
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.`,
25
+ Returns symbol_ref and footprint_ref for use with schematic placement.`,
66
26
  inputSchema: {
67
27
  type: 'object',
68
28
  properties: {
@@ -76,105 +36,54 @@ Returns symbol_ref and footprint_ref for immediate use with add_schematic_compon
76
36
  },
77
37
  include_3d: {
78
38
  type: 'boolean',
79
- description: 'Include 3D model if available (default: false for LCSC, true for EasyEDA)',
39
+ description: 'Include 3D model if available (default: false)',
40
+ },
41
+ force: {
42
+ type: 'boolean',
43
+ description: 'Reinstall even if already exists (default: false)',
80
44
  },
81
45
  },
82
46
  required: ['id'],
83
47
  },
84
48
  };
85
49
 
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.',
50
+ export const libraryGetComponentTool: Tool = {
51
+ name: 'library_get_component',
52
+ description: `Get metadata for an installed component's symbol and footprint.
53
+
54
+ Returns symbol reference, footprint reference, file paths, and pin/pad counts.
55
+ Does NOT return full file contents to minimize token usage.
56
+
57
+ Use this to verify installation or get references for schematic placement.`,
89
58
  inputSchema: {
90
59
  type: 'object',
91
60
  properties: {
92
- uuid: {
93
- type: 'string',
94
- description: '3D model UUID from component_get result',
95
- },
96
- format: {
61
+ id: {
97
62
  type: 'string',
98
- enum: ['step', 'obj'],
99
- description: 'Model format: "step" or "obj" (default: step)',
63
+ description: 'LCSC part number (e.g., C2040)',
100
64
  },
101
65
  },
102
- required: ['uuid'],
66
+ required: ['id'],
103
67
  },
104
68
  };
105
69
 
106
- export const LibraryParamsSchema = z.object({
107
- lcsc_id: z.string().regex(/^C\d+$/, 'Invalid LCSC part number'),
108
- });
70
+ // Zod Schemas
109
71
 
110
- export const FetchLibraryParamsSchema = z.object({
72
+ export const LibraryInstallParamsSchema = z.object({
111
73
  id: z.string().min(1),
112
- project_path: z.string().min(1).optional(),
74
+ project_path: SafePathSchema.optional(),
113
75
  include_3d: z.boolean().optional(),
76
+ force: z.boolean().optional(),
114
77
  });
115
78
 
116
- export const Model3DParamsSchema = z.object({
117
- uuid: z.string().min(1),
118
- format: z.enum(['step', 'obj']).default('step'),
79
+ export const LibraryGetComponentParamsSchema = z.object({
80
+ id: LcscIdSchema,
119
81
  });
120
82
 
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
- }
83
+ // Handlers
135
84
 
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);
85
+ export async function handleLibraryInstall(args: unknown) {
86
+ const params = LibraryInstallParamsSchema.parse(args);
178
87
  const isCommunityComponent = !isLcscId(params.id);
179
88
 
180
89
  // Community components require project_path
@@ -194,33 +103,36 @@ export async function handleFetchLibrary(args: unknown) {
194
103
  }
195
104
 
196
105
  try {
197
- const result = await libraryService.install(params.id, {
106
+ const result = await getLibraryService().install(params.id, {
198
107
  projectPath: params.project_path,
199
108
  include3d: params.include_3d,
109
+ force: params.force,
200
110
  });
201
111
 
112
+ // Compact response - essential data only
202
113
  return {
203
114
  content: [{
204
115
  type: 'text' as const,
205
116
  text: JSON.stringify({
206
117
  success: true,
207
118
  id: params.id,
208
- source: result.source,
209
- storage_mode: result.storageMode,
210
- category: result.category,
211
- symbol_name: result.symbolName,
119
+ installed: true,
212
120
  symbol_ref: result.symbolRef,
213
121
  footprint_ref: result.footprintRef,
214
- footprint_type: result.footprintType,
215
- datasheet: result.datasheet,
122
+ category: result.category,
216
123
  files: {
217
124
  symbol_library: result.files.symbolLibrary,
218
125
  footprint: result.files.footprint,
219
126
  model_3d: result.files.model3d,
220
127
  },
221
- symbol_action: result.symbolAction,
222
- validation_data: result.validationData,
223
- }, null, 2),
128
+ validation: result.validationData ? {
129
+ pin_pad_match: result.validationData.checks.pin_pad_count_match,
130
+ pin_count: result.validationData.symbol.pin_count,
131
+ pad_count: result.validationData.footprint.pad_count,
132
+ has_power_pins: result.validationData.checks.has_power_pins,
133
+ has_ground_pins: result.validationData.checks.has_ground_pins,
134
+ } : undefined,
135
+ }),
224
136
  }],
225
137
  };
226
138
  } catch (error) {
@@ -231,7 +143,6 @@ export async function handleFetchLibrary(args: unknown) {
231
143
  success: false,
232
144
  error: error instanceof Error ? error.message : 'Unknown error',
233
145
  id: params.id,
234
- source: isCommunityComponent ? 'easyeda_community' : 'lcsc',
235
146
  }),
236
147
  }],
237
148
  isError: true,
@@ -239,25 +150,73 @@ export async function handleFetchLibrary(args: unknown) {
239
150
  }
240
151
  }
241
152
 
242
- export async function handleGet3DModel(args: unknown) {
243
- const params = Model3DParamsSchema.parse(args);
244
-
245
- const model = await easyedaClient.get3DModel(params.uuid, params.format);
153
+ export async function handleLibraryGetComponent(args: unknown) {
154
+ const params = LibraryGetComponentParamsSchema.parse(args);
246
155
 
247
- if (!model) {
156
+ try {
157
+ // Check if component is installed
158
+ const status = await getLibraryService().getStatus();
159
+ if (!status.installed) {
160
+ return {
161
+ content: [{
162
+ type: 'text' as const,
163
+ text: JSON.stringify({
164
+ success: false,
165
+ error: 'JLC-MCP libraries not installed. Run library_install first.',
166
+ id: params.id,
167
+ }),
168
+ }],
169
+ isError: true,
170
+ };
171
+ }
172
+
173
+ // Get list of installed components
174
+ const installed = await getLibraryService().listInstalled();
175
+ const component = installed.find(c => c.lcscId === params.id);
176
+
177
+ if (!component) {
178
+ return {
179
+ content: [{
180
+ type: 'text' as const,
181
+ text: JSON.stringify({
182
+ success: false,
183
+ error: `Component ${params.id} is not installed`,
184
+ id: params.id,
185
+ hint: 'Use library_install to add this component first',
186
+ }),
187
+ }],
188
+ isError: true,
189
+ };
190
+ }
191
+
192
+ // Return metadata only (no file contents)
248
193
  return {
249
194
  content: [{
250
195
  type: 'text' as const,
251
- text: `3D model ${params.uuid} not found`,
196
+ text: JSON.stringify({
197
+ success: true,
198
+ id: params.id,
199
+ installed: true,
200
+ symbol_ref: component.symbolRef,
201
+ footprint_ref: component.footprintRef,
202
+ category: component.category,
203
+ symbol_library: component.library,
204
+ name: component.name,
205
+ has_3d_model: component.has3dModel,
206
+ }),
207
+ }],
208
+ };
209
+ } catch (error) {
210
+ return {
211
+ content: [{
212
+ type: 'text' as const,
213
+ text: JSON.stringify({
214
+ success: false,
215
+ error: error instanceof Error ? error.message : 'Unknown error',
216
+ id: params.id,
217
+ }),
252
218
  }],
253
219
  isError: true,
254
220
  };
255
221
  }
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
222
  }
@@ -1,20 +1,37 @@
1
1
  /**
2
- * Component search tools for MCP
2
+ * Unified component search tool for MCP
3
+ * Searches both LCSC (via JLCPCB API) and EasyEDA community library
3
4
  */
4
5
 
5
6
  import { z } from 'zod';
6
7
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
7
- import { jlcClient } from '@jlcpcb/core';
8
+ import type { ComponentSearchResult } from '@jlcpcb/core';
9
+ import { getComponentService } from '../services.js';
8
10
 
9
- export const searchComponentsTool: Tool = {
11
+ export const componentSearchTool: Tool = {
10
12
  name: 'component_search',
11
- description: 'Search the JLC/JLCPCB component database by keyword. Returns components with LCSC part numbers (JLC\'s preferred supplier for assembly). Includes prices, stock levels, and whether parts are in the Basic Parts Library (lower assembly cost).',
13
+ description: `Search for electronic components by keyword.
14
+
15
+ Sources:
16
+ - "lcsc" (default): Search JLCPCB/LCSC official parts library. Returns components with LCSC IDs available for JLC assembly.
17
+ - "community": Search EasyEDA community library for user-contributed parts (Arduino modules, XIAO, custom breakouts).
18
+
19
+ Returns full component details for selection including:
20
+ - LCSC ID, manufacturer part number, manufacturer name
21
+ - Description, package, datasheet URL
22
+ - Price, stock, library type (basic/extended)
23
+ - Key attributes (resistance, voltage, etc.)`,
12
24
  inputSchema: {
13
25
  type: 'object',
14
26
  properties: {
15
27
  query: {
16
28
  type: 'string',
17
- description: 'Search query (e.g., "ESP32", "STM32F103", "0805 100nF")',
29
+ description: 'Search query (e.g., "ESP32", "STM32F103", "0805 100nF", "XIAO RP2040")',
30
+ },
31
+ source: {
32
+ type: 'string',
33
+ enum: ['lcsc', 'community'],
34
+ description: 'Search source: "lcsc" for official parts (default), "community" for EasyEDA user-contributed',
18
35
  },
19
36
  limit: {
20
37
  type: 'number',
@@ -22,11 +39,11 @@ export const searchComponentsTool: Tool = {
22
39
  },
23
40
  in_stock: {
24
41
  type: 'boolean',
25
- description: 'Only show in-stock items (default: false)',
42
+ description: 'Only show in-stock items (default: false, LCSC only)',
26
43
  },
27
44
  basic_only: {
28
45
  type: 'boolean',
29
- description: 'Only show JLCPCB Basic Parts Library components (lower assembly cost, default: false)',
46
+ description: 'Only show JLCPCB Basic Parts Library components (lower assembly cost, LCSC only)',
30
47
  },
31
48
  },
32
49
  required: ['query'],
@@ -35,24 +52,65 @@ export const searchComponentsTool: Tool = {
35
52
 
36
53
  export const SearchParamsSchema = z.object({
37
54
  query: z.string().min(1),
55
+ source: z.enum(['lcsc', 'community']).default('lcsc'),
38
56
  limit: z.number().min(1).max(50).default(10),
39
57
  in_stock: z.boolean().optional(),
40
58
  basic_only: z.boolean().optional(),
41
59
  });
42
60
 
43
- export async function handleSearchComponents(args: unknown) {
61
+ export async function handleComponentSearch(args: unknown) {
44
62
  const params = SearchParamsSchema.parse(args);
45
63
 
46
- const results = await jlcClient.search(params.query, {
64
+ const results = await getComponentService().search(params.query, {
65
+ source: params.source,
47
66
  limit: params.limit,
48
67
  inStock: params.in_stock,
49
68
  basicOnly: params.basic_only,
50
69
  });
51
70
 
71
+ if (results.length === 0) {
72
+ return {
73
+ content: [{
74
+ type: 'text' as const,
75
+ text: JSON.stringify({
76
+ success: true,
77
+ query: params.query,
78
+ source: params.source,
79
+ count: 0,
80
+ results: [],
81
+ hint: params.source === 'lcsc'
82
+ ? 'Try searching with source: "community" for user-contributed parts'
83
+ : 'Try searching with source: "lcsc" for official JLCPCB parts',
84
+ }),
85
+ }],
86
+ };
87
+ }
88
+
89
+ // Return compact results with all selection-relevant fields
90
+ const compactResults = results.map((r: ComponentSearchResult) => ({
91
+ lcsc_id: r.lcscId,
92
+ name: r.name, // Manufacturer part number
93
+ manufacturer: r.manufacturer,
94
+ description: r.description,
95
+ package: r.package,
96
+ datasheet: r.datasheetPdf,
97
+ stock: r.stock,
98
+ price: r.price,
99
+ library_type: r.libraryType,
100
+ category: r.category,
101
+ attributes: r.attributes,
102
+ }));
103
+
52
104
  return {
53
105
  content: [{
54
106
  type: 'text' as const,
55
- text: JSON.stringify(results, null, 2),
107
+ text: JSON.stringify({
108
+ success: true,
109
+ query: params.query,
110
+ source: params.source,
111
+ count: results.length,
112
+ results: compactResults,
113
+ }),
56
114
  }],
57
115
  };
58
116
  }
@@ -1,66 +0,0 @@
1
- /**
2
- * Component details tools for MCP
3
- */
4
-
5
- import { z } from 'zod';
6
- import type { Tool } from '@modelcontextprotocol/sdk/types.js';
7
- import { easyedaClient } from '@jlcpcb/core';
8
-
9
- export const getComponentTool: Tool = {
10
- name: 'component_get',
11
- description: 'Get detailed component information by LCSC part number (e.g., C2040). LCSC is JLC PCB\'s preferred supplier - components with LCSC IDs are available for JLC assembly. Returns symbol pins, footprint pads, manufacturer info, datasheet URL, and 3D model reference.',
12
- inputSchema: {
13
- type: 'object',
14
- properties: {
15
- lcsc_id: {
16
- type: 'string',
17
- description: 'LCSC part number (e.g., C2040, C14663)',
18
- },
19
- },
20
- required: ['lcsc_id'],
21
- },
22
- };
23
-
24
- export const GetComponentParamsSchema = z.object({
25
- lcsc_id: z.string().regex(/^C\d+$/, 'Invalid LCSC part number'),
26
- });
27
-
28
- export async function handleGetComponent(args: unknown) {
29
- const params = GetComponentParamsSchema.parse(args);
30
-
31
- const component = await easyedaClient.getComponentData(params.lcsc_id);
32
-
33
- if (!component) {
34
- return {
35
- content: [{
36
- type: 'text' as const,
37
- text: `Component ${params.lcsc_id} not found`,
38
- }],
39
- isError: true,
40
- };
41
- }
42
-
43
- // Return a clean summary
44
- const summary = {
45
- info: component.info,
46
- symbol: {
47
- pin_count: component.symbol.pins.length,
48
- pins: component.symbol.pins,
49
- },
50
- footprint: {
51
- name: component.footprint.name,
52
- type: component.footprint.type,
53
- pad_count: component.footprint.pads.length,
54
- pads: component.footprint.pads,
55
- },
56
- has_3d_model: !!component.model3d,
57
- model_3d_uuid: component.model3d?.uuid,
58
- };
59
-
60
- return {
61
- content: [{
62
- type: 'text' as const,
63
- text: JSON.stringify(summary, null, 2),
64
- }],
65
- };
66
- }