Skip to main content
Glama
server-manager.tsโ€ข10.3 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { Logger } from './logger.js'; import { LibSQLConnectionPool } from './database.js'; import { ToolRegistry } from './base-tool.js'; import type { DatabaseConfig } from '../types/index.js'; const logger = new Logger(); export interface ServerManagerOptions { config: DatabaseConfig; enableHotReload?: boolean; developmentMode?: boolean; } export class ServerManager { private server: Server | null = null; private pool: LibSQLConnectionPool | null = null; private toolRegistry: ToolRegistry | null = null; private transport: StdioServerTransport | null = null; private isRunning = false; private isShuttingDown = false; constructor(private options: ServerManagerOptions) {} async start(): Promise<void> { if (this.isRunning) { throw new Error('Server is already running'); } if (this.isShuttingDown) { throw new Error('Server is shutting down, cannot start'); } try { const mode = this.options.developmentMode ? 'development' : 'production'; logger.info(`Starting server manager in ${mode} mode`); if (this.options.developmentMode) { logger.info('Development mode features enabled:', { enhancedLogging: true, hotReload: this.options.enableHotReload || false, nodeEnv: process.env['NODE_ENV'] || 'development' }); } // Initialize database connection pool this.pool = new LibSQLConnectionPool(this.options.config); await this.pool.initialize(); // Create and register tools this.toolRegistry = await this.createToolRegistry(); // Create and configure MCP server this.server = await this.createMCPServer(); // Set up transport and connect this.transport = new StdioServerTransport(); // Add error handling with more detailed logging this.server.onerror = (error): void => { logger.error('MCP Server error', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, errorType: typeof error }); }; await this.server.connect(this.transport); this.isRunning = true; logger.info('Server manager started successfully'); if (this.options.developmentMode) { this.setupDevelopmentFeatures(); } } catch (error) { logger.error('Failed to start server manager', { error: error instanceof Error ? error.message : String(error) }); // Clean up on failure await this.cleanup(); throw error; } } async stop(): Promise<void> { if (!this.isRunning) { logger.warn('Server is not running, nothing to stop'); return; } if (this.isShuttingDown) { logger.warn('Server is already shutting down'); return; } this.isShuttingDown = true; logger.info('Stopping server manager'); await this.cleanup(); this.isRunning = false; this.isShuttingDown = false; logger.info('Server manager stopped successfully'); } async reload(newConfig?: DatabaseConfig): Promise<void> { if (!this.isRunning) { throw new Error('Cannot reload: server is not running'); } logger.info('Reloading server manager'); // Update config if provided if (newConfig) { this.options.config = newConfig; logger.info('Updated configuration', { config: newConfig }); } // Stop current server await this.stop(); // Start with new configuration await this.start(); logger.info('Server manager reloaded successfully'); } isServerRunning(): boolean { return this.isRunning; } getStatus(): { running: boolean; shuttingDown: boolean; poolConnections?: number; registeredTools?: number; } { // eslint-disable-next-line @typescript-eslint/no-explicit-any const status: any = { running: this.isRunning, shuttingDown: this.isShuttingDown }; const poolConnections = this.pool?.getStatus?.()?.totalConnections; if (poolConnections !== undefined) { status.poolConnections = poolConnections; } const registeredTools = this.toolRegistry?.getAll().length; if (registeredTools !== undefined) { status.registeredTools = registeredTools; } return status; } private setupDevelopmentFeatures(): void { logger.info('Setting up development features'); // Set up periodic status logging in development mode const statusInterval = setInterval(() => { if (this.isRunning && !this.isShuttingDown) { const status = this.getStatus(); logger.info('Development status check', status); } else { clearInterval(statusInterval); } }, 30000); // Log status every 30 seconds // Log tool registration details if (this.toolRegistry) { const tools = this.toolRegistry.getAll(); logger.info('Registered tools in development mode:', { count: tools.length, tools: tools.map(tool => tool.name) }); } // Log pool status if (this.pool) { const poolStatus = this.pool.getStatus(); logger.info('Connection pool status in development mode:', poolStatus); } } private async createToolRegistry(): Promise<ToolRegistry> { const { ToolRegistry } = await import('./base-tool.js'); const { ReadQueryTool } = await import('../tools/read-query.js'); const { WriteQueryTool } = await import('../tools/write-query.js'); const { CreateTableTool } = await import('../tools/create-table.js'); const { AlterTableTool } = await import('../tools/alter-table.js'); const { ListTablesTool } = await import('../tools/list-tables.js'); const { DescribeTableTool } = await import('../tools/describe-table.js'); const registry = new ToolRegistry(); registry.register(new ReadQueryTool()); registry.register(new WriteQueryTool()); registry.register(new CreateTableTool()); registry.register(new AlterTableTool()); registry.register(new ListTablesTool()); registry.register(new DescribeTableTool()); return registry; } private async createMCPServer(): Promise<Server> { if (!this.pool || !this.toolRegistry) { throw new Error('Pool and tool registry must be initialized first'); } const { Server } = await import('@modelcontextprotocol/sdk/server/index.js'); const { CallToolRequestSchema, ListToolsRequestSchema } = await import( '@modelcontextprotocol/sdk/types.js' ); const server = new Server( { name: 'xexr-libsql', version: '1.0.0' }, { capabilities: { tools: {} } } ); // Set up handlers server.setRequestHandler(ListToolsRequestSchema, async () => { try { logger.info('Handling list tools request'); if (!this.toolRegistry) { throw new Error('Tool registry not initialized'); } const tools = this.toolRegistry.getToolDefinitions(); logger.info(`Returning ${tools.length} tool definitions`); return { tools }; } catch (error) { logger.error('Failed to list tools', { error: error instanceof Error ? error.message : String(error) }); throw error; } }); server.setRequestHandler(CallToolRequestSchema, async request => { const { name, arguments: args } = request.params; // eslint-disable-next-line @typescript-eslint/no-explicit-any let connection: any = null; try { logger.info(`Handling tool request: ${name}`, { arguments: args }); if (!this.pool) { throw new Error('Database pool not initialized'); } if (!this.toolRegistry) { throw new Error('Tool registry not initialized'); } connection = await this.pool.getConnection(); const result = await this.toolRegistry.execute(name, { connection, arguments: args || {} }); logger.info(`Tool executed successfully: ${name}`, { hasError: result.isError || false }); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Tool execution failed: ${name}`, { error: errorMessage, arguments: args }); return { content: [ { type: 'text', text: `Error: Failed to execute tool ${name}: ${errorMessage}` } ], isError: true }; } finally { if (connection) { try { if (this.pool) { this.pool.releaseConnection(connection); } } catch (releaseError) { logger.error('Failed to release connection', { error: releaseError instanceof Error ? releaseError.message : String(releaseError) }); } } } }); return server; } private async cleanup(): Promise<void> { const errors: string[] = []; // Close server if (this.server) { try { await this.server.close(); logger.info('MCP server closed'); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); errors.push(`Error closing MCP server: ${errorMsg}`); logger.error('Error closing MCP server', { error: errorMsg }); } this.server = null; } // Close database pool if (this.pool) { try { await this.pool.close(); logger.info('Database pool closed'); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); errors.push(`Error closing database pool: ${errorMsg}`); logger.error('Error closing database pool', { error: errorMsg }); } this.pool = null; } // Clear references this.toolRegistry = null; this.transport = null; if (errors.length > 0) { throw new Error(`Cleanup completed with errors: ${errors.join(', ')}`); } } }

Latest Blog Posts

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/Xexr/mcp-libsql'

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