NestJS MCP Server Module
by rekog-labs
import { Body, Controller, Get, Inject, Post, Req, Res } from '@nestjs/common';
import type { Request, Response } from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { McpOptions } from '../interfaces';
import { McpToolsDiscovery } from '../services/mcp-tools.discovery';
export function createSseController(
sseEndpoint: string,
messagesEndpoint: string,
globalApiPrefix: string,
) {
@Controller()
class SseController {
public transports = new Map<string, SSEServerTransport>();
constructor(
@Inject('MCP_OPTIONS') public readonly options: McpOptions,
public readonly mcpToolsDiscovery: McpToolsDiscovery,
) {}
@Get(sseEndpoint)
async sse(@Res() res: Response) {
const transport = new SSEServerTransport(`${globalApiPrefix}/${messagesEndpoint}`, res);
const sessionId = transport.sessionId;
const mcpServer = new McpServer(
{ name: this.options.name, version: this.options.version },
{ capabilities: this.options.capabilities || { tools: {} } },
);
// Register tools (this will now also register tool-specific request handlers)
this.mcpToolsDiscovery.registerTools(mcpServer);
this.transports.set(sessionId, transport);
transport.onclose = () => {
this.transports.delete(sessionId);
};
await mcpServer.connect(transport);
}
@Post(messagesEndpoint)
async messages(@Req() req: Request, @Res() res: Response, @Body() body: unknown) {
const sessionId = req.query.sessionId as string;
const transport = this.transports.get(sessionId);
if (!transport) {
return res.status(404).send('Session not found');
}
await transport.handlePostMessage(req, res, body);
}
}
return SseController;
}