easyeda-mcp-pro 0.2.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/README.md +304 -0
- package/dist/bridge/client.d.ts +16 -0
- package/dist/bridge/client.js +48 -0
- package/dist/bridge/client.js.map +1 -0
- package/dist/bridge/extension.d.ts +26 -0
- package/dist/bridge/extension.js +556 -0
- package/dist/bridge/extension.js.map +1 -0
- package/dist/bridge/manager.d.ts +37 -0
- package/dist/bridge/manager.js +225 -0
- package/dist/bridge/manager.js.map +1 -0
- package/dist/bridge/protocol.d.ts +68 -0
- package/dist/bridge/protocol.js +56 -0
- package/dist/bridge/protocol.js.map +1 -0
- package/dist/bridge/types.d.ts +52 -0
- package/dist/bridge/types.js +19 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/config/env.d.ts +98 -0
- package/dist/config/env.js +103 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/feature-flags.d.ts +16 -0
- package/dist/config/feature-flags.js +17 -0
- package/dist/config/feature-flags.js.map +1 -0
- package/dist/config/profiles.d.ts +10 -0
- package/dist/config/profiles.js +45 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/common.d.ts +123 -0
- package/dist/schemas/common.js +79 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/server/errors.d.ts +3 -0
- package/dist/server/errors.js +59 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/factory.d.ts +17 -0
- package/dist/server/factory.js +60 -0
- package/dist/server/factory.js.map +1 -0
- package/dist/server/transports/http.d.ts +10 -0
- package/dist/server/transports/http.js +54 -0
- package/dist/server/transports/http.js.map +1 -0
- package/dist/storage/database.d.ts +19 -0
- package/dist/storage/database.js +136 -0
- package/dist/storage/database.js.map +1 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/types.d.ts +26 -0
- package/dist/storage/types.js +9 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/tools/L0_diagnostics.d.ts +6 -0
- package/dist/tools/L0_diagnostics.js +297 -0
- package/dist/tools/L0_diagnostics.js.map +1 -0
- package/dist/tools/L1_board.d.ts +6 -0
- package/dist/tools/L1_board.js +182 -0
- package/dist/tools/L1_board.js.map +1 -0
- package/dist/tools/L1_bom.d.ts +6 -0
- package/dist/tools/L1_bom.js +267 -0
- package/dist/tools/L1_bom.js.map +1 -0
- package/dist/tools/L1_drc_erc.d.ts +6 -0
- package/dist/tools/L1_drc_erc.js +235 -0
- package/dist/tools/L1_drc_erc.js.map +1 -0
- package/dist/tools/L1_export.d.ts +6 -0
- package/dist/tools/L1_export.js +214 -0
- package/dist/tools/L1_export.js.map +1 -0
- package/dist/tools/L1_schematic.d.ts +6 -0
- package/dist/tools/L1_schematic.js +182 -0
- package/dist/tools/L1_schematic.js.map +1 -0
- package/dist/tools/registry.d.ts +21 -0
- package/dist/tools/registry.js +55 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +53 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.js +54 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/redaction.d.ts +2 -0
- package/dist/utils/redaction.js +56 -0
- package/dist/utils/redaction.js.map +1 -0
- package/dist/vendors/digikey/client.d.ts +49 -0
- package/dist/vendors/digikey/client.js +200 -0
- package/dist/vendors/digikey/client.js.map +1 -0
- package/dist/vendors/jlcpcb/client.d.ts +79 -0
- package/dist/vendors/jlcpcb/client.js +156 -0
- package/dist/vendors/jlcpcb/client.js.map +1 -0
- package/dist/vendors/lcsc/client.d.ts +57 -0
- package/dist/vendors/lcsc/client.js +140 -0
- package/dist/vendors/lcsc/client.js.map +1 -0
- package/dist/vendors/mouser/client.d.ts +46 -0
- package/dist/vendors/mouser/client.js +141 -0
- package/dist/vendors/mouser/client.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { request } from 'undici';
|
|
2
|
+
import { EasyEdaMcpError } from '../../schemas/common.js';
|
|
3
|
+
import { getLogger } from '../../utils/logger.js';
|
|
4
|
+
async function readBody(stream) {
|
|
5
|
+
const chunks = [];
|
|
6
|
+
for await (const chunk of stream) {
|
|
7
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
8
|
+
}
|
|
9
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
10
|
+
}
|
|
11
|
+
const MAX_RETRIES = 3;
|
|
12
|
+
const BASE_DELAY_MS = 1000;
|
|
13
|
+
export class JlcpcbClient {
|
|
14
|
+
config;
|
|
15
|
+
logger;
|
|
16
|
+
baseUrl;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.logger = getLogger();
|
|
20
|
+
this.baseUrl = config.JLCPCB_API_BASE_URL.replace(/\/+$/, '');
|
|
21
|
+
if (config.JLCPCB_MODE !== 'approved_api') {
|
|
22
|
+
throw new EasyEdaMcpError({
|
|
23
|
+
code: 'FEATURE_DISABLED',
|
|
24
|
+
message: 'JLCPCB API is not enabled. Set JLCPCB_MODE=approved_api to enable.',
|
|
25
|
+
suggestion: 'Set JLCPCB_MODE=approved_api and provide JLCPCB_CLIENT_ID and JLCPCB_CLIENT_SECRET.',
|
|
26
|
+
retryable: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (!config.JLCPCB_CLIENT_ID || !config.JLCPCB_CLIENT_SECRET) {
|
|
30
|
+
throw new EasyEdaMcpError({
|
|
31
|
+
code: 'CREDENTIALS_MISSING',
|
|
32
|
+
message: 'JLCPCB credentials are missing.',
|
|
33
|
+
suggestion: 'Set JLCPCB_CLIENT_ID and JLCPCB_CLIENT_SECRET environment variables.',
|
|
34
|
+
retryable: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
authHeaders() {
|
|
39
|
+
return {
|
|
40
|
+
'x-client-id': this.config.JLCPCB_CLIENT_ID,
|
|
41
|
+
'x-client-secret': this.config.JLCPCB_CLIENT_SECRET,
|
|
42
|
+
'content-type': 'application/json',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async requestWithRetry(path, options) {
|
|
46
|
+
const method = options.method ?? 'POST';
|
|
47
|
+
const url = `${this.baseUrl}${path}`;
|
|
48
|
+
let lastError;
|
|
49
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
50
|
+
try {
|
|
51
|
+
this.logger.debug({ url, method, attempt }, 'jlcpcb api call');
|
|
52
|
+
const { statusCode, body } = await request(url, {
|
|
53
|
+
method,
|
|
54
|
+
headers: this.authHeaders(),
|
|
55
|
+
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
56
|
+
});
|
|
57
|
+
const responseText = await readBody(body);
|
|
58
|
+
if (statusCode === 429) {
|
|
59
|
+
const retryAfter = 2000;
|
|
60
|
+
this.logger.warn({ url, retryAfter, attempt }, 'jlcpcb rate limited');
|
|
61
|
+
if (attempt < MAX_RETRIES) {
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter));
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (statusCode >= 500) {
|
|
67
|
+
this.logger.warn({ url, statusCode, attempt }, 'jlcpcb server error');
|
|
68
|
+
if (attempt < MAX_RETRIES) {
|
|
69
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
75
|
+
let parsed = null;
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(responseText);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// ignore parse failure
|
|
81
|
+
}
|
|
82
|
+
throw new EasyEdaMcpError({
|
|
83
|
+
code: 'VENDOR_API_UNAVAILABLE',
|
|
84
|
+
message: parsed?.message ?? parsed?.error ?? `JLCPCB API returned status ${statusCode}`,
|
|
85
|
+
suggestion: 'Check JLCPCB credentials and request parameters.',
|
|
86
|
+
retryable: statusCode >= 500,
|
|
87
|
+
details: { statusCode, response: responseText.slice(0, 500) },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return JSON.parse(responseText);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
lastError = err;
|
|
94
|
+
if (err instanceof EasyEdaMcpError && !err.retryable) {
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
if (attempt < MAX_RETRIES) {
|
|
98
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
99
|
+
this.logger.debug({ url, attempt, delay }, 'jlcpcb retry');
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
throw lastError instanceof EasyEdaMcpError
|
|
105
|
+
? lastError
|
|
106
|
+
: new EasyEdaMcpError({
|
|
107
|
+
code: 'VENDOR_API_UNAVAILABLE',
|
|
108
|
+
message: 'JLCPCB API request failed after retries.',
|
|
109
|
+
suggestion: 'Check network connectivity and JLCPCB API status.',
|
|
110
|
+
retryable: true,
|
|
111
|
+
details: { error: String(lastError) },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Request a quotation for a PCB order from JLCPCB.
|
|
116
|
+
* @param request - The PCB order specification
|
|
117
|
+
* @returns Quote result with total cost and breakdown
|
|
118
|
+
*/
|
|
119
|
+
async getQuote(request) {
|
|
120
|
+
return this.requestWithRetry('/api/order/getQuote', {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
body: request,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Place a PCB order with JLCPCB.
|
|
127
|
+
* @param request - The PCB order specification
|
|
128
|
+
* @returns Order result containing the order ID
|
|
129
|
+
*/
|
|
130
|
+
async placeOrder(request) {
|
|
131
|
+
return this.requestWithRetry('/api/order/addOrder', {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
body: request,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get the current status of an existing JLCPCB order.
|
|
138
|
+
* @param orderId - The JLCPCB order ID
|
|
139
|
+
* @returns Order status and details
|
|
140
|
+
*/
|
|
141
|
+
async getOrderStatus(orderId) {
|
|
142
|
+
return this.requestWithRetry(`/api/order/getOrderDetail?orderId=${encodeURIComponent(orderId)}`, { method: 'GET' });
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check JLCPCB capabilities for a given board specification.
|
|
146
|
+
* @param boardSpec - Board specification to check
|
|
147
|
+
* @returns Array of capability results
|
|
148
|
+
*/
|
|
149
|
+
async checkCapabilities(boardSpec) {
|
|
150
|
+
return this.requestWithRetry('/api/capabilities/check', {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
body: boardSpec,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/vendors/jlcpcb/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type EnvConfig } from '../../config/env.js';
|
|
2
|
+
export interface LcscPart {
|
|
3
|
+
lcsc: string;
|
|
4
|
+
manufacturer: string;
|
|
5
|
+
description: string;
|
|
6
|
+
datasheet: string;
|
|
7
|
+
stock: number;
|
|
8
|
+
price: string;
|
|
9
|
+
category: string;
|
|
10
|
+
package: string;
|
|
11
|
+
inStock: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface LcscSearchResponse {
|
|
14
|
+
parts: LcscPart[];
|
|
15
|
+
total: number;
|
|
16
|
+
}
|
|
17
|
+
export interface LcscCategoryPart {
|
|
18
|
+
lcsc: string;
|
|
19
|
+
manufacturer: string;
|
|
20
|
+
description: string;
|
|
21
|
+
price: string;
|
|
22
|
+
stock: number;
|
|
23
|
+
package: string;
|
|
24
|
+
datasheet: string;
|
|
25
|
+
}
|
|
26
|
+
export declare class LcscClient {
|
|
27
|
+
private config;
|
|
28
|
+
private logger;
|
|
29
|
+
private jlcsearchBase;
|
|
30
|
+
private lcscApiKey;
|
|
31
|
+
constructor(config: EnvConfig);
|
|
32
|
+
private jlcsearchRequest;
|
|
33
|
+
private lcscOfficialRequest;
|
|
34
|
+
/**
|
|
35
|
+
* Search LCSC parts by keyword query.
|
|
36
|
+
* Uses the jlcsearch API as primary backend, falls back to LCSC official API if available.
|
|
37
|
+
* @param query - The search keyword
|
|
38
|
+
* @param options - Optional search configuration (limit, page)
|
|
39
|
+
* @returns Search results with parts list and total count
|
|
40
|
+
*/
|
|
41
|
+
searchParts(query: string, options?: {
|
|
42
|
+
limit?: number;
|
|
43
|
+
page?: number;
|
|
44
|
+
}): Promise<LcscSearchResponse>;
|
|
45
|
+
/**
|
|
46
|
+
* Get detailed information for a specific LCSC part by its LCSC code.
|
|
47
|
+
* @param lcscCode - The LCSC part number (e.g., "C12345")
|
|
48
|
+
* @returns The part detail or null if not found
|
|
49
|
+
*/
|
|
50
|
+
getPartDetail(lcscCode: string): Promise<LcscPart | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Get parts by category from LCSC.
|
|
53
|
+
* @param category - The category name or ID
|
|
54
|
+
* @returns Array of parts in the category
|
|
55
|
+
*/
|
|
56
|
+
getPartsByCategory(category: string): Promise<LcscPart[]>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { request } from 'undici';
|
|
2
|
+
import { EasyEdaMcpError } from '../../schemas/common.js';
|
|
3
|
+
import { getLogger } from '../../utils/logger.js';
|
|
4
|
+
async function readBody(stream) {
|
|
5
|
+
const chunks = [];
|
|
6
|
+
for await (const chunk of stream) {
|
|
7
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
8
|
+
}
|
|
9
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
10
|
+
}
|
|
11
|
+
export class LcscClient {
|
|
12
|
+
config;
|
|
13
|
+
logger;
|
|
14
|
+
jlcsearchBase;
|
|
15
|
+
lcscApiKey;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.logger = getLogger();
|
|
19
|
+
this.jlcsearchBase = config.JLCSEARCH_BASE_URL.replace(/\/+$/, '');
|
|
20
|
+
this.lcscApiKey = config.LCSC_API_KEY;
|
|
21
|
+
}
|
|
22
|
+
async jlcsearchRequest(path, options) {
|
|
23
|
+
const method = options.method ?? 'GET';
|
|
24
|
+
const queryString = options.query ? '?' + new URLSearchParams(options.query).toString() : '';
|
|
25
|
+
const url = `${this.jlcsearchBase}${path}${queryString}`;
|
|
26
|
+
this.logger.debug({ url, method }, 'lcsc jlcsearch api call');
|
|
27
|
+
const { statusCode, body } = await request(url, {
|
|
28
|
+
method,
|
|
29
|
+
headers: { accept: 'application/json' },
|
|
30
|
+
});
|
|
31
|
+
const responseText = await readBody(body);
|
|
32
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
33
|
+
throw new EasyEdaMcpError({
|
|
34
|
+
code: 'VENDOR_API_UNAVAILABLE',
|
|
35
|
+
message: `LCSC jlcsearch API returned status ${statusCode}`,
|
|
36
|
+
suggestion: 'Check JLCSEARCH_BASE_URL and network connectivity.',
|
|
37
|
+
retryable: statusCode >= 500,
|
|
38
|
+
details: { statusCode, response: responseText.slice(0, 500) },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return JSON.parse(responseText);
|
|
42
|
+
}
|
|
43
|
+
async lcscOfficialRequest(path, options) {
|
|
44
|
+
const method = options.method ?? 'GET';
|
|
45
|
+
const url = `https://www.lcsc.com/api${path}`;
|
|
46
|
+
this.logger.debug({ url, method }, 'lcsc official api call');
|
|
47
|
+
const headers = {
|
|
48
|
+
accept: 'application/json',
|
|
49
|
+
};
|
|
50
|
+
if (this.lcscApiKey) {
|
|
51
|
+
headers['x-api-key'] = this.lcscApiKey;
|
|
52
|
+
}
|
|
53
|
+
if (options.body) {
|
|
54
|
+
headers['content-type'] = 'application/json';
|
|
55
|
+
}
|
|
56
|
+
const { statusCode, body } = await request(url, {
|
|
57
|
+
method,
|
|
58
|
+
headers,
|
|
59
|
+
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
60
|
+
});
|
|
61
|
+
const responseText = await readBody(body);
|
|
62
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
63
|
+
throw new EasyEdaMcpError({
|
|
64
|
+
code: 'VENDOR_API_UNAVAILABLE',
|
|
65
|
+
message: `LCSC official API returned status ${statusCode}`,
|
|
66
|
+
suggestion: 'Check LCSC_API_KEY and network connectivity.',
|
|
67
|
+
retryable: statusCode >= 500,
|
|
68
|
+
details: { statusCode, response: responseText.slice(0, 500) },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return JSON.parse(responseText);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Search LCSC parts by keyword query.
|
|
75
|
+
* Uses the jlcsearch API as primary backend, falls back to LCSC official API if available.
|
|
76
|
+
* @param query - The search keyword
|
|
77
|
+
* @param options - Optional search configuration (limit, page)
|
|
78
|
+
* @returns Search results with parts list and total count
|
|
79
|
+
*/
|
|
80
|
+
async searchParts(query, options) {
|
|
81
|
+
const limit = options?.limit ?? 20;
|
|
82
|
+
const page = options?.page ?? 1;
|
|
83
|
+
try {
|
|
84
|
+
const data = await this.jlcsearchRequest('/api/search', {
|
|
85
|
+
query: { keyword: query, limit: String(limit), page: String(page) },
|
|
86
|
+
});
|
|
87
|
+
const parts = data.results ?? data.parts ?? [];
|
|
88
|
+
const total = data.count ?? data.total ?? parts.length;
|
|
89
|
+
return { parts, total };
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (this.lcscApiKey) {
|
|
93
|
+
this.logger.debug('jlcsearch failed, falling back to lcsc official api');
|
|
94
|
+
return this.lcscOfficialRequest('/search', {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
body: { keyword: query, limit, page },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get detailed information for a specific LCSC part by its LCSC code.
|
|
104
|
+
* @param lcscCode - The LCSC part number (e.g., "C12345")
|
|
105
|
+
* @returns The part detail or null if not found
|
|
106
|
+
*/
|
|
107
|
+
async getPartDetail(lcscCode) {
|
|
108
|
+
try {
|
|
109
|
+
const data = await this.jlcsearchRequest(`/api/part/${encodeURIComponent(lcscCode)}`, {});
|
|
110
|
+
const part = 'part' in data ? data.part : data;
|
|
111
|
+
return part ?? null;
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (this.lcscApiKey) {
|
|
115
|
+
this.logger.debug('jlcsearch failed, falling back to lcsc official api');
|
|
116
|
+
return this.lcscOfficialRequest(`/part/${encodeURIComponent(lcscCode)}`, {});
|
|
117
|
+
}
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get parts by category from LCSC.
|
|
123
|
+
* @param category - The category name or ID
|
|
124
|
+
* @returns Array of parts in the category
|
|
125
|
+
*/
|
|
126
|
+
async getPartsByCategory(category) {
|
|
127
|
+
try {
|
|
128
|
+
const data = await this.jlcsearchRequest(`/api/categories/${encodeURIComponent(category)}/parts`, {});
|
|
129
|
+
return data.parts ?? data.results ?? [];
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (this.lcscApiKey) {
|
|
133
|
+
this.logger.debug('jlcsearch failed, falling back to lcsc official api');
|
|
134
|
+
return this.lcscOfficialRequest(`/categories/${encodeURIComponent(category)}/parts`, {});
|
|
135
|
+
}
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/vendors/lcsc/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type EnvConfig } from '../../config/env.js';
|
|
2
|
+
export interface MouserPriceBreak {
|
|
3
|
+
quantity: number;
|
|
4
|
+
price: number;
|
|
5
|
+
}
|
|
6
|
+
export interface MouserPart {
|
|
7
|
+
mouserNumber: string;
|
|
8
|
+
manufacturer: string;
|
|
9
|
+
description: string;
|
|
10
|
+
datasheetUrl: string;
|
|
11
|
+
priceBreaks: MouserPriceBreak[];
|
|
12
|
+
availability: number;
|
|
13
|
+
leadTime: string;
|
|
14
|
+
rohs: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface MouserSearchResponse {
|
|
17
|
+
parts: MouserPart[];
|
|
18
|
+
total: number;
|
|
19
|
+
}
|
|
20
|
+
export declare class MouserClient {
|
|
21
|
+
private config;
|
|
22
|
+
private logger;
|
|
23
|
+
private baseUrl;
|
|
24
|
+
private apiKey;
|
|
25
|
+
constructor(config: EnvConfig);
|
|
26
|
+
private apiPost;
|
|
27
|
+
private parseMouserPart;
|
|
28
|
+
/**
|
|
29
|
+
* Search Mouser parts by keyword.
|
|
30
|
+
* @param query - The search keyword
|
|
31
|
+
* @returns Array of matching parts
|
|
32
|
+
*/
|
|
33
|
+
searchByKeyword(query: string): Promise<MouserPart[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Search Mouser parts by exact manufacturer part number.
|
|
36
|
+
* @param partNumber - The manufacturer part number
|
|
37
|
+
* @returns Array of matching parts
|
|
38
|
+
*/
|
|
39
|
+
searchByPartNumber(partNumber: string): Promise<MouserPart[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Get price and availability for a specific Mouser part number.
|
|
42
|
+
* @param mouserNumber - The Mouser system part number
|
|
43
|
+
* @returns Part details with pricing and stock
|
|
44
|
+
*/
|
|
45
|
+
getPriceAndAvailability(mouserNumber: string): Promise<MouserPart>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { request } from 'undici';
|
|
2
|
+
import { EasyEdaMcpError } from '../../schemas/common.js';
|
|
3
|
+
import { getLogger } from '../../utils/logger.js';
|
|
4
|
+
async function readBody(stream) {
|
|
5
|
+
const chunks = [];
|
|
6
|
+
for await (const chunk of stream) {
|
|
7
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
8
|
+
}
|
|
9
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
10
|
+
}
|
|
11
|
+
export class MouserClient {
|
|
12
|
+
config;
|
|
13
|
+
logger;
|
|
14
|
+
baseUrl;
|
|
15
|
+
apiKey;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.logger = getLogger();
|
|
19
|
+
this.baseUrl = config.MOUSER_API_BASE_URL.replace(/\/+$/, '');
|
|
20
|
+
this.apiKey = config.MOUSER_API_KEY;
|
|
21
|
+
if (!config.MOUSER_ENABLED) {
|
|
22
|
+
throw new EasyEdaMcpError({
|
|
23
|
+
code: 'FEATURE_DISABLED',
|
|
24
|
+
message: 'Mouser API is not enabled. Set MOUSER_ENABLED=true to enable.',
|
|
25
|
+
suggestion: 'Set MOUSER_ENABLED=true and provide MOUSER_API_KEY in your environment.',
|
|
26
|
+
retryable: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (!this.apiKey) {
|
|
30
|
+
throw new EasyEdaMcpError({
|
|
31
|
+
code: 'CREDENTIALS_MISSING',
|
|
32
|
+
message: 'Mouser API key is missing.',
|
|
33
|
+
suggestion: 'Set MOUSER_API_KEY environment variable.',
|
|
34
|
+
retryable: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async apiPost(endpoint, body) {
|
|
39
|
+
const url = `${this.baseUrl}${endpoint}?apiKey=${encodeURIComponent(this.apiKey)}`;
|
|
40
|
+
this.logger.debug({ url, endpoint }, 'mouser api call');
|
|
41
|
+
const { statusCode, body: responseBody } = await request(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: { 'content-type': 'application/json' },
|
|
44
|
+
body: JSON.stringify(body),
|
|
45
|
+
});
|
|
46
|
+
const responseText = await readBody(responseBody);
|
|
47
|
+
if (statusCode === 429) {
|
|
48
|
+
throw new EasyEdaMcpError({
|
|
49
|
+
code: 'RATE_LIMITED',
|
|
50
|
+
message: 'Mouser API rate limit exceeded.',
|
|
51
|
+
suggestion: 'Retry after a short delay or reduce request frequency.',
|
|
52
|
+
retryable: true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
56
|
+
let parsed = null;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(responseText);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// ignore
|
|
62
|
+
}
|
|
63
|
+
throw new EasyEdaMcpError({
|
|
64
|
+
code: 'VENDOR_API_UNAVAILABLE',
|
|
65
|
+
message: parsed?.ErrorMessage ?? `Mouser API returned status ${statusCode}`,
|
|
66
|
+
suggestion: 'Check MOUSER_API_KEY and request parameters.',
|
|
67
|
+
retryable: statusCode >= 500,
|
|
68
|
+
details: { statusCode, response: responseText.slice(0, 500) },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return JSON.parse(responseText);
|
|
72
|
+
}
|
|
73
|
+
parseMouserPart(raw) {
|
|
74
|
+
const priceBreaks = [];
|
|
75
|
+
const rawBreaks = raw['PriceBreaks'];
|
|
76
|
+
if (rawBreaks) {
|
|
77
|
+
for (const pb of rawBreaks) {
|
|
78
|
+
priceBreaks.push({
|
|
79
|
+
quantity: Number(pb['Quantity'] ?? 0),
|
|
80
|
+
price: Number(pb['Price'] ?? 0),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
mouserNumber: String(raw['MouserPartNumber'] ?? raw['MouserNumber'] ?? ''),
|
|
86
|
+
manufacturer: String(raw['Manufacturer'] ?? ''),
|
|
87
|
+
description: String(raw['Description'] ?? ''),
|
|
88
|
+
datasheetUrl: String(raw['DataSheetUrl'] ?? raw['DatasheetUrl'] ?? ''),
|
|
89
|
+
priceBreaks,
|
|
90
|
+
availability: Number(raw['AvailabilityInStock'] ?? raw['Availability'] ?? 0),
|
|
91
|
+
leadTime: String(raw['LeadTime'] ?? ''),
|
|
92
|
+
rohs: Boolean(raw['RoHS'] ?? raw['Rohs'] ?? false),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Search Mouser parts by keyword.
|
|
97
|
+
* @param query - The search keyword
|
|
98
|
+
* @returns Array of matching parts
|
|
99
|
+
*/
|
|
100
|
+
async searchByKeyword(query) {
|
|
101
|
+
const response = await this.apiPost('/api/v1/search/keyword', {
|
|
102
|
+
SearchByKeywordRequest: { keyword: query },
|
|
103
|
+
});
|
|
104
|
+
const parts = response.SearchResults?.Parts ?? [];
|
|
105
|
+
return parts.map((p) => this.parseMouserPart(p));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Search Mouser parts by exact manufacturer part number.
|
|
109
|
+
* @param partNumber - The manufacturer part number
|
|
110
|
+
* @returns Array of matching parts
|
|
111
|
+
*/
|
|
112
|
+
async searchByPartNumber(partNumber) {
|
|
113
|
+
const response = await this.apiPost('/api/v1/search/partnumber', {
|
|
114
|
+
SearchByPartNumberRequest: { partNumber },
|
|
115
|
+
});
|
|
116
|
+
const parts = response.SearchResults?.Parts ?? [];
|
|
117
|
+
return parts.map((p) => this.parseMouserPart(p));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get price and availability for a specific Mouser part number.
|
|
121
|
+
* @param mouserNumber - The Mouser system part number
|
|
122
|
+
* @returns Part details with pricing and stock
|
|
123
|
+
*/
|
|
124
|
+
async getPriceAndAvailability(mouserNumber) {
|
|
125
|
+
const response = await this.apiPost('/api/v1/search/partnumber', {
|
|
126
|
+
SearchByPartNumberRequest: { partNumber: mouserNumber },
|
|
127
|
+
});
|
|
128
|
+
const raw = response.SearchResults?.Parts?.[0];
|
|
129
|
+
if (!raw) {
|
|
130
|
+
throw new EasyEdaMcpError({
|
|
131
|
+
code: 'VENDOR_API_UNAVAILABLE',
|
|
132
|
+
message: `Mouser part ${mouserNumber} not found.`,
|
|
133
|
+
suggestion: 'Verify the Mouser part number is correct.',
|
|
134
|
+
retryable: false,
|
|
135
|
+
details: { mouserNumber },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return this.parseMouserPart(raw);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/vendors/mouser/client.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "easyeda-mcp-pro",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"mcpName": "io.github.oaslananka/easyeda-mcp-pro",
|
|
5
|
+
"description": "Production-grade MCP server for EasyEDA Pro: safe PCB design inspection, BOM sourcing, manufacturing export, and AI-assisted hardware review.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"easyeda",
|
|
10
|
+
"easyeda-pro",
|
|
11
|
+
"pcb",
|
|
12
|
+
"eda",
|
|
13
|
+
"jlcpcb",
|
|
14
|
+
"lcsc",
|
|
15
|
+
"hardware",
|
|
16
|
+
"schematic",
|
|
17
|
+
"bom"
|
|
18
|
+
],
|
|
19
|
+
"author": "oaslananka",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "dist/index.js",
|
|
23
|
+
"bin": {
|
|
24
|
+
"easyeda-mcp-pro": "dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24 <27",
|
|
33
|
+
"pnpm": ">=11"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
|
+
"better-sqlite3": "^12.10.0",
|
|
38
|
+
"dotenv": "^17.4.2",
|
|
39
|
+
"pino": "^10.3.1",
|
|
40
|
+
"undici": "^8.3.0",
|
|
41
|
+
"ws": "^8.21.0",
|
|
42
|
+
"zod": "^4.4.3"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@eslint/js": "^10.0.1",
|
|
46
|
+
"@jlceda/pro-api-types": "^0.2.54",
|
|
47
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
48
|
+
"@types/express": "^5.0.6",
|
|
49
|
+
"@types/node": "^24.12.4",
|
|
50
|
+
"@types/ws": "^8.5.14",
|
|
51
|
+
"eslint": "^10.4.1",
|
|
52
|
+
"prettier": "^3.8.3",
|
|
53
|
+
"tsx": "^4.22.4",
|
|
54
|
+
"typescript": "^6.0.3",
|
|
55
|
+
"typescript-eslint": "^8.60.1",
|
|
56
|
+
"vitest": "^4.1.8"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"dev": "tsx watch src/index.ts",
|
|
60
|
+
"build": "tsc -p tsconfig.build.json",
|
|
61
|
+
"start": "node dist/index.js",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"lint": "eslint .",
|
|
64
|
+
"format": "prettier --write .",
|
|
65
|
+
"format:check": "prettier --check .",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:watch": "vitest",
|
|
68
|
+
"test:coverage": "vitest run --coverage",
|
|
69
|
+
"inspector": "pnpm build && npx @modelcontextprotocol/inspector node dist/index.js",
|
|
70
|
+
"generate:methods": "tsx scripts/generate-method-registry.ts",
|
|
71
|
+
"generate:tools-doc": "tsx scripts/generate-tools-doc.ts",
|
|
72
|
+
"verify:tool-coverage": "tsx scripts/verify-tool-coverage.ts"
|
|
73
|
+
}
|
|
74
|
+
}
|