@l4t/mcp-ai
Version:
A set of tools for making integration and aggregation of MCP servers extremely easy.
116 lines • 4.27 kB
JavaScript
import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { McpClientConfigs } from '../../common/types.js';
import { create as createFeatures } from '../features.js';
const DEFAULT_PORT = 3000;
const BAD_REQUEST_STATUS = 400;
const NOT_FOUND_STATUS = 404;
const create = (config, options) => {
const app = express();
app.use(bodyParser.json(options?.jsonBodyParser));
app.use(cors());
options?.preRouteMiddleware?.forEach(middleware => app.use(middleware));
// Map to store transports by session ID
const transports = {};
const setupServer = async (features) => {
features.validateConfig();
const server = new McpServer(Object.assign({}, McpClientConfigs.aggregator, {
capabilities: { tools: config.tools },
name: config.name,
version: config.version,
}));
const formatted = features.getFormattedTools();
formatted.forEach(tool => {
//@ts-ignore
server.tool(...tool);
});
return server;
};
const handleRequest = (features) => async (req, res) => {
try {
const server = await setupServer(features);
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on('close', () => {
transport.close();
server.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
}
catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error',
},
id: null,
});
}
}
};
const _unhandledRequest = (req, res) => {
res.writeHead(405).end(JSON.stringify({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Method not allowed.',
},
id: null,
}));
};
const _routeWrapper = (func) => {
if (options?.afterRouteCallback) {
return async (req, res) => {
await func(req, res);
// @ts-ignore
await options.afterRouteCallback(req, res);
};
}
return func;
};
const getApp = async () => {
const features = createFeatures(config);
options?.additionalRoutes?.forEach(route => {
app[route.method.toLowerCase()](route.path, route.handler);
});
// Handle POST requests for client-to-server communication
app.post(config.server.path || '/', _routeWrapper(handleRequest(features)));
// Handle GET requests for server-to-client notifications
app.get(config.server.path || '/', _routeWrapper(_unhandledRequest));
// Handle DELETE requests for session termination
app.delete(config.server.path || '/', _routeWrapper(_unhandledRequest));
// Add catch-all route for non-existent URLs
app.use(_routeWrapper((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,
});
}));
return app;
};
return {
getApp,
set: (key, value) => {
app.set(key, value);
},
start: async () => {
const app = await getApp();
app.listen(config.server.connection.port || DEFAULT_PORT);
},
stop: async () => {
Object.values(transports).forEach(transport => transport.close());
},
};
};
export { create };
//# sourceMappingURL=stateless-http.js.map