diff --git a/src/controllers/serverController.ts b/src/controllers/serverController.ts index f9d0a1a..2daf763 100644 --- a/src/controllers/serverController.ts +++ b/src/controllers/serverController.ts @@ -1,11 +1,12 @@ import { Request, Response } from 'express'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ApiResponse, AddServerRequest } from '../types/index.js'; import { getServersInfo, addServer, removeServer } from '../services/mcpService.js'; import { loadSettings } from '../config/index.js'; -let mcpServerInstance: any; +let mcpServerInstance: McpServer; -export const setMcpServerInstance = (server: any): void => { +export const setMcpServerInstance = (server: McpServer): void => { mcpServerInstance = server; }; @@ -14,13 +15,13 @@ export const getAllServers = (_: Request, res: Response): void => { const serversInfo = getServersInfo(); const response: ApiResponse = { success: true, - data: serversInfo + data: serversInfo, }; res.json(response); } catch (error) { res.status(500).json({ success: false, - message: 'Failed to get servers information' + message: 'Failed to get servers information', }); } }; @@ -30,13 +31,13 @@ export const getAllSettings = (_: Request, res: Response): void => { const settings = loadSettings(); const response: ApiResponse = { success: true, - data: settings + data: settings, }; res.json(response); } catch (error) { res.status(500).json({ success: false, - message: 'Failed to get server settings' + message: 'Failed to get server settings', }); } }; @@ -46,17 +47,17 @@ export const createServer = async (req: Request, res: Response): Promise = const { name, config } = req.body as AddServerRequest; if (!name || typeof name !== 'string') { - res.status(400).json({ - success: false, - message: 'Server name is required' + 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' + res.status(400).json({ + success: false, + message: 'Server configuration is required', }); return; } @@ -64,28 +65,28 @@ export const createServer = async (req: Request, res: Response): Promise = 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' + message: 'Server configuration must include either a URL or command with arguments', }); return; } const result = await addServer(mcpServerInstance, name, config); - + if (result.success) { - res.json({ - success: true, - message: 'Server added successfully' + res.json({ + success: true, + message: 'Server added successfully', }); } else { - res.status(400).json({ - success: false, - message: result.message || 'Failed to add server' + 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' + res.status(500).json({ + success: false, + message: 'Internal server error', }); } }; @@ -93,32 +94,32 @@ export const createServer = async (req: Request, res: Response): Promise = export const deleteServer = async (req: Request, res: Response): Promise => { try { const { name } = req.params; - + if (!name) { - res.status(400).json({ - success: false, - message: 'Server name is required' + res.status(400).json({ + success: false, + message: 'Server name is required', }); return; } const result = removeServer(name); - + if (result.success) { - res.json({ - success: true, - message: 'Server removed successfully' + 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' + 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' + res.status(500).json({ + success: false, + message: 'Internal server error', }); } -}; \ No newline at end of file +}; diff --git a/src/middlewares/index.ts b/src/middlewares/index.ts index 326cdd9..c7e8028 100644 --- a/src/middlewares/index.ts +++ b/src/middlewares/index.ts @@ -8,15 +8,15 @@ export const errorHandler = ( _next: NextFunction ): void => { console.error('Unhandled error:', err); - res.status(500).json({ - success: false, - message: 'Internal server error' + res.status(500).json({ + success: false, + message: 'Internal server error', }); }; export const initMiddlewares = (app: express.Application): void => { app.use(express.static('public')); - + app.use((req, res, next) => { if (req.path !== '/sse' && req.path !== '/messages') { express.json()(req, res, next); @@ -30,4 +30,4 @@ export const initMiddlewares = (app: express.Application): void => { }); app.use(errorHandler); -}; \ No newline at end of file +}; diff --git a/src/server.ts b/src/server.ts index d16e4af..9cc973e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -19,7 +19,7 @@ export class AppServer { async initialize(): Promise { try { - await registerAllTools(this.mcpServer); + registerAllTools(this.mcpServer); initMiddlewares(this.app); initRoutes(this.app, this.mcpServer); this.app.get('/sse', (req, res) => handleSseConnection(req, res, this.mcpServer)); diff --git a/src/services/mcpService.ts b/src/services/mcpService.ts index b3098c6..ddb261b 100644 --- a/src/services/mcpService.ts +++ b/src/services/mcpService.ts @@ -5,7 +5,7 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import * as z from 'zod'; import { ZodType, ZodRawShape } from 'zod'; -import { ServerInfo, ServerConfig, ToolInfo } from '../types/index.js'; +import { ServerInfo, ServerConfig } from '../types/index.js'; import { loadSettings, saveSettings, expandEnvVars } from '../config/index.js'; // Store all server information @@ -19,7 +19,9 @@ export const initializeClientsFromSettings = (): ServerInfo[] => { for (const [name, config] of Object.entries(settings.mcpServers)) { // Check if server is already connected - const existingServer = existingServerInfos.find(s => s.name === name && s.status === 'connected'); + const existingServer = existingServerInfos.find( + (s) => s.name === name && s.status === 'connected', + ); if (existingServer) { serverInfos.push(existingServer); console.log(`Server '${name}' is already connected.`); @@ -32,13 +34,13 @@ export const initializeClientsFromSettings = (): ServerInfo[] => { } else if (config.command && config.args) { const rawEnv = { ...process.env, ...(config.env || {}) }; const env: Record = {}; - + for (const key in rawEnv) { if (typeof rawEnv[key] === 'string') { env[key] = expandEnvVars(rawEnv[key] as string); } } - + transport = new StdioClientTransport({ command: config.command, args: config.args, @@ -83,6 +85,7 @@ export const initializeClientsFromSettings = (): ServerInfo[] => { // Register all MCP tools export const registerAllTools = async (server: McpServer): Promise => { + initializeClientsFromSettings(); for (const serverInfo of serverInfos) { if (serverInfo.status === 'connected') continue; if (!serverInfo.client || !serverInfo.transport) continue; @@ -90,41 +93,43 @@ export const registerAllTools = async (server: McpServer): Promise => { try { serverInfo.status = 'connecting'; console.log(`Connecting to server: ${serverInfo.name}...`); - + await serverInfo.client.connect(serverInfo.transport); const tools = await serverInfo.client.listTools(); - + serverInfo.tools = tools.tools.map((tool) => ({ name: tool.name, description: tool.description || '', inputSchema: tool.inputSchema.properties || {}, })); - + serverInfo.status = 'connected'; console.log(`Successfully connected to server: ${serverInfo.name}`); - + for (const tool of tools.tools) { console.log(`Registering tool: ${JSON.stringify(tool)}`); - + await server.tool( tool.name, tool.description || '', cast(tool.inputSchema.properties), async (params: Record) => { console.log(`Calling tool: ${tool.name} with params: ${JSON.stringify(params)}`); - + const result = await serverInfo.client!.callTool({ name: tool.name, arguments: params, }); - + console.log(`Tool result: ${JSON.stringify(result)}`); return result as CallToolResult; }, ); } } catch (error) { - console.error(`Failed to connect to server for client: ${serverInfo.name} by error: ${error}`); + console.error( + `Failed to connect to server for client: ${serverInfo.name} by error: ${error}`, + ); serverInfo.status = 'disconnected'; } } @@ -143,25 +148,23 @@ export const getServersInfo = (): Omit[] => export const addServer = async ( mcpServer: McpServer, name: string, - config: ServerConfig + config: ServerConfig, ): Promise<{ success: boolean; message?: string }> => { try { const settings = loadSettings(); - + if (settings.mcpServers[name]) { return { success: false, message: 'Server name already exists' }; } - + settings.mcpServers[name] = config; - + if (!saveSettings(settings)) { return { success: false, message: 'Failed to save settings' }; } - - // Reinitialize clients and register tools - serverInfos = initializeClientsFromSettings(); - await registerAllTools(mcpServer); - + + registerAllTools(mcpServer); + return { success: true, message: 'Server added successfully' }; } catch (error) { console.error(`Failed to add server: ${name}`, error); @@ -173,27 +176,27 @@ export const addServer = async ( export const removeServer = (name: string): { success: boolean; message?: string } => { try { const settings = loadSettings(); - + if (!settings.mcpServers[name]) { return { success: false, message: 'Server not found' }; } - + delete settings.mcpServers[name]; - + if (!saveSettings(settings)) { return { success: false, message: 'Failed to save settings' }; } - + // Close existing connections const serverInfo = serverInfos.find((serverInfo) => serverInfo.name === name); if (serverInfo && serverInfo.client) { serverInfo.client.close(); serverInfo.transport?.close(); } - + // Remove from list serverInfos = serverInfos.filter((serverInfo) => serverInfo.name !== name); - + return { success: true, message: 'Server removed successfully' }; } catch (error) { console.error(`Failed to remove server: ${name}`, error); @@ -211,18 +214,18 @@ function cast(inputSchema: unknown): ZodRawShape { if (typeof inputSchema !== 'object' || inputSchema === null) { throw new Error('Invalid input schema'); } - + const properties = inputSchema as Record; const processedSchema: ZodRawShape = {}; - + for (const key in properties) { const prop = properties[key]; - + if (prop instanceof ZodType) { processedSchema[key] = prop.optional(); } else if (typeof prop === 'object' && prop !== null) { let zodType: ZodType; - + switch (prop.type) { case 'string': zodType = z.string(); @@ -245,14 +248,14 @@ function cast(inputSchema: unknown): ZodRawShape { default: zodType = z.any(); } - + if (prop.description) { zodType = zodType.describe(prop.description); } - + processedSchema[key] = zodType.optional(); } } - + return processedSchema; -} \ No newline at end of file +}