diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 825e4e7..b52b461 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -275,6 +275,34 @@ const Dashboard = () => {
}
}
+ const handleServerToggle = async (server: Server, enabled: boolean) => {
+ try {
+ const token = localStorage.getItem('mcphub_token');
+ const response = await fetch(`/api/servers/${server.name}/toggle`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-auth-token': token || ''
+ },
+ body: JSON.stringify({ enabled }),
+ });
+
+ const result = await response.json();
+
+ if (!response.ok) {
+ console.error('Failed to toggle server:', result);
+ setError(t('server.toggleError', { serverName: server.name }));
+ return;
+ }
+
+ // Update the UI immediately to reflect the change
+ setRefreshKey(prevKey => prevKey + 1);
+ } catch (err) {
+ console.error('Error toggling server:', err);
+ setError(err instanceof Error ? err.message : String(err));
+ }
+ };
+
const handleLogout = () => {
logout()
navigate('/login')
@@ -333,6 +361,7 @@ const Dashboard = () => {
server={server}
onRemove={handleServerRemove}
onEdit={handleServerEdit}
+ onToggle={handleServerToggle}
/>
))}
diff --git a/frontend/src/components/ServerCard.tsx b/frontend/src/components/ServerCard.tsx
index 46d6e55..7f79181 100644
--- a/frontend/src/components/ServerCard.tsx
+++ b/frontend/src/components/ServerCard.tsx
@@ -10,12 +10,14 @@ interface ServerCardProps {
server: Server
onRemove: (serverName: string) => void
onEdit: (server: Server) => void
+ onToggle?: (server: Server, enabled: boolean) => void
}
-const ServerCard = ({ server, onRemove, onEdit }: ServerCardProps) => {
+const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) => {
const { t } = useTranslation()
const [isExpanded, setIsExpanded] = useState(false)
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
+ const [isToggling, setIsToggling] = useState(false)
const handleRemove = (e: React.MouseEvent) => {
e.stopPropagation()
@@ -27,6 +29,19 @@ const ServerCard = ({ server, onRemove, onEdit }: ServerCardProps) => {
onEdit(server)
}
+ const handleToggle = async (e: React.MouseEvent) => {
+ e.stopPropagation()
+ if (isToggling || !onToggle) return
+
+ setIsToggling(true)
+ try {
+ // Toggle the server's enabled status
+ await onToggle(server, !(server.enabled !== false))
+ } finally {
+ setIsToggling(false)
+ }
+ }
+
const handleConfirmDelete = () => {
onRemove(server.name)
setShowDeleteDialog(false)
@@ -55,6 +70,26 @@ const ServerCard = ({ server, onRemove, onEdit }: ServerCardProps) => {
>
{t('server.delete')}
+
+
+
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json
index 9469208..e27410c 100644
--- a/frontend/src/locales/en.json
+++ b/frontend/src/locales/en.json
@@ -3,7 +3,7 @@
"title": "MCP Hub Dashboard",
"error": "Error",
"closeButton": "Close",
- "noServers": "No MCP servers available",
+ "noServers": "No servers found. Add a new server to get started.",
"loading": "Loading...",
"logout": "Logout",
"profile": "Profile",
@@ -24,7 +24,14 @@
"passwordsNotMatch": "New password and confirmation do not match",
"changePasswordSuccess": "Password changed successfully",
"changePasswordError": "Failed to change password",
- "changePassword": "Change Password"
+ "changePassword": "Change Password",
+ "confirmNewPassword": "Confirm New Password",
+ "loginButton": "Login",
+ "changePasswordTitle": "Change Password",
+ "changePasswordButton": "Change Password",
+ "passwordsMustMatch": "Passwords must match",
+ "changeSuccess": "Password changed successfully",
+ "invalidCredentials": "Invalid username or password"
},
"server": {
"addServer": "Add Server",
@@ -33,25 +40,34 @@
"delete": "Delete",
"confirmDelete": "Are you sure you want to delete this server?",
"status": "Status",
- "tools": "Tools",
- "name": "Server Name",
- "url": "Server URL",
+ "tools": "Available Tools",
+ "name": "Name",
+ "url": "URL",
"apiKey": "API Key",
- "save": "Save Changes",
+ "save": "Save",
"cancel": "Cancel",
- "invalidConfig": "Could not find configuration data for {{serverName}}",
+ "invalidConfig": "Failed to get configuration for '{{serverName}}'",
"addError": "Failed to add server",
"editError": "Failed to edit server {{serverName}}",
"deleteError": "Failed to delete server {{serverName}}",
- "updateError": "Failed to update server",
+ "updateError": "Failed to update server '{{serverName}}'",
"editTitle": "Edit Server: {{serverName}}",
- "type": "Server Type",
+ "type": "Type",
"command": "Command",
"arguments": "Arguments",
"envVars": "Environment Variables",
- "key": "key",
- "value": "value",
- "remove": "Remove"
+ "key": "Key",
+ "value": "Value",
+ "remove": "Remove",
+ "deleteTitle": "Delete Server",
+ "deleteConfirm": "Are you sure you want to delete {{serverName}}?",
+ "deleteWarning": "This action cannot be undone.",
+ "enable": "Enable",
+ "disable": "Disable",
+ "toggleError": "Failed to toggle server: {{serverName}}",
+ "invalidData": "Invalid server data",
+ "alreadyExists": "Server '{{serverName}}' already exists",
+ "notFound": "Server '{{serverName}}' not found"
},
"status": {
"online": "Online",
@@ -59,16 +75,17 @@
"connecting": "Connecting"
},
"errors": {
- "general": "Something went wrong",
- "network": "Network connection error. Please check your internet connection",
- "serverConnection": "Unable to connect to the server. Please check if the server is running",
- "serverAdd": "Failed to add server. Please check the server status",
- "serverUpdate": "Failed to edit server {{serverName}}. Please check the server status",
- "serverFetch": "Failed to retrieve server data. Please try again later",
- "initialStartup": "The server might be starting up. Please wait a moment as this process can take some time on first launch..."
+ "general": "An error occurred",
+ "network": "Network connection error. Please check your internet connection.",
+ "serverConnection": "Could not connect to server. Please try again later.",
+ "serverAdd": "Failed to add server.",
+ "serverUpdate": "Failed to update server '{{serverName}}'.",
+ "serverFetch": "Failed to fetch servers data.",
+ "initialStartup": "Server is starting up. Please wait..."
},
"common": {
"save": "Save",
- "cancel": "Cancel"
+ "cancel": "Cancel",
+ "processing": "Processing..."
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json
index e858d22..ef1e09e 100644
--- a/frontend/src/locales/zh.json
+++ b/frontend/src/locales/zh.json
@@ -3,7 +3,7 @@
"title": "MCP Hub 控制面板",
"error": "错误",
"closeButton": "关闭",
- "noServers": "没有可用的 MCP 服务器",
+ "noServers": "未找到服务器。添加新服务器以开始使用。",
"loading": "加载中...",
"logout": "退出登录",
"profile": "个人资料",
@@ -14,44 +14,60 @@
"loginTitle": "登录 MCP Hub",
"username": "用户名",
"password": "密码",
- "loggingIn": "登录中...",
+ "loggingIn": "正在登录...",
"emptyFields": "用户名和密码不能为空",
"loginFailed": "登录失败,请检查用户名和密码",
- "loginError": "登录过程中出现错误",
+ "loginError": "登录时发生错误",
"currentPassword": "当前密码",
"newPassword": "新密码",
"confirmPassword": "确认密码",
- "passwordsNotMatch": "新密码与确认密码不一致",
+ "passwordsNotMatch": "新密码和确认密码不匹配",
"changePasswordSuccess": "密码修改成功",
- "changePasswordError": "修改密码失败",
- "changePassword": "修改密码"
+ "changePasswordError": "密码修改失败",
+ "changePassword": "修改密码",
+ "confirmNewPassword": "确认新密码",
+ "loginButton": "登录",
+ "changePasswordTitle": "修改密码",
+ "changePasswordButton": "修改密码",
+ "passwordsMustMatch": "密码必须匹配",
+ "changeSuccess": "密码修改成功",
+ "invalidCredentials": "用户名或密码无效"
},
"server": {
"addServer": "添加服务器",
"add": "添加",
"edit": "编辑",
"delete": "删除",
- "confirmDelete": "您确定要删除此服务器吗?",
+ "confirmDelete": "确定要删除此服务器吗?",
"status": "状态",
- "tools": "工具",
- "name": "服务器名称",
- "url": "服务器 URL",
+ "tools": "可用工具",
+ "name": "名称",
+ "url": "URL",
"apiKey": "API 密钥",
- "save": "保存更改",
+ "save": "保存",
"cancel": "取消",
+ "invalidConfig": "获取 '{{serverName}}' 的配置失败",
"addError": "添加服务器失败",
"editError": "编辑服务器 {{serverName}} 失败",
- "invalidConfig": "无法找到 {{serverName}} 的配置数据",
"deleteError": "删除服务器 {{serverName}} 失败",
- "updateError": "更新服务器失败",
+ "updateError": "更新服务器 '{{serverName}}' 失败",
"editTitle": "编辑服务器: {{serverName}}",
- "type": "服务器类型",
+ "type": "类型",
"command": "命令",
"arguments": "参数",
"envVars": "环境变量",
"key": "键",
"value": "值",
- "remove": "移除"
+ "remove": "移除",
+ "deleteTitle": "删除服务器",
+ "deleteConfirm": "您确定要删除 {{serverName}} 吗?",
+ "deleteWarning": "此操作无法撤销。",
+ "enable": "启用",
+ "disable": "禁用",
+ "toggleError": "切换服务器 {{serverName}} 状态失败",
+ "invalidData": "无效的服务器数据",
+ "alreadyExists": "服务器 '{{serverName}}' 已存在",
+ "notFound": "未找到服务器 '{{serverName}}'"
},
"status": {
"online": "在线",
@@ -60,15 +76,16 @@
},
"errors": {
"general": "发生错误",
- "network": "网络连接错误,请检查您的互联网连接",
- "serverConnection": "无法连接到服务器,请检查服务器是否正在运行",
- "serverAdd": "添加服务器失败,请检查服务器状态",
- "serverUpdate": "编辑服务器 {{serverName}} 失败,请检查服务器状态",
- "serverFetch": "获取服务器数据失败,请稍后重试",
- "initialStartup": "服务器可能正在启动中。首次启动可能需要一些时间,请耐心等候..."
+ "network": "网络连接错误。请检查您的互联网连接。",
+ "serverConnection": "无法连接到服务器。请稍后再试。",
+ "serverAdd": "添加服务器失败。",
+ "serverUpdate": "更新服务器 '{{serverName}}' 失败。",
+ "serverFetch": "获取服务器数据失败。",
+ "initialStartup": "服务器正在启动。请稍候..."
},
"common": {
"save": "保存",
- "cancel": "取消"
+ "cancel": "取消",
+ "processing": "处理中..."
}
}
\ No newline at end of file
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
index ecf0193..c5129fd 100644
--- a/frontend/src/types/index.ts
+++ b/frontend/src/types/index.ts
@@ -30,6 +30,7 @@ export interface Server {
status: ServerStatus;
tools?: Tool[];
config?: ServerConfig;
+ enabled?: boolean;
}
// 环境变量类型
diff --git a/src/controllers/serverController.ts b/src/controllers/serverController.ts
index 8dd5f69..552d03c 100644
--- a/src/controllers/serverController.ts
+++ b/src/controllers/serverController.ts
@@ -6,6 +6,7 @@ import {
removeServer,
updateMcpServer,
recreateMcpServer,
+ toggleServerStatus,
} from '../services/mcpService.js';
import { loadSettings } from '../config/index.js';
@@ -207,3 +208,46 @@ export const getServerConfig = (req: Request, res: Response): void => {
});
}
};
+
+export const toggleServer = async (req: Request, res: Response): Promise => {
+ try {
+ const { name } = req.params;
+ const { enabled } = req.body;
+
+ if (!name) {
+ res.status(400).json({
+ success: false,
+ message: 'Server name is required',
+ });
+ return;
+ }
+
+ if (typeof enabled !== 'boolean') {
+ res.status(400).json({
+ success: false,
+ message: 'Enabled status must be a boolean',
+ });
+ return;
+ }
+
+ const result = await toggleServerStatus(name, enabled);
+
+ if (result.success) {
+ recreateMcpServer();
+ res.json({
+ success: true,
+ message: result.message || `Server ${enabled ? 'enabled' : 'disabled'} successfully`,
+ });
+ } else {
+ res.status(404).json({
+ success: false,
+ message: result.message || 'Server not found or failed to toggle status',
+ });
+ }
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ message: 'Internal server error',
+ });
+ }
+};
diff --git a/src/routes/index.ts b/src/routes/index.ts
index 2eca564..5e1c6ec 100644
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -6,6 +6,7 @@ import {
createServer,
updateServer,
deleteServer,
+ toggleServer,
} from '../controllers/serverController.js';
import {
login,
@@ -24,6 +25,7 @@ export const initRoutes = (app: express.Application): void => {
router.post('/servers', createServer);
router.put('/servers/:name', updateServer);
router.delete('/servers/:name', deleteServer);
+ router.post('/servers/:name/toggle', toggleServer);
// Auth routes (these will NOT be protected by auth middleware)
app.post('/auth/login', [
diff --git a/src/services/mcpService.ts b/src/services/mcpService.ts
index f4e7f5f..c41479e 100644
--- a/src/services/mcpService.ts
+++ b/src/services/mcpService.ts
@@ -44,12 +44,28 @@ export const initializeClientsFromSettings = (): ServerInfo[] => {
serverInfos = [];
for (const [name, conf] of Object.entries(settings.mcpServers)) {
+ // Skip disabled servers
+ if (conf.enabled === false) {
+ console.log(`Skipping disabled server: ${name}`);
+ serverInfos.push({
+ name,
+ status: 'disconnected',
+ tools: [],
+ createTime: Date.now(),
+ enabled: false
+ });
+ continue;
+ }
+
// Check if server is already connected
const existingServer = existingServerInfos.find(
(s) => s.name === name && s.status === 'connected',
);
if (existingServer) {
- serverInfos.push(existingServer);
+ serverInfos.push({
+ ...existingServer,
+ enabled: conf.enabled === undefined ? true : conf.enabled
+ });
console.log(`Server '${name}' is already connected.`);
continue;
}
@@ -160,12 +176,18 @@ export const registerAllTools = async (server: McpServer, forceInit: boolean): P
// Get all server information
export const getServersInfo = (): Omit[] => {
- return serverInfos.map(({ name, status, tools, createTime }) => ({
- name,
- status,
- tools,
- createTime,
- }));
+ const settings = loadSettings();
+ return serverInfos.map(({ name, status, tools, createTime }) => {
+ const serverConfig = settings.mcpServers[name];
+ const enabled = serverConfig ? (serverConfig.enabled !== false) : true;
+ return {
+ name,
+ status,
+ tools,
+ createTime,
+ enabled,
+ };
+ });
};
// Get server information by name
@@ -252,6 +274,51 @@ export const updateMcpServer = async (
}
};
+// Toggle server enabled status
+export const toggleServerStatus = async (
+ name: string,
+ enabled: boolean
+): Promise<{ success: boolean; message?: string }> => {
+ try {
+ const settings = loadSettings();
+ if (!settings.mcpServers[name]) {
+ return { success: false, message: 'Server not found' };
+ }
+
+ // Update the enabled status in settings
+ settings.mcpServers[name].enabled = enabled;
+
+ if (!saveSettings(settings)) {
+ return { success: false, message: 'Failed to save settings' };
+ }
+
+ // If disabling, disconnect the server and remove from active servers
+ if (!enabled) {
+ const serverInfo = serverInfos.find((serverInfo) => serverInfo.name === name);
+ if (serverInfo && serverInfo.client && serverInfo.transport) {
+ serverInfo.client.close();
+ serverInfo.transport.close();
+ console.log(`Closed client and transport for server: ${name}`);
+ }
+
+ // Update the server info to show as disconnected and disabled
+ const index = serverInfos.findIndex(s => s.name === name);
+ if (index !== -1) {
+ serverInfos[index] = {
+ ...serverInfos[index],
+ status: 'disconnected',
+ enabled: false,
+ };
+ }
+ }
+
+ return { success: true, message: `Server ${enabled ? 'enabled' : 'disabled'} successfully` };
+ } catch (error) {
+ console.error(`Failed to toggle server status: ${name}`, error);
+ return { success: false, message: 'Failed to toggle server status' };
+ }
+};
+
// Create McpServer instance
export const createMcpServer = (name: string, version: string): McpServer => {
return new McpServer({ name, version });
diff --git a/src/types/index.ts b/src/types/index.ts
index 82cade1..47ec1c6 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -23,6 +23,7 @@ export interface ServerConfig {
command?: string; // Command to execute for stdio-based servers
args?: string[]; // Arguments for the command
env?: Record; // Environment variables
+ enabled?: boolean; // Flag to enable/disable the server
}
// Information about a server's status and tools
@@ -33,6 +34,7 @@ export interface ServerInfo {
client?: Client; // Client instance for communication
transport?: SSEClientTransport | StdioClientTransport; // Transport mechanism used
createTime: number; // Timestamp of when the server was created
+ enabled?: boolean; // Flag to indicate if the server is enabled
}
// Details about a tool available on the server