n8n-nodes-browser-smart-automation 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/McpClient/McpClient.node.js +333 -0
- package/dist/McpClient/McpClient.node.js.map +1 -0
- package/dist/McpClient/listSearch.js +58 -0
- package/dist/McpClient/listSearch.js.map +1 -0
- package/dist/McpClient/resourceMapping.js +61 -0
- package/dist/McpClient/resourceMapping.js.map +1 -0
- package/dist/McpClient/utils.js +248 -0
- package/dist/McpClient/utils.js.map +1 -0
- package/dist/McpClientTool/McpClientTool.node.js +417 -0
- package/dist/McpClientTool/McpClientTool.node.js.map +1 -0
- package/dist/McpClientTool/loadOptions.js +61 -0
- package/dist/McpClientTool/loadOptions.js.map +1 -0
- package/dist/McpClientTool/types.js +17 -0
- package/dist/McpClientTool/types.js.map +1 -0
- package/dist/McpClientTool/utils.js +120 -0
- package/dist/McpClientTool/utils.js.map +1 -0
- package/dist/McpTrigger/FlushingTransport.js +61 -0
- package/dist/McpTrigger/FlushingTransport.js.map +1 -0
- package/dist/McpTrigger/McpServer.js +246 -0
- package/dist/McpTrigger/McpServer.js.map +1 -0
- package/dist/McpTrigger/McpTrigger.node.js +196 -0
- package/dist/McpTrigger/McpTrigger.node.js.map +1 -0
- package/dist/shared/descriptions.js +89 -0
- package/dist/shared/descriptions.js.map +1 -0
- package/dist/shared/helpers.js +47 -0
- package/dist/shared/helpers.js.map +1 -0
- package/dist/shared/httpProxyAgent.js +31 -0
- package/dist/shared/httpProxyAgent.js.map +1 -0
- package/dist/shared/logWrapper.js +31 -0
- package/dist/shared/logWrapper.js.map +1 -0
- package/dist/shared/schemaParsing.js +32 -0
- package/dist/shared/schemaParsing.js.map +1 -0
- package/dist/shared/sharedFields.js +41 -0
- package/dist/shared/sharedFields.js.map +1 -0
- package/dist/shared/types.js +17 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/utils.js +231 -0
- package/dist/shared/utils.js.map +1 -0
- package/jest.config.js +24 -0
- package/nodes/McpClient/McpClient.node.ts +327 -0
- package/nodes/McpClient/__test__/McpClient.node.test.ts +221 -0
- package/nodes/McpClient/__test__/utils.test.ts +302 -0
- package/nodes/McpClient/listSearch.ts +48 -0
- package/nodes/McpClient/resourceMapping.ts +48 -0
- package/nodes/McpClient/utils.ts +281 -0
- package/nodes/McpClientTool/McpClientTool.node.ts +468 -0
- package/nodes/McpClientTool/__test__/McpClientTool.node.test.ts +730 -0
- package/nodes/McpClientTool/loadOptions.ts +45 -0
- package/nodes/McpClientTool/types.ts +1 -0
- package/nodes/McpClientTool/utils.ts +116 -0
- package/nodes/McpTrigger/FlushingTransport.ts +61 -0
- package/nodes/McpTrigger/McpServer.ts +317 -0
- package/nodes/McpTrigger/McpTrigger.node.ts +204 -0
- package/nodes/McpTrigger/__test__/FlushingTransport.test.ts +102 -0
- package/nodes/McpTrigger/__test__/McpServer.test.ts +532 -0
- package/nodes/McpTrigger/__test__/McpTrigger.node.test.ts +171 -0
- package/nodes/mcp.dark.svg +7 -0
- package/nodes/mcp.svg +7 -0
- package/nodes/shared/__test__/utils.test.ts +318 -0
- package/nodes/shared/descriptions.ts +65 -0
- package/nodes/shared/helpers.ts +31 -0
- package/nodes/shared/httpProxyAgent.ts +11 -0
- package/nodes/shared/logWrapper.ts +13 -0
- package/nodes/shared/schemaParsing.ts +9 -0
- package/nodes/shared/sharedFields.ts +20 -0
- package/nodes/shared/types.ts +12 -0
- package/nodes/shared/utils.ts +296 -0
- package/officail/package.json +255 -0
- package/package.json +46 -0
- package/tsconfig.json +32 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var utils_exports = {};
|
|
20
|
+
__export(utils_exports, {
|
|
21
|
+
connectMcpClient: () => connectMcpClient,
|
|
22
|
+
getAllTools: () => getAllTools,
|
|
23
|
+
getAuthHeaders: () => getAuthHeaders,
|
|
24
|
+
mapToNodeOperationError: () => mapToNodeOperationError,
|
|
25
|
+
tryRefreshOAuth2Token: () => tryRefreshOAuth2Token
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(utils_exports);
|
|
28
|
+
var import_client = require("@modelcontextprotocol/sdk/client/index.js");
|
|
29
|
+
var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
|
|
30
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
31
|
+
var import_n8n_workflow = require("n8n-workflow");
|
|
32
|
+
var import_httpProxyAgent = require("@utils/httpProxyAgent");
|
|
33
|
+
async function getAllTools(client, cursor) {
|
|
34
|
+
const { tools, nextCursor } = await client.listTools({ cursor });
|
|
35
|
+
if (nextCursor) {
|
|
36
|
+
return tools.concat(await getAllTools(client, nextCursor));
|
|
37
|
+
}
|
|
38
|
+
return tools;
|
|
39
|
+
}
|
|
40
|
+
function safeCreateUrl(url, baseUrl) {
|
|
41
|
+
try {
|
|
42
|
+
return (0, import_n8n_workflow.createResultOk)(new URL(url, baseUrl));
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return (0, import_n8n_workflow.createResultError)(error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function normalizeAndValidateUrl(input) {
|
|
48
|
+
const withProtocol = !/^https?:\/\//i.test(input) ? `https://${input}` : input;
|
|
49
|
+
const parsedUrl = safeCreateUrl(withProtocol);
|
|
50
|
+
if (!parsedUrl.ok) {
|
|
51
|
+
return (0, import_n8n_workflow.createResultError)(parsedUrl.error);
|
|
52
|
+
}
|
|
53
|
+
return parsedUrl;
|
|
54
|
+
}
|
|
55
|
+
function errorHasCode(error, code) {
|
|
56
|
+
return !!error && typeof error === "object" && ("code" in error && Number(error.code) === code || "message" in error && typeof error.message === "string" && error.message.includes(code.toString()));
|
|
57
|
+
}
|
|
58
|
+
function isUnauthorizedError(error) {
|
|
59
|
+
return errorHasCode(error, 401);
|
|
60
|
+
}
|
|
61
|
+
function isForbiddenError(error) {
|
|
62
|
+
return errorHasCode(error, 403);
|
|
63
|
+
}
|
|
64
|
+
function mapToNodeOperationError(node, error) {
|
|
65
|
+
switch (error.type) {
|
|
66
|
+
case "invalid_url":
|
|
67
|
+
return new import_n8n_workflow.NodeOperationError(node, error.error, {
|
|
68
|
+
message: "Could not connect to your MCP server. The provided URL is invalid."
|
|
69
|
+
});
|
|
70
|
+
case "auth":
|
|
71
|
+
return new import_n8n_workflow.NodeOperationError(node, error.error, {
|
|
72
|
+
message: "Could not connect to your MCP server. Authentication failed."
|
|
73
|
+
});
|
|
74
|
+
case "connection":
|
|
75
|
+
default:
|
|
76
|
+
return new import_n8n_workflow.NodeOperationError(node, error.error, {
|
|
77
|
+
message: "Could not connect to your MCP server"
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function connectMcpClient({
|
|
82
|
+
headers,
|
|
83
|
+
serverTransport,
|
|
84
|
+
endpointUrl,
|
|
85
|
+
name,
|
|
86
|
+
version,
|
|
87
|
+
onUnauthorized
|
|
88
|
+
}) {
|
|
89
|
+
const endpoint = normalizeAndValidateUrl(endpointUrl);
|
|
90
|
+
if (!endpoint.ok) {
|
|
91
|
+
return (0, import_n8n_workflow.createResultError)({ type: "invalid_url", error: endpoint.error });
|
|
92
|
+
}
|
|
93
|
+
const client = new import_client.Client({ name, version: version.toString() }, { capabilities: {} });
|
|
94
|
+
if (serverTransport === "httpStreamable") {
|
|
95
|
+
try {
|
|
96
|
+
const transport = new import_streamableHttp.StreamableHTTPClientTransport(endpoint.result, {
|
|
97
|
+
requestInit: { headers },
|
|
98
|
+
fetch: import_httpProxyAgent.proxyFetch
|
|
99
|
+
});
|
|
100
|
+
await client.connect(transport);
|
|
101
|
+
return (0, import_n8n_workflow.createResultOk)(client);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (onUnauthorized && isUnauthorizedError(error)) {
|
|
104
|
+
const newHeaders = await onUnauthorized(headers);
|
|
105
|
+
if (newHeaders) {
|
|
106
|
+
return await connectMcpClient({
|
|
107
|
+
headers: newHeaders,
|
|
108
|
+
serverTransport,
|
|
109
|
+
endpointUrl,
|
|
110
|
+
name,
|
|
111
|
+
version
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (isUnauthorizedError(error) || isForbiddenError(error)) {
|
|
116
|
+
return (0, import_n8n_workflow.createResultError)({ type: "auth", error });
|
|
117
|
+
} else {
|
|
118
|
+
return (0, import_n8n_workflow.createResultError)({ type: "connection", error });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const sseTransport = new import_sse.SSEClientTransport(endpoint.result, {
|
|
124
|
+
eventSourceInit: {
|
|
125
|
+
fetch: async (url, init) => await (0, import_httpProxyAgent.proxyFetch)(url, {
|
|
126
|
+
...init,
|
|
127
|
+
headers: {
|
|
128
|
+
...headers,
|
|
129
|
+
Accept: "text/event-stream"
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
},
|
|
133
|
+
fetch: import_httpProxyAgent.proxyFetch,
|
|
134
|
+
requestInit: { headers }
|
|
135
|
+
});
|
|
136
|
+
await client.connect(sseTransport);
|
|
137
|
+
return (0, import_n8n_workflow.createResultOk)(client);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (onUnauthorized && isUnauthorizedError(error)) {
|
|
140
|
+
const newHeaders = await onUnauthorized(headers);
|
|
141
|
+
if (newHeaders) {
|
|
142
|
+
return await connectMcpClient({
|
|
143
|
+
headers: newHeaders,
|
|
144
|
+
serverTransport,
|
|
145
|
+
endpointUrl,
|
|
146
|
+
name,
|
|
147
|
+
version
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (isUnauthorizedError(error) || isForbiddenError(error)) {
|
|
152
|
+
return (0, import_n8n_workflow.createResultError)({ type: "auth", error });
|
|
153
|
+
} else {
|
|
154
|
+
return (0, import_n8n_workflow.createResultError)({ type: "connection", error });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function getAuthHeaders(ctx, authentication) {
|
|
159
|
+
switch (authentication) {
|
|
160
|
+
case "headerAuth": {
|
|
161
|
+
const header = await ctx.getCredentials("httpHeaderAuth").catch(() => null);
|
|
162
|
+
if (!header) return {};
|
|
163
|
+
return { headers: { [header.name]: header.value } };
|
|
164
|
+
}
|
|
165
|
+
case "bearerAuth": {
|
|
166
|
+
const result = await ctx.getCredentials("httpBearerAuth").catch(() => null);
|
|
167
|
+
if (!result) return {};
|
|
168
|
+
return { headers: { Authorization: `Bearer ${result.token}` } };
|
|
169
|
+
}
|
|
170
|
+
case "mcpOAuth2Api": {
|
|
171
|
+
const result = await ctx.getCredentials("mcpOAuth2Api").catch(() => null);
|
|
172
|
+
if (!result) return {};
|
|
173
|
+
return { headers: { Authorization: `Bearer ${result.oauthTokenData.access_token}` } };
|
|
174
|
+
}
|
|
175
|
+
case "multipleHeadersAuth": {
|
|
176
|
+
const result = await ctx.getCredentials(
|
|
177
|
+
"httpMultipleHeadersAuth"
|
|
178
|
+
).catch(() => null);
|
|
179
|
+
if (!result) return {};
|
|
180
|
+
return {
|
|
181
|
+
headers: result.headers.values.reduce(
|
|
182
|
+
(acc, cur) => {
|
|
183
|
+
acc[cur.name] = cur.value;
|
|
184
|
+
return acc;
|
|
185
|
+
},
|
|
186
|
+
{}
|
|
187
|
+
)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
case "none":
|
|
191
|
+
default: {
|
|
192
|
+
return {};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function tryRefreshOAuth2Token(ctx, authentication, headers) {
|
|
197
|
+
if (authentication !== "mcpOAuth2Api") {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
let access_token = null;
|
|
201
|
+
try {
|
|
202
|
+
const result = await ctx.helpers.refreshOAuth2Token.call(
|
|
203
|
+
ctx,
|
|
204
|
+
"mcpOAuth2Api"
|
|
205
|
+
);
|
|
206
|
+
access_token = result?.access_token;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
if (!access_token) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
if (!headers) {
|
|
214
|
+
return {
|
|
215
|
+
Authorization: `Bearer ${access_token}`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
...headers,
|
|
220
|
+
Authorization: `Bearer ${access_token}`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
224
|
+
0 && (module.exports = {
|
|
225
|
+
connectMcpClient,
|
|
226
|
+
getAllTools,
|
|
227
|
+
getAuthHeaders,
|
|
228
|
+
mapToNodeOperationError,
|
|
229
|
+
tryRefreshOAuth2Token
|
|
230
|
+
});
|
|
231
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../nodes/shared/utils.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\n// import type { ClientOAuth2TokenData } from '@n8n/client-oauth2';\n\nexport interface ClientOAuth2TokenData {\n\taccess_token: string;\n\texpires_in: number;\n\trefresh_token?: string;\n\ttoken_type: string;\n}\nimport type {\n\tIExecuteFunctions,\n\tILoadOptionsFunctions,\n\tINode,\n\tISupplyDataFunctions,\n\tResult,\n} from 'n8n-workflow';\nimport { createResultError, createResultOk, NodeOperationError } from 'n8n-workflow';\n\nimport { proxyFetch } from '@utils/httpProxyAgent';\n\nimport type { McpAuthenticationOption, McpServerTransport, McpTool } from './types';\n\nexport async function getAllTools(client: Client, cursor?: string): Promise<McpTool[]> {\n\tconst { tools, nextCursor } = await client.listTools({ cursor });\n\n\tif (nextCursor) {\n\t\treturn (tools as McpTool[]).concat(await getAllTools(client, nextCursor));\n\t}\n\n\treturn tools as McpTool[];\n}\n\nfunction safeCreateUrl(url: string, baseUrl?: string | URL): Result<URL, Error> {\n\ttry {\n\t\treturn createResultOk(new URL(url, baseUrl));\n\t} catch (error) {\n\t\treturn createResultError(error);\n\t}\n}\n\nfunction normalizeAndValidateUrl(input: string): Result<URL, Error> {\n\tconst withProtocol = !/^https?:\\/\\//i.test(input) ? `https://${input}` : input;\n\tconst parsedUrl = safeCreateUrl(withProtocol);\n\n\tif (!parsedUrl.ok) {\n\t\treturn createResultError(parsedUrl.error);\n\t}\n\n\treturn parsedUrl;\n}\n\nfunction errorHasCode(error: unknown, code: number): boolean {\n\treturn (\n\t\t!!error &&\n\t\ttypeof error === 'object' &&\n\t\t(('code' in error && Number(error.code) === code) ||\n\t\t\t('message' in error &&\n\t\t\t\ttypeof error.message === 'string' &&\n\t\t\t\terror.message.includes(code.toString())))\n\t);\n}\n\nfunction isUnauthorizedError(error: unknown): boolean {\n\treturn errorHasCode(error, 401);\n}\n\nfunction isForbiddenError(error: unknown): boolean {\n\treturn errorHasCode(error, 403);\n}\n\ntype OnUnauthorizedHandler = (\n\theaders?: Record<string, string>,\n) => Promise<Record<string, string> | null>;\n\ntype ConnectMcpClientError =\n\t| { type: 'invalid_url'; error: Error }\n\t| { type: 'connection'; error: Error }\n\t| { type: 'auth'; error: Error };\n\nexport function mapToNodeOperationError(\n\tnode: INode,\n\terror: ConnectMcpClientError,\n): NodeOperationError {\n\tswitch (error.type) {\n\t\tcase 'invalid_url':\n\t\t\treturn new NodeOperationError(node, error.error, {\n\t\t\t\tmessage: 'Could not connect to your MCP server. The provided URL is invalid.',\n\t\t\t});\n\t\tcase 'auth':\n\t\t\treturn new NodeOperationError(node, error.error, {\n\t\t\t\tmessage: 'Could not connect to your MCP server. Authentication failed.',\n\t\t\t});\n\t\tcase 'connection':\n\t\tdefault:\n\t\t\treturn new NodeOperationError(node, error.error, {\n\t\t\t\tmessage: 'Could not connect to your MCP server',\n\t\t\t});\n\t}\n}\n\nexport async function connectMcpClient({\n\theaders,\n\tserverTransport,\n\tendpointUrl,\n\tname,\n\tversion,\n\tonUnauthorized,\n}: {\n\tserverTransport: McpServerTransport;\n\tendpointUrl: string;\n\theaders?: Record<string, string>;\n\tname: string;\n\tversion: number;\n\tonUnauthorized?: OnUnauthorizedHandler;\n}): Promise<Result<Client, ConnectMcpClientError>> {\n\tconst endpoint = normalizeAndValidateUrl(endpointUrl);\n\n\tif (!endpoint.ok) {\n\t\treturn createResultError({ type: 'invalid_url', error: endpoint.error });\n\t}\n\n\tconst client = new Client({ name, version: version.toString() }, { capabilities: {} });\n\n\tif (serverTransport === 'httpStreamable') {\n\t\ttry {\n\t\t\tconst transport = new StreamableHTTPClientTransport(endpoint.result, {\n\t\t\t\trequestInit: { headers },\n\t\t\t\tfetch: proxyFetch,\n\t\t\t});\n\t\t\tawait client.connect(transport);\n\t\t\treturn createResultOk(client);\n\t\t} catch (error) {\n\t\t\tif (onUnauthorized && isUnauthorizedError(error)) {\n\t\t\t\tconst newHeaders = await onUnauthorized(headers);\n\t\t\t\tif (newHeaders) {\n\t\t\t\t\t// Don't pass `onUnauthorized` to avoid possible infinite recursion\n\t\t\t\t\treturn await connectMcpClient({\n\t\t\t\t\t\theaders: newHeaders,\n\t\t\t\t\t\tserverTransport,\n\t\t\t\t\t\tendpointUrl,\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tversion,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isUnauthorizedError(error) || isForbiddenError(error)) {\n\t\t\t\treturn createResultError({ type: 'auth', error: error as Error });\n\t\t\t} else {\n\t\t\t\treturn createResultError({ type: 'connection', error: error as Error });\n\t\t\t}\n\t\t}\n\t}\n\n\ttry {\n\t\tconst sseTransport = new SSEClientTransport(endpoint.result, {\n\t\t\teventSourceInit: {\n\t\t\t\tfetch: async (url, init) =>\n\t\t\t\t\tawait proxyFetch(url, {\n\t\t\t\t\t\t...init,\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t...headers,\n\t\t\t\t\t\t\tAccept: 'text/event-stream',\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\tfetch: proxyFetch,\n\t\t\trequestInit: { headers },\n\t\t});\n\t\tawait client.connect(sseTransport);\n\t\treturn createResultOk(client);\n\t} catch (error) {\n\t\tif (onUnauthorized && isUnauthorizedError(error)) {\n\t\t\tconst newHeaders = await onUnauthorized(headers);\n\t\t\tif (newHeaders) {\n\t\t\t\t// Don't pass `onUnauthorized` to avoid possible infinite recursion\n\t\t\t\treturn await connectMcpClient({\n\t\t\t\t\theaders: newHeaders,\n\t\t\t\t\tserverTransport,\n\t\t\t\t\tendpointUrl,\n\t\t\t\t\tname,\n\t\t\t\t\tversion,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (isUnauthorizedError(error) || isForbiddenError(error)) {\n\t\t\treturn createResultError({ type: 'auth', error: error as Error });\n\t\t} else {\n\t\t\treturn createResultError({ type: 'connection', error: error as Error });\n\t\t}\n\t}\n}\n\nexport async function getAuthHeaders(\n\tctx: Pick<IExecuteFunctions, 'getCredentials'>,\n\tauthentication: McpAuthenticationOption,\n): Promise<{ headers?: Record<string, string> }> {\n\tswitch (authentication) {\n\t\tcase 'headerAuth': {\n\t\t\tconst header = await ctx\n\t\t\t\t.getCredentials<{ name: string; value: string }>('httpHeaderAuth')\n\t\t\t\t.catch(() => null);\n\n\t\t\tif (!header) return {};\n\n\t\t\treturn { headers: { [header.name]: header.value } };\n\t\t}\n\t\tcase 'bearerAuth': {\n\t\t\tconst result = await ctx\n\t\t\t\t.getCredentials<{ token: string }>('httpBearerAuth')\n\t\t\t\t.catch(() => null);\n\n\t\t\tif (!result) return {};\n\n\t\t\treturn { headers: { Authorization: `Bearer ${result.token}` } };\n\t\t}\n\t\tcase 'mcpOAuth2Api': {\n\t\t\tconst result = await ctx\n\t\t\t\t.getCredentials<{ oauthTokenData: { access_token: string } }>('mcpOAuth2Api')\n\t\t\t\t.catch(() => null);\n\n\t\t\tif (!result) return {};\n\n\t\t\treturn { headers: { Authorization: `Bearer ${result.oauthTokenData.access_token}` } };\n\t\t}\n\t\tcase 'multipleHeadersAuth': {\n\t\t\tconst result = await ctx\n\t\t\t\t.getCredentials<{ headers: { values: Array<{ name: string; value: string }> } }>(\n\t\t\t\t\t'httpMultipleHeadersAuth',\n\t\t\t\t)\n\t\t\t\t.catch(() => null);\n\n\t\t\tif (!result) return {};\n\n\t\t\treturn {\n\t\t\t\theaders: result.headers.values.reduce(\n\t\t\t\t\t(acc, cur) => {\n\t\t\t\t\t\tacc[cur.name] = cur.value;\n\t\t\t\t\t\treturn acc;\n\t\t\t\t\t},\n\t\t\t\t\t{} as Record<string, string>,\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t\tcase 'none':\n\t\tdefault: {\n\t\t\treturn {};\n\t\t}\n\t}\n}\n\n/**\n * Tries to refresh the OAuth2 token, storing them in the database if successful\n * @param ctx - The execution context\n * @param authentication - The authentication method\n * @param headers - The headers to refresh\n * @returns The refreshed headers or null if the authentication method is not oAuth2Api or has failed\n */\nexport async function tryRefreshOAuth2Token(\n\tctx: IExecuteFunctions | ISupplyDataFunctions | ILoadOptionsFunctions,\n\tauthentication: McpAuthenticationOption,\n\theaders?: Record<string, string>,\n) {\n\tif (authentication !== 'mcpOAuth2Api') {\n\t\treturn null;\n\t}\n\n\tlet access_token: string | null = null;\n\ttry {\n\t\tconst result = (await ctx.helpers.refreshOAuth2Token.call(\n\t\t\tctx,\n\t\t\t'mcpOAuth2Api',\n\t\t)) as ClientOAuth2TokenData;\n\t\taccess_token = result?.access_token;\n\t} catch (error) {\n\t\treturn null;\n\t}\n\n\tif (!access_token) {\n\t\treturn null;\n\t}\n\n\tif (!headers) {\n\t\treturn {\n\t\t\tAuthorization: `Bearer ${access_token}`,\n\t\t};\n\t}\n\n\treturn {\n\t\t...headers,\n\t\tAuthorization: `Bearer ${access_token}`,\n\t};\n}\n"],"mappings":"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","names":[]}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** @type {import('jest').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
collectCoverageFrom: ['nodes/**/*.ts', '!nodes/**/__test__/**', '!nodes/**/*.test.ts'],
|
|
6
|
+
testMatch: ['**/__test__/**/*.test.ts'],
|
|
7
|
+
transform: {
|
|
8
|
+
'^.+\\.ts$': ['ts-jest', {
|
|
9
|
+
tsconfig: {
|
|
10
|
+
...require('./tsconfig.json').compilerOptions,
|
|
11
|
+
sourceMap: true,
|
|
12
|
+
types: ['jest', 'node'],
|
|
13
|
+
},
|
|
14
|
+
}],
|
|
15
|
+
},
|
|
16
|
+
moduleNameMapper: {
|
|
17
|
+
'^@utils/(.*)$': '<rootDir>/nodes/shared/$1',
|
|
18
|
+
},
|
|
19
|
+
globals: {
|
|
20
|
+
'ts-jest': {
|
|
21
|
+
isolatedModules: true,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import type {
|
|
3
|
+
IBinaryKeyData,
|
|
4
|
+
IDataObject,
|
|
5
|
+
IExecuteFunctions,
|
|
6
|
+
INodeExecutionData,
|
|
7
|
+
INodeType,
|
|
8
|
+
INodeTypeDescription,
|
|
9
|
+
NodeExecutionWithMetadata,
|
|
10
|
+
} from 'n8n-workflow';
|
|
11
|
+
import { jsonParse, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
|
12
|
+
import { ZodError } from 'zod';
|
|
13
|
+
import { prettifyError } from 'zod/v4/core';
|
|
14
|
+
|
|
15
|
+
import * as listSearch from './listSearch';
|
|
16
|
+
import * as resourceMapping from './resourceMapping';
|
|
17
|
+
import { credentials, transportSelect } from '../shared/descriptions';
|
|
18
|
+
import type { McpAuthenticationOption, McpServerTransport } from '../shared/types';
|
|
19
|
+
import {
|
|
20
|
+
getAuthHeaders,
|
|
21
|
+
tryRefreshOAuth2Token,
|
|
22
|
+
connectMcpClient,
|
|
23
|
+
mapToNodeOperationError,
|
|
24
|
+
} from '../shared/utils';
|
|
25
|
+
|
|
26
|
+
export class McpClient implements INodeType {
|
|
27
|
+
description: INodeTypeDescription = {
|
|
28
|
+
displayName: 'MCP Client',
|
|
29
|
+
description: 'Standalone MCP Client',
|
|
30
|
+
name: 'mcpClient',
|
|
31
|
+
icon: {
|
|
32
|
+
light: 'file:../mcp.svg',
|
|
33
|
+
dark: 'file:../mcp.dark.svg',
|
|
34
|
+
},
|
|
35
|
+
group: ['transform'],
|
|
36
|
+
version: 1,
|
|
37
|
+
defaults: {
|
|
38
|
+
name: 'MCP Client',
|
|
39
|
+
},
|
|
40
|
+
credentials,
|
|
41
|
+
inputs: [NodeConnectionTypes.Main],
|
|
42
|
+
outputs: [NodeConnectionTypes.Main],
|
|
43
|
+
properties: [
|
|
44
|
+
transportSelect({
|
|
45
|
+
defaultOption: 'httpStreamable',
|
|
46
|
+
}),
|
|
47
|
+
{
|
|
48
|
+
displayName: 'MCP Endpoint URL',
|
|
49
|
+
name: 'endpointUrl',
|
|
50
|
+
type: 'string',
|
|
51
|
+
default: '',
|
|
52
|
+
placeholder: 'e.g. https://my-mcp-server.ai/mcp',
|
|
53
|
+
required: true,
|
|
54
|
+
description: 'The URL of the MCP server to connect to',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
displayName: 'Authentication',
|
|
58
|
+
name: 'authentication',
|
|
59
|
+
type: 'options',
|
|
60
|
+
options: [
|
|
61
|
+
{
|
|
62
|
+
name: 'Bearer Auth',
|
|
63
|
+
value: 'bearerAuth',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'Header Auth',
|
|
67
|
+
value: 'headerAuth',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'MCP OAuth2',
|
|
71
|
+
value: 'mcpOAuth2Api',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'Multiple Headers Auth',
|
|
75
|
+
value: 'multipleHeadersAuth',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'None',
|
|
79
|
+
value: 'none',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
default: 'none',
|
|
83
|
+
description: 'The way to authenticate with your endpoint',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
displayName: 'Credentials',
|
|
87
|
+
name: 'credentials',
|
|
88
|
+
type: 'credentials',
|
|
89
|
+
default: '',
|
|
90
|
+
displayOptions: {
|
|
91
|
+
show: {
|
|
92
|
+
authentication: ['headerAuth', 'bearerAuth', 'mcpOAuth2Api', 'multipleHeadersAuth'],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
displayName: 'Tool',
|
|
98
|
+
name: 'tool',
|
|
99
|
+
type: 'resourceLocator',
|
|
100
|
+
default: { mode: 'list', value: '' },
|
|
101
|
+
required: true,
|
|
102
|
+
description: 'The tool to use',
|
|
103
|
+
modes: [
|
|
104
|
+
{
|
|
105
|
+
displayName: 'From List',
|
|
106
|
+
name: 'list',
|
|
107
|
+
type: 'list',
|
|
108
|
+
typeOptions: {
|
|
109
|
+
searchListMethod: 'getTools',
|
|
110
|
+
searchable: true,
|
|
111
|
+
skipCredentialsCheckInRLC: true,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
displayName: 'ID',
|
|
116
|
+
name: 'id',
|
|
117
|
+
type: 'string',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
displayName: 'Input Mode',
|
|
123
|
+
name: 'inputMode',
|
|
124
|
+
type: 'options',
|
|
125
|
+
default: 'manual',
|
|
126
|
+
noDataExpression: true,
|
|
127
|
+
options: [
|
|
128
|
+
{
|
|
129
|
+
name: 'Manual',
|
|
130
|
+
value: 'manual',
|
|
131
|
+
description: 'Manually specify the input data for each tool parameter',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'JSON',
|
|
135
|
+
value: 'json',
|
|
136
|
+
description: 'Specify the input data as a JSON object',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
displayName: 'Parameters',
|
|
142
|
+
name: 'parameters',
|
|
143
|
+
type: 'resourceMapper',
|
|
144
|
+
default: {
|
|
145
|
+
mappingMode: 'defineBelow',
|
|
146
|
+
value: null,
|
|
147
|
+
},
|
|
148
|
+
noDataExpression: true,
|
|
149
|
+
required: true,
|
|
150
|
+
typeOptions: {
|
|
151
|
+
loadOptionsDependsOn: ['tool.value'],
|
|
152
|
+
resourceMapper: {
|
|
153
|
+
resourceMapperMethod: 'getToolParameters',
|
|
154
|
+
hideNoDataError: true,
|
|
155
|
+
addAllFields: false,
|
|
156
|
+
supportAutoMap: false,
|
|
157
|
+
mode: 'add',
|
|
158
|
+
fieldWords: {
|
|
159
|
+
singular: 'parameter',
|
|
160
|
+
plural: 'parameters',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
displayOptions: {
|
|
165
|
+
show: {
|
|
166
|
+
inputMode: ['manual'],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
displayName: 'JSON',
|
|
172
|
+
name: 'jsonInput',
|
|
173
|
+
type: 'json',
|
|
174
|
+
typeOptions: {
|
|
175
|
+
rows: 5,
|
|
176
|
+
},
|
|
177
|
+
default: '{\n "my_field_1": "value",\n "my_field_2": 1\n}\n',
|
|
178
|
+
validateType: 'object',
|
|
179
|
+
displayOptions: {
|
|
180
|
+
show: {
|
|
181
|
+
inputMode: ['json'],
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
displayName: 'Options',
|
|
187
|
+
name: 'options',
|
|
188
|
+
placeholder: 'Add Option',
|
|
189
|
+
description: 'Additional options to add',
|
|
190
|
+
type: 'collection',
|
|
191
|
+
default: {},
|
|
192
|
+
options: [
|
|
193
|
+
{
|
|
194
|
+
displayName: 'Convert to Binary',
|
|
195
|
+
name: 'convertToBinary',
|
|
196
|
+
type: 'boolean',
|
|
197
|
+
default: true,
|
|
198
|
+
description:
|
|
199
|
+
'Whether to convert images and audio to binary data. If false, images and audio will be returned as base64 encoded strings.',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
displayName: 'Timeout',
|
|
203
|
+
name: 'timeout',
|
|
204
|
+
type: 'number',
|
|
205
|
+
typeOptions: {
|
|
206
|
+
minValue: 1,
|
|
207
|
+
},
|
|
208
|
+
default: 60000,
|
|
209
|
+
description: 'Time in ms to wait for tool calls to finish',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
methods = {
|
|
217
|
+
listSearch,
|
|
218
|
+
resourceMapping,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
async execute(
|
|
222
|
+
this: IExecuteFunctions,
|
|
223
|
+
): Promise<INodeExecutionData[][] | NodeExecutionWithMetadata[][] | null> {
|
|
224
|
+
const authentication = this.getNodeParameter('authentication', 0) as McpAuthenticationOption;
|
|
225
|
+
const serverTransport = this.getNodeParameter('serverTransport', 0) as McpServerTransport;
|
|
226
|
+
const endpointUrl = this.getNodeParameter('endpointUrl', 0) as string;
|
|
227
|
+
const node = this.getNode();
|
|
228
|
+
const { headers } = await getAuthHeaders(this, authentication);
|
|
229
|
+
const client = await connectMcpClient({
|
|
230
|
+
serverTransport,
|
|
231
|
+
endpointUrl,
|
|
232
|
+
headers,
|
|
233
|
+
name: node.type,
|
|
234
|
+
version: node.typeVersion,
|
|
235
|
+
onUnauthorized: async (headers) => await tryRefreshOAuth2Token(this, authentication, headers),
|
|
236
|
+
});
|
|
237
|
+
if (!client.ok) {
|
|
238
|
+
throw mapToNodeOperationError(node, client.error);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const inputMode = this.getNodeParameter('inputMode', 0, 'manual') as 'manual' | 'json';
|
|
242
|
+
const items = this.getInputData();
|
|
243
|
+
const returnData: INodeExecutionData[] = [];
|
|
244
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
245
|
+
try {
|
|
246
|
+
const tool = this.getNodeParameter('tool.value', itemIndex) as string;
|
|
247
|
+
const options = this.getNodeParameter('options', itemIndex);
|
|
248
|
+
let parameters: IDataObject = {};
|
|
249
|
+
if (inputMode === 'manual') {
|
|
250
|
+
parameters = this.getNodeParameter('parameters.value', itemIndex) as IDataObject;
|
|
251
|
+
} else {
|
|
252
|
+
parameters = this.getNodeParameter('jsonInput', itemIndex) as IDataObject;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const result = (await client.result.callTool(
|
|
256
|
+
{
|
|
257
|
+
name: tool,
|
|
258
|
+
arguments: parameters,
|
|
259
|
+
},
|
|
260
|
+
undefined,
|
|
261
|
+
{
|
|
262
|
+
timeout: options.timeout ? Number(options.timeout) : undefined,
|
|
263
|
+
},
|
|
264
|
+
)) as CallToolResult;
|
|
265
|
+
|
|
266
|
+
let binaryIndex = 0;
|
|
267
|
+
const binary: IBinaryKeyData = {};
|
|
268
|
+
const content: IDataObject[] = [];
|
|
269
|
+
const convertToBinary = options.convertToBinary ?? true;
|
|
270
|
+
for (const contentItem of result.content) {
|
|
271
|
+
if (contentItem.type === 'text') {
|
|
272
|
+
content.push({
|
|
273
|
+
...contentItem,
|
|
274
|
+
text: jsonParse(contentItem.text, { fallbackValue: contentItem.text }),
|
|
275
|
+
});
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (convertToBinary && (contentItem.type === 'image' || contentItem.type === 'audio')) {
|
|
280
|
+
binary[`data_${binaryIndex}`] = await this.helpers.prepareBinaryData(
|
|
281
|
+
Buffer.from(contentItem.data, 'base64'),
|
|
282
|
+
undefined,
|
|
283
|
+
contentItem.mimeType,
|
|
284
|
+
);
|
|
285
|
+
binaryIndex++;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
content.push(contentItem as IDataObject);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
returnData.push({
|
|
293
|
+
json: {
|
|
294
|
+
content: content.length > 0 ? content : undefined,
|
|
295
|
+
},
|
|
296
|
+
binary: Object.keys(binary).length > 0 ? binary : undefined,
|
|
297
|
+
pairedItem: {
|
|
298
|
+
item: itemIndex,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
} catch (e) {
|
|
302
|
+
const errorMessage =
|
|
303
|
+
e instanceof ZodError ? prettifyError(e) : e instanceof Error ? e.message : String(e);
|
|
304
|
+
if (this.continueOnFail()) {
|
|
305
|
+
returnData.push({
|
|
306
|
+
json: {
|
|
307
|
+
error: {
|
|
308
|
+
message: errorMessage,
|
|
309
|
+
issues: e instanceof ZodError ? e.issues : undefined,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
pairedItem: {
|
|
313
|
+
item: itemIndex,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
throw new NodeOperationError(node, errorMessage, {
|
|
320
|
+
itemIndex,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return [returnData];
|
|
326
|
+
}
|
|
327
|
+
}
|