UNPKG

@voyager-0x/agent-mcp

Version:

Voyager MCP Agent - A powerful Model Context Protocol agent

288 lines (253 loc) 7.27 kB
import { v4 as uuidv4 } from "uuid"; import { log } from "./log"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { createWebMcpModule, createMcpServerModule, McpServerConfig, } from "./mcp"; export type McpModuleSource = | "builtin" | "user-dsl" | "external-http" | "external-stdio"; export type McpModule = { title: string; description: string; uuid: string; client: Client; source: McpModuleSource; config?: McpServerConfig; // 只有外部服务器模块才有配置 }; export class McpModulesManager { private modules: McpModule[] = []; public createMcpDslModule( { title, description }: { title: string; description: string }, callback: (mcpServer: McpServer) => void ): McpModule { const client = createWebMcpModule( { title, description, }, callback ); this.modules.push({ title, description, client, uuid: uuidv4(), source: "user-dsl", }); return this.modules[this.modules.length - 1]; } public createMcpBuiltinModule( { title, description }: { title: string; description: string }, callback: (mcpServer: McpServer) => void ): McpModule { const client = createWebMcpModule( { title, description, }, callback ); this.modules.push({ title, description, client, uuid: uuidv4(), source: "builtin", }); return this.modules[this.modules.length - 1]; } public async createMcpServerModule( config: McpServerConfig, uuid?: string ): Promise<McpModule> { const client = await createMcpServerModule(config); const moduleUuid = uuid || uuidv4(); // 根据 serverUrl 判断连接类型 const source: McpModuleSource = config.serverUrl.startsWith("http://") || config.serverUrl.startsWith("https://") || config.serverUrl.startsWith("ws://") || config.serverUrl.startsWith("wss://") ? "external-http" : "external-stdio"; this.modules.push({ title: config.title, description: config.description, client: client, uuid: moduleUuid, source: source, config: config, }); return this.modules[this.modules.length - 1]; } public async removeMcpServerModule({ uuid, }: { uuid: string; }): Promise<boolean> { try { const moduleIndex = this.modules.findIndex( (module) => module.uuid === uuid ); if (moduleIndex === -1) { log.warn(`MCP Server Module with uuid ${uuid} not found`); return false; } const module = this.modules[moduleIndex]; // 尝试断开连接 try { if (module.client && typeof module.client.close === "function") { await module.client.close(); } } catch (error) { log.warn( `Failed to close client for MCP server module ${uuid}:`, error ); } // 从数组中移除模块 this.modules.splice(moduleIndex, 1); log.info( `Successfully removed MCP server module: ${module.title} (${uuid})` ); return true; } catch (error) { log.error(`Failed to remove MCP server module ${uuid}:`, error); return false; } } public async updateMcpServerModule({ uuid, config, }: { uuid: string; config: McpServerConfig; }): Promise<McpModule | null> { try { const moduleIndex = this.modules.findIndex( (module) => module.uuid === uuid ); if (moduleIndex === -1) { log.warn(`MCP Server Module with uuid ${uuid} not found`); return null; } const oldModule = this.modules[moduleIndex]; // 关闭旧连接 try { if (oldModule.client && typeof oldModule.client.close === "function") { await oldModule.client.close(); } } catch (error) { log.warn( `Failed to close old client for MCP server module ${uuid}:`, error ); } // 创建新的客户端连接 const newClient = await createMcpServerModule(config); // 根据 serverUrl 判断连接类型 const source: McpModuleSource = config.serverUrl.startsWith("http://") || config.serverUrl.startsWith("https://") || config.serverUrl.startsWith("ws://") || config.serverUrl.startsWith("wss://") ? "external-http" : "external-stdio"; // 更新模块 this.modules[moduleIndex] = { title: config.title, description: config.description, client: newClient, uuid: uuid, // 保持原有的UUID source: source, config: config, }; log.info( `Successfully updated MCP server module: ${config.title} (${uuid})` ); return this.modules[moduleIndex]; } catch (error) { log.error(`Failed to update MCP server module ${uuid}:`, error); return null; } } public listMcpModules(): McpModule[] { return this.modules; } public getModuleUuidsBySource(source: McpModuleSource): string[] { return this.modules .filter((module) => module.source === source) .map((module) => module.uuid); } public getMcpModule({ uuid }: { uuid?: string }): McpModule { const mod = this.modules.find((mcpModule) => mcpModule.uuid === uuid); if (!mod) { throw new Error(`McpModule with uuid ${uuid} not found`); } return mod; } async getTools({ uuids }: { uuids: string[] }) { const _tools: any[] = []; for (const uuid of uuids) { const { client } = this.getMcpModule({ uuid, }); try { const { tools } = await client.listTools(); tools.forEach((tool) => { _tools.push({ moduleId: uuid, ...tool, }); }); } catch (error: any) { // 如果是方法未找到错误(-32601),继续处理其他模块 if (error.code === -32601) { log.warn(`DSL Module ${uuid} does not support listTools method`); } else { // 其他错误重新抛出 throw error; } } } return _tools; } async getResources({ uuids }: { uuids: string[] }) { const _resources: any[] = []; // 处理 DSL 模块 for (const uuid of uuids) { if (uuid) { const { client } = this.getMcpModule({ uuid: uuid, }); try { const { resources } = await client.listResources(); resources.forEach((resource) => { _resources.push({ moduleId: uuid, ...resource, }); }); } catch (error: any) { // 如果是方法未找到错误(-32601),继续处理其他模块 if (error.code === -32601) { log.warn( `DSL Module ${uuid} does not support listResources method` ); } else { // 其他错误重新抛出 throw error; } } } } return _resources; } } export const mcpModulesManager = new McpModulesManager();