| import { Request, Response } from 'express'; |
| import { ApiResponse, AddServerRequest } from '../types/index.js'; |
| import { |
| getServersInfo, |
| addServer, |
| removeServer, |
| updateMcpServer, |
| notifyToolChanged, |
| toggleServerStatus, |
| } from '../services/mcpService.js'; |
| import { loadSettings, saveSettings } from '../config/index.js'; |
| import { syncAllServerToolsEmbeddings } from '../services/vectorSearchService.js'; |
|
|
| export const getAllServers = (_: Request, res: Response): void => { |
| try { |
| const serversInfo = getServersInfo(); |
| const response: ApiResponse = { |
| success: true, |
| data: serversInfo, |
| }; |
| res.json(response); |
| } catch (error) { |
| res.status(500).json({ |
| success: false, |
| message: 'Failed to get servers information', |
| }); |
| } |
| }; |
|
|
| export const getAllSettings = (_: Request, res: Response): void => { |
| try { |
| const settings = loadSettings(); |
| const response: ApiResponse = { |
| success: true, |
| data: settings, |
| }; |
| res.json(response); |
| } catch (error) { |
| res.status(500).json({ |
| success: false, |
| message: 'Failed to get server settings', |
| }); |
| } |
| }; |
|
|
| export const createServer = async (req: Request, res: Response): Promise<void> => { |
| try { |
| const { name, config } = req.body as AddServerRequest; |
| if (!name || typeof name !== 'string') { |
| res.status(400).json({ |
| success: false, |
| message: 'Server name is required', |
| }); |
| return; |
| } |
|
|
| if (!config || typeof config !== 'object') { |
| res.status(400).json({ |
| success: false, |
| message: 'Server configuration is required', |
| }); |
| return; |
| } |
|
|
| if (!config.url && (!config.command || !config.args)) { |
| res.status(400).json({ |
| success: false, |
| message: 'Server configuration must include either a URL or command with arguments', |
| }); |
| return; |
| } |
|
|
| |
| if (config.type && !['stdio', 'sse', 'streamable-http'].includes(config.type)) { |
| res.status(400).json({ |
| success: false, |
| message: 'Server type must be one of: stdio, sse, streamable-http', |
| }); |
| return; |
| } |
|
|
| |
| if ((config.type === 'sse' || config.type === 'streamable-http') && !config.url) { |
| res.status(400).json({ |
| success: false, |
| message: `URL is required for ${config.type} server type`, |
| }); |
| return; |
| } |
|
|
| |
| if (config.headers && typeof config.headers !== 'object') { |
| res.status(400).json({ |
| success: false, |
| message: 'Headers must be an object', |
| }); |
| return; |
| } |
|
|
| |
| if (config.headers && config.type === 'stdio') { |
| res.status(400).json({ |
| success: false, |
| message: 'Headers are not supported for stdio server type', |
| }); |
| return; |
| } |
|
|
| const result = await addServer(name, config); |
| if (result.success) { |
| notifyToolChanged(); |
| res.json({ |
| success: true, |
| message: 'Server added successfully', |
| }); |
| } else { |
| res.status(400).json({ |
| success: false, |
| message: result.message || 'Failed to add server', |
| }); |
| } |
| } catch (error) { |
| res.status(500).json({ |
| success: false, |
| message: 'Internal server error', |
| }); |
| } |
| }; |
|
|
| export const deleteServer = async (req: Request, res: Response): Promise<void> => { |
| try { |
| const { name } = req.params; |
| if (!name) { |
| res.status(400).json({ |
| success: false, |
| message: 'Server name is required', |
| }); |
| return; |
| } |
|
|
| const result = removeServer(name); |
| if (result.success) { |
| notifyToolChanged(); |
| res.json({ |
| success: true, |
| message: 'Server removed successfully', |
| }); |
| } else { |
| res.status(404).json({ |
| success: false, |
| message: result.message || 'Server not found or failed to remove', |
| }); |
| } |
| } catch (error) { |
| res.status(500).json({ |
| success: false, |
| message: 'Internal server error', |
| }); |
| } |
| }; |
|
|
| export const updateServer = async (req: Request, res: Response): Promise<void> => { |
| try { |
| const { name } = req.params; |
| const { config } = req.body; |
| if (!name) { |
| res.status(400).json({ |
| success: false, |
| message: 'Server name is required', |
| }); |
| return; |
| } |
|
|
| if (!config || typeof config !== 'object') { |
| res.status(400).json({ |
| success: false, |
| message: 'Server configuration is required', |
| }); |
| return; |
| } |
|
|
| if (!config.url && (!config.command || !config.args)) { |
| res.status(400).json({ |
| success: false, |
| message: 'Server configuration must include either a URL or command with arguments', |
| }); |
| return; |
| } |
|
|
| |
| if (config.type && !['stdio', 'sse', 'streamable-http'].includes(config.type)) { |
| res.status(400).json({ |
| success: false, |
| message: 'Server type must be one of: stdio, sse, streamable-http', |
| }); |
| return; |
| } |
|
|
| |
| if ((config.type === 'sse' || config.type === 'streamable-http') && !config.url) { |
| res.status(400).json({ |
| success: false, |
| message: `URL is required for ${config.type} server type`, |
| }); |
| return; |
| } |
|
|
| |
| if (config.headers && typeof config.headers !== 'object') { |
| res.status(400).json({ |
| success: false, |
| message: 'Headers must be an object', |
| }); |
| return; |
| } |
|
|
| |
| if (config.headers && config.type === 'stdio') { |
| res.status(400).json({ |
| success: false, |
| message: 'Headers are not supported for stdio server type', |
| }); |
| return; |
| } |
|
|
| const result = await updateMcpServer(name, config); |
| if (result.success) { |
| notifyToolChanged(); |
| res.json({ |
| success: true, |
| message: 'Server updated successfully', |
| }); |
| } else { |
| res.status(404).json({ |
| success: false, |
| message: result.message || 'Server not found or failed to update', |
| }); |
| } |
| } catch (error) { |
| res.status(500).json({ |
| success: false, |
| message: 'Internal server error', |
| }); |
| } |
| }; |
|
|
| export const getServerConfig = (req: Request, res: Response): void => { |
| try { |
| const { name } = req.params; |
| const settings = loadSettings(); |
| if (!settings.mcpServers || !settings.mcpServers[name]) { |
| res.status(404).json({ |
| success: false, |
| message: 'Server not found', |
| }); |
| return; |
| } |
|
|
| const serverInfo = getServersInfo().find((s) => s.name === name); |
| const serverConfig = settings.mcpServers[name]; |
| const response: ApiResponse = { |
| success: true, |
| data: { |
| name, |
| status: serverInfo ? serverInfo.status : 'disconnected', |
| tools: serverInfo ? serverInfo.tools : [], |
| config: serverConfig, |
| }, |
| }; |
|
|
| res.json(response); |
| } catch (error) { |
| res.status(500).json({ |
| success: false, |
| message: 'Failed to get server configuration', |
| }); |
| } |
| }; |
|
|
| export const toggleServer = async (req: Request, res: Response): Promise<void> => { |
| 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) { |
| notifyToolChanged(); |
| 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', |
| }); |
| } |
| }; |
|
|
| export const updateSystemConfig = (req: Request, res: Response): void => { |
| try { |
| const { routing, install, smartRouting } = req.body; |
|
|
| if ( |
| (!routing || |
| (typeof routing.enableGlobalRoute !== 'boolean' && |
| typeof routing.enableGroupNameRoute !== 'boolean' && |
| typeof routing.enableBearerAuth !== 'boolean' && |
| typeof routing.bearerAuthKey !== 'string')) && |
| (!install || |
| (typeof install.pythonIndexUrl !== 'string' && typeof install.npmRegistry !== 'string')) && |
| (!smartRouting || |
| (typeof smartRouting.enabled !== 'boolean' && |
| typeof smartRouting.dbUrl !== 'string' && |
| typeof smartRouting.openaiApiBaseUrl !== 'string' && |
| typeof smartRouting.openaiApiKey !== 'string' && |
| typeof smartRouting.openaiApiEmbeddingModel !== 'string')) |
| ) { |
| res.status(400).json({ |
| success: false, |
| message: 'Invalid system configuration provided', |
| }); |
| return; |
| } |
|
|
| const settings = loadSettings(); |
| if (!settings.systemConfig) { |
| settings.systemConfig = { |
| routing: { |
| enableGlobalRoute: true, |
| enableGroupNameRoute: true, |
| enableBearerAuth: false, |
| bearerAuthKey: '', |
| }, |
| install: { |
| pythonIndexUrl: '', |
| npmRegistry: '', |
| }, |
| smartRouting: { |
| enabled: false, |
| dbUrl: '', |
| openaiApiBaseUrl: '', |
| openaiApiKey: '', |
| openaiApiEmbeddingModel: '', |
| }, |
| }; |
| } |
|
|
| if (!settings.systemConfig.routing) { |
| settings.systemConfig.routing = { |
| enableGlobalRoute: true, |
| enableGroupNameRoute: true, |
| enableBearerAuth: false, |
| bearerAuthKey: '', |
| }; |
| } |
|
|
| if (!settings.systemConfig.install) { |
| settings.systemConfig.install = { |
| pythonIndexUrl: '', |
| npmRegistry: '', |
| }; |
| } |
|
|
| if (!settings.systemConfig.smartRouting) { |
| settings.systemConfig.smartRouting = { |
| enabled: false, |
| dbUrl: '', |
| openaiApiBaseUrl: '', |
| openaiApiKey: '', |
| openaiApiEmbeddingModel: '', |
| }; |
| } |
|
|
| if (routing) { |
| if (typeof routing.enableGlobalRoute === 'boolean') { |
| settings.systemConfig.routing.enableGlobalRoute = routing.enableGlobalRoute; |
| } |
|
|
| if (typeof routing.enableGroupNameRoute === 'boolean') { |
| settings.systemConfig.routing.enableGroupNameRoute = routing.enableGroupNameRoute; |
| } |
|
|
| if (typeof routing.enableBearerAuth === 'boolean') { |
| settings.systemConfig.routing.enableBearerAuth = routing.enableBearerAuth; |
| } |
|
|
| if (typeof routing.bearerAuthKey === 'string') { |
| settings.systemConfig.routing.bearerAuthKey = routing.bearerAuthKey; |
| } |
| } |
|
|
| if (install) { |
| if (typeof install.pythonIndexUrl === 'string') { |
| settings.systemConfig.install.pythonIndexUrl = install.pythonIndexUrl; |
| } |
| if (typeof install.npmRegistry === 'string') { |
| settings.systemConfig.install.npmRegistry = install.npmRegistry; |
| } |
| } |
|
|
| |
| const wasSmartRoutingEnabled = settings.systemConfig.smartRouting.enabled || false; |
| const previousSmartRoutingConfig = { ...settings.systemConfig.smartRouting }; |
| let needsSync = false; |
|
|
| if (smartRouting) { |
| if (typeof smartRouting.enabled === 'boolean') { |
| |
| if (smartRouting.enabled) { |
| const currentDbUrl = smartRouting.dbUrl || settings.systemConfig.smartRouting.dbUrl; |
| const currentOpenaiApiKey = |
| smartRouting.openaiApiKey || settings.systemConfig.smartRouting.openaiApiKey; |
|
|
| if (!currentDbUrl || !currentOpenaiApiKey) { |
| const missingFields = []; |
| if (!currentDbUrl) missingFields.push('Database URL'); |
| if (!currentOpenaiApiKey) missingFields.push('OpenAI API Key'); |
|
|
| res.status(400).json({ |
| success: false, |
| message: `Smart Routing requires the following fields: ${missingFields.join(', ')}`, |
| }); |
| return; |
| } |
| } |
| settings.systemConfig.smartRouting.enabled = smartRouting.enabled; |
| } |
| if (typeof smartRouting.dbUrl === 'string') { |
| settings.systemConfig.smartRouting.dbUrl = smartRouting.dbUrl; |
| } |
| if (typeof smartRouting.openaiApiBaseUrl === 'string') { |
| settings.systemConfig.smartRouting.openaiApiBaseUrl = smartRouting.openaiApiBaseUrl; |
| } |
| if (typeof smartRouting.openaiApiKey === 'string') { |
| settings.systemConfig.smartRouting.openaiApiKey = smartRouting.openaiApiKey; |
| } |
| if (typeof smartRouting.openaiApiEmbeddingModel === 'string') { |
| settings.systemConfig.smartRouting.openaiApiEmbeddingModel = |
| smartRouting.openaiApiEmbeddingModel; |
| } |
|
|
| |
| const isNowEnabled = settings.systemConfig.smartRouting.enabled || false; |
| const hasConfigChanged = |
| previousSmartRoutingConfig.dbUrl !== settings.systemConfig.smartRouting.dbUrl || |
| previousSmartRoutingConfig.openaiApiBaseUrl !== |
| settings.systemConfig.smartRouting.openaiApiBaseUrl || |
| previousSmartRoutingConfig.openaiApiKey !== |
| settings.systemConfig.smartRouting.openaiApiKey || |
| previousSmartRoutingConfig.openaiApiEmbeddingModel !== |
| settings.systemConfig.smartRouting.openaiApiEmbeddingModel; |
|
|
| |
| needsSync = (!wasSmartRoutingEnabled && isNowEnabled) || (isNowEnabled && hasConfigChanged); |
| } |
|
|
| if (saveSettings(settings)) { |
| res.json({ |
| success: true, |
| data: settings.systemConfig, |
| message: 'System configuration updated successfully', |
| }); |
|
|
| |
| if (needsSync) { |
| console.log('SmartRouting configuration changed - syncing all existing server tools...'); |
| |
| syncAllServerToolsEmbeddings().catch((error) => { |
| console.error('Failed to sync server tools embeddings:', error); |
| }); |
| } |
| } else { |
| res.status(500).json({ |
| success: false, |
| message: 'Failed to save system configuration', |
| }); |
| } |
| } catch (error) { |
| res.status(500).json({ |
| success: false, |
| message: 'Internal server error', |
| }); |
| } |
| }; |
|
|