---
title: "MCP Server"
description: "Model Context Protocol (MCP) server implementation for the n8n McpTrigger node."
type: skill
canonical_url: https://claudary.paisolsolutions.com/skills/readme-530
source: "Claudary"
difficulty: intermediate
author: "Claude Code Knowledge Pack"
date: 2026-07-10T11:36:38.436Z
license: CC-BY-4.0
attribution: "MCP Server — Claudary (https://claudary.paisolsolutions.com/skills/readme-530)"
---

# MCP Server
Model Context Protocol (MCP) server implementation for the n8n McpTrigger node.

## Overview

# MCP Server

Model Context Protocol (MCP) server implementation for the n8n McpTrigger node.

## Overview

This module provides a clean, modular architecture for handling MCP connections. It separates concerns into distinct layers that can be tested and extended independently.

```mermaid
flowchart TB
    subgraph Client["MCP Client"]
        C[Claude Desktop / MCP Client]
    end

    subgraph McpServer["McpServer (Facade)"]
        direction TB
        MS[McpServer]
    end

    subgraph Layers["Core Layers"]
        direction TB

        subgraph Session["Session Layer"]
            SM[SessionManager]
            SS[(SessionStore)]
        end

        subgraph Transport["Transport Layer"]
            TF[TransportFactory]
            SSE[SSETransport]
            HTTP[StreamableHttpTransport]
        end

        subgraph Execution["Execution Layer"]
            EC[ExecutionCoordinator]
            DS[DirectStrategy]
            QS[QueuedStrategy]
            PM[PendingCallsManager]
        end

        subgraph Protocol["Protocol Layer"]
            MP[MessageParser]
            MF[MessageFormatter]
        end
    end

    C <-->|SSE/HTTP| MS
    MS --> SM
    MS --> TF
    MS --> EC
    MS --> MP
    MS --> MF
    SM --> SS
    TF --> SSE
    TF --> HTTP
    EC --> DS
    EC --> QS
    QS --> PM
```

## Architecture

### Module Overview

| Module | Purpose |
|--------|---------|
| **McpServer** | Main entry point. Coordinates all subsystems. |
| **Session** | Manages client connections and tool registrations. |
| **Transport** | Handles communication protocols (SSE, Streamable HTTP). |
| **Execution** | Executes tools directly or via worker queue. |
| **Protocol** | Parses and formats MCP messages. |

### McpServer Facade

The `McpServer` class is the **main entry point** for all MCP operations. It implements the [Facade pattern](https://refactoring.guru/design-patterns/facade), providing a simplified interface that coordinates all the underlying subsystems.

#### Why a Facade?

Without the facade, consumers would need to:
1. Create and configure a SessionManager with a SessionStore
2. Create a TransportFactory
3. Create transports and wire up event handlers
4. Create an ExecutionCoordinator with a strategy
5. Parse incoming messages with MessageParser
6. Format responses with MessageFormatter
7. Wire everything together correctly

The `McpServer` facade handles all this complexity internally, exposing just a few high-level methods.

#### Singleton Pattern

```typescript
const mcpServer = McpServer.instance(logger);
```

`McpServer` is a **singleton** - only one instance exists per process. This ensures:
- All MCP requests share the same session registry
- Pending responses are tracked in one place
- Configuration changes (session store, execution strategy) apply globally

#### Request Flow Overview

```mermaid
flowchart TB
    subgraph Incoming["Incoming Requests"]
        GET["GET /sse (SSE setup)"]
        POST_INIT["POST /mcp (Streamable HTTP init)"]
        POST_MSG["POST /messages (tool call)"]
        DELETE["DELETE /mcp (session close)"]
    end

    subgraph McpServer["McpServer Facade"]
        HandleSetup[handleSetupRequest]
        HandleStreamable[handleStreamableHttpSetup]
        HandlePost[handlePostMessage]
        HandleDelete[handleDeleteRequest]
        HandleWorker[handleWorkerResponse]
        StorePending[storePendingResponse]
    end

    subgraph Internal["Internal Coordination"]
        CreateServer[createServer]
        SetupSession[setupSession]
        SetupHandlers[setupHandlers]
        CleanupSession[cleanupSession]
        RecreateTransport[recreateStreamableHttpTransport]
    end

    GET --> HandleSetup
    POST_INIT --> HandleStreamable
    POST_MSG --> HandlePost
    DELETE --> HandleDelete

    HandleSetup --> CreateServer
    HandleSetup --> SetupSession
    HandleStreamable --> CreateServer
    HandleStreamable --> SetupHandlers
    HandlePost --> RecreateTransport
    HandleDelete --> CleanupSession

    SetupSession --> SetupHandlers
```

#### Public Methods

| Method | Purpose |
|--------|---------|
| `instance(logger)` | Get the singleton instance |
| `handleSetupRequest(req, resp, serverName, postUrl, tools)` | Handle SSE connection setup (GET request) |
| `handleStreamableHttpSetup(req, resp, serverName, tools)` | Handle Streamable HTTP initialization (POST with `initialize` method) |
| `handlePostMessage(req, resp, tools, serverName?)` | Handle incoming tool calls or list-tools requests. Returns `HandlePostResult` |
| `handleDeleteRequest(req, resp)` | Handle session termination |
| `handleWorkerResponse(sessionId, messageId, result)` | Route worker results back to clients (queue mode) |
| `storePendingResponse(sessionId, messageId)` | Track a pending response awaiting worker result |
| `hasPendingResponse(sessionId, messageId)` | Check if a pending response exists |
| `removePendingResponse(sessionId, messageId)` | Remove a pending response |
| `pendingResponseCount` | Getter for the number of pending responses |
| `getMcpMetadata(req)` | Extract session ID and message ID from a request |
| `getSessionId(req)` | Extract session ID from query string or header |
| `getTransport(sessionId)` | Get the transport for a session |
| `getTools(sessionId)` | Get the tools registered for a session |

#### HandlePostResult Type

The `handlePostMessage` method returns a `HandlePostResult` object:

```typescript
interface HandlePostResult {
  wasToolCall: boolean;              // Whether the request was a tool call
  toolCallInfo?: McpToolCallInfo;    // Info about the tool call (if any)
  messageId?: string;                // The JSONRPC message ID
  relaySessionId?: string;           // Session ID for relayed requests (queue mode)
  needsListToolsRelay?: boolean;     // Whether this is a list-tools request needing relay
}
```

#### Configuration Methods

| Method | Purpose |
|--------|---------|
| `setSessionStore(store)` | Replace the session store (e.g., InMemory → Redis) |
| `setExecutionStrategy(strategy)` | Replace the execution strategy (e.g., Direct → Queued) |
| `isQueueMode()` | Check if using queued execution |
| `getPendingCallsManager()` | Get the pending calls manager (needed for QueuedExecutionStrategy) |

#### Internal Coordination

The facade coordinates these internal operations:

| Internal Method | What It Does |
|-----------------|--------------|
| `createServer(serverName)` | Creates an MCP SDK `Server` instance with capabilities |
| `setupSession(server, transport, tools, resp)` | Registers session, sets up close handlers, connects server to transport |
| `setupHandlers(server)` | Registers `tools/list` and `tools/call` request handlers on the MCP server |
| `cleanupSession(sessionId)` | Cleans up pending calls, pending responses, and destroys the session |
| `recreateStreamableHttpTransport(...)` | Recreates a transport for an existing session (multi-instance scenarios) |

#### Queue Mode Behavior

In queue mode (multi-instance deployment), the facade has additional responsibilities:

```mermaid
sequenceDiagram
    participant Client
    participant Main as McpServer (Main)
    participant Redis
    participant Worker

    Client->>Main: POST /messages (tool call)
    Main->>Main: storePendingResponse()
    Main-->>Client: 202 Accepted
    Main->>Redis: Enqueue job

    Redis->>Worker: Dequeue job
    Worker->>Worker: Execute tool
    Worker->>Redis: Publish result

    Redis->>Main: mcp-response event
    Main->>Main: handleWorkerResponse()
    Main-->>Client: Result via SSE/HTTP
```

Key queue mode methods:
- **`storePendingResponse()`** - Tracks that we're waiting for a worker result
- **`handleWorkerResponse()`** - Routes the worker's result back to the correct client
- **`hasPendingResponse()`** / **`removePendingResponse()`** - Manage pending response state

## Layers

### 1. Protocol Layer

The Protocol layer handles the translation between raw HTTP request bodies and strongly-typed MCP data structures. MCP uses [JSONRPC 2.0](https://www.jsonrpc.org/specification) as its wire protocol, so every message from an MCP client is a JSONRPC request.

#### Why This Layer Exists

When an MCP client sends a request (e.g., "call tool X with arguments Y"), it arrives as a raw JSON string in the HTTP request body. The Protocol layer:

1. **Parses and validates** the raw JSON against the JSONRPC schema
2. **Identifies the request type** (tool call, list tools, etc.)
3. **Extracts the relevant data** (tool name, arguments) into typed structures
4. **Formats responses** back into the MCP-expected format

This keeps the rest of the codebase working with clean, typed data instead of raw JSON.

```mermaid
flowchart LR
    subgraph Incoming["Incoming Request"]
        Raw["Raw JSON Body<br/>{ jsonrpc: '2.0', method: 'tools/call', ... }"]
    end

    subgraph MessageParser["MessageParser"]
        Parse[parse]
        IsToolCall[isToolCall]
        IsListTools[isListToolsRequest]
        GetId[getRequestId]
        Extract[extractToolCallInfo]
    end

    subgraph Outgoing["Outgoing Response"]
        Result["Tool Execution Result<br/>(string, object, Error)"]
    end

    subgraph MessageFormatter["MessageFormatter"]
        FormatResult[formatToolResult]
        FormatError[formatError]
    end

    Raw --> Parse
    Parse --> IsToolCall
    Parse --> IsListTools
    Parse --> GetId
    Parse --> Extract
    Extract --> Info["McpToolCallInfo<br/>{ toolName, arguments }"]

    Result --> FormatResult
    Result --> FormatError
    FormatResult --> McpResult["McpToolResult<br/>{ content: [{ type, text }] }"]
    FormatError --> McpResult
```

#### Types

```typescript
// Extracted info from a tool call request
interface McpToolCallInfo {
  toolName: string;                    // Name of the tool to invoke
  arguments: Record<string, unknown>;  // Arguments passed to the tool
  sourceNodeName?: string;             // Optional: n8n node that registered the tool
}

// Formatted result to send back to the client
interface McpToolResult {
  content: Array<{ type: string; text: string }>;  // MCP content blocks
  isError?: boolean;                                // Flag for error responses
}

// Special marker returned when handling list-tools requests
const MCP_LIST_TOOLS_REQUEST_MARKER = { _listToolsRequest: true };
```

#### MessageParser Methods

| Method | Purpose |
|--------|---------|
| `parse(body)` | Parses a raw JSON string into a validated `JSONRPCMessage`. Returns `undefined` if invalid. |
| `isToolCall(body)` | Returns `true` if the message is a `tools/call` request (client wants to invoke a tool) |
| `isListToolsRequest(body)` | Returns `true` if the message is a `tools/list` request (client wants to discover available tools) |
| `getRequestId(message)` | Extracts the JSONRPC request ID (needed to correlate responses with requests) |
| `extractToolCallInfo(body)` | Extracts the tool name and arguments from a tool call request into `McpToolCallInfo` |

#### MessageFormatter Methods

| Method | Purpose |
|--------|---------|
| `formatToolResult(result)` | Converts a tool's return value (string, object, etc.) into an `McpToolResult` with proper content blocks |
| `formatError(error)` | Converts an Error into an `McpToolResult` with `isError: true` and the error message |

#### Example Flow

```typescript
// 1. Client sends a tool call
const body = '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_weather","arguments":{"city":"London"}}}';

// 2. Parse and identify
MessageParser.isToolCall(body);  // true
MessageParser.isListToolsRequest(body);  // false

// 3. Extract tool info
const info = MessageParser.extractToolCallInfo(body);
// { toolName: 'get_weather', arguments: { city: 'London' } }

// 4. Execute tool and format result
const result = await executeTool(info);  // { temperature: 15, unit: 'celsius' }
const formatted = MessageFormatter.formatToolResult(result);
// { content: [{ type: 'text', text: '{"temperature":15,"unit":"celsius"}' }] }

// 5. Or format an error
const error = MessageFormatter.formatError(new Error('City not found'));
// { isError: true, content: [{ type: 'text', text: 'Error: City not found' }] }
```

**Files:**
- `types.ts` - Type definitions (`McpToolCallInfo`, `McpToolResult`, `MCP_LIST_TOOLS_REQUEST_MARKER`)
- `MessageParser.ts` - Parses raw JSON, identifies request types, extracts tool call info
- `MessageFormatter.ts` - Formats tool results and errors for MCP responses

### 2. Session Layer

The Session layer manages MCP client connections and their associated state (tools, transport, server instance). Each MCP client establishes a session when connecting, and that session persists for the lifetime of the connection.

#### Why Sessions Are Needed

MCP uses a stateful protocol where:
1. A client connects and establishes a session (via SSE or Streamable HTTP)
2. The client can then make multiple tool calls within that session
3. Each session has its own transport (for sending responses back) and set of available tools

Sessions allow the server to:
- Track which clients are connected
- Route responses back to the correct client
- Associate tools with specific client connections
- Validate that incoming requests belong to active sessions

```mermaid
flowchart TB
    subgraph SessionManager["SessionManager (Coordinator)"]
        direction LR
        Register[registerSession]
        Destroy[destroySession]
        GetSession[getSession]
        GetTransport[getTransport]
        GetServer[getServer]
        IsValid[isSessionValid]
        Tools[getTools / setTools]
    end

    subgraph InMemoryState["In-Memory State (SessionManager)"]
        SessionInfo["sessions: Record&lt;sessionId, SessionInfo&gt;"]
        SI_Content["SessionInfo = { sessionId, server, transport }"]
    end

    subgraph SessionStore["SessionStore Interface"]
        InMemory[InMemorySessionStore]
        Redis[RedisSessionStore]
    end

    SessionManager --> InMemoryState
    SessionManager --> SessionStore
    InMemory -.->|implements| SessionStore
    Redis -.->|implements| SessionStore
```

#### Two-Level Storage Architecture

The Session layer uses a two-level storage architecture:

| Storage Level | What It Stores | Why |
|---------------|----------------|-----|
| **SessionManager (in-memory)** | `SessionInfo` objects containing the MCP `Server` instance and `Transport` | These are runtime objects (WebSocket connections, SSE streams) that cannot be serialized or shared across processes |
| **SessionStore (pluggable)** | Session IDs (for validation) and Tools array | Can be backed by Redis for multi-instance deployments where sessions need to be validated across workers |

This separation allows:
- **Single-instance mode**: Use `InMemorySessionStore` (default) - everything stays in process memory
- **Multi-instance/queue mode**: Use `RedisSessionStore` - session validation and tools can be checked by any worker, while the act

---

Source: [Claudary](https://claudary.paisolsolutions.com/skills/readme-530) · https://claudary.paisolsolutions.com
