|
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', |
|
}); |
|
} |
|
}; |
|
|