xiaozhi-client 1.6.3-beta.1 → 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 +6 -1
- package/dist/ProxyMCPServer.js +2 -2
- package/dist/ProxyMCPServer.js.map +1 -1
- package/dist/WebServer.js +13 -12
- package/dist/WebServer.js.map +1 -1
- package/dist/WebServerStandalone.js +13 -12
- package/dist/WebServerStandalone.js.map +1 -1
- package/dist/cli.d.ts +0 -28
- package/dist/cli.js +45 -12
- 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 +3 -3
- 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
|
@@ -15,6 +15,11 @@ declare enum ToolCallErrorCode {
|
|
|
15
15
|
SERVICE_UNAVAILABLE = -32001,// 服务不可用
|
|
16
16
|
TIMEOUT = -32002
|
|
17
17
|
}
|
|
18
|
+
declare class ToolCallError extends Error {
|
|
19
|
+
code: ToolCallErrorCode;
|
|
20
|
+
data?: any | undefined;
|
|
21
|
+
constructor(code: ToolCallErrorCode, message: string, data?: any | undefined);
|
|
22
|
+
}
|
|
18
23
|
interface ToolCallOptions {
|
|
19
24
|
timeout?: number;
|
|
20
25
|
retryAttempts?: number;
|
|
@@ -286,4 +291,4 @@ declare class ProxyMCPServer {
|
|
|
286
291
|
};
|
|
287
292
|
}
|
|
288
293
|
|
|
289
|
-
export { ProxyMCPServer };
|
|
294
|
+
export { ProxyMCPServer, ToolCallError, ToolCallErrorCode };
|
package/dist/ProxyMCPServer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var y=Object.defineProperty;var
|
|
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:m(g=>g,"color")},s=n.color(`[${n.name}]`),o=e.msg;if(e.args&&Array.isArray(e.args)){let g=e.args.map(r=>typeof r=="object"?JSON.stringify(r):String(r)).join(" ");o=`${o} ${g}`}return`[${i}] ${s} ${o}`}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 s=l.join(e,`${t}.${n}.log`),o=l.join(e,`${t}.${n+1}.log`);c.existsSync(s)&&(n===this.maxLogFiles-1?c.unlinkSync(s):c.renameSync(s,o))}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 a=class extends Error{constructor(t,i,n){super(i);this.code=t;this.data=n;this.name="ToolCallError"}static{m(this,"ToolCallError")}},v=class{static{m(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 d(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===d.OPEN?this.ws.close(1e3,"Cleaning up connection"):this.ws.readyState===d.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.isConnected&&this.ws?.readyState===d.OPEN){let i={jsonrpc:"2.0",id:e,result:t};this.ws.send(JSON.stringify(i))}}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){let t=String(e.id||"unknown"),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 s=this.serviceManager;if(!s)throw new a(-32001,"MCPServiceManager \u672A\u8BBE\u7F6E");let o=await this.executeToolWithRetry(s,n.name,n.arguments||{});this.sendResponse(t,{content:o.content||[{type:"text",text:JSON.stringify(o)}],isError:o.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 s=n instanceof a?n.code:-32e3,o=n instanceof Error?n.message:"\u672A\u77E5\u9519\u8BEF";this.recordCallEnd(i,!1,s,o)}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 s=1;s<=this.retryConfig.maxAttempts;s++)try{return await this.executeToolWithTimeout(e,t,i,this.toolCallConfig.timeout)}catch(o){if(o instanceof a?n=o:n=new a(-32e3,o instanceof Error?o.message:"\u672A\u77E5\u9519\u8BEF"),this.retryConfig.retryableErrors.includes(n.code)&&s<this.retryConfig.maxAttempts){let g=Math.min(this.retryConfig.initialDelay*this.retryConfig.backoffMultiplier**(s-1),this.retryConfig.maxDelay);this.logger.warn(`\u5DE5\u5177\u8C03\u7528\u5931\u8D25\uFF0C\u5C06\u5728 ${g}ms \u540E\u91CD\u8BD5 (${s}/${this.retryConfig.maxAttempts})`,{toolName:t,error:n.message,attempt:s,delay:g}),await new Promise(r=>setTimeout(r,g));continue}break}throw n}async executeToolWithTimeout(e,t,i,n=3e4){return new Promise((s,o)=>{let g=setTimeout(()=>{o(new a(-32002,`\u5DE5\u5177\u8C03\u7528\u8D85\u65F6 (${n}ms): ${t}`))},n);e.callTool(t,i).then(r=>{clearTimeout(g),s(r)}).catch(r=>{clearTimeout(g),r.message?.includes("\u672A\u627E\u5230\u5DE5\u5177")?o(new a(-32601,`\u5DE5\u5177\u4E0D\u5B58\u5728: ${t}`)):r.message?.includes("\u670D\u52A1")&&r.message?.includes("\u4E0D\u53EF\u7528")?o(new a(-32001,r.message)):r.message?.includes("\u6682\u65F6\u4E0D\u53EF\u7528")?o(new a(-32001,r.message)):r.message?.includes("\u6301\u7EED\u4E0D\u53EF\u7528")?o(new a(-32001,r.message)):o(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===d.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: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,s)=>n+(s.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};
|
|
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
|