@xiaozhi-client/endpoint 1.9.7-beta.10
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/LICENSE +21 -0
- package/README.md +612 -0
- package/dist/index.d.ts +551 -0
- package/dist/index.js +801 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/endpoint.ts
|
|
5
|
+
import WebSocket from "ws";
|
|
6
|
+
|
|
7
|
+
// src/types.ts
|
|
8
|
+
var ToolCallErrorCode = /* @__PURE__ */ ((ToolCallErrorCode2) => {
|
|
9
|
+
ToolCallErrorCode2[ToolCallErrorCode2["INVALID_PARAMS"] = -32602] = "INVALID_PARAMS";
|
|
10
|
+
ToolCallErrorCode2[ToolCallErrorCode2["TOOL_NOT_FOUND"] = -32601] = "TOOL_NOT_FOUND";
|
|
11
|
+
ToolCallErrorCode2[ToolCallErrorCode2["SERVICE_UNAVAILABLE"] = -32001] = "SERVICE_UNAVAILABLE";
|
|
12
|
+
ToolCallErrorCode2[ToolCallErrorCode2["TIMEOUT"] = -32002] = "TIMEOUT";
|
|
13
|
+
ToolCallErrorCode2[ToolCallErrorCode2["TOOL_EXECUTION_ERROR"] = -32e3] = "TOOL_EXECUTION_ERROR";
|
|
14
|
+
return ToolCallErrorCode2;
|
|
15
|
+
})(ToolCallErrorCode || {});
|
|
16
|
+
var ToolCallError = class extends Error {
|
|
17
|
+
constructor(code, message, data) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.data = data;
|
|
21
|
+
this.name = "ToolCallError";
|
|
22
|
+
}
|
|
23
|
+
static {
|
|
24
|
+
__name(this, "ToolCallError");
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function ensureToolJSONSchema(schema) {
|
|
28
|
+
if (typeof schema === "object" && schema !== null && "type" in schema && schema.type === "object") {
|
|
29
|
+
return schema;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {},
|
|
34
|
+
required: [],
|
|
35
|
+
additionalProperties: true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
__name(ensureToolJSONSchema, "ensureToolJSONSchema");
|
|
39
|
+
var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
40
|
+
ConnectionState2["DISCONNECTED"] = "disconnected";
|
|
41
|
+
ConnectionState2["CONNECTING"] = "connecting";
|
|
42
|
+
ConnectionState2["CONNECTED"] = "connected";
|
|
43
|
+
ConnectionState2["FAILED"] = "failed";
|
|
44
|
+
return ConnectionState2;
|
|
45
|
+
})(ConnectionState || {});
|
|
46
|
+
|
|
47
|
+
// src/utils.ts
|
|
48
|
+
function sliceEndpoint(endpoint) {
|
|
49
|
+
return `${endpoint.slice(0, 30)}...${endpoint.slice(-10)}`;
|
|
50
|
+
}
|
|
51
|
+
__name(sliceEndpoint, "sliceEndpoint");
|
|
52
|
+
function validateToolCallParams(params) {
|
|
53
|
+
if (!params || typeof params !== "object") {
|
|
54
|
+
throw new Error("\u5DE5\u5177\u8C03\u7528\u53C2\u6570\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
55
|
+
}
|
|
56
|
+
const p = params;
|
|
57
|
+
if (!p.name || typeof p.name !== "string") {
|
|
58
|
+
throw new Error("\u5DE5\u5177\u540D\u79F0\u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
59
|
+
}
|
|
60
|
+
const validated = {
|
|
61
|
+
name: p.name
|
|
62
|
+
};
|
|
63
|
+
if (p.arguments !== void 0) {
|
|
64
|
+
if (typeof p.arguments !== "object" || p.arguments === null) {
|
|
65
|
+
throw new Error("\u5DE5\u5177\u53C2\u6570\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
66
|
+
}
|
|
67
|
+
validated.arguments = p.arguments;
|
|
68
|
+
}
|
|
69
|
+
return validated;
|
|
70
|
+
}
|
|
71
|
+
__name(validateToolCallParams, "validateToolCallParams");
|
|
72
|
+
function isValidEndpointUrl(endpoint) {
|
|
73
|
+
if (!endpoint || typeof endpoint !== "string") {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (!endpoint.startsWith("ws://") && !endpoint.startsWith("wss://")) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
new URL(endpoint);
|
|
81
|
+
return true;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
__name(isValidEndpointUrl, "isValidEndpointUrl");
|
|
87
|
+
function isSafeKey(key) {
|
|
88
|
+
const dangerousKeys = ["__proto__", "constructor", "prototype"];
|
|
89
|
+
if (dangerousKeys.includes(key)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return typeof key === "string" && key.length > 0;
|
|
93
|
+
}
|
|
94
|
+
__name(isSafeKey, "isSafeKey");
|
|
95
|
+
function deepMerge(target, ...sources) {
|
|
96
|
+
if (sources.length === 0) {
|
|
97
|
+
return target;
|
|
98
|
+
}
|
|
99
|
+
const source = sources.shift();
|
|
100
|
+
if (source === void 0) {
|
|
101
|
+
return target;
|
|
102
|
+
}
|
|
103
|
+
const keys = Object.keys(source);
|
|
104
|
+
for (const key of keys) {
|
|
105
|
+
if (!isSafeKey(key)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const sourceValue = source[key];
|
|
109
|
+
const targetValue = target[key];
|
|
110
|
+
if (typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
111
|
+
target[key] = deepMerge(
|
|
112
|
+
targetValue,
|
|
113
|
+
sourceValue
|
|
114
|
+
);
|
|
115
|
+
} else {
|
|
116
|
+
target[key] = sourceValue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return deepMerge(target, ...sources);
|
|
120
|
+
}
|
|
121
|
+
__name(deepMerge, "deepMerge");
|
|
122
|
+
function sleep(ms) {
|
|
123
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
124
|
+
}
|
|
125
|
+
__name(sleep, "sleep");
|
|
126
|
+
function formatErrorMessage(error) {
|
|
127
|
+
if (error instanceof Error) {
|
|
128
|
+
return error.message;
|
|
129
|
+
}
|
|
130
|
+
if (typeof error === "string") {
|
|
131
|
+
return error;
|
|
132
|
+
}
|
|
133
|
+
return String(error);
|
|
134
|
+
}
|
|
135
|
+
__name(formatErrorMessage, "formatErrorMessage");
|
|
136
|
+
|
|
137
|
+
// src/internal-mcp-manager.ts
|
|
138
|
+
import { MCPManager } from "@xiaozhi-client/mcp-core";
|
|
139
|
+
import { convertLegacyToNew } from "@xiaozhi-client/config";
|
|
140
|
+
var InternalMCPManagerAdapter = class {
|
|
141
|
+
constructor(config) {
|
|
142
|
+
this.config = config;
|
|
143
|
+
this.mcpManager = new MCPManager();
|
|
144
|
+
for (const [serviceName, serverConfig] of Object.entries(
|
|
145
|
+
config.mcpServers
|
|
146
|
+
)) {
|
|
147
|
+
const mcpConfig = this.convertToMCPServiceConfig(serviceName, serverConfig);
|
|
148
|
+
this.mcpManager.addServer(serviceName, mcpConfig);
|
|
149
|
+
}
|
|
150
|
+
this.mcpManager.on("connected", (data) => {
|
|
151
|
+
});
|
|
152
|
+
this.mcpManager.on("error", (data) => {
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
static {
|
|
156
|
+
__name(this, "InternalMCPManagerAdapter");
|
|
157
|
+
}
|
|
158
|
+
mcpManager;
|
|
159
|
+
tools = /* @__PURE__ */ new Map();
|
|
160
|
+
isInitialized = false;
|
|
161
|
+
/**
|
|
162
|
+
* 初始化并启动所有 MCP 服务
|
|
163
|
+
*/
|
|
164
|
+
async initialize() {
|
|
165
|
+
if (this.isInitialized) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await this.mcpManager.connect();
|
|
169
|
+
await this.refreshTools();
|
|
170
|
+
this.isInitialized = true;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 获取所有工具列表
|
|
174
|
+
*/
|
|
175
|
+
getAllTools() {
|
|
176
|
+
return Array.from(this.tools.values());
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 调用工具(真实实现)
|
|
180
|
+
*/
|
|
181
|
+
async callTool(toolName, arguments_) {
|
|
182
|
+
const [serviceName, actualToolName] = this.parseToolName(toolName);
|
|
183
|
+
return this.mcpManager.callTool(serviceName, actualToolName, arguments_);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 清理资源
|
|
187
|
+
*/
|
|
188
|
+
async cleanup() {
|
|
189
|
+
await this.mcpManager.disconnect();
|
|
190
|
+
this.tools.clear();
|
|
191
|
+
this.isInitialized = false;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 刷新工具列表
|
|
195
|
+
*/
|
|
196
|
+
async refreshTools() {
|
|
197
|
+
this.tools.clear();
|
|
198
|
+
const mcpTools = this.mcpManager.listTools();
|
|
199
|
+
for (const mcpTool of mcpTools) {
|
|
200
|
+
const enhancedTool = {
|
|
201
|
+
name: `${mcpTool.serverName}__${mcpTool.name}`,
|
|
202
|
+
description: mcpTool.description,
|
|
203
|
+
inputSchema: mcpTool.inputSchema,
|
|
204
|
+
serviceName: mcpTool.serverName,
|
|
205
|
+
originalName: mcpTool.name,
|
|
206
|
+
enabled: true,
|
|
207
|
+
usageCount: 0,
|
|
208
|
+
lastUsedTime: (/* @__PURE__ */ new Date()).toISOString()
|
|
209
|
+
};
|
|
210
|
+
this.tools.set(enhancedTool.name, enhancedTool);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 解析工具名称
|
|
215
|
+
*/
|
|
216
|
+
parseToolName(toolName) {
|
|
217
|
+
const parts = toolName.split("__");
|
|
218
|
+
if (parts.length < 2) {
|
|
219
|
+
throw new Error(`\u65E0\u6548\u7684\u5DE5\u5177\u540D\u79F0\u683C\u5F0F: ${toolName}`);
|
|
220
|
+
}
|
|
221
|
+
const serviceName = parts[0];
|
|
222
|
+
const actualToolName = parts.slice(1).join("__");
|
|
223
|
+
return [serviceName, actualToolName];
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* 将 MCPServerConfig 转换为 MCPServiceConfig
|
|
227
|
+
* 使用统一的配置适配器,确保路径解析逻辑一致
|
|
228
|
+
*/
|
|
229
|
+
convertToMCPServiceConfig(serviceName, config) {
|
|
230
|
+
const converted = convertLegacyToNew(serviceName, config);
|
|
231
|
+
const { name, ...rest } = converted;
|
|
232
|
+
return rest;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/endpoint.ts
|
|
237
|
+
var Endpoint = class {
|
|
238
|
+
static {
|
|
239
|
+
__name(this, "Endpoint");
|
|
240
|
+
}
|
|
241
|
+
endpointUrl;
|
|
242
|
+
ws = null;
|
|
243
|
+
connectionStatus = false;
|
|
244
|
+
serverInitialized = false;
|
|
245
|
+
mcpAdapter;
|
|
246
|
+
// 连接状态管理
|
|
247
|
+
connectionState = "disconnected" /* DISCONNECTED */;
|
|
248
|
+
// 最后一次错误信息
|
|
249
|
+
lastError = null;
|
|
250
|
+
// 连接超时定时器
|
|
251
|
+
connectionTimeout = null;
|
|
252
|
+
// 工具调用超时配置
|
|
253
|
+
toolCallTimeout = 3e4;
|
|
254
|
+
reconnectDelay;
|
|
255
|
+
// 重连延迟(毫秒)
|
|
256
|
+
/**
|
|
257
|
+
* 构造函数
|
|
258
|
+
*
|
|
259
|
+
* @param endpointUrl - 小智接入点 URL
|
|
260
|
+
* @param config - Endpoint 配置(包含 mcpServers)
|
|
261
|
+
*/
|
|
262
|
+
constructor(endpointUrl, config) {
|
|
263
|
+
this.endpointUrl = endpointUrl;
|
|
264
|
+
this.reconnectDelay = config.reconnectDelay ?? 2e3;
|
|
265
|
+
this.mcpAdapter = new InternalMCPManagerAdapter(config);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* 获取 Endpoint URL
|
|
269
|
+
*/
|
|
270
|
+
getUrl() {
|
|
271
|
+
return this.endpointUrl;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 获取当前所有工具列表
|
|
275
|
+
*/
|
|
276
|
+
getTools() {
|
|
277
|
+
try {
|
|
278
|
+
const allTools = this.mcpAdapter.getAllTools();
|
|
279
|
+
return allTools.map((toolInfo) => ({
|
|
280
|
+
name: toolInfo.name,
|
|
281
|
+
description: toolInfo.description,
|
|
282
|
+
inputSchema: ensureToolJSONSchema(toolInfo.inputSchema)
|
|
283
|
+
}));
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 连接小智接入点
|
|
290
|
+
*/
|
|
291
|
+
async connect() {
|
|
292
|
+
await this.mcpAdapter.initialize();
|
|
293
|
+
if (this.connectionState === "connecting" /* CONNECTING */) {
|
|
294
|
+
throw new Error("\u8FDE\u63A5\u6B63\u5728\u8FDB\u884C\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u8FDE\u63A5\u5B8C\u6210");
|
|
295
|
+
}
|
|
296
|
+
this.cleanupConnection();
|
|
297
|
+
return this.attemptConnection();
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 尝试建立连接
|
|
301
|
+
*/
|
|
302
|
+
async attemptConnection() {
|
|
303
|
+
this.connectionState = "connecting" /* CONNECTING */;
|
|
304
|
+
return new Promise((resolve, reject) => {
|
|
305
|
+
this.connectionTimeout = setTimeout(() => {
|
|
306
|
+
const error = new Error("\u8FDE\u63A5\u8D85\u65F6 (10000ms)");
|
|
307
|
+
this.handleConnectionError(error);
|
|
308
|
+
reject(error);
|
|
309
|
+
}, 1e4);
|
|
310
|
+
this.ws = new WebSocket(this.endpointUrl);
|
|
311
|
+
this.ws.on("open", () => {
|
|
312
|
+
this.handleConnectionSuccess();
|
|
313
|
+
resolve();
|
|
314
|
+
});
|
|
315
|
+
this.ws.on("message", (data) => {
|
|
316
|
+
try {
|
|
317
|
+
const message = JSON.parse(data.toString());
|
|
318
|
+
this.handleMessage(message);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
this.ws.on("close", (code, reason) => {
|
|
323
|
+
this.handleConnectionClose(code, reason.toString());
|
|
324
|
+
});
|
|
325
|
+
this.ws.on("error", (error) => {
|
|
326
|
+
this.handleConnectionError(error);
|
|
327
|
+
reject(error);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* 处理连接成功
|
|
333
|
+
*/
|
|
334
|
+
handleConnectionSuccess() {
|
|
335
|
+
if (this.connectionTimeout) {
|
|
336
|
+
clearTimeout(this.connectionTimeout);
|
|
337
|
+
this.connectionTimeout = null;
|
|
338
|
+
}
|
|
339
|
+
this.connectionStatus = true;
|
|
340
|
+
this.connectionState = "connected" /* CONNECTED */;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* 处理连接错误
|
|
344
|
+
*/
|
|
345
|
+
handleConnectionError(error) {
|
|
346
|
+
this.lastError = error.message;
|
|
347
|
+
if (this.connectionTimeout) {
|
|
348
|
+
clearTimeout(this.connectionTimeout);
|
|
349
|
+
this.connectionTimeout = null;
|
|
350
|
+
}
|
|
351
|
+
this.cleanupConnection();
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* 处理连接关闭
|
|
355
|
+
*/
|
|
356
|
+
handleConnectionClose(code, reason) {
|
|
357
|
+
this.connectionStatus = false;
|
|
358
|
+
this.serverInitialized = false;
|
|
359
|
+
this.connectionState = "disconnected" /* DISCONNECTED */;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 清理连接资源
|
|
363
|
+
*/
|
|
364
|
+
cleanupConnection() {
|
|
365
|
+
if (this.ws) {
|
|
366
|
+
this.ws.removeAllListeners();
|
|
367
|
+
try {
|
|
368
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
369
|
+
this.ws.close(1e3, "Cleaning up connection");
|
|
370
|
+
} else if (this.ws.readyState === WebSocket.CONNECTING) {
|
|
371
|
+
this.ws.terminate();
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
}
|
|
375
|
+
this.ws = null;
|
|
376
|
+
}
|
|
377
|
+
if (this.connectionTimeout) {
|
|
378
|
+
clearTimeout(this.connectionTimeout);
|
|
379
|
+
this.connectionTimeout = null;
|
|
380
|
+
}
|
|
381
|
+
this.connectionStatus = false;
|
|
382
|
+
this.serverInitialized = false;
|
|
383
|
+
this.connectionState = "disconnected" /* DISCONNECTED */;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* 处理 MCP 消息
|
|
387
|
+
*/
|
|
388
|
+
handleMessage(message) {
|
|
389
|
+
if (!message.method) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
switch (message.method) {
|
|
393
|
+
case "initialize":
|
|
394
|
+
case "notifications/initialized":
|
|
395
|
+
this.sendResponse(message.id, {
|
|
396
|
+
protocolVersion: "2024-11-05",
|
|
397
|
+
capabilities: {
|
|
398
|
+
tools: { listChanged: true },
|
|
399
|
+
logging: {}
|
|
400
|
+
},
|
|
401
|
+
serverInfo: {
|
|
402
|
+
name: "xiaozhi-mcp-server",
|
|
403
|
+
version: "1.0.0"
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
this.serverInitialized = true;
|
|
407
|
+
break;
|
|
408
|
+
case "tools/list": {
|
|
409
|
+
const toolsList = this.getTools();
|
|
410
|
+
this.sendResponse(message.id, { tools: toolsList });
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case "tools/call": {
|
|
414
|
+
this.handleToolCall(message).catch((error) => {
|
|
415
|
+
});
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
case "ping":
|
|
419
|
+
this.sendResponse(message.id, {});
|
|
420
|
+
break;
|
|
421
|
+
default:
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* 发送响应消息
|
|
426
|
+
*/
|
|
427
|
+
sendResponse(id, result) {
|
|
428
|
+
if (this.connectionStatus && this.ws?.readyState === WebSocket.OPEN) {
|
|
429
|
+
const response = {
|
|
430
|
+
jsonrpc: "2.0",
|
|
431
|
+
id,
|
|
432
|
+
result
|
|
433
|
+
};
|
|
434
|
+
try {
|
|
435
|
+
this.ws.send(JSON.stringify(response));
|
|
436
|
+
} catch (error) {
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* 获取服务器状态
|
|
443
|
+
*/
|
|
444
|
+
getStatus() {
|
|
445
|
+
const availableTools = this.mcpAdapter.getAllTools().length;
|
|
446
|
+
return {
|
|
447
|
+
connected: this.connectionStatus,
|
|
448
|
+
initialized: this.serverInitialized,
|
|
449
|
+
url: this.endpointUrl,
|
|
450
|
+
availableTools,
|
|
451
|
+
connectionState: this.connectionState,
|
|
452
|
+
lastError: this.lastError
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* 检查连接状态
|
|
457
|
+
*/
|
|
458
|
+
isConnected() {
|
|
459
|
+
return this.connectionStatus;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* 主动断开小智连接
|
|
463
|
+
*/
|
|
464
|
+
async disconnect() {
|
|
465
|
+
await this.mcpAdapter.cleanup();
|
|
466
|
+
this.cleanupConnection();
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* 重连小智接入点
|
|
470
|
+
*/
|
|
471
|
+
async reconnect() {
|
|
472
|
+
this.disconnect();
|
|
473
|
+
await new Promise((resolve) => setTimeout(resolve, this.reconnectDelay));
|
|
474
|
+
await this.connect();
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* 处理工具调用请求
|
|
478
|
+
*/
|
|
479
|
+
async handleToolCall(request) {
|
|
480
|
+
if (request.id === void 0 || request.id === null) {
|
|
481
|
+
throw new ToolCallError(
|
|
482
|
+
-32602 /* INVALID_PARAMS */,
|
|
483
|
+
"\u8BF7\u6C42 ID \u4E0D\u80FD\u4E3A\u7A7A"
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
const requestId = request.id;
|
|
487
|
+
const startTime = Date.now();
|
|
488
|
+
try {
|
|
489
|
+
const params = validateToolCallParams(request.params);
|
|
490
|
+
const result = await this.executeToolWithTimeout(
|
|
491
|
+
params.name,
|
|
492
|
+
params.arguments || {},
|
|
493
|
+
this.toolCallTimeout
|
|
494
|
+
);
|
|
495
|
+
this.sendResponse(requestId, {
|
|
496
|
+
content: result.content || [
|
|
497
|
+
{ type: "text", text: JSON.stringify(result) }
|
|
498
|
+
],
|
|
499
|
+
isError: result.isError || false
|
|
500
|
+
});
|
|
501
|
+
} catch (error) {
|
|
502
|
+
this.handleToolCallError(error, requestId, Date.now() - startTime);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* 带超时控制的工具执行
|
|
507
|
+
*/
|
|
508
|
+
async executeToolWithTimeout(toolName, arguments_, timeoutMs = 3e4) {
|
|
509
|
+
return new Promise((resolve, reject) => {
|
|
510
|
+
const timeoutId = setTimeout(() => {
|
|
511
|
+
reject(
|
|
512
|
+
new ToolCallError(
|
|
513
|
+
-32002 /* TIMEOUT */,
|
|
514
|
+
`\u5DE5\u5177\u8C03\u7528\u8D85\u65F6 (${timeoutMs}ms): ${toolName}`
|
|
515
|
+
)
|
|
516
|
+
);
|
|
517
|
+
}, timeoutMs);
|
|
518
|
+
this.mcpAdapter.callTool(toolName, arguments_).then((result) => {
|
|
519
|
+
clearTimeout(timeoutId);
|
|
520
|
+
resolve(result);
|
|
521
|
+
}).catch((error) => {
|
|
522
|
+
clearTimeout(timeoutId);
|
|
523
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
524
|
+
if (errorMessage.includes("\u672A\u627E\u5230\u5DE5\u5177")) {
|
|
525
|
+
reject(
|
|
526
|
+
new ToolCallError(
|
|
527
|
+
-32601 /* TOOL_NOT_FOUND */,
|
|
528
|
+
`\u5DE5\u5177\u4E0D\u5B58\u5728: ${toolName}`
|
|
529
|
+
)
|
|
530
|
+
);
|
|
531
|
+
} else {
|
|
532
|
+
reject(
|
|
533
|
+
new ToolCallError(
|
|
534
|
+
-32e3 /* TOOL_EXECUTION_ERROR */,
|
|
535
|
+
`\u5DE5\u5177\u6267\u884C\u5931\u8D25: ${errorMessage}`
|
|
536
|
+
)
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* 处理工具调用错误
|
|
544
|
+
*/
|
|
545
|
+
handleToolCallError(error, requestId, duration) {
|
|
546
|
+
let errorResponse;
|
|
547
|
+
if (error instanceof ToolCallError) {
|
|
548
|
+
errorResponse = {
|
|
549
|
+
code: error.code,
|
|
550
|
+
message: error.message,
|
|
551
|
+
data: error.data
|
|
552
|
+
};
|
|
553
|
+
} else {
|
|
554
|
+
const errorMessage = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
|
|
555
|
+
errorResponse = {
|
|
556
|
+
code: -32e3 /* TOOL_EXECUTION_ERROR */,
|
|
557
|
+
message: errorMessage,
|
|
558
|
+
data: { originalError: String(error) || "null" }
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
this.sendErrorResponse(requestId, errorResponse);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* 发送错误响应
|
|
565
|
+
*/
|
|
566
|
+
sendErrorResponse(id, error) {
|
|
567
|
+
if (this.connectionStatus && this.ws?.readyState === WebSocket.OPEN) {
|
|
568
|
+
const response = {
|
|
569
|
+
jsonrpc: "2.0",
|
|
570
|
+
id,
|
|
571
|
+
error
|
|
572
|
+
};
|
|
573
|
+
this.ws.send(JSON.stringify(response));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// src/manager.ts
|
|
579
|
+
import { EventEmitter } from "events";
|
|
580
|
+
var EndpointManager = class extends EventEmitter {
|
|
581
|
+
/**
|
|
582
|
+
* 构造函数
|
|
583
|
+
*
|
|
584
|
+
* @param config - 可选的配置
|
|
585
|
+
*/
|
|
586
|
+
constructor(config) {
|
|
587
|
+
super();
|
|
588
|
+
this.config = config;
|
|
589
|
+
}
|
|
590
|
+
static {
|
|
591
|
+
__name(this, "EndpointManager");
|
|
592
|
+
}
|
|
593
|
+
endpoints = /* @__PURE__ */ new Map();
|
|
594
|
+
connectionStates = /* @__PURE__ */ new Map();
|
|
595
|
+
/**
|
|
596
|
+
* 添加 Endpoint 实例
|
|
597
|
+
*
|
|
598
|
+
* @param endpoint - Endpoint 实例
|
|
599
|
+
*/
|
|
600
|
+
addEndpoint(endpoint) {
|
|
601
|
+
const url = endpoint.getUrl();
|
|
602
|
+
if (this.endpoints.has(url)) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
this.endpoints.set(url, endpoint);
|
|
606
|
+
this.connectionStates.set(url, {
|
|
607
|
+
endpoint: url,
|
|
608
|
+
connected: false,
|
|
609
|
+
initialized: false
|
|
610
|
+
});
|
|
611
|
+
this.emit("endpointAdded", { endpoint: url });
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* 移除 Endpoint 实例
|
|
615
|
+
*
|
|
616
|
+
* @param endpoint - Endpoint 实例
|
|
617
|
+
*/
|
|
618
|
+
removeEndpoint(endpoint) {
|
|
619
|
+
const url = endpoint.getUrl();
|
|
620
|
+
if (!this.endpoints.has(url)) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
endpoint.disconnect();
|
|
624
|
+
this.endpoints.delete(url);
|
|
625
|
+
this.connectionStates.delete(url);
|
|
626
|
+
this.emit("endpointRemoved", { endpoint: url });
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* 连接所有 Endpoint
|
|
630
|
+
*/
|
|
631
|
+
async connect() {
|
|
632
|
+
const promises = [];
|
|
633
|
+
for (const [url, endpoint] of this.endpoints) {
|
|
634
|
+
promises.push(
|
|
635
|
+
this.connectSingleEndpoint(url, endpoint).catch((error) => {
|
|
636
|
+
const status = this.connectionStates.get(url);
|
|
637
|
+
if (status) {
|
|
638
|
+
status.connected = false;
|
|
639
|
+
status.initialized = false;
|
|
640
|
+
status.lastError = error instanceof Error ? error.message : String(error);
|
|
641
|
+
}
|
|
642
|
+
})
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
await Promise.allSettled(promises);
|
|
646
|
+
const connectedCount = Array.from(this.connectionStates.values()).filter(
|
|
647
|
+
(s) => s.connected
|
|
648
|
+
).length;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* 断开所有连接
|
|
652
|
+
*/
|
|
653
|
+
async disconnect() {
|
|
654
|
+
const promises = [];
|
|
655
|
+
for (const endpoint of this.endpoints.values()) {
|
|
656
|
+
promises.push(
|
|
657
|
+
Promise.resolve().then(() => {
|
|
658
|
+
endpoint.disconnect();
|
|
659
|
+
})
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
await Promise.allSettled(promises);
|
|
663
|
+
for (const status of this.connectionStates.values()) {
|
|
664
|
+
status.connected = false;
|
|
665
|
+
status.initialized = false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* 获取所有 Endpoint URL
|
|
670
|
+
*/
|
|
671
|
+
getEndpoints() {
|
|
672
|
+
return Array.from(this.endpoints.keys());
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* 获取指定 Endpoint 实例
|
|
676
|
+
*
|
|
677
|
+
* @param url - Endpoint URL
|
|
678
|
+
*/
|
|
679
|
+
getEndpoint(url) {
|
|
680
|
+
return this.endpoints.get(url);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* 获取所有连接状态
|
|
684
|
+
*/
|
|
685
|
+
getConnectionStatus() {
|
|
686
|
+
return Array.from(this.connectionStates.values());
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* 检查是否有任何连接处于连接状态
|
|
690
|
+
*/
|
|
691
|
+
isAnyConnected() {
|
|
692
|
+
for (const status of this.connectionStates.values()) {
|
|
693
|
+
if (status.connected) {
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* 检查指定端点是否已连接
|
|
701
|
+
*
|
|
702
|
+
* @param url - 端点 URL
|
|
703
|
+
*/
|
|
704
|
+
isEndpointConnected(url) {
|
|
705
|
+
const status = this.connectionStates.get(url);
|
|
706
|
+
return status?.connected ?? false;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* 获取指定端点的状态
|
|
710
|
+
*
|
|
711
|
+
* @param url - 端点 URL
|
|
712
|
+
*/
|
|
713
|
+
getEndpointStatus(url) {
|
|
714
|
+
return this.connectionStates.get(url);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* 重连所有端点
|
|
718
|
+
*/
|
|
719
|
+
async reconnectAll() {
|
|
720
|
+
const promises = [];
|
|
721
|
+
for (const [url, endpoint] of this.endpoints) {
|
|
722
|
+
promises.push(
|
|
723
|
+
this.reconnectSingleEndpoint(url, endpoint).catch((error) => {
|
|
724
|
+
})
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
await Promise.allSettled(promises);
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* 重连指定的端点
|
|
731
|
+
*
|
|
732
|
+
* @param url - 要重连的端点 URL
|
|
733
|
+
*/
|
|
734
|
+
async reconnectEndpoint(url) {
|
|
735
|
+
const endpoint = this.endpoints.get(url);
|
|
736
|
+
if (!endpoint) {
|
|
737
|
+
throw new Error(`\u63A5\u5165\u70B9\u4E0D\u5B58\u5728: ${sliceEndpoint(url)}`);
|
|
738
|
+
}
|
|
739
|
+
await this.reconnectSingleEndpoint(url, endpoint);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* 清除所有端点
|
|
743
|
+
*/
|
|
744
|
+
async clearEndpoints() {
|
|
745
|
+
await this.disconnect();
|
|
746
|
+
this.endpoints.clear();
|
|
747
|
+
this.connectionStates.clear();
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* 清理资源
|
|
751
|
+
*/
|
|
752
|
+
async cleanup() {
|
|
753
|
+
await this.clearEndpoints();
|
|
754
|
+
}
|
|
755
|
+
// ==================== 私有方法 ====================
|
|
756
|
+
/**
|
|
757
|
+
* 连接单个端点
|
|
758
|
+
*/
|
|
759
|
+
async connectSingleEndpoint(url, endpoint) {
|
|
760
|
+
const status = this.connectionStates.get(url);
|
|
761
|
+
if (!status) {
|
|
762
|
+
throw new Error(`\u7AEF\u70B9\u72B6\u6001\u4E0D\u5B58\u5728: ${sliceEndpoint(url)}`);
|
|
763
|
+
}
|
|
764
|
+
status.connected = false;
|
|
765
|
+
status.initialized = false;
|
|
766
|
+
await endpoint.connect();
|
|
767
|
+
status.connected = true;
|
|
768
|
+
status.initialized = true;
|
|
769
|
+
status.lastConnected = /* @__PURE__ */ new Date();
|
|
770
|
+
status.lastError = void 0;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* 重连单个端点
|
|
774
|
+
*/
|
|
775
|
+
async reconnectSingleEndpoint(url, endpoint) {
|
|
776
|
+
const status = this.connectionStates.get(url);
|
|
777
|
+
if (!status) {
|
|
778
|
+
throw new Error(`\u7AEF\u70B9\u72B6\u6001\u4E0D\u5B58\u5728: ${sliceEndpoint(url)}`);
|
|
779
|
+
}
|
|
780
|
+
await endpoint.reconnect();
|
|
781
|
+
status.connected = true;
|
|
782
|
+
status.initialized = true;
|
|
783
|
+
status.lastConnected = /* @__PURE__ */ new Date();
|
|
784
|
+
status.lastError = void 0;
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
export {
|
|
788
|
+
ConnectionState,
|
|
789
|
+
Endpoint,
|
|
790
|
+
EndpointManager,
|
|
791
|
+
ToolCallError,
|
|
792
|
+
ToolCallErrorCode,
|
|
793
|
+
deepMerge,
|
|
794
|
+
ensureToolJSONSchema,
|
|
795
|
+
formatErrorMessage,
|
|
796
|
+
isValidEndpointUrl,
|
|
797
|
+
sleep,
|
|
798
|
+
sliceEndpoint,
|
|
799
|
+
validateToolCallParams
|
|
800
|
+
};
|
|
801
|
+
//# sourceMappingURL=index.js.map
|