UNPKG

@l4t/mcp-ai

Version:

A set of tools for making integration and aggregation of MCP servers extremely easy.

124 lines 4.79 kB
import express from 'express'; import cors from 'cors'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { McpClientConfigs, } from '../../common/types.js'; import { create as createFeatures } from '../features.js'; import { openApiToZodSchema } from '../../common/libs.js'; const HTTP_ERROR = 500; const BAD_REQUEST = 400; const NOT_FOUND_STATUS = 404; const DEFAULT_PORT = 3000; const create = (config, options) => { // eslint-disable-next-line functional/no-let let server; const transports = {}; const app = express(); app.use(express.json()); app.use(cors()); options?.preRouteMiddleware?.forEach(middleware => app.use(middleware)); const handleSseConnection = async (res) => { // eslint-disable-next-line functional/no-try-statements try { const transport = new SSEServerTransport('/messages', res); const sessionId = transport.sessionId; // eslint-disable-next-line functional/immutable-data transports[sessionId] = transport; // eslint-disable-next-line functional/immutable-data transport.onclose = () => { // eslint-disable-next-line functional/immutable-data delete transports[sessionId]; }; if (server) { await server.connect(transport); } return sessionId; } catch { if (!res.headersSent) { res.status(HTTP_ERROR).send('Error establishing SSE stream'); } throw new Error('Failed to establish SSE connection'); } }; const handlePostMessage = async (req, res) => { const sessionId = req.query.sessionId; if (!sessionId || typeof sessionId !== 'string') { res.status(BAD_REQUEST).send('Missing sessionId parameter'); return; } const transport = transports[sessionId]; if (!transport) { res.status(NOT_FOUND_STATUS).send('Session not found'); return; } // eslint-disable-next-line functional/no-try-statements try { await transport.handlePostMessage(req, res, req.body); } catch { if (!res.headersSent) { res.status(HTTP_ERROR).send('Error handling request'); } } }; const setupServer = async (features) => { const rawTools = await features.getTools(); const tools = rawTools.map(tool => ({ name: tool.name, description: tool.description || '', parameters: tool.inputSchema, })); server = new McpServer(Object.assign({}, McpClientConfigs.aggregator, { capabilities: { tools }, })); tools.forEach(tool => { const schema = openApiToZodSchema(tool.parameters); // @ts-ignore server.tool(tool.name, tool.description || '', schema, async (extra) => { const results = await features.executeTool(tool.name, extra); return results; }); }); }; return { start: async (port = DEFAULT_PORT) => { const features = await createFeatures(config); await features.connect(); await setupServer(features); const path = config.server.path || '/'; const messagesPath = config.server.messagesPath || '/messages'; options?.additionalRoutes?.forEach(route => { app[route.method](route.path, route.handler); }); app.get(path, async (req, res) => { // eslint-disable-next-line functional/no-try-statements try { await handleSseConnection(res); } catch { // Error already handled in handleSseConnection } }); app.post(messagesPath, handlePostMessage); // Add catch-all route for non-existent URLs app.use((req, res) => { res.status(NOT_FOUND_STATUS).json({ error: 'Not Found', message: `The requested URL ${req.url} was not found on this server`, status: NOT_FOUND_STATUS, }); }); app.listen(port); }, stop: async () => { await Promise.all(Object.values(transports).map(transport => transport.close())); await server?.close(); }, set: (key, value) => { app.set(key, value); }, }; }; export { create }; //# sourceMappingURL=sse.js.map