feat(MCP Server Trigger Node): Cleanup MCP server management, use sanitized trigger node's name as name for the MCP server (#15751)

This commit is contained in:
Yiorgis Gozadinos
2025-05-29 15:07:17 +02:00
committed by GitHub
parent 1daf0ff169
commit 07a636eed6
4 changed files with 142 additions and 88 deletions

View File

@@ -38,8 +38,9 @@ function wasToolCall(body: string) {
}
/**
* Extracts the request ID from a JSONRPC message
* Returns undefined if the message doesn't have an ID or can't be parsed
* Extracts the request ID from a JSONRPC message (for example for tool calls).
* Returns undefined if the message doesn't have an ID (for example on a tool list request)
*
*/
function getRequestId(body: string): string | undefined {
try {
@@ -51,25 +52,56 @@ function getRequestId(body: string): string | undefined {
}
}
export class McpServer {
/**
* This singleton is shared across the instance, making sure it is the one
* keeping account of MCP servers.
* It needs to stay in memory to keep track of the long-lived connections.
* It requires a logger at first creation to set everything up.
*/
export class McpServerManager {
static #instance: McpServerManager;
servers: { [sessionId: string]: Server } = {};
transports: { [sessionId: string]: FlushingSSEServerTransport } = {};
logger: Logger;
private tools: { [sessionId: string]: Tool[] } = {};
private resolveFunctions: { [callId: string]: CallableFunction } = {};
constructor(logger: Logger) {
logger: Logger;
private constructor(logger: Logger) {
this.logger = logger;
this.logger.debug('MCP Server created');
}
async connectTransport(postUrl: string, resp: CompressionResponse): Promise<void> {
static instance(logger: Logger): McpServerManager {
if (!McpServerManager.#instance) {
McpServerManager.#instance = new McpServerManager(logger);
logger.debug('Created singleton MCP manager');
}
return McpServerManager.#instance;
}
async createServerAndTransport(
serverName: string,
postUrl: string,
resp: CompressionResponse,
): Promise<void> {
const transport = new FlushingSSEServerTransport(postUrl, resp);
const server = this.setUpServer();
const server = new Server(
{
name: serverName,
version: '0.1.0',
},
{
capabilities: { tools: {} },
},
);
this.setUpHandlers(server);
const { sessionId } = transport;
this.transports[sessionId] = transport;
this.servers[sessionId] = server;
@@ -123,17 +155,7 @@ export class McpServer {
return wasToolCall(req.rawBody.toString());
}
setUpServer(): Server {
const server = new Server(
{
name: 'n8n-mcp-server',
version: '0.1.0',
},
{
capabilities: { tools: {} },
},
);
setUpHandlers(server: Server) {
server.setRequestHandler(
ListToolsRequestSchema,
async (_, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
@@ -203,34 +225,5 @@ export class McpServer {
server.onerror = (error: unknown) => {
this.logger.error(`MCP Error: ${error}`);
};
return server;
}
}
/**
* This singleton is shared across the instance, making sure we only have one server to worry about.
* It needs to stay in memory to keep track of the long-lived connections.
* It requires a logger at first creation to set everything up.
*/
export class McpServerSingleton {
static #instance: McpServerSingleton;
private _serverData: McpServer;
private constructor(logger: Logger) {
this._serverData = new McpServer(logger);
}
static instance(logger: Logger): McpServer {
if (!McpServerSingleton.#instance) {
McpServerSingleton.#instance = new McpServerSingleton(logger);
logger.debug('Created singleton for MCP Servers');
}
return McpServerSingleton.#instance.serverData;
}
get serverData() {
return this._serverData;
}
}