@xiaozhi-client/endpoint 1.9.7-beta.3
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 +542 -0
- package/dist/index.js +846 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,846 @@
|
|
|
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 deepMerge(target, ...sources) {
|
|
88
|
+
if (sources.length === 0) {
|
|
89
|
+
return target;
|
|
90
|
+
}
|
|
91
|
+
const source = sources.shift();
|
|
92
|
+
if (source === void 0) {
|
|
93
|
+
return target;
|
|
94
|
+
}
|
|
95
|
+
for (const key in source) {
|
|
96
|
+
if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key]) && typeof target[key] === "object" && target[key] !== null && !Array.isArray(target[key])) {
|
|
97
|
+
target[key] = deepMerge(
|
|
98
|
+
target[key],
|
|
99
|
+
source[key]
|
|
100
|
+
);
|
|
101
|
+
} else {
|
|
102
|
+
target[key] = source[key];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return deepMerge(target, ...sources);
|
|
106
|
+
}
|
|
107
|
+
__name(deepMerge, "deepMerge");
|
|
108
|
+
function sleep(ms) {
|
|
109
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
110
|
+
}
|
|
111
|
+
__name(sleep, "sleep");
|
|
112
|
+
function formatErrorMessage(error) {
|
|
113
|
+
if (error instanceof Error) {
|
|
114
|
+
return error.message;
|
|
115
|
+
}
|
|
116
|
+
if (typeof error === "string") {
|
|
117
|
+
return error;
|
|
118
|
+
}
|
|
119
|
+
return String(error);
|
|
120
|
+
}
|
|
121
|
+
__name(formatErrorMessage, "formatErrorMessage");
|
|
122
|
+
|
|
123
|
+
// src/internal-mcp-manager.ts
|
|
124
|
+
var InternalMCPManagerAdapter = class {
|
|
125
|
+
constructor(config) {
|
|
126
|
+
this.config = config;
|
|
127
|
+
}
|
|
128
|
+
static {
|
|
129
|
+
__name(this, "InternalMCPManagerAdapter");
|
|
130
|
+
}
|
|
131
|
+
tools = /* @__PURE__ */ new Map();
|
|
132
|
+
isInitialized = false;
|
|
133
|
+
/**
|
|
134
|
+
* 初始化并启动所有 MCP 服务
|
|
135
|
+
*/
|
|
136
|
+
async initialize() {
|
|
137
|
+
if (this.isInitialized) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
for (const [serviceName, serverConfig] of Object.entries(
|
|
141
|
+
this.config.mcpServers
|
|
142
|
+
)) {
|
|
143
|
+
await this._loadServiceTools(serviceName, serverConfig);
|
|
144
|
+
}
|
|
145
|
+
this.isInitialized = true;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 获取所有工具列表
|
|
149
|
+
*/
|
|
150
|
+
getAllTools() {
|
|
151
|
+
return Array.from(this.tools.values());
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 调用工具
|
|
155
|
+
*/
|
|
156
|
+
async callTool(toolName, arguments_) {
|
|
157
|
+
const tool = this.tools.get(toolName);
|
|
158
|
+
if (!tool) {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: `\u5DE5\u5177\u4E0D\u5B58\u5728: ${toolName}`
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
isError: true
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: `\u5DE5\u5177 ${toolName} \u8C03\u7528\u6210\u529F\uFF08\u5360\u4F4D\u5B9E\u73B0\uFF09`
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
isError: false
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 清理资源
|
|
181
|
+
*/
|
|
182
|
+
async cleanup() {
|
|
183
|
+
this.tools.clear();
|
|
184
|
+
this.isInitialized = false;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 加载服务的工具列表
|
|
188
|
+
* 这是一个简化实现,实际应该连接到 MCP 服务获取工具列表
|
|
189
|
+
*/
|
|
190
|
+
async _loadServiceTools(serviceName, serverConfig) {
|
|
191
|
+
const toolName = `${serviceName}__tool`;
|
|
192
|
+
this.tools.set(toolName, {
|
|
193
|
+
name: toolName,
|
|
194
|
+
description: `\u6765\u81EA ${serviceName} \u7684\u5DE5\u5177`,
|
|
195
|
+
inputSchema: {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {}
|
|
198
|
+
},
|
|
199
|
+
serviceName,
|
|
200
|
+
originalName: "tool",
|
|
201
|
+
enabled: true,
|
|
202
|
+
usageCount: 0,
|
|
203
|
+
lastUsedTime: (/* @__PURE__ */ new Date()).toISOString()
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// src/endpoint.ts
|
|
209
|
+
var Endpoint = class {
|
|
210
|
+
static {
|
|
211
|
+
__name(this, "Endpoint");
|
|
212
|
+
}
|
|
213
|
+
endpointUrl;
|
|
214
|
+
ws = null;
|
|
215
|
+
connectionStatus = false;
|
|
216
|
+
serverInitialized = false;
|
|
217
|
+
mcpAdapter;
|
|
218
|
+
// 连接状态管理
|
|
219
|
+
connectionState = "disconnected" /* DISCONNECTED */;
|
|
220
|
+
// 最后一次错误信息
|
|
221
|
+
lastError = null;
|
|
222
|
+
// 连接超时定时器
|
|
223
|
+
connectionTimeout = null;
|
|
224
|
+
// 工具调用超时配置
|
|
225
|
+
toolCallTimeout = 3e4;
|
|
226
|
+
reconnectDelay;
|
|
227
|
+
// 重连延迟(毫秒)
|
|
228
|
+
/**
|
|
229
|
+
* 构造函数
|
|
230
|
+
*
|
|
231
|
+
* @param endpointUrl - 小智接入点 URL
|
|
232
|
+
* @param config - Endpoint 配置(包含 mcpServers)
|
|
233
|
+
*/
|
|
234
|
+
constructor(endpointUrl, config) {
|
|
235
|
+
this.endpointUrl = endpointUrl;
|
|
236
|
+
this.reconnectDelay = config.reconnectDelay ?? 2e3;
|
|
237
|
+
this.mcpAdapter = new InternalMCPManagerAdapter(config);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 获取 Endpoint URL
|
|
241
|
+
*/
|
|
242
|
+
getUrl() {
|
|
243
|
+
return this.endpointUrl;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 获取当前所有工具列表
|
|
247
|
+
*/
|
|
248
|
+
getTools() {
|
|
249
|
+
try {
|
|
250
|
+
const allTools = this.mcpAdapter.getAllTools();
|
|
251
|
+
return allTools.map((toolInfo) => ({
|
|
252
|
+
name: toolInfo.name,
|
|
253
|
+
description: toolInfo.description,
|
|
254
|
+
inputSchema: ensureToolJSONSchema(toolInfo.inputSchema)
|
|
255
|
+
}));
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error(
|
|
258
|
+
`\u83B7\u53D6\u5DE5\u5177\u5217\u8868\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`
|
|
259
|
+
);
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 连接小智接入点
|
|
265
|
+
*/
|
|
266
|
+
async connect() {
|
|
267
|
+
await this.mcpAdapter.initialize();
|
|
268
|
+
if (this.connectionState === "connecting" /* CONNECTING */) {
|
|
269
|
+
throw new Error("\u8FDE\u63A5\u6B63\u5728\u8FDB\u884C\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u8FDE\u63A5\u5B8C\u6210");
|
|
270
|
+
}
|
|
271
|
+
this.cleanupConnection();
|
|
272
|
+
return this.attemptConnection();
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 尝试建立连接
|
|
276
|
+
*/
|
|
277
|
+
async attemptConnection() {
|
|
278
|
+
this.connectionState = "connecting" /* CONNECTING */;
|
|
279
|
+
console.debug(`\u6B63\u5728\u8FDE\u63A5\u5C0F\u667A\u63A5\u5165\u70B9: ${sliceEndpoint(this.endpointUrl)}`);
|
|
280
|
+
return new Promise((resolve, reject) => {
|
|
281
|
+
this.connectionTimeout = setTimeout(() => {
|
|
282
|
+
const error = new Error("\u8FDE\u63A5\u8D85\u65F6 (10000ms)");
|
|
283
|
+
this.handleConnectionError(error);
|
|
284
|
+
reject(error);
|
|
285
|
+
}, 1e4);
|
|
286
|
+
this.ws = new WebSocket(this.endpointUrl);
|
|
287
|
+
this.ws.on("open", () => {
|
|
288
|
+
this.handleConnectionSuccess();
|
|
289
|
+
resolve();
|
|
290
|
+
});
|
|
291
|
+
this.ws.on("message", (data) => {
|
|
292
|
+
try {
|
|
293
|
+
const message = JSON.parse(data.toString());
|
|
294
|
+
this.handleMessage(message);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error("MCP \u6D88\u606F\u89E3\u6790\u9519\u8BEF:", error);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
this.ws.on("close", (code, reason) => {
|
|
300
|
+
this.handleConnectionClose(code, reason.toString());
|
|
301
|
+
});
|
|
302
|
+
this.ws.on("error", (error) => {
|
|
303
|
+
this.handleConnectionError(error);
|
|
304
|
+
reject(error);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 处理连接成功
|
|
310
|
+
*/
|
|
311
|
+
handleConnectionSuccess() {
|
|
312
|
+
if (this.connectionTimeout) {
|
|
313
|
+
clearTimeout(this.connectionTimeout);
|
|
314
|
+
this.connectionTimeout = null;
|
|
315
|
+
}
|
|
316
|
+
this.connectionStatus = true;
|
|
317
|
+
this.connectionState = "connected" /* CONNECTED */;
|
|
318
|
+
console.debug("MCP WebSocket \u8FDE\u63A5\u5DF2\u5EFA\u7ACB");
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* 处理连接错误
|
|
322
|
+
*/
|
|
323
|
+
handleConnectionError(error) {
|
|
324
|
+
this.lastError = error.message;
|
|
325
|
+
if (this.connectionTimeout) {
|
|
326
|
+
clearTimeout(this.connectionTimeout);
|
|
327
|
+
this.connectionTimeout = null;
|
|
328
|
+
}
|
|
329
|
+
console.error("MCP WebSocket \u9519\u8BEF:", error.message);
|
|
330
|
+
this.cleanupConnection();
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* 处理连接关闭
|
|
334
|
+
*/
|
|
335
|
+
handleConnectionClose(code, reason) {
|
|
336
|
+
this.connectionStatus = false;
|
|
337
|
+
this.serverInitialized = false;
|
|
338
|
+
this.connectionState = "disconnected" /* DISCONNECTED */;
|
|
339
|
+
console.info(`\u5C0F\u667A\u8FDE\u63A5\u5DF2\u5173\u95ED (\u4EE3\u7801: ${code}, \u539F\u56E0: ${reason})`);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* 清理连接资源
|
|
343
|
+
*/
|
|
344
|
+
cleanupConnection() {
|
|
345
|
+
if (this.ws) {
|
|
346
|
+
this.ws.removeAllListeners();
|
|
347
|
+
try {
|
|
348
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
349
|
+
this.ws.close(1e3, "Cleaning up connection");
|
|
350
|
+
} else if (this.ws.readyState === WebSocket.CONNECTING) {
|
|
351
|
+
this.ws.terminate();
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.debug("WebSocket \u5173\u95ED\u65F6\u51FA\u73B0\u9519\u8BEF\uFF08\u5DF2\u5FFD\u7565\uFF09:", error);
|
|
355
|
+
}
|
|
356
|
+
this.ws = null;
|
|
357
|
+
}
|
|
358
|
+
if (this.connectionTimeout) {
|
|
359
|
+
clearTimeout(this.connectionTimeout);
|
|
360
|
+
this.connectionTimeout = null;
|
|
361
|
+
}
|
|
362
|
+
this.connectionStatus = false;
|
|
363
|
+
this.serverInitialized = false;
|
|
364
|
+
this.connectionState = "disconnected" /* DISCONNECTED */;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* 处理 MCP 消息
|
|
368
|
+
*/
|
|
369
|
+
handleMessage(message) {
|
|
370
|
+
console.debug("\u6536\u5230 MCP \u6D88\u606F:", JSON.stringify(message, null, 2));
|
|
371
|
+
if (!message.method) {
|
|
372
|
+
console.debug("\u6536\u5230\u6CA1\u6709 method \u5B57\u6BB5\u7684\u6D88\u606F\uFF0C\u5FFD\u7565");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
switch (message.method) {
|
|
376
|
+
case "initialize":
|
|
377
|
+
case "notifications/initialized":
|
|
378
|
+
this.sendResponse(message.id, {
|
|
379
|
+
protocolVersion: "2024-11-05",
|
|
380
|
+
capabilities: {
|
|
381
|
+
tools: { listChanged: true },
|
|
382
|
+
logging: {}
|
|
383
|
+
},
|
|
384
|
+
serverInfo: {
|
|
385
|
+
name: "xiaozhi-mcp-server",
|
|
386
|
+
version: "1.0.0"
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
this.serverInitialized = true;
|
|
390
|
+
console.debug("MCP \u670D\u52A1\u5668\u521D\u59CB\u5316\u5B8C\u6210");
|
|
391
|
+
break;
|
|
392
|
+
case "tools/list": {
|
|
393
|
+
const toolsList = this.getTools();
|
|
394
|
+
this.sendResponse(message.id, { tools: toolsList });
|
|
395
|
+
console.debug(`MCP \u5DE5\u5177\u5217\u8868\u5DF2\u53D1\u9001 (${toolsList.length}\u4E2A\u5DE5\u5177)`);
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case "tools/call": {
|
|
399
|
+
this.handleToolCall(message).catch((error) => {
|
|
400
|
+
console.error("\u5904\u7406\u5DE5\u5177\u8C03\u7528\u65F6\u53D1\u751F\u672A\u6355\u83B7\u9519\u8BEF:", error);
|
|
401
|
+
});
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case "ping":
|
|
405
|
+
this.sendResponse(message.id, {});
|
|
406
|
+
console.debug("\u56DE\u5E94 MCP ping \u6D88\u606F");
|
|
407
|
+
break;
|
|
408
|
+
default:
|
|
409
|
+
console.warn(`\u672A\u77E5\u7684 MCP \u8BF7\u6C42: ${message.method}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* 发送响应消息
|
|
414
|
+
*/
|
|
415
|
+
sendResponse(id, result) {
|
|
416
|
+
console.debug(
|
|
417
|
+
`\u5C1D\u8BD5\u53D1\u9001\u54CD\u5E94: id=${id}, isConnected=${this.connectionStatus}, wsReadyState=${this.ws?.readyState}`
|
|
418
|
+
);
|
|
419
|
+
if (this.connectionStatus && this.ws?.readyState === WebSocket.OPEN) {
|
|
420
|
+
const response = {
|
|
421
|
+
jsonrpc: "2.0",
|
|
422
|
+
id,
|
|
423
|
+
result
|
|
424
|
+
};
|
|
425
|
+
try {
|
|
426
|
+
this.ws.send(JSON.stringify(response));
|
|
427
|
+
console.debug("\u54CD\u5E94\u5DF2\u53D1\u9001", {
|
|
428
|
+
id,
|
|
429
|
+
responseSize: JSON.stringify(response).length
|
|
430
|
+
});
|
|
431
|
+
} catch (error) {
|
|
432
|
+
console.error("\u53D1\u9001\u54CD\u5E94\u5931\u8D25", {
|
|
433
|
+
id,
|
|
434
|
+
error
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
console.error("\u65E0\u6CD5\u53D1\u9001\u54CD\u5E94", {
|
|
439
|
+
id,
|
|
440
|
+
isConnected: this.connectionStatus,
|
|
441
|
+
wsReadyState: this.ws?.readyState
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* 获取服务器状态
|
|
447
|
+
*/
|
|
448
|
+
getStatus() {
|
|
449
|
+
const availableTools = this.mcpAdapter.getAllTools().length;
|
|
450
|
+
return {
|
|
451
|
+
connected: this.connectionStatus,
|
|
452
|
+
initialized: this.serverInitialized,
|
|
453
|
+
url: this.endpointUrl,
|
|
454
|
+
availableTools,
|
|
455
|
+
connectionState: this.connectionState,
|
|
456
|
+
lastError: this.lastError
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* 检查连接状态
|
|
461
|
+
*/
|
|
462
|
+
isConnected() {
|
|
463
|
+
return this.connectionStatus;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* 主动断开小智连接
|
|
467
|
+
*/
|
|
468
|
+
disconnect() {
|
|
469
|
+
console.info("\u4E3B\u52A8\u65AD\u5F00\u5C0F\u667A\u8FDE\u63A5");
|
|
470
|
+
this.cleanupConnection();
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* 重连小智接入点
|
|
474
|
+
*/
|
|
475
|
+
async reconnect() {
|
|
476
|
+
console.info(`\u91CD\u8FDE\u5C0F\u667A\u63A5\u5165\u70B9: ${sliceEndpoint(this.endpointUrl)}`);
|
|
477
|
+
this.disconnect();
|
|
478
|
+
await new Promise((resolve) => setTimeout(resolve, this.reconnectDelay));
|
|
479
|
+
await this.connect();
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* 处理工具调用请求
|
|
483
|
+
*/
|
|
484
|
+
async handleToolCall(request) {
|
|
485
|
+
if (request.id === void 0 || request.id === null) {
|
|
486
|
+
throw new ToolCallError(
|
|
487
|
+
-32602 /* INVALID_PARAMS */,
|
|
488
|
+
"\u8BF7\u6C42 ID \u4E0D\u80FD\u4E3A\u7A7A"
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
const requestId = request.id;
|
|
492
|
+
const startTime = Date.now();
|
|
493
|
+
try {
|
|
494
|
+
const params = validateToolCallParams(request.params);
|
|
495
|
+
console.info("\u5F00\u59CB\u5904\u7406\u5DE5\u5177\u8C03\u7528", {
|
|
496
|
+
requestId,
|
|
497
|
+
toolName: params.name,
|
|
498
|
+
hasArguments: !!params.arguments
|
|
499
|
+
});
|
|
500
|
+
const result = await this.executeToolWithTimeout(
|
|
501
|
+
params.name,
|
|
502
|
+
params.arguments || {},
|
|
503
|
+
this.toolCallTimeout
|
|
504
|
+
);
|
|
505
|
+
this.sendResponse(requestId, {
|
|
506
|
+
content: result.content || [
|
|
507
|
+
{ type: "text", text: JSON.stringify(result) }
|
|
508
|
+
],
|
|
509
|
+
isError: result.isError || false
|
|
510
|
+
});
|
|
511
|
+
console.info("\u5DE5\u5177\u8C03\u7528\u6210\u529F", {
|
|
512
|
+
requestId,
|
|
513
|
+
toolName: params.name,
|
|
514
|
+
duration: `${Date.now() - startTime}ms`
|
|
515
|
+
});
|
|
516
|
+
} catch (error) {
|
|
517
|
+
this.handleToolCallError(error, requestId, Date.now() - startTime);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* 带超时控制的工具执行
|
|
522
|
+
*/
|
|
523
|
+
async executeToolWithTimeout(toolName, arguments_, timeoutMs = 3e4) {
|
|
524
|
+
return new Promise((resolve, reject) => {
|
|
525
|
+
const timeoutId = setTimeout(() => {
|
|
526
|
+
reject(
|
|
527
|
+
new ToolCallError(
|
|
528
|
+
-32002 /* TIMEOUT */,
|
|
529
|
+
`\u5DE5\u5177\u8C03\u7528\u8D85\u65F6 (${timeoutMs}ms): ${toolName}`
|
|
530
|
+
)
|
|
531
|
+
);
|
|
532
|
+
}, timeoutMs);
|
|
533
|
+
this.mcpAdapter.callTool(toolName, arguments_).then((result) => {
|
|
534
|
+
clearTimeout(timeoutId);
|
|
535
|
+
resolve(result);
|
|
536
|
+
}).catch((error) => {
|
|
537
|
+
clearTimeout(timeoutId);
|
|
538
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
539
|
+
if (errorMessage.includes("\u672A\u627E\u5230\u5DE5\u5177")) {
|
|
540
|
+
reject(
|
|
541
|
+
new ToolCallError(
|
|
542
|
+
-32601 /* TOOL_NOT_FOUND */,
|
|
543
|
+
`\u5DE5\u5177\u4E0D\u5B58\u5728: ${toolName}`
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
} else {
|
|
547
|
+
reject(
|
|
548
|
+
new ToolCallError(
|
|
549
|
+
-32e3 /* TOOL_EXECUTION_ERROR */,
|
|
550
|
+
`\u5DE5\u5177\u6267\u884C\u5931\u8D25: ${errorMessage}`
|
|
551
|
+
)
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* 处理工具调用错误
|
|
559
|
+
*/
|
|
560
|
+
handleToolCallError(error, requestId, duration) {
|
|
561
|
+
let errorResponse;
|
|
562
|
+
if (error instanceof ToolCallError) {
|
|
563
|
+
errorResponse = {
|
|
564
|
+
code: error.code,
|
|
565
|
+
message: error.message,
|
|
566
|
+
data: error.data
|
|
567
|
+
};
|
|
568
|
+
} else {
|
|
569
|
+
const errorMessage = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
|
|
570
|
+
errorResponse = {
|
|
571
|
+
code: -32e3 /* TOOL_EXECUTION_ERROR */,
|
|
572
|
+
message: errorMessage,
|
|
573
|
+
data: { originalError: String(error) || "null" }
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
this.sendErrorResponse(requestId, errorResponse);
|
|
577
|
+
console.error("\u5DE5\u5177\u8C03\u7528\u5931\u8D25", {
|
|
578
|
+
requestId,
|
|
579
|
+
duration: `${duration}ms`,
|
|
580
|
+
error: errorResponse
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* 发送错误响应
|
|
585
|
+
*/
|
|
586
|
+
sendErrorResponse(id, error) {
|
|
587
|
+
if (this.connectionStatus && this.ws?.readyState === WebSocket.OPEN) {
|
|
588
|
+
const response = {
|
|
589
|
+
jsonrpc: "2.0",
|
|
590
|
+
id,
|
|
591
|
+
error
|
|
592
|
+
};
|
|
593
|
+
this.ws.send(JSON.stringify(response));
|
|
594
|
+
console.debug("\u5DF2\u53D1\u9001\u9519\u8BEF\u54CD\u5E94:", response);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/manager.ts
|
|
600
|
+
import { EventEmitter } from "events";
|
|
601
|
+
var EndpointManager = class extends EventEmitter {
|
|
602
|
+
/**
|
|
603
|
+
* 构造函数
|
|
604
|
+
*
|
|
605
|
+
* @param config - 可选的配置
|
|
606
|
+
*/
|
|
607
|
+
constructor(config) {
|
|
608
|
+
super();
|
|
609
|
+
this.config = config;
|
|
610
|
+
console.debug("[EndpointManager] \u5B9E\u4F8B\u5DF2\u521B\u5EFA\uFF08\u65B0 API\uFF09");
|
|
611
|
+
}
|
|
612
|
+
static {
|
|
613
|
+
__name(this, "EndpointManager");
|
|
614
|
+
}
|
|
615
|
+
endpoints = /* @__PURE__ */ new Map();
|
|
616
|
+
connectionStates = /* @__PURE__ */ new Map();
|
|
617
|
+
/**
|
|
618
|
+
* 添加 Endpoint 实例
|
|
619
|
+
*
|
|
620
|
+
* @param endpoint - Endpoint 实例
|
|
621
|
+
*/
|
|
622
|
+
addEndpoint(endpoint) {
|
|
623
|
+
const url = endpoint.getUrl();
|
|
624
|
+
if (this.endpoints.has(url)) {
|
|
625
|
+
console.debug(
|
|
626
|
+
`[EndpointManager] \u63A5\u5165\u70B9 ${sliceEndpoint(url)} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u6DFB\u52A0`
|
|
627
|
+
);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
console.debug(`[EndpointManager] \u6DFB\u52A0\u63A5\u5165\u70B9: ${sliceEndpoint(url)}`);
|
|
631
|
+
this.endpoints.set(url, endpoint);
|
|
632
|
+
this.connectionStates.set(url, {
|
|
633
|
+
endpoint: url,
|
|
634
|
+
connected: false,
|
|
635
|
+
initialized: false
|
|
636
|
+
});
|
|
637
|
+
this.emit("endpointAdded", { endpoint: url });
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* 移除 Endpoint 实例
|
|
641
|
+
*
|
|
642
|
+
* @param endpoint - Endpoint 实例
|
|
643
|
+
*/
|
|
644
|
+
removeEndpoint(endpoint) {
|
|
645
|
+
const url = endpoint.getUrl();
|
|
646
|
+
if (!this.endpoints.has(url)) {
|
|
647
|
+
console.debug(
|
|
648
|
+
`[EndpointManager] \u63A5\u5165\u70B9 ${sliceEndpoint(url)} \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u79FB\u9664`
|
|
649
|
+
);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
console.debug(`[EndpointManager] \u79FB\u9664\u63A5\u5165\u70B9: ${sliceEndpoint(url)}`);
|
|
653
|
+
endpoint.disconnect();
|
|
654
|
+
this.endpoints.delete(url);
|
|
655
|
+
this.connectionStates.delete(url);
|
|
656
|
+
this.emit("endpointRemoved", { endpoint: url });
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* 连接所有 Endpoint
|
|
660
|
+
*/
|
|
661
|
+
async connect() {
|
|
662
|
+
console.debug(`[EndpointManager] \u5F00\u59CB\u8FDE\u63A5\u6240\u6709\u63A5\u5165\u70B9\uFF0C\u603B\u6570: ${this.endpoints.size}`);
|
|
663
|
+
const promises = [];
|
|
664
|
+
for (const [url, endpoint] of this.endpoints) {
|
|
665
|
+
promises.push(
|
|
666
|
+
this.connectSingleEndpoint(url, endpoint).catch((error) => {
|
|
667
|
+
console.error(`[EndpointManager] \u8FDE\u63A5\u5931\u8D25: ${sliceEndpoint(url)}`, error);
|
|
668
|
+
const status = this.connectionStates.get(url);
|
|
669
|
+
if (status) {
|
|
670
|
+
status.connected = false;
|
|
671
|
+
status.initialized = false;
|
|
672
|
+
status.lastError = error instanceof Error ? error.message : String(error);
|
|
673
|
+
}
|
|
674
|
+
})
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
await Promise.allSettled(promises);
|
|
678
|
+
const connectedCount = Array.from(this.connectionStates.values()).filter(
|
|
679
|
+
(s) => s.connected
|
|
680
|
+
).length;
|
|
681
|
+
console.info(`[EndpointManager] \u8FDE\u63A5\u5B8C\u6210: \u6210\u529F ${connectedCount}/${this.endpoints.size}`);
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* 断开所有连接
|
|
685
|
+
*/
|
|
686
|
+
async disconnect() {
|
|
687
|
+
console.debug("[EndpointManager] \u5F00\u59CB\u65AD\u5F00\u6240\u6709\u8FDE\u63A5");
|
|
688
|
+
const promises = [];
|
|
689
|
+
for (const endpoint of this.endpoints.values()) {
|
|
690
|
+
promises.push(
|
|
691
|
+
Promise.resolve().then(() => {
|
|
692
|
+
endpoint.disconnect();
|
|
693
|
+
})
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
await Promise.allSettled(promises);
|
|
697
|
+
for (const status of this.connectionStates.values()) {
|
|
698
|
+
status.connected = false;
|
|
699
|
+
status.initialized = false;
|
|
700
|
+
}
|
|
701
|
+
console.debug("[EndpointManager] \u6240\u6709\u63A5\u5165\u70B9\u5DF2\u65AD\u5F00\u8FDE\u63A5");
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* 获取所有 Endpoint URL
|
|
705
|
+
*/
|
|
706
|
+
getEndpoints() {
|
|
707
|
+
return Array.from(this.endpoints.keys());
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* 获取指定 Endpoint 实例
|
|
711
|
+
*
|
|
712
|
+
* @param url - Endpoint URL
|
|
713
|
+
*/
|
|
714
|
+
getEndpoint(url) {
|
|
715
|
+
return this.endpoints.get(url);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* 获取所有连接状态
|
|
719
|
+
*/
|
|
720
|
+
getConnectionStatus() {
|
|
721
|
+
return Array.from(this.connectionStates.values());
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* 检查是否有任何连接处于连接状态
|
|
725
|
+
*/
|
|
726
|
+
isAnyConnected() {
|
|
727
|
+
for (const status of this.connectionStates.values()) {
|
|
728
|
+
if (status.connected) {
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* 检查指定端点是否已连接
|
|
736
|
+
*
|
|
737
|
+
* @param url - 端点 URL
|
|
738
|
+
*/
|
|
739
|
+
isEndpointConnected(url) {
|
|
740
|
+
const status = this.connectionStates.get(url);
|
|
741
|
+
return status?.connected ?? false;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* 获取指定端点的状态
|
|
745
|
+
*
|
|
746
|
+
* @param url - 端点 URL
|
|
747
|
+
*/
|
|
748
|
+
getEndpointStatus(url) {
|
|
749
|
+
return this.connectionStates.get(url);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* 重连所有端点
|
|
753
|
+
*/
|
|
754
|
+
async reconnectAll() {
|
|
755
|
+
console.info("[EndpointManager] \u5F00\u59CB\u91CD\u8FDE\u6240\u6709\u63A5\u5165\u70B9");
|
|
756
|
+
const promises = [];
|
|
757
|
+
for (const [url, endpoint] of this.endpoints) {
|
|
758
|
+
promises.push(
|
|
759
|
+
this.reconnectSingleEndpoint(url, endpoint).catch((error) => {
|
|
760
|
+
console.error(`[EndpointManager] \u91CD\u8FDE\u5931\u8D25: ${sliceEndpoint(url)}`, error);
|
|
761
|
+
})
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
await Promise.allSettled(promises);
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* 重连指定的端点
|
|
768
|
+
*
|
|
769
|
+
* @param url - 要重连的端点 URL
|
|
770
|
+
*/
|
|
771
|
+
async reconnectEndpoint(url) {
|
|
772
|
+
const endpoint = this.endpoints.get(url);
|
|
773
|
+
if (!endpoint) {
|
|
774
|
+
throw new Error(`\u63A5\u5165\u70B9\u4E0D\u5B58\u5728: ${sliceEndpoint(url)}`);
|
|
775
|
+
}
|
|
776
|
+
await this.reconnectSingleEndpoint(url, endpoint);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* 清除所有端点
|
|
780
|
+
*/
|
|
781
|
+
async clearEndpoints() {
|
|
782
|
+
console.debug("[EndpointManager] \u6E05\u9664\u6240\u6709\u63A5\u5165\u70B9");
|
|
783
|
+
await this.disconnect();
|
|
784
|
+
this.endpoints.clear();
|
|
785
|
+
this.connectionStates.clear();
|
|
786
|
+
console.info("[EndpointManager] \u6240\u6709\u63A5\u5165\u70B9\u5DF2\u6E05\u9664");
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* 清理资源
|
|
790
|
+
*/
|
|
791
|
+
async cleanup() {
|
|
792
|
+
console.debug("[EndpointManager] \u5F00\u59CB\u6E05\u7406\u8D44\u6E90");
|
|
793
|
+
await this.clearEndpoints();
|
|
794
|
+
console.debug("[EndpointManager] \u8D44\u6E90\u6E05\u7406\u5B8C\u6210");
|
|
795
|
+
}
|
|
796
|
+
// ==================== 私有方法 ====================
|
|
797
|
+
/**
|
|
798
|
+
* 连接单个端点
|
|
799
|
+
*/
|
|
800
|
+
async connectSingleEndpoint(url, endpoint) {
|
|
801
|
+
const status = this.connectionStates.get(url);
|
|
802
|
+
if (!status) {
|
|
803
|
+
throw new Error(`\u7AEF\u70B9\u72B6\u6001\u4E0D\u5B58\u5728: ${sliceEndpoint(url)}`);
|
|
804
|
+
}
|
|
805
|
+
console.debug(`[EndpointManager] \u8FDE\u63A5\u7AEF\u70B9: ${sliceEndpoint(url)}`);
|
|
806
|
+
status.connected = false;
|
|
807
|
+
status.initialized = false;
|
|
808
|
+
await endpoint.connect();
|
|
809
|
+
status.connected = true;
|
|
810
|
+
status.initialized = true;
|
|
811
|
+
status.lastConnected = /* @__PURE__ */ new Date();
|
|
812
|
+
status.lastError = void 0;
|
|
813
|
+
console.info(`[EndpointManager] \u7AEF\u70B9\u8FDE\u63A5\u6210\u529F: ${sliceEndpoint(url)}`);
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* 重连单个端点
|
|
817
|
+
*/
|
|
818
|
+
async reconnectSingleEndpoint(url, endpoint) {
|
|
819
|
+
const status = this.connectionStates.get(url);
|
|
820
|
+
if (!status) {
|
|
821
|
+
throw new Error(`\u7AEF\u70B9\u72B6\u6001\u4E0D\u5B58\u5728: ${sliceEndpoint(url)}`);
|
|
822
|
+
}
|
|
823
|
+
console.debug(`[EndpointManager] \u91CD\u8FDE\u7AEF\u70B9: ${sliceEndpoint(url)}`);
|
|
824
|
+
await endpoint.reconnect();
|
|
825
|
+
status.connected = true;
|
|
826
|
+
status.initialized = true;
|
|
827
|
+
status.lastConnected = /* @__PURE__ */ new Date();
|
|
828
|
+
status.lastError = void 0;
|
|
829
|
+
console.info(`[EndpointManager] \u7AEF\u70B9\u91CD\u8FDE\u6210\u529F: ${sliceEndpoint(url)}`);
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
export {
|
|
833
|
+
ConnectionState,
|
|
834
|
+
Endpoint,
|
|
835
|
+
EndpointManager,
|
|
836
|
+
ToolCallError,
|
|
837
|
+
ToolCallErrorCode,
|
|
838
|
+
deepMerge,
|
|
839
|
+
ensureToolJSONSchema,
|
|
840
|
+
formatErrorMessage,
|
|
841
|
+
isValidEndpointUrl,
|
|
842
|
+
sleep,
|
|
843
|
+
sliceEndpoint,
|
|
844
|
+
validateToolCallParams
|
|
845
|
+
};
|
|
846
|
+
//# sourceMappingURL=index.js.map
|