xiaozhi-client 1.9.4-beta.4 → 1.9.4-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/WebServer.js +12 -12
- package/dist/backend/WebServer.js.map +1 -1
- package/dist/backend/WebServerLauncher.js +11 -11
- package/dist/backend/WebServerLauncher.js.map +1 -1
- package/dist/backend/managers/MCPServiceManagerSingleton.js +5 -5
- package/dist/backend/managers/MCPServiceManagerSingleton.js.map +1 -1
- package/dist/backend/package.json +9 -11
- package/dist/backend/templates/default/xiaozhi.cache.json +0 -0
- package/dist/backend/templates/default/xiaozhi.log +0 -0
- package/dist/backend/templates/hello-world/xiaozhi.cache.json +0 -0
- package/dist/backend/templates/hello-world/xiaozhi.log +0 -0
- package/dist/backend/templates/json5/xiaozhi.cache.json +0 -0
- package/dist/backend/templates/json5/xiaozhi.log +0 -0
- package/dist/backend/templates/jsonc/xiaozhi.cache.json +0 -0
- package/dist/backend/templates/jsonc/xiaozhi.log +0 -0
- package/dist/backend/templates/modelscope/xiaozhi.cache.json +0 -0
- package/dist/backend/templates/modelscope/xiaozhi.log +0 -0
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +580 -0
- package/dist/config/index.js +1784 -0
- package/dist/config/index.js.map +1 -0
- package/dist/frontend/assets/index-88NfCOo9.js.map +1 -1
- package/package.json +9 -11
- package/templates/default/xiaozhi.cache.json +0 -0
- package/templates/default/xiaozhi.log +0 -0
- package/templates/hello-world/xiaozhi.cache.json +0 -0
- package/templates/hello-world/xiaozhi.log +0 -0
- package/templates/json5/xiaozhi.cache.json +0 -0
- package/templates/json5/xiaozhi.log +0 -0
- package/templates/jsonc/xiaozhi.cache.json +0 -0
- package/templates/jsonc/xiaozhi.log +0 -0
- package/templates/modelscope/xiaozhi.cache.json +0 -0
- package/templates/modelscope/xiaozhi.log +0 -0
- package/dist/backend/lib/config/manager.js +0 -3
- package/dist/backend/lib/config/manager.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../apps/backend/lib/config/manager.ts","../../../apps/backend/services/EventBus.ts","../../../apps/backend/Logger.ts","../../../apps/backend/utils/TypeFieldNormalizer.ts","../../../apps/backend/utils/mcpServerUtils.ts","../../../apps/backend/lib/config/json5-adapter.ts","../../../apps/backend/lib/mcp/manager.ts","../../../apps/backend/lib/config/adapter.ts","../../../apps/backend/lib/mcp/types.ts","../../../apps/backend/lib/mcp/utils.ts","../../../apps/backend/lib/coze/index.ts","../../../apps/backend/lib/coze/config.ts","../../../apps/backend/lib/coze/client.ts","../../../apps/backend/lib/coze/service.ts","../../../apps/backend/types/mcp.ts","../../../apps/backend/types/timeout.ts","../../../apps/backend/lib/mcp/custom.ts","../../../apps/backend/lib/mcp/log.ts","../../../apps/backend/utils/PathUtils.ts","../../../apps/backend/lib/mcp/message.ts","../../../apps/backend/lib/mcp/transports/WebSocketAdapter.ts","../../../apps/backend/lib/mcp/connection.ts","../../../apps/backend/lib/mcp/transport-factory.ts","../../../apps/backend/lib/mcp/cache.ts","../../../apps/backend/types/hono.context.ts","../../../apps/backend/managers/MCPServiceManagerSingleton.ts"],"sourcesContent":["import { copyFileSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { CustomMCPTool as CoreCustomMCPTool } from \"@/lib/mcp/types.js\";\nimport { getEventBus } from \"@services/EventBus.js\";\nimport { TypeFieldNormalizer } from \"@utils/TypeFieldNormalizer.js\";\nimport { validateMcpServerConfig } from \"@utils/mcpServerUtils\";\nimport * as commentJson from \"comment-json\";\nimport dayjs from \"dayjs\";\nimport { createJson5Writer, parseJson5 } from \"./json5-adapter.js\";\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 headers?: Record<string, string>;\n}\n\n// Streamable HTTP MCP 服务配置\nexport interface StreamableHTTPMCPServerConfig {\n type?: \"streamable-http\"; // 可选,因为默认就是 streamable-http\n url: string;\n headers?: Record<string, 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 usageCount?: number; // 工具使用次数\n lastUsedTime?: string; // 最后使用时间(ISO 8601 格式)\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\n// 工具调用日志配置接口\nexport interface ToolCallLogConfig {\n maxRecords?: number; // 最大记录条数,默认 100\n logFilePath?: string; // 自定义日志文件路径(可选)\n}\n\n// CustomMCP 相关接口定义\n\n// 代理处理器配置\nexport interface ProxyHandlerConfig {\n type: \"proxy\";\n platform: \"coze\" | \"openai\" | \"anthropic\" | \"custom\";\n config: {\n // Coze 平台配置\n workflow_id?: string;\n bot_id?: string;\n api_key?: string;\n base_url?: string;\n // 通用配置\n timeout?: number;\n retry_count?: number;\n retry_delay?: number;\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n };\n}\n\n// HTTP 处理器配置\nexport interface HttpHandlerConfig {\n type: \"http\";\n url: string;\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n timeout?: number;\n retry_count?: number;\n retry_delay?: number;\n auth?: {\n type: \"bearer\" | \"basic\" | \"api_key\";\n token?: string;\n username?: string;\n password?: string;\n api_key?: string;\n api_key_header?: string;\n };\n body_template?: string; // 支持模板变量替换\n response_mapping?: {\n success_path?: string; // JSONPath 表达式\n error_path?: string;\n data_path?: string;\n };\n}\n\n// 函数处理器配置\nexport interface FunctionHandlerConfig {\n type: \"function\";\n module: string; // 模块路径\n function: string; // 函数名\n timeout?: number;\n context?: Record<string, unknown>; // 函数执行上下文\n}\n\n// 脚本处理器配置\nexport interface ScriptHandlerConfig {\n type: \"script\";\n script: string; // 脚本内容或文件路径\n interpreter?: \"node\" | \"python\" | \"bash\";\n timeout?: number;\n env?: Record<string, string>; // 环境变量\n}\n\n// 链式处理器配置\nexport interface ChainHandlerConfig {\n type: \"chain\";\n tools: string[]; // 要链式调用的工具名称\n mode: \"sequential\" | \"parallel\"; // 执行模式\n error_handling: \"stop\" | \"continue\" | \"retry\"; // 错误处理策略\n}\n\n// MCP 处理器配置(用于同步的工具)\nexport interface MCPHandlerConfig {\n type: \"mcp\";\n config: {\n serviceName: string;\n toolName: string;\n };\n}\n\nexport type HandlerConfig =\n | ProxyHandlerConfig\n | HttpHandlerConfig\n | FunctionHandlerConfig\n | ScriptHandlerConfig\n | ChainHandlerConfig\n | MCPHandlerConfig;\n\n// 扩展核心库的 CustomMCPTool 接口,添加使用统计信息\nexport interface CustomMCPTool extends CoreCustomMCPTool {\n // 确保必填字段\n description: string;\n handler: HandlerConfig;\n\n // 使用统计信息(可选)\n stats?: {\n usageCount?: number; // 工具使用次数\n lastUsedTime?: string; // 最后使用时间(ISO 8601格式)\n };\n}\n\nexport interface CustomMCPConfig {\n tools: CustomMCPTool[];\n}\n\n// Web 服务器实例接口(用于配置更新通知)\nexport interface WebServerInstance {\n broadcastConfigUpdate(config: AppConfig): void;\n}\n\nexport interface PlatformsConfig {\n [platformName: string]: PlatformConfig;\n}\n\nexport interface PlatformConfig {\n token?: string;\n}\n\n/**\n * 扣子平台配置接口\n */\nexport interface CozePlatformConfig extends PlatformConfig {\n /** 扣子 API Token */\n token: string;\n}\n\nexport interface AppConfig {\n mcpEndpoint: string | string[];\n mcpServers: Record<string, MCPServerConfig>;\n mcpServerConfig?: Record<string, MCPServerToolsConfig>;\n customMCP?: CustomMCPConfig; // 新增 customMCP 配置支持\n connection?: ConnectionConfig; // 连接配置(可选,用于向后兼容)\n modelscope?: ModelScopeConfig; // ModelScope 配置(可选)\n webUI?: WebUIConfig; // Web UI 配置(可选)\n platforms?: PlatformsConfig; // 平台配置(可选)\n toolCallLog?: ToolCallLogConfig; // 工具调用日志配置(可选)\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 private currentConfigPath: string | null = null; // 跟踪当前使用的配置文件路径\n private json5Writer: {\n write(data: unknown): void;\n toSource(): string;\n } | null = null; // json5-writer 实例,用于保留 JSON5 注释\n private eventBus = getEventBus(); // 事件总线\n\n // 统计更新并发控制\n private statsUpdateLocks: Map<string, Promise<void>> = new Map();\n private statsUpdateLockTimeouts: Map<string, NodeJS.Timeout> = new Map();\n private readonly STATS_UPDATE_TIMEOUT = 5000; // 5秒超时\n\n private constructor() {\n // 使用模板目录中的默认配置文件\n // 在不同环境中尝试不同的路径\n const possiblePaths = [\n // 构建后的环境:dist/configManager.js -> dist/templates/default/xiaozhi.config.json\n resolve(__dirname, \"templates\", \"default\", \"xiaozhi.config.json\"),\n // 开发环境:src/configManager.ts -> templates/default/xiaozhi.config.json\n resolve(__dirname, \"..\", \"templates\", \"default\", \"xiaozhi.config.json\"),\n // 测试环境或其他情况\n resolve(process.cwd(), \"templates\", \"default\", \"xiaozhi.config.json\"),\n ];\n\n // 找到第一个存在的路径\n this.defaultConfigPath =\n possiblePaths.find((path) => existsSync(path)) || possiblePaths[0];\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(`默认配置模板文件不存在: ${this.defaultConfigPath}`);\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 this.json5Writer = null; // 重置 json5Writer 实例\n }\n\n /**\n * 加载配置文件\n */\n private loadConfig(): AppConfig {\n if (!this.configExists()) {\n const error = new Error(\n \"配置文件不存在,请先运行 xiaozhi init 初始化配置\"\n );\n this.eventBus.emitEvent(\"config:error\", {\n error,\n operation: \"loadConfig\",\n });\n throw error;\n }\n\n try {\n const configPath = this.getConfigFilePath();\n this.currentConfigPath = configPath; // 记录当前使用的配置文件路径\n const configFileFormat = this.getConfigFileFormat(configPath);\n const rawConfigData = readFileSync(configPath, \"utf8\");\n\n // 移除可能存在的UTF-8 BOM字符(\\uFEFF)\n // BOM字符在某些编辑器中不可见,但会导致JSON解析失败\n // 这个过滤确保即使文件包含BOM字符也能正常解析\n const configData = rawConfigData.replace(/^\\uFEFF/, \"\");\n\n let config: AppConfig;\n\n // 根据文件格式使用相应的解析器\n switch (configFileFormat) {\n case \"json5\":\n // 使用 JSON5 解析配置对象,同时使用适配器保留注释信息\n config = parseJson5(configData) as AppConfig;\n // 创建适配器实例用于后续保存时保留注释\n this.json5Writer = createJson5Writer(configData);\n break;\n case \"jsonc\":\n // 使用 comment-json 解析 JSONC 格式,保留注释信息\n config = commentJson.parse(configData) as unknown 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 // 发射配置错误事件\n this.eventBus.emitEvent(\"config:error\", {\n error: error instanceof Error ? error : new Error(String(error)),\n operation: \"loadConfig\",\n });\n if (error instanceof SyntaxError) {\n throw new Error(`配置文件格式错误: ${error.message}`);\n }\n throw error;\n }\n }\n\n /**\n * 验证配置文件结构\n */\n public 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 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 // 使用 TypeFieldNormalizer 标准化 type 字段\n const normalizedConfig =\n TypeFieldNormalizer.normalizeTypeField(serverConfig);\n\n // 使用统一的验证逻辑验证标准化后的配置\n const validation = validateMcpServerConfig(serverName, normalizedConfig);\n if (!validation.valid) {\n throw new Error(`配置文件格式错误:${validation.error}`);\n }\n }\n }\n\n /**\n * 获取配置(只读)\n */\n public getConfig(): Readonly<AppConfig> {\n this.config = this.loadConfig();\n\n console.log(\"获取配置成功\");\n // 返回深度只读副本\n return JSON.parse(JSON.stringify(this.config));\n }\n\n /**\n * 获取可修改的配置对象(内部使用,保留注释信息)\n */\n private getMutableConfig(): AppConfig {\n if (!this.config) {\n this.config = this.loadConfig();\n }\n return 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 for (const ep of endpoint) {\n if (!ep || typeof ep !== \"string\") {\n throw new Error(\"MCP 端点数组中的每个元素必须是非空字符串\");\n }\n }\n }\n\n const config = this.getMutableConfig();\n config.mcpEndpoint = endpoint;\n this.saveConfig(config);\n\n // 发射配置更新事件\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"endpoint\",\n timestamp: new Date(),\n });\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.getMutableConfig();\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 config.mcpEndpoint = newEndpoints;\n this.saveConfig(config);\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.getMutableConfig();\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 const newEndpoints = currentEndpoints.filter((ep) => ep !== endpoint);\n config.mcpEndpoint = newEndpoints;\n this.saveConfig(config);\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 const validation = validateMcpServerConfig(serverName, serverConfig);\n if (!validation.valid) {\n throw new Error(validation.error || \"服务配置验证失败\");\n }\n const config = this.getMutableConfig();\n // 直接修改配置对象以保留注释信息\n config.mcpServers[serverName] = serverConfig;\n this.saveConfig(config);\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.getMutableConfig();\n\n // 检查服务是否存在\n if (!config.mcpServers[serverName]) {\n throw new Error(`服务 ${serverName} 不存在`);\n }\n\n // 1. 清理 mcpServers 字段(现有逻辑)\n delete config.mcpServers[serverName];\n\n // 2. 清理 mcpServerConfig 字段(复用现有方法)\n if (config.mcpServerConfig?.[serverName]) {\n delete config.mcpServerConfig[serverName];\n }\n\n // 3. 清理 customMCP 字段中相关的工具定义\n if (config.customMCP?.tools) {\n // 查找与该服务相关的 CustomMCP 工具\n const relatedTools = config.customMCP.tools.filter(\n (tool) =>\n tool.handler?.type === \"mcp\" &&\n tool.handler.config?.serviceName === serverName\n );\n\n // 移除相关工具\n for (const tool of relatedTools) {\n const toolIndex = config.customMCP.tools.findIndex(\n (t) => t.name === tool.name\n );\n if (toolIndex !== -1) {\n config.customMCP.tools.splice(toolIndex, 1);\n }\n }\n\n // 如果没有工具了,可以清理整个 customMCP 对象\n if (config.customMCP.tools.length === 0) {\n config.customMCP = undefined;\n }\n }\n\n // 4. 保存配置(单次原子性操作)\n this.saveConfig(config);\n\n // 5. 发射配置更新事件,通知 CustomMCPHandler 重新初始化\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"customMCP\",\n timestamp: new Date(),\n });\n\n // 记录清理结果\n console.log(\"成功移除 MCP 服务\", { serverName });\n }\n\n /**\n * 批量更新配置(由 Handler 调用)\n */\n public updateConfig(newConfig: Partial<AppConfig>): void {\n const config = this.getMutableConfig();\n\n // 更新 MCP 端点\n if (newConfig.mcpEndpoint !== undefined) {\n config.mcpEndpoint = newConfig.mcpEndpoint;\n }\n\n // 更新 MCP 服务\n if (newConfig.mcpServers) {\n const currentServers = { ...config.mcpServers };\n for (const [name, serverConfig] of Object.entries(newConfig.mcpServers)) {\n config.mcpServers[name] = serverConfig;\n }\n // 删除不存在的服务\n for (const name of Object.keys(currentServers)) {\n if (!(name in newConfig.mcpServers)) {\n delete config.mcpServers[name];\n // 同时清理工具配置\n if (config.mcpServerConfig?.[name]) {\n delete config.mcpServerConfig[name];\n }\n }\n }\n }\n\n // 更新连接配置\n if (newConfig.connection) {\n if (!config.connection) {\n config.connection = {};\n }\n Object.assign(config.connection, newConfig.connection);\n }\n\n // 更新 ModelScope 配置\n if (newConfig.modelscope) {\n if (!config.modelscope) {\n config.modelscope = {};\n }\n Object.assign(config.modelscope, newConfig.modelscope);\n }\n\n // 更新 Web UI 配置\n if (newConfig.webUI) {\n if (!config.webUI) {\n config.webUI = {};\n }\n Object.assign(config.webUI, newConfig.webUI);\n }\n\n // 更新服务工具配置\n if (newConfig.mcpServerConfig) {\n for (const [serverName, toolsConfig] of Object.entries(\n newConfig.mcpServerConfig\n )) {\n if (config.mcpServerConfig?.[serverName]) {\n config.mcpServerConfig[serverName] = toolsConfig;\n }\n }\n }\n\n // 更新平台配置\n if (newConfig.platforms) {\n for (const [platformName, platformConfig] of Object.entries(\n newConfig.platforms\n )) {\n if (!config.platforms) {\n config.platforms = {};\n }\n config.platforms[platformName] = platformConfig;\n }\n }\n\n this.saveConfig(config);\n\n // 发射配置更新事件\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"config\",\n timestamp: new Date(),\n });\n }\n\n /**\n * 更新服务工具配置\n */\n public updateServerToolsConfig(\n serverName: string,\n toolsConfig: Record<string, MCPToolConfig>\n ): void {\n const config = this.getMutableConfig();\n\n // 确保 mcpServerConfig 存在\n if (!config.mcpServerConfig) {\n config.mcpServerConfig = {};\n }\n\n // 如果 toolsConfig 为空对象,则删除该服务的配置\n if (Object.keys(toolsConfig).length === 0) {\n delete config.mcpServerConfig[serverName];\n } else {\n // 更新指定服务的工具配置\n config.mcpServerConfig[serverName] = {\n tools: toolsConfig,\n };\n }\n\n this.saveConfig(config);\n\n // 发射配置更新事件\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"serverTools\",\n serviceName: serverName,\n timestamp: new Date(),\n });\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 * 删除在 mcpServerConfig 中存在但在 mcpServers 中不存在的服务配置\n */\n public cleanupInvalidServerToolsConfig(): void {\n const config = this.getMutableConfig();\n\n // 如果没有 mcpServerConfig,无需清理\n if (!config.mcpServerConfig) {\n return;\n }\n\n const validServerNames = Object.keys(config.mcpServers);\n const configuredServerNames = Object.keys(config.mcpServerConfig);\n\n // 找出需要清理的服务名称\n const invalidServerNames = configuredServerNames.filter(\n (serverName) => !validServerNames.includes(serverName)\n );\n\n if (invalidServerNames.length > 0) {\n // 删除无效的服务配置\n for (const serverName of invalidServerNames) {\n delete config.mcpServerConfig[serverName];\n }\n\n this.saveConfig(config);\n\n console.log(\"已清理无效的服务工具配置\", {\n count: invalidServerNames.length,\n serverNames: invalidServerNames,\n });\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.getMutableConfig();\n\n // 确保 mcpServerConfig 存在\n if (!config.mcpServerConfig) {\n config.mcpServerConfig = {};\n }\n\n // 确保服务配置存在\n if (!config.mcpServerConfig[serverName]) {\n config.mcpServerConfig[serverName] = { tools: {} };\n }\n\n // 更新工具配置\n config.mcpServerConfig[serverName].tools[toolName] = {\n ...config.mcpServerConfig[serverName].tools[toolName],\n enable: enabled,\n ...(description && { description }),\n };\n\n this.saveConfig(config);\n }\n\n /**\n * 保存配置到文件\n * 保存到原始配置文件路径,保持文件格式一致性\n */\n private saveConfig(config: AppConfig): void {\n try {\n // 验证配置\n this.validateConfig(config);\n\n // 确定保存路径 - 优先使用当前配置文件路径,否则使用默认路径\n let configPath: string;\n if (this.currentConfigPath) {\n configPath = this.currentConfigPath;\n } else {\n // 如果没有当前路径,使用 getConfigFilePath 获取\n configPath = this.getConfigFilePath();\n this.currentConfigPath = configPath;\n }\n\n // 根据文件格式选择序列化方法\n const configFileFormat = this.getConfigFileFormat(configPath);\n let configContent: string;\n\n switch (configFileFormat) {\n case \"json5\":\n // 对于 JSON5 格式,使用适配器保留注释\n try {\n if (this.json5Writer) {\n // 使用适配器更新配置并保留注释\n this.json5Writer.write(config);\n configContent = this.json5Writer.toSource();\n } else {\n // 如果没有适配器实例,回退到 comment-json 序列化\n console.warn(\"没有 JSON5 适配器实例,使用 comment-json 序列化\");\n configContent = commentJson.stringify(config, null, 2);\n }\n } catch (json5Error) {\n // 如果适配器序列化失败,回退到 comment-json 序列化\n console.warn(\n \"使用 JSON5 适配器保存失败,回退到 comment-json 序列化:\",\n json5Error\n );\n configContent = commentJson.stringify(config, null, 2);\n }\n break;\n case \"jsonc\":\n // 对于 JSONC 格式,使用 comment-json 库保留注释\n try {\n // 直接使用 comment-json 的 stringify 方法\n // 如果 config 是通过 comment-json.parse 解析的,注释信息会被保留\n configContent = commentJson.stringify(config, null, 2);\n } catch (commentJsonError) {\n // 如果 comment-json 序列化失败,回退到标准 JSON\n console.warn(\n \"使用 comment-json 保存失败,回退到标准 JSON 格式:\",\n commentJsonError\n );\n configContent = JSON.stringify(config, null, 2);\n }\n break;\n default:\n configContent = JSON.stringify(config, null, 2);\n break;\n }\n\n // 保存到文件\n writeFileSync(configPath, configContent, \"utf8\");\n\n // 更新缓存\n this.config = config;\n\n console.log(\"配置保存成功\");\n\n // 通知 Web 界面配置已更新(如果 Web 服务器正在运行)\n this.notifyConfigUpdate(config);\n } catch (error) {\n // 发射配置错误事件\n this.eventBus.emitEvent(\"config:error\", {\n error: error instanceof Error ? error : new Error(String(error)),\n operation: \"saveConfig\",\n });\n throw new Error(\n `保存配置失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * 重新加载配置(清除缓存)\n */\n public reloadConfig(): void {\n this.config = null;\n this.currentConfigPath = null; // 清除配置文件路径缓存\n this.json5Writer = null; // 清除 json5Writer 实例\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.getMutableConfig();\n\n // 确保 connection 对象存在\n if (!config.connection) {\n config.connection = {};\n }\n\n // 直接修改现有的 connection 对象以保留注释\n Object.assign(config.connection, connectionConfig);\n this.saveConfig(config);\n\n // 发射配置更新事件\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"connection\",\n timestamp: new Date(),\n });\n }\n\n /**\n * 更新工具使用统计信息(MCP 服务工具)\n * @param serverName 服务名称\n * @param toolName 工具名称\n * @param callTime 调用时间(ISO 8601 格式)\n */\n public async updateToolUsageStats(\n serverName: string,\n toolName: string,\n callTime: string\n ): Promise<void>;\n\n /**\n * 更新工具使用统计信息(CustomMCP 工具)\n * @param toolName 工具名称(customMCP 工具名称)\n * @param incrementUsageCount 是否增加使用计数,默认为 true\n */\n public async updateToolUsageStats(\n toolName: string,\n incrementUsageCount?: boolean\n ): Promise<void>;\n\n /**\n * 更新工具使用统计信息的实现\n */\n public async updateToolUsageStats(\n arg1: string,\n arg2: string | boolean | undefined,\n arg3?: string\n ): Promise<void> {\n try {\n // 判断参数类型来区分不同的重载\n if (typeof arg2 === \"string\" && arg3) {\n // 三个参数的情况:updateToolUsageStats(serverName, toolName, callTime)\n const serverName = arg1;\n const toolName = arg2;\n const callTime = arg3;\n\n // 双写机制:同时更新 mcpServerConfig 和 customMCP 中的统计信息\n await Promise.all([\n this._updateMCPServerToolStats(serverName, toolName, callTime),\n this.updateCustomMCPToolStats(serverName, toolName, callTime),\n ]);\n\n console.log(\"工具使用统计已更新\", { serverName, toolName });\n } else {\n // 两个参数的情况:updateToolUsageStats(toolName, incrementUsageCount)\n const toolName = arg1;\n const incrementUsageCount = arg2 as boolean;\n const callTime = new Date().toISOString();\n\n // 只更新 customMCP 中的统计信息\n await this.updateCustomMCPToolStats(\n toolName,\n callTime,\n incrementUsageCount\n );\n\n console.log(\"CustomMCP 工具使用统计已更新\", { toolName });\n }\n } catch (error) {\n // 错误不应该影响主要的工具调用流程\n if (typeof arg2 === \"string\" && arg3) {\n const serverName = arg1;\n const toolName = arg2;\n console.error(\"更新工具使用统计失败\", { serverName, toolName, error });\n } else {\n const toolName = arg1;\n console.error(\"更新 CustomMCP 工具使用统计失败\", { toolName, error });\n }\n }\n }\n\n /**\n * 更新 MCP 服务工具统计信息(重载方法)\n * @param serviceName 服务名称\n * @param toolName 工具名称\n * @param callTime 调用时间(ISO 8601 格式)\n * @param incrementUsageCount 是否增加使用计数,默认为 true\n */\n public async updateMCPServerToolStats(\n serviceName: string,\n toolName: string,\n callTime: string,\n incrementUsageCount = true\n ): Promise<void> {\n await this._updateMCPServerToolStats(\n serviceName,\n toolName,\n callTime,\n incrementUsageCount\n );\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.getMutableConfig();\n\n // 确保 modelscope 对象存在\n if (!config.modelscope) {\n config.modelscope = {};\n }\n\n // 直接修改现有的 modelscope 对象以保留注释\n Object.assign(config.modelscope, modelScopeConfig);\n this.saveConfig(config);\n\n // 发射配置更新事件\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"modelscope\",\n timestamp: new Date(),\n });\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 * 获取 customMCP 配置\n */\n public getCustomMCPConfig(): CustomMCPConfig | null {\n const config = this.getConfig();\n return config.customMCP || null;\n }\n\n /**\n * 获取 customMCP 工具列表\n */\n public getCustomMCPTools(): CustomMCPTool[] {\n const customMCPConfig = this.getCustomMCPConfig();\n if (!customMCPConfig || !customMCPConfig.tools) {\n return [];\n }\n\n return customMCPConfig.tools;\n }\n\n /**\n * 验证 customMCP 工具配置\n */\n public validateCustomMCPTools(tools: CustomMCPTool[]): boolean {\n if (!Array.isArray(tools)) {\n return false;\n }\n\n for (const tool of tools) {\n // 检查必需字段\n if (!tool.name || typeof tool.name !== \"string\") {\n console.warn(\"CustomMCP 工具缺少有效的 name 字段\", { tool });\n return false;\n }\n\n if (!tool.description || typeof tool.description !== \"string\") {\n console.warn(\"CustomMCP 工具缺少有效的 description 字段\", {\n toolName: tool.name,\n });\n return false;\n }\n\n if (!tool.inputSchema || typeof tool.inputSchema !== \"object\") {\n console.warn(\"CustomMCP 工具缺少有效的 inputSchema 字段\", {\n toolName: tool.name,\n });\n return false;\n }\n\n if (!tool.handler || typeof tool.handler !== \"object\") {\n console.warn(\"CustomMCP 工具缺少有效的 handler 字段\", {\n toolName: tool.name,\n });\n return false;\n }\n\n // 检查 handler 类型\n if (\n ![\"proxy\", \"function\", \"http\", \"script\", \"chain\", \"mcp\"].includes(\n tool.handler.type\n )\n ) {\n console.warn(\"CustomMCP 工具的 handler.type 类型无效\", {\n toolName: tool.name,\n type: tool.handler.type,\n });\n return false;\n }\n\n // 根据处理器类型进行特定验证\n if (!this.validateHandlerConfig(tool.name, tool.handler)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * 验证处理器配置\n */\n private validateHandlerConfig(\n toolName: string,\n handler: HandlerConfig\n ): boolean {\n switch (handler.type) {\n case \"proxy\":\n return this.validateProxyHandler(toolName, handler);\n case \"http\":\n return this.validateHttpHandler(toolName, handler);\n case \"function\":\n return this.validateFunctionHandler(toolName, handler);\n case \"script\":\n return this.validateScriptHandler(toolName, handler);\n case \"chain\":\n return this.validateChainHandler(toolName, handler);\n case \"mcp\":\n return this.validateMCPHandler(toolName, handler);\n default:\n console.warn(\"CustomMCP 工具使用了未知的处理器类型\", {\n toolName,\n handlerType: (handler as HandlerConfig).type,\n });\n return false;\n }\n }\n\n /**\n * 验证代理处理器配置\n */\n private validateProxyHandler(\n toolName: string,\n handler: ProxyHandlerConfig\n ): boolean {\n if (!handler.platform) {\n console.warn(\"CustomMCP 工具的 proxy 处理器缺少 platform 字段\", {\n toolName,\n });\n return false;\n }\n\n if (![\"coze\", \"openai\", \"anthropic\", \"custom\"].includes(handler.platform)) {\n console.warn(\"CustomMCP 工具的 proxy 处理器使用了不支持的平台\", {\n toolName,\n platform: handler.platform,\n });\n return false;\n }\n\n if (!handler.config || typeof handler.config !== \"object\") {\n console.warn(\"CustomMCP 工具的 proxy 处理器缺少 config 字段\", {\n toolName,\n });\n return false;\n }\n\n // Coze 平台特定验证\n if (handler.platform === \"coze\") {\n if (!handler.config.workflow_id && !handler.config.bot_id) {\n console.warn(\n \"CustomMCP 工具的 Coze 处理器必须提供 workflow_id 或 bot_id\",\n { toolName }\n );\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * 验证 HTTP 处理器配置\n */\n private validateHttpHandler(\n toolName: string,\n handler: HttpHandlerConfig\n ): boolean {\n if (!handler.url || typeof handler.url !== \"string\") {\n console.warn(\"CustomMCP 工具的 http 处理器缺少有效的 url 字段\", {\n toolName,\n });\n return false;\n }\n\n try {\n new URL(handler.url);\n } catch {\n console.warn(\"CustomMCP 工具的 http 处理器 url 格式无效\", {\n toolName,\n url: handler.url,\n });\n return false;\n }\n\n if (\n handler.method &&\n ![\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"].includes(handler.method)\n ) {\n console.warn(\"CustomMCP 工具的 http 处理器使用了不支持的 HTTP 方法\", {\n toolName,\n method: handler.method,\n });\n return false;\n }\n\n return true;\n }\n\n /**\n * 验证函数处理器配置\n */\n private validateFunctionHandler(\n toolName: string,\n handler: FunctionHandlerConfig\n ): boolean {\n if (!handler.module || typeof handler.module !== \"string\") {\n console.warn(\"CustomMCP 工具的 function 处理器缺少有效的 module 字段\", {\n toolName,\n });\n return false;\n }\n\n if (!handler.function || typeof handler.function !== \"string\") {\n console.warn(\"CustomMCP 工具的 function 处理器缺少有效的 function 字段\", {\n toolName,\n });\n return false;\n }\n\n return true;\n }\n\n /**\n * 验证脚本处理器配置\n */\n private validateScriptHandler(\n toolName: string,\n handler: ScriptHandlerConfig\n ): boolean {\n if (!handler.script || typeof handler.script !== \"string\") {\n console.warn(\"CustomMCP 工具的 script 处理器缺少有效的 script 字段\", {\n toolName,\n });\n return false;\n }\n\n if (\n handler.interpreter &&\n ![\"node\", \"python\", \"bash\"].includes(handler.interpreter)\n ) {\n console.warn(\"CustomMCP 工具的 script 处理器使用了不支持的解释器\", {\n toolName,\n interpreter: handler.interpreter,\n });\n return false;\n }\n\n return true;\n }\n\n /**\n * 验证链式处理器配置\n */\n private validateChainHandler(\n toolName: string,\n handler: ChainHandlerConfig\n ): boolean {\n if (\n !handler.tools ||\n !Array.isArray(handler.tools) ||\n handler.tools.length === 0\n ) {\n console.warn(\"CustomMCP 工具的 chain 处理器缺少有效的 tools 数组\", {\n toolName,\n });\n return false;\n }\n\n if (![\"sequential\", \"parallel\"].includes(handler.mode)) {\n console.warn(\"CustomMCP 工具的 chain 处理器使用了不支持的执行模式\", {\n toolName,\n mode: handler.mode,\n });\n return false;\n }\n\n if (![\"stop\", \"continue\", \"retry\"].includes(handler.error_handling)) {\n console.warn(\"CustomMCP 工具的 chain 处理器使用了不支持的错误处理策略\", {\n toolName,\n errorHandling: handler.error_handling,\n });\n return false;\n }\n\n return true;\n }\n\n /**\n * 验证 MCP 处理器配置\n */\n private validateMCPHandler(\n toolName: string,\n handler: MCPHandlerConfig\n ): boolean {\n if (!handler.config || typeof handler.config !== \"object\") {\n console.warn(\"CustomMCP 工具的 mcp 处理器缺少 config 字段\", { toolName });\n return false;\n }\n\n if (\n !handler.config.serviceName ||\n typeof handler.config.serviceName !== \"string\"\n ) {\n console.warn(\"CustomMCP 工具的 mcp 处理器缺少有效的 serviceName\", {\n toolName,\n });\n return false;\n }\n\n if (\n !handler.config.toolName ||\n typeof handler.config.toolName !== \"string\"\n ) {\n console.warn(\"CustomMCP 工具的 mcp 处理器缺少有效的 toolName\", {\n toolName,\n });\n return false;\n }\n\n return true;\n }\n\n /**\n * 检查是否配置了有效的 customMCP 工具\n */\n public hasValidCustomMCPTools(): boolean {\n try {\n const tools = this.getCustomMCPTools();\n if (tools.length === 0) {\n return false;\n }\n\n return this.validateCustomMCPTools(tools);\n } catch (error) {\n console.error(\"检查 customMCP 工具配置时出错\", { error });\n return false;\n }\n }\n\n /**\n * 添加自定义 MCP 工具\n */\n public addCustomMCPTool(tool: CustomMCPTool): void {\n if (!tool || typeof tool !== \"object\") {\n throw new Error(\"工具配置不能为空\");\n }\n\n const config = this.getMutableConfig();\n\n // 确保 customMCP 配置存在\n if (!config.customMCP) {\n config.customMCP = { tools: [] };\n }\n\n // 检查工具名称是否已存在\n const existingTool = config.customMCP.tools.find(\n (t) => t.name === tool.name\n );\n if (existingTool) {\n throw new Error(`工具 \"${tool.name}\" 已存在`);\n }\n\n // 验证工具配置\n if (!this.validateCustomMCPTools([tool])) {\n throw new Error(\"工具配置验证失败\");\n }\n\n // 添加工具\n config.customMCP.tools.unshift(tool);\n this.saveConfig(config);\n\n console.log(\"成功添加自定义 MCP 工具\", { toolName: tool.name });\n }\n\n /**\n * 批量添加自定义 MCP 工具\n * @param tools 要添加的工具数组\n */\n public async addCustomMCPTools(tools: CustomMCPTool[]): Promise<void> {\n if (!Array.isArray(tools)) {\n throw new Error(\"工具配置必须是数组\");\n }\n\n if (tools.length === 0) {\n return; // 空数组,无需处理\n }\n\n const config = this.getMutableConfig();\n\n // 确保 customMCP 配置存在\n if (!config.customMCP) {\n config.customMCP = { tools: [] };\n }\n\n // 添加新工具,避免重复\n const existingNames = new Set(\n config.customMCP.tools.map((tool) => tool.name)\n );\n const newTools = tools.filter((tool) => !existingNames.has(tool.name));\n\n if (newTools.length > 0) {\n // 验证新工具配置\n if (!this.validateCustomMCPTools(newTools)) {\n throw new Error(\"工具配置验证失败\");\n }\n\n // 添加工具\n config.customMCP.tools.push(...newTools);\n this.saveConfig(config);\n\n // 发射配置更新事件\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"customMCP\",\n timestamp: new Date(),\n });\n\n console.log(\"成功批量添加自定义 MCP 工具\", {\n count: newTools.length,\n toolNames: newTools.map((t) => t.name),\n });\n }\n }\n\n /**\n * 删除自定义 MCP 工具\n */\n public removeCustomMCPTool(toolName: string): void {\n if (!toolName || typeof toolName !== \"string\") {\n throw new Error(\"工具名称不能为空\");\n }\n\n const config = this.getMutableConfig();\n\n if (!config.customMCP || !config.customMCP.tools) {\n throw new Error(\"未配置自定义 MCP 工具\");\n }\n\n const toolIndex = config.customMCP.tools.findIndex(\n (t) => t.name === toolName\n );\n if (toolIndex === -1) {\n throw new Error(`工具 \"${toolName}\" 不存在`);\n }\n\n // 删除工具\n config.customMCP.tools.splice(toolIndex, 1);\n this.saveConfig(config);\n\n console.log(\"成功删除自定义 MCP 工具\", { toolName });\n }\n\n /**\n * 更新单个自定义 MCP 工具配置\n * @param toolName 工具名称\n * @param updatedTool 更新后的工具配置\n */\n public updateCustomMCPTool(\n toolName: string,\n updatedTool: CustomMCPTool\n ): void {\n if (!toolName || typeof toolName !== \"string\") {\n throw new Error(\"工具名称不能为空\");\n }\n if (!updatedTool || typeof updatedTool !== \"object\") {\n throw new Error(\"更新后的工具配置不能为空\");\n }\n\n const config = this.getMutableConfig();\n\n if (!config.customMCP || !config.customMCP.tools) {\n throw new Error(\"未配置自定义 MCP 工具\");\n }\n\n const toolIndex = config.customMCP.tools.findIndex(\n (t) => t.name === toolName\n );\n if (toolIndex === -1) {\n throw new Error(`工具 \"${toolName}\" 不存在`);\n }\n\n // 验证更新后的工具配置\n if (!this.validateCustomMCPTools([updatedTool])) {\n throw new Error(\"更新后的工具配置验证失败\");\n }\n\n // 更新工具配置\n config.customMCP.tools[toolIndex] = updatedTool;\n this.saveConfig(config);\n\n console.log(\"成功更新自定义 MCP 工具\", { toolName });\n }\n\n /**\n * 更新自定义 MCP 工具配置\n */\n public updateCustomMCPTools(tools: CustomMCPTool[]): void {\n if (!Array.isArray(tools)) {\n throw new Error(\"工具配置必须是数组\");\n }\n\n // 验证工具配置\n if (!this.validateCustomMCPTools(tools)) {\n throw new Error(\"工具配置验证失败\");\n }\n\n const config = this.getMutableConfig();\n\n // 确保 customMCP 配置存在\n if (!config.customMCP) {\n config.customMCP = { tools: [] };\n }\n\n config.customMCP.tools = tools;\n this.saveConfig(config);\n\n // 发射配置更新事件\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"customMCP\",\n timestamp: new Date(),\n });\n\n console.log(\"成功更新自定义 MCP 工具配置\", { count: tools.length });\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 界面配置已更新\n * 如果 Web 服务器正在运行,通过 WebSocket 广播配置更新\n */\n private notifyConfigUpdate(config: AppConfig): void {\n try {\n // 检查是否有全局的 webServer 实例(当使用 --ui 参数启动时会设置)\n const webServer = (\n global as typeof global & { __webServer?: WebServerInstance }\n ).__webServer;\n if (webServer && typeof webServer.broadcastConfigUpdate === \"function\") {\n // 调用 webServer 的 broadcastConfigUpdate 方法来通知所有连接的客户端\n webServer.broadcastConfigUpdate(config);\n console.log(\"已通过 WebSocket 广播配置更新\");\n }\n } catch (error) {\n // 静默处理错误,不影响配置保存的主要功能\n console.warn(\n \"通知 Web 界面配置更新失败:\",\n error instanceof Error ? error.message : String(error)\n );\n }\n }\n\n /**\n * 更新 Web UI 配置\n */\n public updateWebUIConfig(webUIConfig: Partial<WebUIConfig>): void {\n const config = this.getMutableConfig();\n\n // 确保 webUI 对象存在\n if (!config.webUI) {\n config.webUI = {};\n }\n\n // 直接修改现有的 webUI 对象以保留注释\n Object.assign(config.webUI, webUIConfig);\n this.saveConfig(config);\n\n // 发射配置更新事件\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"webui\",\n timestamp: new Date(),\n });\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 public updatePlatformConfig(\n platformName: string,\n platformConfig: PlatformConfig\n ): void {\n const config = this.getMutableConfig();\n if (!config.platforms) {\n config.platforms = {};\n }\n config.platforms[platformName] = platformConfig;\n // 注意:Web UI 可能需要刷新才能看到更新后的数据\n this.saveConfig(config);\n\n // 发射配置更新事件\n this.eventBus.emitEvent(\"config:updated\", {\n type: \"platform\",\n platformName,\n timestamp: new Date(),\n });\n }\n\n /**\n * 获取扣子平台配置\n */\n public getCozePlatformConfig(): CozePlatformConfig | null {\n const config = this.getConfig();\n const cozeConfig = config.platforms?.coze;\n\n if (!cozeConfig || !cozeConfig.token) {\n return null;\n }\n\n return {\n token: cozeConfig.token,\n };\n }\n\n /**\n * 获取扣子 API Token\n */\n public getCozeToken(): string | null {\n const cozeConfig = this.getCozePlatformConfig();\n return cozeConfig?.token || null;\n }\n\n /**\n * 设置扣子平台配置\n */\n public setCozePlatformConfig(config: CozePlatformConfig): void {\n if (\n !config.token ||\n typeof config.token !== \"string\" ||\n config.token.trim() === \"\"\n ) {\n throw new Error(\"扣子 API Token 不能为空\");\n }\n\n this.updatePlatformConfig(\"coze\", {\n token: config.token.trim(),\n });\n }\n\n /**\n * 检查扣子平台配置是否有效\n */\n public isCozeConfigValid(): boolean {\n const cozeConfig = this.getCozePlatformConfig();\n return (\n cozeConfig !== null &&\n typeof cozeConfig.token === \"string\" &&\n cozeConfig.token.trim() !== \"\"\n );\n }\n\n /**\n * 更新 mcpServerConfig 中的工具使用统计信息(内部实现)\n * @param serverName 服务名称\n * @param toolName 工具名称\n * @param callTime 调用时间(ISO 8601 格式)\n * @param incrementUsageCount 是否增加使用计数\n * @private\n */\n private async _updateMCPServerToolStats(\n serverName: string,\n toolName: string,\n callTime: string,\n incrementUsageCount = true\n ): Promise<void> {\n const config = this.getMutableConfig();\n\n // 确保 mcpServerConfig 存在\n if (!config.mcpServerConfig) {\n config.mcpServerConfig = {};\n }\n\n // 确保服务配置存在\n if (!config.mcpServerConfig[serverName]) {\n config.mcpServerConfig[serverName] = { tools: {} };\n }\n\n // 确保工具配置存在\n if (!config.mcpServerConfig[serverName].tools[toolName]) {\n config.mcpServerConfig[serverName].tools[toolName] = {\n enable: true, // 默认启用\n };\n }\n\n const toolConfig = config.mcpServerConfig[serverName].tools[toolName];\n const currentUsageCount = toolConfig.usageCount || 0;\n const currentLastUsedTime = toolConfig.lastUsedTime;\n\n // 根据参数决定是否更新使用次数\n if (incrementUsageCount) {\n toolConfig.usageCount = currentUsageCount + 1;\n }\n\n // 时间校验:只有新时间晚于现有时间才更新 lastUsedTime\n if (\n !currentLastUsedTime ||\n new Date(callTime) > new Date(currentLastUsedTime)\n ) {\n // 使用 dayjs 格式化时间为更易读的格式\n toolConfig.lastUsedTime = dayjs(callTime).format(\"YYYY-MM-DD HH:mm:ss\");\n }\n\n // 保存配置\n this.saveConfig(config);\n }\n\n /**\n * 更新 customMCP 中的工具使用统计信息(服务名+工具名版本)\n * @param serverName 服务名称\n * @param toolName 工具名称\n * @param callTime 调用时间(ISO 8601 格式)\n * @private\n */\n private async updateCustomMCPToolStats(\n serverName: string,\n toolName: string,\n callTime: string\n ): Promise<void>;\n\n /**\n * 更新 customMCP 中的工具使用统计信息(工具名版本)\n * @param toolName 工具名称(customMCP 工具名称)\n * @param callTime 调用时间(ISO 8601 格式)\n * @param incrementUsageCount 是否增加使用计数,默认为 true\n * @private\n */\n private async updateCustomMCPToolStats(\n toolName: string,\n callTime: string,\n incrementUsageCount?: boolean\n ): Promise<void>;\n\n /**\n * 更新 customMCP 工具使用统计信息的实现\n * @private\n */\n private async updateCustomMCPToolStats(\n arg1: string,\n arg2: string,\n arg3?: string | boolean\n ): Promise<void> {\n try {\n let toolName: string;\n let callTime: string;\n let incrementUsageCount = true;\n let logPrefix: string;\n\n // 判断参数类型来区分不同的重载\n if (typeof arg3 === \"string\") {\n // 三个字符串参数的情况:updateCustomMCPToolStats(serverName, toolName, callTime)\n const serverName = arg1;\n toolName = `${serverName}__${arg2}`;\n callTime = arg3;\n logPrefix = `${serverName}/${arg2}`;\n } else {\n // 两个或三个参数的情况:updateCustomMCPToolStats(toolName, callTime, incrementUsageCount?)\n toolName = arg1;\n callTime = arg2;\n incrementUsageCount = (arg3 as boolean) || true;\n logPrefix = toolName;\n }\n\n const customTools = this.getCustomMCPTools();\n const toolIndex = customTools.findIndex((tool) => tool.name === toolName);\n\n if (toolIndex === -1) {\n // 如果 customMCP 中没有对应的工具,跳过更新\n return;\n }\n\n const updatedTools = [...customTools];\n const tool = updatedTools[toolIndex];\n\n // 确保 stats 对象存在\n if (!tool.stats) {\n tool.stats = {};\n }\n\n const currentUsageCount = tool.stats.usageCount || 0;\n const currentLastUsedTime = tool.stats.lastUsedTime;\n\n // 根据参数决定是否更新使用次数\n if (incrementUsageCount) {\n tool.stats.usageCount = currentUsageCount + 1;\n }\n\n // 时间校验:只有新时间晚于现有时间才更新 lastUsedTime\n if (\n !currentLastUsedTime ||\n new Date(callTime) > new Date(currentLastUsedTime)\n ) {\n tool.stats.lastUsedTime = dayjs(callTime).format(\"YYYY-MM-DD HH:mm:ss\");\n }\n\n // 保存更新后的工具配置\n await this.updateCustomMCPTools(updatedTools);\n } catch (error) {\n // 根据参数类型决定错误日志的前缀\n if (typeof arg3 === \"string\") {\n const serverName = arg1;\n const toolName = arg2;\n console.error(\"更新 customMCP 工具统计信息失败\", {\n serverName,\n toolName,\n error,\n });\n } else {\n const toolName = arg1;\n console.error(\"更新 customMCP 工具统计信息失败\", { toolName, error });\n }\n // customMCP 统计更新失败不应该影响主要流程\n }\n }\n\n /**\n * 获取统计更新锁(确保同一工具的统计更新串行执行)\n * @param toolKey 工具键\n * @private\n */\n private async acquireStatsUpdateLock(toolKey: string): Promise<boolean> {\n if (this.statsUpdateLocks.has(toolKey)) {\n console.log(\"工具统计更新正在进行中,跳过本次更新\", { toolKey });\n return false;\n }\n\n const updatePromise = new Promise<void>((resolve) => {\n // 锁定逻辑在调用者中实现\n });\n\n this.statsUpdateLocks.set(toolKey, updatePromise);\n\n // 设置超时自动释放锁\n const timeout = setTimeout(() => {\n this.releaseStatsUpdateLock(toolKey);\n }, this.STATS_UPDATE_TIMEOUT);\n\n this.statsUpdateLockTimeouts.set(toolKey, timeout);\n\n return true;\n }\n\n /**\n * 释放统计更新锁\n * @param toolKey 工具键\n * @private\n */\n private releaseStatsUpdateLock(toolKey: string): void {\n this.statsUpdateLocks.delete(toolKey);\n\n const timeout = this.statsUpdateLockTimeouts.get(toolKey);\n if (timeout) {\n clearTimeout(timeout);\n this.statsUpdateLockTimeouts.delete(toolKey);\n }\n\n console.log(\"已释放工具的统计更新锁\", { toolKey });\n }\n\n /**\n * 带并发控制的工具统计更新(CustomMCP 工具)\n * @param toolName 工具名称\n * @param incrementUsageCount 是否增加使用计数\n */\n public async updateToolUsageStatsWithLock(\n toolName: string,\n incrementUsageCount = true\n ): Promise<void> {\n const toolKey = `custommcp_${toolName}`;\n\n if (!(await this.acquireStatsUpdateLock(toolKey))) {\n return; // 已有其他更新在进行\n }\n\n try {\n await this.updateToolUsageStats(toolName, incrementUsageCount);\n console.log(\"工具统计更新完成\", { toolName });\n } catch (error) {\n console.error(\"工具统计更新失败\", { toolName, error });\n throw error;\n } finally {\n this.releaseStatsUpdateLock(toolKey);\n }\n }\n\n /**\n * 带并发控制的工具统计更新(MCP 服务工具)\n * @param serviceName 服务名称\n * @param toolName 工具名称\n * @param callTime 调用时间\n * @param incrementUsageCount 是否增加使用计数\n */\n public async updateMCPServerToolStatsWithLock(\n serviceName: string,\n toolName: string,\n callTime: string,\n incrementUsageCount = true\n ): Promise<void> {\n const toolKey = `mcpserver_${serviceName}_${toolName}`;\n\n if (!(await this.acquireStatsUpdateLock(toolKey))) {\n return; // 已有其他更新在进行\n }\n\n try {\n await this.updateMCPServerToolStats(\n serviceName,\n toolName,\n callTime,\n incrementUsageCount\n );\n console.log(\"MCP 服务工具统计更新完成\", { serviceName, toolName });\n } catch (error) {\n console.error(\"MCP 服务工具统计更新失败\", {\n serviceName,\n toolName,\n error,\n });\n throw error;\n } finally {\n this.releaseStatsUpdateLock(toolKey);\n }\n }\n\n /**\n * 清理所有统计更新锁(用于异常恢复)\n */\n public clearAllStatsUpdateLocks(): void {\n const lockCount = this.statsUpdateLocks.size;\n this.statsUpdateLocks.clear();\n\n // 清理所有超时定时器\n for (const timeout of this.statsUpdateLockTimeouts.values()) {\n clearTimeout(timeout);\n }\n this.statsUpdateLockTimeouts.clear();\n\n if (lockCount > 0) {\n console.log(\"已清理统计更新锁\", { count: lockCount });\n }\n }\n\n /**\n * 获取统计更新锁状态(用于调试和监控)\n */\n public getStatsUpdateLocks(): string[] {\n return Array.from(this.statsUpdateLocks.keys());\n }\n\n /**\n * 获取工具调用日志配置\n */\n public getToolCallLogConfig(): Readonly<ToolCallLogConfig> {\n const config = this.getConfig();\n return config.toolCallLog || {};\n }\n\n /**\n * 更新工具调用日志配置\n */\n public updateToolCallLogConfig(\n toolCallLogConfig: Partial<ToolCallLogConfig>\n ): void {\n const config = this.getMutableConfig();\n\n // 确保 toolCallLog 对象存在\n if (!config.toolCallLog) {\n config.toolCallLog = {};\n }\n\n // 直接修改现有的 toolCallLog 对象以保留注释\n Object.assign(config.toolCallLog, toolCallLogConfig);\n this.saveConfig(config);\n }\n\n /**\n * 获取配置目录路径(与配置文件同级目录)\n */\n public getConfigDir(): string {\n // 配置文件路径 - 优先使用环境变量指定的目录,否则使用当前工作目录\n return process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n }\n}\n\n// 导出单例实例\nexport const configManager = ConfigManager.getInstance();\n","import { EventEmitter } from \"node:events\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Logger } from \"@root/Logger.js\";\nimport { logger } from \"@root/Logger.js\";\n\n/**\n * 事件类型定义\n */\nexport interface EventBusEvents {\n // 配置相关事件\n \"config:updated\": {\n type: string;\n serviceName?: string;\n platformName?: string;\n timestamp: Date;\n };\n \"config:error\": { error: Error; operation: string };\n\n // 状态相关事件\n \"status:updated\": { status: any; source: string };\n \"status:error\": { error: Error; operation: string };\n\n // 接入点状态变更事件\n \"endpoint:status:changed\": {\n endpoint: string;\n connected: boolean;\n operation: \"connect\" | \"disconnect\" | \"reconnect\" | \"add\" | \"remove\";\n success: boolean;\n message?: string;\n timestamp: number;\n source: string;\n };\n\n // 服务相关事件\n \"service:restart:requested\": {\n serviceName: string;\n reason?: string;\n delay: number;\n attempt: number;\n timestamp: number;\n source?: string;\n };\n \"service:restart:started\": {\n serviceName: string;\n reason?: string;\n attempt: number;\n timestamp: number;\n };\n \"service:restart:completed\": {\n serviceName: string;\n reason?: string;\n attempt: number;\n timestamp: number;\n };\n \"service:restart:failed\": {\n serviceName: string;\n error: Error;\n attempt: number;\n timestamp: number;\n };\n \"service:restart:execute\": {\n serviceName: string;\n reason?: string;\n attempt: number;\n timestamp: number;\n };\n \"service:health:changed\": {\n serviceName: string;\n oldStatus: string;\n newStatus: string;\n timestamp: number;\n };\n\n // WebSocket 相关事件\n \"websocket:client:connected\": { clientId: string; timestamp: number };\n \"websocket:client:disconnected\": { clientId: string; timestamp: number };\n \"websocket:message:received\": { type: string; data: any; clientId: string };\n\n // 通知相关事件\n \"notification:broadcast\": { type: string; data: any; target?: string };\n \"notification:error\": { error: Error; type: string };\n\n // MCP服务相关事件\n \"mcp:service:connected\": {\n serviceName: string;\n tools: Tool[];\n connectionTime: Date;\n };\n \"mcp:service:disconnected\": {\n serviceName: string;\n reason?: string;\n disconnectionTime: Date;\n };\n \"mcp:service:connection:failed\": {\n serviceName: string;\n error: Error;\n attempt: number;\n };\n \"mcp:server:added\": {\n serverName: string;\n config: any;\n tools: string[];\n timestamp: Date;\n };\n \"mcp:server:removed\": {\n serverName: string;\n affectedTools: string[];\n timestamp: Date;\n };\n \"mcp:server:status_changed\": {\n serverName: string;\n oldStatus: \"connected\" | \"disconnected\" | \"connecting\" | \"error\";\n newStatus: \"connected\" | \"disconnected\" | \"connecting\" | \"error\";\n timestamp: Date;\n reason?: string;\n };\n \"mcp:server:connection:attempt\": {\n serverName: string;\n attempt: number;\n maxAttempts: number;\n timestamp: Date;\n };\n \"mcp:server:tools:updated\": {\n serverName: string;\n tools: string[];\n addedTools: string[];\n removedTools: string[];\n timestamp: Date;\n };\n \"mcp:server:batch_added\": {\n totalServers: number;\n addedCount: number;\n failedCount: number;\n successfullyAddedServers: string[];\n results: any[];\n timestamp: Date;\n };\n \"mcp:server:rollback\": {\n serverName: string;\n timestamp: Date;\n };\n\n // 连接相关事件\n \"connection:reconnect:completed\": {\n success: boolean;\n reason: string;\n timestamp: Date;\n };\n\n // NPM 安装相关事件\n \"npm:install:started\": {\n version: string;\n installId: string;\n timestamp: number;\n };\n \"npm:install:log\": {\n version: string;\n installId: string;\n type: \"stdout\" | \"stderr\";\n message: string;\n timestamp: number;\n };\n \"npm:install:completed\": {\n version: string;\n installId: string;\n success: boolean;\n duration: number;\n timestamp: number;\n };\n \"npm:install:failed\": {\n version: string;\n installId: string;\n error: string;\n duration: number;\n timestamp: number;\n };\n\n // 测试相关事件(仅用于测试)\n \"high-frequency\": {\n id: number;\n timestamp: number;\n };\n \"bulk-test\": {\n id: number;\n timestamp: number;\n };\n \"error-test\": {\n error: string;\n timestamp: number;\n };\n \"large-data-test\": {\n data: any;\n timestamp: number;\n };\n \"destroy-test\": {\n message: string;\n timestamp: number;\n };\n \"chain-event-1\": {\n value: number;\n timestamp: number;\n };\n \"chain-event-2\": {\n value: number;\n timestamp: number;\n };\n \"chain-event-3\": {\n value: number;\n timestamp: number;\n };\n \"performance-test\": {\n data: any;\n timestamp: number;\n };\n \"test:performance\": {\n id: number;\n timestamp: number;\n };\n \"chain:start\": {\n value: number;\n timestamp: number;\n };\n \"chain:middle\": {\n value: number;\n timestamp: number;\n };\n \"chain:end\": {\n value: number;\n timestamp: number;\n };\n \"test:error\": {\n error: boolean;\n timestamp: number;\n };\n \"test:remove\": {\n id: number;\n timestamp: number;\n };\n}\n\n/**\n * 事件总线 - 用于模块间的解耦通信\n */\nexport class EventBus extends EventEmitter {\n private logger: Logger;\n private eventStats: Map<string, { count: number; lastEmitted: Date }> =\n new Map();\n private maxListeners = 50; // 增加最大监听器数量\n\n constructor() {\n super();\n this.logger = logger;\n this.setMaxListeners(this.maxListeners);\n this.setupErrorHandling();\n }\n\n /**\n * 设置错误处理\n */\n private setupErrorHandling(): void {\n this.on(\"error\", (error) => {\n this.logger.error(\"EventBus 内部错误:\", error);\n });\n\n // 监听器数量警告\n this.on(\"newListener\", (eventName) => {\n const listenerCount = this.listenerCount(eventName);\n if (listenerCount > this.maxListeners * 0.8) {\n this.logger.warn(\n `事件 ${eventName} 的监听器数量过多: ${listenerCount}`\n );\n }\n });\n }\n\n /**\n * 发射事件(类型安全)\n */\n emitEvent<K extends keyof EventBusEvents>(\n eventName: K,\n data: EventBusEvents[K]\n ): boolean {\n try {\n this.updateEventStats(eventName as string);\n this.logger.debug(`发射事件: ${eventName}`, data);\n\n // 使用原始emit方法,保持EventEmitter的所有特性\n return super.emit(eventName, data);\n } catch (error) {\n this.logger.error(`发射事件失败: ${eventName}`, error);\n // 将监听器错误发射到error事件\n if (error instanceof Error) {\n this.emit(\"error\", error);\n }\n return false;\n }\n }\n\n /**\n * 监听事件(类型安全)\n */\n onEvent<K extends keyof EventBusEvents>(\n eventName: K,\n listener: (data: EventBusEvents[K]) => void\n ): this {\n this.logger.debug(`添加事件监听器: ${eventName}`);\n return this.on(eventName, listener);\n }\n\n /**\n * 一次性监听事件(类型安全)\n */\n onceEvent<K extends keyof EventBusEvents>(\n eventName: K,\n listener: (data: EventBusEvents[K]) => void\n ): this {\n this.logger.debug(`添加一次性事件监听器: ${eventName}`);\n\n // 创建包装器来实现一次性监听\n const onceListener = (data: EventBusEvents[K]) => {\n try {\n listener(data);\n } catch (error) {\n // 监听器抛出错误,发射到错误事件\n this.emit(\"error\", error);\n throw error;\n } finally {\n // 在任何情况下都移除监听器\n this.offEvent(eventName, onceListener);\n }\n };\n\n return this.on(eventName, onceListener);\n }\n\n /**\n * 移除事件监听器(类型安全)\n */\n offEvent<K extends keyof EventBusEvents>(\n eventName: K,\n listener: (data: EventBusEvents[K]) => void\n ): this {\n this.logger.debug(`移除事件监听器: ${eventName}`);\n return this.off(eventName, listener);\n }\n\n /**\n * 更新事件统计\n */\n private updateEventStats(eventName: string): void {\n const stats = this.eventStats.get(eventName) || {\n count: 0,\n lastEmitted: new Date(),\n };\n stats.count++;\n stats.lastEmitted = new Date();\n this.eventStats.set(eventName, stats);\n }\n\n /**\n * 获取事件统计信息\n */\n getEventStats(): Record<string, { count: number; lastEmitted: Date }> {\n const stats: Record<string, { count: number; lastEmitted: Date }> = {};\n for (const [eventName, stat] of this.eventStats) {\n stats[eventName] = { ...stat };\n }\n return stats;\n }\n\n /**\n * 获取监听器统计信息\n */\n getListenerStats(): Record<string, number> {\n const stats: Record<string, number> = {};\n for (const eventName of this.eventNames()) {\n stats[eventName as string] = this.listenerCount(eventName);\n }\n return stats;\n }\n\n /**\n * 清理事件统计\n */\n clearEventStats(): void {\n this.eventStats.clear();\n this.logger.info(\"事件统计已清理\");\n }\n\n /**\n * 获取事件总线状态\n */\n getStatus(): {\n totalEvents: number;\n totalListeners: number;\n eventStats: Record<string, { count: number; lastEmitted: Date }>;\n listenerStats: Record<string, number>;\n } {\n return {\n totalEvents: this.eventStats.size,\n totalListeners: Object.values(this.getListenerStats()).reduce(\n (sum, count) => sum + count,\n 0\n ),\n eventStats: this.getEventStats(),\n listenerStats: this.getListenerStats(),\n };\n }\n\n /**\n * 销毁事件总线\n */\n destroy(): void {\n this.removeAllListeners();\n this.eventStats.clear();\n this.logger.info(\"EventBus 已销毁\");\n }\n}\n\n// 单例实例\nlet eventBusInstance: EventBus | null = null;\n\n/**\n * 获取事件总线单例\n */\nexport function getEventBus(): EventBus {\n if (!eventBusInstance) {\n eventBusInstance = new EventBus();\n }\n return eventBusInstance;\n}\n\n/**\n * 销毁事件总线单例\n */\nexport function destroyEventBus(): void {\n if (eventBusInstance) {\n eventBusInstance.destroy();\n eventBusInstance = null;\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport chalk from \"chalk\";\nimport pino from \"pino\";\nimport type { Logger as PinoLogger } from \"pino\";\nimport { z } from \"zod\";\n\nconst LogLevelSchema = z.enum([\n \"fatal\",\n \"error\",\n \"warn\",\n \"info\",\n \"debug\",\n \"trace\",\n]);\ntype Level = z.infer<typeof LogLevelSchema>;\n\n/**\n * 格式化日期时间为 YYYY-MM-DD HH:mm:ss 格式\n * @param date 要格式化的日期对象\n * @returns 格式化后的日期时间字符串\n */\nfunction formatDateTime(date: Date): string {\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\n/**\n * 高性能日志记录器,基于 pino 实现\n *\n * 特性:\n * - 支持控制台和文件双重输出\n * - 支持守护进程模式(仅文件输出)\n * - 支持结构化日志记录\n * - 自动日志文件轮转和管理\n * - 高性能异步写入\n * - 完整的错误堆栈跟踪\n */\nexport class Logger {\n private logFilePath: string | null = null;\n private pinoInstance: PinoLogger;\n private isDaemonMode: boolean;\n private logLevel: Level; // 新增:动态日志级别\n private maxLogFileSize = 10 * 1024 * 1024; // 10MB 默认最大文件大小\n private maxLogFiles = 5; // 最多保留5个日志文件\n\n constructor(level: Level = \"info\") {\n // 检查是否为守护进程模式\n this.isDaemonMode = process.env.XIAOZHI_DAEMON === \"true\";\n\n // 设置并验证日志级别\n this.logLevel = this.validateLogLevel(level);\n\n // 创建 pino 实例\n this.pinoInstance = this.createPinoInstance();\n }\n\n /**\n * 验证日志级别\n * @param level 日志级别\n * @returns 验证后的日志级别\n */\n private validateLogLevel(level: string): Level {\n const normalizedLevel = level.toLowerCase();\n const result = LogLevelSchema.safeParse(normalizedLevel);\n\n if (result.success) {\n return result.data;\n }\n\n return \"info\";\n }\n\n private createPinoInstance(): PinoLogger {\n const streams: pino.StreamEntry[] = [];\n\n // 控制台流 - 只在非守护进程模式下添加\n if (!this.isDaemonMode) {\n // 使用高性能的控制台输出流\n const consoleStream = this.createOptimizedConsoleStream();\n streams.push({\n level: this.logLevel, // 修改:使用动态日志级别\n stream: consoleStream,\n });\n }\n\n // 文件流 - 如果有日志文件路径,使用高性能异步写入\n if (this.logFilePath) {\n streams.push({\n level: this.logLevel, // 修改:使用动态日志级别\n stream: pino.destination({\n dest: this.logFilePath,\n sync: false, // 异步写入提升性能\n append: true,\n mkdir: true,\n }),\n });\n }\n\n // 如果没有流,创建一个空的流避免错误\n if (streams.length === 0) {\n streams.push({\n level: this.logLevel, // 修改:使用动态日志级别\n stream: pino.destination({ dest: \"/dev/null\" }),\n });\n }\n\n return pino(\n {\n level: this.logLevel, // 修改:使用动态日志级别\n // 高性能配置\n timestamp:\n pino.stdTimeFunctions?.isoTime || (() => `,\"time\":${Date.now()}`),\n formatters: {\n // 优化级别格式化\n level: (_label: string, number: number) => ({ level: number }),\n },\n // 禁用不必要的功能以提升性能\n base: null, // 不包含 pid 和 hostname\n serializers: {\n // 优化错误序列化,在测试环境中安全处理\n err: pino.stdSerializers?.err || ((err: any) => err),\n },\n },\n pino.multistream(streams, { dedupe: true })\n );\n }\n\n private createOptimizedConsoleStream() {\n // 预编译级别映射以提升性能\n const levelMap = new Map([\n [20, { name: \"DEBUG\", color: chalk.gray }],\n [30, { name: \"INFO\", color: chalk.blue }],\n [40, { name: \"WARN\", color: chalk.yellow }],\n [50, { name: \"ERROR\", color: chalk.red }],\n [60, { name: \"FATAL\", color: chalk.red }],\n ]);\n\n return {\n write: (chunk: string) => {\n try {\n const logObj = JSON.parse(chunk);\n const message = this.formatConsoleMessageOptimized(logObj, levelMap);\n // 在测试环境中安全地写入\n this.safeWrite(`${message}\\n`);\n } catch (error) {\n // 如果解析失败,直接输出原始内容\n this.safeWrite(chunk);\n }\n },\n };\n }\n\n /**\n * 安全地写入到 stderr,在测试环境中避免错误\n */\n private safeWrite(content: string): void {\n try {\n if (process.stderr && typeof process.stderr.write === \"function\") {\n process.stderr.write(content);\n } else if (console && typeof console.error === \"function\") {\n // 在测试环境中回退到 console.error\n console.error(content.trim());\n }\n } catch (error) {\n // 在极端情况下静默失败,避免测试中断\n }\n }\n\n private formatConsoleMessageOptimized(\n logObj: any,\n levelMap: Map<number, { name: string; color: (text: string) => string }>\n ): string {\n const timestamp = formatDateTime(new Date());\n\n const levelInfo = levelMap.get(logObj.level) || {\n name: \"UNKNOWN\",\n color: (text: string) => text,\n };\n const coloredLevel = levelInfo.color(`[${levelInfo.name}]`);\n\n // 处理结构化日志中的 args,保持兼容性\n let message = logObj.msg;\n if (logObj.args && Array.isArray(logObj.args)) {\n const argsStr = logObj.args\n .map((arg: any) =>\n typeof arg === \"object\" ? JSON.stringify(arg) : String(arg)\n )\n .join(\" \");\n message = `${message} ${argsStr}`;\n }\n\n return `[${timestamp}] ${coloredLevel} ${message}`;\n }\n\n /**\n * 初始化日志文件\n * @param projectDir 项目目录\n */\n initLogFile(projectDir: string): void {\n this.logFilePath = path.join(projectDir, \"xiaozhi.log\");\n\n // 检查并轮转日志文件\n this.rotateLogFileIfNeeded();\n\n // 确保日志文件存在\n if (!fs.existsSync(this.logFilePath)) {\n fs.writeFileSync(this.logFilePath, \"\");\n }\n\n // 重新创建 pino 实例以包含文件流\n this.pinoInstance = this.createPinoInstance();\n }\n\n /**\n * 设置是否启用文件日志\n * @param enable 是否启用\n */\n enableFileLogging(enable: boolean): void {\n // 在 pino 实现中,文件日志的启用/禁用通过重新创建实例来实现\n // 这里保持方法兼容性,但实际上文件日志在 initLogFile 时就已经启用\n if (enable && this.logFilePath) {\n // 重新创建 pino 实例以确保文件流正确配置\n this.pinoInstance = this.createPinoInstance();\n }\n }\n\n /**\n * 记录信息级别日志\n * @param message 日志消息\n * @param args 额外参数\n * @example\n * logger.info('用户登录', 'userId', 12345);\n * logger.info({ userId: 12345, action: 'login' }, '用户登录');\n */\n info(message: string, ...args: any[]): void;\n /**\n * 记录结构化信息级别日志\n * @param obj 结构化日志对象\n * @param message 可选的日志消息\n */\n info(obj: object, message?: string): void;\n info(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.info(messageOrObj);\n } else {\n this.pinoInstance.info({ args }, messageOrObj);\n }\n } else {\n // 结构化日志支持\n this.pinoInstance.info(messageOrObj, args[0] || \"\");\n }\n }\n\n success(message: string, ...args: any[]): void;\n success(obj: object, message?: string): void;\n success(messageOrObj: string | object, ...args: any[]): void {\n // success 映射为 info 级别,保持 API 兼容性\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.info(messageOrObj);\n } else {\n this.pinoInstance.info({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.info(messageOrObj, args[0] || \"\");\n }\n }\n\n warn(message: string, ...args: any[]): void;\n warn(obj: object, message?: string): void;\n warn(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.warn(messageOrObj);\n } else {\n this.pinoInstance.warn({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.warn(messageOrObj, args[0] || \"\");\n }\n }\n\n error(message: string, ...args: any[]): void;\n error(obj: object, message?: string): void;\n error(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.error(messageOrObj);\n } else {\n // 改进错误处理 - 特殊处理 Error 对象\n const errorArgs = args.map((arg) => {\n if (arg instanceof Error) {\n if (this.pinoInstance.level === \"debug\") return arg.message;\n return {\n message: arg.message,\n stack: arg.stack,\n name: arg.name,\n cause: arg.cause,\n };\n }\n return arg;\n });\n this.pinoInstance.error({ args: errorArgs }, messageOrObj);\n }\n } else {\n // 结构化错误日志,自动提取错误信息\n const enhancedObj = this.enhanceErrorObject(messageOrObj);\n this.pinoInstance.error(enhancedObj, args[0] || \"\");\n }\n }\n\n debug(message: string, ...args: any[]): void;\n debug(obj: object, message?: string): void;\n debug(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.debug(messageOrObj);\n } else {\n this.pinoInstance.debug({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.debug(messageOrObj, args[0] || \"\");\n }\n }\n\n log(message: string, ...args: any[]): void;\n log(obj: object, message?: string): void;\n log(messageOrObj: string | object, ...args: any[]): void {\n // log 方法使用 info 级别\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.info(messageOrObj);\n } else {\n this.pinoInstance.info({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.info(messageOrObj, args[0] || \"\");\n }\n }\n\n /**\n * 增强错误对象,提取更多错误信息\n */\n private enhanceErrorObject(obj: any): any {\n const enhanced = { ...obj };\n\n // 遍历对象属性,查找 Error 实例\n for (const [key, value] of Object.entries(enhanced)) {\n if (value instanceof Error) {\n enhanced[key] = {\n message: value.message,\n stack: value.stack,\n name: value.name,\n cause: value.cause,\n };\n }\n }\n\n return enhanced;\n }\n\n /**\n * 检查并轮转日志文件(如果需要)\n */\n private rotateLogFileIfNeeded(): void {\n if (!this.logFilePath || !fs.existsSync(this.logFilePath)) {\n return;\n }\n\n try {\n const stats = fs.statSync(this.logFilePath);\n if (stats.size > this.maxLogFileSize) {\n this.rotateLogFile();\n }\n } catch (error) {\n // 忽略文件状态检查错误\n }\n }\n\n /**\n * 轮转日志文件\n */\n private rotateLogFile(): void {\n if (!this.logFilePath) return;\n\n try {\n const logDir = path.dirname(this.logFilePath);\n const logName = path.basename(this.logFilePath, \".log\");\n\n // 移动现有的编号日志文件\n for (let i = this.maxLogFiles - 1; i >= 1; i--) {\n const oldFile = path.join(logDir, `${logName}.${i}.log`);\n const newFile = path.join(logDir, `${logName}.${i + 1}.log`);\n\n if (fs.existsSync(oldFile)) {\n if (i === this.maxLogFiles - 1) {\n // 删除最老的文件\n fs.unlinkSync(oldFile);\n } else {\n fs.renameSync(oldFile, newFile);\n }\n }\n }\n\n // 将当前日志文件重命名为 .1.log\n const firstRotatedFile = path.join(logDir, `${logName}.1.log`);\n fs.renameSync(this.logFilePath, firstRotatedFile);\n } catch (error) {\n // 轮转失败时忽略错误,继续使用当前文件\n }\n }\n\n /**\n * 清理旧的日志文件\n */\n cleanupOldLogs(): void {\n if (!this.logFilePath) return;\n\n try {\n const logDir = path.dirname(this.logFilePath);\n const logName = path.basename(this.logFilePath, \".log\");\n\n // 删除超过最大数量的日志文件\n for (let i = this.maxLogFiles + 1; i <= this.maxLogFiles + 10; i++) {\n const oldFile = path.join(logDir, `${logName}.${i}.log`);\n if (fs.existsSync(oldFile)) {\n fs.unlinkSync(oldFile);\n }\n }\n } catch (error) {\n // 忽略清理错误\n }\n }\n\n /**\n * 设置日志文件管理参数\n */\n setLogFileOptions(maxSize: number, maxFiles: number): void {\n this.maxLogFileSize = maxSize;\n this.maxLogFiles = maxFiles;\n }\n\n /**\n * 关闭日志文件流\n */\n close(): void {\n // pino 实例会自动处理流的关闭\n // 这里保持方法兼容性\n }\n\n /**\n * 动态设置日志级别\n * @param level 新的日志级别\n * @description 动态更新Logger实例的日志级别\n */\n setLevel(level: Level): void {\n this.logLevel = this.validateLogLevel(level);\n\n // 重新创建pino实例以应用新的日志级别\n this.pinoInstance = this.createPinoInstance();\n }\n\n /**\n * 获取当前日志级别\n * @returns 当前日志级别\n */\n getLevel(): Level {\n return this.logLevel;\n }\n}\n\n// 全局Logger实例管理\nlet globalLogger: Logger | null = null;\nlet globalLogLevel: Level = \"info\"; // 全局日志级别\n\n/**\n * 创建Logger实例\n * @param level 日志级别,默认为全局级别\n * @returns Logger实例\n */\nexport function createLogger(level?: Level): Logger {\n return new Logger(level || globalLogLevel);\n}\n\n/**\n * 获取全局Logger实例\n * @returns 全局Logger实例\n */\nexport function getLogger(): Logger {\n if (!globalLogger) {\n globalLogger = new Logger(globalLogLevel); // 使用全局级别\n }\n return globalLogger;\n}\n\n/**\n * 设置全局Logger实例\n * @param logger 新的Logger实例\n */\nexport function setGlobalLogger(logger: Logger): void {\n globalLogger = logger;\n}\n\n/**\n * 设置全局日志级别\n * @param level 新的日志级别\n * @description 更新全局日志级别,并影响现有和未来的Logger实例\n */\nexport function setGlobalLogLevel(level: Level): void {\n globalLogLevel = level;\n\n // 如果已存在全局Logger实例,更新其级别\n if (globalLogger) {\n globalLogger.setLevel(level);\n }\n}\n\n/**\n * 获取当前全局日志级别\n * @returns 当前日志级别\n */\nexport function getGlobalLogLevel(): Level {\n return globalLogLevel;\n}\n\n// 导出默认实例(向后兼容)\nexport const logger = getLogger();\n","/**\n * MCP 服务器配置 Type 字段标准化工具\n * 支持将各种 type 字段格式转换为标准的中划线格式\n */\n\n/**\n * MCP 服务器配置的基础接口\n * 定义包含可选 type 字段的配置对象结构\n */\nexport interface MCPBaseConfig {\n type?: string;\n [key: string]: unknown; // 允许其他配置属性\n}\n\n/**\n * MCP 服务器配置 Type 字段标准化工具类\n */\nexport namespace TypeFieldNormalizer {\n /**\n * 标准化type字段格式\n * 支持将各种格式转换为标准的中划线格式\n */\n // 函数重载:泛型版本,用于类型安全的调用\n export function normalizeTypeField<T extends MCPBaseConfig>(config: T): T;\n\n // 函数重载:向后兼容版本,用于 unknown 类型输入\n export function normalizeTypeField(config: unknown): unknown;\n\n // 统一实现\n export function normalizeTypeField<T extends MCPBaseConfig>(\n config: T | unknown\n ): T | unknown {\n if (!config || typeof config !== \"object\") {\n return config;\n }\n\n // 创建配置的深拷贝以避免修改原始对象\n const normalizedConfig = JSON.parse(JSON.stringify(config));\n\n // 如果配置中没有type字段,直接返回\n if (!(\"type\" in normalizedConfig)) {\n return normalizedConfig;\n }\n\n const originalType = normalizedConfig.type;\n\n // 如果已经是标准格式,直接返回\n if (originalType === \"sse\" || originalType === \"streamable-http\") {\n return normalizedConfig;\n }\n\n // 转换为标准格式\n let normalizedType: string;\n\n if (\n originalType === \"streamableHttp\" ||\n originalType === \"streamable_http\"\n ) {\n normalizedType = \"streamable-http\";\n } else if (originalType === \"s_se\" || originalType === \"s-se\") {\n normalizedType = \"sse\";\n } else {\n // 对于其他格式,尝试智能转换\n normalizedType = convertToKebabCase(originalType);\n }\n\n // 验证转换后的类型是否有效\n if (normalizedType === \"sse\" || normalizedType === \"streamable-http\") {\n normalizedConfig.type = normalizedType;\n // 记录转换日志(如果有的话)\n if (originalType !== normalizedType) {\n // 可以在需要时添加日志记录\n }\n }\n\n return normalizedConfig;\n }\n\n /**\n * 将字符串转换为kebab-case格式\n */\n function convertToKebabCase(str: string): string {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1-$2\") // 驼峰转中划线\n .replace(/_/g, \"-\") // 下划线转中划线\n .toLowerCase(); // 转小写\n }\n}\n","/**\n * MCP 服务工具函数 - 服务端版本\n * 用于判断 MCP 服务的通信类型和其他相关操作\n */\n\n// 定义通信类型\nexport type MCPCommunicationType = \"stdio\" | \"sse\" | \"streamable-http\";\n\n// 定义 MCP 服务配置类型(与客户端保持一致)\nexport interface LocalMCPServerConfig {\n command: string;\n args: string[];\n env?: Record<string, string>;\n}\n\nexport interface SSEMCPServerConfig {\n type: \"sse\";\n url: string;\n headers?: Record<string, string>;\n}\n\nexport interface StreamableHTTPMCPServerConfig {\n type?: \"streamable-http\"; // 可选,因为默认就是 streamable-http\n url: string;\n headers?: Record<string, string>;\n}\n\nexport type MCPServerConfig =\n | LocalMCPServerConfig\n | SSEMCPServerConfig\n | StreamableHTTPMCPServerConfig;\n\n/**\n * 判断 MCP 服务的通信类型\n *\n * @param serverConfig MCP 服务配置对象\n * @returns 通信类型:'stdio' | 'sse' | 'streamable-http'\n *\n * 判断逻辑:\n * 1. 如果配置对象有 command 字段 → stdio\n * 2. 如果配置对象有 type 字段且值为 \"sse\" → sse\n * 3. 如果配置对象有 url 字段但没有 type 字段,或者 type 字段不是 \"sse\" → streamable-http\n *\n * @example\n * ```typescript\n * // stdio 类型\n * const stdioConfig = {\n * command: \"node\",\n * args: [\"./mcpServers/calculator.js\"]\n * };\n * getMcpServerCommunicationType(stdioConfig); // \"stdio\"\n *\n * // sse 类型\n * const sseConfig = {\n * type: \"sse\" as const,\n * url: \"https://mcp.api-inference.modelscope.net/d3cfd34529ae4e/sse\"\n * };\n * getMcpServerCommunicationType(sseConfig); // \"sse\"\n *\n * // streamable-http 类型\n * const httpConfig = {\n * url: \"https://mcp.amap.com/mcp?key=1ec31da021b2702787841ea4ee822de3\"\n * };\n * getMcpServerCommunicationType(httpConfig); // \"streamable-http\"\n * ```\n */\nexport function getMcpServerCommunicationType(\n serverConfig: MCPServerConfig | Record<string, unknown>\n): MCPCommunicationType {\n // 参数验证\n if (!serverConfig || typeof serverConfig !== \"object\") {\n throw new Error(\"服务配置必须是一个有效的对象\");\n }\n\n // 1. 检查是否为 stdio 类型(有 command 字段)\n if (\"command\" in serverConfig && typeof serverConfig.command === \"string\") {\n return \"stdio\";\n }\n\n // 2. 检查是否为 sse 类型(有 type: \"sse\" 字段)\n if (\"type\" in serverConfig && serverConfig.type === \"sse\") {\n return \"sse\";\n }\n\n // 3. 检查是否为 streamable-http 类型(有 type: \"streamable-http\" 字段或有 url 字段)\n if (\n (\"type\" in serverConfig && serverConfig.type === \"streamable-http\") ||\n (\"url\" in serverConfig && typeof serverConfig.url === \"string\")\n ) {\n return \"streamable-http\";\n }\n\n // 如果都不匹配,抛出错误\n throw new Error(\n \"无法识别的 MCP 服务配置类型。配置必须包含 command 字段(stdio)、type: 'sse' 字段(sse)或 url 字段(streamable-http)\"\n );\n}\n\n/**\n * 检查 MCP 服务配置是否为 stdio 类型\n */\nexport function isStdioMcpServer(\n serverConfig: MCPServerConfig | Record<string, unknown>\n): serverConfig is LocalMCPServerConfig {\n return getMcpServerCommunicationType(serverConfig) === \"stdio\";\n}\n\n/**\n * 检查 MCP 服务配置是否为 sse 类型\n */\nexport function isSSEMcpServer(\n serverConfig: MCPServerConfig | Record<string, unknown>\n): serverConfig is SSEMCPServerConfig {\n return getMcpServerCommunicationType(serverConfig) === \"sse\";\n}\n\n/**\n * 检查 MCP 服务配置是否为 streamable-http 类型\n */\nexport function isStreamableHTTPMcpServer(\n serverConfig: MCPServerConfig | Record<string, unknown>\n): serverConfig is StreamableHTTPMCPServerConfig {\n return getMcpServerCommunicationType(serverConfig) === \"streamable-http\";\n}\n\n/**\n * 获取 MCP 服务配置的显示名称\n * 用于在日志中显示更友好的通信类型名称\n */\nexport function getMcpServerTypeDisplayName(\n serverConfig: MCPServerConfig | Record<string, unknown>\n): string {\n const type = getMcpServerCommunicationType(serverConfig);\n\n switch (type) {\n case \"stdio\":\n return \"本地进程 (stdio)\";\n case \"sse\":\n return \"服务器推送 (SSE)\";\n case \"streamable-http\":\n return \"流式 HTTP\";\n default:\n return \"未知类型\";\n }\n}\n\n/**\n * 验证 MCP 服务配置的完整性\n * 根据不同的通信类型验证必需的字段\n */\nexport function validateMcpServerConfig(\n serverName: string,\n serverConfig: unknown\n): { valid: boolean; error?: string } {\n if (\n !serverConfig ||\n typeof serverConfig !== \"object\" ||\n Array.isArray(serverConfig)\n ) {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 的配置必须是一个对象`,\n };\n }\n\n try {\n const communicationType = getMcpServerCommunicationType(\n serverConfig as Record<string, unknown>\n );\n\n const config = serverConfig as Record<string, unknown>;\n\n switch (communicationType) {\n case \"stdio\":\n if (!config.command || typeof config.command !== \"string\") {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 缺少必需的 command 字段或字段类型不正确`,\n };\n }\n if (!Array.isArray(config.args)) {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 的 args 字段必须是数组`,\n };\n }\n if (\n config.env &&\n (typeof config.env !== \"object\" || Array.isArray(config.env))\n ) {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 的 env 字段必须是对象`,\n };\n }\n break;\n\n case \"sse\":\n if (config.type !== \"sse\") {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 的 type 字段必须是 \"sse\"`,\n };\n }\n if (!config.url || typeof config.url !== \"string\") {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 缺少必需的 url 字段或字段类型不正确`,\n };\n }\n if (\n config.headers &&\n (typeof config.headers !== \"object\" || Array.isArray(config.headers))\n ) {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 的 headers 字段必须是对象`,\n };\n }\n break;\n\n case \"streamable-http\":\n if (!config.url || typeof config.url !== \"string\") {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 缺少必需的 url 字段或字段类型不正确`,\n };\n }\n if (config.type && config.type !== \"streamable-http\") {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 的 type 字段如果存在,必须是 \"streamable-http\"`,\n };\n }\n if (\n config.headers &&\n (typeof config.headers !== \"object\" || Array.isArray(config.headers))\n ) {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 的 headers 字段必须是对象`,\n };\n }\n break;\n\n default:\n return {\n valid: false,\n error: `服务 \"${serverName}\" 的配置类型无法识别`,\n };\n }\n\n return { valid: true };\n } catch (error) {\n return {\n valid: false,\n error: `服务 \"${serverName}\" 的配置无效: ${\n error instanceof Error ? error.message : \"未知错误\"\n }`,\n };\n }\n}\n\nexport function sliceEndpoint(endpoint: string) {\n return `${endpoint.slice(0, 30)}...${endpoint.slice(-10)}`;\n}\n","/**\n * JSON5 注释保留适配器\n * 使用 comment-json 实现 JSON5/JSONC 注释保留功能\n *\n * 注意:为了使用 comment-json 保留注释,JSON5 配置文件的键需要带引号。\n * 这与 JSON5 标准语法允许不带引号的键略有不同,但能实现注释保留功能。\n */\nimport * as commentJson from \"comment-json\";\n\n/**\n * JSON5 写入器适配器接口\n * 保持与 json5-writer 兼容的 API\n */\nexport interface Json5WriterAdapter {\n write(data: unknown): void;\n toSource(): string;\n}\n\n/**\n * 创建 JSON5 写入器适配器\n * @param content 原始 JSON5 内容字符串\n * @returns Json5WriterAdapter 实例\n */\nexport function createJson5Writer(content: string): Json5WriterAdapter {\n // 使用 comment-json 解析原始内容\n // comment-json 会保留注释信息在返回的对象中\n const parsedData = commentJson.parse(content) as Record<string, unknown>;\n\n return {\n write(data: unknown): void {\n // 通过 Object.assign 合并新数据\n if (parsedData && typeof parsedData === \"object\" && data) {\n Object.assign(parsedData, data);\n }\n },\n\n toSource(): string {\n // 使用 comment-json 序列化,保留注释和格式\n return commentJson.stringify(parsedData, null, 2);\n },\n };\n}\n\n/**\n * 解析 JSON5 内容(带注释保留)\n * @param content JSON5 内容字符串\n * @returns 解析后的对象\n */\nexport function parseJson5(content: string): unknown {\n // 使用 comment-json 解析,支持注释保留\n return commentJson.parse(content);\n}\n","#!/usr/bin/env node\n\n/**\n * MCP 服务管理器\n * 使用 MCPService 实例管理多个 MCP 服务\n * 专注于实例管理、工具聚合和路由调用\n */\n\nimport { EventEmitter } from \"node:events\";\nimport { isModelScopeURL } from \"@/lib/config/adapter.js\";\nimport type { MCPToolConfig } from \"@/lib/config/manager.js\";\nimport { configManager } from \"@/lib/config/manager.js\";\nimport { MCPService } from \"@/lib/mcp\";\nimport { MCPCacheManager } from \"@/lib/mcp\";\nimport type {\n CustomMCPTool,\n JSONSchema,\n MCPServiceConfig,\n ManagerStatus,\n ToolCallResult,\n ToolInfo,\n UnifiedServerConfig,\n UnifiedServerStatus,\n} from \"@/lib/mcp/types\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { getEventBus } from \"@root/services/EventBus.js\";\nimport type { MCPMessage } from \"@root/types/mcp.js\";\nimport { CustomMCPHandler } from \"./custom.js\";\nimport { ToolCallLogger } from \"./log.js\";\nimport { MCPMessageHandler } from \"./message.js\";\nimport { ConnectionState, type TransportAdapter } from \"./transports/index.js\";\n\nexport class MCPServiceManager extends EventEmitter {\n private services: Map<string, MCPService> = new Map();\n private configs: Record<string, MCPServiceConfig> = {};\n private tools: Map<string, ToolInfo> = new Map(); // 缓存工具信息,保持向后兼容\n private customMCPHandler: CustomMCPHandler; // CustomMCP 工具处理器\n private cacheManager: MCPCacheManager; // 缓存管理器\n private eventBus = getEventBus(); // 事件总线\n private toolCallLogger: ToolCallLogger; // 工具调用记录器\n private retryTimers: Map<string, NodeJS.Timeout> = new Map(); // 重试定时器\n private failedServices: Set<string> = new Set(); // 失败的服务集合\n\n // 新增:传输适配器管理\n private transportAdapters: Map<string, TransportAdapter> = new Map();\n private messageHandler: MCPMessageHandler;\n\n // 新增:服务器状态管理(从 UnifiedMCPServer 移入)\n private isRunning = false;\n private config: UnifiedServerConfig;\n\n /**\n * 创建 MCPServiceManager 实例\n * @param configs 可选的初始服务配置或服务器配置\n */\n constructor(\n configs?: Record<string, MCPServiceConfig> | UnifiedServerConfig\n ) {\n super();\n\n // 处理参数,支持 UnifiedServerConfig 格式\n if (configs && this.isUnifiedServerConfig(configs)) {\n // UnifiedServerConfig 格式\n this.config = {\n name: \"MCPServiceManager\",\n enableLogging: true,\n logLevel: \"info\",\n ...configs,\n };\n this.configs = configs.configs || {};\n } else {\n // 原有的 configs 格式\n this.config = {\n name: \"MCPServiceManager\",\n enableLogging: true,\n logLevel: \"info\",\n };\n this.configs = configs || {};\n }\n\n // 在测试环境中使用临时目录,避免在项目根目录创建缓存文件\n const isTestEnv =\n process.env.NODE_ENV === \"test\" || process.env.VITEST === \"true\";\n const cachePath = isTestEnv\n ? `/tmp/xiaozhi-test-${Date.now()}-${Math.random()\n .toString(36)\n .substring(2, 11)}/xiaozhi.cache.json`\n : undefined;\n\n this.cacheManager = new MCPCacheManager(cachePath);\n this.customMCPHandler = new CustomMCPHandler(this.cacheManager, this);\n\n // 初始化工具调用记录器\n const toolCallLogConfig = configManager.getToolCallLogConfig();\n const configDir = configManager.getConfigDir();\n this.toolCallLogger = new ToolCallLogger(toolCallLogConfig, configDir);\n\n // 设置事件监听器\n this.setupEventListeners();\n\n // 初始化消息处理器(确保在其他组件初始化完成后)\n this.messageHandler = new MCPMessageHandler(this);\n }\n\n /**\n * 设置事件监听器\n */\n private setupEventListeners(): void {\n // 监听MCP服务连接成功事件\n this.eventBus.onEvent(\"mcp:service:connected\", async (data) => {\n await this.handleServiceConnected(data);\n });\n\n // 监听MCP服务断开连接事件\n this.eventBus.onEvent(\"mcp:service:disconnected\", async (data) => {\n await this.handleServiceDisconnected(data);\n });\n\n // 监听MCP服务连接失败事件\n this.eventBus.onEvent(\"mcp:service:connection:failed\", async (data) => {\n await this.handleServiceConnectionFailed(data);\n });\n }\n\n /**\n * 处理MCP服务连接成功事件\n */\n private async handleServiceConnected(data: {\n serviceName: string;\n tools: Tool[];\n connectionTime: Date;\n }): Promise<void> {\n console.debug(`服务 ${data.serviceName} 连接成功,开始刷新工具缓存`);\n\n try {\n // 获取最新的工具列表\n const service = this.services.get(data.serviceName);\n if (service) {\n // 重新初始化CustomMCPHandler\n await this.refreshCustomMCPHandlerPublic();\n\n console.info(`服务 ${data.serviceName} 工具缓存刷新完成`);\n }\n } catch (error) {\n console.error(`刷新服务 ${data.serviceName} 工具缓存失败:`, error);\n }\n }\n\n /**\n * 处理MCP服务断开连接事件\n */\n private async handleServiceDisconnected(data: {\n serviceName: string;\n reason?: string;\n disconnectionTime: Date;\n }): Promise<void> {\n console.info(\n `服务 ${data.serviceName} 断开连接,原因: ${data.reason || \"未知\"}`\n );\n\n try {\n // 更新工具缓存\n await this.refreshToolsCache();\n\n // 重新初始化CustomMCPHandler\n await this.refreshCustomMCPHandlerPublic();\n\n console.info(`服务 ${data.serviceName} 断开连接处理完成`);\n } catch (error) {\n console.error(`服务 ${data.serviceName} 断开连接处理失败:`, error);\n }\n }\n\n /**\n * 处理MCP服务连接失败事件\n */\n private async handleServiceConnectionFailed(data: {\n serviceName: string;\n error: Error;\n attempt: number;\n }): Promise<void> {\n try {\n await this.refreshCustomMCPHandlerPublic();\n } catch (error) {\n console.error(\"刷新CustomMCPHandler失败:\", error);\n }\n }\n\n /**\n * 启动所有 MCP 服务\n */\n async startAllServices(): Promise<void> {\n console.debug(\"[MCPManager] 正在启动所有 MCP 服务...\");\n\n // 初始化 CustomMCP 处理器\n try {\n this.customMCPHandler.initialize();\n console.debug(\"[MCPManager] CustomMCP 处理器初始化完成\");\n } catch (error) {\n console.error(\"[MCPManager] CustomMCP 处理器初始化失败:\", error);\n // CustomMCP 初始化失败不应该阻止标准 MCP 服务启动\n }\n\n const configEntries = Object.entries(this.configs);\n if (configEntries.length === 0) {\n console.warn(\n \"[MCPManager] 没有配置任何 MCP 服务,请使用 addServiceConfig() 添加服务配置\"\n );\n // 即使没有标准 MCP 服务,也可能有 CustomMCP 工具\n return;\n }\n\n // 记录启动开始\n console.info(\n `[MCPManager] 开始并行启动 ${configEntries.length} 个 MCP 服务`\n );\n\n // 并行启动所有服务,实现服务隔离\n const startPromises = configEntries.map(async ([serviceName]) => {\n try {\n await this.startService(serviceName);\n return { serviceName, success: true, error: null };\n } catch (error) {\n return {\n serviceName,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n });\n\n // 等待所有服务启动完成\n const results = await Promise.allSettled(startPromises);\n\n // 统计启动结果\n let successCount = 0;\n let failureCount = 0;\n const failedServices: string[] = [];\n\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n if (result.value.success) {\n successCount++;\n } else {\n failureCount++;\n failedServices.push(result.value.serviceName);\n }\n } else {\n failureCount++;\n }\n }\n\n // 记录启动完成统计\n console.info(\n `[MCPManager] 服务启动完成 - 成功: ${successCount}, 失败: ${failureCount}`\n );\n\n // 记录失败的服务列表\n if (failedServices.length > 0) {\n console.warn(\n `[MCPManager] 以下服务启动失败: ${failedServices.join(\", \")}`\n );\n\n // 如果所有服务都失败了,发出警告但系统继续运行以便重试\n if (failureCount === configEntries.length) {\n console.warn(\n \"[MCPManager] 所有 MCP 服务启动失败,但系统将继续运行以便重试\"\n );\n }\n }\n\n // 启动失败服务重试机制\n if (failedServices.length > 0) {\n this.scheduleFailedServicesRetry(failedServices);\n }\n }\n\n /**\n * 启动单个 MCP 服务\n */\n async startService(serviceName: string): Promise<void> {\n const config = this.configs[serviceName];\n if (!config) {\n throw new Error(`未找到服务配置: ${serviceName}`);\n }\n\n try {\n // 如果服务已存在,先停止它\n if (this.services.has(serviceName)) {\n await this.stopService(serviceName);\n }\n\n // 创建 MCPService 实例\n const service = new MCPService(config);\n\n // 连接到服务\n await service.connect();\n\n // 存储服务实例\n this.services.set(serviceName, service);\n\n // 更新工具缓存\n await this.refreshToolsCache();\n\n // 注意:工具缓存刷新现在通过事件监听器自动处理,不需要在这里手动调用\n // MCPService.connect() 成功后会发射 mcp:service:connected 事件\n // 事件监听器会自动触发工具缓存刷新和CustomMCPHandler刷新\n\n const tools = service.getTools();\n console.debug(\n `[MCPManager] ${serviceName} 服务启动成功,加载了 ${tools.length} 个工具:`,\n tools.map((t) => t.name).join(\", \")\n );\n } catch (error) {\n console.error(\n `[MCPManager] 启动 ${serviceName} 服务失败:`,\n (error as Error).message\n );\n // 清理可能的部分状态\n this.services.delete(serviceName);\n throw error;\n }\n }\n\n /**\n * 停止单个服务\n */\n async stopService(serviceName: string): Promise<void> {\n console.info(`[MCPManager] 停止 MCP 服务: ${serviceName}`);\n\n const service = this.services.get(serviceName);\n if (!service) {\n console.warn(`[MCPManager] 服务 ${serviceName} 不存在或未启动`);\n return;\n }\n\n try {\n await service.disconnect();\n this.services.delete(serviceName);\n\n // 更新工具缓存\n await this.refreshToolsCache();\n\n console.info(`[MCPManager] ${serviceName} 服务已停止`);\n } catch (error) {\n console.error(\n `[MCPManager] 停止 ${serviceName} 服务失败:`,\n (error as Error).message\n );\n throw error;\n }\n }\n\n /**\n * 刷新工具缓存\n */\n private async refreshToolsCache(): Promise<void> {\n this.tools.clear();\n\n for (const [serviceName, service] of this.services) {\n if (service.isConnected()) {\n const tools = service.getTools();\n const config = this.configs[serviceName];\n\n // 异步写入缓存(不阻塞主流程)\n if (config) {\n this.cacheManager\n .writeCacheEntry(serviceName, tools, config)\n .then(() => {\n console.debug(\n `[MCPManager] 已将 ${serviceName} 工具列表写入缓存`\n );\n })\n .catch((error) => {\n console.warn(\n `[MCPManager] 写入缓存失败: ${serviceName}, 错误: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n });\n }\n\n // 原有逻辑保持不变\n for (const tool of tools) {\n const toolKey = `${serviceName}__${tool.name}`;\n this.tools.set(toolKey, {\n serviceName,\n originalName: tool.name,\n tool,\n });\n }\n }\n }\n\n // 同步工具配置到配置文件\n await this.syncToolsConfigToFile();\n }\n\n /**\n * 获取所有可用工具(优化版本,移除阻塞逻辑,添加工具启用状态过滤)\n */\n getAllTools(): Array<{\n name: string;\n description: string;\n inputSchema: JSONSchema;\n serviceName: string;\n originalName: string;\n }> {\n const allTools: Array<{\n name: string;\n description: string;\n inputSchema: JSONSchema;\n serviceName: string;\n originalName: string;\n }> = [];\n\n // 1. 收集所有已连接服务的工具(包含启用状态过滤)\n for (const [serviceName, service] of this.services) {\n try {\n if (service.isConnected()) {\n const serviceTools = service.getTools();\n for (const tool of serviceTools) {\n try {\n // 检查工具启用状态 - 这个调用可能会抛出异常\n const isEnabled = configManager.isToolEnabled(\n serviceName,\n tool.name\n );\n if (!isEnabled) {\n continue; // 跳过禁用的工具\n }\n\n const toolKey = `${serviceName}__${tool.name}`;\n allTools.push({\n name: toolKey,\n description: tool.description || \"\",\n inputSchema: tool.inputSchema,\n serviceName,\n originalName: tool.name,\n });\n } catch (toolError) {\n console.warn(\n `[MCPManager] 检查工具 ${serviceName}.${tool.name} 启用状态失败,跳过该工具:`,\n toolError\n );\n }\n }\n }\n } catch (serviceError) {\n console.warn(\n `[MCPManager] 获取服务 ${serviceName} 的工具失败,跳过该服务:`,\n serviceError\n );\n }\n }\n\n // 2. 添加CustomMCP工具(添加异常处理确保优雅降级)\n let customTools: Tool[] = [];\n try {\n customTools = this.customMCPHandler.getTools();\n console.debug(\n `[MCPManager] 成功获取 ${customTools.length} 个 customMCP 工具`\n );\n } catch (error) {\n console.warn(\n \"[MCPManager] 获取 CustomMCP 工具失败,将只返回标准 MCP 工具:\",\n error\n );\n // 根据技术方案要求,CustomMCP 工具获取失败时不应该影响标准 MCP 工具的返回\n customTools = [];\n }\n\n for (const tool of customTools) {\n try {\n allTools.push({\n name: tool.name,\n description: tool.description || \"\",\n inputSchema: tool.inputSchema,\n serviceName: this.getServiceNameForTool(tool),\n originalName: tool.name,\n });\n } catch (toolError) {\n console.warn(\n `[MCPManager] 处理 CustomMCP 工具 ${tool.name} 失败,跳过该工具:`,\n toolError\n );\n }\n }\n\n console.debug(`[MCPManager] 成功获取 ${allTools.length} 个可用工具`);\n return allTools;\n }\n\n /**\n * 根据工具配置确定服务名称\n * @param tool 工具对象\n * @returns 服务名称\n */\n private getServiceNameForTool(tool: CustomMCPTool): string {\n if (tool.handler?.type === \"mcp\") {\n // 如果是从 MCP 同步的工具,返回原始服务名称\n const config = tool.handler.config as\n | { serviceName?: string; toolName?: string }\n | undefined;\n return config?.serviceName || \"customMCP\";\n }\n return \"customMCP\";\n }\n\n /**\n * 根据工具信息获取日志记录用的服务名称\n * @param customTool CustomMCP 工具信息\n * @returns 用于日志记录的服务名称\n */\n private getLogServerName(customTool: CustomMCPTool): string {\n if (!customTool?.handler) {\n return \"custom\";\n }\n\n switch (customTool.handler.type) {\n case \"mcp\": {\n const config = customTool.handler.config as\n | { serviceName?: string; toolName?: string }\n | undefined;\n return config?.serviceName || \"customMCP\";\n }\n case \"coze\":\n return \"coze\";\n case \"dify\":\n return \"dify\";\n case \"n8n\":\n return \"n8n\";\n default:\n return \"custom\";\n }\n }\n\n /**\n * 根据工具信息获取原始工具名称\n * @param toolName 格式化后的工具名称\n * @param customTool CustomMCP 工具信息\n * @param toolInfo 标准工具信息\n * @returns 原始工具名称\n */\n private getOriginalToolName(\n toolName: string,\n customTool: CustomMCPTool | undefined,\n toolInfo?: ToolInfo\n ): string {\n if (customTool) {\n // CustomMCP 工具\n if (customTool.handler?.type === \"mcp\") {\n const config = customTool.handler.config as\n | { serviceName?: string; toolName?: string }\n | undefined;\n return config?.toolName || toolName;\n }\n return toolName;\n }\n\n // 标准 MCP 工具\n return toolInfo?.originalName || toolName;\n }\n\n /**\n * 调用 MCP 工具(支持标准 MCP 工具和 customMCP 工具)\n */\n async callTool(\n toolName: string,\n arguments_: Record<string, unknown>,\n options?: { timeout?: number }\n ): Promise<ToolCallResult> {\n const startTime = Date.now();\n\n // 初始化日志信息\n let logServerName = \"unknown\";\n let originalToolName: string = toolName;\n\n try {\n let result: ToolCallResult;\n\n // 检查是否是 customMCP 工具\n if (this.customMCPHandler.hasTool(toolName)) {\n const customTool = this.customMCPHandler.getToolInfo(toolName);\n\n // 设置日志信息(添加空值检查)\n if (customTool) {\n logServerName = this.getLogServerName(customTool);\n originalToolName = this.getOriginalToolName(toolName, customTool);\n }\n\n if (customTool?.handler?.type === \"mcp\") {\n // 对于 mcp 类型的工具,直接路由到对应的 MCP 服务\n result = await this.callMCPTool(\n toolName,\n customTool.handler.config,\n arguments_\n );\n\n // 异步更新工具调用统计(成功调用)\n this.updateToolStatsSafe(\n toolName,\n customTool.handler.config.serviceName,\n customTool.handler.config.toolName,\n true\n );\n } else {\n // 其他类型的 customMCP 工具正常处理,传递options参数\n result = await this.customMCPHandler.callTool(\n toolName,\n arguments_,\n options\n );\n console.info(`[MCPManager] CustomMCP 工具 ${toolName} 调用成功`);\n\n // 异步更新工具调用统计(成功调用)\n this.updateToolStatsSafe(toolName, \"customMCP\", toolName, true);\n }\n } else {\n // 如果不是 customMCP 工具,则查找标准 MCP 工具\n const toolInfo = this.tools.get(toolName);\n if (!toolInfo) {\n throw new Error(`未找到工具: ${toolName}`);\n }\n\n // 设置日志信息\n logServerName = toolInfo.serviceName;\n originalToolName = toolInfo.originalName;\n\n const service = this.services.get(toolInfo.serviceName);\n if (!service) {\n throw new Error(`服务 ${toolInfo.serviceName} 不可用`);\n }\n\n if (!service.isConnected()) {\n throw new Error(`服务 ${toolInfo.serviceName} 未连接`);\n }\n\n result = await service.callTool(\n toolInfo.originalName,\n arguments_ || {}\n );\n\n console.debug(\"[MCPManager] 工具调用成功\", {\n toolName: toolName,\n result: result,\n });\n\n // 异步更新工具调用统计(成功调用)\n this.updateToolStatsSafe(\n toolName,\n toolInfo.serviceName,\n toolInfo.originalName,\n true\n );\n }\n\n // 记录成功的工具调用\n this.toolCallLogger.recordToolCall({\n toolName: originalToolName,\n serverName: logServerName,\n arguments: arguments_,\n result: result,\n success: result.isError !== true,\n duration: Date.now() - startTime,\n });\n\n return result as ToolCallResult;\n } catch (error) {\n // 记录失败的工具调用\n this.toolCallLogger.recordToolCall({\n toolName: originalToolName,\n serverName: logServerName,\n arguments: arguments_,\n result: null,\n success: false,\n duration: Date.now() - startTime,\n error: error instanceof Error ? error.message : String(error),\n });\n\n // 更新失败统计\n if (this.customMCPHandler.hasTool(toolName)) {\n const customTool = this.customMCPHandler.getToolInfo(toolName);\n if (customTool?.handler?.type === \"mcp\") {\n this.updateToolStatsSafe(\n toolName,\n customTool.handler.config.serviceName,\n customTool.handler.config.toolName,\n false\n );\n } else {\n this.updateToolStatsSafe(toolName, \"customMCP\", toolName, false);\n console.error(\n `[MCPManager] CustomMCP 工具 ${toolName} 调用失败:`,\n (error as Error).message\n );\n }\n } else {\n const toolInfo = this.tools.get(toolName);\n if (toolInfo) {\n this.updateToolStatsSafe(\n toolName,\n toolInfo.serviceName,\n toolInfo.originalName,\n false\n );\n console.error(\n `[MCPManager] 工具 ${toolName} 调用失败:`,\n (error as Error).message\n );\n }\n }\n\n throw error;\n }\n }\n\n /**\n * 更新工具调用统计信息的通用方法\n * @param toolName 工具名称\n * @param serviceName 服务名称\n * @param originalToolName 原始工具名称\n * @param isSuccess 是否调用成功\n * @private\n */\n private async updateToolStats(\n toolName: string,\n serviceName: string,\n originalToolName: string,\n isSuccess: boolean\n ): Promise<void> {\n try {\n const currentTime = new Date().toISOString();\n\n if (isSuccess) {\n // 成功调用:更新使用统计\n await this.updateCustomMCPToolStats(toolName, currentTime);\n\n // 如果是 MCP 服务工具,同时更新 mcpServerConfig 配置(双写机制)\n if (serviceName !== \"customMCP\") {\n await this.updateMCPServerToolStats(\n serviceName,\n originalToolName,\n currentTime\n );\n }\n\n console.debug(`[MCPManager] 已更新工具 ${toolName} 的统计信息`);\n } else {\n // 失败调用:只更新最后使用时间\n await this.updateCustomMCPToolLastUsedTime(toolName, currentTime);\n\n // 如果是 MCP 服务工具,同时更新 mcpServerConfig 配置(双写机制)\n if (serviceName !== \"customMCP\") {\n await this.updateMCPServerToolLastUsedTime(\n serviceName,\n originalToolName,\n currentTime\n );\n }\n\n console.debug(\"[MCPManager] 已更新工具的失败调用统计信息\", {\n toolName,\n });\n }\n } catch (error) {\n console.error(\"[MCPManager] 更新工具统计信息失败:\", { toolName, error });\n throw error;\n }\n }\n\n /**\n * 统一的统计更新处理方法(带错误处理)\n * @param toolName 工具名称\n * @param serviceName 服务名称\n * @param originalToolName 原始工具名称\n * @param isSuccess 是否调用成功\n * @private\n */\n private async updateToolStatsSafe(\n toolName: string,\n serviceName: string,\n originalToolName: string,\n isSuccess: boolean\n ): Promise<void> {\n try {\n await this.updateToolStats(\n toolName,\n serviceName,\n originalToolName,\n isSuccess\n );\n } catch (error) {\n const action = isSuccess ? \"统计信息\" : \"失败统计信息\";\n console.warn(\"[MCPManager] 更新工具统计信息失败:\", {\n toolName,\n action,\n error,\n });\n // 统计更新失败不应该影响主流程,所以这里只记录警告\n }\n }\n\n /**\n * 更新 customMCP 工具统计信息\n * @param toolName 工具名称\n * @param currentTime 当前时间\n * @private\n */\n private async updateCustomMCPToolStats(\n toolName: string,\n currentTime: string\n ): Promise<void> {\n try {\n await configManager.updateToolUsageStatsWithLock(toolName, true);\n console.debug(`[MCPManager] 已更新 customMCP 工具 ${toolName} 使用统计`);\n } catch (error) {\n console.error(\n `[MCPManager] 更新 customMCP 工具 ${toolName} 统计失败:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * 更新 customMCP 工具最后使用时间\n * @param toolName 工具名称\n * @param currentTime 当前时间\n * @private\n */\n private async updateCustomMCPToolLastUsedTime(\n toolName: string,\n currentTime: string\n ): Promise<void> {\n try {\n await configManager.updateToolUsageStatsWithLock(toolName, false); // 只更新时间,不增加计数\n console.debug(\n `[MCPManager] 已更新 customMCP 工具 ${toolName} 最后使用时间`\n );\n } catch (error) {\n console.error(\n `[MCPManager] 更新 customMCP 工具 ${toolName} 最后使用时间失败:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * 更新 MCP 服务工具统计信息\n * @param serviceName 服务名称\n * @param toolName 工具名称\n * @param currentTime 当前时间\n * @private\n */\n private async updateMCPServerToolStats(\n serviceName: string,\n toolName: string,\n currentTime: string\n ): Promise<void> {\n try {\n await configManager.updateMCPServerToolStatsWithLock(\n serviceName,\n toolName,\n currentTime,\n true\n );\n console.debug(\n `[MCPManager] 已更新 MCP 服务工具 ${serviceName}/${toolName} 统计`\n );\n } catch (error) {\n console.error(\n `[MCPManager] 更新 MCP 服务工具 ${serviceName}/${toolName} 统计失败:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * 更新 MCP 服务工具最后使用时间\n * @param serviceName 服务名称\n * @param toolName 工具名称\n * @param currentTime 当前时间\n * @private\n */\n private async updateMCPServerToolLastUsedTime(\n serviceName: string,\n toolName: string,\n currentTime: string\n ): Promise<void> {\n try {\n await configManager.updateMCPServerToolStatsWithLock(\n serviceName,\n toolName,\n currentTime,\n false\n ); // 只更新时间,不增加计数\n console.debug(\n `[MCPManager] 已更新 MCP 服务工具 ${serviceName}/${toolName} 最后使用时间`\n );\n } catch (error) {\n console.error(\n `[MCPManager] 更新 MCP 服务工具 ${serviceName}/${toolName} 最后使用时间失败:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * 调用 MCP 工具(用于从 mcpServerConfig 同步的工具)\n * @param toolName 工具名称\n * @param config MCP handler 配置\n * @param arguments_ 工具参数\n */\n private async callMCPTool(\n toolName: string,\n config: { serviceName: string; toolName: string },\n arguments_: Record<string, unknown>\n ): Promise<ToolCallResult> {\n const { serviceName, toolName: originalToolName } = config;\n\n console.debug(\n `[MCPManager] 调用 MCP 同步工具 ${toolName} -> ${serviceName}.${originalToolName}`\n );\n\n const service = this.services.get(serviceName);\n if (!service) {\n throw new Error(`服务 ${serviceName} 不可用`);\n }\n\n if (!service.isConnected()) {\n throw new Error(`服务 ${serviceName} 未连接`);\n }\n\n try {\n const result = await service.callTool(originalToolName, arguments_ || {});\n console.debug(`[MCPManager] MCP 同步工具 ${toolName} 调用成功`);\n return result as ToolCallResult;\n } catch (error) {\n console.error(\n `[MCPManager] MCP 同步工具 ${toolName} 调用失败:`,\n (error as Error).message\n );\n throw error;\n }\n }\n\n /**\n * 检查是否存在指定工具(包括标准 MCP 工具和 customMCP 工具)\n */\n hasTool(toolName: string): boolean {\n // 检查是否是 customMCP 工具\n if (this.customMCPHandler.hasTool(toolName)) {\n return true;\n }\n\n // 检查是否是标准 MCP 工具\n return this.tools.has(toolName);\n }\n\n /**\n * 停止所有服务\n */\n async stopAllServices(): Promise<void> {\n console.info(\"[MCPManager] 正在停止所有 MCP 服务...\");\n\n // 停止所有服务重试\n this.stopAllServiceRetries();\n\n // 停止所有服务实例\n for (const [serviceName, service] of this.services) {\n try {\n await service.disconnect();\n console.info(`[MCPManager] ${serviceName} 服务已停止`);\n } catch (error) {\n console.error(\n `[MCPManager] 停止 ${serviceName} 服务失败:`,\n (error as Error).message\n );\n }\n }\n\n // 清理 CustomMCP 处理器\n try {\n this.customMCPHandler.cleanup();\n console.info(\"[MCPManager] CustomMCP 处理器已清理\");\n } catch (error) {\n console.error(\"[MCPManager] CustomMCP 处理器清理失败:\", error);\n }\n\n // 清理统计更新锁\n try {\n configManager.clearAllStatsUpdateLocks();\n console.info(\"[MCPManager] 统计更新锁已清理\");\n } catch (error) {\n console.error(\"[MCPManager] 清理统计更新锁失败:\", error);\n }\n\n this.services.clear();\n this.tools.clear();\n\n console.info(\"[MCPManager] 所有 MCP 服务已停止\");\n }\n\n /**\n * 获取服务器状态(兼容 UnifiedServerStatus 格式)\n */\n getStatus(): UnifiedServerStatus {\n return this.getUnifiedStatus();\n }\n\n /**\n * 获取统计更新监控信息\n */\n getStatsUpdateInfo(): {\n activeLocks: string[];\n totalLocks: number;\n } {\n try {\n const activeLocks = configManager.getStatsUpdateLocks();\n return {\n activeLocks,\n totalLocks: activeLocks.length,\n };\n } catch (error) {\n console.warn(\"[MCPManager] 获取统计更新监控信息失败:\", error);\n return {\n activeLocks: [],\n totalLocks: 0,\n };\n }\n }\n\n /**\n * 获取指定服务实例\n */\n getService(name: string): MCPService | undefined {\n return this.services.get(name);\n }\n\n /**\n * 获取所有已连接的服务名称\n */\n getConnectedServices(): string[] {\n const connectedServices: string[] = [];\n for (const [serviceName, service] of this.services) {\n if (service.isConnected()) {\n connectedServices.push(serviceName);\n }\n }\n return connectedServices;\n }\n\n /**\n * 刷新CustomMCPHandler的私有方法\n */\n private async refreshCustomMCPHandler(): Promise<void> {\n try {\n console.debug(\"重新初始化CustomMCPHandler\");\n this.customMCPHandler.initialize();\n console.debug(\"CustomMCPHandler重新初始化完成\");\n } catch (error) {\n console.error(\"CustomMCPHandler重新初始化失败:\", error);\n throw error;\n }\n }\n\n /**\n * 公开的CustomMCPHandler刷新方法,供外部调用\n */\n async refreshCustomMCPHandlerPublic(): Promise<void> {\n return this.refreshCustomMCPHandler();\n }\n\n /**\n * 获取所有服务实例\n */\n getAllServices(): Map<string, MCPService> {\n return new Map(this.services);\n }\n\n /**\n * 获取 CustomMCP 处理器实例\n */\n getCustomMCPHandler(): CustomMCPHandler {\n return this.customMCPHandler;\n }\n\n /**\n * 检查指定的 customMCP 工具是否存在\n * @param toolName 工具名称\n * @returns 如果工具存在返回 true,否则返回 false\n */\n hasCustomMCPTool(toolName: string): boolean {\n try {\n return this.customMCPHandler.hasTool(toolName);\n } catch (error) {\n console.warn(\n `[MCPManager] 检查 CustomMCP 工具 ${toolName} 是否存在失败:`,\n error\n );\n // 异常情况下返回 false,表示工具不存在\n return false;\n }\n }\n\n /**\n * 获取所有 customMCP 工具列表\n * @returns customMCP 工具数组\n */\n getCustomMCPTools(): Tool[] {\n try {\n return this.customMCPHandler.getTools();\n } catch (error) {\n console.warn(\n \"[MCPManager] 获取 CustomMCP 工具列表失败,返回空数组:\",\n error\n );\n // 异常情况下返回空数组,避免影响调用方\n return [];\n }\n }\n\n /**\n * 检查是否为 ModelScope 服务\n * 统一使用 ConfigAdapter 的 isModelScopeURL 函数\n */\n private isModelScopeService(config: MCPServiceConfig): boolean {\n return config.url ? isModelScopeURL(config.url) : false;\n }\n\n /**\n * 处理 ModelScope 服务认证\n * 智能检查现有认证信息,按优先级处理\n */\n private handleModelScopeAuth(\n originalConfig: MCPServiceConfig,\n enhancedConfig: MCPServiceConfig\n ): void {\n // 1. 检查是否已有 Authorization header\n const existingAuthHeader = originalConfig.headers?.Authorization;\n\n if (existingAuthHeader) {\n // 已有认证信息,直接使用\n console.info(\n `[MCPManager] 服务 ${originalConfig.name} 使用已有的 Authorization header`\n );\n return;\n }\n\n // 2. 检查全局 ModelScope API Key\n const modelScopeApiKey = configManager.getModelScopeApiKey();\n\n if (modelScopeApiKey) {\n // 注入全局 API Key\n enhancedConfig.apiKey = modelScopeApiKey;\n console.info(\n `[MCPManager] 为 ${originalConfig.name} 服务添加 ModelScope API Key`\n );\n return;\n }\n\n // 3. 无法获取认证信息,提供详细错误信息\n const serviceUrl = originalConfig.url || \"未知\";\n const serviceName = originalConfig.name || \"未知\";\n\n throw new Error(\n `ModelScope 服务 \"${serviceName}\" 需要认证信息,但未找到有效的认证配置。服务 URL: ${serviceUrl}请选择以下任一方式配置认证:1. 在服务配置中添加 headers.Authorization2. 或者在全局配置中设置 modelscope.apiKey3. 或者设置环境变量 MODELSCOPE_API_TOKEN获取 ModelScope API Key: https://modelscope.cn/my?myInfo=true`\n );\n }\n\n /**\n * 增强服务配置\n * 根据服务类型添加必要的全局配置,智能处理认证信息\n */\n private enhanceServiceConfig(config: MCPServiceConfig): MCPServiceConfig {\n const enhancedConfig = { ...config };\n\n try {\n // 处理 ModelScope 服务(智能认证检查)\n if (this.isModelScopeService(config)) {\n this.handleModelScopeAuth(config, enhancedConfig);\n }\n\n return enhancedConfig;\n } catch (error) {\n console.error(`[MCPManager] 配置增强失败: ${config.name}`, error);\n throw error;\n }\n }\n\n /**\n * 添加服务配置(重载方法以支持两种调用方式)\n */\n addServiceConfig(name: string, config: MCPServiceConfig): void;\n addServiceConfig(config: MCPServiceConfig): void;\n addServiceConfig(\n nameOrConfig: string | MCPServiceConfig,\n config?: MCPServiceConfig\n ): void {\n let finalConfig: MCPServiceConfig;\n let serviceName: string;\n\n if (typeof nameOrConfig === \"string\" && config) {\n // 两参数版本\n serviceName = nameOrConfig;\n finalConfig = config;\n } else if (typeof nameOrConfig === \"object\") {\n // 单参数版本\n serviceName = nameOrConfig.name;\n finalConfig = nameOrConfig;\n } else {\n throw new Error(\"Invalid arguments for addServiceConfig\");\n }\n\n // 增强配置\n const enhancedConfig = this.enhanceServiceConfig(finalConfig);\n\n // 存储增强后的配置\n this.configs[serviceName] = enhancedConfig;\n console.debug(`[MCPManager] 已添加服务配置: ${serviceName}`);\n }\n\n /**\n * 更新服务配置\n */\n updateServiceConfig(name: string, config: MCPServiceConfig): void {\n // 增强配置\n const enhancedConfig = this.enhanceServiceConfig(config);\n\n // 存储增强后的配置\n this.configs[name] = enhancedConfig;\n console.debug(`[MCPManager] 已更新并增强服务配置: ${name}`);\n }\n\n /**\n * 移除服务配置\n */\n removeServiceConfig(name: string): void {\n delete this.configs[name];\n console.debug(`[MCPManager] 已移除服务配置: ${name}`);\n }\n\n /**\n * 同步工具配置到配置文件\n * 实现自动同步 MCP 服务工具配置到 xiaozhi.config.json\n */\n private async syncToolsConfigToFile(): Promise<void> {\n try {\n console.debug(\"[MCPManager] 开始同步工具配置到配置文件\");\n\n // 获取当前配置文件中的 mcpServerConfig\n const currentServerConfigs = configManager.getMcpServerConfig();\n\n // 遍历所有已连接的服务\n for (const [serviceName, service] of this.services) {\n if (!service.isConnected()) {\n continue;\n }\n\n const tools = service.getTools();\n if (tools.length === 0) {\n continue;\n }\n\n // 获取当前服务在配置文件中的工具配置\n const currentToolsConfig =\n currentServerConfigs[serviceName]?.tools || {};\n\n // 构建新的工具配置\n const newToolsConfig: Record<string, MCPToolConfig> = {};\n\n for (const tool of tools) {\n const currentToolConfig = currentToolsConfig[tool.name];\n\n // 如果工具已存在,保留用户设置的 enable 状态,但更新描述\n if (currentToolConfig) {\n newToolsConfig[tool.name] = {\n ...currentToolConfig,\n description:\n tool.description || currentToolConfig.description || \"\",\n };\n } else {\n // 新工具,默认启用\n newToolsConfig[tool.name] = {\n description: tool.description || \"\",\n enable: true,\n };\n }\n }\n\n // 检查是否有工具被移除(在配置文件中存在但在当前工具列表中不存在)\n const currentToolNames = tools.map((t) => t.name);\n const configToolNames = Object.keys(currentToolsConfig);\n const removedTools = configToolNames.filter(\n (name) => !currentToolNames.includes(name)\n );\n\n if (removedTools.length > 0) {\n console.info(\n `[MCPManager] 检测到服务 ${serviceName} 移除了 ${\n removedTools.length\n } 个工具: ${removedTools.join(\", \")}`\n );\n }\n\n // 检查配置是否有变化\n const hasChanges = this.hasToolsConfigChanged(\n currentToolsConfig,\n newToolsConfig\n );\n\n if (hasChanges) {\n // 更新配置文件\n configManager.updateServerToolsConfig(serviceName, newToolsConfig);\n\n const addedTools = Object.keys(newToolsConfig).filter(\n (name) => !currentToolsConfig[name]\n );\n const updatedTools = Object.keys(newToolsConfig).filter((name) => {\n const current = currentToolsConfig[name];\n const updated = newToolsConfig[name];\n return current && current.description !== updated.description;\n });\n\n console.debug(`[MCPManager] 已同步服务 ${serviceName} 的工具配置:`);\n if (addedTools.length > 0) {\n console.debug(` - 新增工具: ${addedTools.join(\", \")}`);\n }\n if (updatedTools.length > 0) {\n console.debug(` - 更新工具: ${updatedTools.join(\", \")}`);\n }\n if (removedTools.length > 0) {\n console.debug(` - 移除工具: ${removedTools.join(\", \")}`);\n }\n }\n }\n\n console.debug(\"[MCPManager] 工具配置同步完成\");\n } catch (error) {\n console.error(\"[MCPManager] 同步工具配置到配置文件失败:\", error);\n // 不抛出错误,避免影响服务正常运行\n }\n }\n\n /**\n * 检查工具配置是否有变化\n */\n private hasToolsConfigChanged(\n currentConfig: Record<string, MCPToolConfig>,\n newConfig: Record<string, MCPToolConfig>\n ): boolean {\n const currentKeys = Object.keys(currentConfig);\n const newKeys = Object.keys(newConfig);\n\n // 检查工具数量是否变化\n if (currentKeys.length !== newKeys.length) {\n return true;\n }\n\n // 检查是否有新增或删除的工具\n const addedTools = newKeys.filter((key) => !currentKeys.includes(key));\n const removedTools = currentKeys.filter((key) => !newKeys.includes(key));\n\n if (addedTools.length > 0 || removedTools.length > 0) {\n return true;\n }\n\n // 检查现有工具的描述是否有变化\n for (const toolName of currentKeys) {\n const currentTool = currentConfig[toolName];\n const newTool = newConfig[toolName];\n\n if (currentTool.description !== newTool.description) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * 安排失败服务的重试\n * @param failedServices 失败的服务列表\n */\n private scheduleFailedServicesRetry(failedServices: string[]): void {\n if (failedServices.length === 0) return;\n\n // 记录重试安排\n console.info(`[MCPManager] 安排 ${failedServices.length} 个失败服务的重试`);\n\n // 初始重试延迟:30秒\n const initialDelay = 30000;\n\n for (const serviceName of failedServices) {\n this.failedServices.add(serviceName);\n this.scheduleServiceRetry(serviceName, initialDelay);\n }\n }\n\n /**\n * 安排单个服务的重试\n * @param serviceName 服务名称\n * @param delay 延迟时间(毫秒)\n */\n private scheduleServiceRetry(serviceName: string, delay: number): void {\n // 清除现有定时器\n const existingTimer = this.retryTimers.get(serviceName);\n if (existingTimer) {\n clearTimeout(existingTimer);\n this.retryTimers.delete(serviceName);\n }\n\n console.debug(`[MCPManager] 安排服务 ${serviceName} 在 ${delay}ms 后重试`);\n\n const timer = setTimeout(async () => {\n this.retryTimers.delete(serviceName);\n await this.retryFailedService(serviceName);\n }, delay);\n\n this.retryTimers.set(serviceName, timer);\n }\n\n /**\n * 重试失败的服务\n * @param serviceName 服务名称\n */\n private async retryFailedService(serviceName: string): Promise<void> {\n if (!this.failedServices.has(serviceName)) {\n return; // 服务已经成功启动或不再需要重试\n }\n\n try {\n await this.startService(serviceName);\n\n // 重试成功\n this.failedServices.delete(serviceName);\n console.info(`[MCPManager] 服务 ${serviceName} 重试启动成功`);\n\n // 重新初始化CustomMCPHandler以包含新启动的服务工具\n try {\n await this.refreshCustomMCPHandlerPublic();\n } catch (error) {\n console.error(\"[MCPManager] 刷新CustomMCPHandler失败:\", error);\n }\n } catch (error) {\n console.error(\n `[MCPManager] 服务 ${serviceName} 重试启动失败:`,\n (error as Error).message\n );\n\n // 指数退避重试策略:延迟时间翻倍,最大不超过5分钟\n const currentDelay = this.getRetryDelay(serviceName);\n const nextDelay = Math.min(currentDelay * 2, 300000); // 最大5分钟\n\n console.debug(\n `[MCPManager] 服务 ${serviceName} 下次重试将在 ${nextDelay}ms 后进行`\n );\n\n this.scheduleServiceRetry(serviceName, nextDelay);\n }\n }\n\n /**\n * 获取当前重试延迟时间\n * @param serviceName 服务名称\n * @returns 当前延迟时间\n */\n private getRetryDelay(serviceName: string): number {\n // 这里可以实现更复杂的状态跟踪来计算准确的延迟\n // 简化实现:返回一个基于服务名称的哈希值的初始延迟\n const hash = serviceName\n .split(\"\")\n .reduce((acc, char) => acc + char.charCodeAt(0), 0);\n return 30000 + (hash % 60000); // 30-90秒之间的初始延迟\n }\n\n /**\n * 停止指定服务的重试\n * @param serviceName 服务名称\n */\n public stopServiceRetry(serviceName: string): void {\n const timer = this.retryTimers.get(serviceName);\n if (timer) {\n clearTimeout(timer);\n this.retryTimers.delete(serviceName);\n console.debug(`[MCPManager] 已停止服务 ${serviceName} 的重试`);\n }\n this.failedServices.delete(serviceName);\n }\n\n /**\n * 停止所有服务的重试\n */\n public stopAllServiceRetries(): void {\n console.info(\"[MCPManager] 停止所有服务重试\");\n\n for (const [serviceName, timer] of this.retryTimers) {\n clearTimeout(timer);\n console.debug(`[MCPManager] 已停止服务 ${serviceName} 的重试`);\n }\n\n this.retryTimers.clear();\n this.failedServices.clear();\n }\n\n /**\n * 获取失败服务列表\n * @returns 失败的服务名称数组\n */\n public getFailedServices(): string[] {\n return Array.from(this.failedServices);\n }\n\n /**\n * 检查服务是否失败\n * @param serviceName 服务名称\n * @returns 如果服务失败返回true\n */\n public isServiceFailed(serviceName: string): boolean {\n return this.failedServices.has(serviceName);\n }\n\n /**\n * 获取重试统计信息\n * @returns 重试统计信息\n */\n public getRetryStats(): {\n failedServices: string[];\n activeRetries: string[];\n totalFailed: number;\n totalActiveRetries: number;\n } {\n return {\n failedServices: Array.from(this.failedServices),\n activeRetries: Array.from(this.retryTimers.keys()),\n totalFailed: this.failedServices.size,\n totalActiveRetries: this.retryTimers.size,\n };\n }\n\n // ===== 传输适配器管理方法(从 UnifiedMCPServer 移入) =====\n\n /**\n * 注册传输适配器\n * @param name 传输适配器名称\n * @param adapter 传输适配器实例\n */\n public async registerTransport(\n name: string,\n adapter: TransportAdapter\n ): Promise<void> {\n if (this.transportAdapters.has(name)) {\n throw new Error(`传输适配器 ${name} 已存在`);\n }\n\n console.info(`注册传输适配器: ${name}`);\n\n try {\n await adapter.initialize();\n this.transportAdapters.set(name, adapter);\n\n console.info(`传输适配器 ${name} 注册成功`);\n this.emit(\"transportRegistered\", { name, adapter });\n } catch (error) {\n console.error(`注册传输适配器 ${name} 失败`, error);\n throw error;\n }\n }\n\n /**\n * 启动所有传输适配器\n *\n * 改进的错误处理策略:\n * - 单个适配器启动失败不会中断其他适配器的启动\n * - 记录失败的适配器,但继续启动其他适配器\n * - 如果所有适配器都失败,则抛出错误\n * - 如果部分适配器成功,则记录警告但继续运行\n */\n public async startTransports(): Promise<void> {\n console.info(\"启动所有传输适配器\");\n\n const successfulAdapters: string[] = [];\n const failedAdapters: string[] = [];\n\n for (const [name, adapter] of this.transportAdapters) {\n try {\n await adapter.start();\n successfulAdapters.push(name);\n console.info(`传输适配器 ${name} 启动成功`);\n } catch (error) {\n failedAdapters.push(name);\n console.error(`传输适配器 ${name} 启动失败`, error);\n // 不立即抛出错误,继续尝试启动其他适配器\n }\n }\n\n // 评估启动结果\n if (successfulAdapters.length === 0 && failedAdapters.length > 0) {\n // 所有适配器都失败了\n const errorMessage = `所有传输适配器启动失败,失败的适配器: ${failedAdapters.join(\n \", \"\n )}`;\n console.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n if (failedAdapters.length > 0) {\n // 部分适配器失败\n console.warn(\n `部分传输适配器启动失败,成功: ${successfulAdapters.join(\n \", \"\n )}, 失败: ${failedAdapters.join(\", \")}`\n );\n // 继续运行,因为至少有一个适配器成功\n }\n\n console.info(\n `传输适配器启动完成,成功: ${successfulAdapters.length}, 失败: ${failedAdapters.length}`\n );\n }\n\n /**\n * 停止所有传输适配器\n */\n public async stopTransports(): Promise<void> {\n console.info(\"停止所有传输适配器\");\n\n try {\n for (const [name, adapter] of this.transportAdapters) {\n try {\n await adapter.stop();\n console.info(`传输适配器 ${name} 停止成功`);\n } catch (error) {\n console.error(`传输适配器 ${name} 停止失败`, error);\n }\n }\n } catch (error) {\n console.error(\"传输适配器停止失败\", error);\n throw error;\n }\n }\n\n /**\n * 获取所有传输适配器\n * @returns 传输适配器映射表\n */\n public getTransportAdapters(): Map<string, TransportAdapter> {\n return new Map(this.transportAdapters);\n }\n\n /**\n * 获取消息处理器(供外部使用)\n * @returns 消息处理器实例\n */\n public getMessageHandler(): MCPMessageHandler {\n return this.messageHandler;\n }\n\n /**\n * 启动管理器(包含服务和传输)\n */\n public async start(): Promise<void> {\n if (this.isRunning) {\n throw new Error(\"服务器已在运行\");\n }\n\n console.info(\"启动 MCP 服务管理器\");\n\n try {\n await this.startAllServices();\n await this.startTransports();\n this.isRunning = true;\n\n console.info(\"MCP 服务管理器启动成功\");\n this.emit(\"started\");\n } catch (error) {\n console.error(\"MCP 服务管理器启动失败\", error);\n throw error;\n }\n }\n\n /**\n * 停止管理器(包含传输和服务)\n */\n public async stop(): Promise<void> {\n if (!this.isRunning) {\n return;\n }\n\n console.info(\"停止 MCP 服务管理器\");\n\n try {\n await this.stopTransports();\n await this.stopAllServices();\n this.isRunning = false;\n\n console.info(\"MCP 服务管理器停止成功\");\n this.emit(\"stopped\");\n } catch (error) {\n console.error(\"MCP 服务管理器停止失败\", error);\n throw error;\n }\n }\n\n /**\n * 获取所有连接信息\n * @returns 连接信息列表\n */\n public getAllConnections(): Array<{\n id: string;\n name: string;\n state: ConnectionState;\n }> {\n const connections: Array<{\n id: string;\n name: string;\n state: ConnectionState;\n }> = [];\n\n // 收集服务连接\n for (const [serviceName, service] of this.services) {\n if (service.isConnected()) {\n connections.push({\n id: `service-${serviceName}`,\n name: serviceName,\n state: ConnectionState.CONNECTED,\n });\n }\n }\n\n // 收集传输适配器连接\n for (const [adapterName, adapter] of this.transportAdapters) {\n connections.push({\n id: adapter.getConnectionId(),\n name: adapterName,\n state: adapter.getState(),\n });\n }\n\n return connections;\n }\n\n /**\n * 获取活跃连接数\n * @returns 活跃连接数量\n */\n public getActiveConnectionCount(): number {\n return this.getAllConnections().filter(\n (conn) => conn.state === ConnectionState.CONNECTED\n ).length;\n }\n\n // ===== 从 UnifiedMCPServer 移入的方法 =====\n\n /**\n * 获取服务器状态(从 UnifiedMCPServer 移入)\n */\n getUnifiedStatus(): UnifiedServerStatus {\n const serviceStatus = this.getServiceManagerStatus();\n return {\n isRunning: this.isRunning,\n serviceStatus,\n transportCount: this.getTransportAdapters().size,\n activeConnections: this.getActiveConnectionCount(),\n config: this.config,\n // 便捷访问属性\n services: serviceStatus.services,\n totalTools: serviceStatus.totalTools,\n availableTools: serviceStatus.availableTools,\n };\n }\n\n /**\n * 获取管理器状态(原有的 getStatus 方法重命名)\n */\n getServiceManagerStatus(): ManagerStatus {\n // 计算总工具数量(包括 customMCP 工具,添加异常处理)\n let customMCPToolCount = 0;\n let customToolNames: string[] = [];\n\n try {\n customMCPToolCount = this.customMCPHandler.getToolCount();\n customToolNames = this.customMCPHandler.getToolNames();\n console.debug(\n `[MCPManager] 成功获取 customMCP 状态: ${customMCPToolCount} 个工具`\n );\n } catch (error) {\n console.warn(\n \"[MCPManager] 获取 CustomMCP 状态失败,将只包含标准 MCP 工具:\",\n error\n );\n // 异常情况下,customMCP 工具数量为0,不影响标准 MCP 工具\n customMCPToolCount = 0;\n customToolNames = [];\n }\n\n const totalTools = this.tools.size + customMCPToolCount;\n\n // 获取所有可用工具名称\n const standardToolNames = Array.from(this.tools.keys());\n const availableTools = [...standardToolNames, ...customToolNames];\n\n const status: ManagerStatus = {\n services: {},\n totalTools,\n availableTools,\n };\n\n // 添加标准 MCP 服务状态\n for (const [serviceName, service] of this.services) {\n const serviceStatus = service.getStatus();\n status.services[serviceName] = {\n connected: serviceStatus.connected,\n clientName: `xiaozhi-${serviceName}-client`,\n };\n }\n\n // 添加 CustomMCP 服务状态\n if (customMCPToolCount > 0) {\n status.services.customMCP = {\n connected: true, // CustomMCP 工具总是可用的\n clientName: \"xiaozhi-customMCP-handler\",\n };\n }\n\n return status;\n }\n\n /**\n * 检查服务器是否正在运行(从 UnifiedMCPServer 移入)\n */\n isServerRunning(): boolean {\n return this.isRunning;\n }\n\n /**\n * 类型守卫:检查是否为 UnifiedServerConfig\n */\n private isUnifiedServerConfig(\n configs: unknown\n ): configs is UnifiedServerConfig {\n return (\n configs !== null && typeof configs === \"object\" && \"configs\" in configs\n );\n }\n\n /**\n * 消息路由核心功能(从 UnifiedMCPServer 移入)\n */\n async routeMessage(message: MCPMessage): Promise<MCPMessage | null> {\n const response = await this.messageHandler.handleMessage(message);\n // 如果响应是 null,直接返回\n if (response === null) {\n return null;\n }\n // 将 MCPResponse 转换为 MCPMessage 格式\n return {\n jsonrpc: \"2.0\",\n method: \"response\", // 标识这是一个响应消息\n params: response,\n id: response.id, // 使用响应中的ID\n };\n }\n\n // ===== 向后兼容方法 =====\n\n /**\n * 初始化方法(向后兼容,实际调用 start)\n *\n * 注意:此方法仅为向后兼容而保留\n * 实际功能:调用 start() 方法并设置 isRunning 状态\n * 建议新代码直接使用 start() 方法\n */\n async initialize(): Promise<void> {\n // 为了向后兼容,初始化时调用 start\n // 会设置 isRunning 状态为 true\n await this.start();\n }\n\n /**\n * 获取工具注册表(向后兼容,返回自身)\n */\n getToolRegistry(): MCPServiceManager {\n return this;\n }\n\n /**\n * 获取连接管理器(向后兼容,返回自身)\n */\n getConnectionManager(): MCPServiceManager {\n return this;\n }\n}\n\nexport default MCPServiceManager;\n","/**\n * 配置适配器\n * 将旧的配置格式转换为新的 MCPServiceConfig 格式,确保向后兼容性\n */\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport type {\n LocalMCPServerConfig,\n MCPServerConfig,\n SSEMCPServerConfig,\n StreamableHTTPMCPServerConfig,\n} from \"@/lib/config/manager.js\";\nimport type { MCPServiceConfig } from \"@/lib/mcp/types\";\nimport { MCPTransportType } from \"@/lib/mcp/types\";\nimport { inferTransportTypeFromUrl } from \"@/lib/mcp/utils\";\nimport { TypeFieldNormalizer } from \"@utils/TypeFieldNormalizer.js\";\n\n/**\n * 配置验证错误类\n */\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly configName?: string\n ) {\n super(message);\n this.name = \"ConfigValidationError\";\n }\n}\n\n/**\n * 将旧的 MCPServerConfig 转换为新的 MCPServiceConfig\n */\nexport function convertLegacyToNew(\n serviceName: string,\n legacyConfig: MCPServerConfig\n): MCPServiceConfig {\n console.log(\"转换配置\", { serviceName, legacyConfig });\n\n try {\n // 验证输入参数\n if (!serviceName || typeof serviceName !== \"string\") {\n throw new ConfigValidationError(\"服务名称必须是非空字符串\");\n }\n\n if (!legacyConfig || typeof legacyConfig !== \"object\") {\n throw new ConfigValidationError(\"配置对象不能为空\", serviceName);\n }\n\n // 首先标准化配置中的 type 字段\n const normalizedConfig = TypeFieldNormalizer.normalizeTypeField(\n legacyConfig\n ) as MCPServerConfig;\n\n // 根据配置类型进行转换\n const newConfig = convertByConfigType(serviceName, normalizedConfig);\n\n // 验证转换后的配置\n validateNewConfig(newConfig);\n\n console.log(\"配置转换成功\", { serviceName, type: newConfig.type });\n return newConfig;\n } catch (error) {\n console.error(\"配置转换失败\", { serviceName, error });\n throw error instanceof ConfigValidationError\n ? error\n : new ConfigValidationError(\n `配置转换失败: ${error instanceof Error ? error.message : String(error)}`,\n serviceName\n );\n }\n}\n\n/**\n * 根据配置类型进行转换\n */\nfunction convertByConfigType(\n serviceName: string,\n legacyConfig: MCPServerConfig\n): MCPServiceConfig {\n // 检查是否为本地 stdio 配置(最高优先级)\n if (isLocalConfig(legacyConfig)) {\n return convertLocalConfig(serviceName, legacyConfig);\n }\n\n // 检查是否有显式指定的类型\n if (\"type\" in legacyConfig) {\n switch (legacyConfig.type) {\n case \"sse\":\n return convertSSEConfig(serviceName, legacyConfig);\n case \"streamable-http\":\n return convertStreamableHTTPConfig(serviceName, legacyConfig);\n default:\n throw new ConfigValidationError(\n `不支持的传输类型: ${legacyConfig.type}`,\n serviceName\n );\n }\n }\n\n // 检查是否为网络配置(自动推断类型)\n if (\"url\" in legacyConfig) {\n // 如果 URL 是 undefined 或 null,抛出错误\n if (legacyConfig.url === undefined || legacyConfig.url === null) {\n throw new ConfigValidationError(\n \"网络配置必须包含有效的 url 字段\",\n serviceName\n );\n }\n\n // 先推断类型,然后根据推断的类型选择正确的转换函数\n const inferredType = inferTransportTypeFromUrl(legacyConfig.url || \"\");\n\n if (inferredType === MCPTransportType.SSE) {\n // 为SSE类型添加显式type字段\n const sseConfig = { ...legacyConfig, type: \"sse\" as const };\n return convertSSEConfig(serviceName, sseConfig);\n }\n // 为STREAMABLE_HTTP类型添加显式type字段\n const httpConfig = { ...legacyConfig, type: \"streamable-http\" as const };\n return convertStreamableHTTPConfig(serviceName, httpConfig);\n }\n\n throw new ConfigValidationError(\"无法识别的配置类型\", serviceName);\n}\n\n/** * 转换本地 stdio 配置 */\nfunction convertLocalConfig(\n serviceName: string,\n config: LocalMCPServerConfig\n): MCPServiceConfig {\n if (!config.command) {\n throw new ConfigValidationError(\n \"本地配置必须包含 command 字段\",\n serviceName\n );\n }\n\n // 获取用户的工作目录(优先使用环境变量,否则使用当前工作目录)\n const workingDir = process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n\n // 解析 args 中的相对路径\n const resolvedArgs = (config.args || []).map((arg) => {\n // 检查是否为相对路径(以 ./ 开头或不以 / 开头且包含文件扩展名)\n if (isRelativePath(arg)) {\n const resolvedPath = resolve(workingDir, arg);\n console.log(\"解析相对路径\", { arg, resolvedPath });\n return resolvedPath;\n }\n return arg;\n });\n\n return {\n name: serviceName,\n type: MCPTransportType.STDIO,\n command: config.command,\n args: resolvedArgs,\n env: config.env, // 传递环境变量\n timeout: 30000,\n };\n}\n\n/**\n * 转换 SSE 配置\n */\nfunction convertSSEConfig(\n serviceName: string,\n config: SSEMCPServerConfig\n): MCPServiceConfig {\n if (config.url === undefined || config.url === null) {\n throw new ConfigValidationError(\"SSE 配置必须包含 url 字段\", serviceName);\n }\n\n // 优先使用显式指定的类型,如果没有则进行推断\n const inferredType =\n config.type === \"sse\"\n ? MCPTransportType.SSE\n : inferTransportTypeFromUrl(config.url || \"\");\n const isModelScope = config.url ? isModelScopeURL(config.url) : false;\n\n const baseConfig: MCPServiceConfig = {\n name: serviceName,\n type: inferredType,\n url: config.url,\n timeout: 30000,\n headers: config.headers,\n };\n\n // 如果是 ModelScope 服务,添加特殊配置\n if (isModelScope) {\n baseConfig.modelScopeAuth = true;\n }\n\n console.log(\"SSE配置转换\", {\n serviceName,\n url: config.url,\n inferredType,\n isModelScope,\n });\n\n return baseConfig;\n}\n\n/**\n * 转换 Streamable HTTP 配置\n */\nfunction convertStreamableHTTPConfig(\n serviceName: string,\n config: StreamableHTTPMCPServerConfig\n): MCPServiceConfig {\n // 检查 URL 是否存在\n if (config.url === undefined || config.url === null) {\n throw new ConfigValidationError(\n \"STREAMABLE_HTTP 配置必须包含 url 字段\",\n serviceName\n );\n }\n\n const url = config.url || \"\";\n\n return {\n name: serviceName,\n type: MCPTransportType.STREAMABLE_HTTP,\n url,\n timeout: 30000,\n headers: config.headers,\n };\n}\n\n/**\n * 批量转换配置\n */\nexport function convertLegacyConfigBatch(\n legacyConfigs: Record<string, MCPServerConfig>\n): Record<string, MCPServiceConfig> {\n const newConfigs: Record<string, MCPServiceConfig> = {};\n const errors: Array<{ serviceName: string; error: Error }> = [];\n\n for (const [serviceName, legacyConfig] of Object.entries(legacyConfigs)) {\n try {\n newConfigs[serviceName] = convertLegacyToNew(serviceName, legacyConfig);\n } catch (error) {\n errors.push({\n serviceName,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n\n if (errors.length > 0) {\n const errorMessages = errors\n .map(({ serviceName, error }) => `${serviceName}: ${error.message}`)\n .join(\"; \");\n throw new ConfigValidationError(`批量配置转换失败: ${errorMessages}`);\n }\n\n console.log(\"批量配置转换成功\", { count: Object.keys(newConfigs).length });\n return newConfigs;\n}\n\n/**\n * 检查是否为相对路径\n */\nfunction isRelativePath(path: string): boolean {\n // 使用 Node.js 的 path.isAbsolute() 来正确检测绝对路径\n // 这个方法能够正确处理 Windows、macOS、Linux 三个平台的路径格式\n if (isAbsolute(path)) {\n return false; // 绝对路径不是相对路径\n }\n\n // 检查是否为相对路径的条件:\n // 1. 以 ./ 或 ../ 开头\n // 2. 包含常见的脚本文件扩展名(且不是绝对路径)\n if (path.startsWith(\"./\") || path.startsWith(\"../\")) {\n return true;\n }\n\n // 如果包含文件扩展名且不是绝对路径,也认为是相对路径\n if (/\\.(js|py|ts|mjs|cjs)$/i.test(path)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * 检查是否为本地配置\n */\nfunction isLocalConfig(\n config: MCPServerConfig\n): config is LocalMCPServerConfig {\n return \"command\" in config && typeof config.command === \"string\";\n}\n\n/**\n * 检查是否为 ModelScope URL\n */\nexport function isModelScopeURL(url: string): boolean {\n return url.includes(\"modelscope.net\") || url.includes(\"modelscope.cn\");\n}\n\n/**\n * 验证新配置格式\n */\nfunction validateNewConfig(config: MCPServiceConfig): void {\n if (!config.name || typeof config.name !== \"string\") {\n throw new ConfigValidationError(\"配置必须包含有效的 name 字段\");\n }\n\n if (config.type && !Object.values(MCPTransportType).includes(config.type)) {\n throw new ConfigValidationError(`无效的传输类型: ${config.type}`);\n }\n\n // 根据传输类型验证必需字段\n if (!config.type) {\n throw new ConfigValidationError(\"传输类型未指定,请检查配置或启用自动推断\");\n }\n\n switch (config.type) {\n case MCPTransportType.STDIO:\n if (!config.command) {\n throw new ConfigValidationError(\"STDIO 配置必须包含 command 字段\");\n }\n break;\n\n case MCPTransportType.SSE:\n // SSE 配置必须有 URL(即使是空字符串也会被推断为 STREAMABLE_HTTP)\n if (config.url === undefined || config.url === null) {\n throw new ConfigValidationError(\"SSE 配置必须包含 url 字段\");\n }\n break;\n\n case MCPTransportType.STREAMABLE_HTTP:\n // STREAMABLE_HTTP 配置允许空 URL,会在后续处理中设置默认值\n // 只有当 URL 完全不存在时才报错\n if (config.url === undefined || config.url === null) {\n throw new ConfigValidationError(\n \"STREAMABLE_HTTP 配置必须包含 url 字段\"\n );\n }\n break;\n\n default:\n throw new ConfigValidationError(`不支持的传输类型: ${config.type}`);\n }\n}\n\n/**\n * 获取配置类型描述\n */\nexport function getConfigTypeDescription(config: MCPServerConfig): string {\n if (isLocalConfig(config)) {\n return `本地进程 (${config.command})`;\n }\n\n if (\"url\" in config) {\n // 检查是否为显式 streamable-http 配置\n if (\"type\" in config && config.type === \"streamable-http\") {\n return `Streamable HTTP (${config.url})`;\n }\n\n // 检查是否为显式 sse 配置\n if (\"type\" in config && config.type === \"sse\") {\n const isModelScope = isModelScopeURL(config.url);\n return `SSE${isModelScope ? \" (ModelScope)\" : \"\"} (${config.url})`;\n }\n\n // 对于只有 url 的配置,根据路径推断类型\n const inferredType = inferTransportTypeFromUrl(config.url);\n const isModelScope = isModelScopeURL(config.url);\n\n if (inferredType === MCPTransportType.SSE) {\n return `SSE${isModelScope ? \" (ModelScope)\" : \"\"} (${config.url})`;\n }\n return `Streamable HTTP (${config.url})`;\n }\n\n return \"未知类型\";\n}\n","/**\n * MCP 核心库类型定义\n * 统一管理所有 MCP 相关的类型定义,避免重复定义和导入路径混乱\n */\n\nimport type { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport type { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport type { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\n\n// =========================\n// 1. 基础传输类型\n// =========================\n\n/**\n * MCP 传输层联合类型定义\n * 支持 STDIO、SSE、StreamableHTTP 三种传输协议\n */\nexport type MCPServerTransport =\n | StdioClientTransport\n | SSEClientTransport\n | StreamableHTTPClientTransport;\n\n/**\n * 通信方式枚举\n * 定义 MCP 支持的传输类型\n */\nexport enum MCPTransportType {\n STDIO = \"stdio\",\n SSE = \"sse\",\n STREAMABLE_HTTP = \"streamable-http\",\n}\n\n// =========================\n// 2. 配置接口类型\n// =========================\n\n/**\n * ModelScope SSE 自定义选项接口\n * 专门用于 ModelScope 相关的 SSE 配置\n */\nexport interface ModelScopeSSEOptions {\n eventSourceInit?: {\n fetch?: (\n url: string | URL | Request,\n init?: RequestInit\n ) => Promise<Response>;\n };\n requestInit?: RequestInit;\n}\n\n/**\n * MCP 服务配置接口\n * 包含所有 MCP 服务的配置选项\n */\nexport interface MCPServiceConfig {\n name: string;\n type?: MCPTransportType; // 现在是可选的,支持自动推断\n // stdio 配置\n command?: string;\n args?: string[];\n env?: Record<string, string>; // 环境变量配置\n // 网络配置\n url?: string;\n // 认证配置\n apiKey?: string;\n headers?: Record<string, string>;\n // ModelScope 特有配置\n modelScopeAuth?: boolean;\n customSSEOptions?: ModelScopeSSEOptions;\n // 超时配置\n timeout?: number;\n // 重试配置\n retryAttempts?: number;\n}\n\n// =========================\n// 3. 状态枚举类型\n// =========================\n\n/**\n * 连接状态枚举\n * 合并了 connection.ts 和 TransportAdapter.ts 中的定义\n */\nexport enum ConnectionState {\n DISCONNECTED = \"disconnected\",\n CONNECTING = \"connecting\",\n CONNECTED = \"connected\",\n RECONNECTING = \"reconnecting\",\n FAILED = \"failed\",\n ERROR = \"error\", // 从 TransportAdapter.ts 合并的额外状态\n}\n\n/**\n * MCP 服务状态接口\n * 描述 MCP 服务的运行时状态信息\n */\nexport interface MCPServiceStatus {\n name: string;\n connected: boolean;\n initialized: boolean;\n transportType: MCPTransportType;\n toolCount: number;\n lastError?: string;\n connectionState: ConnectionState;\n}\n\n// =========================\n// 4. 工具调用相关类型\n// =========================\n\n/**\n * 工具调用结果接口\n * 统一了 connection.ts、manager.ts 和 CustomMCPHandler.ts 中的定义\n */\nexport interface ToolCallResult {\n content: Array<{\n type: string;\n text: string;\n }>;\n isError?: boolean;\n}\n\n/**\n * JSON Schema 类型定义\n * 兼容 MCP SDK 的 JSON Schema 格式,同时支持更宽松的对象格式以保持向后兼容\n */\nexport type JSONSchema =\n | (Record<string, unknown> & {\n type: \"object\";\n properties?: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n })\n | Record<string, unknown>; // 允许更宽松的格式以保持向后兼容\n\n/**\n * 类型守卫:检查对象是否为有效的 MCP Tool JSON Schema\n */\nexport function isValidToolJSONSchema(obj: unknown): obj is {\n type: \"object\";\n properties?: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n} {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"type\" in obj &&\n (obj as { type?: unknown }).type === \"object\"\n );\n}\n\n/**\n * 确保对象符合 MCP Tool JSON Schema 格式\n * 如果不符合,会返回一个默认的空对象 schema\n */\nexport function ensureToolJSONSchema(schema: JSONSchema): {\n type: \"object\";\n properties?: Record<string, object>;\n required?: string[];\n additionalProperties?: boolean;\n} {\n if (isValidToolJSONSchema(schema)) {\n return schema as {\n type: \"object\";\n properties?: Record<string, object>;\n required?: string[];\n additionalProperties?: boolean;\n };\n }\n\n // 如果不符合标准格式,返回默认的空对象 schema\n return {\n type: \"object\",\n properties: {} as Record<string, object>,\n required: [],\n additionalProperties: true,\n };\n}\n\n/**\n * CustomMCP 工具类型定义\n * 统一了 manager.ts 和 configManager.ts 中的定义\n */\nexport interface CustomMCPTool {\n name: string;\n description?: string;\n inputSchema: JSONSchema;\n handler?: {\n type: string;\n config?: Record<string, unknown>;\n };\n}\n\n/**\n * 工具信息接口\n * 用于缓存工具映射关系,保持向后兼容性\n */\nexport interface ToolInfo {\n serviceName: string;\n originalName: string;\n tool: Tool;\n}\n\n// =========================\n// 5. 服务器配置类型\n// =========================\n\n/**\n * 统一服务器配置接口\n * 从 UnifiedMCPServer 移入,用于统一服务器配置管理\n */\nexport interface UnifiedServerConfig {\n name?: string;\n enableLogging?: boolean;\n logLevel?: string;\n configs?: Record<string, MCPServiceConfig>; // MCPService 配置\n}\n\n/**\n * 统一服务器状态接口\n * 从 UnifiedMCPServer 移入,用于统一服务器状态管理\n */\nexport interface UnifiedServerStatus {\n isRunning: boolean;\n serviceStatus: ManagerStatus;\n transportCount: number;\n activeConnections: number;\n config: UnifiedServerConfig;\n // 添加对 serviceStatus 的便捷访问属性\n services?: Record<string, MCPServiceConnectionStatus>;\n totalTools?: number;\n availableTools?: string[];\n}\n\n// =========================\n// 6. 管理器相关类型\n// =========================\n\n/**\n * MCP 服务连接状态接口\n * 重命名原 ServiceStatus 为 MCPServiceConnectionStatus 避免与 CLI 的 ServiceStatus 冲突\n */\nexport interface MCPServiceConnectionStatus {\n connected: boolean;\n clientName: string;\n}\n\n/**\n * 管理器状态接口\n * 描述 MCP 服务管理器的整体状态\n */\nexport interface ManagerStatus {\n services: Record<string, MCPServiceConnectionStatus>;\n totalTools: number;\n availableTools: string[];\n}\n\n// =========================\n// 7. 参数校验相关类型\n// =========================\n\n/**\n * 工具调用参数接口\n * 定义标准工具调用参数结构\n */\nexport interface ToolCallParams {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\n/**\n * 验证后的工具调用参数\n * 参数校验通过后的标准化参数结构\n */\nexport interface ValidatedToolCallParams {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\n/**\n * 工具调用验证选项\n * 提供灵活的参数校验配置\n */\nexport interface ToolCallValidationOptions {\n /** 是否验证工具名称,默认为 true */\n validateName?: boolean;\n /** 是否验证参数格式,默认为 true */\n validateArguments?: boolean;\n /** 是否允许空参数,默认为 true */\n allowEmptyArguments?: boolean;\n /** 自定义验证函数 */\n customValidator?: (params: ToolCallParams) => string | null;\n}\n\n/**\n * 工具调用错误码枚举\n * 统一的工具调用错误码定义\n */\nexport enum ToolCallErrorCode {\n /** 无效参数 */\n INVALID_PARAMS = -32602,\n /** 工具不存在 */\n TOOL_NOT_FOUND = -32601,\n /** 服务不可用 */\n SERVICE_UNAVAILABLE = -32001,\n /** 调用超时 */\n TIMEOUT = -32002,\n /** 工具执行错误 */\n TOOL_EXECUTION_ERROR = -32000,\n}\n\n/**\n * 工具调用错误类\n * 统一的工具调用错误处理\n */\nexport class ToolCallError extends Error {\n constructor(\n public code: ToolCallErrorCode,\n message: string,\n public data?: unknown\n ) {\n super(message);\n this.name = \"ToolCallError\";\n }\n}\n\n// =========================\n// 向后兼容性别名\n// =========================\n\n/**\n * 向后兼容:ServiceStatus 别名\n * 为了与现有代码保持兼容,暂时保留此别名\n * @deprecated 请使用 MCPServiceConnectionStatus\n */\nexport type ServiceStatus = MCPServiceConnectionStatus;\n","import { TypeFieldNormalizer } from \"@utils/TypeFieldNormalizer.js\";\nimport { MCPTransportType, ToolCallError, ToolCallErrorCode } from \"./types.js\";\nimport type {\n MCPServiceConfig,\n ToolCallParams,\n ToolCallValidationOptions,\n ValidatedToolCallParams,\n} from \"./types.js\";\n\n/**\n * 根据 URL 路径推断传输类型\n * 基于路径末尾推断,支持包含多个 / 的复杂路径\n *\n * @param url - 要推断的 URL\n * @param options - 可选配置项\n * @returns 推断出的传输类型\n */\nexport function inferTransportTypeFromUrl(\n url: string,\n options?: {\n serviceName?: string;\n }\n): MCPTransportType {\n try {\n const parsedUrl = new URL(url);\n const pathname = parsedUrl.pathname;\n\n // 检查路径末尾\n if (pathname.endsWith(\"/sse\")) {\n return MCPTransportType.SSE;\n }\n if (pathname.endsWith(\"/mcp\")) {\n return MCPTransportType.STREAMABLE_HTTP;\n }\n\n // 默认类型 - 使用 console 输出\n if (options?.serviceName) {\n console.info(\n `[MCP-${options.serviceName}] URL 路径 ${pathname} 不匹配特定规则,默认推断为 streamable-http 类型`\n );\n }\n return MCPTransportType.STREAMABLE_HTTP;\n } catch (error) {\n if (options?.serviceName) {\n console.warn(\n `[MCP-${options.serviceName}] URL 解析失败,默认推断为 streamable-http 类型`,\n error\n );\n }\n return MCPTransportType.STREAMABLE_HTTP;\n }\n}\n\n/**\n * 完整的配置类型推断(包括 command 字段)\n *\n * @param config - MCP 服务配置\n * @returns 完整的配置对象,包含推断出的类型\n */\nexport function inferTransportTypeFromConfig(\n config: MCPServiceConfig\n): MCPServiceConfig {\n // 如果已显式指定类型,先标准化然后返回\n if (config.type) {\n const normalizedConfig = TypeFieldNormalizer.normalizeTypeField(config);\n return normalizedConfig as MCPServiceConfig;\n }\n\n // 基于 command 字段推断\n if (config.command) {\n return {\n ...config,\n type: MCPTransportType.STDIO,\n };\n }\n\n // 基于 URL 字段推断(排除 null 和 undefined)\n if (config.url !== undefined && config.url !== null) {\n const inferredType = inferTransportTypeFromUrl(config.url, {\n serviceName: config.name,\n });\n return {\n ...config,\n type: inferredType,\n };\n }\n\n throw new Error(\n `无法为服务 ${config.name} 推断传输类型。请显式指定 type 字段,或提供 command/url 配置`\n );\n}\n\n// =========================\n// 参数校验工具函数\n// =========================\n\n/**\n * 验证工具调用参数\n * 对传入的参数进行完整性和格式验证\n *\n * @param params 待验证的参数\n * @param options 验证选项\n * @returns 验证后的参数\n * @throws ToolCallError 验证失败时抛出\n */\nexport function validateToolCallParams(\n params: unknown,\n options?: ToolCallValidationOptions\n): ValidatedToolCallParams {\n const opts = {\n validateName: true,\n validateArguments: true,\n allowEmptyArguments: true,\n ...options,\n };\n\n // 1. 验证参数必须是对象\n if (!params || typeof params !== \"object\") {\n throw new ToolCallError(\n ToolCallErrorCode.INVALID_PARAMS,\n \"请求参数必须是对象\"\n );\n }\n\n const paramsObj = params as Record<string, unknown>;\n\n // 2. 验证工具名称\n if (opts.validateName) {\n if (!paramsObj.name || typeof paramsObj.name !== \"string\") {\n throw new ToolCallError(\n ToolCallErrorCode.INVALID_PARAMS,\n \"工具名称必须是非空字符串\"\n );\n }\n }\n\n // 3. 验证工具参数格式\n if (\n opts.validateArguments &&\n paramsObj.arguments !== undefined &&\n paramsObj.arguments !== null\n ) {\n if (\n typeof paramsObj.arguments !== \"object\" ||\n Array.isArray(paramsObj.arguments)\n ) {\n throw new ToolCallError(\n ToolCallErrorCode.INVALID_PARAMS,\n \"工具参数必须是对象\"\n );\n }\n }\n\n // 4. 验证是否允许空参数\n if (\n !opts.allowEmptyArguments &&\n paramsObj.arguments !== undefined &&\n paramsObj.arguments !== null\n ) {\n const argsObj = paramsObj.arguments as Record<string, unknown>;\n if (Object.keys(argsObj).length === 0) {\n throw new ToolCallError(\n ToolCallErrorCode.INVALID_PARAMS,\n \"工具参数不能为空\"\n );\n }\n }\n\n // 5. 执行自定义验证\n if (opts.customValidator) {\n const error = opts.customValidator(paramsObj as unknown as ToolCallParams);\n if (error) {\n throw new ToolCallError(ToolCallErrorCode.INVALID_PARAMS, error);\n }\n }\n\n return {\n name: paramsObj.name as string,\n arguments: paramsObj.arguments as Record<string, unknown>,\n };\n}\n","export { default as config } from \"./config\";\nexport * from \"@coze/api\";\nexport { createCozeClient } from \"./client\";\nexport { CozeApiService } from \"./service\";\n","export default {\n zh: {\n COZE_BASE_URL: \"https://api.coze.cn\",\n COZE_BASE_WS_URL: \"wss://ws.coze.cn\",\n },\n en: {\n COZE_BASE_URL: \"https://api.coze.com\",\n COZE_BASE_WS_URL: \"wss://ws.coze.com\",\n },\n};\n","/**\n * 扣子 API 客户端封装\n * 提供统一的客户端创建和配置\n */\n\nimport { CozeAPI } from \"@/lib/coze\";\nimport config from \"./config\";\n\nexport type Language = \"zh\" | \"en\";\n\n/**\n * 创建 Coze API 客户端\n * @param token - API 访问令牌\n * @param language - API 环境语言,默认为 \"zh\"(中文),可选 \"en\"(英文)\n */\nexport function createCozeClient(\n token: string,\n language: Language = \"zh\"\n): CozeAPI {\n if (!token || typeof token !== \"string\" || token.trim() === \"\") {\n throw new Error(\"扣子 API Token 不能为空\");\n }\n\n const env = config[language] || config.zh;\n\n return new CozeAPI({\n baseURL: env.COZE_BASE_URL,\n token: token.trim(),\n baseWsURL: env.COZE_BASE_WS_URL,\n debug: false,\n });\n}\n","/**\n * 扣子 API 服务类\n * 负责与扣子 API 的交互,包括工作空间和工作流的获取\n */\n\nimport type { RunWorkflowData, WorkSpace } from \"@/lib/coze\";\nimport type {\n CozeWorkflowsData,\n CozeWorkflowsParams,\n CozeWorkflowsResponse,\n} from \"@root/types/coze\";\nimport NodeCache from \"node-cache\";\nimport { createCozeClient } from \"./client\";\n\n/**\n * 扣子 API 服务类\n */\nexport class CozeApiService {\n private cache: NodeCache;\n private token: string; // 保留 token 字段用于可能的后续扩展(如 token 刷新)\n private client: ReturnType<typeof createCozeClient>;\n\n constructor(token: string) {\n this.token = token.trim();\n this.client = createCozeClient(this.token);\n\n // 初始化缓存\n this.cache = new NodeCache({\n stdTTL: 5 * 60, // 默认5分钟(工作流缓存使用此默认值)\n });\n }\n\n /**\n * 获取工作空间列表\n */\n async getWorkspaces(): Promise<WorkSpace[]> {\n const cacheKey = \"workspaces\";\n const cached = this.cache.get<WorkSpace[]>(cacheKey);\n if (cached) return cached;\n\n const { workspaces = [] } = await this.client.workspaces.list();\n\n // 设置缓存,过期时间30分钟\n this.cache.set(cacheKey, workspaces, 30 * 60);\n\n return workspaces;\n }\n\n /**\n * 获取工作流列表\n */\n async getWorkflows(params: CozeWorkflowsParams): Promise<CozeWorkflowsData> {\n const { workspace_id, page_num = 1, page_size = 20 } = params;\n\n if (!workspace_id || typeof workspace_id !== \"string\") {\n throw new Error(\"工作空间ID不能为空\");\n }\n\n const cacheKey = `workflows:${workspace_id}:${page_num}:${page_size}`;\n const cached = this.cache.get<CozeWorkflowsData>(cacheKey);\n if (cached) return cached;\n\n const response = await this.client.get<\n CozeWorkflowsParams,\n CozeWorkflowsResponse\n >(\"/v1/workflows\", {\n workspace_id,\n page_num: page_num,\n page_size: page_size,\n workflow_mode: \"workflow\",\n });\n\n const result = response.data;\n\n // 设置缓存,使用默认的5分钟过期时间\n this.cache.set(cacheKey, result);\n\n return result;\n }\n\n /**\n * 运行工作流\n * @param workflowId - 工作流ID\n * @param parameters - 参数\n * @returns 运行工作流数据\n */\n callWorkflow(\n workflowId: string,\n parameters: Record<string, unknown>\n ): Promise<RunWorkflowData> {\n return this.client.workflows.runs.create({\n workflow_id: workflowId,\n parameters,\n });\n }\n\n /**\n * 清除缓存\n * @param pattern 可选的模式字符串,清除所有以该模式开头的缓存键\n */\n clearCache(pattern?: string): void {\n if (!pattern) {\n // 清除所有缓存\n this.cache.flushAll();\n return;\n }\n\n // node-cache 不支持模式匹配,需要手动实现\n // 使用前缀匹配,避免意外匹配\n const keys = this.cache.keys();\n const keysToDelete = keys.filter((key) => key.startsWith(pattern));\n this.cache.del(keysToDelete);\n }\n\n /**\n * 获取缓存统计信息\n */\n getCacheStats(): {\n size: number;\n keys: string[];\n hits: number;\n misses: number;\n hitRate: number;\n ksize: number;\n vsize: number;\n } {\n const stats = this.cache.getStats();\n const keys = this.cache.keys();\n const totalRequests = stats.hits + stats.misses;\n const hitRate = totalRequests > 0 ? stats.hits / totalRequests : 0;\n\n return {\n size: stats.keys,\n keys,\n hits: stats.hits,\n misses: stats.misses,\n hitRate,\n ksize: stats.ksize,\n vsize: stats.vsize,\n };\n }\n}\n","/**\n * MCP 相关类型定义\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { MCPToolsCache } from \"@/lib/mcp\";\nimport type { TimeoutResponse } from \"./timeout.js\";\n\n// 工具调用结果接口(与 MCPServiceManager 保持一致)\nexport interface ToolCallResult {\n content: Array<{\n type: string;\n text: string;\n }>;\n isError?: boolean;\n}\n\n// MCP 消息接口 - 定义 JSON-RPC 2.0 标准消息格式\nexport interface MCPMessage {\n jsonrpc: \"2.0\";\n method: string;\n params?: unknown;\n id: string | number;\n}\n\n// MCP 响应接口 - 定义 JSON-RPC 2.0 标准响应格式\nexport interface MCPResponse {\n jsonrpc: \"2.0\";\n id: string | number;\n result?: unknown;\n error?: MCPError;\n}\n\n// MCP 错误接口 - 定义 JSON-RPC 2.0 标准错误格式\nexport interface MCPError {\n code: number;\n message: string;\n data?: unknown;\n}\n\n/**\n * 扩展的 MCP 工具缓存接口\n * 增加对 CustomMCP 执行结果的支持\n */\nexport interface ExtendedMCPToolsCache extends MCPToolsCache {\n customMCPResults?: Record<string, EnhancedToolResultCache>; // 增强的工具执行结果缓存\n}\n\n/**\n * 增强的工具执行结果缓存\n * 用于存储 CustomMCP 工具的执行结果和状态\n */\nexport interface EnhancedToolResultCache {\n result: ToolCallResult;\n timestamp: string; // ISO 8601 格式时间戳\n ttl: number; // 过期时间(毫秒)\n status: TaskStatus; // 任务状态\n consumed: boolean; // 是否已被消费(一次性缓存机制)\n taskId?: string; // 任务ID,用于查询\n retryCount: number; // 重试次数\n}\n\n/**\n * 任务状态类型\n */\nexport type TaskStatus =\n | \"pending\"\n | \"completed\"\n | \"failed\"\n | \"consumed\"\n | \"deleted\";\n\n/**\n * 缓存状态转换接口\n */\nexport interface CacheStateTransition {\n from: TaskStatus;\n to: TaskStatus;\n reason: string;\n timestamp: string;\n}\n\n/**\n * 工具调用选项\n */\nexport interface ToolCallOptions {\n timeout?: number; // 超时时间(毫秒)\n retries?: number; // 重试次数\n retryDelay?: number; // 重试延迟(毫秒)\n enableCache?: boolean; // 是否启用缓存\n taskId?: string; // 任务ID\n}\n\n/**\n * 缓存配置选项\n */\nexport interface CacheConfig {\n ttl?: number; // 缓存过期时间(毫秒),默认5分钟\n cleanupInterval?: number; // 清理间隔(毫秒),默认1分钟\n maxCacheSize?: number; // 最大缓存条目数\n enableOneTimeCache?: boolean; // 是否启用一次性缓存\n}\n\n/**\n * 超时配置选项\n */\nexport interface TimeoutConfig {\n timeout?: number; // 超时时间(毫秒),默认8秒\n enableFriendlyTimeout?: boolean; // 是否启用友好超时响应\n backgroundProcessing?: boolean; // 是否启用后台处理\n}\n\n/**\n * 任务信息接口\n */\nexport interface TaskInfo {\n taskId: string;\n toolName: string;\n arguments: Record<string, unknown>;\n status: TaskStatus;\n startTime: string;\n endTime?: string;\n error?: string;\n result?: ToolCallResult;\n}\n\n/**\n * 缓存统计信息\n */\nexport interface CacheStatistics {\n totalEntries: number;\n pendingTasks: number;\n completedTasks: number;\n failedTasks: number;\n consumedEntries: number;\n cacheHitRate: number;\n lastCleanupTime: string;\n memoryUsage: number;\n}\n\n/**\n * 工具调用结果联合类型\n * 包含正常结果和超时响应\n */\nexport type ToolCallResponse = ToolCallResult | TimeoutResponse;\n\n/**\n * 验证是否为工具调用结果\n */\nexport function isToolCallResult(\n response: unknown\n): response is ToolCallResult {\n return (\n !!response &&\n typeof response === \"object\" &&\n response !== null &&\n \"content\" in response &&\n Array.isArray((response as ToolCallResult).content) &&\n (response as ToolCallResult).content.length > 0 &&\n (response as ToolCallResult).content[0]?.type === \"text\" &&\n typeof (response as ToolCallResult).content[0]?.text === \"string\"\n );\n}\n\n/**\n * 验证是否为增强的工具结果缓存\n */\nexport function isEnhancedToolResultCache(\n cache: unknown\n): cache is EnhancedToolResultCache {\n const cacheObj = cache as EnhancedToolResultCache;\n return (\n !!cache &&\n typeof cache === \"object\" &&\n cache !== null &&\n typeof cacheObj.timestamp === \"string\" &&\n typeof cacheObj.ttl === \"number\" &&\n typeof cacheObj.status === \"string\" &&\n [\"completed\", \"pending\", \"failed\", \"consumed\"].includes(cacheObj.status) &&\n typeof cacheObj.consumed === \"boolean\" &&\n typeof cacheObj.retryCount === \"number\"\n );\n}\n\n/**\n * 验证是否为扩展的 MCP 工具缓存\n */\nexport function isExtendedMCPToolsCache(\n cache: unknown\n): cache is ExtendedMCPToolsCache {\n const cacheObj = cache as ExtendedMCPToolsCache;\n return (\n !!cache &&\n typeof cache === \"object\" &&\n cache !== null &&\n typeof cacheObj.version === \"string\" &&\n typeof cacheObj.mcpServers === \"object\" &&\n cacheObj.mcpServers !== null &&\n typeof cacheObj.metadata === \"object\" &&\n cacheObj.metadata !== null\n );\n}\n\n/**\n * 生成缓存键的工具函数\n */\nexport function generateCacheKey(\n toolName: string,\n arguments_: Record<string, unknown>\n): string {\n const argsHash = createHash(\"md5\")\n .update(JSON.stringify(arguments_ || {}))\n .digest(\"hex\");\n return `${toolName}_${argsHash}`;\n}\n\n/**\n * 格式化时间戳的工具函数\n */\nexport function formatTimestamp(timestamp: number | Date = Date.now()): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * 检查缓存是否过期\n */\nexport function isCacheExpired(timestamp: string, ttl: number): boolean {\n const cachedTime = new Date(timestamp).getTime();\n const now = Date.now();\n return now - cachedTime > ttl;\n}\n\n/**\n * 检查是否应该清理缓存条目\n */\nexport function shouldCleanupCache(cache: EnhancedToolResultCache): boolean {\n const now = Date.now();\n const cachedTime = new Date(cache.timestamp).getTime();\n\n // 已消费且超过清理时间(1分钟)\n if (cache.consumed && now - cachedTime > 60000) {\n return true;\n }\n\n // 已过期\n if (now - cachedTime > cache.ttl) {\n return true;\n }\n\n // 失败的任务立即清理\n if (cache.status === \"failed\") {\n return true;\n }\n\n return false;\n}\n\n/**\n * 默认配置常量\n */\nexport const DEFAULT_CONFIG = {\n TIMEOUT: 8000, // 8秒超时\n CACHE_TTL: 300000, // 5分钟缓存\n CLEANUP_INTERVAL: 60000, // 1分钟清理间隔\n MAX_CACHE_SIZE: 1000, // 最大缓存条目数\n ENABLE_ONE_TIME_CACHE: true, // 启用一次性缓存\n} as const;\n","/**\n * 超时错误类型\n */\nexport class TimeoutError extends Error {\n public override readonly name = \"TimeoutError\" as const;\n\n constructor(message: string) {\n super(message);\n this.name = \"TimeoutError\";\n Error.captureStackTrace(this, TimeoutError);\n }\n\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n stack: this.stack,\n };\n }\n}\n\n/**\n * 超时响应接口\n */\nexport interface TimeoutResponse {\n content: Array<{\n type: \"text\";\n text: string;\n }>;\n isError: boolean;\n taskId: string;\n status: \"timeout\";\n message: string;\n nextAction: string;\n}\n\n/**\n * 创建超时响应的工具函数\n */\nexport function createTimeoutResponse(\n taskId: string,\n toolName?: string\n): TimeoutResponse {\n const toolSpecificMessage = toolName\n ? getToolSpecificTimeoutMessage(toolName, taskId)\n : getDefaultTimeoutMessage(taskId);\n\n return {\n content: [\n {\n type: \"text\",\n text: toolSpecificMessage,\n },\n ],\n isError: false,\n taskId,\n status: \"timeout\",\n message: \"工具调用超时,正在后台处理中\",\n nextAction: \"请稍后重试或等待任务完成\",\n };\n}\n\n/**\n * 获取工具特定的超时提示信息\n */\nfunction getToolSpecificTimeoutMessage(\n toolName: string,\n taskId: string\n): string {\n const toolMessages: Record<string, string> = {\n coze_workflow: `⏱️ 扣子工作流执行超时,正在后台处理中...\n \n📋 任务信息:\n- 任务ID: ${taskId}\n- 工具类型: 扣子工作流\n- 状态: 处理中\n- 建议: 请等待30-60秒后重试查询\n\n🔄 后续操作:\n1. 使用相同参数重新调用工具\n2. 系统会自动返回已完成的任务结果\n3. 复杂工作流可能需要更长时间处理`,\n\n default: getDefaultTimeoutMessage(taskId),\n };\n\n return toolMessages[toolName] || toolMessages.default;\n}\n\n/**\n * 获取默认超时提示信息\n */\nfunction getDefaultTimeoutMessage(taskId: string): string {\n return `⏱️ 工具调用超时,正在后台处理中...\n \n📋 任务信息:\n- 任务ID: ${taskId}\n- 状态: 处理中\n- 建议: 请等待30秒后重试查询\n\n🔄 后续操作:\n1. 使用相同的参数重新调用工具\n2. 系统会自动返回已完成的任务结果\n3. 如果长时间未完成,请联系管理员`;\n}\n\n/**\n * 验证是否为超时响应\n */\nexport function isTimeoutResponse(response: any): response is TimeoutResponse {\n return !!(\n response &&\n response.status === \"timeout\" &&\n typeof response.taskId === \"string\" &&\n Array.isArray(response.content) &&\n response.content.length > 0 &&\n response.content[0].type === \"text\"\n );\n}\n\n/**\n * 验证是否为超时错误\n */\nexport function isTimeoutError(error: any): error is TimeoutError {\n return !!(\n error &&\n error.name === \"TimeoutError\" &&\n error instanceof TimeoutError\n );\n}\n","#!/usr/bin/env node\nimport type {\n CustomMCPTool,\n HandlerConfig,\n ProxyHandlerConfig,\n} from \"@/lib/config/manager.js\";\nimport { configManager } from \"@/lib/config/manager.js\";\nimport { CozeApiService } from \"@/lib/coze\";\nimport type { RunWorkflowData } from \"@/lib/coze\";\nimport type { MCPServiceManager } from \"@/lib/mcp\";\nimport { MCPCacheManager } from \"@/lib/mcp\";\nimport { ensureToolJSONSchema } from \"@/lib/mcp/types.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Logger } from \"@root/Logger.js\";\nimport { logger } from \"@root/Logger.js\";\nimport { getEventBus } from \"@root/services/EventBus.js\";\nimport type {\n EnhancedToolResultCache,\n ExtendedMCPToolsCache,\n ToolCallResponse,\n ToolCallResult,\n} from \"@root/types/mcp.js\";\nimport {\n DEFAULT_CONFIG,\n generateCacheKey,\n isCacheExpired,\n shouldCleanupCache,\n} from \"@root/types/mcp.js\";\nimport { TimeoutError, createTimeoutResponse } from \"@root/types/timeout.js\";\n\n// 工具调用参数类型\ntype ToolArguments = Record<string, unknown>;\n\n// 类型守卫函数:检查是否为代理处理器\nfunction isProxyHandler(handler: HandlerConfig): handler is ProxyHandlerConfig {\n return handler.type === \"proxy\";\n}\n\n// 扩展的工具调用选项\ninterface ToolCallOptions {\n timeout?: number; // 超时时间(毫秒)\n enableCache?: boolean; // 是否启用缓存\n taskId?: string; // 任务ID\n}\n\n/**\n * 简化版的 CustomMCPHandler\n * 专门用于处理 Coze 工作流工具,保持超时友好响应机制\n */\nexport class CustomMCPHandler {\n private logger: Logger;\n private tools: Map<string, CustomMCPTool> = new Map();\n private cacheManager: MCPCacheManager;\n private mcpServiceManager?: MCPServiceManager;\n private readonly TIMEOUT = DEFAULT_CONFIG.TIMEOUT; // 统一8秒超时\n private readonly CACHE_TTL = DEFAULT_CONFIG.CACHE_TTL; // 5分钟缓存过期\n\n constructor(\n cacheManager?: MCPCacheManager,\n mcpServiceManager?: MCPServiceManager\n ) {\n this.logger = logger;\n this.cacheManager = cacheManager || new MCPCacheManager();\n this.mcpServiceManager = mcpServiceManager;\n\n // 设置事件监听器\n this.setupEventListeners();\n }\n\n /**\n * 获取 CozeApiService 实例\n */\n private getCozeApiService(): CozeApiService {\n const token = configManager.getConfig().platforms?.coze?.token;\n\n if (!token) {\n throw new Error(\"Coze Token 配置不存在\");\n }\n\n return new CozeApiService(token);\n }\n\n /**\n * 设置事件监听器\n */\n private setupEventListeners(): void {\n const eventBus = getEventBus();\n\n // 监听配置更新事件\n eventBus.onEvent(\"config:updated\", async (data) => {\n if (data.type === \"customMCP\") {\n this.logger.info(\"[CustomMCP] 检测到配置更新,重新初始化...\");\n try {\n this.reinitialize();\n } catch (error) {\n this.logger.error(\"[CustomMCP] 配置更新处理失败:\", error);\n }\n }\n });\n }\n\n /**\n * 初始化 CustomMCP 处理器\n * 加载配置中的 customMCP 工具\n * @param tools 可选的工具数组,如果提供则使用该数组,否则从配置管理器获取\n */\n public initialize(tools?: CustomMCPTool[]): void {\n this.logger.debug(\"[CustomMCP] 初始化 CustomMCP 处理器...\");\n\n try {\n const customTools = tools || configManager.getCustomMCPTools();\n\n // 清空现有工具\n this.tools.clear();\n\n // 只加载 coze 代理工具\n for (const tool of customTools) {\n if (isProxyHandler(tool.handler) && tool.handler.platform === \"coze\") {\n this.tools.set(tool.name, tool);\n this.logger.debug(\n `[CustomMCP] 已加载 Coze 工具: ${tool.name} (workflow_id: ${tool.handler.config.workflow_id})`\n );\n } else {\n // 根据是否为 proxy 类型显示不同的警告信息\n const platformInfo = isProxyHandler(tool.handler)\n ? `/${tool.handler.platform}`\n : \"\";\n this.logger.warn(\n `[CustomMCP] 跳过不支持的工具类型: ${tool.name} (${tool.handler.type}${platformInfo})`\n );\n }\n }\n\n this.logger.debug(\n `[CustomMCP] 初始化完成,共加载 ${this.tools.size} 个 Coze 工具`\n );\n } catch (error) {\n this.logger.error(\"[CustomMCP] 初始化失败:\", error);\n throw error;\n }\n }\n\n /**\n * 获取所有工具(标准 MCP 格式)\n */\n public getTools(): Tool[] {\n return Array.from(this.tools.values()).map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: ensureToolJSONSchema(tool.inputSchema),\n }));\n }\n\n /**\n * 检查是否存在指定工具\n */\n public hasTool(toolName: string): boolean {\n return this.tools.has(toolName);\n }\n\n /**\n * 获取工具数量\n */\n public getToolCount(): number {\n return this.tools.size;\n }\n\n /**\n * 获取所有工具名称\n */\n public getToolNames(): string[] {\n return Array.from(this.tools.keys());\n }\n\n /**\n * 获取工具详细信息(用于调试)\n */\n public getToolInfo(toolName: string): CustomMCPTool | undefined {\n return this.tools.get(toolName);\n }\n\n /**\n * 重新初始化 CustomMCP 处理器\n * 重新加载配置中的 customMCP 工具\n */\n public reinitialize(): void {\n this.logger.debug(\"[CustomMCP] 重新初始化 CustomMCP 处理器...\");\n this.initialize();\n }\n\n /**\n * 调用工具(支持超时友好响应和缓存管理)\n */\n public async callTool(\n toolName: string,\n arguments_: ToolArguments,\n options?: ToolCallOptions\n ): Promise<ToolCallResponse> {\n const tool = this.tools.get(toolName);\n if (!tool) {\n throw new Error(`未找到工具: ${toolName}`);\n }\n\n // 首先检查是否有已完成的任务结果(一次性缓存)\n const completedResult = await this.getCompletedResult(toolName, arguments_);\n if (completedResult) {\n this.logger.debug(`[CustomMCP] 返回已完成的任务结果: ${toolName}`);\n // 立即清理已消费的缓存\n await this.clearConsumedCache(toolName, arguments_);\n return completedResult;\n }\n\n try {\n const timeout = options?.timeout || this.TIMEOUT;\n const result = await Promise.race([\n this.callCozeWorkflow(tool, arguments_),\n this.createTimeoutPromise(toolName, timeout),\n ]);\n\n // 缓存结果(标记为未消费)\n await this.cacheResult(toolName, arguments_, result);\n\n return result;\n } catch (error) {\n // 如果是超时错误,返回友好提示\n if (error instanceof TimeoutError) {\n const taskId = await this.generateTaskId(toolName, arguments_);\n this.logger.info(\n `[CustomMCP] 工具超时,返回友好提示: ${toolName}, taskId: ${taskId}`\n );\n return createTimeoutResponse(taskId, toolName);\n }\n\n throw error;\n }\n }\n\n /**\n * 创建超时 Promise\n */\n private async createTimeoutPromise(\n toolName: string,\n timeout: number\n ): Promise<never> {\n return new Promise((_, reject) => {\n setTimeout(() => {\n reject(new TimeoutError(`工具调用超时: ${toolName}`));\n }, timeout);\n });\n }\n\n /**\n * 获取已完成的任务结果(一次性缓存)\n */\n private async getCompletedResult(\n toolName: string,\n arguments_: ToolArguments\n ): Promise<ToolCallResult | null> {\n try {\n const cacheKey = this.generateCacheKey(toolName, arguments_);\n const cache = await this.loadExtendedCache();\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return null;\n }\n\n const cached = cache.customMCPResults[cacheKey];\n\n // 只返回已完成且未消费的结果\n if (cached.status === \"completed\" && !cached.consumed) {\n // 检查是否过期\n if (!isCacheExpired(cached.timestamp, cached.ttl)) {\n return cached.result;\n }\n }\n\n return null;\n } catch (error) {\n this.logger.warn(`[CustomMCP] 获取缓存失败: ${error}`);\n return null;\n }\n }\n\n /**\n * 处理工作流响应\n */\n private processWorkflowResponse(\n toolName: string,\n workflowData: RunWorkflowData\n ): ToolCallResult {\n try {\n // 根据 RunWorkflowData 的实际结构进行处理\n // 假设 workflowData 有 data 字段或其他响应数据字段\n const responseData = workflowData.data || workflowData;\n\n if (typeof responseData === \"string\") {\n return {\n content: [\n {\n type: \"text\",\n text: responseData,\n },\n ],\n isError: false,\n };\n }\n\n // 如果是对象,转换为 JSON 字符串\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(responseData, null, 2),\n },\n ],\n isError: false,\n };\n } catch (error) {\n this.logger.error(`[CustomMCP] 处理工作流响应失败: ${toolName}`, error);\n\n return {\n content: [\n {\n type: \"text\",\n text: `处理响应失败: ${\n error instanceof Error ? error.message : String(error)\n }`,\n },\n ],\n isError: true,\n };\n }\n }\n\n /**\n * 调用 Coze 工作流\n */\n private async callCozeWorkflow(\n tool: CustomMCPTool,\n arguments_: ToolArguments\n ): Promise<ToolCallResult> {\n const handler = tool.handler as ProxyHandlerConfig;\n const config = handler.config;\n\n this.logger.info(`[CustomMCP] 调用 Coze 工作流: ${tool.name}`, {\n workflow_id: config.workflow_id,\n });\n\n try {\n // 使用 CozeApiService\n const cozeApiService = this.getCozeApiService();\n\n // 检查 workflow_id 是否存在\n if (!config.workflow_id) {\n throw new Error(\"工作流ID未配置\");\n }\n\n // 调用 callWorkflow 方法\n const workflowResult = await cozeApiService.callWorkflow(\n config.workflow_id,\n arguments_\n );\n\n this.logger.info(`[CustomMCP] Coze 工作流调用成功: ${tool.name}`);\n\n // 转换响应格式为 ToolCallResult\n return this.processWorkflowResponse(tool.name, workflowResult);\n } catch (error) {\n this.logger.error(`[CustomMCP] Coze 工作流调用失败: ${tool.name}`, error);\n\n return {\n content: [\n {\n type: \"text\",\n text: `Coze 工作流调用失败: ${\n error instanceof Error ? error.message : String(error)\n }`,\n },\n ],\n isError: true,\n };\n }\n }\n\n /**\n * 清理已消费的缓存\n */\n private async clearConsumedCache(\n toolName: string,\n arguments_: ToolArguments\n ): Promise<void> {\n try {\n const cacheKey = this.generateCacheKey(toolName, arguments_);\n const cache = await this.loadExtendedCache();\n\n if (cache.customMCPResults?.[cacheKey]) {\n // 标记为已消费\n cache.customMCPResults[cacheKey].consumed = true;\n\n // 如果已消费且已过期,直接删除\n const cached = cache.customMCPResults[cacheKey];\n if (shouldCleanupCache(cached)) {\n delete cache.customMCPResults[cacheKey];\n }\n\n // 保存缓存更改\n await this.saveCache(cache);\n this.logger.debug(`[CustomMCP] 清理已消费缓存: ${cacheKey}`);\n }\n } catch (error) {\n this.logger.warn(`[CustomMCP] 清理缓存失败: ${error}`);\n }\n }\n\n /**\n * 生成任务ID\n */\n private async generateTaskId(\n toolName: string,\n arguments_: ToolArguments\n ): Promise<string> {\n return generateCacheKey(toolName, arguments_);\n }\n\n /**\n * 生成缓存键\n */\n private generateCacheKey(\n toolName: string,\n arguments_: ToolArguments\n ): string {\n return generateCacheKey(toolName, arguments_);\n }\n\n /**\n * 加载扩展缓存\n */\n private async loadExtendedCache(): Promise<ExtendedMCPToolsCache> {\n try {\n const cacheData = await this.cacheManager.loadExistingCache();\n return cacheData as ExtendedMCPToolsCache;\n } catch (error) {\n return {\n version: \"1.0.0\",\n mcpServers: {},\n metadata: {\n lastGlobalUpdate: new Date().toISOString(),\n totalWrites: 0,\n createdAt: new Date().toISOString(),\n },\n customMCPResults: {},\n };\n }\n }\n\n /**\n * 更新缓存结果\n */\n private async updateCacheWithResult(\n cacheKey: string,\n cacheData: EnhancedToolResultCache\n ): Promise<void> {\n try {\n const cache = await this.loadExtendedCache();\n\n if (!cache.customMCPResults) {\n cache.customMCPResults = {};\n }\n\n cache.customMCPResults[cacheKey] = cacheData;\n\n // 使用 MCPCacheManager 的保存方法\n await this.saveCache(cache);\n } catch (error) {\n this.logger.warn(`[CustomMCP] 更新缓存失败: ${error}`);\n }\n }\n\n /**\n * 缓存结果\n */\n private async cacheResult(\n toolName: string,\n arguments_: ToolArguments,\n result: ToolCallResult\n ): Promise<void> {\n try {\n const cacheKey = this.generateCacheKey(toolName, arguments_);\n const cacheData: EnhancedToolResultCache = {\n result,\n timestamp: new Date().toISOString(),\n ttl: this.CACHE_TTL,\n status: \"completed\",\n consumed: false, // 初始状态为未消费\n retryCount: 0,\n };\n\n await this.updateCacheWithResult(cacheKey, cacheData);\n this.logger.debug(`[CustomMCP] 缓存工具结果: ${toolName}`);\n } catch (error) {\n this.logger.warn(`[CustomMCP] 缓存结果失败: ${error}`);\n }\n }\n\n /**\n * 保存缓存\n */\n private async saveCache(cache: ExtendedMCPToolsCache): Promise<void> {\n try {\n await this.cacheManager.saveCache(cache);\n } catch (error) {\n this.logger.warn(`[CustomMCP] 保存缓存失败: ${error}`);\n }\n }\n\n /**\n * 清理资源\n */\n public cleanup(): void {\n this.logger.info(\"[CustomMCP] 清理 CustomMCP 处理器资源\");\n this.tools.clear();\n this.cacheManager.cleanup();\n }\n}\n","/**\n * MCP 工具调用日志模块\n * 提供工具调用的写入和查询功能\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { PathUtils } from \"@utils/PathUtils.js\";\nimport pino from \"pino\";\nimport type { Logger as PinoLogger } from \"pino\";\n\n// ==================== 类型定义 ====================\n\n/**\n * Pino 日志对象类型(内部使用)\n * 用于 formatConsoleMessage 方法的参数类型\n */\ninterface PinoLogObject {\n toolName?: string;\n success?: boolean;\n duration?: number;\n [key: string]: unknown;\n}\n\n/**\n * 工具调用记录接口\n */\nexport interface ToolCallRecord {\n toolName: string; // 工具名称\n originalToolName?: string; // 原始工具名称(未格式化的)\n serverName?: string; // 服务器名称(coze、dify、n8n、custom等)\n arguments?: Record<string, unknown>; // 调用参数\n result?: unknown; // 响应结果\n success: boolean; // 是否成功\n duration?: number; // 调用耗时(毫秒)\n error?: string; // 错误信息(如果有)\n timestamp?: number; // 时间戳(毫秒)\n}\n\n/**\n * 工具调用日志配置接口\n */\nexport interface ToolCallLogConfig {\n maxRecords?: number; // 最大记录条数,默认 100\n logFilePath?: string; // 自定义日志文件路径(可选)\n}\n\n/**\n * 查询参数接口\n */\nexport interface ToolCallQuery {\n limit?: number;\n offset?: number;\n toolName?: string;\n serverName?: string;\n success?: boolean;\n startDate?: string;\n endDate?: string;\n}\n\n// ==================== ToolCallLogger 类(写入功能)====================\n\n/**\n * MCP 工具调用记录器\n * 提供工具调用的 JSONL 格式记录功能\n */\nexport class ToolCallLogger {\n private pinoLogger: PinoLogger;\n private maxRecords: number;\n private logFilePath: string;\n\n constructor(config: ToolCallLogConfig, configDir: string) {\n this.maxRecords = config?.maxRecords ?? 100;\n\n // 确定日志文件路径 - 使用更健壮的路径处理\n if (config?.logFilePath) {\n this.logFilePath = path.resolve(path.normalize(config.logFilePath));\n } else {\n // 使用 PathUtils 的跨平台临时目录处理\n const baseDir = configDir || PathUtils.getTempDir();\n this.logFilePath = path.join(path.normalize(baseDir), \"tool-calls.jsonl\");\n }\n\n // 创建 Pino 实例\n this.pinoLogger = this.createPinoLogger(this.logFilePath);\n\n console.log(\"ToolCallLogger 初始化\", {\n maxRecords: this.maxRecords,\n path: this.logFilePath,\n });\n }\n\n /**\n * 创建 Pino Logger 实例\n */\n private createPinoLogger(logFilePath: string): PinoLogger {\n const streams: pino.StreamEntry[] = [];\n\n // 控制台流 - 使用彩色输出\n streams.push({\n level: \"info\",\n stream: {\n write: (chunk: string) => {\n try {\n const logObj = JSON.parse(chunk);\n const message = this.formatConsoleMessage(logObj);\n console.log(\"[工具调用]\", { message });\n } catch {\n console.log(\"[工具调用]\", { chunk: chunk.trim() });\n }\n },\n },\n });\n\n // 文件流 - JSONL 格式,带错误处理\n try {\n streams.push({\n level: \"info\",\n stream: pino.destination({\n dest: logFilePath,\n sync: true, // 同步写入确保测试可靠性\n append: true,\n mkdir: true,\n }),\n });\n } catch (error) {\n // 如果文件路径无效,记录错误但不抛出异常\n console.error(\"无法创建工具调用日志文件\", { error });\n }\n\n return pino(\n {\n level: \"info\",\n timestamp:\n pino.stdTimeFunctions?.isoTime || (() => `,\"time\":${Date.now()}`),\n formatters: {\n level: (_label: string, number: number) => ({ level: number }),\n },\n base: null, // 不包含 pid 和 hostname\n },\n pino.multistream(streams, { dedupe: true })\n );\n }\n\n /**\n * 格式化控制台消息\n */\n private formatConsoleMessage(logObj: PinoLogObject): string {\n const toolName = logObj.toolName || \"未知工具\";\n const success = logObj.success !== false;\n const duration = logObj.duration ? ` (${logObj.duration}ms)` : \"\";\n const status = success ? \"✅\" : \"❌\";\n\n return `${status} ${toolName}${duration}`;\n }\n\n /**\n * 清理旧的日志记录,确保不超过最大记录数量\n */\n private async cleanupOldRecords(): Promise<void> {\n try {\n // 检查日志文件是否存在\n if (!fs.existsSync(this.logFilePath)) {\n return;\n }\n\n // 读取文件内容\n const content = fs.readFileSync(this.logFilePath, \"utf8\");\n const lines = content\n .trim()\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\");\n\n // 如果记录数量未超过限制,直接返回\n if (lines.length <= this.maxRecords) {\n return;\n }\n\n // 计算需要删除的记录数量\n const recordsToRemove = lines.length - this.maxRecords + 1; // +1 为即将写入的新记录预留空间\n\n // 删除最旧的记录(从文件开头删除)\n const linesToKeep = lines.slice(recordsToRemove);\n\n // 重新写入文件\n const newContent =\n linesToKeep.join(\"\\n\") + (linesToKeep.length > 0 ? \"\\n\" : \"\");\n fs.writeFileSync(this.logFilePath, newContent, \"utf8\");\n\n console.log(\"已清理旧的工具调用记录\", {\n recordsToRemove,\n maxRecords: this.maxRecords,\n });\n } catch (error) {\n console.error(\"清理旧工具调用记录失败\", { error });\n }\n }\n\n /**\n * 记录工具调用\n */\n async recordToolCall(record: ToolCallRecord): Promise<void> {\n try {\n // 在写入新记录前,先清理旧记录以确保不超过最大记录数量\n await this.cleanupOldRecords();\n\n // 使用 Pino 记录日志,自动处理并发和文件写入\n this.pinoLogger.info(record, record.toolName);\n } catch (error) {\n // 记录失败不应该影响主流程,只记录错误日志\n console.error(\"记录工具调用失败\", { error });\n }\n }\n\n /**\n * 获取日志文件路径\n */\n getLogFilePath(): string {\n return this.logFilePath;\n }\n\n /**\n * 获取最大记录数量\n */\n getMaxRecords(): number {\n return this.maxRecords;\n }\n}\n\n// ==================== ToolCallLogService 类(查询功能)====================\n\n/**\n * 工具调用日志服务类\n * 负责读取和查询工具调用日志\n */\nexport class ToolCallLogService {\n private configDir: string;\n\n constructor(configDir?: string) {\n this.configDir = configDir || PathUtils.getConfigDir();\n }\n\n /**\n * 获取工具调用日志文件路径\n */\n private getLogFilePath(): string {\n const toolCallLogger = new ToolCallLogger({}, this.configDir);\n return toolCallLogger.getLogFilePath();\n }\n\n /**\n * 检查日志文件是否存在\n */\n private checkLogFile(): void {\n const logFilePath = this.getLogFilePath();\n if (!fs.existsSync(logFilePath)) {\n throw new Error(\"工具调用日志文件不存在\");\n }\n }\n\n /**\n * 读取并解析工具调用日志\n */\n private parseLogFile(): ToolCallRecord[] {\n const logFilePath = this.getLogFilePath();\n\n try {\n const content = fs.readFileSync(logFilePath, \"utf8\");\n const lines = content\n .trim()\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\");\n\n const records: ToolCallRecord[] = [];\n\n for (const line of lines) {\n try {\n const record = JSON.parse(line);\n // 添加时间戳字段(如果 pino 添加了时间信息)\n if (record.time) {\n record.timestamp = new Date(record.time).getTime();\n }\n // 如果没有时间戳,记录警告信息提示数据质量问题\n if (!record.timestamp) {\n console.warn(\"日志记录缺少时间戳\", { line });\n }\n records.push(record);\n } catch {\n console.warn(\"跳过无效的日志行\", { line });\n }\n }\n\n // 按时间戳倒序排列(最新的在前)\n records.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));\n\n return records;\n } catch (error) {\n console.error(\"读取日志文件失败\", { error });\n throw new Error(\"无法读取工具调用日志文件\");\n }\n }\n\n /**\n * 过滤工具调用记录\n */\n private filterRecords(\n records: ToolCallRecord[],\n query: ToolCallQuery\n ): ToolCallRecord[] {\n let filtered = [...records];\n\n // 按工具名称过滤\n if (query.toolName) {\n filtered = filtered.filter((record) =>\n record.toolName\n .toLowerCase()\n .includes(query.toolName?.toLowerCase() ?? \"\")\n );\n }\n\n // 按服务器名称过滤\n if (query.serverName) {\n filtered = filtered.filter((record) =>\n record.serverName\n ?.toLowerCase()\n .includes(query.serverName?.toLowerCase() ?? \"\")\n );\n }\n\n // 按成功状态过滤\n if (query.success !== undefined) {\n filtered = filtered.filter((record) => record.success === query.success);\n }\n\n // 按时间范围过滤\n if (query.startDate || query.endDate) {\n const startTime = query.startDate\n ? new Date(query.startDate).getTime()\n : 0;\n const endTime = query.endDate\n ? new Date(query.endDate).getTime()\n : Date.now();\n\n filtered = filtered.filter((record) => {\n const recordTime = record.timestamp || 0;\n return recordTime >= startTime && recordTime <= endTime;\n });\n }\n\n return filtered;\n }\n\n /**\n * 获取工具调用日志\n */\n async getToolCallLogs(query: ToolCallQuery = {}): Promise<{\n records: ToolCallRecord[];\n total: number;\n hasMore: boolean;\n }> {\n this.checkLogFile();\n\n const records = this.parseLogFile();\n const filtered = this.filterRecords(records, query);\n const total = filtered.length;\n\n // 分页处理\n const limit = Math.min(\n query.limit || 50,\n 1000 // 最大限制 1000\n );\n const offset = query.offset || 0;\n const paginated = filtered.slice(offset, offset + limit);\n const hasMore = offset + limit < total;\n\n console.log(\"返回工具调用日志\", {\n count: paginated.length,\n total,\n });\n\n return {\n records: paginated,\n total,\n hasMore,\n };\n }\n}\n","/**\n * 路径处理工具\n */\n\nimport { tmpdir } from \"node:os\";\n\n/**\n * 路径工具类\n */\nexport class PathUtils {\n /**\n * 获取配置目录路径\n */\n static getConfigDir(): string {\n return process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n }\n\n /**\n * 获取临时目录路径\n */\n static getTempDir(): string {\n return process.env.TMPDIR || process.env.TEMP || tmpdir();\n }\n\n /**\n * 获取用户主目录路径\n */\n static getHomeDir(): string {\n return process.env.HOME || process.env.USERPROFILE || \"\";\n }\n}\n","/**\n * 统一的 MCP 消息处理器\n * 负责处理所有 MCP 协议消息,包括 initialize、tools/list、tools/call、resources/list、prompts/list 等\n * 这是阶段一重构的核心组件,用于消除双层代理架构\n */\n\nimport type { MCPServiceManager } from \"@/lib/mcp\";\nimport { validateToolCallParams } from \"@/lib/mcp\";\nimport type {\n ClientCapabilities,\n InitializedNotification,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Logger } from \"@root/Logger.js\";\nimport { logger } from \"@root/Logger.js\";\nimport type { MCPMessage, MCPResponse } from \"@root/types/mcp.js\";\n\n// 初始化参数接口\ninterface InitializeParams {\n protocolVersion: string;\n capabilities: ClientCapabilities;\n clientInfo: {\n name: string;\n version: string;\n };\n}\n\n// 工具调用参数接口\ninterface ToolCallParams {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\n// MCP 资源接口\ninterface MCPResource {\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n}\n\n// MCP 提示接口\ninterface MCPPrompt {\n name: string;\n description?: string;\n arguments?: Array<{\n name: string;\n description?: string;\n required?: boolean;\n }>;\n}\n\nexport class MCPMessageHandler {\n private logger: Logger;\n private serviceManager: MCPServiceManager;\n\n constructor(serviceManager: MCPServiceManager) {\n this.serviceManager = serviceManager;\n this.logger = logger;\n }\n\n /**\n * 处理 MCP 消息的统一入口\n * @param message MCP 消息\n * @returns MCP 响应(对于通知消息返回 null)\n */\n async handleMessage(message: MCPMessage): Promise<MCPResponse | null> {\n this.logger.debug(`处理 MCP 消息: ${message.method}`, message);\n\n try {\n // 检查是否为通知消息(没有 id 字段)\n const isNotification = message.id === undefined;\n\n switch (message.method) {\n case \"initialize\":\n return await this.handleInitialize(\n message.params as InitializeParams,\n message.id\n );\n case \"notifications/initialized\":\n return await this.handleInitializedNotification(\n message.params as InitializedNotification[\"params\"]\n );\n case \"tools/list\":\n return await this.handleToolsList(message.id);\n case \"tools/call\":\n return await this.handleToolCall(\n message.params as ToolCallParams,\n message.id\n );\n case \"resources/list\":\n return await this.handleResourcesList(message.id);\n case \"prompts/list\":\n return await this.handlePromptsList(message.id);\n case \"ping\":\n return await this.handlePing(message.id);\n default:\n if (isNotification) {\n // 对于未知的通知消息,记录警告但不抛出错误\n this.logger.warn(`收到未知的通知消息: ${message.method}`, message);\n return null;\n }\n throw new Error(`未知的方法: ${message.method}`);\n }\n } catch (error) {\n this.logger.error(`处理消息时出错: ${message.method}`, error);\n // 通知消息不需要错误响应\n if (message.id === undefined) {\n return null;\n }\n return this.createErrorResponse(error as Error, message.id);\n }\n }\n\n /**\n * 处理 initialize 请求\n * @param params 初始化参数\n * @param id 消息ID\n * @returns 初始化响应\n */\n private async handleInitialize(\n params: InitializeParams,\n id?: string | number\n ): Promise<MCPResponse> {\n this.logger.debug(\"处理 initialize 请求\", params);\n\n // 支持多个协议版本,优先使用客户端请求的版本\n const supportedVersions = [\"2024-11-05\", \"2025-06-18\"];\n const clientVersion = params.protocolVersion;\n const responseVersion = supportedVersions.includes(clientVersion)\n ? clientVersion\n : \"2024-11-05\";\n\n this.logger.debug(\n `协议版本协商: 客户端=${clientVersion}, 服务器响应=${responseVersion}`\n );\n\n return {\n jsonrpc: \"2.0\",\n result: {\n serverInfo: {\n name: \"xiaozhi-mcp-server\",\n version: \"1.0.0\",\n },\n capabilities: {\n tools: {},\n logging: {},\n },\n protocolVersion: responseVersion,\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 处理 notifications/initialized 通知\n * @param params 通知参数\n * @returns null(通知消息不需要响应)\n */\n private async handleInitializedNotification(\n params?: InitializedNotification[\"params\"]\n ): Promise<null> {\n this.logger.debug(\"收到 initialized 通知,客户端初始化完成\", params);\n\n // 可以在这里执行一些初始化完成后的逻辑\n // 例如:记录客户端连接状态、触发事件等\n\n return null;\n }\n\n /**\n * 处理 tools/list 请求\n * @param id 消息ID\n * @returns 工具列表响应\n */\n private async handleToolsList(id?: string | number): Promise<MCPResponse> {\n this.logger.debug(\"处理 tools/list 请求\");\n\n try {\n const tools = this.serviceManager.getAllTools();\n\n // 转换为 MCP 标准格式\n const mcpTools = tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n }));\n\n return {\n jsonrpc: \"2.0\",\n result: {\n tools: mcpTools,\n },\n id: id !== undefined ? id : 1,\n };\n } catch (error) {\n this.logger.error(\"获取工具列表失败\", error);\n throw error;\n }\n }\n\n /**\n * 处理 tools/call 请求\n * @param params 工具调用参数\n * @param id 消息ID\n * @returns 工具调用响应\n */\n private async handleToolCall(\n params: ToolCallParams,\n id?: string | number\n ): Promise<MCPResponse> {\n try {\n // 参数校验\n const validatedParams = validateToolCallParams(params);\n\n const result = await this.serviceManager.callTool(\n validatedParams.name,\n validatedParams.arguments || {}\n );\n\n return {\n jsonrpc: \"2.0\",\n result: {\n content: result.content,\n isError: result.isError || false,\n },\n id: id !== undefined ? id : 1,\n };\n } catch (error) {\n this.logger.error(`工具调用失败: ${params.name}`, error);\n throw error;\n }\n }\n\n /**\n * 处理 ping 请求\n * @param id 消息ID\n * @returns ping 响应\n */\n private async handlePing(id?: string | number): Promise<MCPResponse> {\n this.logger.debug(\"处理 ping 请求\");\n\n return {\n jsonrpc: \"2.0\",\n result: {\n status: \"ok\",\n timestamp: new Date().toISOString(),\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 处理 resources/list 请求\n * @param id 消息ID\n * @returns 资源列表响应\n */\n private async handleResourcesList(\n id?: string | number\n ): Promise<MCPResponse> {\n this.logger.debug(\"处理 resources/list 请求\");\n\n // 目前返回空的资源列表\n // 如果将来需要提供资源功能,可以在这里扩展\n const resources: MCPResource[] = [];\n\n this.logger.debug(`返回 ${resources.length} 个资源`);\n\n return {\n jsonrpc: \"2.0\",\n result: {\n resources: resources,\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 处理 prompts/list 请求\n * @param id 消息ID\n * @returns 提示列表响应\n */\n private async handlePromptsList(id?: string | number): Promise<MCPResponse> {\n this.logger.debug(\"处理 prompts/list 请求\");\n\n // 目前返回空的提示列表\n // 如果将来需要提供提示模板功能,可以在这里扩展\n const prompts: MCPPrompt[] = [];\n\n this.logger.debug(`返回 ${prompts.length} 个提示模板`);\n\n return {\n jsonrpc: \"2.0\",\n result: {\n prompts: prompts,\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 创建错误响应\n * @param error 错误对象\n * @param id 消息ID\n * @returns 错误响应\n */\n private createErrorResponse(error: Error, id?: string | number): MCPResponse {\n // 根据错误类型确定错误代码\n let errorCode = -32603; // Internal error\n\n if (\n error.message.includes(\"未找到工具\") ||\n error.message.includes(\"未知的方法\")\n ) {\n errorCode = -32601; // Method not found\n } else if (\n error.message.includes(\"参数\") ||\n error.message.includes(\"不能为空\")\n ) {\n errorCode = -32602; // Invalid params\n }\n\n return {\n jsonrpc: \"2.0\",\n error: {\n code: errorCode,\n message: error.message,\n data: {\n stack: error.stack,\n },\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 获取服务管理器实例\n * @returns MCPServiceManager 实例\n */\n getServiceManager(): MCPServiceManager {\n return this.serviceManager;\n }\n}\n","/**\n * WebSocket 传输适配器\n * 阶段四重构:支持 WebSocket 双向通信和自动重连\n *\n * 主要功能:\n * 1. WebSocket 连接管理和自动重连\n * 2. 双向实时通信支持\n * 3. 连接池管理和性能优化\n * 4. 消息压缩和批量处理\n */\n\nimport type { IncomingMessage } from \"node:http\";\nimport type { MCPMessageHandler } from \"@/lib/mcp/message.js\";\nimport WebSocket, { WebSocketServer } from \"ws\";\nimport { ConnectionState, TransportAdapter } from \"./TransportAdapter.js\";\nimport type {\n MCPMessage,\n MCPResponse,\n TransportConfig,\n} from \"./TransportAdapter.js\";\n\n/**\n * WebSocket 连接状态枚举\n */\nexport enum WebSocketState {\n DISCONNECTED = \"disconnected\",\n CONNECTING = \"connecting\",\n CONNECTED = \"connected\",\n RECONNECTING = \"reconnecting\",\n FAILED = \"failed\",\n}\n\n/**\n * 重连配置接口\n */\nexport interface ReconnectOptions {\n enabled: boolean;\n maxAttempts: number;\n initialInterval: number;\n maxInterval: number;\n backoffStrategy: \"linear\" | \"exponential\" | \"fixed\";\n backoffMultiplier: number;\n timeout: number;\n jitter: boolean;\n}\n\n/**\n * WebSocket 适配器配置\n */\nexport interface WebSocketConfig extends TransportConfig {\n endpointUrl: string;\n mode?: \"client\" | \"server\";\n reconnect?: Partial<ReconnectOptions>;\n compression?: boolean;\n batchSize?: number;\n batchTimeout?: number;\n maxConnections?: number;\n}\n\n/**\n * 重连状态接口\n */\ninterface ReconnectState {\n attempts: number;\n nextInterval: number;\n timer: NodeJS.Timeout | null;\n lastError: Error | null;\n isManualDisconnect: boolean;\n}\n\n/**\n * 消息批处理队列项\n */\ninterface BatchQueueItem {\n message: MCPMessage | MCPResponse;\n timestamp: number;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\n/**\n * WebSocket 传输适配器实现\n * 支持客户端和服务器模式的 WebSocket 通信\n */\nexport class WebSocketAdapter extends TransportAdapter {\n private ws: WebSocket | null = null;\n private wsServer: WebSocketServer | null = null;\n private endpointUrl: string;\n private mode: \"client\" | \"server\";\n private wsState: WebSocketState = WebSocketState.DISCONNECTED;\n\n // 重连相关\n private reconnectOptions: ReconnectOptions;\n private reconnectState: ReconnectState;\n private connectionTimeout: NodeJS.Timeout | null = null;\n\n // 性能优化相关\n private compression: boolean;\n private batchQueue: BatchQueueItem[] = [];\n private batchTimer: NodeJS.Timeout | null = null;\n private batchSize: number;\n private batchTimeout: number;\n\n // 连接池管理\n private connections: Map<string, WebSocket> = new Map();\n private maxConnections: number;\n\n constructor(messageHandler: MCPMessageHandler, config: WebSocketConfig) {\n super(messageHandler, config);\n\n this.endpointUrl = config.endpointUrl;\n this.mode = config.mode || \"client\";\n this.compression = config.compression || false;\n this.batchSize = config.batchSize || 10;\n this.batchTimeout = config.batchTimeout || 100;\n this.maxConnections = config.maxConnections || 100;\n\n // 初始化重连配置\n this.reconnectOptions = {\n enabled: true,\n maxAttempts: 5,\n initialInterval: 1000,\n maxInterval: 30000,\n backoffStrategy: \"exponential\",\n backoffMultiplier: 1.5,\n timeout: 10000,\n jitter: true,\n ...config.reconnect,\n };\n\n // 初始化重连状态\n this.reconnectState = {\n attempts: 0,\n nextInterval: this.reconnectOptions.initialInterval,\n timer: null,\n lastError: null,\n isManualDisconnect: false,\n };\n }\n\n /**\n * 初始化 WebSocket 适配器\n */\n async initialize(): Promise<void> {\n console.info(`初始化 WebSocket 适配器 (${this.mode} 模式)`);\n\n try {\n this.setState(ConnectionState.CONNECTING);\n this.wsState = WebSocketState.CONNECTING;\n\n if (this.mode === \"client\") {\n await this.initializeClient();\n } else {\n await this.initializeServer();\n }\n\n console.info(\"WebSocket 适配器初始化完成\");\n } catch (error) {\n console.error(\"WebSocket 适配器初始化失败\", error);\n this.setState(ConnectionState.ERROR);\n this.wsState = WebSocketState.FAILED;\n throw error;\n }\n }\n\n /**\n * 初始化客户端模式\n */\n private async initializeClient(): Promise<void> {\n return new Promise((resolve, reject) => {\n // 设置连接超时\n this.connectionTimeout = setTimeout(() => {\n const error = new Error(\n `连接超时 (${this.reconnectOptions.timeout}ms)`\n );\n this.handleConnectionError(error);\n reject(error);\n }, this.reconnectOptions.timeout);\n\n this.ws = new WebSocket(this.endpointUrl);\n\n // 启用压缩\n if (this.compression) {\n // WebSocket 压缩扩展会自动处理\n }\n\n this.ws.on(\"open\", () => {\n this.handleConnectionSuccess();\n resolve();\n });\n\n this.ws.on(\"message\", (data) => {\n this.handleIncomingData(data);\n });\n\n this.ws.on(\"close\", (code, reason) => {\n this.handleConnectionClose(code, reason.toString());\n });\n\n this.ws.on(\"error\", (error) => {\n this.handleConnectionError(error);\n reject(error);\n });\n });\n }\n\n /**\n * 初始化服务器模式\n */\n private async initializeServer(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const url = new URL(this.endpointUrl);\n const port = Number.parseInt(url.port) || 8080;\n\n this.wsServer = new WebSocketServer({\n port,\n perMessageDeflate: this.compression,\n });\n\n this.wsServer.on(\"connection\", (ws, request) => {\n this.handleNewConnection(ws, request);\n });\n\n this.wsServer.on(\"error\", (error) => {\n console.error(\"WebSocket 服务器错误\", error);\n reject(error);\n });\n\n console.info(`WebSocket 服务器监听端口 ${port}`);\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * 启动 WebSocket 适配器\n */\n async start(): Promise<void> {\n if (this.wsState === WebSocketState.CONNECTED) {\n console.warn(\"WebSocket 适配器已启动\");\n return;\n }\n\n console.info(\"启动 WebSocket 适配器\");\n\n try {\n this.setState(ConnectionState.CONNECTED);\n this.wsState = WebSocketState.CONNECTED;\n\n console.info(\"WebSocket 适配器启动成功\");\n } catch (error) {\n console.error(\"启动 WebSocket 适配器失败\", error);\n this.setState(ConnectionState.ERROR);\n this.wsState = WebSocketState.FAILED;\n throw error;\n }\n }\n\n /**\n * 停止 WebSocket 适配器\n */\n async stop(): Promise<void> {\n console.info(\"停止 WebSocket 适配器\");\n\n try {\n // 标记为手动断开\n this.reconnectState.isManualDisconnect = true;\n\n // 清理重连定时器\n if (this.reconnectState.timer) {\n clearTimeout(this.reconnectState.timer);\n this.reconnectState.timer = null;\n }\n\n // 清理批处理定时器\n if (this.batchTimer) {\n clearTimeout(this.batchTimer);\n this.batchTimer = null;\n }\n\n // 处理剩余的批处理消息\n await this.flushBatchQueue();\n\n // 关闭客户端连接(等待关闭完成)\n if (this.ws) {\n await new Promise<void>((resolve) => {\n if (this.ws!.readyState === WebSocket.CLOSED) {\n resolve();\n return;\n }\n\n const onClose = () => {\n resolve();\n };\n\n this.ws!.once(\"close\", onClose);\n this.ws!.close();\n\n // 超时保护,避免无限等待\n setTimeout(() => {\n if (this.ws && this.ws.readyState === WebSocket.CLOSED) {\n resolve();\n } else {\n // 超时后强制清理\n resolve();\n }\n }, 1000);\n });\n\n this.ws = null;\n }\n\n // 关闭服务器(等待关闭完成)\n if (this.wsServer) {\n await new Promise<void>((resolve) => {\n this.wsServer!.close((err) => {\n // 忽略关闭错误\n resolve();\n });\n // 超时保护\n setTimeout(resolve, 1000);\n });\n this.wsServer = null;\n }\n\n // 关闭所有连接\n for (const [id, connection] of this.connections) {\n if (connection.readyState === WebSocket.OPEN) {\n connection.close();\n }\n }\n this.connections.clear();\n\n this.setState(ConnectionState.DISCONNECTED);\n this.wsState = WebSocketState.DISCONNECTED;\n\n console.info(\"WebSocket 适配器已停止\");\n } catch (error) {\n console.error(\"停止 WebSocket 适配器时出错\", error);\n throw error;\n }\n }\n\n /**\n * 发送消息\n */\n async sendMessage(message: MCPMessage | MCPResponse): Promise<void> {\n if (this.wsState !== WebSocketState.CONNECTED) {\n throw new Error(`WebSocket 未连接 (状态: ${this.wsState})`);\n }\n\n // 如果启用了批处理,添加到队列\n if (this.batchSize > 1) {\n return this.addToBatchQueue(message);\n }\n\n // 直接发送\n return this.sendMessageDirect(message);\n }\n\n /**\n * 直接发送消息\n */\n private async sendMessageDirect(\n message: MCPMessage | MCPResponse\n ): Promise<void> {\n try {\n const serializedMessage = this.serializeMessage(message);\n\n if (this.mode === \"client\" && this.ws) {\n this.ws.send(serializedMessage);\n } else if (this.mode === \"server\") {\n // 广播到所有连接\n for (const connection of this.connections.values()) {\n if (connection.readyState === WebSocket.OPEN) {\n connection.send(serializedMessage);\n }\n }\n }\n\n console.debug(\"消息已发送\", {\n messageId: message.id,\n method: \"method\" in message ? message.method : \"response\",\n });\n } catch (error) {\n console.error(\"发送消息失败\", error);\n throw error;\n }\n }\n\n /**\n * 添加消息到批处理队列\n */\n private async addToBatchQueue(\n message: MCPMessage | MCPResponse\n ): Promise<void> {\n return new Promise((resolve, reject) => {\n this.batchQueue.push({\n message,\n timestamp: Date.now(),\n resolve,\n reject,\n });\n\n // 如果队列达到批处理大小,立即处理\n if (this.batchQueue.length >= this.batchSize) {\n this.flushBatchQueue();\n } else if (!this.batchTimer) {\n // 设置批处理超时\n this.batchTimer = setTimeout(() => {\n this.flushBatchQueue();\n }, this.batchTimeout);\n }\n });\n }\n\n /**\n * 刷新批处理队列\n */\n private async flushBatchQueue(): Promise<void> {\n if (this.batchQueue.length === 0) {\n return;\n }\n\n // 清理定时器\n if (this.batchTimer) {\n clearTimeout(this.batchTimer);\n this.batchTimer = null;\n }\n\n const batch = this.batchQueue.splice(0);\n\n try {\n // 批量发送消息\n const messages = batch.map((item) => item.message);\n const batchMessage = {\n jsonrpc: \"2.0\" as const,\n method: \"batch\",\n params: { messages },\n id: `batch_${Date.now()}`,\n };\n\n await this.sendMessageDirect(batchMessage);\n\n // 解析所有 Promise\n for (const item of batch) {\n item.resolve();\n }\n\n console.debug(`批处理发送 ${batch.length} 条消息`);\n } catch (error) {\n // 拒绝所有 Promise\n for (const item of batch) {\n item.reject(error as Error);\n }\n console.error(\"批处理发送失败\", error);\n }\n }\n\n /**\n * 处理接收到的数据\n */\n private async handleIncomingData(data: WebSocket.Data): Promise<void> {\n try {\n const messageStr = data.toString();\n const message = this.parseMessage(messageStr);\n\n if (message) {\n // 检查是否是批处理消息\n if (\n message.method === \"batch\" &&\n message.params &&\n typeof message.params === \"object\" &&\n \"messages\" in message.params &&\n Array.isArray((message.params as { messages: unknown[] }).messages)\n ) {\n // 处理批处理消息\n const batchMessages = (message.params as { messages: unknown[] })\n .messages;\n for (const batchedMessage of batchMessages) {\n await this.handleIncomingMessage(batchedMessage as MCPMessage);\n }\n } else {\n await this.handleIncomingMessage(message);\n }\n }\n } catch (error) {\n console.error(\"处理接收数据失败\", error);\n }\n }\n\n /**\n * 处理连接成功\n */\n private handleConnectionSuccess(): void {\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n this.setState(ConnectionState.CONNECTED);\n this.wsState = WebSocketState.CONNECTED;\n\n // 重置重连状态\n this.reconnectState.attempts = 0;\n this.reconnectState.nextInterval = this.reconnectOptions.initialInterval;\n this.reconnectState.lastError = null;\n\n console.info(\"WebSocket 连接已建立\");\n }\n\n /**\n * 处理连接错误\n */\n private handleConnectionError(error: Error): void {\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n this.reconnectState.lastError = error;\n console.error(\"WebSocket 连接错误\", error);\n\n this.setState(ConnectionState.ERROR);\n this.wsState = WebSocketState.FAILED;\n\n // 清理当前连接\n this.cleanupConnection();\n }\n\n /**\n * 处理连接关闭\n */\n private handleConnectionClose(code: number, reason: string): void {\n this.setState(ConnectionState.DISCONNECTED);\n this.wsState = WebSocketState.DISCONNECTED;\n\n console.info(`WebSocket 连接已关闭 (代码: ${code}, 原因: ${reason})`);\n\n // 如果是手动断开,不进行重连\n if (this.reconnectState.isManualDisconnect) {\n return;\n }\n\n // 检查是否需要重连\n if (this.shouldReconnect()) {\n this.scheduleReconnect();\n } else {\n this.wsState = WebSocketState.FAILED;\n console.warn(\n `已达到最大重连次数 (${this.reconnectOptions.maxAttempts}),停止重连`\n );\n }\n }\n\n /**\n * 处理新连接(服务器模式)\n */\n private handleNewConnection(ws: WebSocket, request: IncomingMessage): void {\n // 检查连接数限制\n if (this.connections.size >= this.maxConnections) {\n console.warn(\"达到最大连接数限制,拒绝新连接\");\n ws.close(1013, \"服务器繁忙\");\n return;\n }\n\n const connectionId = `${this.getConnectionId()}_${this.connections.size}`;\n this.connections.set(connectionId, ws);\n\n console.info(`新 WebSocket 连接: ${connectionId}`);\n\n ws.on(\"message\", (data) => {\n this.handleIncomingData(data);\n });\n\n ws.on(\"close\", () => {\n this.connections.delete(connectionId);\n console.info(`WebSocket 连接已断开: ${connectionId}`);\n });\n\n ws.on(\"error\", (error) => {\n console.error(`WebSocket 连接错误 ${connectionId}:`, error);\n this.connections.delete(connectionId);\n });\n }\n\n /**\n * 清理连接\n */\n private cleanupConnection(): void {\n if (this.ws) {\n try {\n // 移除监听器,防止错误事件传播\n this.ws.removeAllListeners();\n // 只在已连接状态下关闭连接\n // 在 CONNECTING 状态下调用 close() 会抛出异常\n if (this.ws.readyState === WebSocket.OPEN) {\n try {\n this.ws.close();\n } catch {\n // 忽略关闭错误\n }\n }\n // 在 CONNECTING 状态下,让连接自然超时失败\n } catch {\n // 忽略所有错误\n } finally {\n this.ws = null;\n }\n }\n }\n\n /**\n * 检查是否应该重连\n */\n private shouldReconnect(): boolean {\n return (\n this.reconnectOptions.enabled &&\n this.reconnectState.attempts < this.reconnectOptions.maxAttempts &&\n !this.reconnectState.isManualDisconnect\n );\n }\n\n /**\n * 安排重连\n */\n private scheduleReconnect(): void {\n this.wsState = WebSocketState.RECONNECTING;\n this.reconnectState.attempts++;\n\n // 计算重连间隔\n let interval = this.calculateReconnectInterval();\n\n // 添加随机抖动\n if (this.reconnectOptions.jitter) {\n interval += Math.random() * 1000;\n }\n\n console.info(\n `安排重连 (第 ${this.reconnectState.attempts} 次,${interval}ms 后)`\n );\n\n this.reconnectState.timer = setTimeout(async () => {\n try {\n await this.initializeClient();\n } catch (error) {\n console.error(\"重连失败\", error);\n\n if (this.shouldReconnect()) {\n this.scheduleReconnect();\n } else {\n this.wsState = WebSocketState.FAILED;\n }\n }\n }, interval);\n }\n\n /**\n * 计算重连间隔\n */\n private calculateReconnectInterval(): number {\n const { backoffStrategy, initialInterval, maxInterval, backoffMultiplier } =\n this.reconnectOptions;\n const attempts = this.reconnectState.attempts;\n\n let interval: number;\n\n switch (backoffStrategy) {\n case \"linear\":\n interval = initialInterval + attempts * 1000;\n break;\n case \"exponential\":\n interval = initialInterval * backoffMultiplier ** attempts;\n break;\n default:\n interval = initialInterval;\n break;\n }\n\n return Math.min(interval, maxInterval);\n }\n\n /**\n * 获取适配器状态\n */\n getStatus(): {\n wsState: WebSocketState;\n connectionState: ConnectionState;\n mode: string;\n endpointUrl: string;\n connectionCount: number;\n reconnectAttempts: number;\n batchQueueSize: number;\n compression: boolean;\n } {\n return {\n wsState: this.wsState,\n connectionState: this.state,\n mode: this.mode,\n endpointUrl: this.endpointUrl,\n connectionCount: this.connections.size,\n reconnectAttempts: this.reconnectState.attempts,\n batchQueueSize: this.batchQueue.length,\n compression: this.compression,\n };\n }\n\n /**\n * 强制重连\n */\n async forceReconnect(): Promise<void> {\n if (this.mode !== \"client\") {\n throw new Error(\"只有客户端模式支持重连\");\n }\n\n console.info(\"强制重连\");\n\n // 重置重连状态\n this.reconnectState.attempts = 0;\n this.reconnectState.isManualDisconnect = false;\n\n // 关闭当前连接\n if (this.ws) {\n this.ws.close();\n }\n\n // 立即重连\n await this.initializeClient();\n }\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { getEventBus } from \"@root/services/EventBus.js\";\nimport { TransportFactory } from \"./transport-factory.js\";\nimport type {\n MCPServerTransport,\n MCPServiceConfig,\n MCPServiceStatus,\n ToolCallResult,\n} from \"./types.js\";\nimport { ConnectionState, MCPTransportType } from \"./types.js\";\nimport { inferTransportTypeFromConfig } from \"./utils.js\";\n\n/**\n * MCP 服务类\n * 负责管理单个 MCP 服务的连接、工具管理和调用\n */\nexport class MCPService {\n private config: MCPServiceConfig;\n private client: Client | null = null;\n private transport: MCPServerTransport | null = null;\n private tools: Map<string, Tool> = new Map();\n private connectionState: ConnectionState = ConnectionState.DISCONNECTED;\n private connectionTimeout: NodeJS.Timeout | null = null;\n private initialized = false;\n private eventBus = getEventBus();\n\n constructor(config: MCPServiceConfig) {\n // 使用工具方法推断服务类型\n this.config = inferTransportTypeFromConfig(config);\n\n // 验证配置\n this.validateConfig();\n }\n\n /**\n * 验证配置\n */\n private validateConfig(): void {\n // 使用 TransportFactory 进行配置验证\n TransportFactory.validateConfig(this.config);\n }\n\n /**\n * 连接到 MCP 服务\n */\n async connect(): Promise<void> {\n // 如果正在连接中,等待当前连接完成\n if (this.connectionState === ConnectionState.CONNECTING) {\n throw new Error(\"连接正在进行中,请等待连接完成\");\n }\n\n // 清理之前的连接\n this.cleanupConnection();\n\n return this.attemptConnection();\n }\n\n /**\n * 尝试建立连接\n */\n private async attemptConnection(): Promise<void> {\n this.connectionState = ConnectionState.CONNECTING;\n console.debug(\n `[MCP-${this.config.name}] 正在连接 MCP 服务: ${this.config.name}`\n );\n\n return new Promise((resolve, reject) => {\n // 设置连接超时\n this.connectionTimeout = setTimeout(() => {\n const error = new Error(`连接超时 (${this.config.timeout || 10000}ms)`);\n this.handleConnectionError(error);\n reject(error);\n }, this.config.timeout || 10000);\n\n try {\n this.client = new Client(\n {\n name: `xiaozhi-${this.config.name}-client`,\n version: \"1.0.0\",\n },\n {\n capabilities: {},\n }\n );\n\n // 使用 TransportFactory 创建传输层\n this.transport = TransportFactory.create(this.config);\n\n // 连接到 MCP 服务\n this.client\n .connect(this.transport as MCPServerTransport)\n .then(async () => {\n this.handleConnectionSuccess();\n\n // 获取工具列表\n await this.refreshTools();\n\n // 发射连接成功事件(包含工具列表)\n this.eventBus.emitEvent(\"mcp:service:connected\", {\n serviceName: this.config.name,\n tools: this.getTools(),\n connectionTime: new Date(),\n });\n\n resolve();\n })\n .catch((error) => {\n this.handleConnectionError(error);\n reject(error);\n });\n } catch (error) {\n this.handleConnectionError(error as Error);\n reject(error);\n }\n });\n }\n\n /**\n * 处理连接成功\n */\n private handleConnectionSuccess(): void {\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n this.connectionState = ConnectionState.CONNECTED;\n this.initialized = true;\n\n console.info(\n `[MCP-${this.config.name}] MCP 服务 ${this.config.name} 连接已建立`\n );\n }\n\n /**\n * 处理连接错误\n */\n private handleConnectionError(error: Error): void {\n this.connectionState = ConnectionState.DISCONNECTED;\n this.initialized = false;\n\n console.debug(`MCP 服务 ${this.config.name} 连接错误:`, error.message);\n\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n // 清理当前连接\n this.cleanupConnection();\n\n // 发射连接失败事件\n this.eventBus.emitEvent(\"mcp:service:connection:failed\", {\n serviceName: this.config.name,\n error,\n attempt: 0,\n });\n }\n\n /**\n * 清理连接资源\n */\n private cleanupConnection(): void {\n // 清理客户端\n if (this.client) {\n try {\n this.client.close().catch(() => {\n // 忽略关闭时的错误\n });\n } catch (error) {\n // 忽略关闭时的错误\n }\n this.client = null;\n }\n\n // 清理传输层\n this.transport = null;\n\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n // 重置状态\n this.initialized = false;\n }\n\n /**\n * 刷新工具列表\n */\n private async refreshTools(): Promise<void> {\n if (!this.client) {\n throw new Error(\"客户端未初始化\");\n }\n\n try {\n const toolsResult = await this.client.listTools();\n const tools: Tool[] = toolsResult.tools || [];\n\n // 清空现有工具\n this.tools.clear();\n\n // 注册工具到映射表\n for (const tool of tools) {\n this.tools.set(tool.name, tool);\n }\n\n console.debug(\n `${this.config.name} 服务加载了 ${tools.length} 个工具: ${tools\n .map((t) => t.name)\n .join(\", \")}`\n );\n } catch (error) {\n console.error(\n `${this.config.name} 获取工具列表失败:`,\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n }\n\n /**\n * 断开连接\n */\n async disconnect(): Promise<void> {\n console.info(`主动断开 MCP 服务 ${this.config.name} 连接`);\n\n // 清理连接资源\n this.cleanupConnection();\n\n // 设置状态为已断开\n this.connectionState = ConnectionState.DISCONNECTED;\n\n // 发射断开连接事件\n this.eventBus.emitEvent(\"mcp:service:disconnected\", {\n serviceName: this.config.name,\n reason: \"手动断开\",\n disconnectionTime: new Date(),\n });\n }\n\n /**\n * 获取工具列表\n */\n getTools(): Tool[] {\n return Array.from(this.tools.values());\n }\n\n /**\n * 调用工具\n */\n async callTool(\n name: string,\n arguments_: Record<string, unknown>\n ): Promise<ToolCallResult> {\n if (!this.client) {\n throw new Error(`服务 ${this.config.name} 未连接`);\n }\n\n if (!this.tools.has(name)) {\n throw new Error(`工具 ${name} 在服务 ${this.config.name} 中不存在`);\n }\n\n console.debug(\n `调用 ${this.config.name} 服务的工具 ${name},参数:`,\n JSON.stringify(arguments_)\n );\n\n try {\n const result = await this.client.callTool({\n name,\n arguments: arguments_ || {},\n });\n\n console.debug(\n `工具 ${name} 调用成功,结果:`,\n `${JSON.stringify(result).substring(0, 500)}...`\n );\n\n return result as ToolCallResult;\n } catch (error) {\n console.error(\n `工具 ${name} 调用失败:`,\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n }\n\n /**\n * 获取服务配置\n */\n getConfig(): MCPServiceConfig {\n return this.config;\n }\n\n /**\n * 获取服务状态\n */\n getStatus(): MCPServiceStatus {\n return {\n name: this.config.name,\n connected: this.connectionState === ConnectionState.CONNECTED,\n initialized: this.initialized,\n transportType: this.config.type || MCPTransportType.STREAMABLE_HTTP,\n toolCount: this.tools.size,\n connectionState: this.connectionState,\n };\n }\n\n /**\n * 检查是否已连接\n */\n isConnected(): boolean {\n return (\n this.connectionState === ConnectionState.CONNECTED && this.initialized\n );\n }\n}\n","import type { MCPServerTransport, MCPServiceConfig } from \"@/lib/mcp/types\";\nimport { MCPTransportType } from \"@/lib/mcp/types\";\nimport type { SSEClientTransportOptions } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport type { StreamableHTTPClientTransportOptions } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { EventSource } from \"eventsource\";\n\n// 全局 polyfill EventSource(用于 SSE)\n// 使用类型断言避免与已有 EventSource 类型冲突\nif (\n typeof global !== \"undefined\" &&\n !(global as typeof global & { EventSource?: unknown }).EventSource\n) {\n (global as typeof global & { EventSource: typeof EventSource }).EventSource =\n EventSource;\n}\n\n// Transport 基础接口\nexport interface Transport {\n connect?(): Promise<void>;\n close?(): Promise<void>;\n}\n\n/**\n * 创建 transport 实例\n * @param config MCP 服务配置\n * @returns transport 实例\n */\nexport function createTransport(config: MCPServiceConfig): MCPServerTransport {\n console.debug(\n `[TransportFactory] 创建 ${config.type} transport for ${config.name}`\n );\n\n switch (config.type) {\n case MCPTransportType.STDIO:\n return createStdioTransport(config);\n\n case MCPTransportType.SSE:\n return createSSETransport(config);\n\n case MCPTransportType.STREAMABLE_HTTP:\n return createStreamableHTTPTransport(config);\n\n default:\n throw new Error(`不支持的传输类型: ${config.type}`);\n }\n}\n\n/**\n * 创建 Stdio transport\n */\nfunction createStdioTransport(config: MCPServiceConfig): StdioClientTransport {\n if (!config.command) {\n throw new Error(\"stdio transport 需要 command 配置\");\n }\n\n return new StdioClientTransport({\n command: config.command,\n args: config.args || [],\n env: config.env, // 传递环境变量\n });\n}\n\n/**\n * 创建 SSE transport\n */\nfunction createSSETransport(config: MCPServiceConfig): SSEClientTransport {\n if (!config.url) {\n throw new Error(\"SSE transport 需要 URL 配置\");\n }\n\n const url = new URL(config.url);\n const options = createSSEOptions(config);\n\n return new SSEClientTransport(url, options);\n}\n\n/**\n * 创建 ModelScope SSE transport\n */\nfunction createModelScopeSSETransport(\n config: MCPServiceConfig\n): SSEClientTransport {\n if (!config.url) {\n throw new Error(\"ModelScope SSE transport 需要 URL 配置\");\n }\n\n if (!config.apiKey) {\n throw new Error(\"ModelScope SSE transport 需要 apiKey 配置\");\n }\n\n const url = new URL(config.url);\n const options = createModelScopeSSEOptions(config);\n\n return new SSEClientTransport(url, options);\n}\n\nfunction createStreamableHTTPTransport(\n config: MCPServiceConfig\n): StreamableHTTPClientTransport {\n if (!config.url) {\n throw new Error(\"StreamableHTTP transport 需要 URL 配置\");\n }\n\n const url = new URL(config.url);\n const options = createStreamableHTTPOptions(config);\n return new StreamableHTTPClientTransport(url, options);\n}\n\n/**\n * 创建 SSE 选项\n */\nfunction createSSEOptions(config: MCPServiceConfig): SSEClientTransportOptions {\n const options: SSEClientTransportOptions = {};\n\n // 添加认证头\n if (config.apiKey) {\n options.requestInit = {\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n ...config.headers,\n },\n };\n } else if (config.headers) {\n options.requestInit = {\n headers: config.headers,\n };\n }\n\n return options;\n}\n\n/**\n * 创建 ModelScope SSE 选项\n */\nfunction createModelScopeSSEOptions(\n config: MCPServiceConfig\n): SSEClientTransportOptions {\n // 添加防御性空值检查\n if (!config.apiKey) {\n throw new Error(\"ModelScope SSE transport 需要 apiKey 配置\");\n }\n\n const token = config.apiKey;\n\n // 如果有自定义SSE选项,使用它们\n if (config.customSSEOptions) {\n return config.customSSEOptions;\n }\n\n // 默认的ModelScope SSE选项配置\n return {\n eventSourceInit: {\n fetch: async (url: string | URL | Request, init?: RequestInit) => {\n // 添加认证头\n const headers = {\n ...init?.headers,\n Authorization: `Bearer ${token}`,\n };\n\n return fetch(url, { ...init, headers });\n },\n },\n requestInit: {\n headers: {\n Authorization: `Bearer ${token}`,\n ...config.headers,\n },\n },\n };\n}\n\nfunction createStreamableHTTPOptions(\n config: MCPServiceConfig\n): StreamableHTTPClientTransportOptions {\n const options: StreamableHTTPClientTransportOptions = {};\n\n // 添加认证头\n if (config.apiKey) {\n options.requestInit = {\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n ...config.headers,\n },\n };\n } else if (config.headers) {\n options.requestInit = {\n headers: config.headers,\n };\n }\n\n return options;\n}\n\n/**\n * 验证配置\n */\nexport function validateConfig(config: MCPServiceConfig): void {\n if (!config.name || typeof config.name !== \"string\") {\n throw new Error(\"配置必须包含有效的 name 字段\");\n }\n\n // type 字段现在是可选的,由 MCPService 自动推断\n // 这里我们只验证如果 type 存在,必须是有效的类型\n if (config.type && !Object.values(MCPTransportType).includes(config.type)) {\n throw new Error(`不支持的传输类型: ${config.type}`);\n }\n\n // 注意:这个验证方法在 MCPService.inferTransportType 之后调用\n // 此时 config.type 应该已经被推断或显式设置\n if (!config.type) {\n throw new Error(\"传输类型未设置,这应该在 inferTransportType 中处理\");\n }\n\n switch (config.type) {\n case MCPTransportType.STDIO:\n if (!config.command) {\n throw new Error(\"stdio 类型需要 command 字段\");\n }\n break;\n\n case MCPTransportType.SSE:\n if (config.url === undefined || config.url === null) {\n throw new Error(`${config.type} 类型需要 url 字段`);\n }\n break;\n case MCPTransportType.STREAMABLE_HTTP:\n // STREAMABLE_HTTP 允许空 URL,会在后续处理中设置默认值\n if (config.url === undefined || config.url === null) {\n throw new Error(`${config.type} 类型需要 url 字段`);\n }\n break;\n\n default:\n throw new Error(`不支持的传输类型: ${config.type}`);\n }\n}\n\n/**\n * 获取支持的传输类型列表\n */\nexport function getSupportedTypes(): MCPTransportType[] {\n return [\n MCPTransportType.STDIO,\n MCPTransportType.SSE,\n MCPTransportType.STREAMABLE_HTTP,\n ];\n}\n\n/**\n * Transport 工厂对象(保持 API 兼容性)\n */\nexport const TransportFactory = {\n create: createTransport,\n validateConfig,\n getSupportedTypes,\n};\n","/**\n * MCP 缓存管理器\n * 负责 MCP 服务工具列表的缓存写入功能\n * 专注于缓存文件管理和数据写入的基础设施\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport type { MCPServiceConfig } from \"@/lib/mcp/types\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Logger } from \"@root/Logger.js\";\nimport { logger } from \"@root/Logger.js\";\nimport type {\n CacheStatistics,\n EnhancedToolResultCache,\n ExtendedMCPToolsCache,\n TaskStatus,\n ToolCallResult,\n} from \"@root/types/index.js\";\nimport { generateCacheKey, shouldCleanupCache } from \"@root/types/index.js\";\nimport dayjs from \"dayjs\";\n\n// 缓存条目接口\nexport interface MCPToolsCacheEntry {\n tools: Tool[]; // 工具列表\n lastUpdated: string; // 最后更新时间 (YYYY-MM-DD HH:mm:ss)\n serverConfig: MCPServiceConfig; // 服务配置快照\n configHash: string; // 配置哈希值,用于快速变更检测\n version: string; // 缓存条目版本\n}\n\n// 缓存文件接口\nexport interface MCPToolsCache {\n version: string; // 缓存文件格式版本 \"1.0.0\"\n mcpServers: Record<string, MCPToolsCacheEntry>;\n metadata: {\n lastGlobalUpdate: string; // 全局最后更新时间 (YYYY-MM-DD HH:mm:ss)\n totalWrites: number; // 总写入次数\n createdAt: string; // 缓存文件创建时间 (YYYY-MM-DD HH:mm:ss)\n };\n}\n\n// 缓存统计接口\nexport interface CacheStats {\n totalWrites: number;\n lastUpdate: string;\n serverCount: number;\n cacheFileSize: number;\n}\n\n// 重新导出相关类型\nexport type {\n CacheStatistics,\n EnhancedToolResultCache,\n ExtendedMCPToolsCache,\n} from \"@root/types/index.js\";\n\nexport class MCPCacheManager {\n private cachePath: string;\n private logger: Logger;\n private readonly CACHE_VERSION = \"1.0.0\";\n private readonly CACHE_ENTRY_VERSION = \"1.0.0\";\n private cleanupInterval?: NodeJS.Timeout;\n private readonly CLEANUP_INTERVAL = 60000; // 1分钟清理间隔\n\n constructor(customCachePath?: string) {\n this.logger = logger;\n this.cachePath = customCachePath || this.getCacheFilePath();\n this.startCleanupTimer();\n }\n\n /**\n * 格式化时间戳为 YYYY-MM-DD HH:mm:ss 格式\n */\n private formatTimestamp(): string {\n return dayjs().format(\"YYYY-MM-DD HH:mm:ss\");\n }\n\n /**\n * 获取缓存文件路径\n * 与 xiaozhi.config.json 同级目录\n */\n private getCacheFilePath(): string {\n try {\n const configDir = process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n return resolve(configDir, \"xiaozhi.cache.json\");\n } catch (error) {\n // 在某些测试环境中 process.cwd() 可能不可用,使用默认路径\n const configDir = process.env.XIAOZHI_CONFIG_DIR || \"/tmp\";\n return resolve(configDir, \"xiaozhi.cache.json\");\n }\n }\n\n /**\n * 确保缓存文件存在,如不存在则创建\n */\n async ensureCacheFile(): Promise<void> {\n try {\n if (!existsSync(this.cachePath)) {\n // 确保缓存文件的目录存在\n const cacheDir = dirname(this.cachePath);\n if (!existsSync(cacheDir)) {\n mkdirSync(cacheDir, { recursive: true });\n this.logger.debug(`[CacheManager] 已创建缓存目录: ${cacheDir}`);\n }\n\n this.logger.debug(\"[CacheManager] 缓存文件不存在,创建初始缓存文件\");\n const initialCache = await this.createInitialCache();\n await this.saveCache(initialCache);\n this.logger.info(`[CacheManager] 已创建缓存文件: ${this.cachePath}`);\n }\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 创建缓存文件失败: ${error instanceof Error ? error.message : String(error)}`\n );\n // 不抛出异常,确保不影响主流程\n }\n }\n\n /**\n * 创建初始缓存结构\n */\n private async createInitialCache(): Promise<MCPToolsCache> {\n const now = this.formatTimestamp();\n return {\n version: this.CACHE_VERSION,\n mcpServers: {},\n metadata: {\n lastGlobalUpdate: now,\n totalWrites: 0,\n createdAt: now,\n },\n };\n }\n\n /**\n * 写入缓存条目\n * @param serverName 服务名称\n * @param tools 工具列表\n * @param config 服务配置\n */\n async writeCacheEntry(\n serverName: string,\n tools: Tool[],\n config: MCPServiceConfig\n ): Promise<void> {\n try {\n this.logger.debug(`[CacheManager] 开始写入缓存: ${serverName}`);\n\n // 确保缓存文件存在\n await this.ensureCacheFile();\n\n // 加载现有缓存\n const cache = await this.loadExistingCache();\n\n // 生成配置哈希\n const configHash = this.generateConfigHash(config);\n\n // 创建缓存条目\n const cacheEntry: MCPToolsCacheEntry = {\n tools: tools.map((tool) => ({\n name: tool.name,\n description: tool.description || \"\",\n inputSchema: tool.inputSchema,\n })),\n lastUpdated: this.formatTimestamp(),\n serverConfig: { ...config }, // 深拷贝配置\n configHash,\n version: this.CACHE_ENTRY_VERSION,\n };\n\n // 更新缓存\n cache.mcpServers[serverName] = cacheEntry;\n cache.metadata.lastGlobalUpdate = this.formatTimestamp();\n cache.metadata.totalWrites += 1;\n\n // 保存缓存\n await this.saveCache(cache);\n\n this.logger.debug(\n `[CacheManager] 缓存写入成功: ${serverName}, 工具数量: ${tools.length}`\n );\n } catch (error) {\n // 记录错误但不抛出异常,确保不影响主流程\n this.logger.warn(\n `[CacheManager] 缓存写入失败: ${serverName}, 错误: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * 加载现有缓存\n */\n public async loadExistingCache(): Promise<MCPToolsCache> {\n try {\n if (!existsSync(this.cachePath)) {\n return await this.createInitialCache();\n }\n\n const cacheData = readFileSync(this.cachePath, \"utf8\");\n const cache: unknown = JSON.parse(cacheData);\n\n // 验证缓存结构\n if (!this.validateCacheStructure(cache)) {\n this.logger.warn(\"[CacheManager] 缓存文件结构无效,重新创建\");\n return await this.createInitialCache();\n }\n\n return cache;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 加载缓存失败,创建新缓存: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return await this.createInitialCache();\n }\n }\n\n /**\n * 保存缓存到文件(原子写入)\n */\n public async saveCache(cache: MCPToolsCache): Promise<void> {\n const cacheContent = JSON.stringify(cache, null, 2);\n await this.atomicWrite(this.cachePath, cacheContent);\n }\n\n /**\n * 原子写入文件\n * 使用临时文件确保写入操作的原子性\n */\n private async atomicWrite(filePath: string, data: string): Promise<void> {\n const tempPath = `${filePath}.tmp`;\n try {\n // 写入临时文件\n writeFileSync(tempPath, data, \"utf8\");\n // 原子性重命名\n renameSync(tempPath, filePath);\n } catch (error) {\n // 清理临时文件\n try {\n if (existsSync(tempPath)) {\n writeFileSync(tempPath, \"\", \"utf8\"); // 清空后删除\n }\n } catch {\n // 忽略清理错误\n }\n throw error;\n }\n }\n\n /**\n * 生成配置哈希\n * 用于快速检测配置变更\n */\n private generateConfigHash(config: MCPServiceConfig): string {\n try {\n return createHash(\"sha256\").update(JSON.stringify(config)).digest(\"hex\");\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 生成配置哈希失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return \"\";\n }\n }\n\n /**\n * 验证缓存数据结构\n * 使用类型谓词确保类型安全\n */\n private validateCacheStructure(cache: unknown): cache is MCPToolsCache {\n try {\n if (!cache || typeof cache !== \"object\") {\n return false;\n }\n\n const cacheObj = cache as Record<string, unknown>;\n const metadata = cacheObj.metadata as Record<string, unknown>;\n\n return (\n typeof cacheObj.version === \"string\" &&\n typeof cacheObj.mcpServers === \"object\" &&\n cacheObj.mcpServers !== null &&\n cacheObj.metadata !== null &&\n cacheObj.metadata !== undefined &&\n typeof metadata === \"object\" &&\n metadata !== null &&\n typeof metadata.lastGlobalUpdate === \"string\" &&\n typeof metadata.totalWrites === \"number\" &&\n typeof metadata.createdAt === \"string\"\n );\n } catch {\n return false;\n }\n }\n\n /**\n * 获取缓存统计信息\n */\n async getStats(): Promise<CacheStats | null> {\n try {\n const cache = await this.loadExistingCache();\n const stats: CacheStats = {\n totalWrites: cache.metadata.totalWrites,\n lastUpdate: cache.metadata.lastGlobalUpdate,\n serverCount: Object.keys(cache.mcpServers).length,\n cacheFileSize: existsSync(this.cachePath)\n ? readFileSync(this.cachePath, \"utf8\").length\n : 0,\n };\n return stats;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 获取缓存统计失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return null;\n }\n }\n\n /**\n * 获取缓存文件路径(用于测试和调试)\n */\n getFilePath(): string {\n return this.cachePath;\n }\n\n /**\n * 获取所有缓存中的工具\n * 返回所有服务中的所有工具列表\n */\n async getAllCachedTools(): Promise<Tool[]> {\n try {\n const cache = await this.loadExistingCache();\n const allTools: Tool[] = [];\n\n // 遍历所有服务,收集所有工具\n for (const [serverName, cacheEntry] of Object.entries(cache.mcpServers)) {\n for (const tool of cacheEntry.tools) {\n // 为每个工具添加服务名称信息\n allTools.push({\n ...tool,\n name: `${serverName}__${tool.name}`, // 格式: serviceName__toolName\n });\n }\n }\n\n this.logger.debug(\n `[CacheManager] 获取到所有缓存工具,共 ${allTools.length} 个`\n );\n return allTools;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 获取所有缓存工具失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return [];\n }\n }\n\n // ==================== CustomMCP 结果缓存管理方法 ====================\n\n /**\n * 写入 CustomMCP 工具执行结果缓存\n */\n async writeCustomMCPResult(\n toolName: string,\n arguments_: Record<string, unknown>,\n result: ToolCallResult,\n status: TaskStatus = \"completed\",\n taskId?: string,\n ttl = 300000\n ): Promise<void> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n // 创建缓存条目\n const cacheEntry: EnhancedToolResultCache = {\n result,\n timestamp: new Date().toISOString(),\n ttl,\n status,\n consumed: false,\n taskId,\n retryCount: 0,\n };\n\n // 确保customMCPResults存在\n if (!cache.customMCPResults) {\n cache.customMCPResults = {};\n }\n\n cache.customMCPResults[cacheKey] = cacheEntry;\n await this.saveExtendedCache(cache);\n\n this.logger.debug(\n `[CacheManager] 写入CustomMCP结果缓存: ${toolName}, 状态: ${status}`\n );\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 写入CustomMCP结果缓存失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * 读取 CustomMCP 工具执行结果缓存\n */\n async readCustomMCPResult(\n toolName: string,\n arguments_: Record<string, unknown>\n ): Promise<EnhancedToolResultCache | null> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return null;\n }\n\n const cacheEntry = cache.customMCPResults[cacheKey];\n\n // 检查是否过期\n const now = Date.now();\n const cachedTime = new Date(cacheEntry.timestamp).getTime();\n if (now - cachedTime > cacheEntry.ttl) {\n this.logger.debug(`[CacheManager] 缓存已过期: ${toolName}`);\n return null;\n }\n\n return cacheEntry;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 读取CustomMCP结果缓存失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return null;\n }\n }\n\n /**\n * 更新 CustomMCP 缓存状态\n */\n async updateCustomMCPStatus(\n toolName: string,\n arguments_: Record<string, unknown>,\n newStatus: TaskStatus,\n result?: ToolCallResult,\n error?: string\n ): Promise<boolean> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return false;\n }\n\n const cacheEntry = cache.customMCPResults[cacheKey];\n const oldStatus = cacheEntry.status;\n\n // 更新状态\n cacheEntry.status = newStatus;\n cacheEntry.timestamp = new Date().toISOString();\n\n // 更新结果或错误信息\n if (result) {\n cacheEntry.result = result;\n }\n\n if (error && newStatus === \"failed\") {\n cacheEntry.result = {\n content: [{ type: \"text\", text: `任务失败: ${error}` }],\n };\n cacheEntry.consumed = true; // 失败的任务自动标记为已消费\n }\n\n // 特殊状态处理\n if (newStatus === \"completed\") {\n cacheEntry.consumed = false; // 完成的任务初始状态为未消费\n }\n\n await this.saveExtendedCache(cache);\n\n this.logger.debug(\n `[CacheManager] 更新缓存状态: ${toolName} ${oldStatus} -> ${newStatus}`\n );\n return true;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 更新CustomMCP缓存状态失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return false;\n }\n }\n\n /**\n * 标记 CustomMCP 缓存为已消费\n */\n async markCustomMCPAsConsumed(\n toolName: string,\n arguments_: Record<string, unknown>\n ): Promise<boolean> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return false;\n }\n\n const cacheEntry = cache.customMCPResults[cacheKey];\n if (cacheEntry.consumed) {\n return true;\n }\n\n cacheEntry.consumed = true;\n cacheEntry.timestamp = new Date().toISOString();\n\n await this.saveExtendedCache(cache);\n\n this.logger.debug(`[CacheManager] 标记缓存为已消费: ${toolName}`);\n return true;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 标记CustomMCP缓存为已消费失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return false;\n }\n }\n\n /**\n * 删除 CustomMCP 缓存条目\n */\n async deleteCustomMCPResult(\n toolName: string,\n arguments_: Record<string, unknown>\n ): Promise<boolean> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return false;\n }\n\n delete cache.customMCPResults[cacheKey];\n await this.saveExtendedCache(cache);\n\n this.logger.debug(`[CacheManager] 删除缓存条目: ${toolName}`);\n return true;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 删除CustomMCP缓存条目失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return false;\n }\n }\n\n /**\n * 批量清理 CustomMCP 缓存\n */\n async cleanupCustomMCPResults(): Promise<{ cleaned: number; total: number }> {\n try {\n const cache = await this.loadExtendedCache();\n\n if (!cache.customMCPResults) {\n return { cleaned: 0, total: 0 };\n }\n\n const entries = Object.entries(cache.customMCPResults);\n let cleanedCount = 0;\n\n for (const [cacheKey, cacheEntry] of entries) {\n if (shouldCleanupCache(cacheEntry)) {\n delete cache.customMCPResults[cacheKey];\n cleanedCount++;\n }\n }\n\n if (cleanedCount > 0) {\n await this.saveExtendedCache(cache);\n this.logger.info(\n `[CacheManager] 清理CustomMCP缓存: ${cleanedCount}/${entries.length}`\n );\n }\n\n return { cleaned: cleanedCount, total: entries.length };\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 清理CustomMCP缓存失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return { cleaned: 0, total: 0 };\n }\n }\n\n /**\n * 获取 CustomMCP 缓存统计信息\n */\n async getCustomMCPStatistics(): Promise<CacheStatistics> {\n try {\n const cache = await this.loadExtendedCache();\n\n if (!cache.customMCPResults) {\n return {\n totalEntries: 0,\n pendingTasks: 0,\n completedTasks: 0,\n failedTasks: 0,\n consumedEntries: 0,\n cacheHitRate: 0,\n lastCleanupTime: new Date().toISOString(),\n memoryUsage: 0,\n };\n }\n\n const entries = Object.values(cache.customMCPResults);\n const totalEntries = entries.length;\n const pendingTasks = entries.filter((e) => e.status === \"pending\").length;\n const completedTasks = entries.filter(\n (e) => e.status === \"completed\"\n ).length;\n const failedTasks = entries.filter((e) => e.status === \"failed\").length;\n const consumedEntries = entries.filter((e) => e.consumed).length;\n\n // 计算缓存命中率\n const cacheHitRate =\n completedTasks > 0 ? (consumedEntries / completedTasks) * 100 : 0;\n\n // 估算内存使用\n const memoryUsage = JSON.stringify(cache.customMCPResults).length;\n\n return {\n totalEntries,\n pendingTasks,\n completedTasks,\n failedTasks,\n consumedEntries,\n cacheHitRate,\n lastCleanupTime: new Date().toISOString(),\n memoryUsage,\n };\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 获取CustomMCP缓存统计失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return {\n totalEntries: 0,\n pendingTasks: 0,\n completedTasks: 0,\n failedTasks: 0,\n consumedEntries: 0,\n cacheHitRate: 0,\n lastCleanupTime: new Date().toISOString(),\n memoryUsage: 0,\n };\n }\n }\n\n /**\n * 加载扩展缓存(包含 CustomMCP 结果)\n */\n async loadExtendedCache(): Promise<ExtendedMCPToolsCache> {\n try {\n const cache = await this.loadExistingCache();\n return cache as ExtendedMCPToolsCache;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 加载扩展缓存失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return {\n version: this.CACHE_VERSION,\n mcpServers: {},\n metadata: {\n lastGlobalUpdate: this.formatTimestamp(),\n totalWrites: 0,\n createdAt: this.formatTimestamp(),\n },\n customMCPResults: {},\n };\n }\n }\n\n /**\n * 保存扩展缓存(包含 CustomMCP 结果)\n */\n async saveExtendedCache(cache: ExtendedMCPToolsCache): Promise<void> {\n await this.saveCache(cache);\n }\n\n /**\n * 启动清理定时器\n */\n private startCleanupTimer(): void {\n this.cleanupInterval = setInterval(() => {\n this.cleanupCustomMCPResults().catch((error) => {\n this.logger.warn(`[CacheManager] 自动清理失败: ${error}`);\n });\n }, this.CLEANUP_INTERVAL);\n\n this.logger.debug(\n `[CacheManager] 启动清理定时器,间隔: ${this.CLEANUP_INTERVAL}ms`\n );\n }\n\n /**\n * 停止清理定时器\n */\n public stopCleanupTimer(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n this.logger.debug(\"[CacheManager] 停止清理定时器\");\n }\n }\n\n /**\n * 清理资源\n */\n public cleanup(): void {\n this.stopCleanupTimer();\n this.logger.debug(\"[CacheManager] 清理资源完成\");\n }\n}\n","/**\n * Hono Context 类型扩展\n * 为 Hono Context 添加项目特定的变量类型定义\n */\n\nimport type { EndpointManager } from \"@/lib/endpoint/index.js\";\nimport type { MCPServiceManager } from \"@/lib/mcp\";\nimport type { Logger } from \"@root/Logger.js\";\nimport type { Context } from \"hono\";\nimport { Hono } from \"hono\";\nimport type {\n HandlerDependencies,\n MCPEndpointApiHandler,\n} from \"../routes/index.js\";\n\n/**\n * WebServer 实例类型定义\n * 避免与 WebServer 实现类的循环引用\n * 使用类型断言的方式来定义接口\n */\nexport interface IWebServer {\n /**\n * 获取小智连接管理器实例\n * 在 endpointManagerMiddleware 中使用\n * WebServer 启动后始终返回有效的连接管理器实例\n * 如果在启动前调用,会抛出错误\n */\n getEndpointManager(): EndpointManager;\n\n /**\n * 获取 MCP 服务管理器实例\n * 在 mcpServiceManagerMiddleware 中使用\n * WebServer 启动后始终返回有效的服务管理器实例\n * 如果在启动前调用,会抛出错误\n */\n getMCPServiceManager(): MCPServiceManager;\n}\n\n/**\n * 扩展 Hono Context 的 Variables 类型\n * 定义了项目中所有通过中间件注入的变量\n */\nexport type AppContextVariables = {\n /**\n * 日志记录器实例\n * 由 loggerMiddleware 注入\n */\n logger?: Logger;\n\n /**\n * MCP 服务管理器实例\n * 由 mcpServiceManagerMiddleware 注入\n */\n mcpServiceManager?: MCPServiceManager;\n\n /**\n * 路由处理器依赖\n * 由 RouteManager 注入\n */\n dependencies?: HandlerDependencies;\n\n /**\n * WebServer 实例引用\n * 用于获取运行时依赖\n */\n webServer?: IWebServer;\n\n /**\n * 小智连接管理器实例\n * 由 endpointManagerMiddleware 注入\n * WebServer 启动后始终可用的连接管理器实例\n * 可能为 null(初始化失败时)\n */\n endpointManager: EndpointManager | null;\n\n /**\n * 端点处理器实例\n * 由 endpointsMiddleware 注入\n */\n endpointHandler?: MCPEndpointApiHandler | null;\n};\n\n/**\n * 扩展的 Hono Context 类型\n * 包含项目中所有自定义变量\n */\nexport type AppContext = {\n Variables: AppContextVariables;\n};\n\n/**\n * 创建类型化的 Hono 实例\n * 使用这个类型来创建新的 Hono 应用,确保 Context 类型安全\n */\nexport const createApp = (): Hono<AppContext> => {\n return new Hono<AppContext>();\n};\n\n/**\n * 从 Context 中获取日志记录器的类型安全方法\n */\nexport const getLogger = (c: Context<AppContext>): Logger | undefined => {\n return c.get(\"logger\");\n};\n\n/**\n * 从 Context 中获取 MCPServiceManager 的类型安全方法\n */\nexport const getMCPServiceManager = (\n c: Context<AppContext>\n): MCPServiceManager | undefined => {\n return c.get(\"mcpServiceManager\");\n};\n\n/**\n * 要求必须存在 MCPServiceManager 的类型安全方法\n */\nexport const requireMCPServiceManager = (\n c: Context<AppContext>\n): MCPServiceManager => {\n const serviceManager = c.get(\"mcpServiceManager\");\n\n if (!serviceManager) {\n throw new Error(\n \"MCPServiceManager 未初始化,请检查 mcpServiceManagerMiddleware 是否正确配置\"\n );\n }\n\n return serviceManager;\n};\n","/**\n * MCP 服务管理器全局单例\n * 用于在 API Handler 中获取服务状态\n */\n\nimport { configManager } from \"@/lib/config/manager\";\nimport { MCPServiceManager } from \"@/lib/mcp\";\n\n/**\n * MCP 服务管理器全局单例类\n */\nclass MCPServiceManagerSingleton {\n private static instance: MCPServiceManager | null = null;\n\n /**\n * 获取 MCP 服务管理器单例实例\n */\n static getInstance(): MCPServiceManager {\n if (!MCPServiceManagerSingleton.instance) {\n const configs = configManager.getMcpServers();\n MCPServiceManagerSingleton.instance = new MCPServiceManager(configs);\n }\n return MCPServiceManagerSingleton.instance;\n }\n\n /**\n * 重置单例实例(主要用于测试)\n */\n static reset(): void {\n MCPServiceManagerSingleton.instance = null;\n }\n}\n\n/**\n * 导出单例实例供外部使用\n */\nexport const mcpServiceManager = MCPServiceManagerSingleton.getInstance();\n\nexport default MCPServiceManagerSingleton;\n"],"mappings":"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","names":["copyFileSync","existsSync","readFileSync","writeFileSync","dirname","resolve","fileURLToPath","EventEmitter","fs","path","chalk","pino","z","LogLevelSchema","z","formatDateTime","date","year","month","day","hours","minutes","seconds","__name","Logger","level","normalizedLevel","result","streams","consoleStream","pino","_label","number","err","levelMap","chalk","chunk","logObj","message","content","timestamp","levelInfo","text","coloredLevel","argsStr","arg","projectDir","enable","messageOrObj","args","errorArgs","enhancedObj","obj","enhanced","key","value","logDir","logName","i","oldFile","newFile","firstRotatedFile","maxSize","maxFiles","globalLogger","globalLogLevel","getLogger","globalLogger","Logger","globalLogLevel","__name","logger","getLogger","EventBus","EventEmitter","__name","logger","error","eventName","listenerCount","data","listener","onceListener","stats","stat","sum","count","eventBusInstance","getEventBus","TypeFieldNormalizer","normalizeTypeField","config","normalizedConfig","originalType","normalizedType","convertToKebabCase","__name","str","getMcpServerCommunicationType","serverConfig","__name","validateMcpServerConfig","serverName","serverConfig","communicationType","getMcpServerCommunicationType","config","error","__name","commentJson","dayjs","commentJson","createJson5Writer","content","parsedData","data","__name","parseJson5","__dirname","dirname","fileURLToPath","DEFAULT_CONNECTION_CONFIG","ConfigManager","_ConfigManager","__name","getEventBus","possiblePaths","resolve","path","existsSync","configDir","configFileNames","fileName","filePath","format","targetFileName","configPath","copyFileSync","error","configFileFormat","configData","readFileSync","config","parseJson5","createJson5Writer","configObj","endpoint","serverName","serverConfig","normalizedConfig","TypeFieldNormalizer","validation","validateMcpServerConfig","toolName","ep","currentEndpoints","newEndpoints","relatedTools","tool","toolIndex","t","newConfig","currentServers","name","toolsConfig","platformName","platformConfig","validServerNames","invalidServerNames","enabled","description","configContent","writeFileSync","connectionConfig","arg1","arg2","arg3","callTime","incrementUsageCount","serviceName","interval","timeout","modelScopeConfig","apiKey","customMCPConfig","tools","handler","existingNames","newTools","updatedTool","webServer","webUIConfig","port","cozeConfig","toolConfig","currentUsageCount","currentLastUsedTime","dayjs","logPrefix","customTools","updatedTools","toolKey","updatePromise","lockCount","toolCallLogConfig","configManager","EventEmitter","isAbsolute","resolve","MCPTransportType","isValidToolJSONSchema","obj","__name","ensureToolJSONSchema","schema","ToolCallError","code","message","data","__name","inferTransportTypeFromUrl","url","options","pathname","__name","inferTransportTypeFromConfig","config","TypeFieldNormalizer","inferredType","validateToolCallParams","params","opts","ToolCallError","paramsObj","argsObj","error","isModelScopeURL","url","__name","coze_exports","__export","CozeApiService","config_default","createCozeClient","config_default","__reExport","coze_exports","api_star","createCozeClient","token","language","env","config_default","__name","NodeCache","CozeApiService","__name","token","createCozeClient","NodeCache","cacheKey","cached","workspaces","params","workspace_id","page_num","page_size","result","workflowId","parameters","pattern","keysToDelete","key","stats","keys","totalRequests","hitRate","createHash","generateCacheKey","toolName","arguments_","argsHash","createHash","__name","isCacheExpired","timestamp","ttl","cachedTime","__name","shouldCleanupCache","cache","now","DEFAULT_CONFIG","TimeoutError","_TimeoutError","__name","message","createTimeoutResponse","taskId","toolName","getToolSpecificTimeoutMessage","getDefaultTimeoutMessage","toolMessages","isProxyHandler","handler","__name","CustomMCPHandler","DEFAULT_CONFIG","cacheManager","mcpServiceManager","logger","MCPCacheManager","token","configManager","CozeApiService","getEventBus","data","error","tools","customTools","tool","platformInfo","ensureToolJSONSchema","toolName","arguments_","options","completedResult","timeout","result","TimeoutError","taskId","createTimeoutResponse","_","reject","cacheKey","cache","cached","isCacheExpired","workflowData","responseData","config","cozeApiService","workflowResult","shouldCleanupCache","generateCacheKey","cacheData","fs","path","tmpdir","PathUtils","__name","tmpdir","pino","ToolCallLogger","__name","config","configDir","baseDir","PathUtils","logFilePath","streams","chunk","logObj","message","pino","_label","number","toolName","success","duration","lines","line","recordsToRemove","linesToKeep","newContent","record","MCPMessageHandler","__name","serviceManager","logger","message","isNotification","error","params","id","supportedVersions","clientVersion","responseVersion","tool","validatedParams","validateToolCallParams","result","resources","prompts","errorCode","WebSocket","WebSocketServer","MCPServiceManager","EventEmitter","__name","getEventBus","configs","cachePath","MCPCacheManager","CustomMCPHandler","toolCallLogConfig","configManager","configDir","ToolCallLogger","MCPMessageHandler","data","configEntries","startPromises","serviceName","error","results","successCount","failureCount","failedServices","result","config","service","MCPService","tools","tool","toolKey","allTools","serviceTools","customTools","customTool","toolName","toolInfo","arguments_","options","startTime","logServerName","originalToolName","isSuccess","currentTime","action","activeLocks","name","connectedServices","isModelScopeURL","originalConfig","enhancedConfig","modelScopeApiKey","serviceUrl","nameOrConfig","finalConfig","currentServerConfigs","currentToolsConfig","newToolsConfig","currentToolConfig","currentToolNames","t","removedTools","addedTools","updatedTools","current","updated","currentConfig","newConfig","currentKeys","newKeys","key","currentTool","newTool","initialDelay","delay","existingTimer","timer","currentDelay","nextDelay","acc","char","adapter","successfulAdapters","failedAdapters","errorMessage","connections","adapterName","conn","serviceStatus","customMCPToolCount","customToolNames","totalTools","availableTools","status","message","response","Client","SSEClientTransport","StdioClientTransport","StreamableHTTPClientTransport","EventSource","EventSource","createTransport","config","createStdioTransport","createSSETransport","createStreamableHTTPTransport","__name","StdioClientTransport","url","options","createSSEOptions","SSEClientTransport","createStreamableHTTPTransport","config","url","options","createStreamableHTTPOptions","StreamableHTTPClientTransport","__name","createSSEOptions","createStreamableHTTPOptions","config","options","__name","validateConfig","MCPTransportType","getSupportedTypes","TransportFactory","createTransport","MCPService","__name","getEventBus","config","inferTransportTypeFromConfig","TransportFactory","resolve","reject","error","Client","tools","tool","name","arguments_","createHash","existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","dirname","resolve","Hono","dayjs","MCPCacheManager","__name","customCachePath","logger","dayjs","configDir","resolve","existsSync","cacheDir","dirname","mkdirSync","initialCache","error","now","serverName","tools","config","cache","configHash","cacheEntry","tool","cacheData","readFileSync","cacheContent","filePath","data","tempPath","writeFileSync","renameSync","createHash","cacheObj","metadata","allTools","toolName","arguments_","result","status","taskId","ttl","cacheKey","generateCacheKey","cachedTime","newStatus","oldStatus","entries","cleanedCount","shouldCleanupCache","totalEntries","pendingTasks","e","completedTasks","failedTasks","consumedEntries","cacheHitRate","memoryUsage","MCPServiceManagerSingleton","_MCPServiceManagerSingleton","__name","configs","configManager","MCPServiceManager","mcpServiceManager","MCPServiceManagerSingleton_default"]}
|
|
1
|
+
{"version":3,"sources":["../../../apps/backend/lib/mcp/manager.ts","../../../apps/backend/services/EventBus.ts","../../../apps/backend/Logger.ts","../../../apps/backend/lib/coze/index.ts","../../../apps/backend/lib/coze/config.ts","../../../apps/backend/lib/coze/client.ts","../../../apps/backend/lib/coze/service.ts","../../../apps/backend/lib/mcp/types.ts","../../../apps/backend/types/mcp.ts","../../../apps/backend/types/timeout.ts","../../../apps/backend/lib/mcp/custom.ts","../../../apps/backend/lib/mcp/log.ts","../../../apps/backend/utils/PathUtils.ts","../../../apps/backend/lib/mcp/message.ts","../../../apps/backend/lib/mcp/transports/WebSocketAdapter.ts","../../../apps/backend/lib/mcp/connection.ts","../../../apps/backend/lib/mcp/transport-factory.ts","../../../apps/backend/utils/TypeFieldNormalizer.ts","../../../apps/backend/lib/mcp/utils.ts","../../../apps/backend/lib/mcp/cache.ts","../../../apps/backend/types/hono.context.ts","../../../apps/backend/managers/MCPServiceManagerSingleton.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * MCP 服务管理器\n * 使用 MCPService 实例管理多个 MCP 服务\n * 专注于实例管理、工具聚合和路由调用\n */\n\nimport { EventEmitter } from \"node:events\";\nimport { MCPService } from \"@/lib/mcp\";\nimport { MCPCacheManager } from \"@/lib/mcp\";\nimport type {\n CustomMCPTool,\n JSONSchema,\n MCPServiceConfig,\n ManagerStatus,\n ToolCallResult,\n ToolInfo,\n UnifiedServerConfig,\n UnifiedServerStatus,\n} from \"@/lib/mcp/types\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { getEventBus } from \"@root/services/EventBus.js\";\nimport type { MCPMessage } from \"@root/types/mcp.js\";\nimport { isModelScopeURL } from \"@xiaozhi-client/config\";\nimport type { MCPToolConfig } from \"@xiaozhi-client/config\";\nimport { configManager } from \"@xiaozhi-client/config\";\nimport { CustomMCPHandler } from \"./custom.js\";\nimport { ToolCallLogger } from \"./log.js\";\nimport { MCPMessageHandler } from \"./message.js\";\nimport { ConnectionState, type TransportAdapter } from \"./transports/index.js\";\n\nexport class MCPServiceManager extends EventEmitter {\n private services: Map<string, MCPService> = new Map();\n private configs: Record<string, MCPServiceConfig> = {};\n private tools: Map<string, ToolInfo> = new Map(); // 缓存工具信息,保持向后兼容\n private customMCPHandler: CustomMCPHandler; // CustomMCP 工具处理器\n private cacheManager: MCPCacheManager; // 缓存管理器\n private eventBus = getEventBus(); // 事件总线\n private toolCallLogger: ToolCallLogger; // 工具调用记录器\n private retryTimers: Map<string, NodeJS.Timeout> = new Map(); // 重试定时器\n private failedServices: Set<string> = new Set(); // 失败的服务集合\n\n // 新增:传输适配器管理\n private transportAdapters: Map<string, TransportAdapter> = new Map();\n private messageHandler: MCPMessageHandler;\n\n // 新增:服务器状态管理(从 UnifiedMCPServer 移入)\n private isRunning = false;\n private config: UnifiedServerConfig;\n\n /**\n * 创建 MCPServiceManager 实例\n * @param configs 可选的初始服务配置或服务器配置\n */\n constructor(\n configs?: Record<string, MCPServiceConfig> | UnifiedServerConfig\n ) {\n super();\n\n // 处理参数,支持 UnifiedServerConfig 格式\n if (configs && this.isUnifiedServerConfig(configs)) {\n // UnifiedServerConfig 格式\n this.config = {\n name: \"MCPServiceManager\",\n enableLogging: true,\n logLevel: \"info\",\n ...configs,\n };\n this.configs = configs.configs || {};\n } else {\n // 原有的 configs 格式\n this.config = {\n name: \"MCPServiceManager\",\n enableLogging: true,\n logLevel: \"info\",\n };\n this.configs = configs || {};\n }\n\n // 在测试环境中使用临时目录,避免在项目根目录创建缓存文件\n const isTestEnv =\n process.env.NODE_ENV === \"test\" || process.env.VITEST === \"true\";\n const cachePath = isTestEnv\n ? `/tmp/xiaozhi-test-${Date.now()}-${Math.random()\n .toString(36)\n .substring(2, 11)}/xiaozhi.cache.json`\n : undefined;\n\n this.cacheManager = new MCPCacheManager(cachePath);\n this.customMCPHandler = new CustomMCPHandler(this.cacheManager, this);\n\n // 初始化工具调用记录器\n const toolCallLogConfig = configManager.getToolCallLogConfig();\n const configDir = configManager.getConfigDir();\n this.toolCallLogger = new ToolCallLogger(toolCallLogConfig, configDir);\n\n // 设置事件监听器\n this.setupEventListeners();\n\n // 初始化消息处理器(确保在其他组件初始化完成后)\n this.messageHandler = new MCPMessageHandler(this);\n }\n\n /**\n * 设置事件监听器\n */\n private setupEventListeners(): void {\n // 监听MCP服务连接成功事件\n this.eventBus.onEvent(\"mcp:service:connected\", async (data) => {\n await this.handleServiceConnected(data);\n });\n\n // 监听MCP服务断开连接事件\n this.eventBus.onEvent(\"mcp:service:disconnected\", async (data) => {\n await this.handleServiceDisconnected(data);\n });\n\n // 监听MCP服务连接失败事件\n this.eventBus.onEvent(\"mcp:service:connection:failed\", async (data) => {\n await this.handleServiceConnectionFailed(data);\n });\n }\n\n /**\n * 处理MCP服务连接成功事件\n */\n private async handleServiceConnected(data: {\n serviceName: string;\n tools: Tool[];\n connectionTime: Date;\n }): Promise<void> {\n console.debug(`服务 ${data.serviceName} 连接成功,开始刷新工具缓存`);\n\n try {\n // 获取最新的工具列表\n const service = this.services.get(data.serviceName);\n if (service) {\n // 重新初始化CustomMCPHandler\n await this.refreshCustomMCPHandlerPublic();\n\n console.info(`服务 ${data.serviceName} 工具缓存刷新完成`);\n }\n } catch (error) {\n console.error(`刷新服务 ${data.serviceName} 工具缓存失败:`, error);\n }\n }\n\n /**\n * 处理MCP服务断开连接事件\n */\n private async handleServiceDisconnected(data: {\n serviceName: string;\n reason?: string;\n disconnectionTime: Date;\n }): Promise<void> {\n console.info(\n `服务 ${data.serviceName} 断开连接,原因: ${data.reason || \"未知\"}`\n );\n\n try {\n // 更新工具缓存\n await this.refreshToolsCache();\n\n // 重新初始化CustomMCPHandler\n await this.refreshCustomMCPHandlerPublic();\n\n console.info(`服务 ${data.serviceName} 断开连接处理完成`);\n } catch (error) {\n console.error(`服务 ${data.serviceName} 断开连接处理失败:`, error);\n }\n }\n\n /**\n * 处理MCP服务连接失败事件\n */\n private async handleServiceConnectionFailed(data: {\n serviceName: string;\n error: Error;\n attempt: number;\n }): Promise<void> {\n try {\n await this.refreshCustomMCPHandlerPublic();\n } catch (error) {\n console.error(\"刷新CustomMCPHandler失败:\", error);\n }\n }\n\n /**\n * 启动所有 MCP 服务\n */\n async startAllServices(): Promise<void> {\n console.debug(\"[MCPManager] 正在启动所有 MCP 服务...\");\n\n // 初始化 CustomMCP 处理器\n try {\n this.customMCPHandler.initialize();\n console.debug(\"[MCPManager] CustomMCP 处理器初始化完成\");\n } catch (error) {\n console.error(\"[MCPManager] CustomMCP 处理器初始化失败:\", error);\n // CustomMCP 初始化失败不应该阻止标准 MCP 服务启动\n }\n\n const configEntries = Object.entries(this.configs);\n if (configEntries.length === 0) {\n console.warn(\n \"[MCPManager] 没有配置任何 MCP 服务,请使用 addServiceConfig() 添加服务配置\"\n );\n // 即使没有标准 MCP 服务,也可能有 CustomMCP 工具\n return;\n }\n\n // 记录启动开始\n console.info(\n `[MCPManager] 开始并行启动 ${configEntries.length} 个 MCP 服务`\n );\n\n // 并行启动所有服务,实现服务隔离\n const startPromises = configEntries.map(async ([serviceName]) => {\n try {\n await this.startService(serviceName);\n return { serviceName, success: true, error: null };\n } catch (error) {\n return {\n serviceName,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n });\n\n // 等待所有服务启动完成\n const results = await Promise.allSettled(startPromises);\n\n // 统计启动结果\n let successCount = 0;\n let failureCount = 0;\n const failedServices: string[] = [];\n\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n if (result.value.success) {\n successCount++;\n } else {\n failureCount++;\n failedServices.push(result.value.serviceName);\n }\n } else {\n failureCount++;\n }\n }\n\n // 记录启动完成统计\n console.info(\n `[MCPManager] 服务启动完成 - 成功: ${successCount}, 失败: ${failureCount}`\n );\n\n // 记录失败的服务列表\n if (failedServices.length > 0) {\n console.warn(\n `[MCPManager] 以下服务启动失败: ${failedServices.join(\", \")}`\n );\n\n // 如果所有服务都失败了,发出警告但系统继续运行以便重试\n if (failureCount === configEntries.length) {\n console.warn(\n \"[MCPManager] 所有 MCP 服务启动失败,但系统将继续运行以便重试\"\n );\n }\n }\n\n // 启动失败服务重试机制\n if (failedServices.length > 0) {\n this.scheduleFailedServicesRetry(failedServices);\n }\n }\n\n /**\n * 启动单个 MCP 服务\n */\n async startService(serviceName: string): Promise<void> {\n const config = this.configs[serviceName];\n if (!config) {\n throw new Error(`未找到服务配置: ${serviceName}`);\n }\n\n try {\n // 如果服务已存在,先停止它\n if (this.services.has(serviceName)) {\n await this.stopService(serviceName);\n }\n\n // 创建 MCPService 实例\n const service = new MCPService(config);\n\n // 连接到服务\n await service.connect();\n\n // 存储服务实例\n this.services.set(serviceName, service);\n\n // 更新工具缓存\n await this.refreshToolsCache();\n\n // 注意:工具缓存刷新现在通过事件监听器自动处理,不需要在这里手动调用\n // MCPService.connect() 成功后会发射 mcp:service:connected 事件\n // 事件监听器会自动触发工具缓存刷新和CustomMCPHandler刷新\n\n const tools = service.getTools();\n console.debug(\n `[MCPManager] ${serviceName} 服务启动成功,加载了 ${tools.length} 个工具:`,\n tools.map((t) => t.name).join(\", \")\n );\n } catch (error) {\n console.error(\n `[MCPManager] 启动 ${serviceName} 服务失败:`,\n (error as Error).message\n );\n // 清理可能的部分状态\n this.services.delete(serviceName);\n throw error;\n }\n }\n\n /**\n * 停止单个服务\n */\n async stopService(serviceName: string): Promise<void> {\n console.info(`[MCPManager] 停止 MCP 服务: ${serviceName}`);\n\n const service = this.services.get(serviceName);\n if (!service) {\n console.warn(`[MCPManager] 服务 ${serviceName} 不存在或未启动`);\n return;\n }\n\n try {\n await service.disconnect();\n this.services.delete(serviceName);\n\n // 更新工具缓存\n await this.refreshToolsCache();\n\n console.info(`[MCPManager] ${serviceName} 服务已停止`);\n } catch (error) {\n console.error(\n `[MCPManager] 停止 ${serviceName} 服务失败:`,\n (error as Error).message\n );\n throw error;\n }\n }\n\n /**\n * 刷新工具缓存\n */\n private async refreshToolsCache(): Promise<void> {\n this.tools.clear();\n\n for (const [serviceName, service] of this.services) {\n if (service.isConnected()) {\n const tools = service.getTools();\n const config = this.configs[serviceName];\n\n // 异步写入缓存(不阻塞主流程)\n if (config) {\n this.cacheManager\n .writeCacheEntry(serviceName, tools, config)\n .then(() => {\n console.debug(\n `[MCPManager] 已将 ${serviceName} 工具列表写入缓存`\n );\n })\n .catch((error) => {\n console.warn(\n `[MCPManager] 写入缓存失败: ${serviceName}, 错误: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n });\n }\n\n // 原有逻辑保持不变\n for (const tool of tools) {\n const toolKey = `${serviceName}__${tool.name}`;\n this.tools.set(toolKey, {\n serviceName,\n originalName: tool.name,\n tool,\n });\n }\n }\n }\n\n // 同步工具配置到配置文件\n await this.syncToolsConfigToFile();\n }\n\n /**\n * 获取所有可用工具(优化版本,移除阻塞逻辑,添加工具启用状态过滤)\n */\n getAllTools(): Array<{\n name: string;\n description: string;\n inputSchema: JSONSchema;\n serviceName: string;\n originalName: string;\n }> {\n const allTools: Array<{\n name: string;\n description: string;\n inputSchema: JSONSchema;\n serviceName: string;\n originalName: string;\n }> = [];\n\n // 1. 收集所有已连接服务的工具(包含启用状态过滤)\n for (const [serviceName, service] of this.services) {\n try {\n if (service.isConnected()) {\n const serviceTools = service.getTools();\n for (const tool of serviceTools) {\n try {\n // 检查工具启用状态 - 这个调用可能会抛出异常\n const isEnabled = configManager.isToolEnabled(\n serviceName,\n tool.name\n );\n if (!isEnabled) {\n continue; // 跳过禁用的工具\n }\n\n const toolKey = `${serviceName}__${tool.name}`;\n allTools.push({\n name: toolKey,\n description: tool.description || \"\",\n inputSchema: tool.inputSchema,\n serviceName,\n originalName: tool.name,\n });\n } catch (toolError) {\n console.warn(\n `[MCPManager] 检查工具 ${serviceName}.${tool.name} 启用状态失败,跳过该工具:`,\n toolError\n );\n }\n }\n }\n } catch (serviceError) {\n console.warn(\n `[MCPManager] 获取服务 ${serviceName} 的工具失败,跳过该服务:`,\n serviceError\n );\n }\n }\n\n // 2. 添加CustomMCP工具(添加异常处理确保优雅降级)\n let customTools: Tool[] = [];\n try {\n customTools = this.customMCPHandler.getTools();\n console.debug(\n `[MCPManager] 成功获取 ${customTools.length} 个 customMCP 工具`\n );\n } catch (error) {\n console.warn(\n \"[MCPManager] 获取 CustomMCP 工具失败,将只返回标准 MCP 工具:\",\n error\n );\n // 根据技术方案要求,CustomMCP 工具获取失败时不应该影响标准 MCP 工具的返回\n customTools = [];\n }\n\n for (const tool of customTools) {\n try {\n allTools.push({\n name: tool.name,\n description: tool.description || \"\",\n inputSchema: tool.inputSchema,\n serviceName: this.getServiceNameForTool(tool),\n originalName: tool.name,\n });\n } catch (toolError) {\n console.warn(\n `[MCPManager] 处理 CustomMCP 工具 ${tool.name} 失败,跳过该工具:`,\n toolError\n );\n }\n }\n\n console.debug(`[MCPManager] 成功获取 ${allTools.length} 个可用工具`);\n return allTools;\n }\n\n /**\n * 根据工具配置确定服务名称\n * @param tool 工具对象\n * @returns 服务名称\n */\n private getServiceNameForTool(tool: CustomMCPTool): string {\n if (tool.handler?.type === \"mcp\") {\n // 如果是从 MCP 同步的工具,返回原始服务名称\n const config = tool.handler.config as\n | { serviceName?: string; toolName?: string }\n | undefined;\n return config?.serviceName || \"customMCP\";\n }\n return \"customMCP\";\n }\n\n /**\n * 根据工具信息获取日志记录用的服务名称\n * @param customTool CustomMCP 工具信息\n * @returns 用于日志记录的服务名称\n */\n private getLogServerName(customTool: CustomMCPTool): string {\n if (!customTool?.handler) {\n return \"custom\";\n }\n\n switch (customTool.handler.type) {\n case \"mcp\": {\n const config = customTool.handler.config as\n | { serviceName?: string; toolName?: string }\n | undefined;\n return config?.serviceName || \"customMCP\";\n }\n case \"coze\":\n return \"coze\";\n case \"dify\":\n return \"dify\";\n case \"n8n\":\n return \"n8n\";\n default:\n return \"custom\";\n }\n }\n\n /**\n * 根据工具信息获取原始工具名称\n * @param toolName 格式化后的工具名称\n * @param customTool CustomMCP 工具信息\n * @param toolInfo 标准工具信息\n * @returns 原始工具名称\n */\n private getOriginalToolName(\n toolName: string,\n customTool: CustomMCPTool | undefined,\n toolInfo?: ToolInfo\n ): string {\n if (customTool) {\n // CustomMCP 工具\n if (customTool.handler?.type === \"mcp\") {\n const config = customTool.handler.config as\n | { serviceName?: string; toolName?: string }\n | undefined;\n return config?.toolName || toolName;\n }\n return toolName;\n }\n\n // 标准 MCP 工具\n return toolInfo?.originalName || toolName;\n }\n\n /**\n * 调用 MCP 工具(支持标准 MCP 工具和 customMCP 工具)\n */\n async callTool(\n toolName: string,\n arguments_: Record<string, unknown>,\n options?: { timeout?: number }\n ): Promise<ToolCallResult> {\n const startTime = Date.now();\n\n // 初始化日志信息\n let logServerName = \"unknown\";\n let originalToolName: string = toolName;\n\n try {\n let result: ToolCallResult;\n\n // 检查是否是 customMCP 工具\n if (this.customMCPHandler.hasTool(toolName)) {\n const customTool = this.customMCPHandler.getToolInfo(toolName);\n\n // 设置日志信息(添加空值检查)\n if (customTool) {\n logServerName = this.getLogServerName(customTool);\n originalToolName = this.getOriginalToolName(toolName, customTool);\n }\n\n if (customTool?.handler?.type === \"mcp\") {\n // 对于 mcp 类型的工具,直接路由到对应的 MCP 服务\n result = await this.callMCPTool(\n toolName,\n customTool.handler.config,\n arguments_\n );\n\n // 异步更新工具调用统计(成功调用)\n this.updateToolStatsSafe(\n toolName,\n customTool.handler.config.serviceName,\n customTool.handler.config.toolName,\n true\n );\n } else {\n // 其他类型的 customMCP 工具正常处理,传递options参数\n result = await this.customMCPHandler.callTool(\n toolName,\n arguments_,\n options\n );\n console.info(`[MCPManager] CustomMCP 工具 ${toolName} 调用成功`);\n\n // 异步更新工具调用统计(成功调用)\n this.updateToolStatsSafe(toolName, \"customMCP\", toolName, true);\n }\n } else {\n // 如果不是 customMCP 工具,则查找标准 MCP 工具\n const toolInfo = this.tools.get(toolName);\n if (!toolInfo) {\n throw new Error(`未找到工具: ${toolName}`);\n }\n\n // 设置日志信息\n logServerName = toolInfo.serviceName;\n originalToolName = toolInfo.originalName;\n\n const service = this.services.get(toolInfo.serviceName);\n if (!service) {\n throw new Error(`服务 ${toolInfo.serviceName} 不可用`);\n }\n\n if (!service.isConnected()) {\n throw new Error(`服务 ${toolInfo.serviceName} 未连接`);\n }\n\n result = await service.callTool(\n toolInfo.originalName,\n arguments_ || {}\n );\n\n console.debug(\"[MCPManager] 工具调用成功\", {\n toolName: toolName,\n result: result,\n });\n\n // 异步更新工具调用统计(成功调用)\n this.updateToolStatsSafe(\n toolName,\n toolInfo.serviceName,\n toolInfo.originalName,\n true\n );\n }\n\n // 记录成功的工具调用\n this.toolCallLogger.recordToolCall({\n toolName: originalToolName,\n serverName: logServerName,\n arguments: arguments_,\n result: result,\n success: result.isError !== true,\n duration: Date.now() - startTime,\n });\n\n return result as ToolCallResult;\n } catch (error) {\n // 记录失败的工具调用\n this.toolCallLogger.recordToolCall({\n toolName: originalToolName,\n serverName: logServerName,\n arguments: arguments_,\n result: null,\n success: false,\n duration: Date.now() - startTime,\n error: error instanceof Error ? error.message : String(error),\n });\n\n // 更新失败统计\n if (this.customMCPHandler.hasTool(toolName)) {\n const customTool = this.customMCPHandler.getToolInfo(toolName);\n if (customTool?.handler?.type === \"mcp\") {\n this.updateToolStatsSafe(\n toolName,\n customTool.handler.config.serviceName,\n customTool.handler.config.toolName,\n false\n );\n } else {\n this.updateToolStatsSafe(toolName, \"customMCP\", toolName, false);\n console.error(\n `[MCPManager] CustomMCP 工具 ${toolName} 调用失败:`,\n (error as Error).message\n );\n }\n } else {\n const toolInfo = this.tools.get(toolName);\n if (toolInfo) {\n this.updateToolStatsSafe(\n toolName,\n toolInfo.serviceName,\n toolInfo.originalName,\n false\n );\n console.error(\n `[MCPManager] 工具 ${toolName} 调用失败:`,\n (error as Error).message\n );\n }\n }\n\n throw error;\n }\n }\n\n /**\n * 更新工具调用统计信息的通用方法\n * @param toolName 工具名称\n * @param serviceName 服务名称\n * @param originalToolName 原始工具名称\n * @param isSuccess 是否调用成功\n * @private\n */\n private async updateToolStats(\n toolName: string,\n serviceName: string,\n originalToolName: string,\n isSuccess: boolean\n ): Promise<void> {\n try {\n const currentTime = new Date().toISOString();\n\n if (isSuccess) {\n // 成功调用:更新使用统计\n await this.updateCustomMCPToolStats(toolName, currentTime);\n\n // 如果是 MCP 服务工具,同时更新 mcpServerConfig 配置(双写机制)\n if (serviceName !== \"customMCP\") {\n await this.updateMCPServerToolStats(\n serviceName,\n originalToolName,\n currentTime\n );\n }\n\n console.debug(`[MCPManager] 已更新工具 ${toolName} 的统计信息`);\n } else {\n // 失败调用:只更新最后使用时间\n await this.updateCustomMCPToolLastUsedTime(toolName, currentTime);\n\n // 如果是 MCP 服务工具,同时更新 mcpServerConfig 配置(双写机制)\n if (serviceName !== \"customMCP\") {\n await this.updateMCPServerToolLastUsedTime(\n serviceName,\n originalToolName,\n currentTime\n );\n }\n\n console.debug(\"[MCPManager] 已更新工具的失败调用统计信息\", {\n toolName,\n });\n }\n } catch (error) {\n console.error(\"[MCPManager] 更新工具统计信息失败:\", { toolName, error });\n throw error;\n }\n }\n\n /**\n * 统一的统计更新处理方法(带错误处理)\n * @param toolName 工具名称\n * @param serviceName 服务名称\n * @param originalToolName 原始工具名称\n * @param isSuccess 是否调用成功\n * @private\n */\n private async updateToolStatsSafe(\n toolName: string,\n serviceName: string,\n originalToolName: string,\n isSuccess: boolean\n ): Promise<void> {\n try {\n await this.updateToolStats(\n toolName,\n serviceName,\n originalToolName,\n isSuccess\n );\n } catch (error) {\n const action = isSuccess ? \"统计信息\" : \"失败统计信息\";\n console.warn(\"[MCPManager] 更新工具统计信息失败:\", {\n toolName,\n action,\n error,\n });\n // 统计更新失败不应该影响主流程,所以这里只记录警告\n }\n }\n\n /**\n * 更新 customMCP 工具统计信息\n * @param toolName 工具名称\n * @param currentTime 当前时间\n * @private\n */\n private async updateCustomMCPToolStats(\n toolName: string,\n currentTime: string\n ): Promise<void> {\n try {\n await configManager.updateToolUsageStatsWithLock(toolName, true);\n console.debug(`[MCPManager] 已更新 customMCP 工具 ${toolName} 使用统计`);\n } catch (error) {\n console.error(\n `[MCPManager] 更新 customMCP 工具 ${toolName} 统计失败:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * 更新 customMCP 工具最后使用时间\n * @param toolName 工具名称\n * @param currentTime 当前时间\n * @private\n */\n private async updateCustomMCPToolLastUsedTime(\n toolName: string,\n currentTime: string\n ): Promise<void> {\n try {\n await configManager.updateToolUsageStatsWithLock(toolName, false); // 只更新时间,不增加计数\n console.debug(\n `[MCPManager] 已更新 customMCP 工具 ${toolName} 最后使用时间`\n );\n } catch (error) {\n console.error(\n `[MCPManager] 更新 customMCP 工具 ${toolName} 最后使用时间失败:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * 更新 MCP 服务工具统计信息\n * @param serviceName 服务名称\n * @param toolName 工具名称\n * @param currentTime 当前时间\n * @private\n */\n private async updateMCPServerToolStats(\n serviceName: string,\n toolName: string,\n currentTime: string\n ): Promise<void> {\n try {\n await configManager.updateMCPServerToolStatsWithLock(\n serviceName,\n toolName,\n currentTime,\n true\n );\n console.debug(\n `[MCPManager] 已更新 MCP 服务工具 ${serviceName}/${toolName} 统计`\n );\n } catch (error) {\n console.error(\n `[MCPManager] 更新 MCP 服务工具 ${serviceName}/${toolName} 统计失败:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * 更新 MCP 服务工具最后使用时间\n * @param serviceName 服务名称\n * @param toolName 工具名称\n * @param currentTime 当前时间\n * @private\n */\n private async updateMCPServerToolLastUsedTime(\n serviceName: string,\n toolName: string,\n currentTime: string\n ): Promise<void> {\n try {\n await configManager.updateMCPServerToolStatsWithLock(\n serviceName,\n toolName,\n currentTime,\n false\n ); // 只更新时间,不增加计数\n console.debug(\n `[MCPManager] 已更新 MCP 服务工具 ${serviceName}/${toolName} 最后使用时间`\n );\n } catch (error) {\n console.error(\n `[MCPManager] 更新 MCP 服务工具 ${serviceName}/${toolName} 最后使用时间失败:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * 调用 MCP 工具(用于从 mcpServerConfig 同步的工具)\n * @param toolName 工具名称\n * @param config MCP handler 配置\n * @param arguments_ 工具参数\n */\n private async callMCPTool(\n toolName: string,\n config: { serviceName: string; toolName: string },\n arguments_: Record<string, unknown>\n ): Promise<ToolCallResult> {\n const { serviceName, toolName: originalToolName } = config;\n\n console.debug(\n `[MCPManager] 调用 MCP 同步工具 ${toolName} -> ${serviceName}.${originalToolName}`\n );\n\n const service = this.services.get(serviceName);\n if (!service) {\n throw new Error(`服务 ${serviceName} 不可用`);\n }\n\n if (!service.isConnected()) {\n throw new Error(`服务 ${serviceName} 未连接`);\n }\n\n try {\n const result = await service.callTool(originalToolName, arguments_ || {});\n console.debug(`[MCPManager] MCP 同步工具 ${toolName} 调用成功`);\n return result as ToolCallResult;\n } catch (error) {\n console.error(\n `[MCPManager] MCP 同步工具 ${toolName} 调用失败:`,\n (error as Error).message\n );\n throw error;\n }\n }\n\n /**\n * 检查是否存在指定工具(包括标准 MCP 工具和 customMCP 工具)\n */\n hasTool(toolName: string): boolean {\n // 检查是否是 customMCP 工具\n if (this.customMCPHandler.hasTool(toolName)) {\n return true;\n }\n\n // 检查是否是标准 MCP 工具\n return this.tools.has(toolName);\n }\n\n /**\n * 停止所有服务\n */\n async stopAllServices(): Promise<void> {\n console.info(\"[MCPManager] 正在停止所有 MCP 服务...\");\n\n // 停止所有服务重试\n this.stopAllServiceRetries();\n\n // 停止所有服务实例\n for (const [serviceName, service] of this.services) {\n try {\n await service.disconnect();\n console.info(`[MCPManager] ${serviceName} 服务已停止`);\n } catch (error) {\n console.error(\n `[MCPManager] 停止 ${serviceName} 服务失败:`,\n (error as Error).message\n );\n }\n }\n\n // 清理 CustomMCP 处理器\n try {\n this.customMCPHandler.cleanup();\n console.info(\"[MCPManager] CustomMCP 处理器已清理\");\n } catch (error) {\n console.error(\"[MCPManager] CustomMCP 处理器清理失败:\", error);\n }\n\n // 清理统计更新锁\n try {\n configManager.clearAllStatsUpdateLocks();\n console.info(\"[MCPManager] 统计更新锁已清理\");\n } catch (error) {\n console.error(\"[MCPManager] 清理统计更新锁失败:\", error);\n }\n\n this.services.clear();\n this.tools.clear();\n\n console.info(\"[MCPManager] 所有 MCP 服务已停止\");\n }\n\n /**\n * 获取服务器状态(兼容 UnifiedServerStatus 格式)\n */\n getStatus(): UnifiedServerStatus {\n return this.getUnifiedStatus();\n }\n\n /**\n * 获取统计更新监控信息\n */\n getStatsUpdateInfo(): {\n activeLocks: string[];\n totalLocks: number;\n } {\n try {\n const activeLocks = configManager.getStatsUpdateLocks();\n return {\n activeLocks,\n totalLocks: activeLocks.length,\n };\n } catch (error) {\n console.warn(\"[MCPManager] 获取统计更新监控信息失败:\", error);\n return {\n activeLocks: [],\n totalLocks: 0,\n };\n }\n }\n\n /**\n * 获取指定服务实例\n */\n getService(name: string): MCPService | undefined {\n return this.services.get(name);\n }\n\n /**\n * 获取所有已连接的服务名称\n */\n getConnectedServices(): string[] {\n const connectedServices: string[] = [];\n for (const [serviceName, service] of this.services) {\n if (service.isConnected()) {\n connectedServices.push(serviceName);\n }\n }\n return connectedServices;\n }\n\n /**\n * 刷新CustomMCPHandler的私有方法\n */\n private async refreshCustomMCPHandler(): Promise<void> {\n try {\n console.debug(\"重新初始化CustomMCPHandler\");\n this.customMCPHandler.initialize();\n console.debug(\"CustomMCPHandler重新初始化完成\");\n } catch (error) {\n console.error(\"CustomMCPHandler重新初始化失败:\", error);\n throw error;\n }\n }\n\n /**\n * 公开的CustomMCPHandler刷新方法,供外部调用\n */\n async refreshCustomMCPHandlerPublic(): Promise<void> {\n return this.refreshCustomMCPHandler();\n }\n\n /**\n * 获取所有服务实例\n */\n getAllServices(): Map<string, MCPService> {\n return new Map(this.services);\n }\n\n /**\n * 获取 CustomMCP 处理器实例\n */\n getCustomMCPHandler(): CustomMCPHandler {\n return this.customMCPHandler;\n }\n\n /**\n * 检查指定的 customMCP 工具是否存在\n * @param toolName 工具名称\n * @returns 如果工具存在返回 true,否则返回 false\n */\n hasCustomMCPTool(toolName: string): boolean {\n try {\n return this.customMCPHandler.hasTool(toolName);\n } catch (error) {\n console.warn(\n `[MCPManager] 检查 CustomMCP 工具 ${toolName} 是否存在失败:`,\n error\n );\n // 异常情况下返回 false,表示工具不存在\n return false;\n }\n }\n\n /**\n * 获取所有 customMCP 工具列表\n * @returns customMCP 工具数组\n */\n getCustomMCPTools(): Tool[] {\n try {\n return this.customMCPHandler.getTools();\n } catch (error) {\n console.warn(\n \"[MCPManager] 获取 CustomMCP 工具列表失败,返回空数组:\",\n error\n );\n // 异常情况下返回空数组,避免影响调用方\n return [];\n }\n }\n\n /**\n * 检查是否为 ModelScope 服务\n * 统一使用 ConfigAdapter 的 isModelScopeURL 函数\n */\n private isModelScopeService(config: MCPServiceConfig): boolean {\n return config.url ? isModelScopeURL(config.url) : false;\n }\n\n /**\n * 处理 ModelScope 服务认证\n * 智能检查现有认证信息,按优先级处理\n */\n private handleModelScopeAuth(\n originalConfig: MCPServiceConfig,\n enhancedConfig: MCPServiceConfig\n ): void {\n // 1. 检查是否已有 Authorization header\n const existingAuthHeader = originalConfig.headers?.Authorization;\n\n if (existingAuthHeader) {\n // 已有认证信息,直接使用\n console.info(\n `[MCPManager] 服务 ${originalConfig.name} 使用已有的 Authorization header`\n );\n return;\n }\n\n // 2. 检查全局 ModelScope API Key\n const modelScopeApiKey = configManager.getModelScopeApiKey();\n\n if (modelScopeApiKey) {\n // 注入全局 API Key\n enhancedConfig.apiKey = modelScopeApiKey;\n console.info(\n `[MCPManager] 为 ${originalConfig.name} 服务添加 ModelScope API Key`\n );\n return;\n }\n\n // 3. 无法获取认证信息,提供详细错误信息\n const serviceUrl = originalConfig.url || \"未知\";\n const serviceName = originalConfig.name || \"未知\";\n\n throw new Error(\n `ModelScope 服务 \"${serviceName}\" 需要认证信息,但未找到有效的认证配置。服务 URL: ${serviceUrl}请选择以下任一方式配置认证:1. 在服务配置中添加 headers.Authorization2. 或者在全局配置中设置 modelscope.apiKey3. 或者设置环境变量 MODELSCOPE_API_TOKEN获取 ModelScope API Key: https://modelscope.cn/my?myInfo=true`\n );\n }\n\n /**\n * 增强服务配置\n * 根据服务类型添加必要的全局配置,智能处理认证信息\n */\n private enhanceServiceConfig(config: MCPServiceConfig): MCPServiceConfig {\n const enhancedConfig = { ...config };\n\n try {\n // 处理 ModelScope 服务(智能认证检查)\n if (this.isModelScopeService(config)) {\n this.handleModelScopeAuth(config, enhancedConfig);\n }\n\n return enhancedConfig;\n } catch (error) {\n console.error(`[MCPManager] 配置增强失败: ${config.name}`, error);\n throw error;\n }\n }\n\n /**\n * 添加服务配置(重载方法以支持两种调用方式)\n */\n addServiceConfig(name: string, config: MCPServiceConfig): void;\n addServiceConfig(config: MCPServiceConfig): void;\n addServiceConfig(\n nameOrConfig: string | MCPServiceConfig,\n config?: MCPServiceConfig\n ): void {\n let finalConfig: MCPServiceConfig;\n let serviceName: string;\n\n if (typeof nameOrConfig === \"string\" && config) {\n // 两参数版本\n serviceName = nameOrConfig;\n finalConfig = config;\n } else if (typeof nameOrConfig === \"object\") {\n // 单参数版本\n serviceName = nameOrConfig.name;\n finalConfig = nameOrConfig;\n } else {\n throw new Error(\"Invalid arguments for addServiceConfig\");\n }\n\n // 增强配置\n const enhancedConfig = this.enhanceServiceConfig(finalConfig);\n\n // 存储增强后的配置\n this.configs[serviceName] = enhancedConfig;\n console.debug(`[MCPManager] 已添加服务配置: ${serviceName}`);\n }\n\n /**\n * 更新服务配置\n */\n updateServiceConfig(name: string, config: MCPServiceConfig): void {\n // 增强配置\n const enhancedConfig = this.enhanceServiceConfig(config);\n\n // 存储增强后的配置\n this.configs[name] = enhancedConfig;\n console.debug(`[MCPManager] 已更新并增强服务配置: ${name}`);\n }\n\n /**\n * 移除服务配置\n */\n removeServiceConfig(name: string): void {\n delete this.configs[name];\n console.debug(`[MCPManager] 已移除服务配置: ${name}`);\n }\n\n /**\n * 同步工具配置到配置文件\n * 实现自动同步 MCP 服务工具配置到 xiaozhi.config.json\n */\n private async syncToolsConfigToFile(): Promise<void> {\n try {\n console.debug(\"[MCPManager] 开始同步工具配置到配置文件\");\n\n // 获取当前配置文件中的 mcpServerConfig\n const currentServerConfigs = configManager.getMcpServerConfig();\n\n // 遍历所有已连接的服务\n for (const [serviceName, service] of this.services) {\n if (!service.isConnected()) {\n continue;\n }\n\n const tools = service.getTools();\n if (tools.length === 0) {\n continue;\n }\n\n // 获取当前服务在配置文件中的工具配置\n const currentToolsConfig =\n currentServerConfigs[serviceName]?.tools || {};\n\n // 构建新的工具配置\n const newToolsConfig: Record<string, MCPToolConfig> = {};\n\n for (const tool of tools) {\n const currentToolConfig = currentToolsConfig[tool.name];\n\n // 如果工具已存在,保留用户设置的 enable 状态,但更新描述\n if (currentToolConfig) {\n newToolsConfig[tool.name] = {\n ...currentToolConfig,\n description:\n tool.description || currentToolConfig.description || \"\",\n };\n } else {\n // 新工具,默认启用\n newToolsConfig[tool.name] = {\n description: tool.description || \"\",\n enable: true,\n };\n }\n }\n\n // 检查是否有工具被移除(在配置文件中存在但在当前工具列表中不存在)\n const currentToolNames = tools.map((t) => t.name);\n const configToolNames = Object.keys(currentToolsConfig);\n const removedTools = configToolNames.filter(\n (name) => !currentToolNames.includes(name)\n );\n\n if (removedTools.length > 0) {\n console.info(\n `[MCPManager] 检测到服务 ${serviceName} 移除了 ${\n removedTools.length\n } 个工具: ${removedTools.join(\", \")}`\n );\n }\n\n // 检查配置是否有变化\n const hasChanges = this.hasToolsConfigChanged(\n currentToolsConfig,\n newToolsConfig\n );\n\n if (hasChanges) {\n // 更新配置文件\n configManager.updateServerToolsConfig(serviceName, newToolsConfig);\n\n const addedTools = Object.keys(newToolsConfig).filter(\n (name) => !currentToolsConfig[name]\n );\n const updatedTools = Object.keys(newToolsConfig).filter((name) => {\n const current = currentToolsConfig[name];\n const updated = newToolsConfig[name];\n return current && current.description !== updated.description;\n });\n\n console.debug(`[MCPManager] 已同步服务 ${serviceName} 的工具配置:`);\n if (addedTools.length > 0) {\n console.debug(` - 新增工具: ${addedTools.join(\", \")}`);\n }\n if (updatedTools.length > 0) {\n console.debug(` - 更新工具: ${updatedTools.join(\", \")}`);\n }\n if (removedTools.length > 0) {\n console.debug(` - 移除工具: ${removedTools.join(\", \")}`);\n }\n }\n }\n\n console.debug(\"[MCPManager] 工具配置同步完成\");\n } catch (error) {\n console.error(\"[MCPManager] 同步工具配置到配置文件失败:\", error);\n // 不抛出错误,避免影响服务正常运行\n }\n }\n\n /**\n * 检查工具配置是否有变化\n */\n private hasToolsConfigChanged(\n currentConfig: Record<string, MCPToolConfig>,\n newConfig: Record<string, MCPToolConfig>\n ): boolean {\n const currentKeys = Object.keys(currentConfig);\n const newKeys = Object.keys(newConfig);\n\n // 检查工具数量是否变化\n if (currentKeys.length !== newKeys.length) {\n return true;\n }\n\n // 检查是否有新增或删除的工具\n const addedTools = newKeys.filter((key) => !currentKeys.includes(key));\n const removedTools = currentKeys.filter((key) => !newKeys.includes(key));\n\n if (addedTools.length > 0 || removedTools.length > 0) {\n return true;\n }\n\n // 检查现有工具的描述是否有变化\n for (const toolName of currentKeys) {\n const currentTool = currentConfig[toolName];\n const newTool = newConfig[toolName];\n\n if (currentTool.description !== newTool.description) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * 安排失败服务的重试\n * @param failedServices 失败的服务列表\n */\n private scheduleFailedServicesRetry(failedServices: string[]): void {\n if (failedServices.length === 0) return;\n\n // 记录重试安排\n console.info(`[MCPManager] 安排 ${failedServices.length} 个失败服务的重试`);\n\n // 初始重试延迟:30秒\n const initialDelay = 30000;\n\n for (const serviceName of failedServices) {\n this.failedServices.add(serviceName);\n this.scheduleServiceRetry(serviceName, initialDelay);\n }\n }\n\n /**\n * 安排单个服务的重试\n * @param serviceName 服务名称\n * @param delay 延迟时间(毫秒)\n */\n private scheduleServiceRetry(serviceName: string, delay: number): void {\n // 清除现有定时器\n const existingTimer = this.retryTimers.get(serviceName);\n if (existingTimer) {\n clearTimeout(existingTimer);\n this.retryTimers.delete(serviceName);\n }\n\n console.debug(`[MCPManager] 安排服务 ${serviceName} 在 ${delay}ms 后重试`);\n\n const timer = setTimeout(async () => {\n this.retryTimers.delete(serviceName);\n await this.retryFailedService(serviceName);\n }, delay);\n\n this.retryTimers.set(serviceName, timer);\n }\n\n /**\n * 重试失败的服务\n * @param serviceName 服务名称\n */\n private async retryFailedService(serviceName: string): Promise<void> {\n if (!this.failedServices.has(serviceName)) {\n return; // 服务已经成功启动或不再需要重试\n }\n\n try {\n await this.startService(serviceName);\n\n // 重试成功\n this.failedServices.delete(serviceName);\n console.info(`[MCPManager] 服务 ${serviceName} 重试启动成功`);\n\n // 重新初始化CustomMCPHandler以包含新启动的服务工具\n try {\n await this.refreshCustomMCPHandlerPublic();\n } catch (error) {\n console.error(\"[MCPManager] 刷新CustomMCPHandler失败:\", error);\n }\n } catch (error) {\n console.error(\n `[MCPManager] 服务 ${serviceName} 重试启动失败:`,\n (error as Error).message\n );\n\n // 指数退避重试策略:延迟时间翻倍,最大不超过5分钟\n const currentDelay = this.getRetryDelay(serviceName);\n const nextDelay = Math.min(currentDelay * 2, 300000); // 最大5分钟\n\n console.debug(\n `[MCPManager] 服务 ${serviceName} 下次重试将在 ${nextDelay}ms 后进行`\n );\n\n this.scheduleServiceRetry(serviceName, nextDelay);\n }\n }\n\n /**\n * 获取当前重试延迟时间\n * @param serviceName 服务名称\n * @returns 当前延迟时间\n */\n private getRetryDelay(serviceName: string): number {\n // 这里可以实现更复杂的状态跟踪来计算准确的延迟\n // 简化实现:返回一个基于服务名称的哈希值的初始延迟\n const hash = serviceName\n .split(\"\")\n .reduce((acc, char) => acc + char.charCodeAt(0), 0);\n return 30000 + (hash % 60000); // 30-90秒之间的初始延迟\n }\n\n /**\n * 停止指定服务的重试\n * @param serviceName 服务名称\n */\n public stopServiceRetry(serviceName: string): void {\n const timer = this.retryTimers.get(serviceName);\n if (timer) {\n clearTimeout(timer);\n this.retryTimers.delete(serviceName);\n console.debug(`[MCPManager] 已停止服务 ${serviceName} 的重试`);\n }\n this.failedServices.delete(serviceName);\n }\n\n /**\n * 停止所有服务的重试\n */\n public stopAllServiceRetries(): void {\n console.info(\"[MCPManager] 停止所有服务重试\");\n\n for (const [serviceName, timer] of this.retryTimers) {\n clearTimeout(timer);\n console.debug(`[MCPManager] 已停止服务 ${serviceName} 的重试`);\n }\n\n this.retryTimers.clear();\n this.failedServices.clear();\n }\n\n /**\n * 获取失败服务列表\n * @returns 失败的服务名称数组\n */\n public getFailedServices(): string[] {\n return Array.from(this.failedServices);\n }\n\n /**\n * 检查服务是否失败\n * @param serviceName 服务名称\n * @returns 如果服务失败返回true\n */\n public isServiceFailed(serviceName: string): boolean {\n return this.failedServices.has(serviceName);\n }\n\n /**\n * 获取重试统计信息\n * @returns 重试统计信息\n */\n public getRetryStats(): {\n failedServices: string[];\n activeRetries: string[];\n totalFailed: number;\n totalActiveRetries: number;\n } {\n return {\n failedServices: Array.from(this.failedServices),\n activeRetries: Array.from(this.retryTimers.keys()),\n totalFailed: this.failedServices.size,\n totalActiveRetries: this.retryTimers.size,\n };\n }\n\n // ===== 传输适配器管理方法(从 UnifiedMCPServer 移入) =====\n\n /**\n * 注册传输适配器\n * @param name 传输适配器名称\n * @param adapter 传输适配器实例\n */\n public async registerTransport(\n name: string,\n adapter: TransportAdapter\n ): Promise<void> {\n if (this.transportAdapters.has(name)) {\n throw new Error(`传输适配器 ${name} 已存在`);\n }\n\n console.info(`注册传输适配器: ${name}`);\n\n try {\n await adapter.initialize();\n this.transportAdapters.set(name, adapter);\n\n console.info(`传输适配器 ${name} 注册成功`);\n this.emit(\"transportRegistered\", { name, adapter });\n } catch (error) {\n console.error(`注册传输适配器 ${name} 失败`, error);\n throw error;\n }\n }\n\n /**\n * 启动所有传输适配器\n *\n * 改进的错误处理策略:\n * - 单个适配器启动失败不会中断其他适配器的启动\n * - 记录失败的适配器,但继续启动其他适配器\n * - 如果所有适配器都失败,则抛出错误\n * - 如果部分适配器成功,则记录警告但继续运行\n */\n public async startTransports(): Promise<void> {\n console.info(\"启动所有传输适配器\");\n\n const successfulAdapters: string[] = [];\n const failedAdapters: string[] = [];\n\n for (const [name, adapter] of this.transportAdapters) {\n try {\n await adapter.start();\n successfulAdapters.push(name);\n console.info(`传输适配器 ${name} 启动成功`);\n } catch (error) {\n failedAdapters.push(name);\n console.error(`传输适配器 ${name} 启动失败`, error);\n // 不立即抛出错误,继续尝试启动其他适配器\n }\n }\n\n // 评估启动结果\n if (successfulAdapters.length === 0 && failedAdapters.length > 0) {\n // 所有适配器都失败了\n const errorMessage = `所有传输适配器启动失败,失败的适配器: ${failedAdapters.join(\n \", \"\n )}`;\n console.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n if (failedAdapters.length > 0) {\n // 部分适配器失败\n console.warn(\n `部分传输适配器启动失败,成功: ${successfulAdapters.join(\n \", \"\n )}, 失败: ${failedAdapters.join(\", \")}`\n );\n // 继续运行,因为至少有一个适配器成功\n }\n\n console.info(\n `传输适配器启动完成,成功: ${successfulAdapters.length}, 失败: ${failedAdapters.length}`\n );\n }\n\n /**\n * 停止所有传输适配器\n */\n public async stopTransports(): Promise<void> {\n console.info(\"停止所有传输适配器\");\n\n try {\n for (const [name, adapter] of this.transportAdapters) {\n try {\n await adapter.stop();\n console.info(`传输适配器 ${name} 停止成功`);\n } catch (error) {\n console.error(`传输适配器 ${name} 停止失败`, error);\n }\n }\n } catch (error) {\n console.error(\"传输适配器停止失败\", error);\n throw error;\n }\n }\n\n /**\n * 获取所有传输适配器\n * @returns 传输适配器映射表\n */\n public getTransportAdapters(): Map<string, TransportAdapter> {\n return new Map(this.transportAdapters);\n }\n\n /**\n * 获取消息处理器(供外部使用)\n * @returns 消息处理器实例\n */\n public getMessageHandler(): MCPMessageHandler {\n return this.messageHandler;\n }\n\n /**\n * 启动管理器(包含服务和传输)\n */\n public async start(): Promise<void> {\n if (this.isRunning) {\n throw new Error(\"服务器已在运行\");\n }\n\n console.info(\"启动 MCP 服务管理器\");\n\n try {\n await this.startAllServices();\n await this.startTransports();\n this.isRunning = true;\n\n console.info(\"MCP 服务管理器启动成功\");\n this.emit(\"started\");\n } catch (error) {\n console.error(\"MCP 服务管理器启动失败\", error);\n throw error;\n }\n }\n\n /**\n * 停止管理器(包含传输和服务)\n */\n public async stop(): Promise<void> {\n if (!this.isRunning) {\n return;\n }\n\n console.info(\"停止 MCP 服务管理器\");\n\n try {\n await this.stopTransports();\n await this.stopAllServices();\n this.isRunning = false;\n\n console.info(\"MCP 服务管理器停止成功\");\n this.emit(\"stopped\");\n } catch (error) {\n console.error(\"MCP 服务管理器停止失败\", error);\n throw error;\n }\n }\n\n /**\n * 获取所有连接信息\n * @returns 连接信息列表\n */\n public getAllConnections(): Array<{\n id: string;\n name: string;\n state: ConnectionState;\n }> {\n const connections: Array<{\n id: string;\n name: string;\n state: ConnectionState;\n }> = [];\n\n // 收集服务连接\n for (const [serviceName, service] of this.services) {\n if (service.isConnected()) {\n connections.push({\n id: `service-${serviceName}`,\n name: serviceName,\n state: ConnectionState.CONNECTED,\n });\n }\n }\n\n // 收集传输适配器连接\n for (const [adapterName, adapter] of this.transportAdapters) {\n connections.push({\n id: adapter.getConnectionId(),\n name: adapterName,\n state: adapter.getState(),\n });\n }\n\n return connections;\n }\n\n /**\n * 获取活跃连接数\n * @returns 活跃连接数量\n */\n public getActiveConnectionCount(): number {\n return this.getAllConnections().filter(\n (conn) => conn.state === ConnectionState.CONNECTED\n ).length;\n }\n\n // ===== 从 UnifiedMCPServer 移入的方法 =====\n\n /**\n * 获取服务器状态(从 UnifiedMCPServer 移入)\n */\n getUnifiedStatus(): UnifiedServerStatus {\n const serviceStatus = this.getServiceManagerStatus();\n return {\n isRunning: this.isRunning,\n serviceStatus,\n transportCount: this.getTransportAdapters().size,\n activeConnections: this.getActiveConnectionCount(),\n config: this.config,\n // 便捷访问属性\n services: serviceStatus.services,\n totalTools: serviceStatus.totalTools,\n availableTools: serviceStatus.availableTools,\n };\n }\n\n /**\n * 获取管理器状态(原有的 getStatus 方法重命名)\n */\n getServiceManagerStatus(): ManagerStatus {\n // 计算总工具数量(包括 customMCP 工具,添加异常处理)\n let customMCPToolCount = 0;\n let customToolNames: string[] = [];\n\n try {\n customMCPToolCount = this.customMCPHandler.getToolCount();\n customToolNames = this.customMCPHandler.getToolNames();\n console.debug(\n `[MCPManager] 成功获取 customMCP 状态: ${customMCPToolCount} 个工具`\n );\n } catch (error) {\n console.warn(\n \"[MCPManager] 获取 CustomMCP 状态失败,将只包含标准 MCP 工具:\",\n error\n );\n // 异常情况下,customMCP 工具数量为0,不影响标准 MCP 工具\n customMCPToolCount = 0;\n customToolNames = [];\n }\n\n const totalTools = this.tools.size + customMCPToolCount;\n\n // 获取所有可用工具名称\n const standardToolNames = Array.from(this.tools.keys());\n const availableTools = [...standardToolNames, ...customToolNames];\n\n const status: ManagerStatus = {\n services: {},\n totalTools,\n availableTools,\n };\n\n // 添加标准 MCP 服务状态\n for (const [serviceName, service] of this.services) {\n const serviceStatus = service.getStatus();\n status.services[serviceName] = {\n connected: serviceStatus.connected,\n clientName: `xiaozhi-${serviceName}-client`,\n };\n }\n\n // 添加 CustomMCP 服务状态\n if (customMCPToolCount > 0) {\n status.services.customMCP = {\n connected: true, // CustomMCP 工具总是可用的\n clientName: \"xiaozhi-customMCP-handler\",\n };\n }\n\n return status;\n }\n\n /**\n * 检查服务器是否正在运行(从 UnifiedMCPServer 移入)\n */\n isServerRunning(): boolean {\n return this.isRunning;\n }\n\n /**\n * 类型守卫:检查是否为 UnifiedServerConfig\n */\n private isUnifiedServerConfig(\n configs: unknown\n ): configs is UnifiedServerConfig {\n return (\n configs !== null && typeof configs === \"object\" && \"configs\" in configs\n );\n }\n\n /**\n * 消息路由核心功能(从 UnifiedMCPServer 移入)\n */\n async routeMessage(message: MCPMessage): Promise<MCPMessage | null> {\n const response = await this.messageHandler.handleMessage(message);\n // 如果响应是 null,直接返回\n if (response === null) {\n return null;\n }\n // 将 MCPResponse 转换为 MCPMessage 格式\n return {\n jsonrpc: \"2.0\",\n method: \"response\", // 标识这是一个响应消息\n params: response,\n id: response.id, // 使用响应中的ID\n };\n }\n\n // ===== 向后兼容方法 =====\n\n /**\n * 初始化方法(向后兼容,实际调用 start)\n *\n * 注意:此方法仅为向后兼容而保留\n * 实际功能:调用 start() 方法并设置 isRunning 状态\n * 建议新代码直接使用 start() 方法\n */\n async initialize(): Promise<void> {\n // 为了向后兼容,初始化时调用 start\n // 会设置 isRunning 状态为 true\n await this.start();\n }\n\n /**\n * 获取工具注册表(向后兼容,返回自身)\n */\n getToolRegistry(): MCPServiceManager {\n return this;\n }\n\n /**\n * 获取连接管理器(向后兼容,返回自身)\n */\n getConnectionManager(): MCPServiceManager {\n return this;\n }\n}\n\nexport default MCPServiceManager;\n","import { EventEmitter } from \"node:events\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Logger } from \"@root/Logger.js\";\nimport { logger } from \"@root/Logger.js\";\n\n/**\n * 事件类型定义\n */\nexport interface EventBusEvents {\n // 配置相关事件\n \"config:updated\": {\n type: string;\n serviceName?: string;\n platformName?: string;\n timestamp: Date;\n };\n \"config:error\": { error: Error; operation: string };\n\n // 状态相关事件\n \"status:updated\": { status: any; source: string };\n \"status:error\": { error: Error; operation: string };\n\n // 接入点状态变更事件\n \"endpoint:status:changed\": {\n endpoint: string;\n connected: boolean;\n operation: \"connect\" | \"disconnect\" | \"reconnect\" | \"add\" | \"remove\";\n success: boolean;\n message?: string;\n timestamp: number;\n source: string;\n };\n\n // 服务相关事件\n \"service:restart:requested\": {\n serviceName: string;\n reason?: string;\n delay: number;\n attempt: number;\n timestamp: number;\n source?: string;\n };\n \"service:restart:started\": {\n serviceName: string;\n reason?: string;\n attempt: number;\n timestamp: number;\n };\n \"service:restart:completed\": {\n serviceName: string;\n reason?: string;\n attempt: number;\n timestamp: number;\n };\n \"service:restart:failed\": {\n serviceName: string;\n error: Error;\n attempt: number;\n timestamp: number;\n };\n \"service:restart:execute\": {\n serviceName: string;\n reason?: string;\n attempt: number;\n timestamp: number;\n };\n \"service:health:changed\": {\n serviceName: string;\n oldStatus: string;\n newStatus: string;\n timestamp: number;\n };\n\n // WebSocket 相关事件\n \"websocket:client:connected\": { clientId: string; timestamp: number };\n \"websocket:client:disconnected\": { clientId: string; timestamp: number };\n \"websocket:message:received\": { type: string; data: any; clientId: string };\n\n // 通知相关事件\n \"notification:broadcast\": { type: string; data: any; target?: string };\n \"notification:error\": { error: Error; type: string };\n\n // MCP服务相关事件\n \"mcp:service:connected\": {\n serviceName: string;\n tools: Tool[];\n connectionTime: Date;\n };\n \"mcp:service:disconnected\": {\n serviceName: string;\n reason?: string;\n disconnectionTime: Date;\n };\n \"mcp:service:connection:failed\": {\n serviceName: string;\n error: Error;\n attempt: number;\n };\n \"mcp:server:added\": {\n serverName: string;\n config: any;\n tools: string[];\n timestamp: Date;\n };\n \"mcp:server:removed\": {\n serverName: string;\n affectedTools: string[];\n timestamp: Date;\n };\n \"mcp:server:status_changed\": {\n serverName: string;\n oldStatus: \"connected\" | \"disconnected\" | \"connecting\" | \"error\";\n newStatus: \"connected\" | \"disconnected\" | \"connecting\" | \"error\";\n timestamp: Date;\n reason?: string;\n };\n \"mcp:server:connection:attempt\": {\n serverName: string;\n attempt: number;\n maxAttempts: number;\n timestamp: Date;\n };\n \"mcp:server:tools:updated\": {\n serverName: string;\n tools: string[];\n addedTools: string[];\n removedTools: string[];\n timestamp: Date;\n };\n \"mcp:server:batch_added\": {\n totalServers: number;\n addedCount: number;\n failedCount: number;\n successfullyAddedServers: string[];\n results: any[];\n timestamp: Date;\n };\n \"mcp:server:rollback\": {\n serverName: string;\n timestamp: Date;\n };\n\n // 连接相关事件\n \"connection:reconnect:completed\": {\n success: boolean;\n reason: string;\n timestamp: Date;\n };\n\n // NPM 安装相关事件\n \"npm:install:started\": {\n version: string;\n installId: string;\n timestamp: number;\n };\n \"npm:install:log\": {\n version: string;\n installId: string;\n type: \"stdout\" | \"stderr\";\n message: string;\n timestamp: number;\n };\n \"npm:install:completed\": {\n version: string;\n installId: string;\n success: boolean;\n duration: number;\n timestamp: number;\n };\n \"npm:install:failed\": {\n version: string;\n installId: string;\n error: string;\n duration: number;\n timestamp: number;\n };\n\n // 测试相关事件(仅用于测试)\n \"high-frequency\": {\n id: number;\n timestamp: number;\n };\n \"bulk-test\": {\n id: number;\n timestamp: number;\n };\n \"error-test\": {\n error: string;\n timestamp: number;\n };\n \"large-data-test\": {\n data: any;\n timestamp: number;\n };\n \"destroy-test\": {\n message: string;\n timestamp: number;\n };\n \"chain-event-1\": {\n value: number;\n timestamp: number;\n };\n \"chain-event-2\": {\n value: number;\n timestamp: number;\n };\n \"chain-event-3\": {\n value: number;\n timestamp: number;\n };\n \"performance-test\": {\n data: any;\n timestamp: number;\n };\n \"test:performance\": {\n id: number;\n timestamp: number;\n };\n \"chain:start\": {\n value: number;\n timestamp: number;\n };\n \"chain:middle\": {\n value: number;\n timestamp: number;\n };\n \"chain:end\": {\n value: number;\n timestamp: number;\n };\n \"test:error\": {\n error: boolean;\n timestamp: number;\n };\n \"test:remove\": {\n id: number;\n timestamp: number;\n };\n}\n\n/**\n * 事件总线 - 用于模块间的解耦通信\n */\nexport class EventBus extends EventEmitter {\n private logger: Logger;\n private eventStats: Map<string, { count: number; lastEmitted: Date }> =\n new Map();\n private maxListeners = 50; // 增加最大监听器数量\n\n constructor() {\n super();\n this.logger = logger;\n this.setMaxListeners(this.maxListeners);\n this.setupErrorHandling();\n }\n\n /**\n * 设置错误处理\n */\n private setupErrorHandling(): void {\n this.on(\"error\", (error) => {\n this.logger.error(\"EventBus 内部错误:\", error);\n });\n\n // 监听器数量警告\n this.on(\"newListener\", (eventName) => {\n const listenerCount = this.listenerCount(eventName);\n if (listenerCount > this.maxListeners * 0.8) {\n this.logger.warn(\n `事件 ${eventName} 的监听器数量过多: ${listenerCount}`\n );\n }\n });\n }\n\n /**\n * 发射事件(类型安全)\n */\n emitEvent<K extends keyof EventBusEvents>(\n eventName: K,\n data: EventBusEvents[K]\n ): boolean {\n try {\n this.updateEventStats(eventName as string);\n this.logger.debug(`发射事件: ${eventName}`, data);\n\n // 使用原始emit方法,保持EventEmitter的所有特性\n return super.emit(eventName, data);\n } catch (error) {\n this.logger.error(`发射事件失败: ${eventName}`, error);\n // 将监听器错误发射到error事件\n if (error instanceof Error) {\n this.emit(\"error\", error);\n }\n return false;\n }\n }\n\n /**\n * 监听事件(类型安全)\n */\n onEvent<K extends keyof EventBusEvents>(\n eventName: K,\n listener: (data: EventBusEvents[K]) => void\n ): this {\n this.logger.debug(`添加事件监听器: ${eventName}`);\n return this.on(eventName, listener);\n }\n\n /**\n * 一次性监听事件(类型安全)\n */\n onceEvent<K extends keyof EventBusEvents>(\n eventName: K,\n listener: (data: EventBusEvents[K]) => void\n ): this {\n this.logger.debug(`添加一次性事件监听器: ${eventName}`);\n\n // 创建包装器来实现一次性监听\n const onceListener = (data: EventBusEvents[K]) => {\n try {\n listener(data);\n } catch (error) {\n // 监听器抛出错误,发射到错误事件\n this.emit(\"error\", error);\n throw error;\n } finally {\n // 在任何情况下都移除监听器\n this.offEvent(eventName, onceListener);\n }\n };\n\n return this.on(eventName, onceListener);\n }\n\n /**\n * 移除事件监听器(类型安全)\n */\n offEvent<K extends keyof EventBusEvents>(\n eventName: K,\n listener: (data: EventBusEvents[K]) => void\n ): this {\n this.logger.debug(`移除事件监听器: ${eventName}`);\n return this.off(eventName, listener);\n }\n\n /**\n * 更新事件统计\n */\n private updateEventStats(eventName: string): void {\n const stats = this.eventStats.get(eventName) || {\n count: 0,\n lastEmitted: new Date(),\n };\n stats.count++;\n stats.lastEmitted = new Date();\n this.eventStats.set(eventName, stats);\n }\n\n /**\n * 获取事件统计信息\n */\n getEventStats(): Record<string, { count: number; lastEmitted: Date }> {\n const stats: Record<string, { count: number; lastEmitted: Date }> = {};\n for (const [eventName, stat] of this.eventStats) {\n stats[eventName] = { ...stat };\n }\n return stats;\n }\n\n /**\n * 获取监听器统计信息\n */\n getListenerStats(): Record<string, number> {\n const stats: Record<string, number> = {};\n for (const eventName of this.eventNames()) {\n stats[eventName as string] = this.listenerCount(eventName);\n }\n return stats;\n }\n\n /**\n * 清理事件统计\n */\n clearEventStats(): void {\n this.eventStats.clear();\n this.logger.info(\"事件统计已清理\");\n }\n\n /**\n * 获取事件总线状态\n */\n getStatus(): {\n totalEvents: number;\n totalListeners: number;\n eventStats: Record<string, { count: number; lastEmitted: Date }>;\n listenerStats: Record<string, number>;\n } {\n return {\n totalEvents: this.eventStats.size,\n totalListeners: Object.values(this.getListenerStats()).reduce(\n (sum, count) => sum + count,\n 0\n ),\n eventStats: this.getEventStats(),\n listenerStats: this.getListenerStats(),\n };\n }\n\n /**\n * 销毁事件总线\n */\n destroy(): void {\n this.removeAllListeners();\n this.eventStats.clear();\n this.logger.info(\"EventBus 已销毁\");\n }\n}\n\n// 单例实例\nlet eventBusInstance: EventBus | null = null;\n\n/**\n * 获取事件总线单例\n */\nexport function getEventBus(): EventBus {\n if (!eventBusInstance) {\n eventBusInstance = new EventBus();\n }\n return eventBusInstance;\n}\n\n/**\n * 销毁事件总线单例\n */\nexport function destroyEventBus(): void {\n if (eventBusInstance) {\n eventBusInstance.destroy();\n eventBusInstance = null;\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport chalk from \"chalk\";\nimport pino from \"pino\";\nimport type { Logger as PinoLogger } from \"pino\";\nimport { z } from \"zod\";\n\nconst LogLevelSchema = z.enum([\n \"fatal\",\n \"error\",\n \"warn\",\n \"info\",\n \"debug\",\n \"trace\",\n]);\ntype Level = z.infer<typeof LogLevelSchema>;\n\n/**\n * 格式化日期时间为 YYYY-MM-DD HH:mm:ss 格式\n * @param date 要格式化的日期对象\n * @returns 格式化后的日期时间字符串\n */\nfunction formatDateTime(date: Date): string {\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\n/**\n * 高性能日志记录器,基于 pino 实现\n *\n * 特性:\n * - 支持控制台和文件双重输出\n * - 支持守护进程模式(仅文件输出)\n * - 支持结构化日志记录\n * - 自动日志文件轮转和管理\n * - 高性能异步写入\n * - 完整的错误堆栈跟踪\n */\nexport class Logger {\n private logFilePath: string | null = null;\n private pinoInstance: PinoLogger;\n private isDaemonMode: boolean;\n private logLevel: Level; // 新增:动态日志级别\n private maxLogFileSize = 10 * 1024 * 1024; // 10MB 默认最大文件大小\n private maxLogFiles = 5; // 最多保留5个日志文件\n\n constructor(level: Level = \"info\") {\n // 检查是否为守护进程模式\n this.isDaemonMode = process.env.XIAOZHI_DAEMON === \"true\";\n\n // 设置并验证日志级别\n this.logLevel = this.validateLogLevel(level);\n\n // 创建 pino 实例\n this.pinoInstance = this.createPinoInstance();\n }\n\n /**\n * 验证日志级别\n * @param level 日志级别\n * @returns 验证后的日志级别\n */\n private validateLogLevel(level: string): Level {\n const normalizedLevel = level.toLowerCase();\n const result = LogLevelSchema.safeParse(normalizedLevel);\n\n if (result.success) {\n return result.data;\n }\n\n return \"info\";\n }\n\n private createPinoInstance(): PinoLogger {\n const streams: pino.StreamEntry[] = [];\n\n // 控制台流 - 只在非守护进程模式下添加\n if (!this.isDaemonMode) {\n // 使用高性能的控制台输出流\n const consoleStream = this.createOptimizedConsoleStream();\n streams.push({\n level: this.logLevel, // 修改:使用动态日志级别\n stream: consoleStream,\n });\n }\n\n // 文件流 - 如果有日志文件路径,使用高性能异步写入\n if (this.logFilePath) {\n streams.push({\n level: this.logLevel, // 修改:使用动态日志级别\n stream: pino.destination({\n dest: this.logFilePath,\n sync: false, // 异步写入提升性能\n append: true,\n mkdir: true,\n }),\n });\n }\n\n // 如果没有流,创建一个空的流避免错误\n if (streams.length === 0) {\n streams.push({\n level: this.logLevel, // 修改:使用动态日志级别\n stream: pino.destination({ dest: \"/dev/null\" }),\n });\n }\n\n return pino(\n {\n level: this.logLevel, // 修改:使用动态日志级别\n // 高性能配置\n timestamp:\n pino.stdTimeFunctions?.isoTime || (() => `,\"time\":${Date.now()}`),\n formatters: {\n // 优化级别格式化\n level: (_label: string, number: number) => ({ level: number }),\n },\n // 禁用不必要的功能以提升性能\n base: null, // 不包含 pid 和 hostname\n serializers: {\n // 优化错误序列化,在测试环境中安全处理\n err: pino.stdSerializers?.err || ((err: any) => err),\n },\n },\n pino.multistream(streams, { dedupe: true })\n );\n }\n\n private createOptimizedConsoleStream() {\n // 预编译级别映射以提升性能\n const levelMap = new Map([\n [20, { name: \"DEBUG\", color: chalk.gray }],\n [30, { name: \"INFO\", color: chalk.blue }],\n [40, { name: \"WARN\", color: chalk.yellow }],\n [50, { name: \"ERROR\", color: chalk.red }],\n [60, { name: \"FATAL\", color: chalk.red }],\n ]);\n\n return {\n write: (chunk: string) => {\n try {\n const logObj = JSON.parse(chunk);\n const message = this.formatConsoleMessageOptimized(logObj, levelMap);\n // 在测试环境中安全地写入\n this.safeWrite(`${message}\\n`);\n } catch (error) {\n // 如果解析失败,直接输出原始内容\n this.safeWrite(chunk);\n }\n },\n };\n }\n\n /**\n * 安全地写入到 stderr,在测试环境中避免错误\n */\n private safeWrite(content: string): void {\n try {\n if (process.stderr && typeof process.stderr.write === \"function\") {\n process.stderr.write(content);\n } else if (console && typeof console.error === \"function\") {\n // 在测试环境中回退到 console.error\n console.error(content.trim());\n }\n } catch (error) {\n // 在极端情况下静默失败,避免测试中断\n }\n }\n\n private formatConsoleMessageOptimized(\n logObj: any,\n levelMap: Map<number, { name: string; color: (text: string) => string }>\n ): string {\n const timestamp = formatDateTime(new Date());\n\n const levelInfo = levelMap.get(logObj.level) || {\n name: \"UNKNOWN\",\n color: (text: string) => text,\n };\n const coloredLevel = levelInfo.color(`[${levelInfo.name}]`);\n\n // 处理结构化日志中的 args,保持兼容性\n let message = logObj.msg;\n if (logObj.args && Array.isArray(logObj.args)) {\n const argsStr = logObj.args\n .map((arg: any) =>\n typeof arg === \"object\" ? JSON.stringify(arg) : String(arg)\n )\n .join(\" \");\n message = `${message} ${argsStr}`;\n }\n\n return `[${timestamp}] ${coloredLevel} ${message}`;\n }\n\n /**\n * 初始化日志文件\n * @param projectDir 项目目录\n */\n initLogFile(projectDir: string): void {\n this.logFilePath = path.join(projectDir, \"xiaozhi.log\");\n\n // 检查并轮转日志文件\n this.rotateLogFileIfNeeded();\n\n // 确保日志文件存在\n if (!fs.existsSync(this.logFilePath)) {\n fs.writeFileSync(this.logFilePath, \"\");\n }\n\n // 重新创建 pino 实例以包含文件流\n this.pinoInstance = this.createPinoInstance();\n }\n\n /**\n * 设置是否启用文件日志\n * @param enable 是否启用\n */\n enableFileLogging(enable: boolean): void {\n // 在 pino 实现中,文件日志的启用/禁用通过重新创建实例来实现\n // 这里保持方法兼容性,但实际上文件日志在 initLogFile 时就已经启用\n if (enable && this.logFilePath) {\n // 重新创建 pino 实例以确保文件流正确配置\n this.pinoInstance = this.createPinoInstance();\n }\n }\n\n /**\n * 记录信息级别日志\n * @param message 日志消息\n * @param args 额外参数\n * @example\n * logger.info('用户登录', 'userId', 12345);\n * logger.info({ userId: 12345, action: 'login' }, '用户登录');\n */\n info(message: string, ...args: any[]): void;\n /**\n * 记录结构化信息级别日志\n * @param obj 结构化日志对象\n * @param message 可选的日志消息\n */\n info(obj: object, message?: string): void;\n info(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.info(messageOrObj);\n } else {\n this.pinoInstance.info({ args }, messageOrObj);\n }\n } else {\n // 结构化日志支持\n this.pinoInstance.info(messageOrObj, args[0] || \"\");\n }\n }\n\n success(message: string, ...args: any[]): void;\n success(obj: object, message?: string): void;\n success(messageOrObj: string | object, ...args: any[]): void {\n // success 映射为 info 级别,保持 API 兼容性\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.info(messageOrObj);\n } else {\n this.pinoInstance.info({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.info(messageOrObj, args[0] || \"\");\n }\n }\n\n warn(message: string, ...args: any[]): void;\n warn(obj: object, message?: string): void;\n warn(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.warn(messageOrObj);\n } else {\n this.pinoInstance.warn({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.warn(messageOrObj, args[0] || \"\");\n }\n }\n\n error(message: string, ...args: any[]): void;\n error(obj: object, message?: string): void;\n error(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.error(messageOrObj);\n } else {\n // 改进错误处理 - 特殊处理 Error 对象\n const errorArgs = args.map((arg) => {\n if (arg instanceof Error) {\n if (this.pinoInstance.level === \"debug\") return arg.message;\n return {\n message: arg.message,\n stack: arg.stack,\n name: arg.name,\n cause: arg.cause,\n };\n }\n return arg;\n });\n this.pinoInstance.error({ args: errorArgs }, messageOrObj);\n }\n } else {\n // 结构化错误日志,自动提取错误信息\n const enhancedObj = this.enhanceErrorObject(messageOrObj);\n this.pinoInstance.error(enhancedObj, args[0] || \"\");\n }\n }\n\n debug(message: string, ...args: any[]): void;\n debug(obj: object, message?: string): void;\n debug(messageOrObj: string | object, ...args: any[]): void {\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.debug(messageOrObj);\n } else {\n this.pinoInstance.debug({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.debug(messageOrObj, args[0] || \"\");\n }\n }\n\n log(message: string, ...args: any[]): void;\n log(obj: object, message?: string): void;\n log(messageOrObj: string | object, ...args: any[]): void {\n // log 方法使用 info 级别\n if (typeof messageOrObj === \"string\") {\n if (args.length === 0) {\n this.pinoInstance.info(messageOrObj);\n } else {\n this.pinoInstance.info({ args }, messageOrObj);\n }\n } else {\n this.pinoInstance.info(messageOrObj, args[0] || \"\");\n }\n }\n\n /**\n * 增强错误对象,提取更多错误信息\n */\n private enhanceErrorObject(obj: any): any {\n const enhanced = { ...obj };\n\n // 遍历对象属性,查找 Error 实例\n for (const [key, value] of Object.entries(enhanced)) {\n if (value instanceof Error) {\n enhanced[key] = {\n message: value.message,\n stack: value.stack,\n name: value.name,\n cause: value.cause,\n };\n }\n }\n\n return enhanced;\n }\n\n /**\n * 检查并轮转日志文件(如果需要)\n */\n private rotateLogFileIfNeeded(): void {\n if (!this.logFilePath || !fs.existsSync(this.logFilePath)) {\n return;\n }\n\n try {\n const stats = fs.statSync(this.logFilePath);\n if (stats.size > this.maxLogFileSize) {\n this.rotateLogFile();\n }\n } catch (error) {\n // 忽略文件状态检查错误\n }\n }\n\n /**\n * 轮转日志文件\n */\n private rotateLogFile(): void {\n if (!this.logFilePath) return;\n\n try {\n const logDir = path.dirname(this.logFilePath);\n const logName = path.basename(this.logFilePath, \".log\");\n\n // 移动现有的编号日志文件\n for (let i = this.maxLogFiles - 1; i >= 1; i--) {\n const oldFile = path.join(logDir, `${logName}.${i}.log`);\n const newFile = path.join(logDir, `${logName}.${i + 1}.log`);\n\n if (fs.existsSync(oldFile)) {\n if (i === this.maxLogFiles - 1) {\n // 删除最老的文件\n fs.unlinkSync(oldFile);\n } else {\n fs.renameSync(oldFile, newFile);\n }\n }\n }\n\n // 将当前日志文件重命名为 .1.log\n const firstRotatedFile = path.join(logDir, `${logName}.1.log`);\n fs.renameSync(this.logFilePath, firstRotatedFile);\n } catch (error) {\n // 轮转失败时忽略错误,继续使用当前文件\n }\n }\n\n /**\n * 清理旧的日志文件\n */\n cleanupOldLogs(): void {\n if (!this.logFilePath) return;\n\n try {\n const logDir = path.dirname(this.logFilePath);\n const logName = path.basename(this.logFilePath, \".log\");\n\n // 删除超过最大数量的日志文件\n for (let i = this.maxLogFiles + 1; i <= this.maxLogFiles + 10; i++) {\n const oldFile = path.join(logDir, `${logName}.${i}.log`);\n if (fs.existsSync(oldFile)) {\n fs.unlinkSync(oldFile);\n }\n }\n } catch (error) {\n // 忽略清理错误\n }\n }\n\n /**\n * 设置日志文件管理参数\n */\n setLogFileOptions(maxSize: number, maxFiles: number): void {\n this.maxLogFileSize = maxSize;\n this.maxLogFiles = maxFiles;\n }\n\n /**\n * 关闭日志文件流\n */\n close(): void {\n // pino 实例会自动处理流的关闭\n // 这里保持方法兼容性\n }\n\n /**\n * 动态设置日志级别\n * @param level 新的日志级别\n * @description 动态更新Logger实例的日志级别\n */\n setLevel(level: Level): void {\n this.logLevel = this.validateLogLevel(level);\n\n // 重新创建pino实例以应用新的日志级别\n this.pinoInstance = this.createPinoInstance();\n }\n\n /**\n * 获取当前日志级别\n * @returns 当前日志级别\n */\n getLevel(): Level {\n return this.logLevel;\n }\n}\n\n// 全局Logger实例管理\nlet globalLogger: Logger | null = null;\nlet globalLogLevel: Level = \"info\"; // 全局日志级别\n\n/**\n * 创建Logger实例\n * @param level 日志级别,默认为全局级别\n * @returns Logger实例\n */\nexport function createLogger(level?: Level): Logger {\n return new Logger(level || globalLogLevel);\n}\n\n/**\n * 获取全局Logger实例\n * @returns 全局Logger实例\n */\nexport function getLogger(): Logger {\n if (!globalLogger) {\n globalLogger = new Logger(globalLogLevel); // 使用全局级别\n }\n return globalLogger;\n}\n\n/**\n * 设置全局Logger实例\n * @param logger 新的Logger实例\n */\nexport function setGlobalLogger(logger: Logger): void {\n globalLogger = logger;\n}\n\n/**\n * 设置全局日志级别\n * @param level 新的日志级别\n * @description 更新全局日志级别,并影响现有和未来的Logger实例\n */\nexport function setGlobalLogLevel(level: Level): void {\n globalLogLevel = level;\n\n // 如果已存在全局Logger实例,更新其级别\n if (globalLogger) {\n globalLogger.setLevel(level);\n }\n}\n\n/**\n * 获取当前全局日志级别\n * @returns 当前日志级别\n */\nexport function getGlobalLogLevel(): Level {\n return globalLogLevel;\n}\n\n// 导出默认实例(向后兼容)\nexport const logger = getLogger();\n","export { default as config } from \"./config\";\nexport * from \"@coze/api\";\nexport { createCozeClient } from \"./client\";\nexport { CozeApiService } from \"./service\";\n","export default {\n zh: {\n COZE_BASE_URL: \"https://api.coze.cn\",\n COZE_BASE_WS_URL: \"wss://ws.coze.cn\",\n },\n en: {\n COZE_BASE_URL: \"https://api.coze.com\",\n COZE_BASE_WS_URL: \"wss://ws.coze.com\",\n },\n};\n","/**\n * 扣子 API 客户端封装\n * 提供统一的客户端创建和配置\n */\n\nimport { CozeAPI } from \"@/lib/coze\";\nimport config from \"./config\";\n\nexport type Language = \"zh\" | \"en\";\n\n/**\n * 创建 Coze API 客户端\n * @param token - API 访问令牌\n * @param language - API 环境语言,默认为 \"zh\"(中文),可选 \"en\"(英文)\n */\nexport function createCozeClient(\n token: string,\n language: Language = \"zh\"\n): CozeAPI {\n if (!token || typeof token !== \"string\" || token.trim() === \"\") {\n throw new Error(\"扣子 API Token 不能为空\");\n }\n\n const env = config[language] || config.zh;\n\n return new CozeAPI({\n baseURL: env.COZE_BASE_URL,\n token: token.trim(),\n baseWsURL: env.COZE_BASE_WS_URL,\n debug: false,\n });\n}\n","/**\n * 扣子 API 服务类\n * 负责与扣子 API 的交互,包括工作空间和工作流的获取\n */\n\nimport type { RunWorkflowData, WorkSpace } from \"@/lib/coze\";\nimport type {\n CozeWorkflowsData,\n CozeWorkflowsParams,\n CozeWorkflowsResponse,\n} from \"@root/types/coze\";\nimport NodeCache from \"node-cache\";\nimport { createCozeClient } from \"./client\";\n\n/**\n * 扣子 API 服务类\n */\nexport class CozeApiService {\n private cache: NodeCache;\n private token: string; // 保留 token 字段用于可能的后续扩展(如 token 刷新)\n private client: ReturnType<typeof createCozeClient>;\n\n constructor(token: string) {\n this.token = token.trim();\n this.client = createCozeClient(this.token);\n\n // 初始化缓存\n this.cache = new NodeCache({\n stdTTL: 5 * 60, // 默认5分钟(工作流缓存使用此默认值)\n });\n }\n\n /**\n * 获取工作空间列表\n */\n async getWorkspaces(): Promise<WorkSpace[]> {\n const cacheKey = \"workspaces\";\n const cached = this.cache.get<WorkSpace[]>(cacheKey);\n if (cached) return cached;\n\n const { workspaces = [] } = await this.client.workspaces.list();\n\n // 设置缓存,过期时间30分钟\n this.cache.set(cacheKey, workspaces, 30 * 60);\n\n return workspaces;\n }\n\n /**\n * 获取工作流列表\n */\n async getWorkflows(params: CozeWorkflowsParams): Promise<CozeWorkflowsData> {\n const { workspace_id, page_num = 1, page_size = 20 } = params;\n\n if (!workspace_id || typeof workspace_id !== \"string\") {\n throw new Error(\"工作空间ID不能为空\");\n }\n\n const cacheKey = `workflows:${workspace_id}:${page_num}:${page_size}`;\n const cached = this.cache.get<CozeWorkflowsData>(cacheKey);\n if (cached) return cached;\n\n const response = await this.client.get<\n CozeWorkflowsParams,\n CozeWorkflowsResponse\n >(\"/v1/workflows\", {\n workspace_id,\n page_num: page_num,\n page_size: page_size,\n workflow_mode: \"workflow\",\n });\n\n const result = response.data;\n\n // 设置缓存,使用默认的5分钟过期时间\n this.cache.set(cacheKey, result);\n\n return result;\n }\n\n /**\n * 运行工作流\n * @param workflowId - 工作流ID\n * @param parameters - 参数\n * @returns 运行工作流数据\n */\n callWorkflow(\n workflowId: string,\n parameters: Record<string, unknown>\n ): Promise<RunWorkflowData> {\n return this.client.workflows.runs.create({\n workflow_id: workflowId,\n parameters,\n });\n }\n\n /**\n * 清除缓存\n * @param pattern 可选的模式字符串,清除所有以该模式开头的缓存键\n */\n clearCache(pattern?: string): void {\n if (!pattern) {\n // 清除所有缓存\n this.cache.flushAll();\n return;\n }\n\n // node-cache 不支持模式匹配,需要手动实现\n // 使用前缀匹配,避免意外匹配\n const keys = this.cache.keys();\n const keysToDelete = keys.filter((key) => key.startsWith(pattern));\n this.cache.del(keysToDelete);\n }\n\n /**\n * 获取缓存统计信息\n */\n getCacheStats(): {\n size: number;\n keys: string[];\n hits: number;\n misses: number;\n hitRate: number;\n ksize: number;\n vsize: number;\n } {\n const stats = this.cache.getStats();\n const keys = this.cache.keys();\n const totalRequests = stats.hits + stats.misses;\n const hitRate = totalRequests > 0 ? stats.hits / totalRequests : 0;\n\n return {\n size: stats.keys,\n keys,\n hits: stats.hits,\n misses: stats.misses,\n hitRate,\n ksize: stats.ksize,\n vsize: stats.vsize,\n };\n }\n}\n","/**\n * MCP 核心库类型定义\n * 统一管理所有 MCP 相关的类型定义,避免重复定义和导入路径混乱\n */\n\nimport type { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport type { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport type { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\n\n// =========================\n// 1. 基础传输类型\n// =========================\n\n/**\n * MCP 传输层联合类型定义\n * 支持 STDIO、SSE、StreamableHTTP 三种传输协议\n */\nexport type MCPServerTransport =\n | StdioClientTransport\n | SSEClientTransport\n | StreamableHTTPClientTransport;\n\n/**\n * 通信方式枚举\n * 定义 MCP 支持的传输类型\n */\nexport enum MCPTransportType {\n STDIO = \"stdio\",\n SSE = \"sse\",\n STREAMABLE_HTTP = \"streamable-http\",\n}\n\n// =========================\n// 2. 配置接口类型\n// =========================\n\n/**\n * ModelScope SSE 自定义选项接口\n * 专门用于 ModelScope 相关的 SSE 配置\n */\nexport interface ModelScopeSSEOptions {\n eventSourceInit?: {\n fetch?: (\n url: string | URL | Request,\n init?: RequestInit\n ) => Promise<Response>;\n };\n requestInit?: RequestInit;\n}\n\n/**\n * MCP 服务配置接口\n * 包含所有 MCP 服务的配置选项\n */\nexport interface MCPServiceConfig {\n name: string;\n type?: MCPTransportType; // 现在是可选的,支持自动推断\n // stdio 配置\n command?: string;\n args?: string[];\n env?: Record<string, string>; // 环境变量配置\n // 网络配置\n url?: string;\n // 认证配置\n apiKey?: string;\n headers?: Record<string, string>;\n // ModelScope 特有配置\n modelScopeAuth?: boolean;\n customSSEOptions?: ModelScopeSSEOptions;\n // 超时配置\n timeout?: number;\n // 重试配置\n retryAttempts?: number;\n}\n\n// =========================\n// 3. 状态枚举类型\n// =========================\n\n/**\n * 连接状态枚举\n * 合并了 connection.ts 和 TransportAdapter.ts 中的定义\n */\nexport enum ConnectionState {\n DISCONNECTED = \"disconnected\",\n CONNECTING = \"connecting\",\n CONNECTED = \"connected\",\n RECONNECTING = \"reconnecting\",\n FAILED = \"failed\",\n ERROR = \"error\", // 从 TransportAdapter.ts 合并的额外状态\n}\n\n/**\n * MCP 服务状态接口\n * 描述 MCP 服务的运行时状态信息\n */\nexport interface MCPServiceStatus {\n name: string;\n connected: boolean;\n initialized: boolean;\n transportType: MCPTransportType;\n toolCount: number;\n lastError?: string;\n connectionState: ConnectionState;\n}\n\n// =========================\n// 4. 工具调用相关类型\n// =========================\n\n/**\n * 工具调用结果接口\n * 统一了 connection.ts、manager.ts 和 CustomMCPHandler.ts 中的定义\n */\nexport interface ToolCallResult {\n content: Array<{\n type: string;\n text: string;\n }>;\n isError?: boolean;\n}\n\n/**\n * JSON Schema 类型定义\n * 兼容 MCP SDK 的 JSON Schema 格式,同时支持更宽松的对象格式以保持向后兼容\n */\nexport type JSONSchema =\n | (Record<string, unknown> & {\n type: \"object\";\n properties?: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n })\n | Record<string, unknown>; // 允许更宽松的格式以保持向后兼容\n\n/**\n * 类型守卫:检查对象是否为有效的 MCP Tool JSON Schema\n */\nexport function isValidToolJSONSchema(obj: unknown): obj is {\n type: \"object\";\n properties?: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n} {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"type\" in obj &&\n (obj as { type?: unknown }).type === \"object\"\n );\n}\n\n/**\n * 确保对象符合 MCP Tool JSON Schema 格式\n * 如果不符合,会返回一个默认的空对象 schema\n */\nexport function ensureToolJSONSchema(schema: JSONSchema): {\n type: \"object\";\n properties?: Record<string, object>;\n required?: string[];\n additionalProperties?: boolean;\n} {\n if (isValidToolJSONSchema(schema)) {\n return schema as {\n type: \"object\";\n properties?: Record<string, object>;\n required?: string[];\n additionalProperties?: boolean;\n };\n }\n\n // 如果不符合标准格式,返回默认的空对象 schema\n return {\n type: \"object\",\n properties: {} as Record<string, object>,\n required: [],\n additionalProperties: true,\n };\n}\n\n/**\n * CustomMCP 工具类型定义\n * 统一了 manager.ts 和 configManager.ts 中的定义\n */\nexport interface CustomMCPTool {\n name: string;\n description?: string;\n inputSchema: JSONSchema;\n handler?: {\n type: string;\n config?: Record<string, unknown>;\n };\n}\n\n/**\n * 工具信息接口\n * 用于缓存工具映射关系,保持向后兼容性\n */\nexport interface ToolInfo {\n serviceName: string;\n originalName: string;\n tool: Tool;\n}\n\n// =========================\n// 5. 服务器配置类型\n// =========================\n\n/**\n * 统一服务器配置接口\n * 从 UnifiedMCPServer 移入,用于统一服务器配置管理\n */\nexport interface UnifiedServerConfig {\n name?: string;\n enableLogging?: boolean;\n logLevel?: string;\n configs?: Record<string, MCPServiceConfig>; // MCPService 配置\n}\n\n/**\n * 统一服务器状态接口\n * 从 UnifiedMCPServer 移入,用于统一服务器状态管理\n */\nexport interface UnifiedServerStatus {\n isRunning: boolean;\n serviceStatus: ManagerStatus;\n transportCount: number;\n activeConnections: number;\n config: UnifiedServerConfig;\n // 添加对 serviceStatus 的便捷访问属性\n services?: Record<string, MCPServiceConnectionStatus>;\n totalTools?: number;\n availableTools?: string[];\n}\n\n// =========================\n// 6. 管理器相关类型\n// =========================\n\n/**\n * MCP 服务连接状态接口\n * 重命名原 ServiceStatus 为 MCPServiceConnectionStatus 避免与 CLI 的 ServiceStatus 冲突\n */\nexport interface MCPServiceConnectionStatus {\n connected: boolean;\n clientName: string;\n}\n\n/**\n * 管理器状态接口\n * 描述 MCP 服务管理器的整体状态\n */\nexport interface ManagerStatus {\n services: Record<string, MCPServiceConnectionStatus>;\n totalTools: number;\n availableTools: string[];\n}\n\n// =========================\n// 7. 参数校验相关类型\n// =========================\n\n/**\n * 工具调用参数接口\n * 定义标准工具调用参数结构\n */\nexport interface ToolCallParams {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\n/**\n * 验证后的工具调用参数\n * 参数校验通过后的标准化参数结构\n */\nexport interface ValidatedToolCallParams {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\n/**\n * 工具调用验证选项\n * 提供灵活的参数校验配置\n */\nexport interface ToolCallValidationOptions {\n /** 是否验证工具名称,默认为 true */\n validateName?: boolean;\n /** 是否验证参数格式,默认为 true */\n validateArguments?: boolean;\n /** 是否允许空参数,默认为 true */\n allowEmptyArguments?: boolean;\n /** 自定义验证函数 */\n customValidator?: (params: ToolCallParams) => string | null;\n}\n\n/**\n * 工具调用错误码枚举\n * 统一的工具调用错误码定义\n */\nexport enum ToolCallErrorCode {\n /** 无效参数 */\n INVALID_PARAMS = -32602,\n /** 工具不存在 */\n TOOL_NOT_FOUND = -32601,\n /** 服务不可用 */\n SERVICE_UNAVAILABLE = -32001,\n /** 调用超时 */\n TIMEOUT = -32002,\n /** 工具执行错误 */\n TOOL_EXECUTION_ERROR = -32000,\n}\n\n/**\n * 工具调用错误类\n * 统一的工具调用错误处理\n */\nexport class ToolCallError extends Error {\n constructor(\n public code: ToolCallErrorCode,\n message: string,\n public data?: unknown\n ) {\n super(message);\n this.name = \"ToolCallError\";\n }\n}\n\n// =========================\n// 向后兼容性别名\n// =========================\n\n/**\n * 向后兼容:ServiceStatus 别名\n * 为了与现有代码保持兼容,暂时保留此别名\n * @deprecated 请使用 MCPServiceConnectionStatus\n */\nexport type ServiceStatus = MCPServiceConnectionStatus;\n","/**\n * MCP 相关类型定义\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { MCPToolsCache } from \"@/lib/mcp\";\nimport type { TimeoutResponse } from \"./timeout.js\";\n\n// 工具调用结果接口(与 MCPServiceManager 保持一致)\nexport interface ToolCallResult {\n content: Array<{\n type: string;\n text: string;\n }>;\n isError?: boolean;\n}\n\n// MCP 消息接口 - 定义 JSON-RPC 2.0 标准消息格式\nexport interface MCPMessage {\n jsonrpc: \"2.0\";\n method: string;\n params?: unknown;\n id: string | number;\n}\n\n// MCP 响应接口 - 定义 JSON-RPC 2.0 标准响应格式\nexport interface MCPResponse {\n jsonrpc: \"2.0\";\n id: string | number;\n result?: unknown;\n error?: MCPError;\n}\n\n// MCP 错误接口 - 定义 JSON-RPC 2.0 标准错误格式\nexport interface MCPError {\n code: number;\n message: string;\n data?: unknown;\n}\n\n/**\n * 扩展的 MCP 工具缓存接口\n * 增加对 CustomMCP 执行结果的支持\n */\nexport interface ExtendedMCPToolsCache extends MCPToolsCache {\n customMCPResults?: Record<string, EnhancedToolResultCache>; // 增强的工具执行结果缓存\n}\n\n/**\n * 增强的工具执行结果缓存\n * 用于存储 CustomMCP 工具的执行结果和状态\n */\nexport interface EnhancedToolResultCache {\n result: ToolCallResult;\n timestamp: string; // ISO 8601 格式时间戳\n ttl: number; // 过期时间(毫秒)\n status: TaskStatus; // 任务状态\n consumed: boolean; // 是否已被消费(一次性缓存机制)\n taskId?: string; // 任务ID,用于查询\n retryCount: number; // 重试次数\n}\n\n/**\n * 任务状态类型\n */\nexport type TaskStatus =\n | \"pending\"\n | \"completed\"\n | \"failed\"\n | \"consumed\"\n | \"deleted\";\n\n/**\n * 缓存状态转换接口\n */\nexport interface CacheStateTransition {\n from: TaskStatus;\n to: TaskStatus;\n reason: string;\n timestamp: string;\n}\n\n/**\n * 工具调用选项\n */\nexport interface ToolCallOptions {\n timeout?: number; // 超时时间(毫秒)\n retries?: number; // 重试次数\n retryDelay?: number; // 重试延迟(毫秒)\n enableCache?: boolean; // 是否启用缓存\n taskId?: string; // 任务ID\n}\n\n/**\n * 缓存配置选项\n */\nexport interface CacheConfig {\n ttl?: number; // 缓存过期时间(毫秒),默认5分钟\n cleanupInterval?: number; // 清理间隔(毫秒),默认1分钟\n maxCacheSize?: number; // 最大缓存条目数\n enableOneTimeCache?: boolean; // 是否启用一次性缓存\n}\n\n/**\n * 超时配置选项\n */\nexport interface TimeoutConfig {\n timeout?: number; // 超时时间(毫秒),默认8秒\n enableFriendlyTimeout?: boolean; // 是否启用友好超时响应\n backgroundProcessing?: boolean; // 是否启用后台处理\n}\n\n/**\n * 任务信息接口\n */\nexport interface TaskInfo {\n taskId: string;\n toolName: string;\n arguments: Record<string, unknown>;\n status: TaskStatus;\n startTime: string;\n endTime?: string;\n error?: string;\n result?: ToolCallResult;\n}\n\n/**\n * 缓存统计信息\n */\nexport interface CacheStatistics {\n totalEntries: number;\n pendingTasks: number;\n completedTasks: number;\n failedTasks: number;\n consumedEntries: number;\n cacheHitRate: number;\n lastCleanupTime: string;\n memoryUsage: number;\n}\n\n/**\n * 工具调用结果联合类型\n * 包含正常结果和超时响应\n */\nexport type ToolCallResponse = ToolCallResult | TimeoutResponse;\n\n/**\n * 验证是否为工具调用结果\n */\nexport function isToolCallResult(\n response: unknown\n): response is ToolCallResult {\n return (\n !!response &&\n typeof response === \"object\" &&\n response !== null &&\n \"content\" in response &&\n Array.isArray((response as ToolCallResult).content) &&\n (response as ToolCallResult).content.length > 0 &&\n (response as ToolCallResult).content[0]?.type === \"text\" &&\n typeof (response as ToolCallResult).content[0]?.text === \"string\"\n );\n}\n\n/**\n * 验证是否为增强的工具结果缓存\n */\nexport function isEnhancedToolResultCache(\n cache: unknown\n): cache is EnhancedToolResultCache {\n const cacheObj = cache as EnhancedToolResultCache;\n return (\n !!cache &&\n typeof cache === \"object\" &&\n cache !== null &&\n typeof cacheObj.timestamp === \"string\" &&\n typeof cacheObj.ttl === \"number\" &&\n typeof cacheObj.status === \"string\" &&\n [\"completed\", \"pending\", \"failed\", \"consumed\"].includes(cacheObj.status) &&\n typeof cacheObj.consumed === \"boolean\" &&\n typeof cacheObj.retryCount === \"number\"\n );\n}\n\n/**\n * 验证是否为扩展的 MCP 工具缓存\n */\nexport function isExtendedMCPToolsCache(\n cache: unknown\n): cache is ExtendedMCPToolsCache {\n const cacheObj = cache as ExtendedMCPToolsCache;\n return (\n !!cache &&\n typeof cache === \"object\" &&\n cache !== null &&\n typeof cacheObj.version === \"string\" &&\n typeof cacheObj.mcpServers === \"object\" &&\n cacheObj.mcpServers !== null &&\n typeof cacheObj.metadata === \"object\" &&\n cacheObj.metadata !== null\n );\n}\n\n/**\n * 生成缓存键的工具函数\n */\nexport function generateCacheKey(\n toolName: string,\n arguments_: Record<string, unknown>\n): string {\n const argsHash = createHash(\"md5\")\n .update(JSON.stringify(arguments_ || {}))\n .digest(\"hex\");\n return `${toolName}_${argsHash}`;\n}\n\n/**\n * 格式化时间戳的工具函数\n */\nexport function formatTimestamp(timestamp: number | Date = Date.now()): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * 检查缓存是否过期\n */\nexport function isCacheExpired(timestamp: string, ttl: number): boolean {\n const cachedTime = new Date(timestamp).getTime();\n const now = Date.now();\n return now - cachedTime > ttl;\n}\n\n/**\n * 检查是否应该清理缓存条目\n */\nexport function shouldCleanupCache(cache: EnhancedToolResultCache): boolean {\n const now = Date.now();\n const cachedTime = new Date(cache.timestamp).getTime();\n\n // 已消费且超过清理时间(1分钟)\n if (cache.consumed && now - cachedTime > 60000) {\n return true;\n }\n\n // 已过期\n if (now - cachedTime > cache.ttl) {\n return true;\n }\n\n // 失败的任务立即清理\n if (cache.status === \"failed\") {\n return true;\n }\n\n return false;\n}\n\n/**\n * 默认配置常量\n */\nexport const DEFAULT_CONFIG = {\n TIMEOUT: 8000, // 8秒超时\n CACHE_TTL: 300000, // 5分钟缓存\n CLEANUP_INTERVAL: 60000, // 1分钟清理间隔\n MAX_CACHE_SIZE: 1000, // 最大缓存条目数\n ENABLE_ONE_TIME_CACHE: true, // 启用一次性缓存\n} as const;\n","/**\n * 超时错误类型\n */\nexport class TimeoutError extends Error {\n public override readonly name = \"TimeoutError\" as const;\n\n constructor(message: string) {\n super(message);\n this.name = \"TimeoutError\";\n Error.captureStackTrace(this, TimeoutError);\n }\n\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n stack: this.stack,\n };\n }\n}\n\n/**\n * 超时响应接口\n */\nexport interface TimeoutResponse {\n content: Array<{\n type: \"text\";\n text: string;\n }>;\n isError: boolean;\n taskId: string;\n status: \"timeout\";\n message: string;\n nextAction: string;\n}\n\n/**\n * 创建超时响应的工具函数\n */\nexport function createTimeoutResponse(\n taskId: string,\n toolName?: string\n): TimeoutResponse {\n const toolSpecificMessage = toolName\n ? getToolSpecificTimeoutMessage(toolName, taskId)\n : getDefaultTimeoutMessage(taskId);\n\n return {\n content: [\n {\n type: \"text\",\n text: toolSpecificMessage,\n },\n ],\n isError: false,\n taskId,\n status: \"timeout\",\n message: \"工具调用超时,正在后台处理中\",\n nextAction: \"请稍后重试或等待任务完成\",\n };\n}\n\n/**\n * 获取工具特定的超时提示信息\n */\nfunction getToolSpecificTimeoutMessage(\n toolName: string,\n taskId: string\n): string {\n const toolMessages: Record<string, string> = {\n coze_workflow: `⏱️ 扣子工作流执行超时,正在后台处理中...\n \n📋 任务信息:\n- 任务ID: ${taskId}\n- 工具类型: 扣子工作流\n- 状态: 处理中\n- 建议: 请等待30-60秒后重试查询\n\n🔄 后续操作:\n1. 使用相同参数重新调用工具\n2. 系统会自动返回已完成的任务结果\n3. 复杂工作流可能需要更长时间处理`,\n\n default: getDefaultTimeoutMessage(taskId),\n };\n\n return toolMessages[toolName] || toolMessages.default;\n}\n\n/**\n * 获取默认超时提示信息\n */\nfunction getDefaultTimeoutMessage(taskId: string): string {\n return `⏱️ 工具调用超时,正在后台处理中...\n \n📋 任务信息:\n- 任务ID: ${taskId}\n- 状态: 处理中\n- 建议: 请等待30秒后重试查询\n\n🔄 后续操作:\n1. 使用相同的参数重新调用工具\n2. 系统会自动返回已完成的任务结果\n3. 如果长时间未完成,请联系管理员`;\n}\n\n/**\n * 验证是否为超时响应\n */\nexport function isTimeoutResponse(response: any): response is TimeoutResponse {\n return !!(\n response &&\n response.status === \"timeout\" &&\n typeof response.taskId === \"string\" &&\n Array.isArray(response.content) &&\n response.content.length > 0 &&\n response.content[0].type === \"text\"\n );\n}\n\n/**\n * 验证是否为超时错误\n */\nexport function isTimeoutError(error: any): error is TimeoutError {\n return !!(\n error &&\n error.name === \"TimeoutError\" &&\n error instanceof TimeoutError\n );\n}\n","#!/usr/bin/env node\nimport { CozeApiService } from \"@/lib/coze\";\nimport type { RunWorkflowData } from \"@/lib/coze\";\nimport type { MCPServiceManager } from \"@/lib/mcp\";\nimport { MCPCacheManager } from \"@/lib/mcp\";\nimport { ensureToolJSONSchema } from \"@/lib/mcp/types.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Logger } from \"@root/Logger.js\";\nimport { logger } from \"@root/Logger.js\";\nimport { getEventBus } from \"@root/services/EventBus.js\";\nimport type {\n EnhancedToolResultCache,\n ExtendedMCPToolsCache,\n ToolCallResponse,\n ToolCallResult,\n} from \"@root/types/mcp.js\";\nimport {\n DEFAULT_CONFIG,\n generateCacheKey,\n isCacheExpired,\n shouldCleanupCache,\n} from \"@root/types/mcp.js\";\nimport { TimeoutError, createTimeoutResponse } from \"@root/types/timeout.js\";\nimport type {\n CustomMCPTool,\n HandlerConfig,\n ProxyHandlerConfig,\n} from \"@xiaozhi-client/config\";\nimport { configManager } from \"@xiaozhi-client/config\";\n\n// 工具调用参数类型\ntype ToolArguments = Record<string, unknown>;\n\n// 类型守卫函数:检查是否为代理处理器\nfunction isProxyHandler(handler: HandlerConfig): handler is ProxyHandlerConfig {\n return handler.type === \"proxy\";\n}\n\n// 扩展的工具调用选项\ninterface ToolCallOptions {\n timeout?: number; // 超时时间(毫秒)\n enableCache?: boolean; // 是否启用缓存\n taskId?: string; // 任务ID\n}\n\n/**\n * 简化版的 CustomMCPHandler\n * 专门用于处理 Coze 工作流工具,保持超时友好响应机制\n */\nexport class CustomMCPHandler {\n private logger: Logger;\n private tools: Map<string, CustomMCPTool> = new Map();\n private cacheManager: MCPCacheManager;\n private mcpServiceManager?: MCPServiceManager;\n private readonly TIMEOUT = DEFAULT_CONFIG.TIMEOUT; // 统一8秒超时\n private readonly CACHE_TTL = DEFAULT_CONFIG.CACHE_TTL; // 5分钟缓存过期\n\n constructor(\n cacheManager?: MCPCacheManager,\n mcpServiceManager?: MCPServiceManager\n ) {\n this.logger = logger;\n this.cacheManager = cacheManager || new MCPCacheManager();\n this.mcpServiceManager = mcpServiceManager;\n\n // 设置事件监听器\n this.setupEventListeners();\n }\n\n /**\n * 获取 CozeApiService 实例\n */\n private getCozeApiService(): CozeApiService {\n const token = configManager.getConfig().platforms?.coze?.token;\n\n if (!token) {\n throw new Error(\"Coze Token 配置不存在\");\n }\n\n return new CozeApiService(token);\n }\n\n /**\n * 设置事件监听器\n */\n private setupEventListeners(): void {\n const eventBus = getEventBus();\n\n // 监听配置更新事件\n eventBus.onEvent(\"config:updated\", async (data) => {\n if (data.type === \"customMCP\") {\n this.logger.info(\"[CustomMCP] 检测到配置更新,重新初始化...\");\n try {\n this.reinitialize();\n } catch (error) {\n this.logger.error(\"[CustomMCP] 配置更新处理失败:\", error);\n }\n }\n });\n }\n\n /**\n * 初始化 CustomMCP 处理器\n * 加载配置中的 customMCP 工具\n * @param tools 可选的工具数组,如果提供则使用该数组,否则从配置管理器获取\n */\n public initialize(tools?: CustomMCPTool[]): void {\n this.logger.debug(\"[CustomMCP] 初始化 CustomMCP 处理器...\");\n\n try {\n const customTools = tools || configManager.getCustomMCPTools();\n\n // 清空现有工具\n this.tools.clear();\n\n // 只加载 coze 代理工具\n for (const tool of customTools) {\n if (isProxyHandler(tool.handler) && tool.handler.platform === \"coze\") {\n this.tools.set(tool.name, tool);\n this.logger.debug(\n `[CustomMCP] 已加载 Coze 工具: ${tool.name} (workflow_id: ${tool.handler.config.workflow_id})`\n );\n } else {\n // 根据是否为 proxy 类型显示不同的警告信息\n const platformInfo = isProxyHandler(tool.handler)\n ? `/${tool.handler.platform}`\n : \"\";\n this.logger.warn(\n `[CustomMCP] 跳过不支持的工具类型: ${tool.name} (${tool.handler.type}${platformInfo})`\n );\n }\n }\n\n this.logger.debug(\n `[CustomMCP] 初始化完成,共加载 ${this.tools.size} 个 Coze 工具`\n );\n } catch (error) {\n this.logger.error(\"[CustomMCP] 初始化失败:\", error);\n throw error;\n }\n }\n\n /**\n * 获取所有工具(标准 MCP 格式)\n */\n public getTools(): Tool[] {\n return Array.from(this.tools.values()).map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: ensureToolJSONSchema(tool.inputSchema),\n }));\n }\n\n /**\n * 检查是否存在指定工具\n */\n public hasTool(toolName: string): boolean {\n return this.tools.has(toolName);\n }\n\n /**\n * 获取工具数量\n */\n public getToolCount(): number {\n return this.tools.size;\n }\n\n /**\n * 获取所有工具名称\n */\n public getToolNames(): string[] {\n return Array.from(this.tools.keys());\n }\n\n /**\n * 获取工具详细信息(用于调试)\n */\n public getToolInfo(toolName: string): CustomMCPTool | undefined {\n return this.tools.get(toolName);\n }\n\n /**\n * 重新初始化 CustomMCP 处理器\n * 重新加载配置中的 customMCP 工具\n */\n public reinitialize(): void {\n this.logger.debug(\"[CustomMCP] 重新初始化 CustomMCP 处理器...\");\n this.initialize();\n }\n\n /**\n * 调用工具(支持超时友好响应和缓存管理)\n */\n public async callTool(\n toolName: string,\n arguments_: ToolArguments,\n options?: ToolCallOptions\n ): Promise<ToolCallResponse> {\n const tool = this.tools.get(toolName);\n if (!tool) {\n throw new Error(`未找到工具: ${toolName}`);\n }\n\n // 首先检查是否有已完成的任务结果(一次性缓存)\n const completedResult = await this.getCompletedResult(toolName, arguments_);\n if (completedResult) {\n this.logger.debug(`[CustomMCP] 返回已完成的任务结果: ${toolName}`);\n // 立即清理已消费的缓存\n await this.clearConsumedCache(toolName, arguments_);\n return completedResult;\n }\n\n try {\n const timeout = options?.timeout || this.TIMEOUT;\n const result = await Promise.race([\n this.callCozeWorkflow(tool, arguments_),\n this.createTimeoutPromise(toolName, timeout),\n ]);\n\n // 缓存结果(标记为未消费)\n await this.cacheResult(toolName, arguments_, result);\n\n return result;\n } catch (error) {\n // 如果是超时错误,返回友好提示\n if (error instanceof TimeoutError) {\n const taskId = await this.generateTaskId(toolName, arguments_);\n this.logger.info(\n `[CustomMCP] 工具超时,返回友好提示: ${toolName}, taskId: ${taskId}`\n );\n return createTimeoutResponse(taskId, toolName);\n }\n\n throw error;\n }\n }\n\n /**\n * 创建超时 Promise\n */\n private async createTimeoutPromise(\n toolName: string,\n timeout: number\n ): Promise<never> {\n return new Promise((_, reject) => {\n setTimeout(() => {\n reject(new TimeoutError(`工具调用超时: ${toolName}`));\n }, timeout);\n });\n }\n\n /**\n * 获取已完成的任务结果(一次性缓存)\n */\n private async getCompletedResult(\n toolName: string,\n arguments_: ToolArguments\n ): Promise<ToolCallResult | null> {\n try {\n const cacheKey = this.generateCacheKey(toolName, arguments_);\n const cache = await this.loadExtendedCache();\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return null;\n }\n\n const cached = cache.customMCPResults[cacheKey];\n\n // 只返回已完成且未消费的结果\n if (cached.status === \"completed\" && !cached.consumed) {\n // 检查是否过期\n if (!isCacheExpired(cached.timestamp, cached.ttl)) {\n return cached.result;\n }\n }\n\n return null;\n } catch (error) {\n this.logger.warn(`[CustomMCP] 获取缓存失败: ${error}`);\n return null;\n }\n }\n\n /**\n * 处理工作流响应\n */\n private processWorkflowResponse(\n toolName: string,\n workflowData: RunWorkflowData\n ): ToolCallResult {\n try {\n // 根据 RunWorkflowData 的实际结构进行处理\n // 假设 workflowData 有 data 字段或其他响应数据字段\n const responseData = workflowData.data || workflowData;\n\n if (typeof responseData === \"string\") {\n return {\n content: [\n {\n type: \"text\",\n text: responseData,\n },\n ],\n isError: false,\n };\n }\n\n // 如果是对象,转换为 JSON 字符串\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(responseData, null, 2),\n },\n ],\n isError: false,\n };\n } catch (error) {\n this.logger.error(`[CustomMCP] 处理工作流响应失败: ${toolName}`, error);\n\n return {\n content: [\n {\n type: \"text\",\n text: `处理响应失败: ${\n error instanceof Error ? error.message : String(error)\n }`,\n },\n ],\n isError: true,\n };\n }\n }\n\n /**\n * 调用 Coze 工作流\n */\n private async callCozeWorkflow(\n tool: CustomMCPTool,\n arguments_: ToolArguments\n ): Promise<ToolCallResult> {\n const handler = tool.handler as ProxyHandlerConfig;\n const config = handler.config;\n\n this.logger.info(`[CustomMCP] 调用 Coze 工作流: ${tool.name}`, {\n workflow_id: config.workflow_id,\n });\n\n try {\n // 使用 CozeApiService\n const cozeApiService = this.getCozeApiService();\n\n // 检查 workflow_id 是否存在\n if (!config.workflow_id) {\n throw new Error(\"工作流ID未配置\");\n }\n\n // 调用 callWorkflow 方法\n const workflowResult = await cozeApiService.callWorkflow(\n config.workflow_id,\n arguments_\n );\n\n this.logger.info(`[CustomMCP] Coze 工作流调用成功: ${tool.name}`);\n\n // 转换响应格式为 ToolCallResult\n return this.processWorkflowResponse(tool.name, workflowResult);\n } catch (error) {\n this.logger.error(`[CustomMCP] Coze 工作流调用失败: ${tool.name}`, error);\n\n return {\n content: [\n {\n type: \"text\",\n text: `Coze 工作流调用失败: ${\n error instanceof Error ? error.message : String(error)\n }`,\n },\n ],\n isError: true,\n };\n }\n }\n\n /**\n * 清理已消费的缓存\n */\n private async clearConsumedCache(\n toolName: string,\n arguments_: ToolArguments\n ): Promise<void> {\n try {\n const cacheKey = this.generateCacheKey(toolName, arguments_);\n const cache = await this.loadExtendedCache();\n\n if (cache.customMCPResults?.[cacheKey]) {\n // 标记为已消费\n cache.customMCPResults[cacheKey].consumed = true;\n\n // 如果已消费且已过期,直接删除\n const cached = cache.customMCPResults[cacheKey];\n if (shouldCleanupCache(cached)) {\n delete cache.customMCPResults[cacheKey];\n }\n\n // 保存缓存更改\n await this.saveCache(cache);\n this.logger.debug(`[CustomMCP] 清理已消费缓存: ${cacheKey}`);\n }\n } catch (error) {\n this.logger.warn(`[CustomMCP] 清理缓存失败: ${error}`);\n }\n }\n\n /**\n * 生成任务ID\n */\n private async generateTaskId(\n toolName: string,\n arguments_: ToolArguments\n ): Promise<string> {\n return generateCacheKey(toolName, arguments_);\n }\n\n /**\n * 生成缓存键\n */\n private generateCacheKey(\n toolName: string,\n arguments_: ToolArguments\n ): string {\n return generateCacheKey(toolName, arguments_);\n }\n\n /**\n * 加载扩展缓存\n */\n private async loadExtendedCache(): Promise<ExtendedMCPToolsCache> {\n try {\n const cacheData = await this.cacheManager.loadExistingCache();\n return cacheData as ExtendedMCPToolsCache;\n } catch (error) {\n return {\n version: \"1.0.0\",\n mcpServers: {},\n metadata: {\n lastGlobalUpdate: new Date().toISOString(),\n totalWrites: 0,\n createdAt: new Date().toISOString(),\n },\n customMCPResults: {},\n };\n }\n }\n\n /**\n * 更新缓存结果\n */\n private async updateCacheWithResult(\n cacheKey: string,\n cacheData: EnhancedToolResultCache\n ): Promise<void> {\n try {\n const cache = await this.loadExtendedCache();\n\n if (!cache.customMCPResults) {\n cache.customMCPResults = {};\n }\n\n cache.customMCPResults[cacheKey] = cacheData;\n\n // 使用 MCPCacheManager 的保存方法\n await this.saveCache(cache);\n } catch (error) {\n this.logger.warn(`[CustomMCP] 更新缓存失败: ${error}`);\n }\n }\n\n /**\n * 缓存结果\n */\n private async cacheResult(\n toolName: string,\n arguments_: ToolArguments,\n result: ToolCallResult\n ): Promise<void> {\n try {\n const cacheKey = this.generateCacheKey(toolName, arguments_);\n const cacheData: EnhancedToolResultCache = {\n result,\n timestamp: new Date().toISOString(),\n ttl: this.CACHE_TTL,\n status: \"completed\",\n consumed: false, // 初始状态为未消费\n retryCount: 0,\n };\n\n await this.updateCacheWithResult(cacheKey, cacheData);\n this.logger.debug(`[CustomMCP] 缓存工具结果: ${toolName}`);\n } catch (error) {\n this.logger.warn(`[CustomMCP] 缓存结果失败: ${error}`);\n }\n }\n\n /**\n * 保存缓存\n */\n private async saveCache(cache: ExtendedMCPToolsCache): Promise<void> {\n try {\n await this.cacheManager.saveCache(cache);\n } catch (error) {\n this.logger.warn(`[CustomMCP] 保存缓存失败: ${error}`);\n }\n }\n\n /**\n * 清理资源\n */\n public cleanup(): void {\n this.logger.info(\"[CustomMCP] 清理 CustomMCP 处理器资源\");\n this.tools.clear();\n this.cacheManager.cleanup();\n }\n}\n","/**\n * MCP 工具调用日志模块\n * 提供工具调用的写入和查询功能\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { PathUtils } from \"@utils/PathUtils.js\";\nimport pino from \"pino\";\nimport type { Logger as PinoLogger } from \"pino\";\n\n// ==================== 类型定义 ====================\n\n/**\n * Pino 日志对象类型(内部使用)\n * 用于 formatConsoleMessage 方法的参数类型\n */\ninterface PinoLogObject {\n toolName?: string;\n success?: boolean;\n duration?: number;\n [key: string]: unknown;\n}\n\n/**\n * 工具调用记录接口\n */\nexport interface ToolCallRecord {\n toolName: string; // 工具名称\n originalToolName?: string; // 原始工具名称(未格式化的)\n serverName?: string; // 服务器名称(coze、dify、n8n、custom等)\n arguments?: Record<string, unknown>; // 调用参数\n result?: unknown; // 响应结果\n success: boolean; // 是否成功\n duration?: number; // 调用耗时(毫秒)\n error?: string; // 错误信息(如果有)\n timestamp?: number; // 时间戳(毫秒)\n}\n\n/**\n * 工具调用日志配置接口\n */\nexport interface ToolCallLogConfig {\n maxRecords?: number; // 最大记录条数,默认 100\n logFilePath?: string; // 自定义日志文件路径(可选)\n}\n\n/**\n * 查询参数接口\n */\nexport interface ToolCallQuery {\n limit?: number;\n offset?: number;\n toolName?: string;\n serverName?: string;\n success?: boolean;\n startDate?: string;\n endDate?: string;\n}\n\n// ==================== ToolCallLogger 类(写入功能)====================\n\n/**\n * MCP 工具调用记录器\n * 提供工具调用的 JSONL 格式记录功能\n */\nexport class ToolCallLogger {\n private pinoLogger: PinoLogger;\n private maxRecords: number;\n private logFilePath: string;\n\n constructor(config: ToolCallLogConfig, configDir: string) {\n this.maxRecords = config?.maxRecords ?? 100;\n\n // 确定日志文件路径 - 使用更健壮的路径处理\n if (config?.logFilePath) {\n this.logFilePath = path.resolve(path.normalize(config.logFilePath));\n } else {\n // 使用 PathUtils 的跨平台临时目录处理\n const baseDir = configDir || PathUtils.getTempDir();\n this.logFilePath = path.join(path.normalize(baseDir), \"tool-calls.jsonl\");\n }\n\n // 创建 Pino 实例\n this.pinoLogger = this.createPinoLogger(this.logFilePath);\n\n console.log(\"ToolCallLogger 初始化\", {\n maxRecords: this.maxRecords,\n path: this.logFilePath,\n });\n }\n\n /**\n * 创建 Pino Logger 实例\n */\n private createPinoLogger(logFilePath: string): PinoLogger {\n const streams: pino.StreamEntry[] = [];\n\n // 控制台流 - 使用彩色输出\n streams.push({\n level: \"info\",\n stream: {\n write: (chunk: string) => {\n try {\n const logObj = JSON.parse(chunk);\n const message = this.formatConsoleMessage(logObj);\n console.log(\"[工具调用]\", { message });\n } catch {\n console.log(\"[工具调用]\", { chunk: chunk.trim() });\n }\n },\n },\n });\n\n // 文件流 - JSONL 格式,带错误处理\n try {\n streams.push({\n level: \"info\",\n stream: pino.destination({\n dest: logFilePath,\n sync: true, // 同步写入确保测试可靠性\n append: true,\n mkdir: true,\n }),\n });\n } catch (error) {\n // 如果文件路径无效,记录错误但不抛出异常\n console.error(\"无法创建工具调用日志文件\", { error });\n }\n\n return pino(\n {\n level: \"info\",\n timestamp:\n pino.stdTimeFunctions?.isoTime || (() => `,\"time\":${Date.now()}`),\n formatters: {\n level: (_label: string, number: number) => ({ level: number }),\n },\n base: null, // 不包含 pid 和 hostname\n },\n pino.multistream(streams, { dedupe: true })\n );\n }\n\n /**\n * 格式化控制台消息\n */\n private formatConsoleMessage(logObj: PinoLogObject): string {\n const toolName = logObj.toolName || \"未知工具\";\n const success = logObj.success !== false;\n const duration = logObj.duration ? ` (${logObj.duration}ms)` : \"\";\n const status = success ? \"✅\" : \"❌\";\n\n return `${status} ${toolName}${duration}`;\n }\n\n /**\n * 清理旧的日志记录,确保不超过最大记录数量\n */\n private async cleanupOldRecords(): Promise<void> {\n try {\n // 检查日志文件是否存在\n if (!fs.existsSync(this.logFilePath)) {\n return;\n }\n\n // 读取文件内容\n const content = fs.readFileSync(this.logFilePath, \"utf8\");\n const lines = content\n .trim()\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\");\n\n // 如果记录数量未超过限制,直接返回\n if (lines.length <= this.maxRecords) {\n return;\n }\n\n // 计算需要删除的记录数量\n const recordsToRemove = lines.length - this.maxRecords + 1; // +1 为即将写入的新记录预留空间\n\n // 删除最旧的记录(从文件开头删除)\n const linesToKeep = lines.slice(recordsToRemove);\n\n // 重新写入文件\n const newContent =\n linesToKeep.join(\"\\n\") + (linesToKeep.length > 0 ? \"\\n\" : \"\");\n fs.writeFileSync(this.logFilePath, newContent, \"utf8\");\n\n console.log(\"已清理旧的工具调用记录\", {\n recordsToRemove,\n maxRecords: this.maxRecords,\n });\n } catch (error) {\n console.error(\"清理旧工具调用记录失败\", { error });\n }\n }\n\n /**\n * 记录工具调用\n */\n async recordToolCall(record: ToolCallRecord): Promise<void> {\n try {\n // 在写入新记录前,先清理旧记录以确保不超过最大记录数量\n await this.cleanupOldRecords();\n\n // 使用 Pino 记录日志,自动处理并发和文件写入\n this.pinoLogger.info(record, record.toolName);\n } catch (error) {\n // 记录失败不应该影响主流程,只记录错误日志\n console.error(\"记录工具调用失败\", { error });\n }\n }\n\n /**\n * 获取日志文件路径\n */\n getLogFilePath(): string {\n return this.logFilePath;\n }\n\n /**\n * 获取最大记录数量\n */\n getMaxRecords(): number {\n return this.maxRecords;\n }\n}\n\n// ==================== ToolCallLogService 类(查询功能)====================\n\n/**\n * 工具调用日志服务类\n * 负责读取和查询工具调用日志\n */\nexport class ToolCallLogService {\n private configDir: string;\n\n constructor(configDir?: string) {\n this.configDir = configDir || PathUtils.getConfigDir();\n }\n\n /**\n * 获取工具调用日志文件路径\n */\n private getLogFilePath(): string {\n const toolCallLogger = new ToolCallLogger({}, this.configDir);\n return toolCallLogger.getLogFilePath();\n }\n\n /**\n * 检查日志文件是否存在\n */\n private checkLogFile(): void {\n const logFilePath = this.getLogFilePath();\n if (!fs.existsSync(logFilePath)) {\n throw new Error(\"工具调用日志文件不存在\");\n }\n }\n\n /**\n * 读取并解析工具调用日志\n */\n private parseLogFile(): ToolCallRecord[] {\n const logFilePath = this.getLogFilePath();\n\n try {\n const content = fs.readFileSync(logFilePath, \"utf8\");\n const lines = content\n .trim()\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\");\n\n const records: ToolCallRecord[] = [];\n\n for (const line of lines) {\n try {\n const record = JSON.parse(line);\n // 添加时间戳字段(如果 pino 添加了时间信息)\n if (record.time) {\n record.timestamp = new Date(record.time).getTime();\n }\n // 如果没有时间戳,记录警告信息提示数据质量问题\n if (!record.timestamp) {\n console.warn(\"日志记录缺少时间戳\", { line });\n }\n records.push(record);\n } catch {\n console.warn(\"跳过无效的日志行\", { line });\n }\n }\n\n // 按时间戳倒序排列(最新的在前)\n records.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));\n\n return records;\n } catch (error) {\n console.error(\"读取日志文件失败\", { error });\n throw new Error(\"无法读取工具调用日志文件\");\n }\n }\n\n /**\n * 过滤工具调用记录\n */\n private filterRecords(\n records: ToolCallRecord[],\n query: ToolCallQuery\n ): ToolCallRecord[] {\n let filtered = [...records];\n\n // 按工具名称过滤\n if (query.toolName) {\n filtered = filtered.filter((record) =>\n record.toolName\n .toLowerCase()\n .includes(query.toolName?.toLowerCase() ?? \"\")\n );\n }\n\n // 按服务器名称过滤\n if (query.serverName) {\n filtered = filtered.filter((record) =>\n record.serverName\n ?.toLowerCase()\n .includes(query.serverName?.toLowerCase() ?? \"\")\n );\n }\n\n // 按成功状态过滤\n if (query.success !== undefined) {\n filtered = filtered.filter((record) => record.success === query.success);\n }\n\n // 按时间范围过滤\n if (query.startDate || query.endDate) {\n const startTime = query.startDate\n ? new Date(query.startDate).getTime()\n : 0;\n const endTime = query.endDate\n ? new Date(query.endDate).getTime()\n : Date.now();\n\n filtered = filtered.filter((record) => {\n const recordTime = record.timestamp || 0;\n return recordTime >= startTime && recordTime <= endTime;\n });\n }\n\n return filtered;\n }\n\n /**\n * 获取工具调用日志\n */\n async getToolCallLogs(query: ToolCallQuery = {}): Promise<{\n records: ToolCallRecord[];\n total: number;\n hasMore: boolean;\n }> {\n this.checkLogFile();\n\n const records = this.parseLogFile();\n const filtered = this.filterRecords(records, query);\n const total = filtered.length;\n\n // 分页处理\n const limit = Math.min(\n query.limit || 50,\n 1000 // 最大限制 1000\n );\n const offset = query.offset || 0;\n const paginated = filtered.slice(offset, offset + limit);\n const hasMore = offset + limit < total;\n\n console.log(\"返回工具调用日志\", {\n count: paginated.length,\n total,\n });\n\n return {\n records: paginated,\n total,\n hasMore,\n };\n }\n}\n","/**\n * 路径处理工具\n */\n\nimport { tmpdir } from \"node:os\";\n\n/**\n * 路径工具类\n */\nexport class PathUtils {\n /**\n * 获取配置目录路径\n */\n static getConfigDir(): string {\n return process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n }\n\n /**\n * 获取临时目录路径\n */\n static getTempDir(): string {\n return process.env.TMPDIR || process.env.TEMP || tmpdir();\n }\n\n /**\n * 获取用户主目录路径\n */\n static getHomeDir(): string {\n return process.env.HOME || process.env.USERPROFILE || \"\";\n }\n}\n","/**\n * 统一的 MCP 消息处理器\n * 负责处理所有 MCP 协议消息,包括 initialize、tools/list、tools/call、resources/list、prompts/list 等\n * 这是阶段一重构的核心组件,用于消除双层代理架构\n */\n\nimport type { MCPServiceManager } from \"@/lib/mcp\";\nimport { validateToolCallParams } from \"@/lib/mcp\";\nimport type {\n ClientCapabilities,\n InitializedNotification,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Logger } from \"@root/Logger.js\";\nimport { logger } from \"@root/Logger.js\";\nimport type { MCPMessage, MCPResponse } from \"@root/types/mcp.js\";\n\n// 初始化参数接口\ninterface InitializeParams {\n protocolVersion: string;\n capabilities: ClientCapabilities;\n clientInfo: {\n name: string;\n version: string;\n };\n}\n\n// 工具调用参数接口\ninterface ToolCallParams {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\n// MCP 资源接口\ninterface MCPResource {\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n}\n\n// MCP 提示接口\ninterface MCPPrompt {\n name: string;\n description?: string;\n arguments?: Array<{\n name: string;\n description?: string;\n required?: boolean;\n }>;\n}\n\nexport class MCPMessageHandler {\n private logger: Logger;\n private serviceManager: MCPServiceManager;\n\n constructor(serviceManager: MCPServiceManager) {\n this.serviceManager = serviceManager;\n this.logger = logger;\n }\n\n /**\n * 处理 MCP 消息的统一入口\n * @param message MCP 消息\n * @returns MCP 响应(对于通知消息返回 null)\n */\n async handleMessage(message: MCPMessage): Promise<MCPResponse | null> {\n this.logger.debug(`处理 MCP 消息: ${message.method}`, message);\n\n try {\n // 检查是否为通知消息(没有 id 字段)\n const isNotification = message.id === undefined;\n\n switch (message.method) {\n case \"initialize\":\n return await this.handleInitialize(\n message.params as InitializeParams,\n message.id\n );\n case \"notifications/initialized\":\n return await this.handleInitializedNotification(\n message.params as InitializedNotification[\"params\"]\n );\n case \"tools/list\":\n return await this.handleToolsList(message.id);\n case \"tools/call\":\n return await this.handleToolCall(\n message.params as ToolCallParams,\n message.id\n );\n case \"resources/list\":\n return await this.handleResourcesList(message.id);\n case \"prompts/list\":\n return await this.handlePromptsList(message.id);\n case \"ping\":\n return await this.handlePing(message.id);\n default:\n if (isNotification) {\n // 对于未知的通知消息,记录警告但不抛出错误\n this.logger.warn(`收到未知的通知消息: ${message.method}`, message);\n return null;\n }\n throw new Error(`未知的方法: ${message.method}`);\n }\n } catch (error) {\n this.logger.error(`处理消息时出错: ${message.method}`, error);\n // 通知消息不需要错误响应\n if (message.id === undefined) {\n return null;\n }\n return this.createErrorResponse(error as Error, message.id);\n }\n }\n\n /**\n * 处理 initialize 请求\n * @param params 初始化参数\n * @param id 消息ID\n * @returns 初始化响应\n */\n private async handleInitialize(\n params: InitializeParams,\n id?: string | number\n ): Promise<MCPResponse> {\n this.logger.debug(\"处理 initialize 请求\", params);\n\n // 支持多个协议版本,优先使用客户端请求的版本\n const supportedVersions = [\"2024-11-05\", \"2025-06-18\"];\n const clientVersion = params.protocolVersion;\n const responseVersion = supportedVersions.includes(clientVersion)\n ? clientVersion\n : \"2024-11-05\";\n\n this.logger.debug(\n `协议版本协商: 客户端=${clientVersion}, 服务器响应=${responseVersion}`\n );\n\n return {\n jsonrpc: \"2.0\",\n result: {\n serverInfo: {\n name: \"xiaozhi-mcp-server\",\n version: \"1.0.0\",\n },\n capabilities: {\n tools: {},\n logging: {},\n },\n protocolVersion: responseVersion,\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 处理 notifications/initialized 通知\n * @param params 通知参数\n * @returns null(通知消息不需要响应)\n */\n private async handleInitializedNotification(\n params?: InitializedNotification[\"params\"]\n ): Promise<null> {\n this.logger.debug(\"收到 initialized 通知,客户端初始化完成\", params);\n\n // 可以在这里执行一些初始化完成后的逻辑\n // 例如:记录客户端连接状态、触发事件等\n\n return null;\n }\n\n /**\n * 处理 tools/list 请求\n * @param id 消息ID\n * @returns 工具列表响应\n */\n private async handleToolsList(id?: string | number): Promise<MCPResponse> {\n this.logger.debug(\"处理 tools/list 请求\");\n\n try {\n const tools = this.serviceManager.getAllTools();\n\n // 转换为 MCP 标准格式\n const mcpTools = tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n }));\n\n return {\n jsonrpc: \"2.0\",\n result: {\n tools: mcpTools,\n },\n id: id !== undefined ? id : 1,\n };\n } catch (error) {\n this.logger.error(\"获取工具列表失败\", error);\n throw error;\n }\n }\n\n /**\n * 处理 tools/call 请求\n * @param params 工具调用参数\n * @param id 消息ID\n * @returns 工具调用响应\n */\n private async handleToolCall(\n params: ToolCallParams,\n id?: string | number\n ): Promise<MCPResponse> {\n try {\n // 参数校验\n const validatedParams = validateToolCallParams(params);\n\n const result = await this.serviceManager.callTool(\n validatedParams.name,\n validatedParams.arguments || {}\n );\n\n return {\n jsonrpc: \"2.0\",\n result: {\n content: result.content,\n isError: result.isError || false,\n },\n id: id !== undefined ? id : 1,\n };\n } catch (error) {\n this.logger.error(`工具调用失败: ${params.name}`, error);\n throw error;\n }\n }\n\n /**\n * 处理 ping 请求\n * @param id 消息ID\n * @returns ping 响应\n */\n private async handlePing(id?: string | number): Promise<MCPResponse> {\n this.logger.debug(\"处理 ping 请求\");\n\n return {\n jsonrpc: \"2.0\",\n result: {\n status: \"ok\",\n timestamp: new Date().toISOString(),\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 处理 resources/list 请求\n * @param id 消息ID\n * @returns 资源列表响应\n */\n private async handleResourcesList(\n id?: string | number\n ): Promise<MCPResponse> {\n this.logger.debug(\"处理 resources/list 请求\");\n\n // 目前返回空的资源列表\n // 如果将来需要提供资源功能,可以在这里扩展\n const resources: MCPResource[] = [];\n\n this.logger.debug(`返回 ${resources.length} 个资源`);\n\n return {\n jsonrpc: \"2.0\",\n result: {\n resources: resources,\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 处理 prompts/list 请求\n * @param id 消息ID\n * @returns 提示列表响应\n */\n private async handlePromptsList(id?: string | number): Promise<MCPResponse> {\n this.logger.debug(\"处理 prompts/list 请求\");\n\n // 目前返回空的提示列表\n // 如果将来需要提供提示模板功能,可以在这里扩展\n const prompts: MCPPrompt[] = [];\n\n this.logger.debug(`返回 ${prompts.length} 个提示模板`);\n\n return {\n jsonrpc: \"2.0\",\n result: {\n prompts: prompts,\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 创建错误响应\n * @param error 错误对象\n * @param id 消息ID\n * @returns 错误响应\n */\n private createErrorResponse(error: Error, id?: string | number): MCPResponse {\n // 根据错误类型确定错误代码\n let errorCode = -32603; // Internal error\n\n if (\n error.message.includes(\"未找到工具\") ||\n error.message.includes(\"未知的方法\")\n ) {\n errorCode = -32601; // Method not found\n } else if (\n error.message.includes(\"参数\") ||\n error.message.includes(\"不能为空\")\n ) {\n errorCode = -32602; // Invalid params\n }\n\n return {\n jsonrpc: \"2.0\",\n error: {\n code: errorCode,\n message: error.message,\n data: {\n stack: error.stack,\n },\n },\n id: id !== undefined ? id : 1,\n };\n }\n\n /**\n * 获取服务管理器实例\n * @returns MCPServiceManager 实例\n */\n getServiceManager(): MCPServiceManager {\n return this.serviceManager;\n }\n}\n","/**\n * WebSocket 传输适配器\n * 阶段四重构:支持 WebSocket 双向通信和自动重连\n *\n * 主要功能:\n * 1. WebSocket 连接管理和自动重连\n * 2. 双向实时通信支持\n * 3. 连接池管理和性能优化\n * 4. 消息压缩和批量处理\n */\n\nimport type { IncomingMessage } from \"node:http\";\nimport type { MCPMessageHandler } from \"@/lib/mcp/message.js\";\nimport WebSocket, { WebSocketServer } from \"ws\";\nimport { ConnectionState, TransportAdapter } from \"./TransportAdapter.js\";\nimport type {\n MCPMessage,\n MCPResponse,\n TransportConfig,\n} from \"./TransportAdapter.js\";\n\n/**\n * WebSocket 连接状态枚举\n */\nexport enum WebSocketState {\n DISCONNECTED = \"disconnected\",\n CONNECTING = \"connecting\",\n CONNECTED = \"connected\",\n RECONNECTING = \"reconnecting\",\n FAILED = \"failed\",\n}\n\n/**\n * 重连配置接口\n */\nexport interface ReconnectOptions {\n enabled: boolean;\n maxAttempts: number;\n initialInterval: number;\n maxInterval: number;\n backoffStrategy: \"linear\" | \"exponential\" | \"fixed\";\n backoffMultiplier: number;\n timeout: number;\n jitter: boolean;\n}\n\n/**\n * WebSocket 适配器配置\n */\nexport interface WebSocketConfig extends TransportConfig {\n endpointUrl: string;\n mode?: \"client\" | \"server\";\n reconnect?: Partial<ReconnectOptions>;\n compression?: boolean;\n batchSize?: number;\n batchTimeout?: number;\n maxConnections?: number;\n}\n\n/**\n * 重连状态接口\n */\ninterface ReconnectState {\n attempts: number;\n nextInterval: number;\n timer: NodeJS.Timeout | null;\n lastError: Error | null;\n isManualDisconnect: boolean;\n}\n\n/**\n * 消息批处理队列项\n */\ninterface BatchQueueItem {\n message: MCPMessage | MCPResponse;\n timestamp: number;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\n/**\n * WebSocket 传输适配器实现\n * 支持客户端和服务器模式的 WebSocket 通信\n */\nexport class WebSocketAdapter extends TransportAdapter {\n private ws: WebSocket | null = null;\n private wsServer: WebSocketServer | null = null;\n private endpointUrl: string;\n private mode: \"client\" | \"server\";\n private wsState: WebSocketState = WebSocketState.DISCONNECTED;\n\n // 重连相关\n private reconnectOptions: ReconnectOptions;\n private reconnectState: ReconnectState;\n private connectionTimeout: NodeJS.Timeout | null = null;\n\n // 性能优化相关\n private compression: boolean;\n private batchQueue: BatchQueueItem[] = [];\n private batchTimer: NodeJS.Timeout | null = null;\n private batchSize: number;\n private batchTimeout: number;\n\n // 连接池管理\n private connections: Map<string, WebSocket> = new Map();\n private maxConnections: number;\n\n constructor(messageHandler: MCPMessageHandler, config: WebSocketConfig) {\n super(messageHandler, config);\n\n this.endpointUrl = config.endpointUrl;\n this.mode = config.mode || \"client\";\n this.compression = config.compression || false;\n this.batchSize = config.batchSize || 10;\n this.batchTimeout = config.batchTimeout || 100;\n this.maxConnections = config.maxConnections || 100;\n\n // 初始化重连配置\n this.reconnectOptions = {\n enabled: true,\n maxAttempts: 5,\n initialInterval: 1000,\n maxInterval: 30000,\n backoffStrategy: \"exponential\",\n backoffMultiplier: 1.5,\n timeout: 10000,\n jitter: true,\n ...config.reconnect,\n };\n\n // 初始化重连状态\n this.reconnectState = {\n attempts: 0,\n nextInterval: this.reconnectOptions.initialInterval,\n timer: null,\n lastError: null,\n isManualDisconnect: false,\n };\n }\n\n /**\n * 初始化 WebSocket 适配器\n */\n async initialize(): Promise<void> {\n console.info(`初始化 WebSocket 适配器 (${this.mode} 模式)`);\n\n try {\n this.setState(ConnectionState.CONNECTING);\n this.wsState = WebSocketState.CONNECTING;\n\n if (this.mode === \"client\") {\n await this.initializeClient();\n } else {\n await this.initializeServer();\n }\n\n console.info(\"WebSocket 适配器初始化完成\");\n } catch (error) {\n console.error(\"WebSocket 适配器初始化失败\", error);\n this.setState(ConnectionState.ERROR);\n this.wsState = WebSocketState.FAILED;\n throw error;\n }\n }\n\n /**\n * 初始化客户端模式\n */\n private async initializeClient(): Promise<void> {\n return new Promise((resolve, reject) => {\n // 设置连接超时\n this.connectionTimeout = setTimeout(() => {\n const error = new Error(\n `连接超时 (${this.reconnectOptions.timeout}ms)`\n );\n this.handleConnectionError(error);\n reject(error);\n }, this.reconnectOptions.timeout);\n\n this.ws = new WebSocket(this.endpointUrl);\n\n // 启用压缩\n if (this.compression) {\n // WebSocket 压缩扩展会自动处理\n }\n\n this.ws.on(\"open\", () => {\n this.handleConnectionSuccess();\n resolve();\n });\n\n this.ws.on(\"message\", (data) => {\n this.handleIncomingData(data);\n });\n\n this.ws.on(\"close\", (code, reason) => {\n this.handleConnectionClose(code, reason.toString());\n });\n\n this.ws.on(\"error\", (error) => {\n this.handleConnectionError(error);\n reject(error);\n });\n });\n }\n\n /**\n * 初始化服务器模式\n */\n private async initializeServer(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const url = new URL(this.endpointUrl);\n const port = Number.parseInt(url.port) || 8080;\n\n this.wsServer = new WebSocketServer({\n port,\n perMessageDeflate: this.compression,\n });\n\n this.wsServer.on(\"connection\", (ws, request) => {\n this.handleNewConnection(ws, request);\n });\n\n this.wsServer.on(\"error\", (error) => {\n console.error(\"WebSocket 服务器错误\", error);\n reject(error);\n });\n\n console.info(`WebSocket 服务器监听端口 ${port}`);\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * 启动 WebSocket 适配器\n */\n async start(): Promise<void> {\n if (this.wsState === WebSocketState.CONNECTED) {\n console.warn(\"WebSocket 适配器已启动\");\n return;\n }\n\n console.info(\"启动 WebSocket 适配器\");\n\n try {\n this.setState(ConnectionState.CONNECTED);\n this.wsState = WebSocketState.CONNECTED;\n\n console.info(\"WebSocket 适配器启动成功\");\n } catch (error) {\n console.error(\"启动 WebSocket 适配器失败\", error);\n this.setState(ConnectionState.ERROR);\n this.wsState = WebSocketState.FAILED;\n throw error;\n }\n }\n\n /**\n * 停止 WebSocket 适配器\n */\n async stop(): Promise<void> {\n console.info(\"停止 WebSocket 适配器\");\n\n try {\n // 标记为手动断开\n this.reconnectState.isManualDisconnect = true;\n\n // 清理重连定时器\n if (this.reconnectState.timer) {\n clearTimeout(this.reconnectState.timer);\n this.reconnectState.timer = null;\n }\n\n // 清理批处理定时器\n if (this.batchTimer) {\n clearTimeout(this.batchTimer);\n this.batchTimer = null;\n }\n\n // 处理剩余的批处理消息\n await this.flushBatchQueue();\n\n // 关闭客户端连接(等待关闭完成)\n if (this.ws) {\n await new Promise<void>((resolve) => {\n if (this.ws!.readyState === WebSocket.CLOSED) {\n resolve();\n return;\n }\n\n const onClose = () => {\n resolve();\n };\n\n this.ws!.once(\"close\", onClose);\n this.ws!.close();\n\n // 超时保护,避免无限等待\n setTimeout(() => {\n if (this.ws && this.ws.readyState === WebSocket.CLOSED) {\n resolve();\n } else {\n // 超时后强制清理\n resolve();\n }\n }, 1000);\n });\n\n this.ws = null;\n }\n\n // 关闭服务器(等待关闭完成)\n if (this.wsServer) {\n await new Promise<void>((resolve) => {\n this.wsServer!.close((err) => {\n // 忽略关闭错误\n resolve();\n });\n // 超时保护\n setTimeout(resolve, 1000);\n });\n this.wsServer = null;\n }\n\n // 关闭所有连接\n for (const [id, connection] of this.connections) {\n if (connection.readyState === WebSocket.OPEN) {\n connection.close();\n }\n }\n this.connections.clear();\n\n this.setState(ConnectionState.DISCONNECTED);\n this.wsState = WebSocketState.DISCONNECTED;\n\n console.info(\"WebSocket 适配器已停止\");\n } catch (error) {\n console.error(\"停止 WebSocket 适配器时出错\", error);\n throw error;\n }\n }\n\n /**\n * 发送消息\n */\n async sendMessage(message: MCPMessage | MCPResponse): Promise<void> {\n if (this.wsState !== WebSocketState.CONNECTED) {\n throw new Error(`WebSocket 未连接 (状态: ${this.wsState})`);\n }\n\n // 如果启用了批处理,添加到队列\n if (this.batchSize > 1) {\n return this.addToBatchQueue(message);\n }\n\n // 直接发送\n return this.sendMessageDirect(message);\n }\n\n /**\n * 直接发送消息\n */\n private async sendMessageDirect(\n message: MCPMessage | MCPResponse\n ): Promise<void> {\n try {\n const serializedMessage = this.serializeMessage(message);\n\n if (this.mode === \"client\" && this.ws) {\n this.ws.send(serializedMessage);\n } else if (this.mode === \"server\") {\n // 广播到所有连接\n for (const connection of this.connections.values()) {\n if (connection.readyState === WebSocket.OPEN) {\n connection.send(serializedMessage);\n }\n }\n }\n\n console.debug(\"消息已发送\", {\n messageId: message.id,\n method: \"method\" in message ? message.method : \"response\",\n });\n } catch (error) {\n console.error(\"发送消息失败\", error);\n throw error;\n }\n }\n\n /**\n * 添加消息到批处理队列\n */\n private async addToBatchQueue(\n message: MCPMessage | MCPResponse\n ): Promise<void> {\n return new Promise((resolve, reject) => {\n this.batchQueue.push({\n message,\n timestamp: Date.now(),\n resolve,\n reject,\n });\n\n // 如果队列达到批处理大小,立即处理\n if (this.batchQueue.length >= this.batchSize) {\n this.flushBatchQueue();\n } else if (!this.batchTimer) {\n // 设置批处理超时\n this.batchTimer = setTimeout(() => {\n this.flushBatchQueue();\n }, this.batchTimeout);\n }\n });\n }\n\n /**\n * 刷新批处理队列\n */\n private async flushBatchQueue(): Promise<void> {\n if (this.batchQueue.length === 0) {\n return;\n }\n\n // 清理定时器\n if (this.batchTimer) {\n clearTimeout(this.batchTimer);\n this.batchTimer = null;\n }\n\n const batch = this.batchQueue.splice(0);\n\n try {\n // 批量发送消息\n const messages = batch.map((item) => item.message);\n const batchMessage = {\n jsonrpc: \"2.0\" as const,\n method: \"batch\",\n params: { messages },\n id: `batch_${Date.now()}`,\n };\n\n await this.sendMessageDirect(batchMessage);\n\n // 解析所有 Promise\n for (const item of batch) {\n item.resolve();\n }\n\n console.debug(`批处理发送 ${batch.length} 条消息`);\n } catch (error) {\n // 拒绝所有 Promise\n for (const item of batch) {\n item.reject(error as Error);\n }\n console.error(\"批处理发送失败\", error);\n }\n }\n\n /**\n * 处理接收到的数据\n */\n private async handleIncomingData(data: WebSocket.Data): Promise<void> {\n try {\n const messageStr = data.toString();\n const message = this.parseMessage(messageStr);\n\n if (message) {\n // 检查是否是批处理消息\n if (\n message.method === \"batch\" &&\n message.params &&\n typeof message.params === \"object\" &&\n \"messages\" in message.params &&\n Array.isArray((message.params as { messages: unknown[] }).messages)\n ) {\n // 处理批处理消息\n const batchMessages = (message.params as { messages: unknown[] })\n .messages;\n for (const batchedMessage of batchMessages) {\n await this.handleIncomingMessage(batchedMessage as MCPMessage);\n }\n } else {\n await this.handleIncomingMessage(message);\n }\n }\n } catch (error) {\n console.error(\"处理接收数据失败\", error);\n }\n }\n\n /**\n * 处理连接成功\n */\n private handleConnectionSuccess(): void {\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n this.setState(ConnectionState.CONNECTED);\n this.wsState = WebSocketState.CONNECTED;\n\n // 重置重连状态\n this.reconnectState.attempts = 0;\n this.reconnectState.nextInterval = this.reconnectOptions.initialInterval;\n this.reconnectState.lastError = null;\n\n console.info(\"WebSocket 连接已建立\");\n }\n\n /**\n * 处理连接错误\n */\n private handleConnectionError(error: Error): void {\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n this.reconnectState.lastError = error;\n console.error(\"WebSocket 连接错误\", error);\n\n this.setState(ConnectionState.ERROR);\n this.wsState = WebSocketState.FAILED;\n\n // 清理当前连接\n this.cleanupConnection();\n }\n\n /**\n * 处理连接关闭\n */\n private handleConnectionClose(code: number, reason: string): void {\n this.setState(ConnectionState.DISCONNECTED);\n this.wsState = WebSocketState.DISCONNECTED;\n\n console.info(`WebSocket 连接已关闭 (代码: ${code}, 原因: ${reason})`);\n\n // 如果是手动断开,不进行重连\n if (this.reconnectState.isManualDisconnect) {\n return;\n }\n\n // 检查是否需要重连\n if (this.shouldReconnect()) {\n this.scheduleReconnect();\n } else {\n this.wsState = WebSocketState.FAILED;\n console.warn(\n `已达到最大重连次数 (${this.reconnectOptions.maxAttempts}),停止重连`\n );\n }\n }\n\n /**\n * 处理新连接(服务器模式)\n */\n private handleNewConnection(ws: WebSocket, request: IncomingMessage): void {\n // 检查连接数限制\n if (this.connections.size >= this.maxConnections) {\n console.warn(\"达到最大连接数限制,拒绝新连接\");\n ws.close(1013, \"服务器繁忙\");\n return;\n }\n\n const connectionId = `${this.getConnectionId()}_${this.connections.size}`;\n this.connections.set(connectionId, ws);\n\n console.info(`新 WebSocket 连接: ${connectionId}`);\n\n ws.on(\"message\", (data) => {\n this.handleIncomingData(data);\n });\n\n ws.on(\"close\", () => {\n this.connections.delete(connectionId);\n console.info(`WebSocket 连接已断开: ${connectionId}`);\n });\n\n ws.on(\"error\", (error) => {\n console.error(`WebSocket 连接错误 ${connectionId}:`, error);\n this.connections.delete(connectionId);\n });\n }\n\n /**\n * 清理连接\n */\n private cleanupConnection(): void {\n if (this.ws) {\n try {\n // 移除监听器,防止错误事件传播\n this.ws.removeAllListeners();\n // 只在已连接状态下关闭连接\n // 在 CONNECTING 状态下调用 close() 会抛出异常\n if (this.ws.readyState === WebSocket.OPEN) {\n try {\n this.ws.close();\n } catch {\n // 忽略关闭错误\n }\n }\n // 在 CONNECTING 状态下,让连接自然超时失败\n } catch {\n // 忽略所有错误\n } finally {\n this.ws = null;\n }\n }\n }\n\n /**\n * 检查是否应该重连\n */\n private shouldReconnect(): boolean {\n return (\n this.reconnectOptions.enabled &&\n this.reconnectState.attempts < this.reconnectOptions.maxAttempts &&\n !this.reconnectState.isManualDisconnect\n );\n }\n\n /**\n * 安排重连\n */\n private scheduleReconnect(): void {\n this.wsState = WebSocketState.RECONNECTING;\n this.reconnectState.attempts++;\n\n // 计算重连间隔\n let interval = this.calculateReconnectInterval();\n\n // 添加随机抖动\n if (this.reconnectOptions.jitter) {\n interval += Math.random() * 1000;\n }\n\n console.info(\n `安排重连 (第 ${this.reconnectState.attempts} 次,${interval}ms 后)`\n );\n\n this.reconnectState.timer = setTimeout(async () => {\n try {\n await this.initializeClient();\n } catch (error) {\n console.error(\"重连失败\", error);\n\n if (this.shouldReconnect()) {\n this.scheduleReconnect();\n } else {\n this.wsState = WebSocketState.FAILED;\n }\n }\n }, interval);\n }\n\n /**\n * 计算重连间隔\n */\n private calculateReconnectInterval(): number {\n const { backoffStrategy, initialInterval, maxInterval, backoffMultiplier } =\n this.reconnectOptions;\n const attempts = this.reconnectState.attempts;\n\n let interval: number;\n\n switch (backoffStrategy) {\n case \"linear\":\n interval = initialInterval + attempts * 1000;\n break;\n case \"exponential\":\n interval = initialInterval * backoffMultiplier ** attempts;\n break;\n default:\n interval = initialInterval;\n break;\n }\n\n return Math.min(interval, maxInterval);\n }\n\n /**\n * 获取适配器状态\n */\n getStatus(): {\n wsState: WebSocketState;\n connectionState: ConnectionState;\n mode: string;\n endpointUrl: string;\n connectionCount: number;\n reconnectAttempts: number;\n batchQueueSize: number;\n compression: boolean;\n } {\n return {\n wsState: this.wsState,\n connectionState: this.state,\n mode: this.mode,\n endpointUrl: this.endpointUrl,\n connectionCount: this.connections.size,\n reconnectAttempts: this.reconnectState.attempts,\n batchQueueSize: this.batchQueue.length,\n compression: this.compression,\n };\n }\n\n /**\n * 强制重连\n */\n async forceReconnect(): Promise<void> {\n if (this.mode !== \"client\") {\n throw new Error(\"只有客户端模式支持重连\");\n }\n\n console.info(\"强制重连\");\n\n // 重置重连状态\n this.reconnectState.attempts = 0;\n this.reconnectState.isManualDisconnect = false;\n\n // 关闭当前连接\n if (this.ws) {\n this.ws.close();\n }\n\n // 立即重连\n await this.initializeClient();\n }\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { getEventBus } from \"@root/services/EventBus.js\";\nimport { TransportFactory } from \"./transport-factory.js\";\nimport type {\n MCPServerTransport,\n MCPServiceConfig,\n MCPServiceStatus,\n ToolCallResult,\n} from \"./types.js\";\nimport { ConnectionState, MCPTransportType } from \"./types.js\";\nimport { inferTransportTypeFromConfig } from \"./utils.js\";\n\n/**\n * MCP 服务类\n * 负责管理单个 MCP 服务的连接、工具管理和调用\n */\nexport class MCPService {\n private config: MCPServiceConfig;\n private client: Client | null = null;\n private transport: MCPServerTransport | null = null;\n private tools: Map<string, Tool> = new Map();\n private connectionState: ConnectionState = ConnectionState.DISCONNECTED;\n private connectionTimeout: NodeJS.Timeout | null = null;\n private initialized = false;\n private eventBus = getEventBus();\n\n constructor(config: MCPServiceConfig) {\n // 使用工具方法推断服务类型\n this.config = inferTransportTypeFromConfig(config);\n\n // 验证配置\n this.validateConfig();\n }\n\n /**\n * 验证配置\n */\n private validateConfig(): void {\n // 使用 TransportFactory 进行配置验证\n TransportFactory.validateConfig(this.config);\n }\n\n /**\n * 连接到 MCP 服务\n */\n async connect(): Promise<void> {\n // 如果正在连接中,等待当前连接完成\n if (this.connectionState === ConnectionState.CONNECTING) {\n throw new Error(\"连接正在进行中,请等待连接完成\");\n }\n\n // 清理之前的连接\n this.cleanupConnection();\n\n return this.attemptConnection();\n }\n\n /**\n * 尝试建立连接\n */\n private async attemptConnection(): Promise<void> {\n this.connectionState = ConnectionState.CONNECTING;\n console.debug(\n `[MCP-${this.config.name}] 正在连接 MCP 服务: ${this.config.name}`\n );\n\n return new Promise((resolve, reject) => {\n // 设置连接超时\n this.connectionTimeout = setTimeout(() => {\n const error = new Error(`连接超时 (${this.config.timeout || 10000}ms)`);\n this.handleConnectionError(error);\n reject(error);\n }, this.config.timeout || 10000);\n\n try {\n this.client = new Client(\n {\n name: `xiaozhi-${this.config.name}-client`,\n version: \"1.0.0\",\n },\n {\n capabilities: {},\n }\n );\n\n // 使用 TransportFactory 创建传输层\n this.transport = TransportFactory.create(this.config);\n\n // 连接到 MCP 服务\n this.client\n .connect(this.transport as MCPServerTransport)\n .then(async () => {\n this.handleConnectionSuccess();\n\n // 获取工具列表\n await this.refreshTools();\n\n // 发射连接成功事件(包含工具列表)\n this.eventBus.emitEvent(\"mcp:service:connected\", {\n serviceName: this.config.name,\n tools: this.getTools(),\n connectionTime: new Date(),\n });\n\n resolve();\n })\n .catch((error) => {\n this.handleConnectionError(error);\n reject(error);\n });\n } catch (error) {\n this.handleConnectionError(error as Error);\n reject(error);\n }\n });\n }\n\n /**\n * 处理连接成功\n */\n private handleConnectionSuccess(): void {\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n this.connectionState = ConnectionState.CONNECTED;\n this.initialized = true;\n\n console.info(\n `[MCP-${this.config.name}] MCP 服务 ${this.config.name} 连接已建立`\n );\n }\n\n /**\n * 处理连接错误\n */\n private handleConnectionError(error: Error): void {\n this.connectionState = ConnectionState.DISCONNECTED;\n this.initialized = false;\n\n console.debug(`MCP 服务 ${this.config.name} 连接错误:`, error.message);\n\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n // 清理当前连接\n this.cleanupConnection();\n\n // 发射连接失败事件\n this.eventBus.emitEvent(\"mcp:service:connection:failed\", {\n serviceName: this.config.name,\n error,\n attempt: 0,\n });\n }\n\n /**\n * 清理连接资源\n */\n private cleanupConnection(): void {\n // 清理客户端\n if (this.client) {\n try {\n this.client.close().catch(() => {\n // 忽略关闭时的错误\n });\n } catch (error) {\n // 忽略关闭时的错误\n }\n this.client = null;\n }\n\n // 清理传输层\n this.transport = null;\n\n // 清理连接超时定时器\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n\n // 重置状态\n this.initialized = false;\n }\n\n /**\n * 刷新工具列表\n */\n private async refreshTools(): Promise<void> {\n if (!this.client) {\n throw new Error(\"客户端未初始化\");\n }\n\n try {\n const toolsResult = await this.client.listTools();\n const tools: Tool[] = toolsResult.tools || [];\n\n // 清空现有工具\n this.tools.clear();\n\n // 注册工具到映射表\n for (const tool of tools) {\n this.tools.set(tool.name, tool);\n }\n\n console.debug(\n `${this.config.name} 服务加载了 ${tools.length} 个工具: ${tools\n .map((t) => t.name)\n .join(\", \")}`\n );\n } catch (error) {\n console.error(\n `${this.config.name} 获取工具列表失败:`,\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n }\n\n /**\n * 断开连接\n */\n async disconnect(): Promise<void> {\n console.info(`主动断开 MCP 服务 ${this.config.name} 连接`);\n\n // 清理连接资源\n this.cleanupConnection();\n\n // 设置状态为已断开\n this.connectionState = ConnectionState.DISCONNECTED;\n\n // 发射断开连接事件\n this.eventBus.emitEvent(\"mcp:service:disconnected\", {\n serviceName: this.config.name,\n reason: \"手动断开\",\n disconnectionTime: new Date(),\n });\n }\n\n /**\n * 获取工具列表\n */\n getTools(): Tool[] {\n return Array.from(this.tools.values());\n }\n\n /**\n * 调用工具\n */\n async callTool(\n name: string,\n arguments_: Record<string, unknown>\n ): Promise<ToolCallResult> {\n if (!this.client) {\n throw new Error(`服务 ${this.config.name} 未连接`);\n }\n\n if (!this.tools.has(name)) {\n throw new Error(`工具 ${name} 在服务 ${this.config.name} 中不存在`);\n }\n\n console.debug(\n `调用 ${this.config.name} 服务的工具 ${name},参数:`,\n JSON.stringify(arguments_)\n );\n\n try {\n const result = await this.client.callTool({\n name,\n arguments: arguments_ || {},\n });\n\n console.debug(\n `工具 ${name} 调用成功,结果:`,\n `${JSON.stringify(result).substring(0, 500)}...`\n );\n\n return result as ToolCallResult;\n } catch (error) {\n console.error(\n `工具 ${name} 调用失败:`,\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n }\n\n /**\n * 获取服务配置\n */\n getConfig(): MCPServiceConfig {\n return this.config;\n }\n\n /**\n * 获取服务状态\n */\n getStatus(): MCPServiceStatus {\n return {\n name: this.config.name,\n connected: this.connectionState === ConnectionState.CONNECTED,\n initialized: this.initialized,\n transportType: this.config.type || MCPTransportType.STREAMABLE_HTTP,\n toolCount: this.tools.size,\n connectionState: this.connectionState,\n };\n }\n\n /**\n * 检查是否已连接\n */\n isConnected(): boolean {\n return (\n this.connectionState === ConnectionState.CONNECTED && this.initialized\n );\n }\n}\n","import type { MCPServerTransport, MCPServiceConfig } from \"@/lib/mcp/types\";\nimport { MCPTransportType } from \"@/lib/mcp/types\";\nimport type { SSEClientTransportOptions } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport type { StreamableHTTPClientTransportOptions } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { EventSource } from \"eventsource\";\n\n// 全局 polyfill EventSource(用于 SSE)\n// 使用类型断言避免与已有 EventSource 类型冲突\nif (\n typeof global !== \"undefined\" &&\n !(global as typeof global & { EventSource?: unknown }).EventSource\n) {\n (global as typeof global & { EventSource: typeof EventSource }).EventSource =\n EventSource;\n}\n\n// Transport 基础接口\nexport interface Transport {\n connect?(): Promise<void>;\n close?(): Promise<void>;\n}\n\n/**\n * 创建 transport 实例\n * @param config MCP 服务配置\n * @returns transport 实例\n */\nexport function createTransport(config: MCPServiceConfig): MCPServerTransport {\n console.debug(\n `[TransportFactory] 创建 ${config.type} transport for ${config.name}`\n );\n\n switch (config.type) {\n case MCPTransportType.STDIO:\n return createStdioTransport(config);\n\n case MCPTransportType.SSE:\n return createSSETransport(config);\n\n case MCPTransportType.STREAMABLE_HTTP:\n return createStreamableHTTPTransport(config);\n\n default:\n throw new Error(`不支持的传输类型: ${config.type}`);\n }\n}\n\n/**\n * 创建 Stdio transport\n */\nfunction createStdioTransport(config: MCPServiceConfig): StdioClientTransport {\n if (!config.command) {\n throw new Error(\"stdio transport 需要 command 配置\");\n }\n\n return new StdioClientTransport({\n command: config.command,\n args: config.args || [],\n env: config.env, // 传递环境变量\n });\n}\n\n/**\n * 创建 SSE transport\n */\nfunction createSSETransport(config: MCPServiceConfig): SSEClientTransport {\n if (!config.url) {\n throw new Error(\"SSE transport 需要 URL 配置\");\n }\n\n const url = new URL(config.url);\n const options = createSSEOptions(config);\n\n return new SSEClientTransport(url, options);\n}\n\n/**\n * 创建 ModelScope SSE transport\n */\nfunction createModelScopeSSETransport(\n config: MCPServiceConfig\n): SSEClientTransport {\n if (!config.url) {\n throw new Error(\"ModelScope SSE transport 需要 URL 配置\");\n }\n\n if (!config.apiKey) {\n throw new Error(\"ModelScope SSE transport 需要 apiKey 配置\");\n }\n\n const url = new URL(config.url);\n const options = createModelScopeSSEOptions(config);\n\n return new SSEClientTransport(url, options);\n}\n\nfunction createStreamableHTTPTransport(\n config: MCPServiceConfig\n): StreamableHTTPClientTransport {\n if (!config.url) {\n throw new Error(\"StreamableHTTP transport 需要 URL 配置\");\n }\n\n const url = new URL(config.url);\n const options = createStreamableHTTPOptions(config);\n return new StreamableHTTPClientTransport(url, options);\n}\n\n/**\n * 创建 SSE 选项\n */\nfunction createSSEOptions(config: MCPServiceConfig): SSEClientTransportOptions {\n const options: SSEClientTransportOptions = {};\n\n // 添加认证头\n if (config.apiKey) {\n options.requestInit = {\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n ...config.headers,\n },\n };\n } else if (config.headers) {\n options.requestInit = {\n headers: config.headers,\n };\n }\n\n return options;\n}\n\n/**\n * 创建 ModelScope SSE 选项\n */\nfunction createModelScopeSSEOptions(\n config: MCPServiceConfig\n): SSEClientTransportOptions {\n // 添加防御性空值检查\n if (!config.apiKey) {\n throw new Error(\"ModelScope SSE transport 需要 apiKey 配置\");\n }\n\n const token = config.apiKey;\n\n // 如果有自定义SSE选项,使用它们\n if (config.customSSEOptions) {\n return config.customSSEOptions;\n }\n\n // 默认的ModelScope SSE选项配置\n return {\n eventSourceInit: {\n fetch: async (url: string | URL | Request, init?: RequestInit) => {\n // 添加认证头\n const headers = {\n ...init?.headers,\n Authorization: `Bearer ${token}`,\n };\n\n return fetch(url, { ...init, headers });\n },\n },\n requestInit: {\n headers: {\n Authorization: `Bearer ${token}`,\n ...config.headers,\n },\n },\n };\n}\n\nfunction createStreamableHTTPOptions(\n config: MCPServiceConfig\n): StreamableHTTPClientTransportOptions {\n const options: StreamableHTTPClientTransportOptions = {};\n\n // 添加认证头\n if (config.apiKey) {\n options.requestInit = {\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n ...config.headers,\n },\n };\n } else if (config.headers) {\n options.requestInit = {\n headers: config.headers,\n };\n }\n\n return options;\n}\n\n/**\n * 验证配置\n */\nexport function validateConfig(config: MCPServiceConfig): void {\n if (!config.name || typeof config.name !== \"string\") {\n throw new Error(\"配置必须包含有效的 name 字段\");\n }\n\n // type 字段现在是可选的,由 MCPService 自动推断\n // 这里我们只验证如果 type 存在,必须是有效的类型\n if (config.type && !Object.values(MCPTransportType).includes(config.type)) {\n throw new Error(`不支持的传输类型: ${config.type}`);\n }\n\n // 注意:这个验证方法在 MCPService.inferTransportType 之后调用\n // 此时 config.type 应该已经被推断或显式设置\n if (!config.type) {\n throw new Error(\"传输类型未设置,这应该在 inferTransportType 中处理\");\n }\n\n switch (config.type) {\n case MCPTransportType.STDIO:\n if (!config.command) {\n throw new Error(\"stdio 类型需要 command 字段\");\n }\n break;\n\n case MCPTransportType.SSE:\n if (config.url === undefined || config.url === null) {\n throw new Error(`${config.type} 类型需要 url 字段`);\n }\n break;\n case MCPTransportType.STREAMABLE_HTTP:\n // STREAMABLE_HTTP 允许空 URL,会在后续处理中设置默认值\n if (config.url === undefined || config.url === null) {\n throw new Error(`${config.type} 类型需要 url 字段`);\n }\n break;\n\n default:\n throw new Error(`不支持的传输类型: ${config.type}`);\n }\n}\n\n/**\n * 获取支持的传输类型列表\n */\nexport function getSupportedTypes(): MCPTransportType[] {\n return [\n MCPTransportType.STDIO,\n MCPTransportType.SSE,\n MCPTransportType.STREAMABLE_HTTP,\n ];\n}\n\n/**\n * Transport 工厂对象(保持 API 兼容性)\n */\nexport const TransportFactory = {\n create: createTransport,\n validateConfig,\n getSupportedTypes,\n};\n","/**\n * MCP 服务器配置 Type 字段标准化工具\n * 支持将各种 type 字段格式转换为标准的中划线格式\n */\n\n/**\n * MCP 服务器配置的基础接口\n * 定义包含可选 type 字段的配置对象结构\n */\nexport interface MCPBaseConfig {\n type?: string;\n [key: string]: unknown; // 允许其他配置属性\n}\n\n/**\n * MCP 服务器配置 Type 字段标准化工具类\n */\nexport namespace TypeFieldNormalizer {\n /**\n * 标准化type字段格式\n * 支持将各种格式转换为标准的中划线格式\n */\n // 函数重载:泛型版本,用于类型安全的调用\n export function normalizeTypeField<T extends MCPBaseConfig>(config: T): T;\n\n // 函数重载:向后兼容版本,用于 unknown 类型输入\n export function normalizeTypeField(config: unknown): unknown;\n\n // 统一实现\n export function normalizeTypeField<T extends MCPBaseConfig>(\n config: T | unknown\n ): T | unknown {\n if (!config || typeof config !== \"object\") {\n return config;\n }\n\n // 创建配置的深拷贝以避免修改原始对象\n const normalizedConfig = JSON.parse(JSON.stringify(config));\n\n // 如果配置中没有type字段,直接返回\n if (!(\"type\" in normalizedConfig)) {\n return normalizedConfig;\n }\n\n const originalType = normalizedConfig.type;\n\n // 如果已经是标准格式,直接返回\n if (originalType === \"sse\" || originalType === \"streamable-http\") {\n return normalizedConfig;\n }\n\n // 转换为标准格式\n let normalizedType: string;\n\n if (\n originalType === \"streamableHttp\" ||\n originalType === \"streamable_http\"\n ) {\n normalizedType = \"streamable-http\";\n } else if (originalType === \"s_se\" || originalType === \"s-se\") {\n normalizedType = \"sse\";\n } else {\n // 对于其他格式,尝试智能转换\n normalizedType = convertToKebabCase(originalType);\n }\n\n // 验证转换后的类型是否有效\n if (normalizedType === \"sse\" || normalizedType === \"streamable-http\") {\n normalizedConfig.type = normalizedType;\n // 记录转换日志(如果有的话)\n if (originalType !== normalizedType) {\n // 可以在需要时添加日志记录\n }\n }\n\n return normalizedConfig;\n }\n\n /**\n * 将字符串转换为kebab-case格式\n */\n function convertToKebabCase(str: string): string {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1-$2\") // 驼峰转中划线\n .replace(/_/g, \"-\") // 下划线转中划线\n .toLowerCase(); // 转小写\n }\n}\n","import { TypeFieldNormalizer } from \"@utils/TypeFieldNormalizer.js\";\nimport { MCPTransportType, ToolCallError, ToolCallErrorCode } from \"./types.js\";\nimport type {\n MCPServiceConfig,\n ToolCallParams,\n ToolCallValidationOptions,\n ValidatedToolCallParams,\n} from \"./types.js\";\n\n/**\n * 根据 URL 路径推断传输类型\n * 基于路径末尾推断,支持包含多个 / 的复杂路径\n *\n * @param url - 要推断的 URL\n * @param options - 可选配置项\n * @returns 推断出的传输类型\n */\nexport function inferTransportTypeFromUrl(\n url: string,\n options?: {\n serviceName?: string;\n }\n): MCPTransportType {\n try {\n const parsedUrl = new URL(url);\n const pathname = parsedUrl.pathname;\n\n // 检查路径末尾\n if (pathname.endsWith(\"/sse\")) {\n return MCPTransportType.SSE;\n }\n if (pathname.endsWith(\"/mcp\")) {\n return MCPTransportType.STREAMABLE_HTTP;\n }\n\n // 默认类型 - 使用 console 输出\n if (options?.serviceName) {\n console.info(\n `[MCP-${options.serviceName}] URL 路径 ${pathname} 不匹配特定规则,默认推断为 streamable-http 类型`\n );\n }\n return MCPTransportType.STREAMABLE_HTTP;\n } catch (error) {\n if (options?.serviceName) {\n console.warn(\n `[MCP-${options.serviceName}] URL 解析失败,默认推断为 streamable-http 类型`,\n error\n );\n }\n return MCPTransportType.STREAMABLE_HTTP;\n }\n}\n\n/**\n * 完整的配置类型推断(包括 command 字段)\n *\n * @param config - MCP 服务配置\n * @returns 完整的配置对象,包含推断出的类型\n */\nexport function inferTransportTypeFromConfig(\n config: MCPServiceConfig\n): MCPServiceConfig {\n // 如果已显式指定类型,先标准化然后返回\n if (config.type) {\n const normalizedConfig = TypeFieldNormalizer.normalizeTypeField(config);\n return normalizedConfig as MCPServiceConfig;\n }\n\n // 基于 command 字段推断\n if (config.command) {\n return {\n ...config,\n type: MCPTransportType.STDIO,\n };\n }\n\n // 基于 URL 字段推断(排除 null 和 undefined)\n if (config.url !== undefined && config.url !== null) {\n const inferredType = inferTransportTypeFromUrl(config.url, {\n serviceName: config.name,\n });\n return {\n ...config,\n type: inferredType,\n };\n }\n\n throw new Error(\n `无法为服务 ${config.name} 推断传输类型。请显式指定 type 字段,或提供 command/url 配置`\n );\n}\n\n// =========================\n// 参数校验工具函数\n// =========================\n\n/**\n * 验证工具调用参数\n * 对传入的参数进行完整性和格式验证\n *\n * @param params 待验证的参数\n * @param options 验证选项\n * @returns 验证后的参数\n * @throws ToolCallError 验证失败时抛出\n */\nexport function validateToolCallParams(\n params: unknown,\n options?: ToolCallValidationOptions\n): ValidatedToolCallParams {\n const opts = {\n validateName: true,\n validateArguments: true,\n allowEmptyArguments: true,\n ...options,\n };\n\n // 1. 验证参数必须是对象\n if (!params || typeof params !== \"object\") {\n throw new ToolCallError(\n ToolCallErrorCode.INVALID_PARAMS,\n \"请求参数必须是对象\"\n );\n }\n\n const paramsObj = params as Record<string, unknown>;\n\n // 2. 验证工具名称\n if (opts.validateName) {\n if (!paramsObj.name || typeof paramsObj.name !== \"string\") {\n throw new ToolCallError(\n ToolCallErrorCode.INVALID_PARAMS,\n \"工具名称必须是非空字符串\"\n );\n }\n }\n\n // 3. 验证工具参数格式\n if (\n opts.validateArguments &&\n paramsObj.arguments !== undefined &&\n paramsObj.arguments !== null\n ) {\n if (\n typeof paramsObj.arguments !== \"object\" ||\n Array.isArray(paramsObj.arguments)\n ) {\n throw new ToolCallError(\n ToolCallErrorCode.INVALID_PARAMS,\n \"工具参数必须是对象\"\n );\n }\n }\n\n // 4. 验证是否允许空参数\n if (\n !opts.allowEmptyArguments &&\n paramsObj.arguments !== undefined &&\n paramsObj.arguments !== null\n ) {\n const argsObj = paramsObj.arguments as Record<string, unknown>;\n if (Object.keys(argsObj).length === 0) {\n throw new ToolCallError(\n ToolCallErrorCode.INVALID_PARAMS,\n \"工具参数不能为空\"\n );\n }\n }\n\n // 5. 执行自定义验证\n if (opts.customValidator) {\n const error = opts.customValidator(paramsObj as unknown as ToolCallParams);\n if (error) {\n throw new ToolCallError(ToolCallErrorCode.INVALID_PARAMS, error);\n }\n }\n\n return {\n name: paramsObj.name as string,\n arguments: paramsObj.arguments as Record<string, unknown>,\n };\n}\n","/**\n * MCP 缓存管理器\n * 负责 MCP 服务工具列表的缓存写入功能\n * 专注于缓存文件管理和数据写入的基础设施\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport type { MCPServiceConfig } from \"@/lib/mcp/types\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Logger } from \"@root/Logger.js\";\nimport { logger } from \"@root/Logger.js\";\nimport type {\n CacheStatistics,\n EnhancedToolResultCache,\n ExtendedMCPToolsCache,\n TaskStatus,\n ToolCallResult,\n} from \"@root/types/index.js\";\nimport { generateCacheKey, shouldCleanupCache } from \"@root/types/index.js\";\nimport dayjs from \"dayjs\";\n\n// 缓存条目接口\nexport interface MCPToolsCacheEntry {\n tools: Tool[]; // 工具列表\n lastUpdated: string; // 最后更新时间 (YYYY-MM-DD HH:mm:ss)\n serverConfig: MCPServiceConfig; // 服务配置快照\n configHash: string; // 配置哈希值,用于快速变更检测\n version: string; // 缓存条目版本\n}\n\n// 缓存文件接口\nexport interface MCPToolsCache {\n version: string; // 缓存文件格式版本 \"1.0.0\"\n mcpServers: Record<string, MCPToolsCacheEntry>;\n metadata: {\n lastGlobalUpdate: string; // 全局最后更新时间 (YYYY-MM-DD HH:mm:ss)\n totalWrites: number; // 总写入次数\n createdAt: string; // 缓存文件创建时间 (YYYY-MM-DD HH:mm:ss)\n };\n}\n\n// 缓存统计接口\nexport interface CacheStats {\n totalWrites: number;\n lastUpdate: string;\n serverCount: number;\n cacheFileSize: number;\n}\n\n// 重新导出相关类型\nexport type {\n CacheStatistics,\n EnhancedToolResultCache,\n ExtendedMCPToolsCache,\n} from \"@root/types/index.js\";\n\nexport class MCPCacheManager {\n private cachePath: string;\n private logger: Logger;\n private readonly CACHE_VERSION = \"1.0.0\";\n private readonly CACHE_ENTRY_VERSION = \"1.0.0\";\n private cleanupInterval?: NodeJS.Timeout;\n private readonly CLEANUP_INTERVAL = 60000; // 1分钟清理间隔\n\n constructor(customCachePath?: string) {\n this.logger = logger;\n this.cachePath = customCachePath || this.getCacheFilePath();\n this.startCleanupTimer();\n }\n\n /**\n * 格式化时间戳为 YYYY-MM-DD HH:mm:ss 格式\n */\n private formatTimestamp(): string {\n return dayjs().format(\"YYYY-MM-DD HH:mm:ss\");\n }\n\n /**\n * 获取缓存文件路径\n * 与 xiaozhi.config.json 同级目录\n */\n private getCacheFilePath(): string {\n try {\n const configDir = process.env.XIAOZHI_CONFIG_DIR || process.cwd();\n return resolve(configDir, \"xiaozhi.cache.json\");\n } catch (error) {\n // 在某些测试环境中 process.cwd() 可能不可用,使用默认路径\n const configDir = process.env.XIAOZHI_CONFIG_DIR || \"/tmp\";\n return resolve(configDir, \"xiaozhi.cache.json\");\n }\n }\n\n /**\n * 确保缓存文件存在,如不存在则创建\n */\n async ensureCacheFile(): Promise<void> {\n try {\n if (!existsSync(this.cachePath)) {\n // 确保缓存文件的目录存在\n const cacheDir = dirname(this.cachePath);\n if (!existsSync(cacheDir)) {\n mkdirSync(cacheDir, { recursive: true });\n this.logger.debug(`[CacheManager] 已创建缓存目录: ${cacheDir}`);\n }\n\n this.logger.debug(\"[CacheManager] 缓存文件不存在,创建初始缓存文件\");\n const initialCache = await this.createInitialCache();\n await this.saveCache(initialCache);\n this.logger.info(`[CacheManager] 已创建缓存文件: ${this.cachePath}`);\n }\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 创建缓存文件失败: ${error instanceof Error ? error.message : String(error)}`\n );\n // 不抛出异常,确保不影响主流程\n }\n }\n\n /**\n * 创建初始缓存结构\n */\n private async createInitialCache(): Promise<MCPToolsCache> {\n const now = this.formatTimestamp();\n return {\n version: this.CACHE_VERSION,\n mcpServers: {},\n metadata: {\n lastGlobalUpdate: now,\n totalWrites: 0,\n createdAt: now,\n },\n };\n }\n\n /**\n * 写入缓存条目\n * @param serverName 服务名称\n * @param tools 工具列表\n * @param config 服务配置\n */\n async writeCacheEntry(\n serverName: string,\n tools: Tool[],\n config: MCPServiceConfig\n ): Promise<void> {\n try {\n this.logger.debug(`[CacheManager] 开始写入缓存: ${serverName}`);\n\n // 确保缓存文件存在\n await this.ensureCacheFile();\n\n // 加载现有缓存\n const cache = await this.loadExistingCache();\n\n // 生成配置哈希\n const configHash = this.generateConfigHash(config);\n\n // 创建缓存条目\n const cacheEntry: MCPToolsCacheEntry = {\n tools: tools.map((tool) => ({\n name: tool.name,\n description: tool.description || \"\",\n inputSchema: tool.inputSchema,\n })),\n lastUpdated: this.formatTimestamp(),\n serverConfig: { ...config }, // 深拷贝配置\n configHash,\n version: this.CACHE_ENTRY_VERSION,\n };\n\n // 更新缓存\n cache.mcpServers[serverName] = cacheEntry;\n cache.metadata.lastGlobalUpdate = this.formatTimestamp();\n cache.metadata.totalWrites += 1;\n\n // 保存缓存\n await this.saveCache(cache);\n\n this.logger.debug(\n `[CacheManager] 缓存写入成功: ${serverName}, 工具数量: ${tools.length}`\n );\n } catch (error) {\n // 记录错误但不抛出异常,确保不影响主流程\n this.logger.warn(\n `[CacheManager] 缓存写入失败: ${serverName}, 错误: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * 加载现有缓存\n */\n public async loadExistingCache(): Promise<MCPToolsCache> {\n try {\n if (!existsSync(this.cachePath)) {\n return await this.createInitialCache();\n }\n\n const cacheData = readFileSync(this.cachePath, \"utf8\");\n const cache: unknown = JSON.parse(cacheData);\n\n // 验证缓存结构\n if (!this.validateCacheStructure(cache)) {\n this.logger.warn(\"[CacheManager] 缓存文件结构无效,重新创建\");\n return await this.createInitialCache();\n }\n\n return cache;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 加载缓存失败,创建新缓存: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return await this.createInitialCache();\n }\n }\n\n /**\n * 保存缓存到文件(原子写入)\n */\n public async saveCache(cache: MCPToolsCache): Promise<void> {\n const cacheContent = JSON.stringify(cache, null, 2);\n await this.atomicWrite(this.cachePath, cacheContent);\n }\n\n /**\n * 原子写入文件\n * 使用临时文件确保写入操作的原子性\n */\n private async atomicWrite(filePath: string, data: string): Promise<void> {\n const tempPath = `${filePath}.tmp`;\n try {\n // 写入临时文件\n writeFileSync(tempPath, data, \"utf8\");\n // 原子性重命名\n renameSync(tempPath, filePath);\n } catch (error) {\n // 清理临时文件\n try {\n if (existsSync(tempPath)) {\n writeFileSync(tempPath, \"\", \"utf8\"); // 清空后删除\n }\n } catch {\n // 忽略清理错误\n }\n throw error;\n }\n }\n\n /**\n * 生成配置哈希\n * 用于快速检测配置变更\n */\n private generateConfigHash(config: MCPServiceConfig): string {\n try {\n return createHash(\"sha256\").update(JSON.stringify(config)).digest(\"hex\");\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 生成配置哈希失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return \"\";\n }\n }\n\n /**\n * 验证缓存数据结构\n * 使用类型谓词确保类型安全\n */\n private validateCacheStructure(cache: unknown): cache is MCPToolsCache {\n try {\n if (!cache || typeof cache !== \"object\") {\n return false;\n }\n\n const cacheObj = cache as Record<string, unknown>;\n const metadata = cacheObj.metadata as Record<string, unknown>;\n\n return (\n typeof cacheObj.version === \"string\" &&\n typeof cacheObj.mcpServers === \"object\" &&\n cacheObj.mcpServers !== null &&\n cacheObj.metadata !== null &&\n cacheObj.metadata !== undefined &&\n typeof metadata === \"object\" &&\n metadata !== null &&\n typeof metadata.lastGlobalUpdate === \"string\" &&\n typeof metadata.totalWrites === \"number\" &&\n typeof metadata.createdAt === \"string\"\n );\n } catch {\n return false;\n }\n }\n\n /**\n * 获取缓存统计信息\n */\n async getStats(): Promise<CacheStats | null> {\n try {\n const cache = await this.loadExistingCache();\n const stats: CacheStats = {\n totalWrites: cache.metadata.totalWrites,\n lastUpdate: cache.metadata.lastGlobalUpdate,\n serverCount: Object.keys(cache.mcpServers).length,\n cacheFileSize: existsSync(this.cachePath)\n ? readFileSync(this.cachePath, \"utf8\").length\n : 0,\n };\n return stats;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 获取缓存统计失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return null;\n }\n }\n\n /**\n * 获取缓存文件路径(用于测试和调试)\n */\n getFilePath(): string {\n return this.cachePath;\n }\n\n /**\n * 获取所有缓存中的工具\n * 返回所有服务中的所有工具列表\n */\n async getAllCachedTools(): Promise<Tool[]> {\n try {\n const cache = await this.loadExistingCache();\n const allTools: Tool[] = [];\n\n // 遍历所有服务,收集所有工具\n for (const [serverName, cacheEntry] of Object.entries(cache.mcpServers)) {\n for (const tool of cacheEntry.tools) {\n // 为每个工具添加服务名称信息\n allTools.push({\n ...tool,\n name: `${serverName}__${tool.name}`, // 格式: serviceName__toolName\n });\n }\n }\n\n this.logger.debug(\n `[CacheManager] 获取到所有缓存工具,共 ${allTools.length} 个`\n );\n return allTools;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 获取所有缓存工具失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return [];\n }\n }\n\n // ==================== CustomMCP 结果缓存管理方法 ====================\n\n /**\n * 写入 CustomMCP 工具执行结果缓存\n */\n async writeCustomMCPResult(\n toolName: string,\n arguments_: Record<string, unknown>,\n result: ToolCallResult,\n status: TaskStatus = \"completed\",\n taskId?: string,\n ttl = 300000\n ): Promise<void> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n // 创建缓存条目\n const cacheEntry: EnhancedToolResultCache = {\n result,\n timestamp: new Date().toISOString(),\n ttl,\n status,\n consumed: false,\n taskId,\n retryCount: 0,\n };\n\n // 确保customMCPResults存在\n if (!cache.customMCPResults) {\n cache.customMCPResults = {};\n }\n\n cache.customMCPResults[cacheKey] = cacheEntry;\n await this.saveExtendedCache(cache);\n\n this.logger.debug(\n `[CacheManager] 写入CustomMCP结果缓存: ${toolName}, 状态: ${status}`\n );\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 写入CustomMCP结果缓存失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * 读取 CustomMCP 工具执行结果缓存\n */\n async readCustomMCPResult(\n toolName: string,\n arguments_: Record<string, unknown>\n ): Promise<EnhancedToolResultCache | null> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return null;\n }\n\n const cacheEntry = cache.customMCPResults[cacheKey];\n\n // 检查是否过期\n const now = Date.now();\n const cachedTime = new Date(cacheEntry.timestamp).getTime();\n if (now - cachedTime > cacheEntry.ttl) {\n this.logger.debug(`[CacheManager] 缓存已过期: ${toolName}`);\n return null;\n }\n\n return cacheEntry;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 读取CustomMCP结果缓存失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return null;\n }\n }\n\n /**\n * 更新 CustomMCP 缓存状态\n */\n async updateCustomMCPStatus(\n toolName: string,\n arguments_: Record<string, unknown>,\n newStatus: TaskStatus,\n result?: ToolCallResult,\n error?: string\n ): Promise<boolean> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return false;\n }\n\n const cacheEntry = cache.customMCPResults[cacheKey];\n const oldStatus = cacheEntry.status;\n\n // 更新状态\n cacheEntry.status = newStatus;\n cacheEntry.timestamp = new Date().toISOString();\n\n // 更新结果或错误信息\n if (result) {\n cacheEntry.result = result;\n }\n\n if (error && newStatus === \"failed\") {\n cacheEntry.result = {\n content: [{ type: \"text\", text: `任务失败: ${error}` }],\n };\n cacheEntry.consumed = true; // 失败的任务自动标记为已消费\n }\n\n // 特殊状态处理\n if (newStatus === \"completed\") {\n cacheEntry.consumed = false; // 完成的任务初始状态为未消费\n }\n\n await this.saveExtendedCache(cache);\n\n this.logger.debug(\n `[CacheManager] 更新缓存状态: ${toolName} ${oldStatus} -> ${newStatus}`\n );\n return true;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 更新CustomMCP缓存状态失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return false;\n }\n }\n\n /**\n * 标记 CustomMCP 缓存为已消费\n */\n async markCustomMCPAsConsumed(\n toolName: string,\n arguments_: Record<string, unknown>\n ): Promise<boolean> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return false;\n }\n\n const cacheEntry = cache.customMCPResults[cacheKey];\n if (cacheEntry.consumed) {\n return true;\n }\n\n cacheEntry.consumed = true;\n cacheEntry.timestamp = new Date().toISOString();\n\n await this.saveExtendedCache(cache);\n\n this.logger.debug(`[CacheManager] 标记缓存为已消费: ${toolName}`);\n return true;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 标记CustomMCP缓存为已消费失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return false;\n }\n }\n\n /**\n * 删除 CustomMCP 缓存条目\n */\n async deleteCustomMCPResult(\n toolName: string,\n arguments_: Record<string, unknown>\n ): Promise<boolean> {\n try {\n const cache = await this.loadExtendedCache();\n const cacheKey = generateCacheKey(toolName, arguments_);\n\n if (!cache.customMCPResults || !cache.customMCPResults[cacheKey]) {\n return false;\n }\n\n delete cache.customMCPResults[cacheKey];\n await this.saveExtendedCache(cache);\n\n this.logger.debug(`[CacheManager] 删除缓存条目: ${toolName}`);\n return true;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 删除CustomMCP缓存条目失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return false;\n }\n }\n\n /**\n * 批量清理 CustomMCP 缓存\n */\n async cleanupCustomMCPResults(): Promise<{ cleaned: number; total: number }> {\n try {\n const cache = await this.loadExtendedCache();\n\n if (!cache.customMCPResults) {\n return { cleaned: 0, total: 0 };\n }\n\n const entries = Object.entries(cache.customMCPResults);\n let cleanedCount = 0;\n\n for (const [cacheKey, cacheEntry] of entries) {\n if (shouldCleanupCache(cacheEntry)) {\n delete cache.customMCPResults[cacheKey];\n cleanedCount++;\n }\n }\n\n if (cleanedCount > 0) {\n await this.saveExtendedCache(cache);\n this.logger.info(\n `[CacheManager] 清理CustomMCP缓存: ${cleanedCount}/${entries.length}`\n );\n }\n\n return { cleaned: cleanedCount, total: entries.length };\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 清理CustomMCP缓存失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return { cleaned: 0, total: 0 };\n }\n }\n\n /**\n * 获取 CustomMCP 缓存统计信息\n */\n async getCustomMCPStatistics(): Promise<CacheStatistics> {\n try {\n const cache = await this.loadExtendedCache();\n\n if (!cache.customMCPResults) {\n return {\n totalEntries: 0,\n pendingTasks: 0,\n completedTasks: 0,\n failedTasks: 0,\n consumedEntries: 0,\n cacheHitRate: 0,\n lastCleanupTime: new Date().toISOString(),\n memoryUsage: 0,\n };\n }\n\n const entries = Object.values(cache.customMCPResults);\n const totalEntries = entries.length;\n const pendingTasks = entries.filter((e) => e.status === \"pending\").length;\n const completedTasks = entries.filter(\n (e) => e.status === \"completed\"\n ).length;\n const failedTasks = entries.filter((e) => e.status === \"failed\").length;\n const consumedEntries = entries.filter((e) => e.consumed).length;\n\n // 计算缓存命中率\n const cacheHitRate =\n completedTasks > 0 ? (consumedEntries / completedTasks) * 100 : 0;\n\n // 估算内存使用\n const memoryUsage = JSON.stringify(cache.customMCPResults).length;\n\n return {\n totalEntries,\n pendingTasks,\n completedTasks,\n failedTasks,\n consumedEntries,\n cacheHitRate,\n lastCleanupTime: new Date().toISOString(),\n memoryUsage,\n };\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 获取CustomMCP缓存统计失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return {\n totalEntries: 0,\n pendingTasks: 0,\n completedTasks: 0,\n failedTasks: 0,\n consumedEntries: 0,\n cacheHitRate: 0,\n lastCleanupTime: new Date().toISOString(),\n memoryUsage: 0,\n };\n }\n }\n\n /**\n * 加载扩展缓存(包含 CustomMCP 结果)\n */\n async loadExtendedCache(): Promise<ExtendedMCPToolsCache> {\n try {\n const cache = await this.loadExistingCache();\n return cache as ExtendedMCPToolsCache;\n } catch (error) {\n this.logger.warn(\n `[CacheManager] 加载扩展缓存失败: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n return {\n version: this.CACHE_VERSION,\n mcpServers: {},\n metadata: {\n lastGlobalUpdate: this.formatTimestamp(),\n totalWrites: 0,\n createdAt: this.formatTimestamp(),\n },\n customMCPResults: {},\n };\n }\n }\n\n /**\n * 保存扩展缓存(包含 CustomMCP 结果)\n */\n async saveExtendedCache(cache: ExtendedMCPToolsCache): Promise<void> {\n await this.saveCache(cache);\n }\n\n /**\n * 启动清理定时器\n */\n private startCleanupTimer(): void {\n this.cleanupInterval = setInterval(() => {\n this.cleanupCustomMCPResults().catch((error) => {\n this.logger.warn(`[CacheManager] 自动清理失败: ${error}`);\n });\n }, this.CLEANUP_INTERVAL);\n\n this.logger.debug(\n `[CacheManager] 启动清理定时器,间隔: ${this.CLEANUP_INTERVAL}ms`\n );\n }\n\n /**\n * 停止清理定时器\n */\n public stopCleanupTimer(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n this.logger.debug(\"[CacheManager] 停止清理定时器\");\n }\n }\n\n /**\n * 清理资源\n */\n public cleanup(): void {\n this.stopCleanupTimer();\n this.logger.debug(\"[CacheManager] 清理资源完成\");\n }\n}\n","/**\n * Hono Context 类型扩展\n * 为 Hono Context 添加项目特定的变量类型定义\n */\n\nimport type { EndpointManager } from \"@/lib/endpoint/index.js\";\nimport type { MCPServiceManager } from \"@/lib/mcp\";\nimport type { Logger } from \"@root/Logger.js\";\nimport type { Context } from \"hono\";\nimport { Hono } from \"hono\";\nimport type {\n HandlerDependencies,\n MCPEndpointApiHandler,\n} from \"../routes/index.js\";\n\n/**\n * WebServer 实例类型定义\n * 避免与 WebServer 实现类的循环引用\n * 使用类型断言的方式来定义接口\n */\nexport interface IWebServer {\n /**\n * 获取小智连接管理器实例\n * 在 endpointManagerMiddleware 中使用\n * WebServer 启动后始终返回有效的连接管理器实例\n * 如果在启动前调用,会抛出错误\n */\n getEndpointManager(): EndpointManager;\n\n /**\n * 获取 MCP 服务管理器实例\n * 在 mcpServiceManagerMiddleware 中使用\n * WebServer 启动后始终返回有效的服务管理器实例\n * 如果在启动前调用,会抛出错误\n */\n getMCPServiceManager(): MCPServiceManager;\n}\n\n/**\n * 扩展 Hono Context 的 Variables 类型\n * 定义了项目中所有通过中间件注入的变量\n */\nexport type AppContextVariables = {\n /**\n * 日志记录器实例\n * 由 loggerMiddleware 注入\n */\n logger?: Logger;\n\n /**\n * MCP 服务管理器实例\n * 由 mcpServiceManagerMiddleware 注入\n */\n mcpServiceManager?: MCPServiceManager;\n\n /**\n * 路由处理器依赖\n * 由 RouteManager 注入\n */\n dependencies?: HandlerDependencies;\n\n /**\n * WebServer 实例引用\n * 用于获取运行时依赖\n */\n webServer?: IWebServer;\n\n /**\n * 小智连接管理器实例\n * 由 endpointManagerMiddleware 注入\n * WebServer 启动后始终可用的连接管理器实例\n * 可能为 null(初始化失败时)\n */\n endpointManager: EndpointManager | null;\n\n /**\n * 端点处理器实例\n * 由 endpointsMiddleware 注入\n */\n endpointHandler?: MCPEndpointApiHandler | null;\n};\n\n/**\n * 扩展的 Hono Context 类型\n * 包含项目中所有自定义变量\n */\nexport type AppContext = {\n Variables: AppContextVariables;\n};\n\n/**\n * 创建类型化的 Hono 实例\n * 使用这个类型来创建新的 Hono 应用,确保 Context 类型安全\n */\nexport const createApp = (): Hono<AppContext> => {\n return new Hono<AppContext>();\n};\n\n/**\n * 从 Context 中获取日志记录器的类型安全方法\n */\nexport const getLogger = (c: Context<AppContext>): Logger | undefined => {\n return c.get(\"logger\");\n};\n\n/**\n * 从 Context 中获取 MCPServiceManager 的类型安全方法\n */\nexport const getMCPServiceManager = (\n c: Context<AppContext>\n): MCPServiceManager | undefined => {\n return c.get(\"mcpServiceManager\");\n};\n\n/**\n * 要求必须存在 MCPServiceManager 的类型安全方法\n */\nexport const requireMCPServiceManager = (\n c: Context<AppContext>\n): MCPServiceManager => {\n const serviceManager = c.get(\"mcpServiceManager\");\n\n if (!serviceManager) {\n throw new Error(\n \"MCPServiceManager 未初始化,请检查 mcpServiceManagerMiddleware 是否正确配置\"\n );\n }\n\n return serviceManager;\n};\n","/**\n * MCP 服务管理器全局单例\n * 用于在 API Handler 中获取服务状态\n */\n\nimport { MCPServiceManager } from \"@/lib/mcp\";\nimport { configManager } from \"@xiaozhi-client/config\";\n\n/**\n * MCP 服务管理器全局单例类\n */\nclass MCPServiceManagerSingleton {\n private static instance: MCPServiceManager | null = null;\n\n /**\n * 获取 MCP 服务管理器单例实例\n */\n static getInstance(): MCPServiceManager {\n if (!MCPServiceManagerSingleton.instance) {\n const configs = configManager.getMcpServers();\n MCPServiceManagerSingleton.instance = new MCPServiceManager(configs);\n }\n return MCPServiceManagerSingleton.instance;\n }\n\n /**\n * 重置单例实例(主要用于测试)\n */\n static reset(): void {\n MCPServiceManagerSingleton.instance = null;\n }\n}\n\n/**\n * 导出单例实例供外部使用\n */\nexport const mcpServiceManager = MCPServiceManagerSingleton.getInstance();\n\nexport default MCPServiceManagerSingleton;\n"],"mappings":"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","names":["EventEmitter","EventEmitter","fs","path","chalk","pino","z","LogLevelSchema","z","formatDateTime","date","year","month","day","hours","minutes","seconds","__name","Logger","level","normalizedLevel","result","streams","consoleStream","pino","_label","number","err","levelMap","chalk","chunk","logObj","message","content","timestamp","levelInfo","text","coloredLevel","argsStr","arg","projectDir","enable","messageOrObj","args","errorArgs","enhancedObj","obj","enhanced","key","value","logDir","logName","i","oldFile","newFile","firstRotatedFile","maxSize","maxFiles","globalLogger","globalLogLevel","getLogger","globalLogger","Logger","globalLogLevel","__name","logger","getLogger","EventBus","EventEmitter","__name","logger","error","eventName","listenerCount","data","listener","onceListener","stats","stat","sum","count","eventBusInstance","getEventBus","isModelScopeURL","configManager","coze_exports","__export","CozeApiService","config_default","createCozeClient","config_default","__reExport","coze_exports","api_star","createCozeClient","token","language","env","config_default","__name","NodeCache","CozeApiService","__name","token","createCozeClient","NodeCache","cacheKey","cached","workspaces","params","workspace_id","page_num","page_size","result","workflowId","parameters","pattern","keysToDelete","key","stats","keys","totalRequests","hitRate","MCPTransportType","isValidToolJSONSchema","obj","__name","ensureToolJSONSchema","schema","ToolCallError","code","message","data","__name","createHash","generateCacheKey","toolName","arguments_","argsHash","createHash","__name","isCacheExpired","timestamp","ttl","cachedTime","__name","shouldCleanupCache","cache","now","DEFAULT_CONFIG","TimeoutError","_TimeoutError","__name","message","createTimeoutResponse","taskId","toolName","getToolSpecificTimeoutMessage","getDefaultTimeoutMessage","toolMessages","configManager","isProxyHandler","handler","__name","CustomMCPHandler","DEFAULT_CONFIG","cacheManager","mcpServiceManager","logger","MCPCacheManager","token","configManager","CozeApiService","getEventBus","data","error","tools","customTools","tool","platformInfo","ensureToolJSONSchema","toolName","arguments_","options","completedResult","timeout","result","TimeoutError","taskId","createTimeoutResponse","_","reject","cacheKey","cache","cached","isCacheExpired","workflowData","responseData","config","cozeApiService","workflowResult","shouldCleanupCache","generateCacheKey","cacheData","fs","path","tmpdir","PathUtils","__name","tmpdir","pino","ToolCallLogger","__name","config","configDir","baseDir","PathUtils","logFilePath","streams","chunk","logObj","message","pino","_label","number","toolName","success","duration","lines","line","recordsToRemove","linesToKeep","newContent","record","MCPMessageHandler","__name","serviceManager","logger","message","isNotification","error","params","id","supportedVersions","clientVersion","responseVersion","tool","validatedParams","validateToolCallParams","result","resources","prompts","errorCode","WebSocket","WebSocketServer","MCPServiceManager","EventEmitter","__name","getEventBus","configs","cachePath","MCPCacheManager","CustomMCPHandler","toolCallLogConfig","configManager","configDir","ToolCallLogger","MCPMessageHandler","data","configEntries","startPromises","serviceName","error","results","successCount","failureCount","failedServices","result","config","service","MCPService","tools","tool","toolKey","allTools","serviceTools","customTools","customTool","toolName","toolInfo","arguments_","options","startTime","logServerName","originalToolName","isSuccess","currentTime","action","activeLocks","name","connectedServices","isModelScopeURL","originalConfig","enhancedConfig","modelScopeApiKey","serviceUrl","nameOrConfig","finalConfig","currentServerConfigs","currentToolsConfig","newToolsConfig","currentToolConfig","currentToolNames","t","removedTools","addedTools","updatedTools","current","updated","currentConfig","newConfig","currentKeys","newKeys","key","currentTool","newTool","initialDelay","delay","existingTimer","timer","currentDelay","nextDelay","acc","char","adapter","successfulAdapters","failedAdapters","errorMessage","connections","adapterName","conn","serviceStatus","customMCPToolCount","customToolNames","totalTools","availableTools","status","message","response","Client","SSEClientTransport","StdioClientTransport","StreamableHTTPClientTransport","EventSource","EventSource","createTransport","config","createStdioTransport","createSSETransport","createStreamableHTTPTransport","__name","StdioClientTransport","url","options","createSSEOptions","SSEClientTransport","createStreamableHTTPTransport","config","url","options","createStreamableHTTPOptions","StreamableHTTPClientTransport","__name","createSSEOptions","createStreamableHTTPOptions","config","options","__name","validateConfig","MCPTransportType","getSupportedTypes","TransportFactory","createTransport","TypeFieldNormalizer","normalizeTypeField","config","normalizedConfig","originalType","normalizedType","convertToKebabCase","__name","str","inferTransportTypeFromUrl","url","options","pathname","__name","inferTransportTypeFromConfig","config","TypeFieldNormalizer","inferredType","validateToolCallParams","params","opts","ToolCallError","paramsObj","argsObj","error","MCPService","__name","getEventBus","config","inferTransportTypeFromConfig","TransportFactory","resolve","reject","error","Client","tools","tool","name","arguments_","createHash","existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","dirname","resolve","Hono","dayjs","MCPCacheManager","__name","customCachePath","logger","dayjs","configDir","resolve","existsSync","cacheDir","dirname","mkdirSync","initialCache","error","now","serverName","tools","config","cache","configHash","cacheEntry","tool","cacheData","readFileSync","cacheContent","filePath","data","tempPath","writeFileSync","renameSync","createHash","cacheObj","metadata","allTools","toolName","arguments_","result","status","taskId","ttl","cacheKey","generateCacheKey","cachedTime","newStatus","oldStatus","entries","cleanedCount","shouldCleanupCache","totalEntries","pendingTasks","e","completedTasks","failedTasks","consumedEntries","cacheHitRate","memoryUsage","configManager","MCPServiceManagerSingleton","_MCPServiceManagerSingleton","__name","configs","configManager","MCPServiceManager","mcpServiceManager","MCPServiceManagerSingleton_default"]}
|