@xiaozhi-client/mcp-core 1.9.7-beta.3 → 1.9.7-beta.6
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 +750 -0
- package/dist/index.d.ts +343 -39
- package/dist/index.js +742 -80
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/dist/index.js
CHANGED
|
@@ -53,144 +53,806 @@ var ToolCallError = class extends Error {
|
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
// src/
|
|
57
|
-
import {
|
|
58
|
-
|
|
56
|
+
// src/connection.ts
|
|
57
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
58
|
+
|
|
59
|
+
// src/transport-factory.ts
|
|
60
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
61
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
62
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
63
|
+
import { EventSource } from "eventsource";
|
|
64
|
+
var globalThisAny = typeof globalThis !== "undefined" ? globalThis : global;
|
|
65
|
+
if (typeof globalThisAny !== "undefined" && !globalThisAny.EventSource) {
|
|
66
|
+
globalThisAny.EventSource = EventSource;
|
|
67
|
+
}
|
|
68
|
+
function createTransport(config) {
|
|
69
|
+
console.debug(
|
|
70
|
+
`[TransportFactory] \u521B\u5EFA ${config.type} transport for ${config.name}`
|
|
71
|
+
);
|
|
72
|
+
switch (config.type) {
|
|
73
|
+
case "stdio" /* STDIO */:
|
|
74
|
+
return createStdioTransport(config);
|
|
75
|
+
case "sse" /* SSE */:
|
|
76
|
+
return createSSETransport(config);
|
|
77
|
+
case "streamable-http" /* STREAMABLE_HTTP */:
|
|
78
|
+
return createStreamableHTTPTransport(config);
|
|
79
|
+
default:
|
|
80
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u4F20\u8F93\u7C7B\u578B: ${config.type}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
__name(createTransport, "createTransport");
|
|
84
|
+
function createStdioTransport(config) {
|
|
85
|
+
if (!config.command) {
|
|
86
|
+
throw new Error("stdio transport \u9700\u8981 command \u914D\u7F6E");
|
|
87
|
+
}
|
|
88
|
+
return new StdioClientTransport({
|
|
89
|
+
command: config.command,
|
|
90
|
+
args: config.args || [],
|
|
91
|
+
env: config.env
|
|
92
|
+
// 传递环境变量
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
__name(createStdioTransport, "createStdioTransport");
|
|
96
|
+
function createSSETransport(config) {
|
|
97
|
+
if (!config.url) {
|
|
98
|
+
throw new Error("SSE transport \u9700\u8981 URL \u914D\u7F6E");
|
|
99
|
+
}
|
|
100
|
+
const url = new URL(config.url);
|
|
101
|
+
const options = createSSEOptions(config);
|
|
102
|
+
return new SSEClientTransport(url, options);
|
|
103
|
+
}
|
|
104
|
+
__name(createSSETransport, "createSSETransport");
|
|
105
|
+
function createStreamableHTTPTransport(config) {
|
|
106
|
+
if (!config.url) {
|
|
107
|
+
throw new Error("StreamableHTTP transport \u9700\u8981 URL \u914D\u7F6E");
|
|
108
|
+
}
|
|
109
|
+
const url = new URL(config.url);
|
|
110
|
+
const options = createStreamableHTTPOptions(config);
|
|
111
|
+
return new StreamableHTTPClientTransport(url, options);
|
|
112
|
+
}
|
|
113
|
+
__name(createStreamableHTTPTransport, "createStreamableHTTPTransport");
|
|
114
|
+
function createSSEOptions(config) {
|
|
115
|
+
const options = {};
|
|
116
|
+
if (config.apiKey) {
|
|
117
|
+
options.requestInit = {
|
|
118
|
+
headers: {
|
|
119
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
120
|
+
...config.headers
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
} else if (config.headers) {
|
|
124
|
+
options.requestInit = {
|
|
125
|
+
headers: config.headers
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return options;
|
|
129
|
+
}
|
|
130
|
+
__name(createSSEOptions, "createSSEOptions");
|
|
131
|
+
function createStreamableHTTPOptions(config) {
|
|
132
|
+
const options = {};
|
|
133
|
+
if (config.apiKey) {
|
|
134
|
+
options.requestInit = {
|
|
135
|
+
headers: {
|
|
136
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
137
|
+
...config.headers
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
} else if (config.headers) {
|
|
141
|
+
options.requestInit = {
|
|
142
|
+
headers: config.headers
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return options;
|
|
146
|
+
}
|
|
147
|
+
__name(createStreamableHTTPOptions, "createStreamableHTTPOptions");
|
|
148
|
+
function validateConfig(config) {
|
|
149
|
+
if (!config.name || typeof config.name !== "string") {
|
|
150
|
+
throw new Error("\u914D\u7F6E\u5FC5\u987B\u5305\u542B\u6709\u6548\u7684 name \u5B57\u6BB5");
|
|
151
|
+
}
|
|
152
|
+
if (config.type && !Object.values(MCPTransportType).includes(config.type)) {
|
|
153
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u4F20\u8F93\u7C7B\u578B: ${config.type}`);
|
|
154
|
+
}
|
|
155
|
+
if (!config.type) {
|
|
156
|
+
throw new Error("\u4F20\u8F93\u7C7B\u578B\u672A\u8BBE\u7F6E\uFF0C\u8FD9\u5E94\u8BE5\u5728 inferTransportType \u4E2D\u5904\u7406");
|
|
157
|
+
}
|
|
158
|
+
switch (config.type) {
|
|
159
|
+
case "stdio" /* STDIO */:
|
|
160
|
+
if (!config.command) {
|
|
161
|
+
throw new Error("stdio \u7C7B\u578B\u9700\u8981 command \u5B57\u6BB5");
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
case "sse" /* SSE */:
|
|
165
|
+
if (config.url === void 0 || config.url === null) {
|
|
166
|
+
throw new Error(`${config.type} \u7C7B\u578B\u9700\u8981 url \u5B57\u6BB5`);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
case "streamable-http" /* STREAMABLE_HTTP */:
|
|
170
|
+
if (config.url === void 0 || config.url === null) {
|
|
171
|
+
throw new Error(`${config.type} \u7C7B\u578B\u9700\u8981 url \u5B57\u6BB5`);
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
default:
|
|
175
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u4F20\u8F93\u7C7B\u578B: ${config.type}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
__name(validateConfig, "validateConfig");
|
|
179
|
+
function getSupportedTypes() {
|
|
180
|
+
return [
|
|
181
|
+
"stdio" /* STDIO */,
|
|
182
|
+
"sse" /* SSE */,
|
|
183
|
+
"streamable-http" /* STREAMABLE_HTTP */
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
__name(getSupportedTypes, "getSupportedTypes");
|
|
187
|
+
var TransportFactory = {
|
|
188
|
+
create: createTransport,
|
|
189
|
+
validateConfig,
|
|
190
|
+
getSupportedTypes
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/utils/type-normalizer.ts
|
|
194
|
+
var TypeFieldNormalizer;
|
|
195
|
+
((TypeFieldNormalizer2) => {
|
|
196
|
+
function normalizeTypeField2(config) {
|
|
197
|
+
if (!config || typeof config !== "object") {
|
|
198
|
+
return config;
|
|
199
|
+
}
|
|
200
|
+
const normalizedConfig = JSON.parse(JSON.stringify(config));
|
|
201
|
+
if (!("type" in normalizedConfig)) {
|
|
202
|
+
return normalizedConfig;
|
|
203
|
+
}
|
|
204
|
+
const originalType = normalizedConfig.type;
|
|
205
|
+
if (originalType === "sse" || originalType === "streamable-http") {
|
|
206
|
+
return normalizedConfig;
|
|
207
|
+
}
|
|
208
|
+
let normalizedType;
|
|
209
|
+
if (originalType === "streamableHttp" || originalType === "streamable_http") {
|
|
210
|
+
normalizedType = "streamable-http";
|
|
211
|
+
} else if (originalType === "s_se" || originalType === "s-se") {
|
|
212
|
+
normalizedType = "sse";
|
|
213
|
+
} else {
|
|
214
|
+
normalizedType = convertToKebabCase(originalType);
|
|
215
|
+
}
|
|
216
|
+
if (normalizedType === "sse" || normalizedType === "streamable-http") {
|
|
217
|
+
normalizedConfig.type = normalizedType;
|
|
218
|
+
if (originalType !== normalizedType) {
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return normalizedConfig;
|
|
222
|
+
}
|
|
223
|
+
TypeFieldNormalizer2.normalizeTypeField = normalizeTypeField2;
|
|
224
|
+
__name(normalizeTypeField2, "normalizeTypeField");
|
|
225
|
+
function convertToKebabCase(str) {
|
|
226
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
|
|
227
|
+
}
|
|
228
|
+
__name(convertToKebabCase, "convertToKebabCase");
|
|
229
|
+
})(TypeFieldNormalizer || (TypeFieldNormalizer = {}));
|
|
230
|
+
function normalizeTypeField(config) {
|
|
231
|
+
return TypeFieldNormalizer.normalizeTypeField(config);
|
|
232
|
+
}
|
|
233
|
+
__name(normalizeTypeField, "normalizeTypeField");
|
|
234
|
+
|
|
235
|
+
// src/utils/validators.ts
|
|
236
|
+
function inferTransportTypeFromUrl(url, options) {
|
|
237
|
+
try {
|
|
238
|
+
const parsedUrl = new URL(url);
|
|
239
|
+
const pathname = parsedUrl.pathname;
|
|
240
|
+
if (pathname.endsWith("/sse")) {
|
|
241
|
+
return "sse" /* SSE */;
|
|
242
|
+
}
|
|
243
|
+
if (pathname.endsWith("/mcp")) {
|
|
244
|
+
return "streamable-http" /* STREAMABLE_HTTP */;
|
|
245
|
+
}
|
|
246
|
+
if (options?.serviceName) {
|
|
247
|
+
console.info(
|
|
248
|
+
`[MCP-${options.serviceName}] URL \u8DEF\u5F84 ${pathname} \u4E0D\u5339\u914D\u7279\u5B9A\u89C4\u5219\uFF0C\u9ED8\u8BA4\u63A8\u65AD\u4E3A streamable-http \u7C7B\u578B`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return "streamable-http" /* STREAMABLE_HTTP */;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (options?.serviceName) {
|
|
254
|
+
console.warn(
|
|
255
|
+
`[MCP-${options.serviceName}] URL \u89E3\u6790\u5931\u8D25\uFF0C\u9ED8\u8BA4\u63A8\u65AD\u4E3A streamable-http \u7C7B\u578B`,
|
|
256
|
+
error
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return "streamable-http" /* STREAMABLE_HTTP */;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
__name(inferTransportTypeFromUrl, "inferTransportTypeFromUrl");
|
|
263
|
+
function inferTransportTypeFromConfig(config) {
|
|
264
|
+
if (config.type) {
|
|
265
|
+
const normalizedConfig = TypeFieldNormalizer.normalizeTypeField(config);
|
|
266
|
+
return normalizedConfig;
|
|
267
|
+
}
|
|
268
|
+
if (config.command) {
|
|
269
|
+
return {
|
|
270
|
+
...config,
|
|
271
|
+
type: "stdio" /* STDIO */
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (config.url !== void 0 && config.url !== null) {
|
|
275
|
+
const inferredType = inferTransportTypeFromUrl(config.url, {
|
|
276
|
+
serviceName: config.name
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
...config,
|
|
280
|
+
type: inferredType
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
throw new Error(
|
|
284
|
+
`\u65E0\u6CD5\u4E3A\u670D\u52A1 ${config.name} \u63A8\u65AD\u4F20\u8F93\u7C7B\u578B\u3002\u8BF7\u663E\u5F0F\u6307\u5B9A type \u5B57\u6BB5\uFF0C\u6216\u63D0\u4F9B command/url \u914D\u7F6E`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
__name(inferTransportTypeFromConfig, "inferTransportTypeFromConfig");
|
|
288
|
+
function validateToolCallParams(params, options) {
|
|
289
|
+
const opts = {
|
|
290
|
+
validateName: true,
|
|
291
|
+
validateArguments: true,
|
|
292
|
+
allowEmptyArguments: true,
|
|
293
|
+
...options
|
|
294
|
+
};
|
|
295
|
+
if (!params || typeof params !== "object") {
|
|
296
|
+
throw new ToolCallError(
|
|
297
|
+
-32602 /* INVALID_PARAMS */,
|
|
298
|
+
"\u8BF7\u6C42\u53C2\u6570\u5FC5\u987B\u662F\u5BF9\u8C61"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
const paramsObj = params;
|
|
302
|
+
if (opts.validateName) {
|
|
303
|
+
if (!paramsObj.name || typeof paramsObj.name !== "string") {
|
|
304
|
+
throw new ToolCallError(
|
|
305
|
+
-32602 /* INVALID_PARAMS */,
|
|
306
|
+
"\u5DE5\u5177\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32"
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (opts.validateArguments && paramsObj.arguments !== void 0 && paramsObj.arguments !== null) {
|
|
311
|
+
if (typeof paramsObj.arguments !== "object" || Array.isArray(paramsObj.arguments)) {
|
|
312
|
+
throw new ToolCallError(
|
|
313
|
+
-32602 /* INVALID_PARAMS */,
|
|
314
|
+
"\u5DE5\u5177\u53C2\u6570\u5FC5\u987B\u662F\u5BF9\u8C61"
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (!opts.allowEmptyArguments && paramsObj.arguments !== void 0 && paramsObj.arguments !== null) {
|
|
319
|
+
const argsObj = paramsObj.arguments;
|
|
320
|
+
if (Object.keys(argsObj).length === 0) {
|
|
321
|
+
throw new ToolCallError(
|
|
322
|
+
-32602 /* INVALID_PARAMS */,
|
|
323
|
+
"\u5DE5\u5177\u53C2\u6570\u4E0D\u80FD\u4E3A\u7A7A"
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (opts.customValidator) {
|
|
328
|
+
const error = opts.customValidator(paramsObj);
|
|
329
|
+
if (error) {
|
|
330
|
+
throw new ToolCallError(-32602 /* INVALID_PARAMS */, error);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
name: paramsObj.name,
|
|
335
|
+
arguments: paramsObj.arguments
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
__name(validateToolCallParams, "validateToolCallParams");
|
|
339
|
+
|
|
340
|
+
// src/connection.ts
|
|
341
|
+
var MCPConnection = class {
|
|
59
342
|
static {
|
|
60
|
-
__name(this, "
|
|
343
|
+
__name(this, "MCPConnection");
|
|
61
344
|
}
|
|
62
|
-
|
|
345
|
+
config;
|
|
346
|
+
client = null;
|
|
347
|
+
transport = null;
|
|
63
348
|
tools = /* @__PURE__ */ new Map();
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
349
|
+
connectionState = "disconnected" /* DISCONNECTED */;
|
|
350
|
+
connectionTimeout = null;
|
|
351
|
+
initialized = false;
|
|
352
|
+
callbacks;
|
|
353
|
+
constructor(config, callbacks) {
|
|
354
|
+
this.config = inferTransportTypeFromConfig(config);
|
|
355
|
+
this.callbacks = callbacks;
|
|
356
|
+
this.validateConfig();
|
|
70
357
|
}
|
|
71
358
|
/**
|
|
72
|
-
*
|
|
359
|
+
* 验证配置
|
|
73
360
|
*/
|
|
74
|
-
|
|
75
|
-
this.
|
|
361
|
+
validateConfig() {
|
|
362
|
+
TransportFactory.validateConfig(this.config);
|
|
76
363
|
}
|
|
77
364
|
/**
|
|
78
|
-
*
|
|
365
|
+
* 连接到 MCP 服务
|
|
79
366
|
*/
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
throw new Error(
|
|
367
|
+
async connect() {
|
|
368
|
+
if (this.connectionState === "connecting" /* CONNECTING */) {
|
|
369
|
+
throw new Error("\u8FDE\u63A5\u6B63\u5728\u8FDB\u884C\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u8FDE\u63A5\u5B8C\u6210");
|
|
83
370
|
}
|
|
84
|
-
this.
|
|
371
|
+
this.cleanupConnection();
|
|
372
|
+
return this.attemptConnection();
|
|
85
373
|
}
|
|
86
374
|
/**
|
|
87
|
-
*
|
|
375
|
+
* 尝试建立连接
|
|
88
376
|
*/
|
|
89
|
-
|
|
90
|
-
|
|
377
|
+
async attemptConnection() {
|
|
378
|
+
this.connectionState = "connecting" /* CONNECTING */;
|
|
379
|
+
console.debug(
|
|
380
|
+
`[MCP-${this.config.name}] \u6B63\u5728\u8FDE\u63A5 MCP \u670D\u52A1: ${this.config.name}`
|
|
381
|
+
);
|
|
382
|
+
return new Promise((resolve, reject) => {
|
|
383
|
+
this.connectionTimeout = setTimeout(() => {
|
|
384
|
+
const error = new Error(`\u8FDE\u63A5\u8D85\u65F6 (${this.config.timeout || 1e4}ms)`);
|
|
385
|
+
this.handleConnectionError(error);
|
|
386
|
+
reject(error);
|
|
387
|
+
}, this.config.timeout || 1e4);
|
|
388
|
+
try {
|
|
389
|
+
this.client = new Client(
|
|
390
|
+
{
|
|
391
|
+
name: `xiaozhi-${this.config.name}-client`,
|
|
392
|
+
version: "1.0.0"
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
capabilities: {}
|
|
396
|
+
}
|
|
397
|
+
);
|
|
398
|
+
this.transport = TransportFactory.create(this.config);
|
|
399
|
+
this.client.connect(this.transport).then(async () => {
|
|
400
|
+
this.handleConnectionSuccess();
|
|
401
|
+
await this.refreshTools();
|
|
402
|
+
this.callbacks?.onConnected?.({
|
|
403
|
+
serviceName: this.config.name,
|
|
404
|
+
tools: this.getTools(),
|
|
405
|
+
connectionTime: /* @__PURE__ */ new Date()
|
|
406
|
+
});
|
|
407
|
+
resolve();
|
|
408
|
+
}).catch((error) => {
|
|
409
|
+
this.handleConnectionError(error);
|
|
410
|
+
reject(error);
|
|
411
|
+
});
|
|
412
|
+
} catch (error) {
|
|
413
|
+
this.handleConnectionError(error);
|
|
414
|
+
reject(error);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
91
417
|
}
|
|
92
418
|
/**
|
|
93
|
-
*
|
|
419
|
+
* 处理连接成功
|
|
94
420
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
421
|
+
handleConnectionSuccess() {
|
|
422
|
+
if (this.connectionTimeout) {
|
|
423
|
+
clearTimeout(this.connectionTimeout);
|
|
424
|
+
this.connectionTimeout = null;
|
|
425
|
+
}
|
|
426
|
+
this.connectionState = "connected" /* CONNECTED */;
|
|
427
|
+
this.initialized = true;
|
|
428
|
+
console.info(
|
|
429
|
+
`[MCP-${this.config.name}] MCP \u670D\u52A1 ${this.config.name} \u8FDE\u63A5\u5DF2\u5EFA\u7ACB`
|
|
430
|
+
);
|
|
97
431
|
}
|
|
98
432
|
/**
|
|
99
|
-
*
|
|
433
|
+
* 处理连接错误
|
|
100
434
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
435
|
+
handleConnectionError(error) {
|
|
436
|
+
this.connectionState = "disconnected" /* DISCONNECTED */;
|
|
437
|
+
this.initialized = false;
|
|
438
|
+
console.debug(`MCP \u670D\u52A1 ${this.config.name} \u8FDE\u63A5\u9519\u8BEF:`, error.message);
|
|
439
|
+
if (this.connectionTimeout) {
|
|
440
|
+
clearTimeout(this.connectionTimeout);
|
|
441
|
+
this.connectionTimeout = null;
|
|
104
442
|
}
|
|
105
|
-
this.
|
|
106
|
-
this.
|
|
443
|
+
this.cleanupConnection();
|
|
444
|
+
this.callbacks?.onConnectionFailed?.({
|
|
445
|
+
serviceName: this.config.name,
|
|
446
|
+
error,
|
|
447
|
+
attempt: 0
|
|
448
|
+
});
|
|
107
449
|
}
|
|
108
450
|
/**
|
|
109
|
-
*
|
|
451
|
+
* 清理连接资源
|
|
110
452
|
*/
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
453
|
+
cleanupConnection() {
|
|
454
|
+
if (this.client) {
|
|
455
|
+
try {
|
|
456
|
+
this.client.close().catch(() => {
|
|
457
|
+
});
|
|
458
|
+
} catch (error) {
|
|
459
|
+
}
|
|
460
|
+
this.client = null;
|
|
461
|
+
}
|
|
462
|
+
this.transport = null;
|
|
463
|
+
if (this.connectionTimeout) {
|
|
464
|
+
clearTimeout(this.connectionTimeout);
|
|
465
|
+
this.connectionTimeout = null;
|
|
114
466
|
}
|
|
115
|
-
this.
|
|
467
|
+
this.initialized = false;
|
|
116
468
|
}
|
|
117
469
|
/**
|
|
118
|
-
*
|
|
470
|
+
* 刷新工具列表
|
|
119
471
|
*/
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
472
|
+
async refreshTools() {
|
|
473
|
+
if (!this.client) {
|
|
474
|
+
throw new Error("\u5BA2\u6237\u7AEF\u672A\u521D\u59CB\u5316");
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
const toolsResult = await this.client.listTools();
|
|
478
|
+
const tools = toolsResult.tools || [];
|
|
479
|
+
this.tools.clear();
|
|
480
|
+
for (const tool of tools) {
|
|
481
|
+
this.tools.set(tool.name, tool);
|
|
482
|
+
}
|
|
483
|
+
console.debug(
|
|
484
|
+
`${this.config.name} \u670D\u52A1\u52A0\u8F7D\u4E86 ${tools.length} \u4E2A\u5DE5\u5177: ${tools.map((t) => t.name).join(", ")}`
|
|
485
|
+
);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
console.error(
|
|
488
|
+
`${this.config.name} \u83B7\u53D6\u5DE5\u5177\u5217\u8868\u5931\u8D25:`,
|
|
489
|
+
error instanceof Error ? error.message : String(error)
|
|
490
|
+
);
|
|
491
|
+
throw error;
|
|
124
492
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* 断开连接
|
|
496
|
+
*/
|
|
497
|
+
async disconnect() {
|
|
498
|
+
console.info(`\u4E3B\u52A8\u65AD\u5F00 MCP \u670D\u52A1 ${this.config.name} \u8FDE\u63A5`);
|
|
499
|
+
this.cleanupConnection();
|
|
500
|
+
this.connectionState = "disconnected" /* DISCONNECTED */;
|
|
501
|
+
this.callbacks?.onDisconnected?.({
|
|
502
|
+
serviceName: this.config.name,
|
|
503
|
+
reason: "\u624B\u52A8\u65AD\u5F00",
|
|
504
|
+
disconnectionTime: /* @__PURE__ */ new Date()
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* 获取工具列表
|
|
509
|
+
*/
|
|
510
|
+
getTools() {
|
|
511
|
+
return Array.from(this.tools.values());
|
|
128
512
|
}
|
|
129
513
|
/**
|
|
130
514
|
* 调用工具
|
|
131
515
|
*/
|
|
132
|
-
async callTool(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
throw new ToolCallError(
|
|
136
|
-
-32601 /* TOOL_NOT_FOUND */,
|
|
137
|
-
`\u5DE5\u5177\u4E0D\u5B58\u5728: ${toolName}`
|
|
138
|
-
);
|
|
516
|
+
async callTool(name, arguments_) {
|
|
517
|
+
if (!this.client) {
|
|
518
|
+
throw new Error(`\u670D\u52A1 ${this.config.name} \u672A\u8FDE\u63A5`);
|
|
139
519
|
}
|
|
140
|
-
if (!
|
|
141
|
-
throw new
|
|
142
|
-
|
|
143
|
-
|
|
520
|
+
if (!this.tools.has(name)) {
|
|
521
|
+
throw new Error(`\u5DE5\u5177 ${name} \u5728\u670D\u52A1 ${this.config.name} \u4E2D\u4E0D\u5B58\u5728`);
|
|
522
|
+
}
|
|
523
|
+
console.debug(
|
|
524
|
+
`\u8C03\u7528 ${this.config.name} \u670D\u52A1\u7684\u5DE5\u5177 ${name}\uFF0C\u53C2\u6570:`,
|
|
525
|
+
JSON.stringify(arguments_)
|
|
526
|
+
);
|
|
527
|
+
try {
|
|
528
|
+
const result = await this.client.callTool({
|
|
529
|
+
name,
|
|
530
|
+
arguments: arguments_ || {}
|
|
531
|
+
});
|
|
532
|
+
console.debug(
|
|
533
|
+
`\u5DE5\u5177 ${name} \u8C03\u7528\u6210\u529F\uFF0C\u7ED3\u679C:`,
|
|
534
|
+
`${JSON.stringify(result).substring(0, 500)}...`
|
|
535
|
+
);
|
|
536
|
+
return result;
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error(
|
|
539
|
+
`\u5DE5\u5177 ${name} \u8C03\u7528\u5931\u8D25:`,
|
|
540
|
+
error instanceof Error ? error.message : String(error)
|
|
144
541
|
);
|
|
542
|
+
throw error;
|
|
145
543
|
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* 获取服务配置
|
|
547
|
+
*/
|
|
548
|
+
getConfig() {
|
|
549
|
+
return this.config;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* 获取服务状态
|
|
553
|
+
*/
|
|
554
|
+
getStatus() {
|
|
146
555
|
return {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
isError: false
|
|
556
|
+
name: this.config.name,
|
|
557
|
+
connected: this.connectionState === "connected" /* CONNECTED */,
|
|
558
|
+
initialized: this.initialized,
|
|
559
|
+
transportType: this.config.type || "streamable-http" /* STREAMABLE_HTTP */,
|
|
560
|
+
toolCount: this.tools.size,
|
|
561
|
+
connectionState: this.connectionState
|
|
154
562
|
};
|
|
155
563
|
}
|
|
156
564
|
/**
|
|
157
|
-
*
|
|
565
|
+
* 检查是否已连接
|
|
158
566
|
*/
|
|
159
|
-
|
|
160
|
-
this.
|
|
567
|
+
isConnected() {
|
|
568
|
+
return this.connectionState === "connected" /* CONNECTED */ && this.initialized;
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
// src/manager.ts
|
|
573
|
+
import { EventEmitter } from "events";
|
|
574
|
+
var MCPManager = class extends EventEmitter {
|
|
575
|
+
static {
|
|
576
|
+
__name(this, "MCPManager");
|
|
577
|
+
}
|
|
578
|
+
connections = /* @__PURE__ */ new Map();
|
|
579
|
+
configs = /* @__PURE__ */ new Map();
|
|
580
|
+
constructor() {
|
|
581
|
+
super();
|
|
161
582
|
}
|
|
162
583
|
/**
|
|
163
|
-
*
|
|
584
|
+
* 添加 MCP 服务器配置
|
|
585
|
+
* @param name 服务器名称
|
|
586
|
+
* @param config 服务器配置
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* ```typescript
|
|
590
|
+
* // 添加 stdio 服务
|
|
591
|
+
* manager.addServer('calculator', {
|
|
592
|
+
* type: 'stdio',
|
|
593
|
+
* command: 'node',
|
|
594
|
+
* args: ['calculator.js']
|
|
595
|
+
* });
|
|
596
|
+
*
|
|
597
|
+
* // 添加 HTTP 服务
|
|
598
|
+
* manager.addServer('web-search', {
|
|
599
|
+
* type: 'http',
|
|
600
|
+
* url: 'https://api.example.com/mcp',
|
|
601
|
+
* headers: {
|
|
602
|
+
* Authorization: 'Bearer your-api-key'
|
|
603
|
+
* }
|
|
604
|
+
* });
|
|
605
|
+
* ```
|
|
164
606
|
*/
|
|
165
|
-
|
|
166
|
-
this.
|
|
607
|
+
addServer(name, config) {
|
|
608
|
+
if (this.configs.has(name)) {
|
|
609
|
+
throw new Error(`\u670D\u52A1 ${name} \u5DF2\u5B58\u5728`);
|
|
610
|
+
}
|
|
611
|
+
const fullConfig = {
|
|
612
|
+
...config,
|
|
613
|
+
name
|
|
614
|
+
};
|
|
615
|
+
if (config.type) {
|
|
616
|
+
const typeStr = String(config.type);
|
|
617
|
+
if (typeStr === "http") {
|
|
618
|
+
fullConfig.type = "streamable-http" /* STREAMABLE_HTTP */;
|
|
619
|
+
} else if (typeStr === "sse") {
|
|
620
|
+
fullConfig.type = "sse" /* SSE */;
|
|
621
|
+
} else {
|
|
622
|
+
fullConfig.type = config.type;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
this.configs.set(name, fullConfig);
|
|
167
626
|
}
|
|
168
627
|
/**
|
|
169
|
-
*
|
|
628
|
+
* 移除服务器配置
|
|
629
|
+
* @param name 服务器名称
|
|
170
630
|
*/
|
|
171
|
-
|
|
172
|
-
|
|
631
|
+
removeServer(name) {
|
|
632
|
+
return this.configs.delete(name);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* 连接所有已添加的 MCP 服务
|
|
636
|
+
* 所有服务并行连接,单个服务失败不会影响其他服务
|
|
637
|
+
*
|
|
638
|
+
* @example
|
|
639
|
+
* ```typescript
|
|
640
|
+
* await manager.connect();
|
|
641
|
+
* ```
|
|
642
|
+
*/
|
|
643
|
+
async connect() {
|
|
644
|
+
this.emit("connect");
|
|
645
|
+
const promises = Array.from(this.configs.entries()).map(
|
|
646
|
+
async ([name, config]) => {
|
|
647
|
+
try {
|
|
648
|
+
const connection = new MCPConnection(config, {
|
|
649
|
+
onConnected: /* @__PURE__ */ __name((data) => {
|
|
650
|
+
this.emit("connected", {
|
|
651
|
+
serverName: data.serviceName,
|
|
652
|
+
tools: data.tools
|
|
653
|
+
});
|
|
654
|
+
}, "onConnected"),
|
|
655
|
+
onDisconnected: /* @__PURE__ */ __name((data) => {
|
|
656
|
+
this.emit("disconnected", {
|
|
657
|
+
serverName: data.serviceName,
|
|
658
|
+
reason: data.reason
|
|
659
|
+
});
|
|
660
|
+
}, "onDisconnected"),
|
|
661
|
+
onConnectionFailed: /* @__PURE__ */ __name((data) => {
|
|
662
|
+
this.emit("error", {
|
|
663
|
+
serverName: data.serviceName,
|
|
664
|
+
error: data.error
|
|
665
|
+
});
|
|
666
|
+
}, "onConnectionFailed")
|
|
667
|
+
});
|
|
668
|
+
await connection.connect();
|
|
669
|
+
this.connections.set(name, connection);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
this.emit("error", { serverName: name, error });
|
|
672
|
+
throw error;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
);
|
|
676
|
+
await Promise.allSettled(promises);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* 断开所有 MCP 服务连接
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
* ```typescript
|
|
683
|
+
* await manager.disconnect();
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
async disconnect() {
|
|
687
|
+
const promises = Array.from(this.connections.values()).map(
|
|
688
|
+
(conn) => conn.disconnect()
|
|
689
|
+
);
|
|
690
|
+
await Promise.allSettled(promises);
|
|
691
|
+
this.connections.clear();
|
|
692
|
+
this.emit("disconnect");
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* 调用指定服务的工具
|
|
696
|
+
* @param serverName 服务名称
|
|
697
|
+
* @param toolName 工具名称
|
|
698
|
+
* @param args 工具参数
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```typescript
|
|
702
|
+
* const result = await manager.callTool('datetime', 'get_current_time', {
|
|
703
|
+
* format: 'YYYY-MM-DD HH:mm:ss'
|
|
704
|
+
* });
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
707
|
+
async callTool(serverName, toolName, args) {
|
|
708
|
+
const connection = this.connections.get(serverName);
|
|
709
|
+
if (!connection) {
|
|
710
|
+
throw new Error(`\u670D\u52A1 ${serverName} \u4E0D\u5B58\u5728`);
|
|
711
|
+
}
|
|
712
|
+
if (!connection.isConnected()) {
|
|
713
|
+
throw new Error(`\u670D\u52A1 ${serverName} \u672A\u8FDE\u63A5`);
|
|
714
|
+
}
|
|
715
|
+
return connection.callTool(toolName, args);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* 列出所有可用的工具
|
|
719
|
+
* @returns 工具列表,格式为 [{ name, serverName, description, inputSchema }]
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* ```typescript
|
|
723
|
+
* const tools = manager.listTools();
|
|
724
|
+
* console.log('可用工具:', tools.map(t => `${t.serverName}/${t.name}`));
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
727
|
+
listTools() {
|
|
728
|
+
const allTools = [];
|
|
729
|
+
for (const [serverName, connection] of this.connections) {
|
|
730
|
+
if (connection.isConnected()) {
|
|
731
|
+
const tools = connection.getTools();
|
|
732
|
+
for (const tool of tools) {
|
|
733
|
+
allTools.push({
|
|
734
|
+
name: tool.name,
|
|
735
|
+
serverName,
|
|
736
|
+
description: tool.description || "",
|
|
737
|
+
inputSchema: tool.inputSchema
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return allTools;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* 获取服务状态
|
|
746
|
+
* @param serverName 服务名称
|
|
747
|
+
* @returns 服务状态,如果服务不存在则返回 null
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* ```typescript
|
|
751
|
+
* const status = manager.getServerStatus('datetime');
|
|
752
|
+
* if (status) {
|
|
753
|
+
* console.log(`已连接: ${status.connected}, 工具数: ${status.toolCount}`);
|
|
754
|
+
* }
|
|
755
|
+
* ```
|
|
756
|
+
*/
|
|
757
|
+
getServerStatus(serverName) {
|
|
758
|
+
const connection = this.connections.get(serverName);
|
|
759
|
+
if (!connection) {
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
const status = connection.getStatus();
|
|
173
763
|
return {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
availableTools
|
|
764
|
+
connected: status.connected,
|
|
765
|
+
toolCount: status.toolCount
|
|
177
766
|
};
|
|
178
767
|
}
|
|
179
768
|
/**
|
|
180
|
-
*
|
|
769
|
+
* 获取所有服务的状态
|
|
770
|
+
* @returns 所有服务的状态映射
|
|
771
|
+
*
|
|
772
|
+
* @example
|
|
773
|
+
* ```typescript
|
|
774
|
+
* const statuses = manager.getAllServerStatus();
|
|
775
|
+
* console.log(statuses);
|
|
776
|
+
* // {
|
|
777
|
+
* // datetime: { connected: true, toolCount: 3 },
|
|
778
|
+
* // calculator: { connected: true, toolCount: 1 }
|
|
779
|
+
* // }
|
|
780
|
+
* ```
|
|
781
|
+
*/
|
|
782
|
+
getAllServerStatus() {
|
|
783
|
+
const statuses = {};
|
|
784
|
+
for (const [serverName, connection] of this.connections) {
|
|
785
|
+
const status = connection.getStatus();
|
|
786
|
+
statuses[serverName] = {
|
|
787
|
+
connected: status.connected,
|
|
788
|
+
toolCount: status.toolCount
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
return statuses;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* 检查服务是否已连接
|
|
795
|
+
* @param serverName 服务名称
|
|
796
|
+
*
|
|
797
|
+
* @example
|
|
798
|
+
* ```typescript
|
|
799
|
+
* if (manager.isConnected('datetime')) {
|
|
800
|
+
* console.log('datetime 服务已连接');
|
|
801
|
+
* }
|
|
802
|
+
* ```
|
|
803
|
+
*/
|
|
804
|
+
isConnected(serverName) {
|
|
805
|
+
const connection = this.connections.get(serverName);
|
|
806
|
+
return connection ? connection.isConnected() : false;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* 获取已配置的服务列表
|
|
810
|
+
* @returns 服务名称数组
|
|
811
|
+
*
|
|
812
|
+
* @example
|
|
813
|
+
* ```typescript
|
|
814
|
+
* const servers = manager.getServerNames();
|
|
815
|
+
* console.log('已配置的服务:', servers);
|
|
816
|
+
* ```
|
|
181
817
|
*/
|
|
182
|
-
|
|
183
|
-
this.
|
|
184
|
-
|
|
818
|
+
getServerNames() {
|
|
819
|
+
return Array.from(this.configs.keys());
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* 获取已连接的服务列表
|
|
823
|
+
* @returns 已连接的服务名称数组
|
|
824
|
+
*
|
|
825
|
+
* @example
|
|
826
|
+
* ```typescript
|
|
827
|
+
* const connectedServers = manager.getConnectedServerNames();
|
|
828
|
+
* console.log('已连接的服务:', connectedServers);
|
|
829
|
+
* ```
|
|
830
|
+
*/
|
|
831
|
+
getConnectedServerNames() {
|
|
832
|
+
const connected = [];
|
|
833
|
+
for (const [serverName, connection] of this.connections) {
|
|
834
|
+
if (connection.isConnected()) {
|
|
835
|
+
connected.push(serverName);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return connected;
|
|
185
839
|
}
|
|
186
840
|
};
|
|
187
841
|
export {
|
|
188
842
|
ConnectionState,
|
|
189
|
-
|
|
843
|
+
MCPConnection,
|
|
844
|
+
MCPManager,
|
|
845
|
+
MCPManager as MCPServiceManager,
|
|
190
846
|
MCPTransportType,
|
|
191
847
|
ToolCallError,
|
|
192
848
|
ToolCallErrorCode,
|
|
849
|
+
TransportFactory,
|
|
850
|
+
TypeFieldNormalizer,
|
|
193
851
|
ensureToolJSONSchema,
|
|
194
|
-
|
|
852
|
+
inferTransportTypeFromConfig,
|
|
853
|
+
inferTransportTypeFromUrl,
|
|
854
|
+
isValidToolJSONSchema,
|
|
855
|
+
normalizeTypeField,
|
|
856
|
+
validateToolCallParams
|
|
195
857
|
};
|
|
196
858
|
//# sourceMappingURL=index.js.map
|