@voyager-0x/agent-mcp
Version:
Voyager MCP Agent - A powerful Model Context Protocol agent
288 lines (253 loc) • 7.27 kB
text/typescript
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();