diff --git a/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/McpClientTool.node.ts b/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/McpClientTool.node.ts index 9a16785c6d..53dd4f9043 100644 --- a/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/McpClientTool.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/McpClientTool.node.ts @@ -11,7 +11,7 @@ import { logWrapper } from '@utils/logWrapper'; import { getConnectionHintNoticeField } from '@utils/sharedFields'; import { getTools } from './loadOptions'; -import type { McpAuthenticationOption, McpToolIncludeMode } from './types'; +import type { McpServerTransport, McpAuthenticationOption, McpToolIncludeMode } from './types'; import { connectMcpClient, createCallTool, @@ -31,7 +31,7 @@ export class McpClientTool implements INodeType { dark: 'file:../mcp.dark.svg', }, group: ['output'], - version: 1, + version: [1, 1.1], description: 'Connect tools from an MCP Server', defaults: { name: 'MCP Client', @@ -83,6 +83,47 @@ export class McpClientTool implements INodeType { placeholder: 'e.g. https://my-mcp-server.ai/sse', default: '', required: true, + displayOptions: { + show: { + '@version': [1], + }, + }, + }, + { + displayName: 'Endpoint', + name: 'endpointUrl', + type: 'string', + description: 'Endpoint of your MCP server', + placeholder: 'e.g. https://my-mcp-server.ai/mcp', + default: '', + required: true, + displayOptions: { + show: { + '@version': [{ _cnd: { gte: 1.1 } }], + }, + }, + }, + { + displayName: 'Server Transport', + name: 'serverTransport', + type: 'options', + options: [ + { + name: 'Server Sent Events (Deprecated)', + value: 'sse', + }, + { + name: 'HTTP Streamable', + value: 'httpStreamable', + }, + ], + default: 'sse', + description: 'The transport used by your endpoint', + displayOptions: { + show: { + '@version': [{ _cnd: { gte: 1.1 } }], + }, + }, }, { displayName: 'Authentication', @@ -103,7 +144,7 @@ export class McpClientTool implements INodeType { }, ], default: 'none', - description: 'The way to authenticate with your SSE endpoint', + description: 'The way to authenticate with your endpoint', }, { displayName: 'Credentials', @@ -187,11 +228,22 @@ export class McpClientTool implements INodeType { 'authentication', itemIndex, ) as McpAuthenticationOption; - const sseEndpoint = this.getNodeParameter('sseEndpoint', itemIndex) as string; const node = this.getNode(); + + let serverTransport: McpServerTransport; + let endpointUrl: string; + if (node.typeVersion === 1) { + serverTransport = 'sse'; + endpointUrl = this.getNodeParameter('sseEndpoint', itemIndex) as string; + } else { + serverTransport = this.getNodeParameter('serverTransport', itemIndex) as McpServerTransport; + endpointUrl = this.getNodeParameter('endpointUrl', itemIndex) as string; + } + const { headers } = await getAuthHeaders(this, authentication); const client = await connectMcpClient({ - sseEndpoint, + serverTransport, + endpointUrl, headers, name: node.type, version: node.typeVersion, diff --git a/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/loadOptions.ts b/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/loadOptions.ts index 99a602b6a7..f32d1e1046 100644 --- a/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/loadOptions.ts +++ b/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/loadOptions.ts @@ -4,16 +4,25 @@ import { NodeOperationError, } from 'n8n-workflow'; -import type { McpAuthenticationOption } from './types'; +import type { McpAuthenticationOption, McpServerTransport } from './types'; import { connectMcpClient, getAllTools, getAuthHeaders } from './utils'; export async function getTools(this: ILoadOptionsFunctions): Promise { const authentication = this.getNodeParameter('authentication') as McpAuthenticationOption; - const sseEndpoint = this.getNodeParameter('sseEndpoint') as string; const node = this.getNode(); + let serverTransport: McpServerTransport; + let endpointUrl: string; + if (node.typeVersion === 1) { + serverTransport = 'sse'; + endpointUrl = this.getNodeParameter('sseEndpoint') as string; + } else { + serverTransport = this.getNodeParameter('serverTransport') as McpServerTransport; + endpointUrl = this.getNodeParameter('endpointUrl') as string; + } const { headers } = await getAuthHeaders(this, authentication); const client = await connectMcpClient({ - sseEndpoint, + serverTransport, + endpointUrl, headers, name: node.type, version: node.typeVersion, diff --git a/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/types.ts b/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/types.ts index 05ea55245e..1637aca4de 100644 --- a/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/types.ts +++ b/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/types.ts @@ -2,6 +2,8 @@ import type { JSONSchema7 } from 'json-schema'; export type McpTool = { name: string; description?: string; inputSchema: JSONSchema7 }; +export type McpServerTransport = 'sse' | 'httpStreamable'; + export type McpToolIncludeMode = 'all' | 'selected' | 'except'; export type McpAuthenticationOption = 'none' | 'headerAuth' | 'bearerAuth'; diff --git a/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/utils.ts b/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/utils.ts index b742122732..140924acb3 100644 --- a/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/utils.ts +++ b/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/utils.ts @@ -1,6 +1,7 @@ import { DynamicStructuredTool, type DynamicStructuredToolInput } from '@langchain/core/tools'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import { CompatibilityCallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; import { Toolkit } from 'langchain/agents'; import { @@ -14,7 +15,12 @@ import { z } from 'zod'; import { convertJsonSchemaToZod } from '@utils/schemaParsing'; -import type { McpAuthenticationOption, McpTool, McpToolIncludeMode } from './types'; +import type { + McpAuthenticationOption, + McpTool, + McpServerTransport, + McpToolIncludeMode, +} from './types'; export async function getAllTools(client: Client, cursor?: string): Promise { const { tools, nextCursor } = await client.listTools({ cursor }); @@ -145,23 +151,39 @@ type ConnectMcpClientError = | { type: 'connection'; error: Error }; export async function connectMcpClient({ headers, - sseEndpoint, + serverTransport, + endpointUrl, name, version, }: { - sseEndpoint: string; + serverTransport: McpServerTransport; + endpointUrl: string; headers?: Record; name: string; version: number; }): Promise> { - try { - const endpoint = normalizeAndValidateUrl(sseEndpoint); + const endpoint = normalizeAndValidateUrl(endpointUrl); - if (!endpoint.ok) { - return createResultError({ type: 'invalid_url', error: endpoint.error }); + if (!endpoint.ok) { + return createResultError({ type: 'invalid_url', error: endpoint.error }); + } + + const client = new Client({ name, version: version.toString() }, { capabilities: { tools: {} } }); + + if (serverTransport === 'httpStreamable') { + try { + const transport = new StreamableHTTPClientTransport(endpoint.result, { + requestInit: { headers }, + }); + await client.connect(transport); + return createResultOk(client); + } catch (error) { + return createResultError({ type: 'connection', error }); } + } - const transport = new SSEClientTransport(endpoint.result, { + try { + const sseTransport = new SSEClientTransport(endpoint.result, { eventSourceInit: { fetch: async (url, init) => await fetch(url, { @@ -174,13 +196,7 @@ export async function connectMcpClient({ }, requestInit: { headers }, }); - - const client = new Client( - { name, version: version.toString() }, - { capabilities: { tools: {} } }, - ); - - await client.connect(transport); + await client.connect(sseTransport); return createResultOk(client); } catch (error) { return createResultError({ type: 'connection', error });