Skip to main content
Glama

MCP Firebird

http-entry.ts14.9 kB
/** * HTTP Entry Point for MCP Firebird * * This entry point starts the MCP server in HTTP mode (Streamable HTTP protocol) * for deployment on platforms like Smithery, Railway, Render, etc. * * Configuration is passed via: * 1. Environment variables (standard Docker deployment) * 2. Query parameters (Smithery deployment) */ import express from 'express'; import cors from 'cors'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { createLogger } from './utils/logger.js'; import { createStreamableHttpRouter } from './server/streamable-http.js'; import { createSseRouter } from './server/sse.js'; import { setupDatabaseTools } from './tools/database.js'; import { setupMetadataTools } from './tools/metadata.js'; import { setupSimpleTools } from './tools/simple.js'; import { setupDatabasePrompts } from './prompts/database.js'; import { setupSqlPrompts } from './prompts/sql.js'; import { setupDatabaseResources } from './resources/database.js'; import { initSecurity } from './security/index.js'; import pkg from '../package.json' with { type: 'json' }; import { z } from 'zod'; import type { ToolDefinition as DbToolDefinition } from './tools/database.js'; import type { ToolDefinition as MetaToolDefinition } from './tools/metadata.js'; import type { ToolDefinition as SimpleToolDefinition } from './tools/simple.js'; const logger = createLogger('http-entry'); /** * Parse configuration from query parameters (Smithery format) * Smithery passes config as query params in dot-notation * Example: ?host=localhost&port=3050&database=/path/to/db.fdb */ function parseConfigFromQuery(query: any): void { if (query.host) { process.env.FIREBIRD_HOST = String(query.host); logger.info(`Config from query: FIREBIRD_HOST=${query.host}`); } if (query.port) { process.env.FIREBIRD_PORT = String(query.port); logger.info(`Config from query: FIREBIRD_PORT=${query.port}`); } if (query.database) { process.env.FIREBIRD_DATABASE = String(query.database); logger.info(`Config from query: FIREBIRD_DATABASE=${query.database}`); } if (query.user) { process.env.FIREBIRD_USER = String(query.user); logger.info(`Config from query: FIREBIRD_USER=${query.user}`); } if (query.password) { process.env.FIREBIRD_PASSWORD = String(query.password); logger.info('Config from query: FIREBIRD_PASSWORD=***'); } if (query.useNativeDriver !== undefined) { process.env.USE_NATIVE_DRIVER = String(query.useNativeDriver); logger.info(`Config from query: USE_NATIVE_DRIVER=${query.useNativeDriver}`); } if (query.logLevel) { process.env.LOG_LEVEL = String(query.logLevel); logger.info(`Config from query: LOG_LEVEL=${query.logLevel}`); } } /** * Main function to start the HTTP server */ async function startHttpServer() { logger.info(`Starting MCP Firebird HTTP Server v${pkg.version}`); logger.info(`Platform: ${process.platform}, Node.js: ${process.version}`); try { // Get port from environment (Smithery sets PORT variable) const port = parseInt(process.env.PORT || process.env.SSE_PORT || '3003', 10); logger.info(`Configuring HTTP server on port ${port}...`); // Create Express app const app = express(); // Enable CORS for web clients app.use(cors({ origin: '*', methods: ['GET', 'POST', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true })); // Parse JSON bodies app.use(express.json()); // Parse URL-encoded bodies (for query parameters) app.use(express.urlencoded({ extended: true })); // Middleware to parse config from query parameters (Smithery) app.use((req, res, next) => { if (Object.keys(req.query).length > 0) { logger.debug('Parsing configuration from query parameters...'); parseConfigFromQuery(req.query); } next(); }); // Root endpoint - health check app.get('/', (req, res) => { res.json({ name: pkg.name, version: pkg.version, status: 'running', protocol: 'Streamable HTTP', endpoints: { mcp: '/mcp', health: '/health', sse: '/sse' }, platform: process.platform, nodeVersion: process.version }); }); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', uptime: process.uptime(), memory: process.memoryUsage(), version: pkg.version }); }); // Create MCP server instance factory logger.info('Creating MCP server factory...'); const createServerInstance = async (): Promise<McpServer> => { logger.info('Creating new MCP server instance...'); // Initialize security await initSecurity(); // Create server with modern capabilities const server = new McpServer({ name: pkg.name, version: pkg.version, capabilities: { tools: { listChanged: true }, prompts: { listChanged: true }, resources: { listChanged: true, subscribe: false } } }); // Register tools const registerTool = (name: string, toolDef: DbToolDefinition | MetaToolDefinition | SimpleToolDefinition) => { const inputSchema = (toolDef.inputSchema && toolDef.inputSchema instanceof z.ZodObject) ? toolDef.inputSchema.shape : {}; server.registerTool( name, { title: toolDef.description || name, description: toolDef.description || name, inputSchema: inputSchema }, async (args: any): Promise<{ content: any[], isError?: boolean }> => { try { const result = await toolDef.handler(args); if (typeof result === 'object' && result !== null) { if ('success' in result && result.success === false) { return { content: [{ type: "text", text: JSON.stringify(result) }], isError: true }; } if ('content' in result && Array.isArray(result.content)) { return result; } } return { content: [{ type: "text", text: JSON.stringify(result) }] }; } catch (error) { const err = error as Error; logger.error(`Error executing tool ${name}:`, { error: err.message }); const message = err.message || 'Unknown error'; return { content: [{ type: "text", text: `Error: ${message}` }], isError: true }; } } ); }; // Setup all tools const dbTools = setupDatabaseTools(); for (const [name, toolDef] of dbTools.entries()) { registerTool(name, toolDef); } const metaTools = setupMetadataTools(dbTools); for (const [name, toolDef] of metaTools.entries()) { registerTool(name, toolDef); } const simpleTools = setupSimpleTools(); for (const [name, toolDef] of simpleTools.entries()) { registerTool(name, toolDef); } // Register prompts const dbPrompts = setupDatabasePrompts(); for (const [name, promptDef] of dbPrompts.entries()) { // Extract the shape from ZodObject if available const argsSchema = (promptDef.inputSchema && promptDef.inputSchema instanceof z.ZodObject) ? promptDef.inputSchema.shape : {}; server.registerPrompt( name, { title: promptDef.description || name, description: promptDef.description || name, argsSchema: argsSchema }, async (args: any) => { const result = await promptDef.handler(args); // Ensure messages have correct role types return { ...result, messages: result.messages.map((msg: any) => ({ ...msg, role: (msg.role === 'user' || msg.role === 'assistant') ? msg.role : 'assistant' })) }; } ); } const sqlPrompts = setupSqlPrompts(); for (const [name, promptDef] of sqlPrompts.entries()) { // Extract the shape from ZodObject if available const argsSchema = (promptDef.inputSchema && promptDef.inputSchema instanceof z.ZodObject) ? promptDef.inputSchema.shape : {}; server.registerPrompt( name, { title: promptDef.description || name, description: promptDef.description || name, argsSchema: argsSchema }, async (args: any) => { const result = await promptDef.handler(args); // Ensure messages have correct role types return { ...result, messages: result.messages.map((msg: any) => ({ ...msg, role: (msg.role === 'user' || msg.role === 'assistant') ? msg.role : 'assistant' })) }; } ); } // Register resources const resources = setupDatabaseResources(); for (const [uri, resourceDef] of resources.entries()) { server.registerResource( `resource-${uri}`, uri, { title: resourceDef.description || uri, description: resourceDef.description || '', mimeType: resourceDef.mimeType || 'application/json' }, async (resourceUri: URL) => { const result = await resourceDef.handler({}); return { contents: [{ uri: resourceUri.href, mimeType: resourceDef.mimeType || 'application/json', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2) }] }; } ); } logger.info('MCP server instance created successfully'); return server; }; // Create and mount Streamable HTTP router (modern MCP protocol) logger.info('Creating Streamable HTTP router...'); const streamableRouter = createStreamableHttpRouter(createServerInstance); app.use('/', streamableRouter); // Create and mount SSE router (legacy support) logger.info('Creating SSE router...'); const sseRouter = createSseRouter(createServerInstance); app.use('/', sseRouter); // Error handling middleware app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { logger.error('Express error:', { error: err.message, stack: err.stack, path: req.path, method: req.method }); if (!res.headersSent) { res.status(500).json({ error: 'Internal server error', message: err.message }); } }); // Start listening const server = app.listen(port, '0.0.0.0', () => { logger.info(`✅ MCP Firebird HTTP Server started successfully`); logger.info(`🌐 Listening on http://0.0.0.0:${port}`); logger.info(`📡 MCP endpoint: http://0.0.0.0:${port}/mcp`); logger.info(`❤️ Health check: http://0.0.0.0:${port}/health`); logger.info(''); logger.info('Server is ready to accept connections!'); }); // Graceful shutdown const shutdown = async () => { logger.info('Shutting down HTTP server...'); server.close(() => { logger.info('HTTP server closed'); process.exit(0); }); // Force shutdown after 10 seconds setTimeout(() => { logger.warn('Forcing shutdown...'); process.exit(1); }, 10000); }; process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown); } catch (error) { logger.error('Failed to start HTTP server:', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); process.exit(1); } } // Start the server startHttpServer().catch(error => { logger.error('Fatal error:', error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PuroDelphi/mcpFirebird'

If you have feedback or need assistance with the MCP directory API, please join our Discord server