mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-05-19 20:20:16 +09:00
feat: support stop/start MCP servers
This commit is contained in:
parent
e440ff56c8
commit
07c63497dc
@ -98,6 +98,10 @@
|
|||||||
background-color: #ef4444;
|
background-color: #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.stopped {
|
||||||
|
background-color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -151,21 +155,11 @@
|
|||||||
|
|
||||||
.mcp-market-actions {
|
.mcp-market-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
:global(.icon-button) {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
filter: brightness(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,30 +207,6 @@
|
|||||||
color: var(--gray-300);
|
color: var(--gray-300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.icon-button) {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 1px solid var(--gray-200);
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--gray-100);
|
|
||||||
border-color: var(--gray-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.icon-button.add-path-button) {
|
:global(.icon-button.add-path-button) {
|
||||||
|
@ -17,16 +17,20 @@ import {
|
|||||||
getClientStatus,
|
getClientStatus,
|
||||||
getClientTools,
|
getClientTools,
|
||||||
getMcpConfigFromFile,
|
getMcpConfigFromFile,
|
||||||
removeMcpServer,
|
|
||||||
restartAllClients,
|
restartAllClients,
|
||||||
|
pauseMcpServer,
|
||||||
|
resumeMcpServer,
|
||||||
} from "../mcp/actions";
|
} from "../mcp/actions";
|
||||||
import {
|
import {
|
||||||
ListToolsResponse,
|
ListToolsResponse,
|
||||||
McpConfigData,
|
McpConfigData,
|
||||||
PresetServer,
|
PresetServer,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
|
ServerStatusResponse,
|
||||||
} from "../mcp/types";
|
} from "../mcp/types";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import PlayIcon from "../icons/play.svg";
|
||||||
|
import StopIcon from "../icons/pause.svg";
|
||||||
|
|
||||||
const presetServers = presetServersJson as PresetServer[];
|
const presetServers = presetServersJson as PresetServer[];
|
||||||
|
|
||||||
@ -47,13 +51,7 @@ export function McpMarketPage() {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [config, setConfig] = useState<McpConfigData>();
|
const [config, setConfig] = useState<McpConfigData>();
|
||||||
const [clientStatuses, setClientStatuses] = useState<
|
const [clientStatuses, setClientStatuses] = useState<
|
||||||
Record<
|
Record<string, ServerStatusResponse>
|
||||||
string,
|
|
||||||
{
|
|
||||||
status: "active" | "error" | "undefined";
|
|
||||||
errorMsg: string | null;
|
|
||||||
}
|
|
||||||
>
|
|
||||||
>({});
|
>({});
|
||||||
|
|
||||||
// 检查服务器是否已添加
|
// 检查服务器是否已添加
|
||||||
@ -253,18 +251,74 @@ export function McpMarketPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 移除服务器
|
// 移除服务器
|
||||||
const removeServer = async (id: string) => {
|
// const removeServer = async (id: string) => {
|
||||||
|
// try {
|
||||||
|
// setIsLoading(true);
|
||||||
|
// const newConfig = await removeMcpServer(id);
|
||||||
|
// setConfig(newConfig);
|
||||||
|
|
||||||
|
// // 移除状态
|
||||||
|
// setClientStatuses((prev) => {
|
||||||
|
// const newStatuses = { ...prev };
|
||||||
|
// delete newStatuses[id];
|
||||||
|
// return newStatuses;
|
||||||
|
// });
|
||||||
|
// } finally {
|
||||||
|
// setIsLoading(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 暂停服务器
|
||||||
|
const pauseServer = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const newConfig = await removeMcpServer(id);
|
showToast("Stopping server...");
|
||||||
|
const newConfig = await pauseMcpServer(id);
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
|
|
||||||
// 移除状态
|
// 更新状态为暂停
|
||||||
setClientStatuses((prev) => {
|
setClientStatuses((prev) => ({
|
||||||
const newStatuses = { ...prev };
|
...prev,
|
||||||
delete newStatuses[id];
|
[id]: { status: "paused", errorMsg: null },
|
||||||
return newStatuses;
|
}));
|
||||||
});
|
showToast("Server stopped successfully");
|
||||||
|
} catch (error) {
|
||||||
|
showToast("Failed to stop server");
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 恢复服务器
|
||||||
|
const resumeServer = async (id: string) => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
showToast("Starting server...");
|
||||||
|
|
||||||
|
// 尝试启动服务器
|
||||||
|
const success = await resumeMcpServer(id);
|
||||||
|
|
||||||
|
// 获取最新状态(这个状态是从 clientsMap 中获取的,反映真实状态)
|
||||||
|
const status = await getClientStatus(id);
|
||||||
|
setClientStatuses((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[id]: status,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 根据启动结果显示消息
|
||||||
|
if (success) {
|
||||||
|
showToast("Server started successfully");
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to start server");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to start server, please check logs",
|
||||||
|
);
|
||||||
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -332,7 +386,12 @@ export function McpMarketPage() {
|
|||||||
} else if (prop.type === "string") {
|
} else if (prop.type === "string") {
|
||||||
const currentValue = userConfig[key as keyof typeof userConfig] || "";
|
const currentValue = userConfig[key as keyof typeof userConfig] || "";
|
||||||
return (
|
return (
|
||||||
<ListItem key={key} title={key} subTitle={prop.description}>
|
<ListItem
|
||||||
|
key={key}
|
||||||
|
title={key}
|
||||||
|
subTitle={prop.description}
|
||||||
|
vertical
|
||||||
|
>
|
||||||
<div className={styles["input-item"]}>
|
<div className={styles["input-item"]}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -356,6 +415,29 @@ export function McpMarketPage() {
|
|||||||
return clientStatuses[clientId] || { status: "undefined", errorMsg: null };
|
return clientStatuses[clientId] || { status: "undefined", errorMsg: null };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 修改状态显示逻辑
|
||||||
|
const getServerStatusDisplay = (clientId: string) => {
|
||||||
|
const status = checkServerStatus(clientId);
|
||||||
|
|
||||||
|
const statusMap = {
|
||||||
|
undefined: null, // 未配置/未找到不显示
|
||||||
|
paused: (
|
||||||
|
<span className={clsx(styles["server-status"], styles["stopped"])}>
|
||||||
|
Stopped
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
active: <span className={styles["server-status"]}>Running</span>,
|
||||||
|
error: (
|
||||||
|
<span className={clsx(styles["server-status"], styles["error"])}>
|
||||||
|
Error
|
||||||
|
<span className={styles["error-message"]}>: {status.errorMsg}</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return statusMap[status.status];
|
||||||
|
};
|
||||||
|
|
||||||
// 渲染服务器列表
|
// 渲染服务器列表
|
||||||
const renderServerList = () => {
|
const renderServerList = () => {
|
||||||
return presetServers
|
return presetServers
|
||||||
@ -373,15 +455,18 @@ export function McpMarketPage() {
|
|||||||
const bStatus = checkServerStatus(b.id).status;
|
const bStatus = checkServerStatus(b.id).status;
|
||||||
|
|
||||||
// 定义状态优先级
|
// 定义状态优先级
|
||||||
const statusPriority = {
|
const statusPriority: Record<string, number> = {
|
||||||
error: 0,
|
error: 0, // 最高优先级
|
||||||
active: 1,
|
active: 1, // 运行中
|
||||||
undefined: 2,
|
paused: 2, // 已暂停
|
||||||
|
undefined: 3, // 未配置/未找到
|
||||||
};
|
};
|
||||||
|
|
||||||
// 首先按状态排序
|
// 首先按状态排序
|
||||||
if (aStatus !== bStatus) {
|
if (aStatus !== bStatus) {
|
||||||
return statusPriority[aStatus] - statusPriority[bStatus];
|
return (
|
||||||
|
(statusPriority[aStatus] || 3) - (statusPriority[bStatus] || 3)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 然后按名称排序
|
// 然后按名称排序
|
||||||
@ -398,25 +483,7 @@ export function McpMarketPage() {
|
|||||||
<div className={styles["mcp-market-title"]}>
|
<div className={styles["mcp-market-title"]}>
|
||||||
<div className={styles["mcp-market-name"]}>
|
<div className={styles["mcp-market-name"]}>
|
||||||
{server.name}
|
{server.name}
|
||||||
{checkServerStatus(server.id).status !== "undefined" && (
|
{getServerStatusDisplay(server.id)}
|
||||||
<span
|
|
||||||
className={clsx(styles["server-status"], {
|
|
||||||
[styles["error"]]:
|
|
||||||
checkServerStatus(server.id).status === "error",
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{checkServerStatus(server.id).status === "error" ? (
|
|
||||||
<>
|
|
||||||
Error
|
|
||||||
<span className={styles["error-message"]}>
|
|
||||||
: {checkServerStatus(server.id).errorMsg}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
"Active"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{server.repo && (
|
{server.repo && (
|
||||||
<a
|
<a
|
||||||
href={server.repo}
|
href={server.repo}
|
||||||
@ -450,14 +517,27 @@ export function McpMarketPage() {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon={<EditIcon />}
|
icon={<EditIcon />}
|
||||||
text="Configure"
|
text="Configure"
|
||||||
className={clsx({
|
|
||||||
[styles["action-error"]]:
|
|
||||||
checkServerStatus(server.id).status === "error",
|
|
||||||
})}
|
|
||||||
onClick={() => setEditingServerId(server.id)}
|
onClick={() => setEditingServerId(server.id)}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{checkServerStatus(server.id).status === "paused" ? (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
icon={<PlayIcon />}
|
||||||
|
text="Start"
|
||||||
|
onClick={() => resumeServer(server.id)}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
{/* <IconButton
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
text="Remove"
|
||||||
|
onClick={() => removeServer(server.id)}
|
||||||
|
disabled={isLoading}
|
||||||
|
/> */}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<EyeIcon />}
|
icon={<EyeIcon />}
|
||||||
text="Tools"
|
text="Tools"
|
||||||
@ -471,18 +551,18 @@ export function McpMarketPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<DeleteIcon />}
|
icon={<StopIcon />}
|
||||||
text="Remove"
|
text="Stop"
|
||||||
className={styles["action-danger"]}
|
onClick={() => pauseServer(server.id)}
|
||||||
onClick={() => removeServer(server.id)}
|
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<AddIcon />}
|
icon={<AddIcon />}
|
||||||
text="Add"
|
text="Add"
|
||||||
className={styles["action-primary"]}
|
|
||||||
onClick={() => addServer(server)}
|
onClick={() => addServer(server)}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
|
@ -1 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M13.33,6.67C13.33,2.98 10.35,0 6.67,0C2.98,0 0,2.98 0,6.67C0,10.35 2.98,13.33 6.67,13.33C10.35,13.33 13.33,10.35 13.33,6.67Z" transform="translate(1.3333333333333333 1.3333333333333333) rotate(0 6.666666666666666 6.666666666666666)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L0,4" transform="translate(6.333333333333333 6) rotate(0 0 2)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L0,4" transform="translate(9.666666666666666 6) rotate(0 0 2)"/></g></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||||
|
</svg>
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 253 B |
3
app/icons/play.svg
Normal file
3
app/icons/play.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 239 B |
@ -12,6 +12,7 @@ import {
|
|||||||
McpConfigData,
|
McpConfigData,
|
||||||
McpRequestMessage,
|
McpRequestMessage,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
|
ServerStatusResponse,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@ -22,14 +23,40 @@ const CONFIG_PATH = path.join(process.cwd(), "app/mcp/mcp_config.json");
|
|||||||
const clientsMap = new Map<string, McpClientData>();
|
const clientsMap = new Map<string, McpClientData>();
|
||||||
|
|
||||||
// 获取客户端状态
|
// 获取客户端状态
|
||||||
export async function getClientStatus(clientId: string) {
|
export async function getClientStatus(
|
||||||
|
clientId: string,
|
||||||
|
): Promise<ServerStatusResponse> {
|
||||||
const status = clientsMap.get(clientId);
|
const status = clientsMap.get(clientId);
|
||||||
if (!status) return { status: "undefined" as const, errorMsg: null };
|
const config = await getMcpConfigFromFile();
|
||||||
|
const serverConfig = config.mcpServers[clientId];
|
||||||
|
|
||||||
return {
|
// 如果配置中不存在该服务器
|
||||||
status: status.errorMsg ? ("error" as const) : ("active" as const),
|
if (!serverConfig) {
|
||||||
errorMsg: status.errorMsg,
|
return { status: "undefined", errorMsg: null };
|
||||||
};
|
}
|
||||||
|
|
||||||
|
// 如果服务器配置为暂停状态
|
||||||
|
if (serverConfig.status === "paused") {
|
||||||
|
return { status: "paused", errorMsg: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 clientsMap 中没有记录
|
||||||
|
if (!status) {
|
||||||
|
return { status: "undefined", errorMsg: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有错误
|
||||||
|
if (status.errorMsg) {
|
||||||
|
return { status: "error", errorMsg: status.errorMsg };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果客户端正常运行
|
||||||
|
if (status.client) {
|
||||||
|
return { status: "active", errorMsg: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果客户端不存在
|
||||||
|
return { status: "error", errorMsg: "Client not found" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取客户端工具
|
// 获取客户端工具
|
||||||
@ -61,6 +88,12 @@ async function initializeSingleClient(
|
|||||||
clientId: string,
|
clientId: string,
|
||||||
serverConfig: ServerConfig,
|
serverConfig: ServerConfig,
|
||||||
) {
|
) {
|
||||||
|
// 如果服务器状态是暂停,则不初始化
|
||||||
|
if (serverConfig.status === "paused") {
|
||||||
|
logger.info(`Skipping initialization for paused client [${clientId}]`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(`Initializing client [${clientId}]...`);
|
logger.info(`Initializing client [${clientId}]...`);
|
||||||
try {
|
try {
|
||||||
const client = await createClient(clientId, serverConfig);
|
const client = await createClient(clientId, serverConfig);
|
||||||
@ -114,6 +147,100 @@ export async function addMcpServer(clientId: string, config: ServerConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 暂停服务器
|
||||||
|
export async function pauseMcpServer(clientId: string) {
|
||||||
|
try {
|
||||||
|
const currentConfig = await getMcpConfigFromFile();
|
||||||
|
const serverConfig = currentConfig.mcpServers[clientId];
|
||||||
|
if (!serverConfig) {
|
||||||
|
throw new Error(`Server ${clientId} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先更新配置
|
||||||
|
const newConfig: McpConfigData = {
|
||||||
|
...currentConfig,
|
||||||
|
mcpServers: {
|
||||||
|
...currentConfig.mcpServers,
|
||||||
|
[clientId]: {
|
||||||
|
...serverConfig,
|
||||||
|
status: "paused" as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
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 pause server [${clientId}]: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复服务器
|
||||||
|
export async function resumeMcpServer(clientId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const currentConfig = await getMcpConfigFromFile();
|
||||||
|
const serverConfig = currentConfig.mcpServers[clientId];
|
||||||
|
if (!serverConfig) {
|
||||||
|
throw new Error(`Server ${clientId} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先尝试初始化客户端
|
||||||
|
logger.info(`Trying to initialize client [${clientId}]...`);
|
||||||
|
try {
|
||||||
|
const client = await createClient(clientId, serverConfig);
|
||||||
|
const tools = await listTools(client);
|
||||||
|
clientsMap.set(clientId, { client, tools, errorMsg: null });
|
||||||
|
logger.success(`Client [${clientId}] initialized successfully`);
|
||||||
|
|
||||||
|
// 初始化成功后更新配置
|
||||||
|
const newConfig: McpConfigData = {
|
||||||
|
...currentConfig,
|
||||||
|
mcpServers: {
|
||||||
|
...currentConfig.mcpServers,
|
||||||
|
[clientId]: {
|
||||||
|
...serverConfig,
|
||||||
|
status: "active" as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await updateMcpConfig(newConfig);
|
||||||
|
|
||||||
|
// 再次确认状态
|
||||||
|
const status = await getClientStatus(clientId);
|
||||||
|
return status.status === "active";
|
||||||
|
} catch (error) {
|
||||||
|
const currentConfig = await getMcpConfigFromFile();
|
||||||
|
const serverConfig = currentConfig.mcpServers[clientId];
|
||||||
|
|
||||||
|
// 如果配置中存在该服务器,则更新其状态为 error
|
||||||
|
if (serverConfig) {
|
||||||
|
serverConfig.status = "error";
|
||||||
|
await updateMcpConfig(currentConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化失败
|
||||||
|
clientsMap.set(clientId, {
|
||||||
|
client: null,
|
||||||
|
tools: null,
|
||||||
|
errorMsg: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
logger.error(`Failed to initialize client [${clientId}]: ${error}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to resume server [${clientId}]: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 移除服务器
|
// 移除服务器
|
||||||
export async function removeMcpServer(clientId: string) {
|
export async function removeMcpServer(clientId: string) {
|
||||||
try {
|
try {
|
||||||
|
@ -72,31 +72,6 @@
|
|||||||
"baseArgs": ["-y", "@executeautomation/playwright-mcp-server"],
|
"baseArgs": ["-y", "@executeautomation/playwright-mcp-server"],
|
||||||
"configurable": false
|
"configurable": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "mongodb",
|
|
||||||
"name": "MongoDB",
|
|
||||||
"description": "Direct interaction with MongoDB databases",
|
|
||||||
"repo": "",
|
|
||||||
"tags": ["database", "mongodb", "nosql"],
|
|
||||||
"command": "node",
|
|
||||||
"baseArgs": ["dist/index.js"],
|
|
||||||
"configurable": true,
|
|
||||||
"configSchema": {
|
|
||||||
"properties": {
|
|
||||||
"connectionString": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "MongoDB connection string",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"argsMapping": {
|
|
||||||
"connectionString": {
|
|
||||||
"type": "single",
|
|
||||||
"position": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "difyworkflow",
|
"id": "difyworkflow",
|
||||||
"name": "Dify Workflow",
|
"name": "Dify Workflow",
|
||||||
|
@ -87,11 +87,20 @@ interface McpErrorClient {
|
|||||||
errorMsg: string;
|
errorMsg: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 服务器状态类型
|
||||||
|
export type ServerStatus = "undefined" | "active" | "paused" | "error";
|
||||||
|
|
||||||
|
export interface ServerStatusResponse {
|
||||||
|
status: ServerStatus;
|
||||||
|
errorMsg: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
// MCP 服务器配置相关类型
|
// MCP 服务器配置相关类型
|
||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
command: string;
|
command: string;
|
||||||
args: string[];
|
args: string[];
|
||||||
env?: Record<string, string>;
|
env?: Record<string, string>;
|
||||||
|
status?: "active" | "paused" | "error";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface McpConfigData {
|
export interface McpConfigData {
|
||||||
|
Loading…
Reference in New Issue
Block a user