xiaozhi-client 1.6.3-beta.0 → 1.6.3-beta.2
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 +97 -17
- package/dist/ProxyMCPServer.d.ts +123 -1
- package/dist/ProxyMCPServer.js +2 -2
- package/dist/ProxyMCPServer.js.map +1 -1
- package/dist/WebServer.js +12 -11
- package/dist/WebServer.js.map +1 -1
- package/dist/WebServerStandalone.js +12 -11
- package/dist/WebServerStandalone.js.map +1 -1
- package/dist/cli.d.ts +0 -28
- package/dist/cli.js +44 -11
- package/dist/cli.js.map +1 -1
- package/dist/configManager.js +2 -2
- package/dist/configManager.js.map +1 -1
- package/dist/mcpCommands.js +2 -2
- package/dist/mcpCommands.js.map +1 -1
- package/dist/mcpServerProxy.js +4 -4
- package/dist/mcpServerProxy.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/services/MCPServer.js +4 -4
- package/dist/services/MCPServer.js.map +1 -1
- package/dist/templates/default/mcpServers/calculator.js +106 -0
- package/dist/templates/default/mcpServers/datetime.js +390 -0
- package/dist/templates/default/package.json +13 -0
- package/dist/templates/default/xiaozhi.config.json +21 -0
- package/docs/CLI.md +825 -92
- package/package.json +2 -2
- package/templates/default/mcpServers/calculator.js +106 -0
- package/templates/default/mcpServers/datetime.js +390 -0
- package/templates/default/package.json +13 -0
- package/templates/default/xiaozhi.config.json +21 -0
package/README.md
CHANGED
|
@@ -86,7 +86,11 @@ cd my-app
|
|
|
86
86
|
## 安装依赖(主要是示例代码中mcp服务所使用的依赖)
|
|
87
87
|
pnpm install
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
## 初始化配置
|
|
90
|
+
xiaozhi config init
|
|
91
|
+
|
|
92
|
+
## 设置接入点地址(需要自行前往xiaozhi.me获取)
|
|
93
|
+
xiaozhi config set mcpEndpoint "your-endpoint-url"
|
|
90
94
|
# 小智AI配置MCP接入点使用说明:https://ccnphfhqs21z.feishu.cn/wiki/HiPEwZ37XiitnwktX13cEM5KnSb
|
|
91
95
|
|
|
92
96
|
## 运行
|
|
@@ -97,15 +101,19 @@ xiaozhi start
|
|
|
97
101
|
|
|
98
102
|
```bash
|
|
99
103
|
# 创建项目
|
|
100
|
-
npx -y xiaozhi-client create --template hello-world
|
|
104
|
+
npx -y xiaozhi-client create my-app --template hello-world
|
|
101
105
|
|
|
102
106
|
# 进入项目目录
|
|
103
|
-
cd
|
|
107
|
+
cd my-app
|
|
104
108
|
|
|
105
109
|
# 安装依赖
|
|
106
110
|
pnpm install
|
|
107
111
|
|
|
108
|
-
#
|
|
112
|
+
# 初始化配置
|
|
113
|
+
npx -y xiaozhi-client config init
|
|
114
|
+
|
|
115
|
+
# 设置接入点地址(需要自行前往xiaozhi.me获取)
|
|
116
|
+
npx -y xiaozhi-client config set mcpEndpoint "your-endpoint-url"
|
|
109
117
|
# 小智AI配置MCP接入点使用说明:https://ccnphfhqs21z.feishu.cn/wiki/HiPEwZ37XiitnwktX13cEM5KnSb
|
|
110
118
|
|
|
111
119
|
# 启动服务
|
|
@@ -360,18 +368,49 @@ docker run -d \
|
|
|
360
368
|
|
|
361
369
|
## 可用命令
|
|
362
370
|
|
|
371
|
+
### 基本命令
|
|
372
|
+
|
|
363
373
|
```bash
|
|
364
374
|
# 查看帮助
|
|
365
375
|
xiaozhi --help
|
|
366
376
|
|
|
367
|
-
#
|
|
377
|
+
# 查看版本信息
|
|
378
|
+
xiaozhi --version
|
|
379
|
+
|
|
380
|
+
# 查看详细系统信息
|
|
381
|
+
xiaozhi --info
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### 项目管理
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# 创建项目
|
|
388
|
+
xiaozhi create my-app --template hello-world
|
|
389
|
+
|
|
390
|
+
# 初始化配置文件
|
|
391
|
+
xiaozhi config init
|
|
392
|
+
|
|
393
|
+
# 查看配置
|
|
394
|
+
xiaozhi config get mcpEndpoint
|
|
395
|
+
|
|
396
|
+
# 设置配置
|
|
397
|
+
xiaozhi config set mcpEndpoint "your-endpoint-url"
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### 服务管理
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
# 启动服务(前台)
|
|
368
404
|
xiaozhi start
|
|
369
405
|
|
|
370
406
|
# 后台启动服务
|
|
371
|
-
xiaozhi start
|
|
407
|
+
xiaozhi start -d
|
|
372
408
|
|
|
373
|
-
#
|
|
374
|
-
xiaozhi
|
|
409
|
+
# 启动并打开 Web UI
|
|
410
|
+
xiaozhi start -u
|
|
411
|
+
|
|
412
|
+
# 以 MCP Server 模式启动(用于 Cursor 等客户端)
|
|
413
|
+
xiaozhi start --stdio
|
|
375
414
|
|
|
376
415
|
# 查看服务状态
|
|
377
416
|
xiaozhi status
|
|
@@ -382,13 +421,45 @@ xiaozhi stop
|
|
|
382
421
|
# 重启服务
|
|
383
422
|
xiaozhi restart
|
|
384
423
|
|
|
385
|
-
#
|
|
424
|
+
# 将后台服务转到前台运行
|
|
425
|
+
xiaozhi attach
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### MCP 管理
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
# 列出所有 MCP 服务
|
|
386
432
|
xiaozhi mcp list
|
|
387
433
|
|
|
388
|
-
# 列出所有
|
|
434
|
+
# 列出所有 MCP 工具
|
|
389
435
|
xiaozhi mcp list --tools
|
|
436
|
+
|
|
437
|
+
# 查看特定服务
|
|
438
|
+
xiaozhi mcp server calculator
|
|
390
439
|
```
|
|
391
440
|
|
|
441
|
+
### 端点管理
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
# 列出所有端点
|
|
445
|
+
xiaozhi endpoint list
|
|
446
|
+
|
|
447
|
+
# 添加端点
|
|
448
|
+
xiaozhi endpoint add "ws://new-server:8080"
|
|
449
|
+
|
|
450
|
+
# 移除端点
|
|
451
|
+
xiaozhi endpoint remove "ws://old-server:8080"
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Web UI
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
# 启动 Web 配置界面
|
|
458
|
+
xiaozhi ui
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
> 📖 **详细使用说明**: 查看 [CLI 使用手册](docs/CLI.md) 获取完整的命令参考和使用示例。
|
|
462
|
+
|
|
392
463
|
## 多接入点配置
|
|
393
464
|
|
|
394
465
|
xiaozhi-client 支持同时连接多个小智 AI 接入点
|
|
@@ -424,13 +495,16 @@ xiaozhi-client 支持同时连接多个小智 AI 接入点
|
|
|
424
495
|
xiaozhi endpoint list
|
|
425
496
|
|
|
426
497
|
# 添加新的接入点
|
|
427
|
-
xiaozhi endpoint add wss://api.xiaozhi.me/mcp/new-endpoint
|
|
498
|
+
xiaozhi endpoint add "wss://api.xiaozhi.me/mcp/new-endpoint"
|
|
428
499
|
|
|
429
500
|
# 移除指定的接入点
|
|
430
|
-
xiaozhi endpoint remove wss://api.xiaozhi.me/mcp/old-endpoint
|
|
501
|
+
xiaozhi endpoint remove "wss://api.xiaozhi.me/mcp/old-endpoint"
|
|
502
|
+
|
|
503
|
+
# 设置单个接入点(覆盖现有配置)
|
|
504
|
+
xiaozhi endpoint set "wss://api.xiaozhi.me/mcp/endpoint-1"
|
|
431
505
|
|
|
432
|
-
#
|
|
433
|
-
xiaozhi
|
|
506
|
+
# 或者使用 config 命令设置
|
|
507
|
+
xiaozhi config set mcpEndpoint "wss://api.xiaozhi.me/mcp/endpoint-1"
|
|
434
508
|
```
|
|
435
509
|
|
|
436
510
|
### 示例配置
|
|
@@ -668,9 +742,15 @@ xiaozhi-client 提供了一个现代化的 Web UI 界面,让配置 MCP 服务
|
|
|
668
742
|
### 启动 Web UI
|
|
669
743
|
|
|
670
744
|
```bash
|
|
745
|
+
# 启动 Web 配置界面
|
|
671
746
|
xiaozhi ui
|
|
747
|
+
|
|
748
|
+
# 或者在启动服务时同时启动 Web UI
|
|
749
|
+
xiaozhi start -u
|
|
672
750
|
```
|
|
673
751
|
|
|
752
|
+
启动后访问 <http://localhost:9999> 进行可视化配置。
|
|
753
|
+
|
|
674
754
|
## 作为 MCP Server 集成到其他客户端
|
|
675
755
|
|
|
676
756
|
> 需升级至 `1.5.0` 及以上版本
|
|
@@ -734,13 +814,13 @@ npm install -g xiaozhi-client
|
|
|
734
814
|
|
|
735
815
|
```bash
|
|
736
816
|
# 使用默认端口 3000
|
|
737
|
-
xiaozhi start
|
|
817
|
+
xiaozhi start -s
|
|
738
818
|
|
|
739
819
|
# 使用自定义端口
|
|
740
|
-
xiaozhi start
|
|
820
|
+
xiaozhi start -s 8080
|
|
741
821
|
|
|
742
822
|
# 后台运行
|
|
743
|
-
xiaozhi start
|
|
823
|
+
xiaozhi start -s -d
|
|
744
824
|
```
|
|
745
825
|
|
|
746
826
|
第二步:在 客户端 中配置 SSE 连接:
|
package/dist/ProxyMCPServer.d.ts
CHANGED
|
@@ -8,6 +8,50 @@ declare enum ConnectionState {
|
|
|
8
8
|
RECONNECTING = "reconnecting",
|
|
9
9
|
FAILED = "failed"
|
|
10
10
|
}
|
|
11
|
+
declare enum ToolCallErrorCode {
|
|
12
|
+
INVALID_PARAMS = -32602,// 无效参数
|
|
13
|
+
TOOL_NOT_FOUND = -32601,// 工具不存在
|
|
14
|
+
TOOL_EXECUTION_ERROR = -32000,// 工具执行错误
|
|
15
|
+
SERVICE_UNAVAILABLE = -32001,// 服务不可用
|
|
16
|
+
TIMEOUT = -32002
|
|
17
|
+
}
|
|
18
|
+
declare class ToolCallError extends Error {
|
|
19
|
+
code: ToolCallErrorCode;
|
|
20
|
+
data?: any | undefined;
|
|
21
|
+
constructor(code: ToolCallErrorCode, message: string, data?: any | undefined);
|
|
22
|
+
}
|
|
23
|
+
interface ToolCallOptions {
|
|
24
|
+
timeout?: number;
|
|
25
|
+
retryAttempts?: number;
|
|
26
|
+
retryDelay?: number;
|
|
27
|
+
}
|
|
28
|
+
interface PerformanceMetrics {
|
|
29
|
+
totalCalls: number;
|
|
30
|
+
successfulCalls: number;
|
|
31
|
+
failedCalls: number;
|
|
32
|
+
averageResponseTime: number;
|
|
33
|
+
minResponseTime: number;
|
|
34
|
+
maxResponseTime: number;
|
|
35
|
+
successRate: number;
|
|
36
|
+
lastUpdated: Date;
|
|
37
|
+
}
|
|
38
|
+
interface CallRecord {
|
|
39
|
+
id: string;
|
|
40
|
+
toolName: string;
|
|
41
|
+
startTime: Date;
|
|
42
|
+
endTime?: Date;
|
|
43
|
+
duration?: number;
|
|
44
|
+
success: boolean;
|
|
45
|
+
errorCode?: number;
|
|
46
|
+
errorMessage?: string;
|
|
47
|
+
}
|
|
48
|
+
interface RetryConfig {
|
|
49
|
+
maxAttempts: number;
|
|
50
|
+
initialDelay: number;
|
|
51
|
+
maxDelay: number;
|
|
52
|
+
backoffMultiplier: number;
|
|
53
|
+
retryableErrors: ToolCallErrorCode[];
|
|
54
|
+
}
|
|
11
55
|
interface ReconnectOptions {
|
|
12
56
|
enabled: boolean;
|
|
13
57
|
maxAttempts: number;
|
|
@@ -41,6 +85,11 @@ declare class ProxyMCPServer {
|
|
|
41
85
|
private reconnectOptions;
|
|
42
86
|
private reconnectState;
|
|
43
87
|
private connectionTimeout;
|
|
88
|
+
private performanceMetrics;
|
|
89
|
+
private callRecords;
|
|
90
|
+
private readonly maxCallRecords;
|
|
91
|
+
private retryConfig;
|
|
92
|
+
private toolCallConfig;
|
|
44
93
|
constructor(endpointUrl: string, options?: ProxyMCPServerOptions);
|
|
45
94
|
/**
|
|
46
95
|
* 设置 MCPServiceManager 实例
|
|
@@ -167,6 +216,79 @@ declare class ProxyMCPServer {
|
|
|
167
216
|
* 重置重连状态
|
|
168
217
|
*/
|
|
169
218
|
resetReconnectState(): void;
|
|
219
|
+
/**
|
|
220
|
+
* 处理工具调用请求
|
|
221
|
+
*/
|
|
222
|
+
private handleToolCall;
|
|
223
|
+
/**
|
|
224
|
+
* 验证工具调用参数
|
|
225
|
+
*/
|
|
226
|
+
private validateToolCallParams;
|
|
227
|
+
/**
|
|
228
|
+
* 带重试机制的工具执行
|
|
229
|
+
*/
|
|
230
|
+
private executeToolWithRetry;
|
|
231
|
+
/**
|
|
232
|
+
* 带超时控制的工具执行
|
|
233
|
+
*/
|
|
234
|
+
private executeToolWithTimeout;
|
|
235
|
+
/**
|
|
236
|
+
* 处理工具调用错误
|
|
237
|
+
*/
|
|
238
|
+
private handleToolCallError;
|
|
239
|
+
/**
|
|
240
|
+
* 发送错误响应
|
|
241
|
+
*/
|
|
242
|
+
private sendErrorResponse;
|
|
243
|
+
/**
|
|
244
|
+
* 记录工具调用开始
|
|
245
|
+
*/
|
|
246
|
+
private recordCallStart;
|
|
247
|
+
/**
|
|
248
|
+
* 记录工具调用结束
|
|
249
|
+
*/
|
|
250
|
+
private recordCallEnd;
|
|
251
|
+
/**
|
|
252
|
+
* 更新性能指标
|
|
253
|
+
*/
|
|
254
|
+
private updatePerformanceMetrics;
|
|
255
|
+
/**
|
|
256
|
+
* 获取性能指标
|
|
257
|
+
*/
|
|
258
|
+
getPerformanceMetrics(): PerformanceMetrics;
|
|
259
|
+
/**
|
|
260
|
+
* 获取调用记录
|
|
261
|
+
*/
|
|
262
|
+
getCallRecords(limit?: number): CallRecord[];
|
|
263
|
+
/**
|
|
264
|
+
* 重置性能指标
|
|
265
|
+
*/
|
|
266
|
+
resetPerformanceMetrics(): void;
|
|
267
|
+
/**
|
|
268
|
+
* 更新工具调用配置
|
|
269
|
+
*/
|
|
270
|
+
updateToolCallConfig(config: Partial<ToolCallOptions>): void;
|
|
271
|
+
/**
|
|
272
|
+
* 更新重试配置
|
|
273
|
+
*/
|
|
274
|
+
updateRetryConfig(config: Partial<RetryConfig>): void;
|
|
275
|
+
/**
|
|
276
|
+
* 获取当前配置
|
|
277
|
+
*/
|
|
278
|
+
getConfiguration(): {
|
|
279
|
+
toolCall: ToolCallOptions;
|
|
280
|
+
retry: RetryConfig;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* 获取服务器状态(增强版)
|
|
284
|
+
*/
|
|
285
|
+
getEnhancedStatus(): ProxyMCPServerStatus & {
|
|
286
|
+
performance: PerformanceMetrics;
|
|
287
|
+
configuration: {
|
|
288
|
+
toolCall: ToolCallOptions;
|
|
289
|
+
retry: RetryConfig;
|
|
290
|
+
};
|
|
291
|
+
};
|
|
170
292
|
}
|
|
171
293
|
|
|
172
|
-
export { ProxyMCPServer };
|
|
294
|
+
export { ProxyMCPServer, ToolCallError, ToolCallErrorCode };
|
package/dist/ProxyMCPServer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)}catch{this.safeWrite(e)}},"write")}}safeWrite(t){try{process.stderr&&typeof process.stderr.write=="function"?process.stderr.write(t):console&&typeof console.error=="function"&&console.error(t.trim())}catch{}}formatConsoleMessageOptimized(t,e){let n=y(new Date),i=e.get(t.level)||{name:"UNKNOWN",color:a(m=>m,"color")},c=i.color(`[${i.name}]`),l=t.msg;if(t.args&&Array.isArray(t.args)){let m=t.args.map(d=>typeof d=="object"?JSON.stringify(d):String(d)).join(" ");l=`${l} ${m}`}return`[${n}] ${c} ${l}`}initLogFile(t){this.logFilePath=s.join(t,"xiaozhi.log"),this.rotateLogFileIfNeeded(),o.existsSync(this.logFilePath)||o.writeFileSync(this.logFilePath,""),this.pinoInstance=this.createPinoInstance()}enableFileLogging(t){t&&this.logFilePath&&(this.pinoInstance=this.createPinoInstance())}info(t,...e){typeof t=="string"?e.length===0?this.pinoInstance.info(t):this.pinoInstance.info({args:e},t):this.pinoInstance.info(t,e[0]||"")}success(t,...e){typeof t=="string"?e.length===0?this.pinoInstance.info(t):this.pinoInstance.info({args:e},t):this.pinoInstance.info(t,e[0]||"")}warn(t,...e){typeof t=="string"?e.length===0?this.pinoInstance.warn(t):this.pinoInstance.warn({args:e},t):this.pinoInstance.warn(t,e[0]||"")}error(t,...e){if(typeof t=="string")if(e.length===0)this.pinoInstance.error(t);else{let n=e.map(i=>i instanceof Error?{message:i.message,stack:i.stack,name:i.name,cause:i.cause}:i);this.pinoInstance.error({args:n},t)}else{let n=this.enhanceErrorObject(t);this.pinoInstance.error(n,e[0]||"")}}debug(t,...e){typeof t=="string"?e.length===0?this.pinoInstance.debug(t):this.pinoInstance.debug({args:e},t):this.pinoInstance.debug(t,e[0]||"")}log(t,...e){typeof t=="string"?e.length===0?this.pinoInstance.info(t):this.pinoInstance.info({args:e},t):this.pinoInstance.info(t,e[0]||"")}enhanceErrorObject(t){let e={...t};for(let[n,i]of Object.entries(e))i instanceof Error&&(e[n]={message:i.message,stack:i.stack,name:i.name,cause:i.cause});return e}rotateLogFileIfNeeded(){if(!(!this.logFilePath||!o.existsSync(this.logFilePath)))try{o.statSync(this.logFilePath).size>this.maxLogFileSize&&this.rotateLogFile()}catch{}}rotateLogFile(){if(this.logFilePath)try{let t=s.dirname(this.logFilePath),e=s.basename(this.logFilePath,".log");for(let i=this.maxLogFiles-1;i>=1;i--){let c=s.join(t,`${e}.${i}.log`),l=s.join(t,`${e}.${i+1}.log`);o.existsSync(c)&&(i===this.maxLogFiles-1?o.unlinkSync(c):o.renameSync(c,l))}let n=s.join(t,`${e}.1.log`);o.renameSync(this.logFilePath,n)}catch{}}cleanupOldLogs(){if(this.logFilePath)try{let t=s.dirname(this.logFilePath),e=s.basename(this.logFilePath,".log");for(let n=this.maxLogFiles+1;n<=this.maxLogFiles+10;n++){let i=s.join(t,`${e}.${n}.log`);o.existsSync(i)&&o.unlinkSync(i)}}catch{}}setLogFileOptions(t,e){this.maxLogFileSize=t,this.maxLogFiles=e}withTag(t){return this}close(){}},f=new u;var v=class{static{a(this,"ProxyMCPServer")}endpointUrl;ws=null;logger;isConnected=!1;serverInitialized=!1;tools=new Map;connectionState="disconnected";reconnectOptions;reconnectState={attempts:0,nextInterval:0,timer:null,lastError:null,isManualDisconnect:!1};connectionTimeout=null;constructor(t,e){this.endpointUrl=t,this.logger=f,this.reconnectOptions={enabled:!0,maxAttempts:10,initialInterval:3e3,maxInterval:3e4,backoffStrategy:"exponential",backoffMultiplier:1.5,timeout:1e4,jitter:!0,...e?.reconnect},this.reconnectState.nextInterval=this.reconnectOptions.initialInterval}setServiceManager(t){this.serviceManager=t,this.logger.info("\u5DF2\u8BBE\u7F6E MCPServiceManager"),this.syncToolsFromServiceManager()}syncToolsFromServiceManager(){let t=this.serviceManager;if(!t){this.logger.debug("MCPServiceManager \u672A\u8BBE\u7F6E\uFF0C\u8DF3\u8FC7\u5DE5\u5177\u540C\u6B65");return}try{let e=t.getAllTools(),n=new Map;for(let i of e)n.set(i.name,{name:i.name,description:i.description,inputSchema:i.inputSchema});this.tools=n,this.logger.info(`\u5DF2\u4ECE MCPServiceManager \u540C\u6B65 ${this.tools.size} \u4E2A\u5DE5\u5177`)}catch(e){this.logger.error(`\u540C\u6B65\u5DE5\u5177\u5931\u8D25: ${e instanceof Error?e.message:String(e)}`)}}addTool(t,e){return this.validateTool(t,e),this.tools.set(t,e),this.logger.debug(`\u5DE5\u5177 '${t}' \u5DF2\u6DFB\u52A0`),this}addTools(t){for(let[e,n]of Object.entries(t))this.addTool(e,n);return this}removeTool(t){return this.tools.delete(t)?this.logger.debug(`\u5DE5\u5177 '${t}' \u5DF2\u79FB\u9664`):this.logger.warn(`\u5C1D\u8BD5\u79FB\u9664\u4E0D\u5B58\u5728\u7684\u5DE5\u5177: '${t}'`),this}getTools(){try{this.syncToolsFromServiceManager()}catch{}return Array.from(this.tools.values())}hasTool(t){return this.tools.has(t)}validateTool(t,e){if(!t||typeof t!="string"||t.trim()==="")throw new Error("\u5DE5\u5177\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if(this.tools.has(t))throw new Error(`\u5DE5\u5177 '${t}' \u5DF2\u5B58\u5728`);if(!e||typeof e!="object")throw new Error("\u5DE5\u5177\u5FC5\u987B\u662F\u6709\u6548\u7684\u5BF9\u8C61");if(!e.name||typeof e.name!="string")throw new Error("\u5DE5\u5177\u5FC5\u987B\u5305\u542B\u6709\u6548\u7684 'name' \u5B57\u6BB5");if(!e.description||typeof e.description!="string")throw new Error("\u5DE5\u5177\u5FC5\u987B\u5305\u542B\u6709\u6548\u7684 'description' \u5B57\u6BB5");if(!e.inputSchema||typeof e.inputSchema!="object")throw new Error("\u5DE5\u5177\u5FC5\u987B\u5305\u542B\u6709\u6548\u7684 'inputSchema' \u5B57\u6BB5");if(!e.inputSchema.type||!e.inputSchema.properties)throw new Error("\u5DE5\u5177\u7684 inputSchema \u5FC5\u987B\u5305\u542B 'type' \u548C 'properties' \u5B57\u6BB5")}async connect(){if(this.tools.size===0)throw new Error("\u672A\u914D\u7F6E\u4EFB\u4F55\u5DE5\u5177\u3002\u8BF7\u5728\u8FDE\u63A5\u524D\u81F3\u5C11\u6DFB\u52A0\u4E00\u4E2A\u5DE5\u5177\u3002");if(this.connectionState==="connecting")throw new Error("\u8FDE\u63A5\u6B63\u5728\u8FDB\u884C\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u8FDE\u63A5\u5B8C\u6210");return this.cleanupConnection(),this.reconnectState.isManualDisconnect=!1,this.attemptConnection()}async attemptConnection(){return this.connectionState="connecting",this.logger.info(`\u6B63\u5728\u8FDE\u63A5 MCP \u63A5\u5165\u70B9: ${this.endpointUrl} (\u5C1D\u8BD5 ${this.reconnectState.attempts+1}/${this.reconnectOptions.maxAttempts})`),new Promise((t,e)=>{this.connectionTimeout=setTimeout(()=>{let n=new Error(`\u8FDE\u63A5\u8D85\u65F6 (${this.reconnectOptions.timeout}ms)`);this.handleConnectionError(n),e(n)},this.reconnectOptions.timeout),this.ws=new g(this.endpointUrl),this.ws.on("open",()=>{this.handleConnectionSuccess(),t()}),this.ws.on("message",n=>{try{let i=JSON.parse(n.toString());this.handleMessage(i)}catch(i){this.logger.error("MCP \u6D88\u606F\u89E3\u6790\u9519\u8BEF:",i)}}),this.ws.on("close",(n,i)=>{this.handleConnectionClose(n,i.toString())}),this.ws.on("error",n=>{this.handleConnectionError(n),e(n)})})}handleConnectionSuccess(){this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.isConnected=!0,this.connectionState="connected",this.reconnectState.attempts=0,this.reconnectState.nextInterval=this.reconnectOptions.initialInterval,this.reconnectState.lastError=null,this.logger.info("MCP WebSocket \u8FDE\u63A5\u5DF2\u5EFA\u7ACB")}handleConnectionError(t){this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.reconnectState.lastError=t,this.logger.error("MCP WebSocket \u9519\u8BEF:",t.message),this.cleanupConnection()}handleConnectionClose(t,e){if(this.isConnected=!1,this.serverInitialized=!1,this.logger.info(`MCP \u8FDE\u63A5\u5DF2\u5173\u95ED (\u4EE3\u7801: ${t}, \u539F\u56E0: ${e})`),this.reconnectState.isManualDisconnect){this.connectionState="disconnected";return}this.shouldReconnect()?this.scheduleReconnect():(this.connectionState="failed",this.logger.warn(`\u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8FDE\u6B21\u6570 (${this.reconnectOptions.maxAttempts})\uFF0C\u505C\u6B62\u91CD\u8FDE`))}shouldReconnect(){return this.reconnectOptions.enabled&&this.reconnectState.attempts<this.reconnectOptions.maxAttempts&&!this.reconnectState.isManualDisconnect}scheduleReconnect(){this.connectionState="reconnecting",this.reconnectState.attempts++,this.calculateNextInterval(),this.logger.info(`\u5C06\u5728 ${this.reconnectState.nextInterval}ms \u540E\u8FDB\u884C\u7B2C ${this.reconnectState.attempts} \u6B21\u91CD\u8FDE`),this.reconnectState.timer&&clearTimeout(this.reconnectState.timer),this.reconnectState.timer=setTimeout(async()=>{try{await this.attemptConnection()}catch{}},this.reconnectState.nextInterval)}calculateNextInterval(){let t;switch(this.reconnectOptions.backoffStrategy){case"fixed":t=this.reconnectOptions.initialInterval;break;case"linear":t=this.reconnectOptions.initialInterval+this.reconnectState.attempts*this.reconnectOptions.backoffMultiplier*1e3;break;case"exponential":t=this.reconnectOptions.initialInterval*this.reconnectOptions.backoffMultiplier**(this.reconnectState.attempts-1);break;default:t=this.reconnectOptions.initialInterval}if(t=Math.min(t,this.reconnectOptions.maxInterval),this.reconnectOptions.jitter){let e=t*.1,n=(Math.random()-.5)*2*e;t+=n}this.reconnectState.nextInterval=Math.max(t,1e3)}cleanupConnection(){if(this.ws){this.ws.removeAllListeners();try{this.ws.readyState===g.OPEN?this.ws.close(1e3,"Cleaning up connection"):this.ws.readyState===g.CONNECTING&&this.ws.terminate()}catch(t){this.logger.debug("WebSocket \u5173\u95ED\u65F6\u51FA\u73B0\u9519\u8BEF\uFF08\u5DF2\u5FFD\u7565\uFF09:",t)}this.ws=null}this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.isConnected=!1,this.serverInitialized=!1}stopReconnect(){this.reconnectState.timer&&(clearTimeout(this.reconnectState.timer),this.reconnectState.timer=null)}handleMessage(t){this.logger.debug("\u6536\u5230 MCP \u6D88\u606F:",JSON.stringify(t,null,2)),t.method&&this.handleServerRequest(t)}handleServerRequest(t){switch(t.method){case"initialize":this.sendResponse(t.id,{protocolVersion:"2024-11-05",capabilities:{tools:{listChanged:!0},logging:{}},serverInfo:{name:"xiaozhi-mcp-server",version:"1.0.0"}}),this.serverInitialized=!0,this.logger.info("MCP \u670D\u52A1\u5668\u521D\u59CB\u5316\u5B8C\u6210");break;case"tools/list":{let e=this.getTools();this.sendResponse(t.id,{tools:e}),this.logger.info(`MCP \u5DE5\u5177\u5217\u8868\u5DF2\u53D1\u9001 (${e.length}\u4E2A\u5DE5\u5177)`);break}case"ping":this.sendResponse(t.id,{}),this.logger.debug("\u56DE\u5E94 MCP ping \u6D88\u606F");break;default:this.logger.warn(`\u672A\u77E5\u7684 MCP \u8BF7\u6C42: ${t.method}`)}}sendResponse(t,e){if(this.isConnected&&this.ws?.readyState===g.OPEN){let n={jsonrpc:"2.0",id:t,result:e};this.ws.send(JSON.stringify(n))}}getStatus(){return{connected:this.isConnected,initialized:this.serverInitialized,url:this.endpointUrl,availableTools:this.tools.size,connectionState:this.connectionState,reconnectAttempts:this.reconnectState.attempts,lastError:this.reconnectState.lastError?.message||null}}disconnect(){this.logger.info("\u4E3B\u52A8\u65AD\u5F00 MCP \u8FDE\u63A5"),this.reconnectState.isManualDisconnect=!0,this.stopReconnect(),this.cleanupConnection(),this.connectionState="disconnected"}async reconnect(){this.logger.info("\u624B\u52A8\u91CD\u8FDE MCP \u63A5\u5165\u70B9"),this.stopReconnect(),this.reconnectState.attempts=0,this.reconnectState.nextInterval=this.reconnectOptions.initialInterval,this.reconnectState.isManualDisconnect=!1,this.cleanupConnection(),await this.connect()}enableReconnect(){this.reconnectOptions.enabled=!0,this.logger.info("\u81EA\u52A8\u91CD\u8FDE\u5DF2\u542F\u7528")}disableReconnect(){this.reconnectOptions.enabled=!1,this.stopReconnect(),this.logger.info("\u81EA\u52A8\u91CD\u8FDE\u5DF2\u7981\u7528")}updateReconnectOptions(t){this.reconnectOptions={...this.reconnectOptions,...t},this.logger.info("\u91CD\u8FDE\u914D\u7F6E\u5DF2\u66F4\u65B0",t)}getReconnectOptions(){return{...this.reconnectOptions}}resetReconnectState(){this.stopReconnect(),this.reconnectState.attempts=0,this.reconnectState.nextInterval=this.reconnectOptions.initialInterval,this.reconnectState.lastError=null,this.logger.info("\u91CD\u8FDE\u72B6\u6001\u5DF2\u91CD\u7F6E")}};export{v as ProxyMCPServer};
|
|
1
|
+
var y=Object.defineProperty;var p=(h,e)=>y(h,"name",{value:e,configurable:!0});import m from"ws";import*as c from"fs";import*as l from"path";import u from"chalk";import d from"pino";function S(h){let e=h.getFullYear(),t=String(h.getMonth()+1).padStart(2,"0"),i=String(h.getDate()).padStart(2,"0"),n=String(h.getHours()).padStart(2,"0"),o=String(h.getMinutes()).padStart(2,"0"),s=String(h.getSeconds()).padStart(2,"0");return`${e}-${t}-${i} ${n}:${o}:${s}`}p(S,"formatDateTime");var f=class{static{p(this,"Logger")}logFilePath=null;pinoInstance;isDaemonMode;maxLogFileSize=10*1024*1024;maxLogFiles=5;constructor(){this.isDaemonMode=process.env.XIAOZHI_DAEMON==="true",this.pinoInstance=this.createPinoInstance()}createPinoInstance(){let e=[];if(!this.isDaemonMode){let t=this.createOptimizedConsoleStream();e.push({level:"debug",stream:t})}return this.logFilePath&&e.push({level:"debug",stream:d.destination({dest:this.logFilePath,sync:!1,append:!0,mkdir:!0})}),e.length===0&&e.push({level:"debug",stream:d.destination({dest:"/dev/null"})}),d({level:"debug",timestamp:d.stdTimeFunctions?.isoTime||(()=>`,"time":${Date.now()}`),formatters:{level:p((t,i)=>({level:i}),"level")},base:null,serializers:{err:d.stdSerializers?.err||(t=>t)}},d.multistream(e,{dedupe:!0}))}createOptimizedConsoleStream(){let e=new Map([[20,{name:"DEBUG",color:u.gray}],[30,{name:"INFO",color:u.blue}],[40,{name:"WARN",color:u.yellow}],[50,{name:"ERROR",color:u.red}],[60,{name:"FATAL",color:u.red}]]);return{write:p(t=>{try{let i=JSON.parse(t),n=this.formatConsoleMessageOptimized(i,e);this.safeWrite(`${n}
|
|
2
|
+
`)}catch{this.safeWrite(t)}},"write")}}safeWrite(e){try{process.stderr&&typeof process.stderr.write=="function"?process.stderr.write(e):console&&typeof console.error=="function"&&console.error(e.trim())}catch{}}formatConsoleMessageOptimized(e,t){let i=S(new Date),n=t.get(e.level)||{name:"UNKNOWN",color:p(g=>g,"color")},o=n.color(`[${n.name}]`),s=e.msg;if(e.args&&Array.isArray(e.args)){let g=e.args.map(r=>typeof r=="object"?JSON.stringify(r):String(r)).join(" ");s=`${s} ${g}`}return`[${i}] ${o} ${s}`}initLogFile(e){this.logFilePath=l.join(e,"xiaozhi.log"),this.rotateLogFileIfNeeded(),c.existsSync(this.logFilePath)||c.writeFileSync(this.logFilePath,""),this.pinoInstance=this.createPinoInstance()}enableFileLogging(e){e&&this.logFilePath&&(this.pinoInstance=this.createPinoInstance())}info(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.info(e):this.pinoInstance.info({args:t},e):this.pinoInstance.info(e,t[0]||"")}success(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.info(e):this.pinoInstance.info({args:t},e):this.pinoInstance.info(e,t[0]||"")}warn(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.warn(e):this.pinoInstance.warn({args:t},e):this.pinoInstance.warn(e,t[0]||"")}error(e,...t){if(typeof e=="string")if(t.length===0)this.pinoInstance.error(e);else{let i=t.map(n=>n instanceof Error?{message:n.message,stack:n.stack,name:n.name,cause:n.cause}:n);this.pinoInstance.error({args:i},e)}else{let i=this.enhanceErrorObject(e);this.pinoInstance.error(i,t[0]||"")}}debug(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.debug(e):this.pinoInstance.debug({args:t},e):this.pinoInstance.debug(e,t[0]||"")}log(e,...t){typeof e=="string"?t.length===0?this.pinoInstance.info(e):this.pinoInstance.info({args:t},e):this.pinoInstance.info(e,t[0]||"")}enhanceErrorObject(e){let t={...e};for(let[i,n]of Object.entries(t))n instanceof Error&&(t[i]={message:n.message,stack:n.stack,name:n.name,cause:n.cause});return t}rotateLogFileIfNeeded(){if(!(!this.logFilePath||!c.existsSync(this.logFilePath)))try{c.statSync(this.logFilePath).size>this.maxLogFileSize&&this.rotateLogFile()}catch{}}rotateLogFile(){if(this.logFilePath)try{let e=l.dirname(this.logFilePath),t=l.basename(this.logFilePath,".log");for(let n=this.maxLogFiles-1;n>=1;n--){let o=l.join(e,`${t}.${n}.log`),s=l.join(e,`${t}.${n+1}.log`);c.existsSync(o)&&(n===this.maxLogFiles-1?c.unlinkSync(o):c.renameSync(o,s))}let i=l.join(e,`${t}.1.log`);c.renameSync(this.logFilePath,i)}catch{}}cleanupOldLogs(){if(this.logFilePath)try{let e=l.dirname(this.logFilePath),t=l.basename(this.logFilePath,".log");for(let i=this.maxLogFiles+1;i<=this.maxLogFiles+10;i++){let n=l.join(e,`${t}.${i}.log`);c.existsSync(n)&&c.unlinkSync(n)}}catch{}}setLogFileOptions(e,t){this.maxLogFileSize=e,this.maxLogFiles=t}withTag(e){return this}close(){}},C=new f;var T=(o=>(o[o.INVALID_PARAMS=-32602]="INVALID_PARAMS",o[o.TOOL_NOT_FOUND=-32601]="TOOL_NOT_FOUND",o[o.TOOL_EXECUTION_ERROR=-32e3]="TOOL_EXECUTION_ERROR",o[o.SERVICE_UNAVAILABLE=-32001]="SERVICE_UNAVAILABLE",o[o.TIMEOUT=-32002]="TIMEOUT",o))(T||{}),a=class extends Error{constructor(t,i,n){super(i);this.code=t;this.data=n;this.name="ToolCallError"}static{p(this,"ToolCallError")}},v=class{static{p(this,"ProxyMCPServer")}endpointUrl;ws=null;logger;isConnected=!1;serverInitialized=!1;tools=new Map;connectionState="disconnected";reconnectOptions;reconnectState={attempts:0,nextInterval:0,timer:null,lastError:null,isManualDisconnect:!1};connectionTimeout=null;performanceMetrics={totalCalls:0,successfulCalls:0,failedCalls:0,averageResponseTime:0,minResponseTime:Number.MAX_VALUE,maxResponseTime:0,successRate:0,lastUpdated:new Date};callRecords=[];maxCallRecords=100;retryConfig={maxAttempts:3,initialDelay:1e3,maxDelay:1e4,backoffMultiplier:2,retryableErrors:[-32001,-32002]};toolCallConfig={timeout:3e4,retryAttempts:3,retryDelay:1e3};constructor(e,t){this.endpointUrl=e,this.logger=C,this.reconnectOptions={enabled:!0,maxAttempts:10,initialInterval:3e3,maxInterval:3e4,backoffStrategy:"exponential",backoffMultiplier:1.5,timeout:1e4,jitter:!0,...t?.reconnect},this.reconnectState.nextInterval=this.reconnectOptions.initialInterval}setServiceManager(e){this.serviceManager=e,this.logger.info("\u5DF2\u8BBE\u7F6E MCPServiceManager"),this.syncToolsFromServiceManager()}syncToolsFromServiceManager(){let e=this.serviceManager;if(!e){this.logger.debug("MCPServiceManager \u672A\u8BBE\u7F6E\uFF0C\u8DF3\u8FC7\u5DE5\u5177\u540C\u6B65");return}try{let t=e.getAllTools(),i=new Map;for(let n of t)i.set(n.name,{name:n.name,description:n.description,inputSchema:n.inputSchema});this.tools=i,this.logger.info(`\u5DF2\u4ECE MCPServiceManager \u540C\u6B65 ${this.tools.size} \u4E2A\u5DE5\u5177`)}catch(t){this.logger.error(`\u540C\u6B65\u5DE5\u5177\u5931\u8D25: ${t instanceof Error?t.message:String(t)}`)}}addTool(e,t){return this.validateTool(e,t),this.tools.set(e,t),this.logger.debug(`\u5DE5\u5177 '${e}' \u5DF2\u6DFB\u52A0`),this}addTools(e){for(let[t,i]of Object.entries(e))this.addTool(t,i);return this}removeTool(e){return this.tools.delete(e)?this.logger.debug(`\u5DE5\u5177 '${e}' \u5DF2\u79FB\u9664`):this.logger.warn(`\u5C1D\u8BD5\u79FB\u9664\u4E0D\u5B58\u5728\u7684\u5DE5\u5177: '${e}'`),this}getTools(){try{this.syncToolsFromServiceManager()}catch{}return Array.from(this.tools.values())}hasTool(e){return this.tools.has(e)}validateTool(e,t){if(!e||typeof e!="string"||e.trim()==="")throw new Error("\u5DE5\u5177\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if(this.tools.has(e))throw new Error(`\u5DE5\u5177 '${e}' \u5DF2\u5B58\u5728`);if(!t||typeof t!="object")throw new Error("\u5DE5\u5177\u5FC5\u987B\u662F\u6709\u6548\u7684\u5BF9\u8C61");if(!t.name||typeof t.name!="string")throw new Error("\u5DE5\u5177\u5FC5\u987B\u5305\u542B\u6709\u6548\u7684 'name' \u5B57\u6BB5");if(!t.description||typeof t.description!="string")throw new Error("\u5DE5\u5177\u5FC5\u987B\u5305\u542B\u6709\u6548\u7684 'description' \u5B57\u6BB5");if(!t.inputSchema||typeof t.inputSchema!="object")throw new Error("\u5DE5\u5177\u5FC5\u987B\u5305\u542B\u6709\u6548\u7684 'inputSchema' \u5B57\u6BB5");if(!t.inputSchema.type||!t.inputSchema.properties)throw new Error("\u5DE5\u5177\u7684 inputSchema \u5FC5\u987B\u5305\u542B 'type' \u548C 'properties' \u5B57\u6BB5")}async connect(){if(this.tools.size===0)throw new Error("\u672A\u914D\u7F6E\u4EFB\u4F55\u5DE5\u5177\u3002\u8BF7\u5728\u8FDE\u63A5\u524D\u81F3\u5C11\u6DFB\u52A0\u4E00\u4E2A\u5DE5\u5177\u3002");if(this.connectionState==="connecting")throw new Error("\u8FDE\u63A5\u6B63\u5728\u8FDB\u884C\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u8FDE\u63A5\u5B8C\u6210");return this.cleanupConnection(),this.reconnectState.isManualDisconnect=!1,this.attemptConnection()}async attemptConnection(){return this.connectionState="connecting",this.logger.info(`\u6B63\u5728\u8FDE\u63A5 MCP \u63A5\u5165\u70B9: ${this.endpointUrl} (\u5C1D\u8BD5 ${this.reconnectState.attempts+1}/${this.reconnectOptions.maxAttempts})`),new Promise((e,t)=>{this.connectionTimeout=setTimeout(()=>{let i=new Error(`\u8FDE\u63A5\u8D85\u65F6 (${this.reconnectOptions.timeout}ms)`);this.handleConnectionError(i),t(i)},this.reconnectOptions.timeout),this.ws=new m(this.endpointUrl),this.ws.on("open",()=>{this.handleConnectionSuccess(),e()}),this.ws.on("message",i=>{try{let n=JSON.parse(i.toString());this.handleMessage(n)}catch(n){this.logger.error("MCP \u6D88\u606F\u89E3\u6790\u9519\u8BEF:",n)}}),this.ws.on("close",(i,n)=>{this.handleConnectionClose(i,n.toString())}),this.ws.on("error",i=>{this.handleConnectionError(i),t(i)})})}handleConnectionSuccess(){this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.isConnected=!0,this.connectionState="connected",this.reconnectState.attempts=0,this.reconnectState.nextInterval=this.reconnectOptions.initialInterval,this.reconnectState.lastError=null,this.logger.info("MCP WebSocket \u8FDE\u63A5\u5DF2\u5EFA\u7ACB")}handleConnectionError(e){this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.reconnectState.lastError=e,this.logger.error("MCP WebSocket \u9519\u8BEF:",e.message),this.cleanupConnection()}handleConnectionClose(e,t){if(this.isConnected=!1,this.serverInitialized=!1,this.logger.info(`MCP \u8FDE\u63A5\u5DF2\u5173\u95ED (\u4EE3\u7801: ${e}, \u539F\u56E0: ${t})`),this.reconnectState.isManualDisconnect){this.connectionState="disconnected";return}this.shouldReconnect()?this.scheduleReconnect():(this.connectionState="failed",this.logger.warn(`\u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8FDE\u6B21\u6570 (${this.reconnectOptions.maxAttempts})\uFF0C\u505C\u6B62\u91CD\u8FDE`))}shouldReconnect(){return this.reconnectOptions.enabled&&this.reconnectState.attempts<this.reconnectOptions.maxAttempts&&!this.reconnectState.isManualDisconnect}scheduleReconnect(){this.connectionState="reconnecting",this.reconnectState.attempts++,this.calculateNextInterval(),this.logger.info(`\u5C06\u5728 ${this.reconnectState.nextInterval}ms \u540E\u8FDB\u884C\u7B2C ${this.reconnectState.attempts} \u6B21\u91CD\u8FDE`),this.reconnectState.timer&&clearTimeout(this.reconnectState.timer),this.reconnectState.timer=setTimeout(async()=>{try{await this.attemptConnection()}catch{}},this.reconnectState.nextInterval)}calculateNextInterval(){let e;switch(this.reconnectOptions.backoffStrategy){case"fixed":e=this.reconnectOptions.initialInterval;break;case"linear":e=this.reconnectOptions.initialInterval+this.reconnectState.attempts*this.reconnectOptions.backoffMultiplier*1e3;break;case"exponential":e=this.reconnectOptions.initialInterval*this.reconnectOptions.backoffMultiplier**(this.reconnectState.attempts-1);break;default:e=this.reconnectOptions.initialInterval}if(e=Math.min(e,this.reconnectOptions.maxInterval),this.reconnectOptions.jitter){let t=e*.1,i=(Math.random()-.5)*2*t;e+=i}this.reconnectState.nextInterval=Math.max(e,1e3)}cleanupConnection(){if(this.ws){this.ws.removeAllListeners();try{this.ws.readyState===m.OPEN?this.ws.close(1e3,"Cleaning up connection"):this.ws.readyState===m.CONNECTING&&this.ws.terminate()}catch(e){this.logger.debug("WebSocket \u5173\u95ED\u65F6\u51FA\u73B0\u9519\u8BEF\uFF08\u5DF2\u5FFD\u7565\uFF09:",e)}this.ws=null}this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.isConnected=!1,this.serverInitialized=!1}stopReconnect(){this.reconnectState.timer&&(clearTimeout(this.reconnectState.timer),this.reconnectState.timer=null)}handleMessage(e){this.logger.debug("\u6536\u5230 MCP \u6D88\u606F:",JSON.stringify(e,null,2)),e.method&&this.handleServerRequest(e)}handleServerRequest(e){switch(e.method){case"initialize":case"notifications/initialized":this.sendResponse(e.id,{protocolVersion:"2024-11-05",capabilities:{tools:{listChanged:!0},logging:{}},serverInfo:{name:"xiaozhi-mcp-server",version:"1.0.0"}}),this.serverInitialized=!0,this.logger.info("MCP \u670D\u52A1\u5668\u521D\u59CB\u5316\u5B8C\u6210");break;case"tools/list":{let t=this.getTools();this.sendResponse(e.id,{tools:t}),this.logger.info(`MCP \u5DE5\u5177\u5217\u8868\u5DF2\u53D1\u9001 (${t.length}\u4E2A\u5DE5\u5177)`);break}case"tools/call":{this.handleToolCall(e).catch(t=>{this.logger.error("\u5904\u7406\u5DE5\u5177\u8C03\u7528\u65F6\u53D1\u751F\u672A\u6355\u83B7\u9519\u8BEF:",t)});break}case"ping":this.sendResponse(e.id,{}),this.logger.debug("\u56DE\u5E94 MCP ping \u6D88\u606F");break;default:this.logger.warn(`\u672A\u77E5\u7684 MCP \u8BF7\u6C42: ${e.method}`)}}sendResponse(e,t){if(this.logger.debug(`\u5C1D\u8BD5\u53D1\u9001\u54CD\u5E94: id=${e}, isConnected=${this.isConnected}, wsReadyState=${this.ws?.readyState}`),this.isConnected&&this.ws?.readyState===m.OPEN){let i={jsonrpc:"2.0",id:e,result:t};try{this.ws.send(JSON.stringify(i)),this.logger.info(`\u54CD\u5E94\u5DF2\u53D1\u9001: id=${e}`,{responseSize:JSON.stringify(i).length})}catch(n){this.logger.error(`\u53D1\u9001\u54CD\u5E94\u5931\u8D25: id=${e}`,n)}}else this.logger.error(`\u65E0\u6CD5\u53D1\u9001\u54CD\u5E94: id=${e}, \u8FDE\u63A5\u72B6\u6001\u68C0\u67E5\u5931\u8D25`,{isConnected:this.isConnected,wsReadyState:this.ws?.readyState,wsReadyStateText:this.ws?.readyState===m.OPEN?"OPEN":this.ws?.readyState===m.CONNECTING?"CONNECTING":this.ws?.readyState===m.CLOSING?"CLOSING":this.ws?.readyState===m.CLOSED?"CLOSED":"UNKNOWN"}),(!this.isConnected||this.ws?.readyState!==m.OPEN)&&(this.logger.warn(`\u5C1D\u8BD5\u91CD\u65B0\u8FDE\u63A5\u4EE5\u53D1\u9001\u54CD\u5E94: id=${e}`),this.scheduleReconnect())}getStatus(){return{connected:this.isConnected,initialized:this.serverInitialized,url:this.endpointUrl,availableTools:this.tools.size,connectionState:this.connectionState,reconnectAttempts:this.reconnectState.attempts,lastError:this.reconnectState.lastError?.message||null}}disconnect(){this.logger.info("\u4E3B\u52A8\u65AD\u5F00 MCP \u8FDE\u63A5"),this.reconnectState.isManualDisconnect=!0,this.stopReconnect(),this.cleanupConnection(),this.connectionState="disconnected"}async reconnect(){this.logger.info("\u624B\u52A8\u91CD\u8FDE MCP \u63A5\u5165\u70B9"),this.stopReconnect(),this.reconnectState.attempts=0,this.reconnectState.nextInterval=this.reconnectOptions.initialInterval,this.reconnectState.isManualDisconnect=!1,this.cleanupConnection(),await this.connect()}enableReconnect(){this.reconnectOptions.enabled=!0,this.logger.info("\u81EA\u52A8\u91CD\u8FDE\u5DF2\u542F\u7528")}disableReconnect(){this.reconnectOptions.enabled=!1,this.stopReconnect(),this.logger.info("\u81EA\u52A8\u91CD\u8FDE\u5DF2\u7981\u7528")}updateReconnectOptions(e){this.reconnectOptions={...this.reconnectOptions,...e},this.logger.info("\u91CD\u8FDE\u914D\u7F6E\u5DF2\u66F4\u65B0",e)}getReconnectOptions(){return{...this.reconnectOptions}}resetReconnectState(){this.stopReconnect(),this.reconnectState.attempts=0,this.reconnectState.nextInterval=this.reconnectOptions.initialInterval,this.reconnectState.lastError=null,this.logger.info("\u91CD\u8FDE\u72B6\u6001\u5DF2\u91CD\u7F6E")}async handleToolCall(e){if(e.id===void 0||e.id===null)throw new a(-32602,"\u8BF7\u6C42 ID \u4E0D\u80FD\u4E3A\u7A7A");let t=e.id,i=null;try{let n=this.validateToolCallParams(e.params);i=this.recordCallStart(n.name,t),this.logger.info(`\u5F00\u59CB\u5904\u7406\u5DE5\u5177\u8C03\u7528: ${n.name}`,{requestId:t,toolName:n.name,hasArguments:!!n.arguments});let o=this.serviceManager;if(!o)throw new a(-32001,"MCPServiceManager \u672A\u8BBE\u7F6E");let s=await this.executeToolWithRetry(o,n.name,n.arguments||{});this.sendResponse(t,{content:s.content||[{type:"text",text:JSON.stringify(s)}],isError:s.isError||!1}),i&&this.recordCallEnd(i,!0),this.logger.info(`\u5DE5\u5177\u8C03\u7528\u6210\u529F: ${n.name}`,{requestId:t,duration:i?.duration?`${i.duration}ms`:"unknown"})}catch(n){if(i){let o=n instanceof a?n.code:-32e3,s=n instanceof Error?n.message:"\u672A\u77E5\u9519\u8BEF";this.recordCallEnd(i,!1,o,s)}this.handleToolCallError(n,t,i?.duration||0)}}validateToolCallParams(e){if(!e||typeof e!="object")throw new a(-32602,"\u8BF7\u6C42\u53C2\u6570\u5FC5\u987B\u662F\u5BF9\u8C61");if(!e.name||typeof e.name!="string")throw new a(-32602,"\u5DE5\u5177\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if(e.arguments!==void 0&&(typeof e.arguments!="object"||Array.isArray(e.arguments)))throw new a(-32602,"\u5DE5\u5177\u53C2\u6570\u5FC5\u987B\u662F\u5BF9\u8C61");return{name:e.name,arguments:e.arguments}}async executeToolWithRetry(e,t,i){let n=null;for(let o=1;o<=this.retryConfig.maxAttempts;o++)try{return await this.executeToolWithTimeout(e,t,i,this.toolCallConfig.timeout)}catch(s){if(s instanceof a?n=s:n=new a(-32e3,s instanceof Error?s.message:"\u672A\u77E5\u9519\u8BEF"),this.retryConfig.retryableErrors.includes(n.code)&&o<this.retryConfig.maxAttempts){let g=Math.min(this.retryConfig.initialDelay*this.retryConfig.backoffMultiplier**(o-1),this.retryConfig.maxDelay);this.logger.warn(`\u5DE5\u5177\u8C03\u7528\u5931\u8D25\uFF0C\u5C06\u5728 ${g}ms \u540E\u91CD\u8BD5 (${o}/${this.retryConfig.maxAttempts})`,{toolName:t,error:n.message,attempt:o,delay:g}),await new Promise(r=>setTimeout(r,g));continue}break}throw n}async executeToolWithTimeout(e,t,i,n=3e4){return new Promise((o,s)=>{let g=setTimeout(()=>{s(new a(-32002,`\u5DE5\u5177\u8C03\u7528\u8D85\u65F6 (${n}ms): ${t}`))},n);e.callTool(t,i).then(r=>{clearTimeout(g),o(r)}).catch(r=>{clearTimeout(g),r.message?.includes("\u672A\u627E\u5230\u5DE5\u5177")?s(new a(-32601,`\u5DE5\u5177\u4E0D\u5B58\u5728: ${t}`)):r.message?.includes("\u670D\u52A1")&&r.message?.includes("\u4E0D\u53EF\u7528")?s(new a(-32001,r.message)):r.message?.includes("\u6682\u65F6\u4E0D\u53EF\u7528")?s(new a(-32001,r.message)):r.message?.includes("\u6301\u7EED\u4E0D\u53EF\u7528")?s(new a(-32001,r.message)):s(new a(-32e3,`\u5DE5\u5177\u6267\u884C\u5931\u8D25: ${r.message}`))})})}handleToolCallError(e,t,i){let n;e instanceof a?n={code:e.code,message:e.message,data:e.data}:n={code:-32e3,message:e?.message||"\u672A\u77E5\u9519\u8BEF",data:{originalError:e?.toString()||"null"}},this.sendErrorResponse(t,n),this.logger.error("\u5DE5\u5177\u8C03\u7528\u5931\u8D25",{requestId:t,duration:`${i}ms`,error:n})}sendErrorResponse(e,t){if(this.isConnected&&this.ws?.readyState===m.OPEN){let i={jsonrpc:"2.0",id:e,error:t};this.ws.send(JSON.stringify(i)),this.logger.debug("\u5DF2\u53D1\u9001\u9519\u8BEF\u54CD\u5E94:",i)}}recordCallStart(e,t){let i={id:String(t),toolName:e,startTime:new Date,success:!1};return this.callRecords.push(i),this.callRecords.length>this.maxCallRecords&&this.callRecords.shift(),i}recordCallEnd(e,t,i,n){e.endTime=new Date,e.duration=e.endTime.getTime()-e.startTime.getTime(),e.success=t,e.errorCode=i,e.errorMessage=n,this.updatePerformanceMetrics(e)}updatePerformanceMetrics(e){if(this.performanceMetrics.totalCalls++,e.success?this.performanceMetrics.successfulCalls++:this.performanceMetrics.failedCalls++,e.duration!==void 0){e.duration<this.performanceMetrics.minResponseTime&&(this.performanceMetrics.minResponseTime=e.duration),e.duration>this.performanceMetrics.maxResponseTime&&(this.performanceMetrics.maxResponseTime=e.duration);let t=this.callRecords.filter(n=>n.duration!==void 0).reduce((n,o)=>n+(o.duration||0),0),i=this.callRecords.filter(n=>n.duration!==void 0).length;this.performanceMetrics.averageResponseTime=i>0?t/i:0}this.performanceMetrics.successRate=this.performanceMetrics.totalCalls>0?this.performanceMetrics.successfulCalls/this.performanceMetrics.totalCalls*100:0,this.performanceMetrics.lastUpdated=new Date}getPerformanceMetrics(){return{...this.performanceMetrics}}getCallRecords(e){let t=[...this.callRecords].reverse();return e?t.slice(0,e):t}resetPerformanceMetrics(){this.performanceMetrics={totalCalls:0,successfulCalls:0,failedCalls:0,averageResponseTime:0,minResponseTime:Number.MAX_VALUE,maxResponseTime:0,successRate:0,lastUpdated:new Date},this.callRecords=[]}updateToolCallConfig(e){this.toolCallConfig={...this.toolCallConfig,...e},this.logger.info("\u5DE5\u5177\u8C03\u7528\u914D\u7F6E\u5DF2\u66F4\u65B0",this.toolCallConfig)}updateRetryConfig(e){this.retryConfig={...this.retryConfig,...e},this.logger.info("\u91CD\u8BD5\u914D\u7F6E\u5DF2\u66F4\u65B0",this.retryConfig)}getConfiguration(){return{toolCall:{...this.toolCallConfig},retry:{...this.retryConfig}}}getEnhancedStatus(){return{connected:this.isConnected,initialized:this.serverInitialized,url:this.endpointUrl,availableTools:this.tools.size,connectionState:this.connectionState,reconnectAttempts:this.reconnectState.attempts,lastError:this.reconnectState.lastError?.message||null,performance:this.getPerformanceMetrics(),configuration:this.getConfiguration()}}};export{v as ProxyMCPServer,a as ToolCallError,T as ToolCallErrorCode};
|
|
3
3
|
//# sourceMappingURL=ProxyMCPServer.js.map
|