mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-02 01:37:07 +02:00
Some checks are pending
Build: Benchmark Image / build (push) Waiting to run
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (22.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (24.14.1) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (25.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Lint (push) Waiting to run
CI: Master (Build, Test, Lint) / Performance (push) Waiting to run
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Blocked by required conditions
Util: Sync API Docs / sync-public-api (push) Waiting to run
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> Co-authored-by: Albert Alises <albert.alises@gmail.com> Co-authored-by: Jaakko Husso <jaakko@n8n.io> Co-authored-by: Dimitri Lavrenük <20122620+dlavrenuek@users.noreply.github.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Tuukka Kantola <Tuukkaa@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Co-authored-by: Raúl Gómez Morales <raul00gm@gmail.com> Co-authored-by: Elias Meire <elias@meire.dev> Co-authored-by: Dimitri Lavrenük <dimitri.lavrenuek@n8n.io> Co-authored-by: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Co-authored-by: Mutasem Aldmour <mutasem@n8n.io>
123 lines
3.7 KiB
JavaScript
123 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
import { randomUUID } from 'node:crypto';
|
|
import { createServer } from 'node:http';
|
|
|
|
import { parseServerOptions } from './server-config';
|
|
import { createBrowserTools } from './tools/index';
|
|
|
|
function registerTools(server: McpServer, tools: ReturnType<typeof createBrowserTools>['tools']) {
|
|
for (const tool of tools) {
|
|
server.registerTool(
|
|
tool.name,
|
|
{
|
|
description: tool.description,
|
|
inputSchema: tool.inputSchema,
|
|
outputSchema: tool.outputSchema,
|
|
},
|
|
async (args) => {
|
|
const result = await tool.execute(args as Record<string, unknown>, { dir: '' });
|
|
// Spread to satisfy SDK's index-signature requirement
|
|
return { ...result };
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const { config, transport: transportType, port } = parseServerOptions();
|
|
const { tools, connection } = createBrowserTools(config);
|
|
|
|
if (transportType === 'http') {
|
|
const sessions = new Map<string, StreamableHTTPServerTransport>();
|
|
|
|
/* eslint-disable @typescript-eslint/naming-convention -- HTTP header names */
|
|
const corsHeaders: Record<string, string> = {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version',
|
|
'Access-Control-Expose-Headers': 'Mcp-Session-Id',
|
|
};
|
|
/* eslint-enable @typescript-eslint/naming-convention */
|
|
|
|
const httpServer = createServer((req, res) => {
|
|
if (req.method === 'OPTIONS') {
|
|
res.writeHead(204, corsHeaders);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
for (const [key, value] of Object.entries(corsHeaders)) {
|
|
res.setHeader(key, value);
|
|
}
|
|
|
|
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
const existingTransport = sessionId ? sessions.get(sessionId) : undefined;
|
|
|
|
if (existingTransport) {
|
|
void existingTransport.handleRequest(req, res);
|
|
return;
|
|
}
|
|
|
|
if (sessionId) {
|
|
// Unknown session ID — tell client to re-initialize
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- HTTP header name
|
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
res.end(
|
|
JSON.stringify({
|
|
jsonrpc: '2.0',
|
|
error: { code: -32000, message: 'Session not found' },
|
|
id: null,
|
|
}),
|
|
);
|
|
return;
|
|
}
|
|
|
|
// New session: create a fresh transport + server pair
|
|
const transport = new StreamableHTTPServerTransport({
|
|
sessionIdGenerator: () => randomUUID(),
|
|
onsessioninitialized: (id) => {
|
|
sessions.set(id, transport);
|
|
},
|
|
});
|
|
|
|
transport.onclose = () => {
|
|
if (transport.sessionId) {
|
|
sessions.delete(transport.sessionId);
|
|
}
|
|
};
|
|
|
|
const mcpServer = new McpServer({ name: 'n8n-browser', version: '1.0.0' });
|
|
registerTools(mcpServer, tools);
|
|
|
|
void mcpServer.connect(transport).then(() => {
|
|
void transport.handleRequest(req, res);
|
|
});
|
|
});
|
|
|
|
httpServer.listen(port, () => {
|
|
console.debug(`n8n-browser MCP server listening on http://localhost:${port}`);
|
|
});
|
|
} else {
|
|
const server = new McpServer({ name: 'n8n-browser', version: '1.0.0' });
|
|
registerTools(server, tools);
|
|
const transport = new StdioServerTransport();
|
|
await server.connect(transport);
|
|
}
|
|
|
|
const shutdown = async () => {
|
|
await connection.shutdown();
|
|
process.exit(0);
|
|
};
|
|
|
|
// eslint-disable-next-line no-void
|
|
process.on('SIGTERM', () => void shutdown());
|
|
// eslint-disable-next-line no-void
|
|
process.on('SIGINT', () => void shutdown());
|
|
}
|
|
|
|
void main();
|