UNPKG

@spryker-sdk/spryker-mcp-server

Version:

AI-powered e-commerce server that enables intelligent shopping assistants for Spryker Commerce OS platforms

179 lines 8.44 kB
/** * SSE MCP Server Implementation */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { createServer } from 'node:http'; import { URL } from 'node:url'; import { config } from '../config/index.js'; import { logger } from '../utils/logger.js'; import { validateEnvironment } from '../utils/validation.js'; export class SSEMCPServer { httpServer = null; options; registerHandlers; constructor(options, registerHandlers) { this.options = { transport: options.transport, httpPort: options.httpPort || config.mcp.http.port, httpHost: options.httpHost || config.mcp.http.host, httpEndpoint: options.httpEndpoint || config.mcp.http.endpoint, }; this.registerHandlers = registerHandlers; } async start() { logger.info('Starting Spryker MCP Server (SSE)...'); logger.info(`Transport: ${this.options.transport}`); try { // Validate environment configuration validateEnvironment(); // Initialize HTTP server for SSE connections await this.initializeHttpServer(); logger.info(`Spryker MCP Server started successfully`); logger.info(`Server Name: ${config.server.name}`); logger.info(`Server Version: ${config.server.version}`); logger.info(`API Base URL: ${config.api.baseUrl}`); logger.info(`HTTP Server: http://${this.options.httpHost}:${this.options.httpPort}`); logger.info(`MCP Endpoint: ${this.options.httpEndpoint}`); } catch (error) { logger.error('Failed to start SSE server:', error instanceof Error ? error : new Error(String(error))); throw error; } } async initializeHttpServer() { return new Promise((resolve, reject) => { this.httpServer = createServer((req, res) => { // Wrap the handler in a try-catch to prevent unhandled promise rejections this.handleHttpRequest(req, res).catch(error => { logger.error('HTTP request handler error:', error instanceof Error ? error : new Error(String(error))); // Only send error response if headers haven't been sent if (!res.headersSent) { try { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error', })); } catch (responseError) { logger.error('Failed to send error response:', responseError instanceof Error ? responseError : new Error(String(responseError))); } } }); }); this.httpServer.listen(this.options.httpPort, this.options.httpHost, () => { logger.info(`HTTP server listening on ${this.options.httpHost}:${this.options.httpPort}`); logger.info(`MCP endpoint available at: http://${this.options.httpHost}:${this.options.httpPort}${this.options.httpEndpoint}`); resolve(); }); this.httpServer.on('error', (error) => { logger.error('HTTP server error:', error); reject(error); }); }); } async handleHttpRequest(req, res) { try { // Enable CORS res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } const url = new URL(req.url || '/', `http://${req.headers.host}`); if (url.pathname === this.options.httpEndpoint) { if (req.method === 'GET') { // For SSE transport, create and manage the connection try { logger.info('Creating SSE connection for client'); // Create a new server instance for this SSE connection const sseServer = new Server({ name: config.server.name, version: config.server.version, }, { capabilities: { tools: {}, prompts: {}, logging: {}, }, }); // Register all handlers for this SSE server instance this.registerHandlers(sseServer); // Create SSE transport for this specific response const sseTransport = new SSEServerTransport(this.options.httpEndpoint, res); logger.info('Connecting SSE server to transport'); // Connect the SSE server to the transport await sseServer.connect(sseTransport); logger.info('SSE connection established and server connected'); // The response is now handled by the SSE transport // Don't call res.end() as the transport manages the response } catch (sseError) { logger.error('SSE connection failed:', sseError instanceof Error ? sseError : new Error(String(sseError))); if (!res.headersSent) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Failed to establish SSE connection' })); } } } else { res.writeHead(405, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Method not allowed' })); } } else if (url.pathname === '/health') { // Health check endpoint res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'healthy', transport: this.options.transport, timestamp: new Date().toISOString(), })); } else { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Not found' })); } } catch (error) { logger.error('Request handling error:', error instanceof Error ? error : new Error(String(error))); if (!res.headersSent) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error', })); } } } async shutdown() { logger.info('Shutting down SSE MCP Server...'); try { // Close HTTP server if it exists if (this.httpServer) { await new Promise((resolve, reject) => { this.httpServer.close((error) => { if (error) { reject(error); } else { resolve(); } }); }); logger.info('HTTP server stopped'); } logger.info('SSE server shutdown complete'); } catch (error) { logger.error('Error during SSE server shutdown:', error instanceof Error ? error : new Error(String(error))); } } } //# sourceMappingURL=sse-server.js.map