Skip to main content
Glama
McpServer.ts14.6 kB
/** * MCP Server implementation using @modelcontextprotocol/sdk * * Provides the foundational MCP server functionality with StdioServerTransport * for stdin/stdout communication. This class serves as the entry point for * the MCP server and handles basic server lifecycle operations. * * @example * ```typescript * const config = await ServerConfig.fromEnvironment() * const server = new McpServer(config) * await server.start() * ``` */ import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { CallToolRequestSchema, type CallToolResult, ListResourcesRequestSchema, type ListResourcesResult, ListToolsRequestSchema, type ListToolsResult, ReadResourceRequestSchema, type ReadResourceResult, } from '@modelcontextprotocol/sdk/types.js' import { AgentManager } from 'src/agents/AgentManager' import type { ServerConfig } from 'src/config/ServerConfig' import { AgentExecutor, createExecutionConfig } from 'src/execution/AgentExecutor' import { AgentResources } from 'src/resources/AgentResources' import { SessionManager } from 'src/session/SessionManager' import { RunAgentTool } from 'src/tools/RunAgentTool' import { AppError, ValidationError } from 'src/utils/ErrorHandler' import { Logger } from 'src/utils/Logger' /** * Server information interface for MCP server identification */ interface ServerInfo { name: string version: string } /** * Logger function type for structured logging */ type LogLevel = 'debug' | 'info' | 'warn' | 'error' /** * MCP Server class providing foundational server functionality */ export class McpServer { private server: Server private transport: StdioServerTransport | null = null private config: ServerConfig private agentManager: AgentManager private agentExecutor: AgentExecutor private runAgentTool: RunAgentTool private agentResources: AgentResources private sessionManager?: SessionManager /** * Create a new MCP server instance * @param config Server configuration object * @throws {Error} When server name is empty */ constructor(config: ServerConfig) { this.validateConfig(config) this.config = config this.log('info', 'Initializing MCP server', { name: config.serverName, version: config.serverVersion, }) // Initialize agent management components this.agentManager = new AgentManager(config) // Create ExecutionConfig with the agent type from server config const executionConfig = createExecutionConfig(config.agentType, { executionTimeout: config.executionTimeoutMs, // Use timeout from config (env var or 90s default) }) // Create logger with log level from config const executorLogger = new Logger(config.logLevel) this.agentExecutor = new AgentExecutor(executionConfig, executorLogger) // Initialize SessionManager if session management is enabled if (config.sessionEnabled) { this.sessionManager = new SessionManager({ enabled: config.sessionEnabled, sessionDir: config.sessionDir, retentionDays: config.sessionRetentionDays, }) this.log('info', 'Session management enabled', { sessionDir: config.sessionDir, retentionDays: config.sessionRetentionDays, }) } else { this.log('info', 'Session management disabled') } this.runAgentTool = new RunAgentTool(this.agentExecutor, this.agentManager, this.sessionManager) this.agentResources = new AgentResources(this.agentManager) // Initialize MCP server with capabilities this.server = new Server( { name: config.serverName, version: config.serverVersion, }, { capabilities: { tools: {}, resources: {}, }, } ) // Setup StdioServerTransport for stdin/stdout communication this.setupTransport() // Setup MCP handlers this.setupHandlers() this.log('info', 'MCP server initialized successfully') } /** * Validate server configuration * @param config Configuration to validate * @throws {Error} When configuration is invalid */ private validateConfig(config: ServerConfig): void { if (!config.serverName || config.serverName.trim() === '') { throw new Error('Server name cannot be empty') } if (!config.serverVersion || config.serverVersion.trim() === '') { throw new Error('Server version cannot be empty') } } /** * Log message with structured format * @param level Log level * @param message Log message * @param metadata Additional metadata */ private log(level: LogLevel, message: string, metadata?: Record<string, unknown>): void { const logLevels: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3, } if (logLevels[level] >= logLevels[this.config.logLevel]) { const timestamp = new Date().toISOString() const logEntry = { timestamp, level, message, service: 'mcp-server', ...metadata, } console.error(JSON.stringify(logEntry)) } } /** * Setup StdioServerTransport for MCP communication */ private setupTransport(): void { try { this.transport = new StdioServerTransport() this.log('debug', 'StdioServerTransport configured successfully') } catch (error) { this.log('error', 'Failed to setup transport', { error: String(error) }) throw new AppError('Failed to setup MCP transport', 'TRANSPORT_SETUP_FAILED') } } /** * Setup MCP protocol handlers with performance monitoring */ private setupHandlers(): void { try { // List tools handler this.server.setRequestHandler(ListToolsRequestSchema, async (): Promise<ListToolsResult> => { const startTime = Date.now() this.log('debug', 'Received list_tools request') try { const result: ListToolsResult = { tools: [ { name: this.runAgentTool.name, description: this.runAgentTool.description, inputSchema: this.runAgentTool.inputSchema, }, ], } this.log('debug', 'List tools request completed', { responseTime: Date.now() - startTime, toolCount: result.tools.length, }) return result } catch (error) { this.log('error', 'List tools request failed', { responseTime: Date.now() - startTime, error: error instanceof Error ? error.message : String(error), }) throw error } }) // Call tool handler this.server.setRequestHandler( CallToolRequestSchema, async (request): Promise<CallToolResult> => { const startTime = Date.now() const { params } = request this.log('debug', 'Received call_tool request', { tool: params.name }) try { if (params.name === 'run_agent') { const result = await this.runAgentTool.execute(params.arguments) this.log('info', 'Tool execution completed', { tool: params.name, responseTime: Date.now() - startTime, success: true, }) return result as CallToolResult } throw new ValidationError(`Unknown tool: ${params.name}`, 'UNKNOWN_TOOL') } catch (error) { this.log('error', 'Tool execution failed', { tool: params.name, responseTime: Date.now() - startTime, error: error instanceof Error ? error.message : String(error), }) throw error } } ) // List resources handler this.server.setRequestHandler( ListResourcesRequestSchema, async (): Promise<ListResourcesResult> => { const startTime = Date.now() this.log('debug', 'Received list_resources request') try { const resources = await this.agentResources.listResources() this.log('debug', 'List resources request completed', { responseTime: Date.now() - startTime, resourceCount: resources.length, }) return { resources } } catch (error) { this.log('error', 'List resources request failed', { responseTime: Date.now() - startTime, error: error instanceof Error ? error.message : String(error), }) throw error } } ) // Read resource handler this.server.setRequestHandler( ReadResourceRequestSchema, async (request): Promise<ReadResourceResult> => { const startTime = Date.now() const { params } = request this.log('debug', 'Received read_resource request', { uri: params.uri }) try { if (!this.agentResources.isValidResourceUri(params.uri)) { throw new ValidationError( `Invalid resource URI: ${params.uri}`, 'INVALID_RESOURCE_URI' ) } const result = await this.agentResources.readResource(params.uri) this.log('debug', 'Read resource request completed', { uri: params.uri, responseTime: Date.now() - startTime, contentLength: result.contents[0]?.text?.length || 0, }) return result as unknown as ReadResourceResult } catch (error) { this.log('error', 'Read resource request failed', { uri: params.uri, responseTime: Date.now() - startTime, error: error instanceof Error ? error.message : String(error), }) throw error } } ) this.log('debug', 'MCP handlers configured successfully') } catch (error) { this.log('error', 'Failed to setup MCP handlers', { error: String(error) }) throw new AppError('Failed to setup MCP handlers', 'HANDLERS_SETUP_FAILED') } } /** * Get server information * @returns Server name and version */ getServerInfo(): ServerInfo { return { name: this.config.serverName, version: this.config.serverVersion, } } /** * Check if transport is configured * @returns True if transport is available */ hasTransport(): boolean { return this.transport !== null } /** * Check if server is ready to start * @returns True if server is ready */ isReady(): boolean { return this.hasTransport() && this.server !== null } /** * Start the MCP server * @throws {Error} When server is not ready or transport setup fails */ async start(): Promise<void> { try { if (!this.isReady()) { throw new AppError('Server is not ready to start', 'SERVER_NOT_READY') } if (!this.transport) { throw new AppError('Transport not configured', 'TRANSPORT_NOT_CONFIGURED') } this.log('info', 'Starting MCP server...') // Connect server to transport await this.server.connect(this.transport) this.log('info', 'MCP server started successfully', { serverName: this.config.serverName, serverVersion: this.config.serverVersion, }) } catch (error) { this.log('error', 'Failed to start MCP server', { error: String(error) }) throw error instanceof AppError ? error : new AppError('Failed to start MCP server', 'SERVER_START_FAILED') } } /** * List available tools (for testing) * @returns Promise resolving to array of tool definitions */ async listTools(): Promise<Array<{ name: string; description: string; inputSchema: unknown }>> { return [ { name: this.runAgentTool.name, description: this.runAgentTool.description, inputSchema: this.runAgentTool.inputSchema, }, ] } /** * List available resources (for testing) * @returns Promise resolving to array of resource definitions */ async listResources(): Promise<Array<{ uri: string; name: string; description: string }>> { const resources = await this.agentResources.listResources() return resources.map((resource) => ({ uri: resource.uri, name: resource.name, description: resource.description, })) } /** * Call a tool (for testing) * @param toolName - Name of the tool to call * @param params - Tool parameters * @returns Promise resolving to tool response */ async callTool(toolName: string, params: unknown): Promise<unknown> { if (toolName === 'run_agent') { return await this.runAgentTool.execute(params) } throw new ValidationError(`Unknown tool: ${toolName}`, 'UNKNOWN_TOOL') } /** * Read a resource (for testing) * @param uri - Resource URI to read * @returns Promise resolving to resource content */ async readResource(uri: string): Promise<unknown> { if (!this.agentResources.isValidResourceUri(uri)) { throw new ValidationError(`Invalid resource URI: ${uri}`, 'INVALID_RESOURCE_URI') } return await this.agentResources.readResource(uri) } /** * Get server performance statistics * * @returns Performance and usage statistics */ getServerStats(): { serverInfo: { name: string; version: string } executionStats: Map<string, { count: number; totalTime: number; lastUsed: Date }> } { return { serverInfo: this.getServerInfo(), executionStats: this.runAgentTool.getExecutionStats(), } } /** * Reset statistics */ resetStats(): void { this.log('info', 'Server statistics reset') } /** * Close the server and cleanup resources */ async close(): Promise<void> { try { this.log('info', 'Shutting down MCP server...') // Log final statistics before shutdown const stats = this.getServerStats() this.log('info', 'Final server statistics', { executionCount: Array.from(stats.executionStats.values()).reduce( (sum, stat) => sum + stat.count, 0 ), }) if (this.server) { await this.server.close() } this.log('info', 'MCP server shutdown completed') } catch (error) { this.log('error', 'Error during server shutdown', { error: String(error) }) throw new AppError('Failed to shutdown MCP server gracefully', 'SERVER_SHUTDOWN_FAILED') } } }

Implementation Reference

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/shinpr/sub-agents-mcp'

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