UNPKG

auto-publishing-mcp-server

Version:

Enterprise-grade MCP Server for Auto-Publishing with pre-publish validation, multi-cloud deployment, and monitoring

531 lines (456 loc) 13.2 kB
/** * MCP Protocol Handler * Core MCP protocol implementation for auto-publishing server */ import { v4 as uuidv4 } from 'uuid'; import { getServerCapabilities, validateClientCapabilities, negotiateCapabilities, SERVER_INFO } from './capabilities.js'; import { ErrorCreators } from '../utils/errors.js'; import { validateInitializeParams, validateToolCallParams } from '../utils/validation.js'; export class McpHandler { constructor() { this.sessions = new Map(); this.tools = new Map(); this.resources = new Map(); this.prompts = new Map(); this.subscriptions = new Map(); this.isInitialized = false; // Initialize built-in tools this.initializeBuiltinTools(); this.initializeBuiltinResources(); this.initializeBuiltinPrompts(); } /** * Handles MCP initialize request */ async handleInitialize(params, message) { try { validateInitializeParams(params); const sessionId = message.connectionId || uuidv4(); // Validate client capabilities const negotiation = negotiateCapabilities(params); if (!negotiation.validation.compatible) { throw ErrorCreators.invalidParams({ message: "Client capabilities not compatible", issues: negotiation.validation.issues }); } // Create session const session = { id: sessionId, clientInfo: params.clientInfo, capabilities: negotiation.negotiated, initialized: true, createdAt: new Date().toISOString(), lastActivity: new Date().toISOString() }; this.sessions.set(sessionId, session); this.isInitialized = true; console.log(`MCP session initialized: ${sessionId}`, { client: params.clientInfo.name, version: params.clientInfo.version, warnings: negotiation.validation.warnings }); // Log warnings if any if (negotiation.validation.warnings.length > 0) { console.warn('Client capability warnings:', negotiation.validation.warnings); } return { protocolVersion: "1.0", capabilities: getServerCapabilities(), serverInfo: SERVER_INFO, sessionId: sessionId, negotiated: negotiation.negotiated }; } catch (error) { console.error('Initialize error:', error); throw error; } } /** * Handles tools/list request */ async handleToolsList(params, message) { this.validateSession(message); const tools = Array.from(this.tools.values()).map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema || {} })); return { tools }; } /** * Handles tools/call request */ async handleToolCall(params, message) { this.validateSession(message); validateToolCallParams(params); const toolName = params.name; const toolArgs = params.arguments || {}; if (!this.tools.has(toolName)) { throw ErrorCreators.toolNotFound(toolName); } const tool = this.tools.get(toolName); try { // Validate tool input if schema is provided if (tool.inputSchema) { // This would be validated by the validation middleware } // Add execution context const context = { sessionId: message.connectionId, requestId: message.id, timestamp: new Date().toISOString(), toolName: toolName }; // Execute tool console.log(`Executing tool: ${toolName}`, { args: toolArgs, context }); const result = await tool.handler(toolArgs, context); // Update session activity this.updateSessionActivity(message.connectionId); return { content: [ { type: "text", text: result.output || result.message || "Tool executed successfully" } ], isError: false, toolResult: result }; } catch (error) { console.error(`Tool execution error for ${toolName}:`, error); throw ErrorCreators.toolExecutionError(toolName, error); } } /** * Handles resources/list request */ async handleResourcesList(params, message) { this.validateSession(message); const resources = Array.from(this.resources.values()).map(resource => ({ uri: resource.uri, name: resource.name, description: resource.description, mimeType: resource.mimeType })); return { resources }; } /** * Handles resources/read request */ async handleResourceRead(params, message) { this.validateSession(message); const uri = params.uri; if (!uri) { throw ErrorCreators.invalidParams({ message: "URI is required" }); } const resource = Array.from(this.resources.values()).find(r => r.uri === uri); if (!resource) { throw ErrorCreators.resourceNotFound(uri); } try { const content = await resource.handler(params); this.updateSessionActivity(message.connectionId); return { contents: [ { uri: uri, mimeType: resource.mimeType, text: content } ] }; } catch (error) { console.error(`Resource read error for ${uri}:`, error); throw ErrorCreators.resourceAccessError(uri, error); } } /** * Handles prompts/list request */ async handlePromptsList(params, message) { this.validateSession(message); const prompts = Array.from(this.prompts.values()).map(prompt => ({ name: prompt.name, description: prompt.description, arguments: prompt.arguments || [] })); return { prompts }; } /** * Handles prompts/get request */ async handlePromptGet(params, message) { this.validateSession(message); const promptName = params.name; if (!this.prompts.has(promptName)) { throw ErrorCreators.invalidParams({ message: `Prompt '${promptName}' not found` }); } const prompt = this.prompts.get(promptName); try { const content = await prompt.handler(params.arguments || {}); this.updateSessionActivity(message.connectionId); return { description: prompt.description, messages: [ { role: "user", content: { type: "text", text: content } } ] }; } catch (error) { console.error(`Prompt generation error for ${promptName}:`, error); throw ErrorCreators.internalError({ message: `Failed to generate prompt: ${error.message}` }); } } /** * Registers a new tool */ registerTool(name, description, handler, inputSchema = null) { const tool = { name, description, handler, inputSchema, registeredAt: new Date().toISOString() }; this.tools.set(name, tool); console.log(`Tool registered: ${name}`); // Notify subscribed clients about tool changes this.notifyToolsChanged(); } /** * Registers a new resource */ registerResource(uri, name, description, handler, mimeType = "text/plain") { const resource = { uri, name, description, handler, mimeType, registeredAt: new Date().toISOString() }; this.resources.set(uri, resource); console.log(`Resource registered: ${uri}`); // Notify subscribed clients about resource changes this.notifyResourcesChanged(); } /** * Registers a new prompt */ registerPrompt(name, description, handler, argumentSchema = []) { const prompt = { name, description, handler, arguments: argumentSchema, registeredAt: new Date().toISOString() }; this.prompts.set(name, prompt); console.log(`Prompt registered: ${name}`); } /** * Validates that a session exists and is valid */ validateSession(message) { const sessionId = message.connectionId; if (!sessionId) { throw ErrorCreators.authenticationError(); } const session = this.sessions.get(sessionId); if (!session || !session.initialized) { throw ErrorCreators.authenticationError(); } return session; } /** * Updates session activity timestamp */ updateSessionActivity(sessionId) { const session = this.sessions.get(sessionId); if (session) { session.lastActivity = new Date().toISOString(); } } /** * Gets session information */ getSessionInfo(sessionId) { return this.sessions.get(sessionId) || null; } /** * Gets all active sessions */ getActiveSessions() { return Array.from(this.sessions.values()); } /** * Removes a session */ removeSession(sessionId) { const removed = this.sessions.delete(sessionId); if (removed) { console.log(`Session removed: ${sessionId}`); } return removed; } /** * Notifies clients about tool changes */ async notifyToolsChanged() { // This would be implemented with the notification system console.log('Tools list changed notification would be sent'); } /** * Notifies clients about resource changes */ async notifyResourcesChanged() { // This would be implemented with the notification system console.log('Resources list changed notification would be sent'); } /** * Initializes built-in tools */ initializeBuiltinTools() { // Built-in system tools this.registerTool( "system/info", "Get system information and server status", async (args, context) => { return { output: "System information retrieved", data: { serverInfo: SERVER_INFO, uptime: process.uptime(), memory: process.memoryUsage(), activeSessions: this.sessions.size, toolsCount: this.tools.size, resourcesCount: this.resources.size } }; }, { type: "object", properties: { detailed: { type: "boolean", default: false } } } ); this.registerTool( "system/health", "Check system health status", async (args, context) => { return { output: "System health check completed", data: { status: "healthy", timestamp: new Date().toISOString(), checks: { memory: process.memoryUsage().heapUsed < 500 * 1024 * 1024, // 500MB sessions: this.sessions.size < 100, tools: this.tools.size > 0 } } }; } ); } /** * Initializes built-in resources */ initializeBuiltinResources() { this.registerResource( "system://server-info", "Server Information", "Current server status and configuration", async (params) => { return JSON.stringify({ serverInfo: SERVER_INFO, capabilities: getServerCapabilities(), activeSessions: this.sessions.size, registeredTools: Array.from(this.tools.keys()), registeredResources: Array.from(this.resources.keys()) }, null, 2); }, "application/json" ); this.registerResource( "system://logs", "System Logs", "Recent system log entries", async (params) => { // This would integrate with actual logging system return "System log entries would be retrieved here"; }, "text/plain" ); } /** * Initializes built-in prompts */ initializeBuiltinPrompts() { this.registerPrompt( "system/status", "Generate a system status report", async (args) => { const sessions = this.getActiveSessions(); const toolCount = this.tools.size; const resourceCount = this.resources.size; return `Please provide a comprehensive status report for the Auto-Publishing MCP Server: Current Status: - Active Sessions: ${sessions.length} - Registered Tools: ${toolCount} - Registered Resources: ${resourceCount} - Server Uptime: ${Math.floor(process.uptime())} seconds Please analyze the current system health, active connections, and suggest any optimizations or actions needed.`; }, [] ); } /** * Gets comprehensive server statistics */ getStats() { return { sessions: { active: this.sessions.size, list: Array.from(this.sessions.values()).map(s => ({ id: s.id, client: s.clientInfo?.name, createdAt: s.createdAt, lastActivity: s.lastActivity })) }, tools: { count: this.tools.size, list: Array.from(this.tools.keys()) }, resources: { count: this.resources.size, list: Array.from(this.resources.keys()) }, prompts: { count: this.prompts.size, list: Array.from(this.prompts.keys()) }, server: { initialized: this.isInitialized, uptime: process.uptime(), memory: process.memoryUsage() } }; } } export default McpHandler;