mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-07 03:20:05 +08:00
feat: Optimize MCP configuration logic
This commit is contained in:
@@ -1,236 +1,217 @@
|
||||
"use server";
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import {
|
||||
createClient,
|
||||
executeRequest,
|
||||
listPrimitives,
|
||||
Primitive,
|
||||
listTools,
|
||||
removeClient,
|
||||
} from "./client";
|
||||
import { MCPClientLogger } from "./logger";
|
||||
import { McpRequestMessage, McpConfig, ServerConfig } from "./types";
|
||||
import {
|
||||
DEFAULT_MCP_CONFIG,
|
||||
McpClientData,
|
||||
McpConfigData,
|
||||
McpRequestMessage,
|
||||
ServerConfig,
|
||||
} from "./types";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
const logger = new MCPClientLogger("MCP Actions");
|
||||
|
||||
// Use Map to store all clients
|
||||
const clientsMap = new Map<
|
||||
string,
|
||||
{ client: Client | null; primitives: Primitive[]; errorMsg: string | null }
|
||||
>();
|
||||
|
||||
// Whether initialized
|
||||
let initialized = false;
|
||||
|
||||
// Store failed clients
|
||||
let errorClients: string[] = [];
|
||||
|
||||
const CONFIG_PATH = path.join(process.cwd(), "app/mcp/mcp_config.json");
|
||||
|
||||
// 获取 MCP 配置
|
||||
export async function getMcpConfig(): Promise<McpConfig> {
|
||||
try {
|
||||
const configStr = await fs.readFile(CONFIG_PATH, "utf-8");
|
||||
return JSON.parse(configStr);
|
||||
} catch (error) {
|
||||
console.error("Failed to read MCP config:", error);
|
||||
return { mcpServers: {} };
|
||||
}
|
||||
const clientsMap = new Map<string, McpClientData>();
|
||||
|
||||
// 获取客户端状态
|
||||
export async function getClientStatus(clientId: string) {
|
||||
const status = clientsMap.get(clientId);
|
||||
if (!status) return { status: "undefined" as const, errorMsg: null };
|
||||
|
||||
return {
|
||||
status: status.errorMsg ? ("error" as const) : ("active" as const),
|
||||
errorMsg: status.errorMsg,
|
||||
};
|
||||
}
|
||||
|
||||
// 更新 MCP 配置
|
||||
export async function updateMcpConfig(config: McpConfig): Promise<void> {
|
||||
try {
|
||||
await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
|
||||
} catch (error) {
|
||||
console.error("Failed to write MCP config:", error);
|
||||
throw error;
|
||||
}
|
||||
// 获取客户端工具
|
||||
export async function getClientTools(clientId: string) {
|
||||
return clientsMap.get(clientId)?.tools ?? null;
|
||||
}
|
||||
|
||||
// 重新初始化所有客户端
|
||||
export async function reinitializeMcpClients() {
|
||||
logger.info("Reinitializing MCP clients...");
|
||||
// 遍历所有客户端,关闭
|
||||
try {
|
||||
for (const [clientId, clientData] of clientsMap.entries()) {
|
||||
clientData.client?.close();
|
||||
// 获取可用客户端数量
|
||||
export async function getAvailableClientsCount() {
|
||||
let count = 0;
|
||||
clientsMap.forEach((map) => {
|
||||
if (!map.errorMsg) {
|
||||
count += map?.tools?.tools?.length ?? 0;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to close clients: ${error}`);
|
||||
}
|
||||
// 清空状态
|
||||
clientsMap.clear();
|
||||
errorClients = [];
|
||||
initialized = false;
|
||||
// 重新初始化
|
||||
return initializeMcpClients();
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
// Initialize all configured clients
|
||||
export async function initializeMcpClients() {
|
||||
// If already initialized, return
|
||||
if (initialized) {
|
||||
return { errorClients };
|
||||
// 获取所有客户端工具
|
||||
export async function getAllTools() {
|
||||
const result = [];
|
||||
for (const [clientId, status] of clientsMap.entries()) {
|
||||
result.push({
|
||||
clientId,
|
||||
tools: status.tools,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info("Starting to initialize MCP clients...");
|
||||
errorClients = [];
|
||||
|
||||
const config = await getMcpConfig();
|
||||
// Initialize all clients, key is clientId, value is client config
|
||||
for (const [clientId, serverConfig] of Object.entries(config.mcpServers)) {
|
||||
try {
|
||||
logger.info(`Initializing MCP client: ${clientId}`);
|
||||
const client = await createClient(serverConfig as ServerConfig, clientId);
|
||||
const primitives = await listPrimitives(client);
|
||||
clientsMap.set(clientId, { client, primitives, errorMsg: null });
|
||||
logger.success(
|
||||
`Client [${clientId}] initialized, ${primitives.length} primitives supported`,
|
||||
);
|
||||
} catch (error) {
|
||||
errorClients.push(clientId);
|
||||
clientsMap.set(clientId, {
|
||||
client: null,
|
||||
primitives: [],
|
||||
errorMsg: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
logger.error(`Failed to initialize client ${clientId}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
|
||||
if (errorClients.length > 0) {
|
||||
logger.warn(`Failed to initialize clients: ${errorClients.join(", ")}`);
|
||||
} else {
|
||||
logger.success("All MCP clients initialized");
|
||||
}
|
||||
|
||||
const availableClients = await getAvailableClients();
|
||||
logger.info(`Available clients: ${availableClients.join(",")}`);
|
||||
|
||||
return { errorClients };
|
||||
return result;
|
||||
}
|
||||
|
||||
// Execute MCP request
|
||||
export async function executeMcpAction(
|
||||
// 初始化单个客户端
|
||||
async function initializeSingleClient(
|
||||
clientId: string,
|
||||
request: McpRequestMessage,
|
||||
serverConfig: ServerConfig,
|
||||
) {
|
||||
logger.info(`Initializing client [${clientId}]...`);
|
||||
try {
|
||||
// Find the corresponding client
|
||||
const client = clientsMap.get(clientId)?.client;
|
||||
if (!client) {
|
||||
logger.error(`Client ${clientId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Executing MCP request for ${clientId}`);
|
||||
|
||||
// Execute request and return result
|
||||
return await executeRequest(client, request);
|
||||
const client = await createClient(clientId, serverConfig);
|
||||
const tools = await listTools(client);
|
||||
clientsMap.set(clientId, { client, tools, errorMsg: null });
|
||||
logger.success(`Client [${clientId}] initialized successfully`);
|
||||
} catch (error) {
|
||||
logger.error(`MCP execution error: ${error}`);
|
||||
clientsMap.set(clientId, {
|
||||
client: null,
|
||||
tools: null,
|
||||
errorMsg: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
logger.error(`Failed to initialize client [${clientId}]: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化系统
|
||||
export async function initializeMcpSystem() {
|
||||
logger.info("MCP Actions starting...");
|
||||
try {
|
||||
const config = await getMcpConfigFromFile();
|
||||
// 初始化所有客户端
|
||||
for (const [clientId, serverConfig] of Object.entries(config.mcpServers)) {
|
||||
await initializeSingleClient(clientId, serverConfig);
|
||||
}
|
||||
return config;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to initialize MCP system: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Get all available client IDs
|
||||
export async function getAvailableClients() {
|
||||
return Array.from(clientsMap.entries())
|
||||
.filter(([_, data]) => data.errorMsg === null)
|
||||
.map(([clientId]) => clientId);
|
||||
}
|
||||
|
||||
// Get all primitives from all clients
|
||||
export async function getAllPrimitives(): Promise<
|
||||
{
|
||||
clientId: string;
|
||||
primitives: Primitive[];
|
||||
}[]
|
||||
> {
|
||||
return Array.from(clientsMap.entries()).map(([clientId, { primitives }]) => ({
|
||||
clientId,
|
||||
primitives,
|
||||
}));
|
||||
}
|
||||
|
||||
// 获取客户端的 Primitives
|
||||
export async function getClientPrimitives(clientId: string) {
|
||||
// 添加服务器
|
||||
export async function addMcpServer(clientId: string, config: ServerConfig) {
|
||||
try {
|
||||
const clientData = clientsMap.get(clientId);
|
||||
if (!clientData) {
|
||||
console.warn(`Client ${clientId} not found in map`);
|
||||
return null;
|
||||
}
|
||||
if (clientData.errorMsg) {
|
||||
console.warn(`Client ${clientId} has error: ${clientData.errorMsg}`);
|
||||
return null;
|
||||
}
|
||||
return clientData.primitives;
|
||||
const currentConfig = await getMcpConfigFromFile();
|
||||
const newConfig = {
|
||||
...currentConfig,
|
||||
mcpServers: {
|
||||
...currentConfig.mcpServers,
|
||||
[clientId]: config,
|
||||
},
|
||||
};
|
||||
await updateMcpConfig(newConfig);
|
||||
// 只初始化新添加的服务器
|
||||
await initializeSingleClient(clientId, config);
|
||||
return newConfig;
|
||||
} catch (error) {
|
||||
console.error(`Failed to get primitives for client ${clientId}:`, error);
|
||||
return null;
|
||||
logger.error(`Failed to add server [${clientId}]: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 移除服务器
|
||||
export async function removeMcpServer(clientId: string) {
|
||||
try {
|
||||
const currentConfig = await getMcpConfigFromFile();
|
||||
const { [clientId]: _, ...rest } = currentConfig.mcpServers;
|
||||
const newConfig = {
|
||||
...currentConfig,
|
||||
mcpServers: rest,
|
||||
};
|
||||
await updateMcpConfig(newConfig);
|
||||
|
||||
// 关闭并移除客户端
|
||||
const client = clientsMap.get(clientId);
|
||||
if (client?.client) {
|
||||
await removeClient(client.client);
|
||||
}
|
||||
clientsMap.delete(clientId);
|
||||
|
||||
return newConfig;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to remove server [${clientId}]: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 重启所有客户端
|
||||
export async function restartAllClients() {
|
||||
logger.info("Restarting all MCP clients...");
|
||||
|
||||
// 清空状态
|
||||
clientsMap.clear();
|
||||
errorClients = [];
|
||||
initialized = false;
|
||||
|
||||
// 重新初始化
|
||||
await initializeMcpClients();
|
||||
|
||||
return {
|
||||
success: errorClients.length === 0,
|
||||
errorClients,
|
||||
};
|
||||
}
|
||||
|
||||
// 获取所有客户端状态
|
||||
export async function getAllClientStatus(): Promise<
|
||||
Record<string, string | null>
|
||||
> {
|
||||
const status: Record<string, string | null> = {};
|
||||
for (const [clientId, data] of clientsMap.entries()) {
|
||||
status[clientId] = data.errorMsg;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// 检查客户端状态
|
||||
export async function getClientErrors(): Promise<
|
||||
Record<string, string | null>
|
||||
> {
|
||||
const errors: Record<string, string | null> = {};
|
||||
for (const [clientId, data] of clientsMap.entries()) {
|
||||
errors[clientId] = data.errorMsg;
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
// 获取客户端状态,不重新初始化
|
||||
export async function refreshClientStatus() {
|
||||
logger.info("Refreshing client status...");
|
||||
|
||||
// 如果还没初始化过,则初始化
|
||||
if (!initialized) {
|
||||
return initializeMcpClients();
|
||||
}
|
||||
|
||||
// 否则只更新错误状态
|
||||
errorClients = [];
|
||||
for (const [clientId, clientData] of clientsMap.entries()) {
|
||||
if (clientData.errorMsg !== null) {
|
||||
errorClients.push(clientId);
|
||||
logger.info("Restarting all clients...");
|
||||
try {
|
||||
// 关闭所有客户端
|
||||
for (const client of clientsMap.values()) {
|
||||
if (client.client) {
|
||||
await removeClient(client.client);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 清空状态
|
||||
clientsMap.clear();
|
||||
|
||||
return { errorClients };
|
||||
// 重新初始化
|
||||
const config = await getMcpConfigFromFile();
|
||||
for (const [clientId, serverConfig] of Object.entries(config.mcpServers)) {
|
||||
await initializeSingleClient(clientId, serverConfig);
|
||||
}
|
||||
return config;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to restart clients: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 执行 MCP 请求
|
||||
export async function executeMcpAction(
|
||||
clientId: string,
|
||||
request: McpRequestMessage,
|
||||
) {
|
||||
try {
|
||||
const client = clientsMap.get(clientId);
|
||||
if (!client?.client) {
|
||||
throw new Error(`Client ${clientId} not found`);
|
||||
}
|
||||
logger.info(`Executing request for [${clientId}]`);
|
||||
return await executeRequest(client.client, request);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to execute request for [${clientId}]: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 MCP 配置文件
|
||||
export async function getMcpConfigFromFile(): Promise<McpConfigData> {
|
||||
try {
|
||||
const configStr = await fs.readFile(CONFIG_PATH, "utf-8");
|
||||
return JSON.parse(configStr);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to load MCP config, using default config: ${error}`);
|
||||
return DEFAULT_MCP_CONFIG;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 MCP 配置文件
|
||||
async function updateMcpConfig(config: McpConfigData): Promise<void> {
|
||||
try {
|
||||
await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新初始化单个客户端
|
||||
export async function reinitializeClient(clientId: string) {
|
||||
const config = await getMcpConfigFromFile();
|
||||
const serverConfig = config.mcpServers[clientId];
|
||||
if (!serverConfig) {
|
||||
throw new Error(`Server config not found for client ${clientId}`);
|
||||
}
|
||||
await initializeSingleClient(clientId, serverConfig);
|
||||
}
|
||||
|
Reference in New Issue
Block a user