xiaozhi-client 1.6.0-beta.1 → 1.6.0-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.
@@ -1,12 +1,15 @@
1
- var x=Object.defineProperty;var p=(c,e)=>x(c,"name",{value:e,configurable:!0});import{spawn as M}from"child_process";import{randomUUID as $}from"crypto";import{EventEmitter as R}from"events";import h from"path";import{fileURLToPath as S}from"url";import y from"express";import u from"fs";import w from"path";import d from"chalk";import{createConsola as C}from"consola";function I(c){let e=c.getFullYear(),t=String(c.getMonth()+1).padStart(2,"0"),s=String(c.getDate()).padStart(2,"0"),r=String(c.getHours()).padStart(2,"0"),i=String(c.getMinutes()).padStart(2,"0"),n=String(c.getSeconds()).padStart(2,"0");return`${e}-${t}-${s} ${r}:${i}:${n}`}p(I,"formatDateTime");var f=class{static{p(this,"Logger")}logFilePath=null;writeStream=null;consolaInstance;isDaemonMode;constructor(){this.isDaemonMode=process.env.XIAOZHI_DAEMON==="true",this.consolaInstance=C({formatOptions:{date:!1,colors:!0,compact:!0},fancy:!1});let e=this.isDaemonMode;this.consolaInstance.setReporters([{log:p(t=>{let s={info:"INFO",success:"SUCCESS",warn:"WARN",error:"ERROR",debug:"DEBUG",log:"LOG"},r={info:d.blue,success:d.green,warn:d.yellow,error:d.red,debug:d.gray,log:p(l=>l,"log")},i=s[t.type]||t.type.toUpperCase(),n=r[t.type]||(l=>l),a=I(new Date),g=n(`[${i}]`),m=`[${a}] ${g} ${t.args.join(" ")}`;if(!e)try{console.error(m)}catch(l){if(l instanceof Error&&l.message?.includes("EPIPE"))return;throw l}},"log")}])}initLogFile(e){this.logFilePath=w.join(e,"xiaozhi.log"),u.existsSync(this.logFilePath)||u.writeFileSync(this.logFilePath,""),this.writeStream=u.createWriteStream(this.logFilePath,{flags:"a",encoding:"utf8"})}logToFile(e,t,...s){if(this.writeStream){let i=`[${new Date().toISOString()}] [${e.toUpperCase()}] ${t}`,n=s.length>0?`${i} ${s.map(a=>typeof a=="object"?JSON.stringify(a):String(a)).join(" ")}`:i;this.writeStream.write(`${n}
2
- `)}}enableFileLogging(e){e&&!this.writeStream&&this.logFilePath?this.writeStream=u.createWriteStream(this.logFilePath,{flags:"a",encoding:"utf8"}):!e&&this.writeStream&&(this.writeStream.end(),this.writeStream=null)}info(e,...t){this.consolaInstance.info(e,...t),this.logToFile("info",e,...t)}success(e,...t){this.consolaInstance.success(e,...t),this.logToFile("success",e,...t)}warn(e,...t){this.consolaInstance.warn(e,...t),this.logToFile("warn",e,...t)}error(e,...t){this.consolaInstance.error(e,...t),this.logToFile("error",e,...t)}debug(e,...t){this.consolaInstance.debug(e,...t),this.logToFile("debug",e,...t)}log(e,...t){this.consolaInstance.log(e,...t),this.logToFile("log",e,...t)}withTag(e){return this}close(){this.writeStream&&(this.writeStream.end(),this.writeStream=null)}},v=new f;var T=S(import.meta.url),k=h.dirname(T),o=v.withTag("mcp-server"),P=class extends R{static{p(this,"MCPServer")}app;server=null;clients=new Map;mcpProxy=null;port;constructor(e=3e3){super(),this.port=e,this.app=y(),this.setupMiddleware(),this.setupRoutes()}setupMiddleware(){this.app.use(y.json()),this.app.use(y.urlencoded({extended:!0})),this.app.use((e,t,s)=>{t.header("Access-Control-Allow-Origin","*"),t.header("Access-Control-Allow-Methods","GET, POST, OPTIONS"),t.header("Access-Control-Allow-Headers","Content-Type, Accept"),t.header("Cache-Control","no-cache"),s()})}setupRoutes(){this.app.get("/sse",(e,t)=>{let s=Date.now().toString(),r=$();t.setHeader("Content-Type","text/event-stream"),t.setHeader("Cache-Control","no-cache, no-transform"),t.setHeader("Connection","keep-alive"),t.setHeader("X-Accel-Buffering","no"),this.clients.set(r,{id:s,sessionId:r,response:t}),o.info(`MCP client connected: ${s} (session: ${r})`),t.write(`event: endpoint
3
- data: /messages?sessionId=${r}
1
+ var F=Object.defineProperty;var f=(a,e)=>F(a,"name",{value:e,configurable:!0});import{spawn as Z}from"child_process";import{randomUUID as q}from"crypto";import{EventEmitter as K}from"events";import y from"fs";import V from"os";import p from"path";import{fileURLToPath as O}from"url";import x from"express";import{copyFileSync as _,existsSync as E,readFileSync as N,writeFileSync as D}from"fs";import{dirname as W,resolve as d}from"path";import{fileURLToPath as H}from"url";import L from"json5";import*as $ from"jsonc-parser";var G=W(H(import.meta.url)),I={heartbeatInterval:3e4,heartbeatTimeout:1e4,reconnectInterval:5e3},M=class a{static{f(this,"ConfigManager")}static instance;defaultConfigPath;config=null;constructor(){this.defaultConfigPath=d(G,"xiaozhi.config.default.json")}getConfigFilePath(){let e=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),t=["xiaozhi.config.json5","xiaozhi.config.jsonc","xiaozhi.config.json"];for(let o of t){let n=d(e,o);if(E(n))return n}return d(e,"xiaozhi.config.json")}getConfigFileFormat(e){return e.endsWith(".json5")?"json5":e.endsWith(".jsonc")?"jsonc":"json"}static getInstance(){return a.instance||(a.instance=new a),a.instance}configExists(){let e=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),t=["xiaozhi.config.json5","xiaozhi.config.jsonc","xiaozhi.config.json"];for(let o of t){let n=d(e,o);if(E(n))return!0}return!1}initConfig(e="json"){if(!E(this.defaultConfigPath))throw new Error("\u9ED8\u8BA4\u914D\u7F6E\u6587\u4EF6 xiaozhi.config.default.json \u4E0D\u5B58\u5728");if(this.configExists())throw new Error("\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728\uFF0C\u65E0\u9700\u91CD\u590D\u521D\u59CB\u5316");let t=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),o=`xiaozhi.config.${e}`,n=d(t,o);_(this.defaultConfigPath,n),this.config=null}loadConfig(){if(!this.configExists())throw new Error("\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u8FD0\u884C xiaozhi init \u521D\u59CB\u5316\u914D\u7F6E");try{let e=this.getConfigFilePath(),t=this.getConfigFileFormat(e),o=N(e,"utf8"),n;switch(t){case"json5":n=L.parse(o);break;case"jsonc":n=$.parse(o);break;default:n=JSON.parse(o);break}return this.validateConfig(n),n}catch(e){throw e instanceof SyntaxError?new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF: ${e.message}`):e}}validateConfig(e){if(!e||typeof e!="object")throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1A\u6839\u5BF9\u8C61\u65E0\u6548");let t=e;if(t.mcpEndpoint===void 0||t.mcpEndpoint===null)throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u5B57\u6BB5\u65E0\u6548");if(typeof t.mcpEndpoint!="string")if(Array.isArray(t.mcpEndpoint)){if(t.mcpEndpoint.length===0)throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u6570\u7EC4\u4E0D\u80FD\u4E3A\u7A7A");for(let o of t.mcpEndpoint)if(typeof o!="string"||o.trim()==="")throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u6570\u7EC4\u4E2D\u7684\u6BCF\u4E2A\u5143\u7D20\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32")}else throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpEndpoint \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216\u5B57\u7B26\u4E32\u6570\u7EC4");if(!t.mcpServers||typeof t.mcpServers!="object")throw new Error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers \u5B57\u6BB5\u65E0\u6548");for(let[o,n]of Object.entries(t.mcpServers)){if(!n||typeof n!="object")throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers.${o} \u65E0\u6548`);let i=n;if(i.url&&typeof i.url=="string"){if(i.type&&i.type!=="sse"&&i.type!=="streamable-http")throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers.${o}.type \u5FC5\u987B\u662F "sse" \u6216 "streamable-http"`)}else{if(!i.command||typeof i.command!="string")throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers.${o}.command \u65E0\u6548`);if(!Array.isArray(i.args))throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers.${o}.args \u5FC5\u987B\u662F\u6570\u7EC4`);if(i.env&&typeof i.env!="object")throw new Error(`\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1AmcpServers.${o}.env \u5FC5\u987B\u662F\u5BF9\u8C61`)}}}getConfig(){return this.config||(this.config=this.loadConfig()),JSON.parse(JSON.stringify(this.config))}getMcpEndpoint(){let e=this.getConfig();return Array.isArray(e.mcpEndpoint)?e.mcpEndpoint[0]||"":e.mcpEndpoint}getMcpEndpoints(){let e=this.getConfig();return Array.isArray(e.mcpEndpoint)?[...e.mcpEndpoint]:e.mcpEndpoint?[e.mcpEndpoint]:[]}getMcpServers(){return this.getConfig().mcpServers}getMcpServerConfig(){return this.getConfig().mcpServerConfig||{}}getServerToolsConfig(e){return this.getMcpServerConfig()[e]?.tools||{}}isToolEnabled(e,t){return this.getServerToolsConfig(e)[t]?.enable!==!1}updateMcpEndpoint(e){if(Array.isArray(e)){if(e.length===0)throw new Error("MCP \u7AEF\u70B9\u6570\u7EC4\u4E0D\u80FD\u4E3A\u7A7A");for(let n of e)if(!n||typeof n!="string")throw new Error("MCP \u7AEF\u70B9\u6570\u7EC4\u4E2D\u7684\u6BCF\u4E2A\u5143\u7D20\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32")}else if(!e||typeof e!="string")throw new Error("MCP \u7AEF\u70B9\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let o={...this.getConfig(),mcpEndpoint:e};this.saveConfig(o)}addMcpEndpoint(e){if(!e||typeof e!="string")throw new Error("MCP \u7AEF\u70B9\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getConfig(),o=this.getMcpEndpoints();if(o.includes(e))throw new Error(`MCP \u7AEF\u70B9 ${e} \u5DF2\u5B58\u5728`);let n=[...o,e],i={...t,mcpEndpoint:n};this.saveConfig(i)}removeMcpEndpoint(e){if(!e||typeof e!="string")throw new Error("MCP \u7AEF\u70B9\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getConfig(),o=this.getMcpEndpoints();if(o.indexOf(e)===-1)throw new Error(`MCP \u7AEF\u70B9 ${e} \u4E0D\u5B58\u5728`);if(o.length===1)throw new Error("\u4E0D\u80FD\u5220\u9664\u6700\u540E\u4E00\u4E2A MCP \u7AEF\u70B9");let i=o.filter(g=>g!==e),c={...t,mcpEndpoint:i};this.saveConfig(c)}updateMcpServer(e,t){if(!e||typeof e!="string")throw new Error("\u670D\u52A1\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if("type"in t&&t.type==="sse"){if(!t.url||typeof t.url!="string")throw new Error("SSE \u670D\u52A1\u914D\u7F6E\u7684 url \u5B57\u6BB5\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32")}else{let i=t;if(!i.command||typeof i.command!="string")throw new Error("\u670D\u52A1\u914D\u7F6E\u7684 command \u5B57\u6BB5\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if(!Array.isArray(i.args))throw new Error("\u670D\u52A1\u914D\u7F6E\u7684 args \u5B57\u6BB5\u5FC5\u987B\u662F\u6570\u7EC4");if(i.env&&typeof i.env!="object")throw new Error("\u670D\u52A1\u914D\u7F6E\u7684 env \u5B57\u6BB5\u5FC5\u987B\u662F\u5BF9\u8C61")}let o=this.getConfig(),n={...o,mcpServers:{...o.mcpServers,[e]:t}};this.saveConfig(n)}removeMcpServer(e){if(!e||typeof e!="string")throw new Error("\u670D\u52A1\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");let t=this.getConfig();if(!t.mcpServers[e])throw new Error(`\u670D\u52A1 ${e} \u4E0D\u5B58\u5728`);let o={...t.mcpServers};delete o[e];let n={...t,mcpServers:o};this.saveConfig(n)}updateServerToolsConfig(e,t){let n={...this.getConfig()};n.mcpServerConfig||(n.mcpServerConfig={}),Object.keys(t).length===0?delete n.mcpServerConfig[e]:n.mcpServerConfig[e]={tools:t},this.saveConfig(n)}removeServerToolsConfig(e){let o={...this.getConfig()};o.mcpServerConfig&&(delete o.mcpServerConfig[e],this.saveConfig(o))}setToolEnabled(e,t,o,n){let c={...this.getConfig()};c.mcpServerConfig||(c.mcpServerConfig={}),c.mcpServerConfig[e]||(c.mcpServerConfig[e]={tools:{}}),c.mcpServerConfig[e].tools[t]={enable:o,...n&&{description:n}},this.saveConfig(c)}saveConfig(e){try{this.validateConfig(e);let t=process.env.XIAOZHI_CONFIG_DIR||process.cwd(),o=d(t,"xiaozhi.config.json"),n=JSON.stringify(e,null,2);D(o,n,"utf8"),this.config=e}catch(t){throw new Error(`\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25: ${t instanceof Error?t.message:String(t)}`)}}reloadConfig(){this.config=null}getConfigPath(){return this.getConfigFilePath()}getDefaultConfigPath(){return this.defaultConfigPath}getConnectionConfig(){let t=this.getConfig().connection||{};return{heartbeatInterval:t.heartbeatInterval??I.heartbeatInterval,heartbeatTimeout:t.heartbeatTimeout??I.heartbeatTimeout,reconnectInterval:t.reconnectInterval??I.reconnectInterval}}getHeartbeatInterval(){return this.getConnectionConfig().heartbeatInterval}getHeartbeatTimeout(){return this.getConnectionConfig().heartbeatTimeout}getReconnectInterval(){return this.getConnectionConfig().reconnectInterval}updateConnectionConfig(e){let t=this.getConfig(),n={...t.connection||{},...e},i={...t,connection:n};this.saveConfig(i)}setHeartbeatInterval(e){if(e<=0)throw new Error("\u5FC3\u8DF3\u68C0\u6D4B\u95F4\u9694\u5FC5\u987B\u5927\u4E8E0");this.updateConnectionConfig({heartbeatInterval:e})}setHeartbeatTimeout(e){if(e<=0)throw new Error("\u5FC3\u8DF3\u8D85\u65F6\u65F6\u95F4\u5FC5\u987B\u5927\u4E8E0");this.updateConnectionConfig({heartbeatTimeout:e})}setReconnectInterval(e){if(e<=0)throw new Error("\u91CD\u8FDE\u95F4\u9694\u5FC5\u987B\u5927\u4E8E0");this.updateConnectionConfig({reconnectInterval:e})}getModelScopeConfig(){return this.getConfig().modelscope||{}}getModelScopeApiKey(){return this.getModelScopeConfig().apiKey||process.env.MODELSCOPE_API_TOKEN}updateModelScopeConfig(e){let t=this.getConfig(),n={...t.modelscope||{},...e},i={...t,modelscope:n};this.saveConfig(i)}setModelScopeApiKey(e){if(!e||typeof e!="string")throw new Error("API Key \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");this.updateModelScopeConfig({apiKey:e})}getWebUIConfig(){return this.getConfig().webUI||{}}getWebUIPort(){return this.getWebUIConfig().port??9999}updateWebUIConfig(e){let t=this.getConfig(),n={...t.webUI||{},...e},i={...t,webUI:n};this.saveConfig(i)}setWebUIPort(e){if(!Number.isInteger(e)||e<=0||e>65535)throw new Error("\u7AEF\u53E3\u53F7\u5FC5\u987B\u662F 1-65535 \u4E4B\u95F4\u7684\u6574\u6570");this.updateWebUIConfig({port:e})}},u=M.getInstance();import w from"fs";import J from"path";import C from"chalk";import{createConsola as B}from"consola";function z(a){let e=a.getFullYear(),t=String(a.getMonth()+1).padStart(2,"0"),o=String(a.getDate()).padStart(2,"0"),n=String(a.getHours()).padStart(2,"0"),i=String(a.getMinutes()).padStart(2,"0"),c=String(a.getSeconds()).padStart(2,"0");return`${e}-${t}-${o} ${n}:${i}:${c}`}f(z,"formatDateTime");var T=class{static{f(this,"Logger")}logFilePath=null;writeStream=null;consolaInstance;isDaemonMode;constructor(){this.isDaemonMode=process.env.XIAOZHI_DAEMON==="true",this.consolaInstance=B({formatOptions:{date:!1,colors:!0,compact:!0},fancy:!1});let e=this.isDaemonMode;this.consolaInstance.setReporters([{log:f(t=>{let o={info:"INFO",success:"SUCCESS",warn:"WARN",error:"ERROR",debug:"DEBUG",log:"LOG"},n={info:C.blue,success:C.green,warn:C.yellow,error:C.red,debug:C.gray,log:f(h=>h,"log")},i=o[t.type]||t.type.toUpperCase(),c=n[t.type]||(h=>h),g=z(new Date),j=c(`[${i}]`),k=`[${g}] ${j} ${t.args.join(" ")}`;if(!e)try{console.error(k)}catch(h){if(h instanceof Error&&h.message?.includes("EPIPE"))return;throw h}},"log")}])}initLogFile(e){this.logFilePath=J.join(e,"xiaozhi.log"),w.existsSync(this.logFilePath)||w.writeFileSync(this.logFilePath,""),this.writeStream=w.createWriteStream(this.logFilePath,{flags:"a",encoding:"utf8"})}logToFile(e,t,...o){if(this.writeStream){let i=`[${new Date().toISOString()}] [${e.toUpperCase()}] ${t}`,c=o.length>0?`${i} ${o.map(g=>typeof g=="object"?JSON.stringify(g):String(g)).join(" ")}`:i;this.writeStream.write(`${c}
2
+ `)}}enableFileLogging(e){e&&!this.writeStream&&this.logFilePath?this.writeStream=w.createWriteStream(this.logFilePath,{flags:"a",encoding:"utf8"}):!e&&this.writeStream&&(this.writeStream.end(),this.writeStream=null)}info(e,...t){this.consolaInstance.info(e,...t),this.logToFile("info",e,...t)}success(e,...t){this.consolaInstance.success(e,...t),this.logToFile("success",e,...t)}warn(e,...t){this.consolaInstance.warn(e,...t),this.logToFile("warn",e,...t)}error(e,...t){this.consolaInstance.error(e,...t),this.logToFile("error",e,...t)}debug(e,...t){this.consolaInstance.debug(e,...t),this.logToFile("debug",e,...t)}log(e,...t){this.consolaInstance.log(e,...t),this.logToFile("log",e,...t)}withTag(e){return this}close(){this.writeStream&&(this.writeStream.end(),this.writeStream=null)}},m=new T;import{spawn as X}from"child_process";import l from"process";import v from"ws";var R=f(()=>l.env.NODE_ENV==="test"||l.env.VITEST==="true","isTestEnvironment"),r=m.withTag("MULTI_MCP_PIPE");l.env.XIAOZHI_DAEMON==="true"&&l.env.XIAOZHI_CONFIG_DIR&&(m.initLogFile(l.env.XIAOZHI_CONFIG_DIR),m.enableFileLogging(!0));var S=class{static{f(this,"MultiEndpointMCPPipe")}mcpScript;endpoints;shouldReconnect;shutdownResolve;connectionConfig;constructor(e,t){this.mcpScript=e,this.endpoints=new Map,this.shouldReconnect=!0,r.info(t.length===1?`\u521D\u59CB\u5316\u5355\u7AEF\u70B9\u8FDE\u63A5: ${t[0]}`:`\u521D\u59CB\u5316\u591A\u7AEF\u70B9\u8FDE\u63A5\uFF08${t.length} \u4E2A\u7AEF\u70B9\uFF09`);for(let o of t)this.endpoints.set(o,{url:o,websocket:null,isConnected:!1,reconnectAttempt:0,maxReconnectAttempts:5,process:null,stdoutBuffer:""});try{this.connectionConfig=u.getConnectionConfig(),r.info(`\u8FDE\u63A5\u914D\u7F6E: \u5FC3\u8DF3\u95F4\u9694=${this.connectionConfig.heartbeatInterval}ms, \u5FC3\u8DF3\u8D85\u65F6=${this.connectionConfig.heartbeatTimeout}ms, \u91CD\u8FDE\u95F4\u9694=${this.connectionConfig.reconnectInterval}ms`)}catch(o){this.connectionConfig={heartbeatInterval:3e4,heartbeatTimeout:1e4,reconnectInterval:5e3},r.warn(`\u65E0\u6CD5\u83B7\u53D6\u8FDE\u63A5\u914D\u7F6E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C: ${o instanceof Error?o.message:String(o)}`)}}async start(){return await this.connectToAllEndpoints(),this.reportStatusToWebUI(),new Promise(e=>{this.shutdownResolve=e})}async connectToAllEndpoints(){let e=[];for(let[t,o]of this.endpoints)e.push(this.connectToEndpoint(t));await Promise.allSettled(e)}async connectToEndpoint(e){let t=this.endpoints.get(e);if(!t||t.isConnected)return;this.startMCPProcessForEndpoint(e),r.info(`\u6B63\u5728\u8FDE\u63A5\u5230 WebSocket \u670D\u52A1\u5668: ${e}`);let o=new v(e);t.websocket=o,o.on("open",()=>{r.info(`\u6210\u529F\u8FDE\u63A5\u5230 WebSocket \u670D\u52A1\u5668: ${e}`),t.isConnected=!0,t.reconnectAttempt=0,t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),this.reportStatusToWebUI(),this.startHeartbeat(e)}),o.on("message",n=>{let i=n.toString();r.info(`<< [${e}] WebSocket\u6536\u5230\u6D88\u606F: ${i}`);try{let c=JSON.parse(i);(c.method==="notifications/initialized"||c.method==="tools/list"&&c.id||c?.result?.tools)&&setTimeout(()=>{this.reportStatusToWebUI()},1e3)}catch{}t.process?.stdin&&!t.process.stdin.destroyed&&t.process.stdin.write(`${i}
3
+ `)}),o.on("close",(n,i)=>{r.error(`[${e}] WebSocket \u8FDE\u63A5\u5DF2\u5173\u95ED: ${n} ${i}`),t.isConnected=!1,t.websocket=null,this.stopHeartbeat(e),this.reportStatusToWebUI(),this.shouldReconnect&&(n===4004?t.reconnectAttempt<t.maxReconnectAttempts?(r.warn(`[${e}] \u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF(4004)\uFF0C\u5C06\u8FDB\u884C\u7B2C ${t.reconnectAttempt+1} \u6B21\u91CD\u8FDE\u5C1D\u8BD5\uFF08\u6700\u591A ${t.maxReconnectAttempts} \u6B21\uFF09`),this.scheduleReconnect(e)):r.error(`[${e}] \u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF(4004)\uFF0C\u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8FDE\u6B21\u6570(${t.maxReconnectAttempts})\uFF0C\u505C\u6B62\u91CD\u8FDE`):this.scheduleReconnect(e))}),o.on("error",n=>{r.error(`[${e}] WebSocket \u9519\u8BEF: ${n.message}`),t.isConnected=!1,this.stopHeartbeat(e)}),o.on("pong",()=>{t.heartbeatTimeoutTimer&&(clearTimeout(t.heartbeatTimeoutTimer),t.heartbeatTimeoutTimer=void 0)})}scheduleReconnect(e){let t=this.endpoints.get(e);if(!t||!this.shouldReconnect)return;t.reconnectTimer&&clearTimeout(t.reconnectTimer),t.reconnectAttempt++;let o=this.connectionConfig.reconnectInterval,i=Math.min(o*2**(t.reconnectAttempt-1),6e4);r.info(`[${e}] \u8BA1\u5212\u5728 ${(i/1e3).toFixed(2)} \u79D2\u540E\u8FDB\u884C\u7B2C ${t.reconnectAttempt} \u6B21\u91CD\u8FDE\u5C1D\u8BD5...`),t.reconnectTimer=setTimeout(async()=>{this.shouldReconnect&&(await this.cleanupEndpointResources(e),(!t.process||t.process.killed)&&r.info(`[${e}] MCP \u8FDB\u7A0B\u672A\u8FD0\u884C\uFF0C\u5C06\u5728\u91CD\u8FDE\u65F6\u542F\u52A8...`),this.connectToEndpoint(e))},i)}startMCPProcessForEndpoint(e){let t=this.endpoints.get(e);if(!t){r.error(`\u7AEF\u70B9\u4E0D\u5B58\u5728: ${e}`);return}if(t.process){r.info(`[${e}] MCP \u8FDB\u7A0B\u5DF2\u5728\u8FD0\u884C`);return}r.info(`[${e}] \u6B63\u5728\u542F\u52A8 MCP \u8FDB\u7A0B`),t.process=X("node",[this.mcpScript],{stdio:["pipe","pipe","pipe"]}),t.process.stdout?.on("data",o=>{t.stdoutBuffer+=o.toString();let n=t.stdoutBuffer.split(`
4
+ `);t.stdoutBuffer=n.pop()||"";for(let i of n)i.trim()&&this.handleMCPMessage(e,i)}),t.process.stderr?.on("data",o=>{if(l.env.XIAOZHI_DAEMON!=="true")try{l.stderr.write(o)}catch{}}),t.process.on("exit",(o,n)=>{r.warn(`[${e}] MCP \u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${o}, \u4FE1\u53F7: ${n}`),t.process=null,this.shouldReconnect&&n!=="SIGTERM"&&n!=="SIGKILL"&&r.info(`[${e}] MCP \u8FDB\u7A0B\u610F\u5916\u9000\u51FA\uFF0C\u5C06\u5728\u4E0B\u6B21\u91CD\u8FDE\u65F6\u5C1D\u8BD5\u91CD\u542F`)}),t.process.on("error",o=>{r.error(`[${e}] \u8FDB\u7A0B\u9519\u8BEF: ${o.message}`),t.process=null,this.shouldReconnect&&r.info(`[${e}] MCP \u8FDB\u7A0B\u53D1\u751F\u9519\u8BEF\uFF0C\u5C06\u5728\u4E0B\u6B21\u91CD\u8FDE\u65F6\u5C1D\u8BD5\u91CD\u542F`)})}handleMCPMessage(e,t){r.info(`>> [${e}] mcpServerProxy\u53D1\u9001\u6D88\u606F\u957F\u5EA6: ${t.length} \u5B57\u8282`),r.info(`>> [${e}] mcpServerProxy\u53D1\u9001\u6D88\u606F: ${t.substring(0,500)}...`),this.sendToEndpoint(e,t)}sendToEndpoint(e,t){let o=this.endpoints.get(e);if(!o||!o.websocket||o.websocket.readyState!==v.OPEN){r.warn(`[${e}] \u7AEF\u70B9\u4E0D\u53EF\u7528\uFF0C\u6D88\u606F\u65E0\u6CD5\u53D1\u9001`);return}try{o.websocket.send(`${t}
5
+ `),r.info(`>> [${e}] \u6210\u529F\u53D1\u9001\u6D88\u606F\u5230 WebSocket`)}catch(n){r.error(`>> [${e}] \u53D1\u9001\u6D88\u606F\u5230 WebSocket \u5931\u8D25: ${n}`)}}startHeartbeat(e){let t=this.endpoints.get(e);t&&(this.stopHeartbeat(e),t.heartbeatTimer=setInterval(()=>{t.websocket&&t.websocket.readyState===v.OPEN&&(t.websocket.ping(),t.heartbeatTimeoutTimer=setTimeout(()=>{r.warn(`[${e}] \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u65AD\u5F00\u8FDE\u63A5`),t.websocket?.close()},this.connectionConfig.heartbeatTimeout),this.reportStatusToWebUI())},this.connectionConfig.heartbeatInterval))}stopHeartbeat(e){let t=this.endpoints.get(e);t&&(t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=void 0),t.heartbeatTimeoutTimer&&(clearTimeout(t.heartbeatTimeoutTimer),t.heartbeatTimeoutTimer=void 0))}async cleanupEndpointResources(e){let t=this.endpoints.get(e);if(t){if(r.debug(`[${e}] \u6E05\u7406\u7AEF\u70B9\u8D44\u6E90...`),this.stopHeartbeat(e),t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),t.websocket){try{t.websocket.readyState===v.OPEN&&t.websocket.close()}catch(o){r.debug(`[${e}] \u5173\u95ED WebSocket \u65F6\u51FA\u9519: ${o}`)}t.websocket=null}if(t.process&&!t.process.killed){try{r.debug(`[${e}] \u7EC8\u6B62 MCP \u8FDB\u7A0B...`),t.process.kill("SIGTERM"),await new Promise(o=>{let n=setTimeout(()=>{t.process&&!t.process.killed&&(r.warn(`[${e}] MCP \u8FDB\u7A0B\u672A\u80FD\u6B63\u5E38\u9000\u51FA\uFF0C\u5F3A\u5236\u7EC8\u6B62`),t.process.kill("SIGKILL")),o()},2e3);t.process?.on("exit",()=>{clearTimeout(n),o()})})}catch(o){r.warn(`[${e}] \u7EC8\u6B62 MCP \u8FDB\u7A0B\u65F6\u51FA\u9519: ${o}`)}t.process=null}t.stdoutBuffer="",t.isConnected=!1,r.debug(`[${e}] \u7AEF\u70B9\u8D44\u6E90\u6E05\u7406\u5B8C\u6210`)}}cleanup(){for(let e of this.endpoints.keys())this.stopHeartbeat(e);for(let[e,t]of this.endpoints){if(t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),t.stdoutBuffer="",t.process){r.info(`[${e}] \u6B63\u5728\u7EC8\u6B62 MCP \u8FDB\u7A0B`);try{t.process.kill("SIGTERM"),setTimeout(()=>{t.process&&!t.process.killed&&t.process.kill("SIGKILL")},5e3)}catch(o){r.error(`[${e}] \u7EC8\u6B62\u8FDB\u7A0B\u65F6\u51FA\u9519: ${o instanceof Error?o.message:String(o)}`)}t.process=null}if(t.websocket){try{t.websocket.close()}catch(o){r.warn(`[${e}] \u5173\u95ED WebSocket \u65F6\u51FA\u9519: ${o}`)}t.websocket=null}}}shutdown(){r.info("\u6B63\u5728\u5173\u95ED Multi-Endpoint MCP Pipe..."),this.shouldReconnect=!1;for(let e of this.endpoints.values())e.isConnected=!1;this.reportStatusToWebUI(),this.cleanup(),this.shutdownResolve&&this.shutdownResolve(),R()||setTimeout(()=>{l.exit(0)},100)}async reportStatusToWebUI(){if(!R())try{let e=u.getWebUIPort(),t=new v(`ws://localhost:${e}`);t.on("open",()=>{let o=[];for(let[i,c]of this.endpoints)o.push({url:i,connected:c.isConnected});let n={type:"clientStatus",data:{status:this.hasAnyConnection()?"connected":"disconnected",mcpEndpoints:o,activeMCPServers:[],lastHeartbeat:Date.now()}};t.send(JSON.stringify(n)),r.debug("\u5DF2\u5411 Web UI \u62A5\u544A\u72B6\u6001"),setTimeout(()=>{t.close()},1e3)}),t.on("error",o=>{r.debug(`Web UI \u8FDE\u63A5\u5931\u8D25\uFF08\u53EF\u80FD\u672A\u8FD0\u884C\uFF09: ${o.message}`)})}catch(e){r.debug(`\u5411 Web UI \u62A5\u544A\u72B6\u6001\u5931\u8D25: ${e instanceof Error?e.message:String(e)}`)}}hasAnyConnection(){for(let e of this.endpoints.values())if(e.isConnected)return!0;return!1}};var Y=O(import.meta.url),P=p.dirname(Y),s=m.withTag("mcp-server"),b="mcpServerProxy.js",A=class extends K{static{f(this,"MCPServer")}app;server=null;clients=new Map;mcpProxy=null;mcpClient=null;mcpProxyPath=null;port;constructor(e=3e3){super(),this.port=e,this.app=x(),this.setupMiddleware(),this.setupRoutes()}setupMiddleware(){this.app.use(x.json()),this.app.use(x.urlencoded({extended:!0})),this.app.use((e,t,o)=>{t.header("Access-Control-Allow-Origin","*"),t.header("Access-Control-Allow-Methods","GET, POST, OPTIONS"),t.header("Access-Control-Allow-Headers","Content-Type, Accept"),t.header("Cache-Control","no-cache"),o()})}setupRoutes(){this.app.get("/sse",(e,t)=>{let o=Date.now().toString(),n=q();t.setHeader("Content-Type","text/event-stream"),t.setHeader("Cache-Control","no-cache, no-transform"),t.setHeader("Connection","keep-alive"),t.setHeader("X-Accel-Buffering","no"),this.clients.set(n,{id:o,sessionId:n,response:t}),s.info(`MCP\u5BA2\u6237\u7AEF\u5DF2\u8FDE\u63A5: ${o} (\u4F1A\u8BDD: ${n})`),t.write(`event: endpoint
6
+ data: /messages?sessionId=${n}
4
7
 
5
- `),e.on("close",()=>{this.clients.delete(r),o.info(`MCP client disconnected: ${s} (session: ${r})`)})}),this.app.post("/messages",async(e,t)=>{try{let s=e.query.sessionId,r=e.body;if(o.info(`Received message via SSE transport (session: ${s}):`,JSON.stringify(r)),!s||!this.clients.has(s)){t.status(400).json({jsonrpc:"2.0",error:{code:-32600,message:"Invalid or missing sessionId"},id:r.id});return}if(!this.mcpProxy){t.status(503).json({jsonrpc:"2.0",error:{code:-32603,message:"MCP proxy not running"},id:r.id});return}if(r.id===void 0)o.info(`Forwarding notification: ${r.method}`),this.mcpProxy.stdin.write(`${JSON.stringify(r)}
6
- `),t.status(202).send();else{let i=await this.forwardToProxy(r),n=this.clients.get(s);n&&this.sendToClient(n,i),t.status(202).send()}}catch(s){o.error("SSE message error:",s),t.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:s instanceof Error?s.message:"Internal error"},id:e.body.id})}}),this.app.post("/rpc",async(e,t)=>{try{let s=e.body;if(o.debug("Received RPC message:",s),!this.mcpProxy){t.status(503).json({jsonrpc:"2.0",error:{code:-32603,message:"MCP proxy not running"},id:s.id});return}let r=await this.forwardToProxy(s);t.json(r)}catch(s){o.error("RPC error:",s),t.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:s instanceof Error?s.message:"Internal error"},id:e.body.id})}}),this.app.get("/health",(e,t)=>{t.json({status:"ok",mode:"mcp-server",proxy:this.mcpProxy?"running":"stopped",clients:this.clients.size})})}responseBuffer="";pendingRequests=new Map;async forwardToProxy(e){return new Promise((t,s)=>{if(!this.mcpProxy||!this.mcpProxy.stdin||!this.mcpProxy.stdout){s(new Error("MCP proxy not available"));return}let r=setTimeout(()=>{this.pendingRequests.delete(e.id),o.warn(`Request timeout for message id: ${e.id}, method: ${e.method} - This may be normal if the response was already sent via SSE`),t({jsonrpc:"2.0",id:e.id,result:{_timeout:!0,message:"Response may have been sent via SSE"}})},3e4);this.pendingRequests.set(e.id,{resolve:t,reject:s,timeoutId:r}),o.info(`Forwarding message to proxy: ${JSON.stringify(e)}`),this.mcpProxy.stdin.write(`${JSON.stringify(e)}
8
+ `),e.on("close",()=>{this.clients.delete(n),s.info(`MCP\u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00\u8FDE\u63A5: ${o} (\u4F1A\u8BDD: ${n})`)})}),this.app.post("/messages",async(e,t)=>{try{let o=e.query.sessionId,n=e.body;if(s.info(`\u901A\u8FC7SSE\u4F20\u8F93\u6536\u5230\u6D88\u606F (\u4F1A\u8BDD: ${o}):`,JSON.stringify(n)),!o||!this.clients.has(o)){t.status(400).json({jsonrpc:"2.0",error:{code:-32600,message:"\u65E0\u6548\u6216\u7F3A\u5C11sessionId"},id:n.id});return}if(!this.mcpProxy){t.status(503).json({jsonrpc:"2.0",error:{code:-32603,message:"MCP\u4EE3\u7406\u672A\u8FD0\u884C"},id:n.id});return}if(n.id===void 0)s.info(`\u8F6C\u53D1\u901A\u77E5: ${n.method}`),this.mcpProxy.stdin.write(`${JSON.stringify(n)}
9
+ `),t.status(202).send();else{let i=await this.forwardToProxy(n),c=this.clients.get(o);c&&this.sendToClient(c,i),t.status(202).send()}}catch(o){s.error("SSE\u6D88\u606F\u9519\u8BEF:",o),t.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:o instanceof Error?o.message:"\u5185\u90E8\u9519\u8BEF"},id:e.body.id})}}),this.app.post("/rpc",async(e,t)=>{try{let o=e.body;if(s.debug("\u6536\u5230RPC\u6D88\u606F:",o),!this.mcpProxy){t.status(503).json({jsonrpc:"2.0",error:{code:-32603,message:"MCP\u4EE3\u7406\u672A\u8FD0\u884C"},id:o.id});return}let n=await this.forwardToProxy(o);t.json(n)}catch(o){s.error("RPC\u9519\u8BEF:",o),t.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:o instanceof Error?o.message:"\u5185\u90E8\u9519\u8BEF"},id:e.body.id})}}),this.app.get("/health",(e,t)=>{t.json({status:"ok",mode:"mcp-server",proxy:this.mcpProxy?"running":"stopped",clients:this.clients.size})})}responseBuffer="";pendingRequests=new Map;async forwardToProxy(e){return new Promise((t,o)=>{if(!this.mcpProxy||!this.mcpProxy.stdin||!this.mcpProxy.stdout){o(new Error("MCP\u4EE3\u7406\u4E0D\u53EF\u7528"));return}let n=setTimeout(()=>{this.pendingRequests.delete(e.id),s.warn(`\u6D88\u606F\u8D85\u65F6 id: ${e.id}, \u65B9\u6CD5: ${e.method} - \u5982\u679C\u54CD\u5E94\u5DF2\u901A\u8FC7SSE\u53D1\u9001\uFF0C\u8FD9\u662F\u6B63\u5E38\u7684`),t({jsonrpc:"2.0",id:e.id,result:{_timeout:!0,message:"\u54CD\u5E94\u53EF\u80FD\u5DF2\u901A\u8FC7SSE\u53D1\u9001"}})},3e4);this.pendingRequests.set(e.id,{resolve:t,reject:o,timeoutId:n}),s.info(`\u8F6C\u53D1\u6D88\u606F\u5230\u4EE3\u7406: ${JSON.stringify(e)}`),this.mcpProxy.stdin.write(`${JSON.stringify(e)}
7
10
  `)})}handleProxyResponse(e){try{this.responseBuffer+=e.toString();let t=this.responseBuffer.split(`
8
- `);this.responseBuffer=t.pop()||"";for(let s of t)if(s.trim())try{let r=JSON.parse(s);if(o.debug(`Received response from proxy: ${s.substring(0,200)}...`),r.id!==void 0&&this.pendingRequests.has(r.id)){let i=this.pendingRequests.get(r.id);clearTimeout(i.timeoutId),this.pendingRequests.delete(r.id),i.resolve(r)}}catch{o.debug(`Non-JSON line from proxy: ${s}`)}}catch(t){o.error("Error handling proxy response:",t)}}sendToClient(e,t){try{let s=`event: message
11
+ `);this.responseBuffer=t.pop()||"";for(let o of t)if(o.trim())try{let n=JSON.parse(o);if(s.debug(`\u6536\u5230\u4EE3\u7406\u54CD\u5E94: ${o.substring(0,200)}...`),n.id!==void 0&&this.pendingRequests.has(n.id)){let i=this.pendingRequests.get(n.id);clearTimeout(i.timeoutId),this.pendingRequests.delete(n.id),i.resolve(n)}}catch{s.debug(`\u6765\u81EA\u4EE3\u7406\u7684\u975EJSON\u884C: ${o}`)}}catch(t){s.error("\u5904\u7406\u4EE3\u7406\u54CD\u5E94\u65F6\u51FA\u9519:",t)}}sendToClient(e,t){try{let o=`event: message
9
12
  data: ${JSON.stringify(t)}
10
13
 
11
- `;e.response.write(s)}catch(s){o.error(`Failed to send to client ${e.id}:`,s),this.clients.delete(e.sessionId)}}broadcastToClients(e){for(let t of this.clients.values())this.sendToClient(t,e)}async start(){try{await this.startMCPProxy(),this.server=this.app.listen(this.port,()=>{o.info(`MCP Server listening on port ${this.port}`),o.info(`SSE endpoint: http://localhost:${this.port}/sse`),o.info(`Messages endpoint: http://localhost:${this.port}/messages`),o.info(`RPC endpoint: http://localhost:${this.port}/rpc`)}),this.emit("started")}catch(e){throw o.error("Failed to start MCP server:",e),e}}async startMCPProxy(){let e=S(import.meta.url),t=h.dirname(e),s=null;for(let r=0;r<5;r++){let i=h.join(t,"mcpServerProxy.js"),n=await import("fs");if(n.existsSync(i)){s=i;break}let a=h.join(t,"dist","mcpServerProxy.js");if(n.existsSync(a)){s=a;break}t=h.dirname(t)}if(!s)throw new Error("Could not find mcpServerProxy.js in the project structure");o.info(`Starting MCP proxy from: ${s}`),this.mcpProxy=M("node",[s],{stdio:["pipe","pipe","pipe"],env:{...process.env,MCP_SERVER_MODE:"true",XIAOZHI_CONFIG_DIR:process.env.XIAOZHI_CONFIG_DIR||process.cwd()}}),this.mcpProxy.on("error",r=>{o.error("MCP proxy error:",r)}),this.mcpProxy.on("exit",(r,i)=>{o.warn(`MCP proxy exited with code ${r}, signal ${i}`),this.mcpProxy=null}),this.mcpProxy.stderr&&this.mcpProxy.stderr.on("data",r=>{let i=r.toString().trim();i.includes("[ERROR]")||i.includes("Error:")||i.includes("Failed")?o.error("MCP proxy stderr:",i):o.info("MCP proxy output:",i)}),this.mcpProxy.stdout&&this.mcpProxy.stdout.on("data",r=>{this.handleProxyResponse(r)}),await new Promise((r,i)=>{let n=setTimeout(()=>{i(new Error("MCP proxy startup timeout"))},1e4),a=p(g=>{let m=g.toString();(m.includes("MCP proxy ready")||m.includes("started"))&&(clearTimeout(n),this.mcpProxy?.stdout?.removeListener("data",a),r())},"dataHandler");this.mcpProxy?.stdout?.on("data",a)}),o.info("MCP proxy started successfully")}async stop(){o.info("Stopping MCP server...");for(let e of this.clients.values())try{e.response.end()}catch{}this.clients.clear(),this.server&&(await new Promise(e=>{this.server.close(()=>e())}),this.server=null),this.mcpProxy&&(this.mcpProxy.kill("SIGTERM"),await new Promise(e=>{this.mcpProxy.on("exit",()=>e()),setTimeout(()=>{this.mcpProxy?.kill("SIGKILL"),e()},5e3)}),this.mcpProxy=null),this.emit("stopped"),o.info("MCP server stopped")}};export{P as MCPServer};
14
+ `;e.response.write(o)}catch(o){s.error(`\u53D1\u9001\u5230\u5BA2\u6237\u7AEF ${e.id} \u5931\u8D25:`,o),this.clients.delete(e.sessionId)}}broadcastToClients(e){for(let t of this.clients.values())this.sendToClient(t,e)}async start(){try{let e=await Promise.allSettled([this.startMCPProxy(),new Promise(n=>{this.server=this.app.listen(this.port,()=>{s.info(`MCP\u670D\u52A1\u5668\u76D1\u542C\u7AEF\u53E3 ${this.port}`),s.info(`SSE\u7AEF\u70B9: http://localhost:${this.port}/sse`),s.info(`\u6D88\u606F\u7AEF\u70B9: http://localhost:${this.port}/messages`),s.info(`RPC\u7AEF\u70B9: http://localhost:${this.port}/rpc`),n()})})]),[t,o]=e;if(t.status==="rejected"&&s.error("MCP\u4EE3\u7406\u542F\u52A8\u5931\u8D25:",t.reason),o.status==="rejected")throw s.error("HTTP\u670D\u52A1\u5668\u542F\u52A8\u5931\u8D25:",o.reason),o.reason;this.startMCPClient().catch(n=>{s.error("\u542F\u52A8\u8FDE\u63A5\u5230xiaozhi.me\u7684MCP\u5BA2\u6237\u7AEF\u5931\u8D25:",n)}),this.emit("started")}catch(e){throw s.error("\u542F\u52A8MCP\u670D\u52A1\u5668\u5931\u8D25:",e),e}}findMCPProxyPath(){if(this.mcpProxyPath)return this.mcpProxyPath;if(process.env.MCP_SERVER_PROXY_PATH){let n=p.normalize(process.env.MCP_SERVER_PROXY_PATH),i=p.resolve(n),c=[P,p.join(P,".."),p.join(P,"..",".."),p.join(P,"..","..","dist"),V.tmpdir()];if(y.existsSync(i)&&p.basename(i)===b&&c.some(g=>i.startsWith(g)))return this.mcpProxyPath=i,this.mcpProxyPath;throw s.warn(`MCP_SERVER_PROXY_PATH \u8DEF\u5F84\u4E0D\u5B89\u5168: ${n}`),new Error(`\u6307\u5B9A\u7684 MCP \u4EE3\u7406\u8DEF\u5F84\u4E0D\u5B58\u5728\u6216\u4E0D\u5B89\u5168: ${process.env.MCP_SERVER_PROXY_PATH}`)}let e=O(import.meta.url),t=p.dirname(e),o=null;for(let n=0;n<5;n++){let i=p.join(t,b);if(y.existsSync(i)){o=i;break}let c=p.join(t,"dist",b);if(y.existsSync(c)){o=c;break}t=p.dirname(t)}if(!o){let n=p.resolve(P,"..",".."),i=p.join(n,"dist",b);y.existsSync(i)&&(o=i)}if(!o)throw new Error(`\u5728\u9879\u76EE\u7ED3\u6784\u4E2D\u627E\u4E0D\u5230 ${b}`);return this.mcpProxyPath=o,this.mcpProxyPath}async startMCPProxy(){let e=this.findMCPProxyPath();s.info(`\u6B63\u5728\u542F\u52A8MCP\u4EE3\u7406: ${e}`),this.mcpProxy=Z("node",[e],{stdio:["pipe","pipe","pipe"],env:{...process.env,MCP_SERVER_MODE:"true",XIAOZHI_CONFIG_DIR:process.env.XIAOZHI_CONFIG_DIR||process.cwd()}}),this.mcpProxy.on("error",t=>{s.error("MCP\u4EE3\u7406\u9519\u8BEF:",t)}),this.mcpProxy.on("exit",(t,o)=>{s.warn(`MCP\u4EE3\u7406\u9000\u51FA\uFF0C\u4EE3\u7801 ${t}\uFF0C\u4FE1\u53F7 ${o}`),this.mcpProxy=null}),this.mcpProxy.stderr&&this.mcpProxy.stderr.on("data",t=>{let o=t.toString().trim();o.includes("[ERROR]")||o.includes("Error:")||o.includes("Failed")?s.error("MCP\u4EE3\u7406stderr:",o):s.info("MCP\u4EE3\u7406\u8F93\u51FA:",o)}),this.mcpProxy.stdout&&this.mcpProxy.stdout.on("data",t=>{this.handleProxyResponse(t)}),await new Promise((t,o)=>{let n=setTimeout(()=>{o(new Error("MCP\u4EE3\u7406\u542F\u52A8\u8D85\u65F6"))},1e4),i=f(c=>{let g=c.toString();(g.includes("MCP proxy ready")||g.includes("started"))&&(clearTimeout(n),this.mcpProxy?.stdout?.removeListener("data",i),t())},"dataHandler");this.mcpProxy?.stdout?.on("data",i)}),s.info("MCP\u4EE3\u7406\u542F\u52A8\u6210\u529F")}async startMCPClient(){let e=[];try{u.configExists()&&(e=u.getMcpEndpoints())}catch(t){s.warn("\u4ECE\u914D\u7F6E\u4E2D\u8BFB\u53D6MCP\u7AEF\u70B9\u5931\u8D25:",t)}if(e.length>0){let t=this.findMCPProxyPath();this.mcpClient=new S(t,e),await this.mcpClient.start(),s.info("MCP\u5BA2\u6237\u7AEF\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u8FDE\u63A5\u5230 xiaozhi.me")}else s.info("\u672A\u914D\u7F6EMCP\u7AEF\u70B9\uFF0C\u8DF3\u8FC7\u5BA2\u6237\u7AEF\u8FDE\u63A5")}async stop(){s.info("\u6B63\u5728\u505C\u6B62MCP\u670D\u52A1\u5668...");for(let e of this.clients.values())try{e.response.end()}catch{}this.clients.clear(),this.server&&(await new Promise(e=>{this.server.close(()=>e())}),this.server=null),this.mcpProxy&&(this.mcpProxy.kill("SIGTERM"),await new Promise(e=>{this.mcpProxy.on("exit",()=>e()),setTimeout(()=>{this.mcpProxy?.kill("SIGKILL"),e()},5e3)}),this.mcpProxy=null),this.mcpClient&&(this.mcpClient.shutdown(),this.mcpClient=null),this.mcpProxyPath=null,this.emit("stopped"),s.info("MCP\u670D\u52A1\u5668\u5DF2\u505C\u6B62")}};export{A as MCPServer};
12
15
  //# sourceMappingURL=mcpServer.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/mcpServer.ts","../../src/logger.ts"],"sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { EventEmitter } from \"node:events\";\nimport type { Server } from \"node:http\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport express from \"express\";\nimport { configManager } from \"../configManager.js\";\nimport { logger as globalLogger } from \"../logger.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst logger = globalLogger.withTag(\"mcp-server\");\n\ninterface SSEClient {\n id: string;\n sessionId: string;\n response: express.Response;\n}\n\nexport class MCPServer extends EventEmitter {\n private app: express.Application;\n private server: Server | null = null;\n private clients: Map<string, SSEClient> = new Map();\n private mcpProxy: ChildProcess | null = null;\n private port: number;\n\n constructor(port = 3000) {\n super();\n this.port = port;\n this.app = express();\n this.setupMiddleware();\n this.setupRoutes();\n }\n\n private setupMiddleware(): void {\n this.app.use(express.json());\n this.app.use(express.urlencoded({ extended: true }));\n\n // CORS for MCP clients\n this.app.use((req, res, next) => {\n res.header(\"Access-Control-Allow-Origin\", \"*\");\n res.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.header(\"Access-Control-Allow-Headers\", \"Content-Type, Accept\");\n res.header(\"Cache-Control\", \"no-cache\");\n next();\n });\n }\n\n private setupRoutes(): void {\n // SSE endpoint for MCP protocol\n this.app.get(\"/sse\", (req, res) => {\n const clientId = Date.now().toString();\n const sessionId = randomUUID();\n\n // Set SSE headers (matching SDK implementation)\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache, no-transform\");\n res.setHeader(\"Connection\", \"keep-alive\");\n res.setHeader(\"X-Accel-Buffering\", \"no\");\n\n // Register client with sessionId\n this.clients.set(sessionId, { id: clientId, sessionId, response: res });\n logger.info(`MCP client connected: ${clientId} (session: ${sessionId})`);\n\n // Send endpoint event first (SDK standard)\n res.write(`event: endpoint\\ndata: /messages?sessionId=${sessionId}\\n\\n`);\n\n // Handle client disconnect\n req.on(\"close\", () => {\n this.clients.delete(sessionId);\n logger.info(\n `MCP client disconnected: ${clientId} (session: ${sessionId})`\n );\n });\n });\n\n // Messages endpoint for SSE transport (MCP SDK standard)\n this.app.post(\"/messages\", async (req, res) => {\n try {\n const sessionId = req.query.sessionId as string;\n const message = req.body;\n\n logger.info(\n `Received message via SSE transport (session: ${sessionId}):`,\n JSON.stringify(message)\n );\n\n if (!sessionId || !this.clients.has(sessionId)) {\n res.status(400).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32600,\n message: \"Invalid or missing sessionId\",\n },\n id: message.id,\n });\n return;\n }\n\n if (!this.mcpProxy) {\n res.status(503).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32603,\n message: \"MCP proxy not running\",\n },\n id: message.id,\n });\n return;\n }\n\n // Check if this is a notification (no id field means it's a notification)\n if (message.id === undefined) {\n // This is a notification, forward it but don't wait for response\n logger.info(`Forwarding notification: ${message.method}`);\n this.mcpProxy!.stdin!.write(`${JSON.stringify(message)}\\n`);\n\n // Send 202 Accepted immediately for notifications\n res.status(202).send();\n } else {\n // This is a request, forward and wait for response\n const response = await this.forwardToProxy(message);\n\n // Send response to specific client via SSE\n const client = this.clients.get(sessionId);\n if (client) {\n this.sendToClient(client, response);\n }\n\n // Send 202 Accepted to acknowledge receipt (SDK standard)\n res.status(202).send();\n }\n } catch (error) {\n logger.error(\"SSE message error:\", error);\n res.status(500).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32603,\n message: error instanceof Error ? error.message : \"Internal error\",\n },\n id: req.body.id,\n });\n }\n });\n\n // JSON-RPC endpoint for direct RPC communication (legacy)\n this.app.post(\"/rpc\", async (req, res) => {\n try {\n const message = req.body;\n logger.debug(\"Received RPC message:\", message);\n\n if (!this.mcpProxy) {\n res.status(503).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32603,\n message: \"MCP proxy not running\",\n },\n id: message.id,\n });\n return;\n }\n\n // Forward to mcpServerProxy\n const response = await this.forwardToProxy(message);\n res.json(response);\n } catch (error) {\n logger.error(\"RPC error:\", error);\n res.status(500).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32603,\n message: error instanceof Error ? error.message : \"Internal error\",\n },\n id: req.body.id,\n });\n }\n });\n\n // Health check\n this.app.get(\"/health\", (req, res) => {\n res.json({\n status: \"ok\",\n mode: \"mcp-server\",\n proxy: this.mcpProxy ? \"running\" : \"stopped\",\n clients: this.clients.size,\n });\n });\n }\n\n private responseBuffer = \"\";\n private pendingRequests = new Map<\n number | string,\n {\n resolve: (value: any) => void;\n reject: (error: any) => void;\n timeoutId: NodeJS.Timeout;\n }\n >();\n\n private async forwardToProxy(message: any): Promise<any> {\n return new Promise((resolve, reject) => {\n if (!this.mcpProxy || !this.mcpProxy.stdin || !this.mcpProxy.stdout) {\n reject(new Error(\"MCP proxy not available\"));\n return;\n }\n\n // Set up timeout\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(message.id);\n logger.warn(\n `Request timeout for message id: ${message.id}, method: ${message.method} - This may be normal if the response was already sent via SSE`\n );\n // Don't reject with error, just resolve with a timeout indicator\n resolve({\n jsonrpc: \"2.0\",\n id: message.id,\n result: {\n _timeout: true,\n message: \"Response may have been sent via SSE\",\n },\n });\n }, 30000);\n\n // Store the pending request\n this.pendingRequests.set(message.id, { resolve, reject, timeoutId });\n\n // Log the message being sent\n logger.info(`Forwarding message to proxy: ${JSON.stringify(message)}`);\n this.mcpProxy.stdin.write(`${JSON.stringify(message)}\\n`);\n });\n }\n\n private handleProxyResponse(data: Buffer): void {\n try {\n // Accumulate data in buffer\n this.responseBuffer += data.toString();\n\n // Process complete lines\n const lines = this.responseBuffer.split(\"\\n\");\n this.responseBuffer = lines.pop() || \"\"; // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const response = JSON.parse(line);\n logger.debug(\n `Received response from proxy: ${line.substring(0, 200)}...`\n );\n\n // Check if this is a response to a pending request\n if (\n response.id !== undefined &&\n this.pendingRequests.has(response.id)\n ) {\n const pendingRequest = this.pendingRequests.get(response.id)!;\n clearTimeout(pendingRequest.timeoutId);\n this.pendingRequests.delete(response.id);\n pendingRequest.resolve(response);\n }\n } catch (e) {\n // Skip invalid JSON lines\n logger.debug(`Non-JSON line from proxy: ${line}`);\n }\n }\n }\n } catch (error) {\n logger.error(\"Error handling proxy response:\", error);\n }\n }\n\n private sendToClient(client: SSEClient, message: any): void {\n try {\n // Use event: message format (SDK standard)\n const data = `event: message\\ndata: ${JSON.stringify(message)}\\n\\n`;\n client.response.write(data);\n } catch (error) {\n logger.error(`Failed to send to client ${client.id}:`, error);\n this.clients.delete(client.sessionId);\n }\n }\n\n private broadcastToClients(message: any): void {\n for (const client of this.clients.values()) {\n this.sendToClient(client, message);\n }\n }\n\n public async start(): Promise<void> {\n try {\n // Start mcpServerProxy\n await this.startMCPProxy();\n\n // Start HTTP server\n this.server = this.app.listen(this.port, () => {\n logger.info(`MCP Server listening on port ${this.port}`);\n logger.info(`SSE endpoint: http://localhost:${this.port}/sse`);\n logger.info(\n `Messages endpoint: http://localhost:${this.port}/messages`\n );\n logger.info(`RPC endpoint: http://localhost:${this.port}/rpc`);\n });\n\n this.emit(\"started\");\n } catch (error) {\n logger.error(\"Failed to start MCP server:\", error);\n throw error;\n }\n }\n\n private async startMCPProxy(): Promise<void> {\n // 由于 tsup 打包的原因,import.meta.url 可能不准确\n // 我们需要找到 mcpServerProxy.js 的正确位置\n\n // 方法1:尝试从当前脚本的目录开始查找\n const currentScript = fileURLToPath(import.meta.url);\n let searchDir = path.dirname(currentScript);\n\n // 向上查找直到找到 mcpServerProxy.js\n let mcpProxyPath: string | null = null;\n for (let i = 0; i < 5; i++) {\n // 最多向上查找5级\n const testPath = path.join(searchDir, \"mcpServerProxy.js\");\n const fs = await import(\"node:fs\");\n if (fs.existsSync(testPath)) {\n mcpProxyPath = testPath;\n break;\n }\n // 也检查 dist 目录\n const distPath = path.join(searchDir, \"dist\", \"mcpServerProxy.js\");\n if (fs.existsSync(distPath)) {\n mcpProxyPath = distPath;\n break;\n }\n searchDir = path.dirname(searchDir);\n }\n\n if (!mcpProxyPath) {\n throw new Error(\n \"Could not find mcpServerProxy.js in the project structure\"\n );\n }\n\n logger.info(`Starting MCP proxy from: ${mcpProxyPath}`);\n\n this.mcpProxy = spawn(\"node\", [mcpProxyPath], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: {\n ...process.env,\n MCP_SERVER_MODE: \"true\",\n XIAOZHI_CONFIG_DIR: process.env.XIAOZHI_CONFIG_DIR || process.cwd(),\n },\n });\n\n this.mcpProxy.on(\"error\", (error) => {\n logger.error(\"MCP proxy error:\", error);\n });\n\n this.mcpProxy.on(\"exit\", (code, signal) => {\n logger.warn(`MCP proxy exited with code ${code}, signal ${signal}`);\n this.mcpProxy = null;\n });\n\n if (this.mcpProxy.stderr) {\n this.mcpProxy.stderr.on(\"data\", (data) => {\n const message = data.toString().trim();\n // mcpServerProxy 使用 logger 输出日志到 stderr,这些不是错误\n // 只有真正的错误信息才应该被标记为 ERROR\n if (\n message.includes(\"[ERROR]\") ||\n message.includes(\"Error:\") ||\n message.includes(\"Failed\")\n ) {\n logger.error(\"MCP proxy stderr:\", message);\n } else {\n // 将正常的日志信息作为 info 级别输出\n logger.info(\"MCP proxy output:\", message);\n }\n });\n }\n\n // Set up global stdout handler for responses\n if (this.mcpProxy.stdout) {\n this.mcpProxy.stdout.on(\"data\", (data: Buffer) => {\n this.handleProxyResponse(data);\n });\n }\n\n // Wait for proxy to be ready\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(\"MCP proxy startup timeout\"));\n }, 10000);\n\n const dataHandler = (data: Buffer) => {\n const message = data.toString();\n if (\n message.includes(\"MCP proxy ready\") ||\n message.includes(\"started\")\n ) {\n clearTimeout(timeout);\n this.mcpProxy?.stdout?.removeListener(\"data\", dataHandler);\n resolve();\n }\n };\n\n this.mcpProxy?.stdout?.on(\"data\", dataHandler);\n });\n\n logger.info(\"MCP proxy started successfully\");\n }\n\n public async stop(): Promise<void> {\n logger.info(\"Stopping MCP server...\");\n\n // Close all SSE connections\n for (const client of this.clients.values()) {\n try {\n client.response.end();\n } catch (error) {\n // Ignore errors when closing\n }\n }\n this.clients.clear();\n\n // Stop HTTP server\n if (this.server) {\n await new Promise<void>((resolve) => {\n this.server!.close(() => resolve());\n });\n this.server = null;\n }\n\n // Stop MCP proxy\n if (this.mcpProxy) {\n this.mcpProxy.kill(\"SIGTERM\");\n await new Promise<void>((resolve) => {\n this.mcpProxy!.on(\"exit\", () => resolve());\n setTimeout(() => {\n this.mcpProxy?.kill(\"SIGKILL\");\n resolve();\n }, 5000);\n });\n this.mcpProxy = null;\n }\n\n this.emit(\"stopped\");\n logger.info(\"MCP server stopped\");\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport chalk from \"chalk\";\nimport { type consola, createConsola } from \"consola\";\n\nfunction formatDateTime(date: Date) {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const hours = String(date.getHours()).padStart(2, \"0\");\n const minutes = String(date.getMinutes()).padStart(2, \"0\");\n const seconds = String(date.getSeconds()).padStart(2, \"0\");\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;\n}\n\nexport class Logger {\n private logFilePath: string | null = null;\n private writeStream: fs.WriteStream | null = null;\n private consolaInstance: typeof consola;\n private isDaemonMode: boolean;\n\n constructor() {\n // 检查是否为守护进程模式\n this.isDaemonMode = process.env.XIAOZHI_DAEMON === \"true\";\n // 创建自定义的 consola 实例,禁用图标并自定义格式\n this.consolaInstance = createConsola({\n formatOptions: {\n date: false,\n colors: true,\n compact: true,\n },\n fancy: false,\n });\n\n // 保存对当前实例的引用,以便在闭包中访问\n const isDaemonMode = this.isDaemonMode;\n\n // 自定义格式化器\n this.consolaInstance.setReporters([\n {\n log: (logObj) => {\n const levelMap: Record<string, string> = {\n info: \"INFO\",\n success: \"SUCCESS\",\n warn: \"WARN\",\n error: \"ERROR\",\n debug: \"DEBUG\",\n log: \"LOG\",\n };\n\n const colorMap: Record<string, (text: string) => string> = {\n info: chalk.blue,\n success: chalk.green,\n warn: chalk.yellow,\n error: chalk.red,\n debug: chalk.gray,\n log: (text: string) => text,\n };\n\n const level = levelMap[logObj.type] || logObj.type.toUpperCase();\n const colorFn = colorMap[logObj.type] || ((text: string) => text);\n const timestamp = formatDateTime(new Date());\n\n // 为级别添加颜色\n const coloredLevel = colorFn(`[${level}]`);\n const message = `[${timestamp}] ${coloredLevel} ${logObj.args.join(\n \" \"\n )}`;\n\n // 守护进程模式下不输出到控制台,只写入文件\n if (!isDaemonMode) {\n // 输出到 stderr(与原来保持一致)\n try {\n console.error(message);\n } catch (error) {\n // 忽略 EPIPE 错误\n if (error instanceof Error && error.message?.includes(\"EPIPE\")) {\n return;\n }\n throw error;\n }\n }\n },\n },\n ]);\n }\n\n /**\n * 初始化日志文件\n * @param projectDir 项目目录\n */\n initLogFile(projectDir: string): void {\n this.logFilePath = path.join(projectDir, \"xiaozhi.log\");\n\n // 确保日志文件存在\n if (!fs.existsSync(this.logFilePath)) {\n fs.writeFileSync(this.logFilePath, \"\");\n }\n\n // 创建写入流,追加模式\n this.writeStream = fs.createWriteStream(this.logFilePath, {\n flags: \"a\",\n encoding: \"utf8\",\n });\n }\n\n /**\n * 记录日志到文件\n * @param level 日志级别\n * @param message 日志消息\n * @param args 额外参数\n */\n private logToFile(level: string, message: string, ...args: any[]): void {\n if (this.writeStream) {\n const timestamp = new Date().toISOString();\n const formattedMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;\n const fullMessage =\n args.length > 0\n ? `${formattedMessage} ${args\n .map((arg) =>\n typeof arg === \"object\" ? JSON.stringify(arg) : String(arg)\n )\n .join(\" \")}`\n : formattedMessage;\n\n this.writeStream.write(`${fullMessage}\\n`);\n }\n }\n\n /**\n * 设置是否启用文件日志\n * @param enable 是否启用\n */\n enableFileLogging(enable: boolean): void {\n if (enable && !this.writeStream && this.logFilePath) {\n this.writeStream = fs.createWriteStream(this.logFilePath, {\n flags: \"a\",\n encoding: \"utf8\",\n });\n } else if (!enable && this.writeStream) {\n this.writeStream.end();\n this.writeStream = null;\n }\n }\n\n /**\n * 日志方法\n */\n info(message: string, ...args: any[]): void {\n this.consolaInstance.info(message, ...args);\n this.logToFile(\"info\", message, ...args);\n }\n\n success(message: string, ...args: any[]): void {\n this.consolaInstance.success(message, ...args);\n this.logToFile(\"success\", message, ...args);\n }\n\n warn(message: string, ...args: any[]): void {\n this.consolaInstance.warn(message, ...args);\n this.logToFile(\"warn\", message, ...args);\n }\n\n error(message: string, ...args: any[]): void {\n this.consolaInstance.error(message, ...args);\n this.logToFile(\"error\", message, ...args);\n }\n\n debug(message: string, ...args: any[]): void {\n this.consolaInstance.debug(message, ...args);\n this.logToFile(\"debug\", message, ...args);\n }\n\n log(message: string, ...args: any[]): void {\n this.consolaInstance.log(message, ...args);\n this.logToFile(\"log\", message, ...args);\n }\n\n /**\n * 创建一个带标签的日志实例(已废弃,直接返回原实例)\n * @param tag 标签(不再使用)\n * @deprecated 标签功能已移除\n */\n withTag(tag: string): Logger {\n // 不再添加标签,直接返回共享实例\n return this;\n }\n\n /**\n * 关闭日志文件流\n */\n close(): void {\n if (this.writeStream) {\n this.writeStream.end();\n this.writeStream = null;\n }\n }\n}\n\n// 导出单例实例\nexport const logger = new Logger();\n"],"mappings":"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","names":["spawn","randomUUID","EventEmitter","path","fileURLToPath","express","fs","path","chalk","createConsola","formatDateTime","date","year","month","day","hours","minutes","seconds","__name","Logger","createConsola","isDaemonMode","logObj","levelMap","colorMap","chalk","text","level","colorFn","timestamp","coloredLevel","message","error","projectDir","path","fs","args","formattedMessage","fullMessage","arg","enable","tag","logger","__filename","fileURLToPath","__dirname","path","logger","MCPServer","EventEmitter","__name","port","express","req","res","next","clientId","sessionId","randomUUID","message","response","client","error","resolve","reject","timeoutId","data","lines","line","pendingRequest","currentScript","searchDir","mcpProxyPath","i","testPath","fs","distPath","spawn","code","signal","timeout","dataHandler"]}
1
+ {"version":3,"sources":["../../src/services/mcpServer.ts","../../src/configManager.ts","../../src/logger.ts","../../src/multiEndpointMCPPipe.ts"],"sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { EventEmitter } from \"node:events\";\nimport fs from \"node:fs\";\nimport type { Server } from \"node:http\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport express from \"express\";\nimport { configManager } from \"../configManager.js\";\nimport { logger as globalLogger } from \"../logger.js\";\nimport { MultiEndpointMCPPipe } from \"../multiEndpointMCPPipe.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst logger = globalLogger.withTag(\"mcp-server\");\nconst MCP_SERVER_PROXY_FILENAME = \"mcpServerProxy.js\";\n\ninterface SSEClient {\n id: string;\n sessionId: string;\n response: express.Response;\n}\n\nexport class MCPServer extends EventEmitter {\n private app: express.Application;\n private server: Server | null = null;\n private clients: Map<string, SSEClient> = new Map();\n private mcpProxy: ChildProcess | null = null;\n private mcpClient: MultiEndpointMCPPipe | null = null;\n private mcpProxyPath: string | null = null; // 缓存MCP代理路径\n private port: number;\n\n constructor(port = 3000) {\n super();\n this.port = port;\n this.app = express();\n this.setupMiddleware();\n this.setupRoutes();\n }\n\n private setupMiddleware(): void {\n this.app.use(express.json());\n this.app.use(express.urlencoded({ extended: true }));\n\n // 为MCP客户端设置CORS\n this.app.use((req, res, next) => {\n res.header(\"Access-Control-Allow-Origin\", \"*\");\n res.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.header(\"Access-Control-Allow-Headers\", \"Content-Type, Accept\");\n res.header(\"Cache-Control\", \"no-cache\");\n next();\n });\n }\n\n private setupRoutes(): void {\n // MCP协议的SSE端点\n this.app.get(\"/sse\", (req, res) => {\n const clientId = Date.now().toString();\n const sessionId = randomUUID();\n\n // 设置SSE头部(匹配SDK实现)\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache, no-transform\");\n res.setHeader(\"Connection\", \"keep-alive\");\n res.setHeader(\"X-Accel-Buffering\", \"no\");\n\n // 使用sessionId注册客户端\n this.clients.set(sessionId, { id: clientId, sessionId, response: res });\n logger.info(`MCP客户端已连接: ${clientId} (会话: ${sessionId})`);\n\n // 首先发送端点事件(SDK标准)\n res.write(`event: endpoint\\ndata: /messages?sessionId=${sessionId}\\n\\n`);\n\n // 处理客户端断开连接\n req.on(\"close\", () => {\n this.clients.delete(sessionId);\n logger.info(`MCP客户端已断开连接: ${clientId} (会话: ${sessionId})`);\n });\n });\n\n // SSE传输的消息端点(MCP SDK标准)\n this.app.post(\"/messages\", async (req, res) => {\n try {\n const sessionId = req.query.sessionId as string;\n const message = req.body;\n\n logger.info(\n `通过SSE传输收到消息 (会话: ${sessionId}):`,\n JSON.stringify(message)\n );\n\n if (!sessionId || !this.clients.has(sessionId)) {\n res.status(400).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32600,\n message: \"无效或缺少sessionId\",\n },\n id: message.id,\n });\n return;\n }\n\n if (!this.mcpProxy) {\n res.status(503).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32603,\n message: \"MCP代理未运行\",\n },\n id: message.id,\n });\n return;\n }\n\n // 检查这是否是通知(没有id字段意味着这是通知)\n if (message.id === undefined) {\n // 这是通知,转发但不等待响应\n logger.info(`转发通知: ${message.method}`);\n this.mcpProxy!.stdin!.write(`${JSON.stringify(message)}\\n`);\n\n // 立即发送202 Accepted以响应通知\n res.status(202).send();\n } else {\n // 这是请求,转发并等待响应\n const response = await this.forwardToProxy(message);\n\n // 通过SSE将响应发送到特定客户端\n const client = this.clients.get(sessionId);\n if (client) {\n this.sendToClient(client, response);\n }\n\n // 发送202 Accepted以确认收到(SDK标准)\n res.status(202).send();\n }\n } catch (error) {\n logger.error(\"SSE消息错误:\", error);\n res.status(500).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32603,\n message: error instanceof Error ? error.message : \"内部错误\",\n },\n id: req.body.id,\n });\n }\n });\n\n // 用于直接RPC通信的JSON-RPC端点(遗留)\n this.app.post(\"/rpc\", async (req, res) => {\n try {\n const message = req.body;\n logger.debug(\"收到RPC消息:\", message);\n\n if (!this.mcpProxy) {\n res.status(503).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32603,\n message: \"MCP代理未运行\",\n },\n id: message.id,\n });\n return;\n }\n\n // 转发到mcpServerProxy\n const response = await this.forwardToProxy(message);\n res.json(response);\n } catch (error) {\n logger.error(\"RPC错误:\", error);\n res.status(500).json({\n jsonrpc: \"2.0\",\n error: {\n code: -32603,\n message: error instanceof Error ? error.message : \"内部错误\",\n },\n id: req.body.id,\n });\n }\n });\n\n // 健康检查\n this.app.get(\"/health\", (req, res) => {\n res.json({\n status: \"ok\",\n mode: \"mcp-server\",\n proxy: this.mcpProxy ? \"running\" : \"stopped\",\n clients: this.clients.size,\n });\n });\n }\n\n private responseBuffer = \"\";\n private pendingRequests = new Map<\n number | string,\n {\n resolve: (value: any) => void;\n reject: (error: any) => void;\n timeoutId: NodeJS.Timeout;\n }\n >();\n\n private async forwardToProxy(message: any): Promise<any> {\n return new Promise((resolve, reject) => {\n if (!this.mcpProxy || !this.mcpProxy.stdin || !this.mcpProxy.stdout) {\n reject(new Error(\"MCP代理不可用\"));\n return;\n }\n\n // 设置超时\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(message.id);\n logger.warn(\n `消息超时 id: ${message.id}, 方法: ${message.method} - 如果响应已通过SSE发送,这是正常的`\n );\n // 不要以错误拒绝,而是用超时指示器解析\n resolve({\n jsonrpc: \"2.0\",\n id: message.id,\n result: {\n _timeout: true,\n message: \"响应可能已通过SSE发送\",\n },\n });\n }, 30000);\n\n // 存储待处理请求\n this.pendingRequests.set(message.id, { resolve, reject, timeoutId });\n\n // 记录正在发送的消息\n logger.info(`转发消息到代理: ${JSON.stringify(message)}`);\n this.mcpProxy.stdin.write(`${JSON.stringify(message)}\\n`);\n });\n }\n\n private handleProxyResponse(data: Buffer): void {\n try {\n // 在缓冲区中累积数据\n this.responseBuffer += data.toString();\n\n // 处理完整行\n const lines = this.responseBuffer.split(\"\\n\");\n this.responseBuffer = lines.pop() || \"\"; // 将不完整的行保留在缓冲区中\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const response = JSON.parse(line);\n logger.debug(`收到代理响应: ${line.substring(0, 200)}...`);\n\n // 检查这是否是对待处理请求的响应\n if (\n response.id !== undefined &&\n this.pendingRequests.has(response.id)\n ) {\n const pendingRequest = this.pendingRequests.get(response.id)!;\n clearTimeout(pendingRequest.timeoutId);\n this.pendingRequests.delete(response.id);\n pendingRequest.resolve(response);\n }\n } catch (e) {\n // 跳过无效的JSON行\n logger.debug(`来自代理的非JSON行: ${line}`);\n }\n }\n }\n } catch (error) {\n logger.error(\"处理代理响应时出错:\", error);\n }\n }\n\n private sendToClient(client: SSEClient, message: any): void {\n try {\n // 使用事件:消息格式(SDK标准)\n const data = `event: message\\ndata: ${JSON.stringify(message)}\\n\\n`;\n client.response.write(data);\n } catch (error) {\n logger.error(`发送到客户端 ${client.id} 失败:`, error);\n this.clients.delete(client.sessionId);\n }\n }\n\n private broadcastToClients(message: any): void {\n for (const client of this.clients.values()) {\n this.sendToClient(client, message);\n }\n }\n\n public async start(): Promise<void> {\n try {\n // 并行启动mcpServerProxy和HTTP服务器\n // 在接受其他客户端连接之前,我们不需要等待MCP客户端连接到xiaozhi.me\n const results = await Promise.allSettled([\n this.startMCPProxy(),\n new Promise<void>((resolve) => {\n // 启动HTTP服务器\n this.server = this.app.listen(this.port, () => {\n logger.info(`MCP服务器监听端口 ${this.port}`);\n logger.info(`SSE端点: http://localhost:${this.port}/sse`);\n logger.info(`消息端点: http://localhost:${this.port}/messages`);\n logger.info(`RPC端点: http://localhost:${this.port}/rpc`);\n resolve();\n });\n }),\n ]);\n\n // 检查 MCP 代理启动结果\n const [mcpProxyResult, httpServerResult] = results;\n if (mcpProxyResult.status === \"rejected\") {\n logger.error(\"MCP代理启动失败:\", mcpProxyResult.reason);\n }\n\n if (httpServerResult.status === \"rejected\") {\n logger.error(\"HTTP服务器启动失败:\", httpServerResult.reason);\n throw httpServerResult.reason;\n }\n\n // 启动MCP客户端连接到xiaozhi.me(不要阻塞服务器启动)\n this.startMCPClient().catch((error) => {\n logger.error(\"启动连接到xiaozhi.me的MCP客户端失败:\", error);\n });\n\n this.emit(\"started\");\n } catch (error) {\n logger.error(\"启动MCP服务器失败:\", error);\n throw error;\n }\n }\n\n private findMCPProxyPath(): string {\n // 如果已经缓存了路径,直接返回\n if (this.mcpProxyPath) {\n return this.mcpProxyPath;\n }\n\n // 由于 tsup 打包的原因,import.meta.url 可能不准确\n // 我们需要找到 mcpServerProxy.js 的正确位置\n\n // 首先尝试使用环境变量指定的路径(主要用于测试环境)\n // MCP_SERVER_PROXY_PATH: 指定 mcpServerProxy.js 文件的完整路径\n // 在测试环境中,可以设置此环境变量来直接指定文件位置\n if (process.env.MCP_SERVER_PROXY_PATH) {\n // 验证路径是否安全,防止路径遍历攻击\n const normalizedPath = path.normalize(process.env.MCP_SERVER_PROXY_PATH);\n const resolvedPath = path.resolve(normalizedPath);\n\n // 定义允许的目录白名单(仅限项目目录和系统临时目录)\n const allowedBaseDirectories = [\n __dirname, // 当前模块目录\n path.join(__dirname, \"..\"), // 上级目录\n path.join(__dirname, \"..\", \"..\"), // 项目根目录\n path.join(__dirname, \"..\", \"..\", \"dist\"), // 项目dist目录\n os.tmpdir(), // 系统临时目录\n ];\n\n // 确保路径在白名单目录内且具有正确的文件名\n if (\n fs.existsSync(resolvedPath) &&\n path.basename(resolvedPath) === MCP_SERVER_PROXY_FILENAME &&\n allowedBaseDirectories.some((dir) => resolvedPath.startsWith(dir))\n ) {\n this.mcpProxyPath = resolvedPath;\n return this.mcpProxyPath;\n }\n\n logger.warn(`MCP_SERVER_PROXY_PATH 路径不安全: ${normalizedPath}`);\n throw new Error(\n `指定的 MCP 代理路径不存在或不安全: ${process.env.MCP_SERVER_PROXY_PATH}`\n );\n }\n\n // 方法1:尝试从当前脚本的目录开始查找\n const currentScript = fileURLToPath(import.meta.url);\n let searchDir = path.dirname(currentScript);\n\n // 向上查找直到找到 mcpServerProxy.js\n let mcpProxyPath: string | null = null;\n for (let i = 0; i < 5; i++) {\n // 最多向上查找5级\n const testPath = path.join(searchDir, MCP_SERVER_PROXY_FILENAME);\n if (fs.existsSync(testPath)) {\n mcpProxyPath = testPath;\n break;\n }\n // 也检查 dist 目录\n const distPath = path.join(searchDir, \"dist\", MCP_SERVER_PROXY_FILENAME);\n if (fs.existsSync(distPath)) {\n mcpProxyPath = distPath;\n break;\n }\n searchDir = path.dirname(searchDir);\n }\n\n // 如果还没找到,尝试从项目根目录查找\n if (!mcpProxyPath) {\n const projectRoot = path.resolve(__dirname, \"..\", \"..\");\n const rootDistPath = path.join(\n projectRoot,\n \"dist\",\n MCP_SERVER_PROXY_FILENAME\n );\n if (fs.existsSync(rootDistPath)) {\n mcpProxyPath = rootDistPath;\n }\n }\n\n if (!mcpProxyPath) {\n throw new Error(`在项目结构中找不到 ${MCP_SERVER_PROXY_FILENAME}`);\n }\n\n // 缓存并返回路径\n this.mcpProxyPath = mcpProxyPath;\n return this.mcpProxyPath;\n }\n\n private async startMCPProxy(): Promise<void> {\n const mcpProxyPath = this.findMCPProxyPath();\n logger.info(`正在启动MCP代理: ${mcpProxyPath}`);\n\n this.mcpProxy = spawn(\"node\", [mcpProxyPath], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: {\n ...process.env,\n MCP_SERVER_MODE: \"true\",\n XIAOZHI_CONFIG_DIR: process.env.XIAOZHI_CONFIG_DIR || process.cwd(),\n },\n });\n\n this.mcpProxy.on(\"error\", (error) => {\n logger.error(\"MCP代理错误:\", error);\n });\n\n this.mcpProxy.on(\"exit\", (code, signal) => {\n logger.warn(`MCP代理退出,代码 ${code},信号 ${signal}`);\n this.mcpProxy = null;\n });\n\n if (this.mcpProxy.stderr) {\n this.mcpProxy.stderr.on(\"data\", (data) => {\n const message = data.toString().trim();\n // mcpServerProxy 使用 logger 输出日志到 stderr,这些不是错误\n // 只有真正的错误信息才应该被标记为 ERROR\n if (\n message.includes(\"[ERROR]\") ||\n message.includes(\"Error:\") ||\n message.includes(\"Failed\")\n ) {\n logger.error(\"MCP代理stderr:\", message);\n } else {\n // 将正常的日志信息作为 info 级别输出\n logger.info(\"MCP代理输出:\", message);\n }\n });\n }\n\n // 为响应设置全局stdout处理器\n if (this.mcpProxy.stdout) {\n this.mcpProxy.stdout.on(\"data\", (data: Buffer) => {\n this.handleProxyResponse(data);\n });\n }\n\n // 等待代理准备就绪\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(\"MCP代理启动超时\"));\n }, 10000);\n\n const dataHandler = (data: Buffer) => {\n const message = data.toString();\n if (\n message.includes(\"MCP proxy ready\") ||\n message.includes(\"started\")\n ) {\n clearTimeout(timeout);\n this.mcpProxy?.stdout?.removeListener(\"data\", dataHandler);\n resolve();\n }\n };\n\n this.mcpProxy?.stdout?.on(\"data\", dataHandler);\n });\n\n logger.info(\"MCP代理启动成功\");\n }\n\n private async startMCPClient(): Promise<void> {\n // 获取配置中的端点\n let endpoints: string[] = [];\n try {\n if (configManager.configExists()) {\n endpoints = configManager.getMcpEndpoints();\n }\n } catch (error) {\n logger.warn(\"从配置中读取MCP端点失败:\", error);\n }\n\n // 只有在配置中有端点时才启动客户端\n if (endpoints.length > 0) {\n // 获取 mcpServerProxy.js 的正确路径\n const mcpProxyPath = this.findMCPProxyPath();\n\n this.mcpClient = new MultiEndpointMCPPipe(mcpProxyPath, endpoints);\n await this.mcpClient.start();\n logger.info(\"MCP客户端已启动,正在连接到 xiaozhi.me\");\n } else {\n logger.info(\"未配置MCP端点,跳过客户端连接\");\n }\n }\n\n public async stop(): Promise<void> {\n logger.info(\"正在停止MCP服务器...\");\n\n // 关闭所有SSE连接\n for (const client of this.clients.values()) {\n try {\n client.response.end();\n } catch (error) {\n // 忽略关闭时的错误\n }\n }\n this.clients.clear();\n\n // 停止HTTP服务器\n if (this.server) {\n await new Promise<void>((resolve) => {\n this.server!.close(() => resolve());\n });\n this.server = null;\n }\n\n // 停止MCP代理\n if (this.mcpProxy) {\n this.mcpProxy.kill(\"SIGTERM\");\n await new Promise<void>((resolve) => {\n this.mcpProxy!.on(\"exit\", () => resolve());\n setTimeout(() => {\n this.mcpProxy?.kill(\"SIGKILL\");\n resolve();\n }, 5000);\n });\n this.mcpProxy = null;\n }\n\n // 停止MCP客户端\n if (this.mcpClient) {\n this.mcpClient.shutdown();\n this.mcpClient = null;\n }\n\n // 清除缓存的MCP代理路径\n this.mcpProxyPath = null;\n\n this.emit(\"stopped\");\n logger.info(\"MCP服务器已停止\");\n }\n}\n","import { copyFileSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport JSON5 from \"json5\";\nimport * as jsonc from \"jsonc-parser\";\n\n// 在 ESM 中,需要从 import.meta.url 获取当前文件目录\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// 默认连接配置\nconst DEFAULT_CONNECTION_CONFIG: Required<ConnectionConfig> = {\n heartbeatInterval: 30000, // 30秒心跳间隔\n heartbeatTimeout: 10000, // 10秒心跳超时\n reconnectInterval: 5000, // 5秒重连间隔\n};\n\n// 配置文件接口定义\n// 本地 MCP 服务配置\nexport interface LocalMCPServerConfig {\n command: string;\n args: string[];\n env?: Record<string, string>;\n}\n\n// SSE MCP 服务配置\nexport interface SSEMCPServerConfig {\n type: \"sse\";\n url: string;\n}\n\n// Streamable HTTP MCP 服务配置\nexport interface StreamableHTTPMCPServerConfig {\n type?: \"streamable-http\"; // 可选,因为默认就是 streamable-http\n url: string;\n}\n\n// 统一的 MCP 服务配置\nexport type MCPServerConfig =\n | LocalMCPServerConfig\n | SSEMCPServerConfig\n | StreamableHTTPMCPServerConfig;\n\nexport interface MCPToolConfig {\n description?: string;\n enable: boolean;\n}\n\nexport interface MCPServerToolsConfig {\n tools: Record<string, MCPToolConfig>;\n}\n\nexport interface ConnectionConfig {\n heartbeatInterval?: number; // 心跳检测间隔(毫秒),默认30000\n heartbeatTimeout?: number; // 心跳超时时间(毫秒),默认10000\n reconnectInterval?: number; // 重连间隔(毫秒),默认5000\n}\n\nexport interface ModelScopeConfig {\n apiKey?: string; // ModelScope API 密钥\n}\n\nexport interface WebUIConfig {\n port?: number; // Web UI 端口号,默认 9999\n autoRestart?: boolean; // 是否在配置更新后自动重启服务,默认 true\n}\n\nexport interface AppConfig {\n mcpEndpoint: string | string[];\n mcpServers: Record<string, MCPServerConfig>;\n mcpServerConfig?: Record<string, MCPServerToolsConfig>;\n connection?: ConnectionConfig; // 连接配置(可选,用于向后兼容)\n modelscope?: ModelScopeConfig; // ModelScope 配置(可选)\n webUI?: WebUIConfig; // Web UI 配置(可选)\n}\n\n/**\n * 配置管理类\n * 负责管理应用配置,提供只读访问和安全的配置更新功能\n */\nexport class ConfigManager {\n private static instance: ConfigManager;\n private defaultConfigPath: string;\n private config: AppConfig | null = null;\n\n private constructor() {\n this.defaultConfigPath = resolve(__dirname, \"xiaozhi.config.default.json\");\n }\n\n /**\n * 获取配置文件路径(动态计算)\n * 支持多种配置文件格式:json5 > jsonc > json\n */\n private getConfigFilePath(): string {\n // 配置文件路径 - 优先使用环境变量指定的目录,否则使用当前工作目录\n const configDir = process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n\n // 按优先级检查配置文件是否存在\n const configFileNames = [\n \"xiaozhi.config.json5\",\n \"xiaozhi.config.jsonc\",\n \"xiaozhi.config.json\",\n ];\n\n for (const fileName of configFileNames) {\n const filePath = resolve(configDir, fileName);\n if (existsSync(filePath)) {\n return filePath;\n }\n }\n\n // 如果都不存在,返回默认的 JSON 文件路径\n return resolve(configDir, \"xiaozhi.config.json\");\n }\n\n /**\n * 获取配置文件格式\n */\n private getConfigFileFormat(filePath: string): \"json5\" | \"jsonc\" | \"json\" {\n if (filePath.endsWith(\".json5\")) {\n return \"json5\";\n }\n\n if (filePath.endsWith(\".jsonc\")) {\n return \"jsonc\";\n }\n\n return \"json\";\n }\n\n /**\n * 获取配置管理器单例实例\n */\n public static getInstance(): ConfigManager {\n if (!ConfigManager.instance) {\n ConfigManager.instance = new ConfigManager();\n }\n return ConfigManager.instance;\n }\n\n /**\n * 检查配置文件是否存在\n */\n public configExists(): boolean {\n // 配置文件路径 - 优先使用环境变量指定的目录,否则使用当前工作目录\n const configDir = process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n\n // 按优先级检查配置文件是否存在\n const configFileNames = [\n \"xiaozhi.config.json5\",\n \"xiaozhi.config.jsonc\",\n \"xiaozhi.config.json\",\n ];\n\n for (const fileName of configFileNames) {\n const filePath = resolve(configDir, fileName);\n if (existsSync(filePath)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * 初始化配置文件\n * 从 config.default.json 复制到 config.json\n * @param format 配置文件格式,默认为 json\n */\n public initConfig(format: \"json\" | \"json5\" | \"jsonc\" = \"json\"): void {\n if (!existsSync(this.defaultConfigPath)) {\n throw new Error(\"默认配置文件 xiaozhi.config.default.json 不存在\");\n }\n\n // 检查是否已有任何格式的配置文件\n if (this.configExists()) {\n throw new Error(\"配置文件已存在,无需重复初始化\");\n }\n\n // 确定目标配置文件路径\n const configDir = process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n const targetFileName = `xiaozhi.config.${format}`;\n const configPath = resolve(configDir, targetFileName);\n\n // 复制默认配置文件\n copyFileSync(this.defaultConfigPath, configPath);\n this.config = null; // 重置缓存\n }\n\n /**\n * 加载配置文件\n */\n private loadConfig(): AppConfig {\n if (!this.configExists()) {\n throw new Error(\"配置文件不存在,请先运行 xiaozhi init 初始化配置\");\n }\n\n try {\n const configPath = this.getConfigFilePath();\n const configFileFormat = this.getConfigFileFormat(configPath);\n const configData = readFileSync(configPath, \"utf8\");\n\n let config: AppConfig;\n\n // 根据文件格式使用相应的解析器\n switch (configFileFormat) {\n case \"json5\":\n config = JSON5.parse(configData) as AppConfig;\n break;\n case \"jsonc\":\n config = jsonc.parse(configData) as AppConfig;\n break;\n default:\n config = JSON.parse(configData) as AppConfig;\n break;\n }\n\n // 验证配置结构\n this.validateConfig(config);\n\n return config;\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(`配置文件格式错误: ${error.message}`);\n }\n throw error;\n }\n }\n\n /**\n * 验证配置文件结构\n */\n private validateConfig(config: unknown): void {\n if (!config || typeof config !== \"object\") {\n throw new Error(\"配置文件格式错误:根对象无效\");\n }\n\n const configObj = config as Record<string, unknown>;\n\n if (configObj.mcpEndpoint === undefined || configObj.mcpEndpoint === null) {\n throw new Error(\"配置文件格式错误:mcpEndpoint 字段无效\");\n }\n\n // 验证 mcpEndpoint 类型(字符串或字符串数组)\n if (typeof configObj.mcpEndpoint === \"string\") {\n // 空字符串是允许的,getMcpEndpoints 会返回空数组\n } else if (Array.isArray(configObj.mcpEndpoint)) {\n if (configObj.mcpEndpoint.length === 0) {\n throw new Error(\"配置文件格式错误:mcpEndpoint 数组不能为空\");\n }\n for (const endpoint of configObj.mcpEndpoint) {\n if (typeof endpoint !== \"string\" || endpoint.trim() === \"\") {\n throw new Error(\n \"配置文件格式错误:mcpEndpoint 数组中的每个元素必须是非空字符串\"\n );\n }\n }\n } else {\n throw new Error(\"配置文件格式错误:mcpEndpoint 必须是字符串或字符串数组\");\n }\n\n if (!configObj.mcpServers || typeof configObj.mcpServers !== \"object\") {\n throw new Error(\"配置文件格式错误:mcpServers 字段无效\");\n }\n\n // 验证每个 MCP 服务配置\n for (const [serverName, serverConfig] of Object.entries(\n configObj.mcpServers as Record<string, unknown>\n )) {\n if (!serverConfig || typeof serverConfig !== \"object\") {\n throw new Error(`配置文件格式错误:mcpServers.${serverName} 无效`);\n }\n\n const sc = serverConfig as Record<string, unknown>;\n\n // 检查服务类型\n if (sc.url && typeof sc.url === \"string\") {\n // URL 类型的服务(SSE 或 Streamable HTTP)\n // type 字段是可选的,可以是 \"sse\" 或 \"streamable-http\"\n if (sc.type && sc.type !== \"sse\" && sc.type !== \"streamable-http\") {\n throw new Error(\n `配置文件格式错误:mcpServers.${serverName}.type 必须是 \"sse\" 或 \"streamable-http\"`\n );\n }\n } else {\n // 本地类型的验证\n if (!sc.command || typeof sc.command !== \"string\") {\n throw new Error(\n `配置文件格式错误:mcpServers.${serverName}.command 无效`\n );\n }\n\n if (!Array.isArray(sc.args)) {\n throw new Error(\n `配置文件格式错误:mcpServers.${serverName}.args 必须是数组`\n );\n }\n\n if (sc.env && typeof sc.env !== \"object\") {\n throw new Error(\n `配置文件格式错误:mcpServers.${serverName}.env 必须是对象`\n );\n }\n }\n }\n }\n\n /**\n * 获取配置(只读)\n */\n public getConfig(): Readonly<AppConfig> {\n if (!this.config) {\n this.config = this.loadConfig();\n }\n\n // 返回深度只读副本\n return JSON.parse(JSON.stringify(this.config));\n }\n\n /**\n * 获取 MCP 端点(向后兼容)\n * @deprecated 使用 getMcpEndpoints() 获取所有端点\n */\n public getMcpEndpoint(): string {\n const config = this.getConfig();\n if (Array.isArray(config.mcpEndpoint)) {\n return config.mcpEndpoint[0] || \"\";\n }\n return config.mcpEndpoint;\n }\n\n /**\n * 获取所有 MCP 端点\n */\n public getMcpEndpoints(): string[] {\n const config = this.getConfig();\n if (Array.isArray(config.mcpEndpoint)) {\n return [...config.mcpEndpoint];\n }\n return config.mcpEndpoint ? [config.mcpEndpoint] : [];\n }\n\n /**\n * 获取 MCP 服务配置\n */\n public getMcpServers(): Readonly<Record<string, MCPServerConfig>> {\n const config = this.getConfig();\n return config.mcpServers;\n }\n\n /**\n * 获取 MCP 服务工具配置\n */\n public getMcpServerConfig(): Readonly<Record<string, MCPServerToolsConfig>> {\n const config = this.getConfig();\n return config.mcpServerConfig || {};\n }\n\n /**\n * 获取指定服务的工具配置\n */\n public getServerToolsConfig(\n serverName: string\n ): Readonly<Record<string, MCPToolConfig>> {\n const serverConfig = this.getMcpServerConfig();\n return serverConfig[serverName]?.tools || {};\n }\n\n /**\n * 检查工具是否启用\n */\n public isToolEnabled(serverName: string, toolName: string): boolean {\n const toolsConfig = this.getServerToolsConfig(serverName);\n const toolConfig = toolsConfig[toolName];\n return toolConfig?.enable !== false; // 默认启用\n }\n\n /**\n * 更新 MCP 端点(支持字符串或数组)\n */\n public updateMcpEndpoint(endpoint: string | string[]): void {\n if (Array.isArray(endpoint)) {\n if (endpoint.length === 0) {\n throw new Error(\"MCP 端点数组不能为空\");\n }\n for (const ep of endpoint) {\n if (!ep || typeof ep !== \"string\") {\n throw new Error(\"MCP 端点数组中的每个元素必须是非空字符串\");\n }\n }\n } else {\n if (!endpoint || typeof endpoint !== \"string\") {\n throw new Error(\"MCP 端点必须是非空字符串\");\n }\n }\n\n const config = this.getConfig();\n const newConfig = { ...config, mcpEndpoint: endpoint };\n this.saveConfig(newConfig);\n }\n\n /**\n * 添加 MCP 端点\n */\n public addMcpEndpoint(endpoint: string): void {\n if (!endpoint || typeof endpoint !== \"string\") {\n throw new Error(\"MCP 端点必须是非空字符串\");\n }\n\n const config = this.getConfig();\n const currentEndpoints = this.getMcpEndpoints();\n\n // 检查是否已存在\n if (currentEndpoints.includes(endpoint)) {\n throw new Error(`MCP 端点 ${endpoint} 已存在`);\n }\n\n const newEndpoints = [...currentEndpoints, endpoint];\n const newConfig = { ...config, mcpEndpoint: newEndpoints };\n this.saveConfig(newConfig);\n }\n\n /**\n * 移除 MCP 端点\n */\n public removeMcpEndpoint(endpoint: string): void {\n if (!endpoint || typeof endpoint !== \"string\") {\n throw new Error(\"MCP 端点必须是非空字符串\");\n }\n\n const config = this.getConfig();\n const currentEndpoints = this.getMcpEndpoints();\n\n // 检查是否存在\n const index = currentEndpoints.indexOf(endpoint);\n if (index === -1) {\n throw new Error(`MCP 端点 ${endpoint} 不存在`);\n }\n\n // 不允许删除最后一个端点\n if (currentEndpoints.length === 1) {\n throw new Error(\"不能删除最后一个 MCP 端点\");\n }\n\n const newEndpoints = currentEndpoints.filter((ep) => ep !== endpoint);\n const newConfig = { ...config, mcpEndpoint: newEndpoints };\n this.saveConfig(newConfig);\n }\n\n /**\n * 更新 MCP 服务配置\n */\n public updateMcpServer(\n serverName: string,\n serverConfig: MCPServerConfig\n ): void {\n if (!serverName || typeof serverName !== \"string\") {\n throw new Error(\"服务名称必须是非空字符串\");\n }\n\n // 验证服务配置\n if (\"type\" in serverConfig && serverConfig.type === \"sse\") {\n // SSE 类型的验证\n if (!serverConfig.url || typeof serverConfig.url !== \"string\") {\n throw new Error(\"SSE 服务配置的 url 字段必须是非空字符串\");\n }\n } else {\n // 本地类型的验证\n const localConfig = serverConfig as LocalMCPServerConfig;\n if (!localConfig.command || typeof localConfig.command !== \"string\") {\n throw new Error(\"服务配置的 command 字段必须是非空字符串\");\n }\n\n if (!Array.isArray(localConfig.args)) {\n throw new Error(\"服务配置的 args 字段必须是数组\");\n }\n\n if (localConfig.env && typeof localConfig.env !== \"object\") {\n throw new Error(\"服务配置的 env 字段必须是对象\");\n }\n }\n\n const config = this.getConfig();\n const newConfig = {\n ...config,\n mcpServers: {\n ...config.mcpServers,\n [serverName]: serverConfig,\n },\n };\n this.saveConfig(newConfig);\n }\n\n /**\n * 删除 MCP 服务配置\n */\n public removeMcpServer(serverName: string): void {\n if (!serverName || typeof serverName !== \"string\") {\n throw new Error(\"服务名称必须是非空字符串\");\n }\n\n const config = this.getConfig();\n if (!config.mcpServers[serverName]) {\n throw new Error(`服务 ${serverName} 不存在`);\n }\n\n const newMcpServers = { ...config.mcpServers };\n delete newMcpServers[serverName];\n\n const newConfig = {\n ...config,\n mcpServers: newMcpServers,\n };\n this.saveConfig(newConfig);\n }\n\n /**\n * 更新服务工具配置\n */\n public updateServerToolsConfig(\n serverName: string,\n toolsConfig: Record<string, MCPToolConfig>\n ): void {\n const config = this.getConfig();\n const newConfig = { ...config };\n\n // 确保 mcpServerConfig 存在\n if (!newConfig.mcpServerConfig) {\n newConfig.mcpServerConfig = {};\n }\n\n // 如果 toolsConfig 为空对象,则删除该服务的配置\n if (Object.keys(toolsConfig).length === 0) {\n delete newConfig.mcpServerConfig[serverName];\n } else {\n // 更新指定服务的工具配置\n newConfig.mcpServerConfig[serverName] = {\n tools: toolsConfig,\n };\n }\n\n this.saveConfig(newConfig);\n }\n\n /**\n * 删除指定服务器的工具配置\n */\n public removeServerToolsConfig(serverName: string): void {\n const config = this.getConfig();\n const newConfig = { ...config };\n\n // 确保 mcpServerConfig 存在\n if (newConfig.mcpServerConfig) {\n // 删除指定服务的工具配置\n delete newConfig.mcpServerConfig[serverName];\n this.saveConfig(newConfig);\n }\n }\n\n /**\n * 设置工具启用状态\n */\n public setToolEnabled(\n serverName: string,\n toolName: string,\n enabled: boolean,\n description?: string\n ): void {\n const config = this.getConfig();\n const newConfig = { ...config };\n\n // 确保 mcpServerConfig 存在\n if (!newConfig.mcpServerConfig) {\n newConfig.mcpServerConfig = {};\n }\n\n // 确保服务配置存在\n if (!newConfig.mcpServerConfig[serverName]) {\n newConfig.mcpServerConfig[serverName] = { tools: {} };\n }\n\n // 更新工具配置\n newConfig.mcpServerConfig[serverName].tools[toolName] = {\n enable: enabled,\n ...(description && { description }),\n };\n\n this.saveConfig(newConfig);\n }\n\n /**\n * 保存配置到文件\n * 始终保存为标准 JSON 格式以确保向后兼容性\n */\n private saveConfig(config: AppConfig): void {\n try {\n // 验证配置\n this.validateConfig(config);\n\n // 获取配置文件路径,但始终保存为 JSON 格式\n const configDir = process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n const configPath = resolve(configDir, \"xiaozhi.config.json\");\n\n // 格式化 JSON 并保存\n const configJson = JSON.stringify(config, null, 2);\n writeFileSync(configPath, configJson, \"utf8\");\n\n // 更新缓存\n this.config = config;\n } catch (error) {\n throw new Error(\n `保存配置失败: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * 重新加载配置(清除缓存)\n */\n public reloadConfig(): void {\n this.config = null;\n }\n\n /**\n * 获取配置文件路径\n */\n public getConfigPath(): string {\n return this.getConfigFilePath();\n }\n\n /**\n * 获取默认配置文件路径\n */\n public getDefaultConfigPath(): string {\n return this.defaultConfigPath;\n }\n\n /**\n * 获取连接配置(包含默认值)\n */\n public getConnectionConfig(): Required<ConnectionConfig> {\n const config = this.getConfig();\n const connectionConfig = config.connection || {};\n\n return {\n heartbeatInterval:\n connectionConfig.heartbeatInterval ??\n DEFAULT_CONNECTION_CONFIG.heartbeatInterval,\n heartbeatTimeout:\n connectionConfig.heartbeatTimeout ??\n DEFAULT_CONNECTION_CONFIG.heartbeatTimeout,\n reconnectInterval:\n connectionConfig.reconnectInterval ??\n DEFAULT_CONNECTION_CONFIG.reconnectInterval,\n };\n }\n\n /**\n * 获取心跳检测间隔(毫秒)\n */\n public getHeartbeatInterval(): number {\n return this.getConnectionConfig().heartbeatInterval;\n }\n\n /**\n * 获取心跳超时时间(毫秒)\n */\n public getHeartbeatTimeout(): number {\n return this.getConnectionConfig().heartbeatTimeout;\n }\n\n /**\n * 获取重连间隔(毫秒)\n */\n public getReconnectInterval(): number {\n return this.getConnectionConfig().reconnectInterval;\n }\n\n /**\n * 更新连接配置\n */\n public updateConnectionConfig(\n connectionConfig: Partial<ConnectionConfig>\n ): void {\n const config = this.getConfig();\n const currentConnectionConfig = config.connection || {};\n\n const newConnectionConfig = {\n ...currentConnectionConfig,\n ...connectionConfig,\n };\n\n const newConfig = {\n ...config,\n connection: newConnectionConfig,\n };\n\n this.saveConfig(newConfig);\n }\n\n /**\n * 设置心跳检测间隔\n */\n public setHeartbeatInterval(interval: number): void {\n if (interval <= 0) {\n throw new Error(\"心跳检测间隔必须大于0\");\n }\n this.updateConnectionConfig({ heartbeatInterval: interval });\n }\n\n /**\n * 设置心跳超时时间\n */\n public setHeartbeatTimeout(timeout: number): void {\n if (timeout <= 0) {\n throw new Error(\"心跳超时时间必须大于0\");\n }\n this.updateConnectionConfig({ heartbeatTimeout: timeout });\n }\n\n /**\n * 设置重连间隔\n */\n public setReconnectInterval(interval: number): void {\n if (interval <= 0) {\n throw new Error(\"重连间隔必须大于0\");\n }\n this.updateConnectionConfig({ reconnectInterval: interval });\n }\n\n /**\n * 获取 ModelScope 配置\n */\n public getModelScopeConfig(): Readonly<ModelScopeConfig> {\n const config = this.getConfig();\n return config.modelscope || {};\n }\n\n /**\n * 获取 ModelScope API Key\n * 优先从配置文件读取,其次从环境变量读取\n */\n public getModelScopeApiKey(): string | undefined {\n const modelScopeConfig = this.getModelScopeConfig();\n return modelScopeConfig.apiKey || process.env.MODELSCOPE_API_TOKEN;\n }\n\n /**\n * 更新 ModelScope 配置\n */\n public updateModelScopeConfig(\n modelScopeConfig: Partial<ModelScopeConfig>\n ): void {\n const config = this.getConfig();\n const currentModelScopeConfig = config.modelscope || {};\n\n const newModelScopeConfig = {\n ...currentModelScopeConfig,\n ...modelScopeConfig,\n };\n\n const newConfig = {\n ...config,\n modelscope: newModelScopeConfig,\n };\n\n this.saveConfig(newConfig);\n }\n\n /**\n * 设置 ModelScope API Key\n */\n public setModelScopeApiKey(apiKey: string): void {\n if (!apiKey || typeof apiKey !== \"string\") {\n throw new Error(\"API Key 必须是非空字符串\");\n }\n this.updateModelScopeConfig({ apiKey });\n }\n\n /**\n * 获取 Web UI 配置\n */\n public getWebUIConfig(): Readonly<WebUIConfig> {\n const config = this.getConfig();\n return config.webUI || {};\n }\n\n /**\n * 获取 Web UI 端口号\n */\n public getWebUIPort(): number {\n const webUIConfig = this.getWebUIConfig();\n return webUIConfig.port ?? 9999; // 默认端口 9999\n }\n\n /**\n * 更新 Web UI 配置\n */\n public updateWebUIConfig(webUIConfig: Partial<WebUIConfig>): void {\n const config = this.getConfig();\n const currentWebUIConfig = config.webUI || {};\n\n const newWebUIConfig = {\n ...currentWebUIConfig,\n ...webUIConfig,\n };\n\n const newConfig = {\n ...config,\n webUI: newWebUIConfig,\n };\n\n this.saveConfig(newConfig);\n }\n\n /**\n * 设置 Web UI 端口号\n */\n public setWebUIPort(port: number): void {\n if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n throw new Error(\"端口号必须是 1-65535 之间的整数\");\n }\n this.updateWebUIConfig({ port });\n }\n}\n\n// 导出单例实例\nexport const configManager = ConfigManager.getInstance();\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport chalk from \"chalk\";\nimport { type consola, createConsola } from \"consola\";\n\nfunction formatDateTime(date: Date) {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const hours = String(date.getHours()).padStart(2, \"0\");\n const minutes = String(date.getMinutes()).padStart(2, \"0\");\n const seconds = String(date.getSeconds()).padStart(2, \"0\");\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;\n}\n\nexport class Logger {\n private logFilePath: string | null = null;\n private writeStream: fs.WriteStream | null = null;\n private consolaInstance: typeof consola;\n private isDaemonMode: boolean;\n\n constructor() {\n // 检查是否为守护进程模式\n this.isDaemonMode = process.env.XIAOZHI_DAEMON === \"true\";\n // 创建自定义的 consola 实例,禁用图标并自定义格式\n this.consolaInstance = createConsola({\n formatOptions: {\n date: false,\n colors: true,\n compact: true,\n },\n fancy: false,\n });\n\n // 保存对当前实例的引用,以便在闭包中访问\n const isDaemonMode = this.isDaemonMode;\n\n // 自定义格式化器\n this.consolaInstance.setReporters([\n {\n log: (logObj) => {\n const levelMap: Record<string, string> = {\n info: \"INFO\",\n success: \"SUCCESS\",\n warn: \"WARN\",\n error: \"ERROR\",\n debug: \"DEBUG\",\n log: \"LOG\",\n };\n\n const colorMap: Record<string, (text: string) => string> = {\n info: chalk.blue,\n success: chalk.green,\n warn: chalk.yellow,\n error: chalk.red,\n debug: chalk.gray,\n log: (text: string) => text,\n };\n\n const level = levelMap[logObj.type] || logObj.type.toUpperCase();\n const colorFn = colorMap[logObj.type] || ((text: string) => text);\n const timestamp = formatDateTime(new Date());\n\n // 为级别添加颜色\n const coloredLevel = colorFn(`[${level}]`);\n const message = `[${timestamp}] ${coloredLevel} ${logObj.args.join(\n \" \"\n )}`;\n\n // 守护进程模式下不输出到控制台,只写入文件\n if (!isDaemonMode) {\n // 输出到 stderr(与原来保持一致)\n try {\n console.error(message);\n } catch (error) {\n // 忽略 EPIPE 错误\n if (error instanceof Error && error.message?.includes(\"EPIPE\")) {\n return;\n }\n throw error;\n }\n }\n },\n },\n ]);\n }\n\n /**\n * 初始化日志文件\n * @param projectDir 项目目录\n */\n initLogFile(projectDir: string): void {\n this.logFilePath = path.join(projectDir, \"xiaozhi.log\");\n\n // 确保日志文件存在\n if (!fs.existsSync(this.logFilePath)) {\n fs.writeFileSync(this.logFilePath, \"\");\n }\n\n // 创建写入流,追加模式\n this.writeStream = fs.createWriteStream(this.logFilePath, {\n flags: \"a\",\n encoding: \"utf8\",\n });\n }\n\n /**\n * 记录日志到文件\n * @param level 日志级别\n * @param message 日志消息\n * @param args 额外参数\n */\n private logToFile(level: string, message: string, ...args: any[]): void {\n if (this.writeStream) {\n const timestamp = new Date().toISOString();\n const formattedMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;\n const fullMessage =\n args.length > 0\n ? `${formattedMessage} ${args\n .map((arg) =>\n typeof arg === \"object\" ? JSON.stringify(arg) : String(arg)\n )\n .join(\" \")}`\n : formattedMessage;\n\n this.writeStream.write(`${fullMessage}\\n`);\n }\n }\n\n /**\n * 设置是否启用文件日志\n * @param enable 是否启用\n */\n enableFileLogging(enable: boolean): void {\n if (enable && !this.writeStream && this.logFilePath) {\n this.writeStream = fs.createWriteStream(this.logFilePath, {\n flags: \"a\",\n encoding: \"utf8\",\n });\n } else if (!enable && this.writeStream) {\n this.writeStream.end();\n this.writeStream = null;\n }\n }\n\n /**\n * 日志方法\n */\n info(message: string, ...args: any[]): void {\n this.consolaInstance.info(message, ...args);\n this.logToFile(\"info\", message, ...args);\n }\n\n success(message: string, ...args: any[]): void {\n this.consolaInstance.success(message, ...args);\n this.logToFile(\"success\", message, ...args);\n }\n\n warn(message: string, ...args: any[]): void {\n this.consolaInstance.warn(message, ...args);\n this.logToFile(\"warn\", message, ...args);\n }\n\n error(message: string, ...args: any[]): void {\n this.consolaInstance.error(message, ...args);\n this.logToFile(\"error\", message, ...args);\n }\n\n debug(message: string, ...args: any[]): void {\n this.consolaInstance.debug(message, ...args);\n this.logToFile(\"debug\", message, ...args);\n }\n\n log(message: string, ...args: any[]): void {\n this.consolaInstance.log(message, ...args);\n this.logToFile(\"log\", message, ...args);\n }\n\n /**\n * 创建一个带标签的日志实例(已废弃,直接返回原实例)\n * @param tag 标签(不再使用)\n * @deprecated 标签功能已移除\n */\n withTag(tag: string): Logger {\n // 不再添加标签,直接返回共享实例\n return this;\n }\n\n /**\n * 关闭日志文件流\n */\n close(): void {\n if (this.writeStream) {\n this.writeStream.end();\n this.writeStream = null;\n }\n }\n}\n\n// 导出单例实例\nexport const logger = new Logger();\n","#!/usr/bin/env node\n\n/**\n * Multi-Endpoint MCP Pipe - 支持多个 MCP 接入点\n * 管理多个 WebSocket 连接,并正确路由消息到对应的接入点\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport process from \"node:process\";\nimport WebSocket from \"ws\";\nimport { configManager } from \"./configManager.js\";\nimport { logger as globalLogger } from \"./logger.js\";\n\n// 检查是否在测试环境中运行\nconst isTestEnvironment = (): boolean => {\n return process.env.NODE_ENV === \"test\" || process.env.VITEST === \"true\";\n};\n\n// 为 MultiEndpointMCPPipe 创建带标签的 logger\nconst logger = globalLogger.withTag(\"MULTI_MCP_PIPE\");\n\n// 如果在守护进程模式下运行,初始化日志文件\nif (process.env.XIAOZHI_DAEMON === \"true\" && process.env.XIAOZHI_CONFIG_DIR) {\n globalLogger.initLogFile(process.env.XIAOZHI_CONFIG_DIR);\n globalLogger.enableFileLogging(true);\n}\n\ninterface EndpointConnection {\n url: string;\n websocket: WebSocket | null;\n isConnected: boolean;\n reconnectAttempt: number;\n maxReconnectAttempts: number; // 最大重连次数\n reconnectTimer?: NodeJS.Timeout;\n heartbeatTimer?: NodeJS.Timeout;\n heartbeatTimeoutTimer?: NodeJS.Timeout;\n process: ChildProcess | null; // 每个端点独立的 MCP 进程\n stdoutBuffer: string; // 每个端点独立的输出缓冲区\n}\n\nexport class MultiEndpointMCPPipe {\n private mcpScript: string;\n private endpoints: Map<string, EndpointConnection>;\n private shouldReconnect: boolean;\n private shutdownResolve?: () => void;\n private connectionConfig: {\n heartbeatInterval: number;\n heartbeatTimeout: number;\n reconnectInterval: number;\n };\n\n constructor(mcpScript: string, endpointUrls: string[]) {\n this.mcpScript = mcpScript;\n this.endpoints = new Map();\n this.shouldReconnect = true;\n\n // 记录端点数量,明确表示支持单端点和多端点\n logger.info(\n endpointUrls.length === 1\n ? `初始化单端点连接: ${endpointUrls[0]}`\n : `初始化多端点连接(${endpointUrls.length} 个端点)`\n );\n\n // 初始化所有端点\n for (const url of endpointUrls) {\n this.endpoints.set(url, {\n url,\n websocket: null,\n isConnected: false,\n reconnectAttempt: 0,\n maxReconnectAttempts: 5, // 允许最多5次重连尝试\n process: null,\n stdoutBuffer: \"\",\n });\n }\n\n // 获取连接配置\n try {\n this.connectionConfig = configManager.getConnectionConfig();\n logger.info(\n `连接配置: 心跳间隔=${this.connectionConfig.heartbeatInterval}ms, ` +\n `心跳超时=${this.connectionConfig.heartbeatTimeout}ms, ` +\n `重连间隔=${this.connectionConfig.reconnectInterval}ms`\n );\n } catch (error) {\n this.connectionConfig = {\n heartbeatInterval: 30000,\n heartbeatTimeout: 10000,\n reconnectInterval: 5000,\n };\n logger.warn(\n `无法获取连接配置,使用默认值: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n async start() {\n // 连接到所有端点(每个端点会启动自己的 MCP 进程)\n await this.connectToAllEndpoints();\n\n // 报告状态到 Web UI\n this.reportStatusToWebUI();\n\n // 保持进程运行\n return new Promise<void>((resolve) => {\n this.shutdownResolve = resolve;\n });\n }\n\n async connectToAllEndpoints() {\n const connectionPromises: Promise<void>[] = [];\n\n for (const [url, endpoint] of this.endpoints) {\n connectionPromises.push(this.connectToEndpoint(url));\n }\n\n await Promise.allSettled(connectionPromises);\n }\n\n async connectToEndpoint(endpointUrl: string) {\n const endpoint = this.endpoints.get(endpointUrl);\n if (!endpoint || endpoint.isConnected) {\n return;\n }\n\n // 先为该端点启动 MCP 进程\n this.startMCPProcessForEndpoint(endpointUrl);\n\n logger.info(`正在连接到 WebSocket 服务器: ${endpointUrl}`);\n\n const ws = new WebSocket(endpointUrl);\n endpoint.websocket = ws;\n\n ws.on(\"open\", () => {\n logger.info(`成功连接到 WebSocket 服务器: ${endpointUrl}`);\n endpoint.isConnected = true;\n endpoint.reconnectAttempt = 0; // 重置重连计数器\n\n // 清除重连定时器\n if (endpoint.reconnectTimer) {\n clearTimeout(endpoint.reconnectTimer);\n endpoint.reconnectTimer = undefined;\n }\n\n // 报告状态\n this.reportStatusToWebUI();\n\n // 启动心跳检测\n this.startHeartbeat(endpointUrl);\n });\n\n ws.on(\"message\", (data: WebSocket.Data) => {\n const message = data.toString();\n logger.info(`<< [${endpointUrl}] WebSocket收到消息: ${message}`);\n\n // 解析消息检查是否是初始化确认\n try {\n const parsedMessage = JSON.parse(message);\n\n // 检查是否是初始化相关的消息\n if (\n parsedMessage.method === \"notifications/initialized\" ||\n (parsedMessage.method === \"tools/list\" && parsedMessage.id) ||\n parsedMessage?.result?.tools\n ) {\n // 延迟一秒后报告状态,确保初始化完成\n setTimeout(() => {\n this.reportStatusToWebUI();\n }, 1000);\n }\n } catch (e) {\n // 忽略解析错误\n }\n\n // 将消息写入对应端点的进程标准输入\n if (endpoint.process?.stdin && !endpoint.process.stdin.destroyed) {\n endpoint.process.stdin.write(`${message}\\n`);\n }\n });\n\n ws.on(\"close\", (code: number, reason: Buffer) => {\n logger.error(`[${endpointUrl}] WebSocket 连接已关闭: ${code} ${reason}`);\n endpoint.isConnected = false;\n endpoint.websocket = null;\n\n // 停止心跳检测\n this.stopHeartbeat(endpointUrl);\n\n // 报告断开状态\n this.reportStatusToWebUI();\n\n // 改进重连逻辑:允许对4004错误进行有限次数的重连\n if (this.shouldReconnect) {\n // 对于4004错误,只在重连次数未超过限制时重连\n if (code === 4004) {\n if (endpoint.reconnectAttempt < endpoint.maxReconnectAttempts) {\n logger.warn(\n `[${endpointUrl}] 服务器内部错误(4004),将进行第 ${\n endpoint.reconnectAttempt + 1\n } 次重连尝试(最多 ${endpoint.maxReconnectAttempts} 次)`\n );\n this.scheduleReconnect(endpointUrl);\n } else {\n logger.error(\n `[${endpointUrl}] 服务器内部错误(4004),已达到最大重连次数(${endpoint.maxReconnectAttempts}),停止重连`\n );\n }\n } else {\n // 其他错误码正常重连\n this.scheduleReconnect(endpointUrl);\n }\n }\n });\n\n ws.on(\"error\", (error: Error) => {\n logger.error(`[${endpointUrl}] WebSocket 错误: ${error.message}`);\n endpoint.isConnected = false;\n\n // 停止心跳检测\n this.stopHeartbeat(endpointUrl);\n });\n\n // 处理 pong 响应\n ws.on(\"pong\", () => {\n // 收到 pong 响应,清除心跳超时定时器\n if (endpoint.heartbeatTimeoutTimer) {\n clearTimeout(endpoint.heartbeatTimeoutTimer);\n endpoint.heartbeatTimeoutTimer = undefined;\n }\n });\n }\n\n scheduleReconnect(endpointUrl: string) {\n const endpoint = this.endpoints.get(endpointUrl);\n if (!endpoint || !this.shouldReconnect) return;\n\n // 清除之前的重连定时器\n if (endpoint.reconnectTimer) {\n clearTimeout(endpoint.reconnectTimer);\n }\n\n endpoint.reconnectAttempt++;\n\n // 使用指数退避算法计算重连延迟\n const baseDelay = this.connectionConfig.reconnectInterval;\n const maxDelay = 60000; // 最大延迟60秒\n const exponentialDelay = Math.min(\n baseDelay * 2 ** (endpoint.reconnectAttempt - 1),\n maxDelay\n );\n\n logger.info(\n `[${endpointUrl}] 计划在 ${(exponentialDelay / 1000).toFixed(\n 2\n )} 秒后进行第 ${endpoint.reconnectAttempt} 次重连尝试...`\n );\n\n endpoint.reconnectTimer = setTimeout(async () => {\n if (this.shouldReconnect) {\n // 在重连前清理资源\n await this.cleanupEndpointResources(endpointUrl);\n\n // 如果MCP进程不存在,先尝试重启\n if (!endpoint.process || endpoint.process.killed) {\n logger.info(`[${endpointUrl}] MCP 进程未运行,将在重连时启动...`);\n }\n this.connectToEndpoint(endpointUrl);\n }\n }, exponentialDelay);\n }\n\n startMCPProcessForEndpoint(endpointUrl: string) {\n const endpoint = this.endpoints.get(endpointUrl);\n if (!endpoint) {\n logger.error(`端点不存在: ${endpointUrl}`);\n return;\n }\n\n if (endpoint.process) {\n logger.info(`[${endpointUrl}] MCP 进程已在运行`);\n return;\n }\n\n logger.info(`[${endpointUrl}] 正在启动 MCP 进程`);\n\n endpoint.process = spawn(\"node\", [this.mcpScript], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n // 处理进程标准输出 - 发送到对应的 WebSocket\n endpoint.process.stdout?.on(\"data\", (data: Buffer) => {\n // 将数据添加到缓冲区\n endpoint.stdoutBuffer += data.toString();\n\n // 按换行符分割消息\n const lines = endpoint.stdoutBuffer.split(\"\\n\");\n endpoint.stdoutBuffer = lines.pop() || \"\"; // 保留最后一个不完整的行\n\n // 处理每个完整的消息\n for (const line of lines) {\n if (line.trim()) {\n this.handleMCPMessage(endpointUrl, line);\n }\n }\n });\n\n // 处理进程标准错误\n endpoint.process.stderr?.on(\"data\", (data: Buffer) => {\n if (process.env.XIAOZHI_DAEMON !== \"true\") {\n try {\n process.stderr.write(data);\n } catch (error) {\n // 忽略 EPIPE 错误\n }\n }\n });\n\n // 处理进程退出\n endpoint.process.on(\n \"exit\",\n (code: number | null, signal: NodeJS.Signals | null) => {\n logger.warn(\n `[${endpointUrl}] MCP 进程已退出,退出码: ${code}, 信号: ${signal}`\n );\n endpoint.process = null;\n\n if (\n this.shouldReconnect &&\n signal !== \"SIGTERM\" &&\n signal !== \"SIGKILL\"\n ) {\n logger.info(\n `[${endpointUrl}] MCP 进程意外退出,将在下次重连时尝试重启`\n );\n }\n }\n );\n\n // 处理进程错误\n endpoint.process.on(\"error\", (error: Error) => {\n logger.error(`[${endpointUrl}] 进程错误: ${error.message}`);\n endpoint.process = null;\n\n if (this.shouldReconnect) {\n logger.info(\n `[${endpointUrl}] MCP 进程发生错误,将在下次重连时尝试重启`\n );\n }\n });\n }\n\n handleMCPMessage(endpointUrl: string, line: string) {\n logger.info(\n `>> [${endpointUrl}] mcpServerProxy发送消息长度: ${line.length} 字节`\n );\n logger.info(\n `>> [${endpointUrl}] mcpServerProxy发送消息: ${line.substring(0, 500)}...`\n );\n\n // 直接发送回对应的端点\n this.sendToEndpoint(endpointUrl, line);\n }\n\n sendToEndpoint(endpointUrl: string, message: string) {\n const endpoint = this.endpoints.get(endpointUrl);\n if (\n !endpoint ||\n !endpoint.websocket ||\n endpoint.websocket.readyState !== WebSocket.OPEN\n ) {\n logger.warn(`[${endpointUrl}] 端点不可用,消息无法发送`);\n return;\n }\n\n try {\n endpoint.websocket.send(`${message}\\n`);\n logger.info(`>> [${endpointUrl}] 成功发送消息到 WebSocket`);\n } catch (error) {\n logger.error(`>> [${endpointUrl}] 发送消息到 WebSocket 失败: ${error}`);\n }\n }\n\n startHeartbeat(endpointUrl: string) {\n const endpoint = this.endpoints.get(endpointUrl);\n if (!endpoint) return;\n\n // 清除之前的心跳定时器\n this.stopHeartbeat(endpointUrl);\n\n // 设置心跳定时器\n endpoint.heartbeatTimer = setInterval(() => {\n if (\n endpoint.websocket &&\n endpoint.websocket.readyState === WebSocket.OPEN\n ) {\n // 发送 ping\n endpoint.websocket.ping();\n\n // 设置心跳超时定时器\n endpoint.heartbeatTimeoutTimer = setTimeout(() => {\n logger.warn(`[${endpointUrl}] 心跳超时,断开连接`);\n endpoint.websocket?.close();\n }, this.connectionConfig.heartbeatTimeout);\n\n // 定期报告状态到 Web UI\n this.reportStatusToWebUI();\n }\n }, this.connectionConfig.heartbeatInterval);\n }\n\n stopHeartbeat(endpointUrl: string) {\n const endpoint = this.endpoints.get(endpointUrl);\n if (!endpoint) return;\n\n if (endpoint.heartbeatTimer) {\n clearInterval(endpoint.heartbeatTimer);\n endpoint.heartbeatTimer = undefined;\n }\n\n if (endpoint.heartbeatTimeoutTimer) {\n clearTimeout(endpoint.heartbeatTimeoutTimer);\n endpoint.heartbeatTimeoutTimer = undefined;\n }\n }\n\n async cleanupEndpointResources(endpointUrl: string) {\n const endpoint = this.endpoints.get(endpointUrl);\n if (!endpoint) return;\n\n logger.debug(`[${endpointUrl}] 清理端点资源...`);\n\n // 停止心跳检测\n this.stopHeartbeat(endpointUrl);\n\n // 清除重连定时器\n if (endpoint.reconnectTimer) {\n clearTimeout(endpoint.reconnectTimer);\n endpoint.reconnectTimer = undefined;\n }\n\n // 关闭 WebSocket 连接\n if (endpoint.websocket) {\n try {\n if (endpoint.websocket.readyState === WebSocket.OPEN) {\n endpoint.websocket.close();\n }\n } catch (error) {\n logger.debug(`[${endpointUrl}] 关闭 WebSocket 时出错: ${error}`);\n }\n endpoint.websocket = null;\n }\n\n // 终止 MCP 进程(如果存在且仍在运行)\n if (endpoint.process && !endpoint.process.killed) {\n try {\n logger.debug(`[${endpointUrl}] 终止 MCP 进程...`);\n endpoint.process.kill(\"SIGTERM\");\n\n // 等待进程退出,最多等待2秒\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n if (endpoint.process && !endpoint.process.killed) {\n logger.warn(`[${endpointUrl}] MCP 进程未能正常退出,强制终止`);\n endpoint.process.kill(\"SIGKILL\");\n }\n resolve();\n }, 2000);\n\n endpoint.process?.on(\"exit\", () => {\n clearTimeout(timeout);\n resolve();\n });\n });\n } catch (error) {\n logger.warn(`[${endpointUrl}] 终止 MCP 进程时出错: ${error}`);\n }\n endpoint.process = null;\n }\n\n // 清空输出缓冲区\n endpoint.stdoutBuffer = \"\";\n endpoint.isConnected = false;\n\n logger.debug(`[${endpointUrl}] 端点资源清理完成`);\n }\n\n cleanup() {\n // 停止所有心跳检测\n for (const url of this.endpoints.keys()) {\n this.stopHeartbeat(url);\n }\n\n // 清除所有重连定时器,清理进程和缓冲区\n for (const [url, endpoint] of this.endpoints) {\n if (endpoint.reconnectTimer) {\n clearTimeout(endpoint.reconnectTimer);\n endpoint.reconnectTimer = undefined;\n }\n\n // 清空每个端点的缓冲区\n endpoint.stdoutBuffer = \"\";\n\n // 终止每个端点的进程\n if (endpoint.process) {\n logger.info(`[${url}] 正在终止 MCP 进程`);\n try {\n endpoint.process.kill(\"SIGTERM\");\n\n // 强制终止\n setTimeout(() => {\n if (endpoint.process && !endpoint.process.killed) {\n endpoint.process.kill(\"SIGKILL\");\n }\n }, 5000);\n } catch (error) {\n logger.error(\n `[${url}] 终止进程时出错: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n endpoint.process = null;\n }\n\n // 关闭 WebSocket 连接\n if (endpoint.websocket) {\n try {\n endpoint.websocket.close();\n } catch (error) {\n logger.warn(`[${url}] 关闭 WebSocket 时出错: ${error}`);\n }\n endpoint.websocket = null;\n }\n }\n }\n\n shutdown() {\n logger.info(\"正在关闭 Multi-Endpoint MCP Pipe...\");\n this.shouldReconnect = false;\n\n // 报告断开状态\n for (const endpoint of this.endpoints.values()) {\n endpoint.isConnected = false;\n }\n this.reportStatusToWebUI();\n\n this.cleanup();\n\n if (this.shutdownResolve) {\n this.shutdownResolve();\n }\n\n // 给状态报告一点时间\n // 在测试环境中不调用 process.exit\n if (!isTestEnvironment()) {\n setTimeout(() => {\n process.exit(0);\n }, 100);\n }\n }\n\n // 报告状态到 Web UI 服务器\n private async reportStatusToWebUI() {\n // 在测试环境中跳过\n if (isTestEnvironment()) {\n return;\n }\n\n try {\n // 从配置获取 WebUI 端口\n const port = configManager.getWebUIPort();\n const statusWs = new WebSocket(`ws://localhost:${port}`);\n\n statusWs.on(\"open\", () => {\n // 收集所有端点的状态\n const endpointStatuses: { url: string; connected: boolean }[] = [];\n for (const [url, endpoint] of this.endpoints) {\n endpointStatuses.push({\n url,\n connected: endpoint.isConnected,\n });\n }\n\n const status = {\n type: \"clientStatus\",\n data: {\n status: this.hasAnyConnection() ? \"connected\" : \"disconnected\",\n mcpEndpoints: endpointStatuses,\n activeMCPServers: [], // 由 mcpServerProxy 填充\n lastHeartbeat: Date.now(),\n },\n };\n statusWs.send(JSON.stringify(status));\n logger.debug(\"已向 Web UI 报告状态\");\n\n // 发送状态后关闭连接\n setTimeout(() => {\n statusWs.close();\n }, 1000);\n });\n\n statusWs.on(\"error\", (error) => {\n logger.debug(`Web UI 连接失败(可能未运行): ${error.message}`);\n });\n } catch (error) {\n logger.debug(\n `向 Web UI 报告状态失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n hasAnyConnection(): boolean {\n for (const endpoint of this.endpoints.values()) {\n if (endpoint.isConnected) {\n return true;\n }\n }\n return false;\n }\n}\n\n// 信号处理器\nexport function setupSignalHandlers(mcpPipe: MultiEndpointMCPPipe): void {\n const isDaemon = process.env.XIAOZHI_DAEMON === \"true\";\n\n process.on(\"SIGINT\", () => {\n logger.info(\"收到中断信号,正在关闭...\");\n mcpPipe.shutdown();\n });\n\n process.on(\"SIGTERM\", () => {\n logger.info(\"收到终止信号,正在关闭...\");\n mcpPipe.shutdown();\n });\n\n // 守护进程模式下的额外信号处理\n if (isDaemon) {\n process.on(\"SIGHUP\", () => {\n logger.info(\n \"收到 SIGHUP 信号(终端已关闭),继续在守护进程模式下运行...\"\n );\n });\n\n process.on(\"uncaughtException\", (error) => {\n if (error.message?.includes(\"EPIPE\")) {\n return;\n }\n logger.error(\n `未捕获的异常: ${error.message || error}\\n${error.stack || \"\"}`\n );\n });\n\n process.on(\"unhandledRejection\", (reason, promise) => {\n logger.error(\n `未处理的 Promise 拒绝: ${\n reason instanceof Error ? reason.message : String(reason)\n }`\n );\n });\n }\n}\n"],"mappings":"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","names":["spawn","randomUUID","EventEmitter","fs","os","path","fileURLToPath","express","copyFileSync","existsSync","readFileSync","writeFileSync","dirname","resolve","fileURLToPath","JSON5","jsonc","__dirname","dirname","fileURLToPath","DEFAULT_CONNECTION_CONFIG","ConfigManager","_ConfigManager","__name","resolve","configDir","configFileNames","fileName","filePath","existsSync","format","targetFileName","configPath","copyFileSync","configFileFormat","configData","readFileSync","config","JSON5","error","configObj","endpoint","serverName","serverConfig","sc","toolName","ep","newConfig","currentEndpoints","newEndpoints","localConfig","newMcpServers","toolsConfig","enabled","description","configJson","writeFileSync","connectionConfig","newConnectionConfig","interval","timeout","modelScopeConfig","newModelScopeConfig","apiKey","webUIConfig","newWebUIConfig","port","configManager","fs","path","chalk","createConsola","formatDateTime","date","year","month","day","hours","minutes","seconds","__name","Logger","createConsola","isDaemonMode","logObj","levelMap","colorMap","chalk","text","level","colorFn","timestamp","coloredLevel","message","error","projectDir","path","fs","args","formattedMessage","fullMessage","arg","enable","tag","logger","spawn","process","WebSocket","isTestEnvironment","__name","process","logger","MultiEndpointMCPPipe","mcpScript","endpointUrls","url","configManager","error","resolve","connectionPromises","endpoint","endpointUrl","ws","WebSocket","data","message","parsedMessage","code","reason","baseDelay","exponentialDelay","spawn","lines","line","signal","timeout","port","statusWs","endpointStatuses","status","__filename","fileURLToPath","__dirname","path","logger","MCP_SERVER_PROXY_FILENAME","MCPServer","EventEmitter","__name","port","express","req","res","next","clientId","sessionId","randomUUID","message","response","client","error","resolve","reject","timeoutId","data","lines","line","pendingRequest","results","mcpProxyResult","httpServerResult","normalizedPath","resolvedPath","allowedBaseDirectories","os","fs","dir","currentScript","searchDir","mcpProxyPath","i","testPath","distPath","projectRoot","rootDistPath","spawn","code","signal","timeout","dataHandler","endpoints","configManager","MultiEndpointMCPPipe"]}