n8n-nodes-browser-smart-automation 0.1.2 → 0.1.5

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.
Files changed (59) hide show
  1. package/dist/McpClientTool/McpClientTool.node.js +2 -13
  2. package/dist/McpClientTool/McpClientTool.node.js.map +1 -1
  3. package/dist/McpClientTool/utils.js +1 -1
  4. package/dist/McpClientTool/utils.js.map +1 -1
  5. package/dist/McpTrigger/McpTrigger.node.js +1 -1
  6. package/dist/McpTrigger/McpTrigger.node.js.map +1 -1
  7. package/dist/shared/N8nBinaryLoader.js +203 -0
  8. package/dist/shared/N8nBinaryLoader.js.map +1 -0
  9. package/dist/shared/N8nJsonLoader.js +89 -0
  10. package/dist/shared/N8nJsonLoader.js.map +1 -0
  11. package/dist/shared/N8nTool.js +106 -0
  12. package/dist/shared/N8nTool.js.map +1 -0
  13. package/dist/shared/embeddingInputValidation.js +55 -0
  14. package/dist/shared/embeddingInputValidation.js.map +1 -0
  15. package/dist/shared/helpers.js +220 -13
  16. package/dist/shared/helpers.js.map +1 -1
  17. package/dist/shared/httpProxyAgent.js +40 -2
  18. package/dist/shared/httpProxyAgent.js.map +1 -1
  19. package/dist/shared/logWrapper.js +347 -2
  20. package/dist/shared/logWrapper.js.map +1 -1
  21. package/dist/shared/schemaParsing.js +47 -4
  22. package/dist/shared/schemaParsing.js.map +1 -1
  23. package/dist/shared/sharedFields.js +142 -7
  24. package/dist/shared/sharedFields.js.map +1 -1
  25. package/dist/shared/typesN8nTool.js +17 -0
  26. package/dist/shared/typesN8nTool.js.map +1 -0
  27. package/dist/shared/utils.js +1 -1
  28. package/dist/shared/utils.js.map +1 -1
  29. package/package.json +25 -7
  30. package/jest.config.js +0 -24
  31. package/nodes/McpClient/McpClient.node.ts +0 -327
  32. package/nodes/McpClient/__test__/McpClient.node.test.ts +0 -221
  33. package/nodes/McpClient/__test__/utils.test.ts +0 -302
  34. package/nodes/McpClient/listSearch.ts +0 -48
  35. package/nodes/McpClient/resourceMapping.ts +0 -48
  36. package/nodes/McpClient/utils.ts +0 -281
  37. package/nodes/McpClientTool/McpClientTool.node.ts +0 -468
  38. package/nodes/McpClientTool/__test__/McpClientTool.node.test.ts +0 -730
  39. package/nodes/McpClientTool/loadOptions.ts +0 -45
  40. package/nodes/McpClientTool/types.ts +0 -1
  41. package/nodes/McpClientTool/utils.ts +0 -116
  42. package/nodes/McpTrigger/FlushingTransport.ts +0 -61
  43. package/nodes/McpTrigger/McpServer.ts +0 -317
  44. package/nodes/McpTrigger/McpTrigger.node.ts +0 -204
  45. package/nodes/McpTrigger/__test__/FlushingTransport.test.ts +0 -102
  46. package/nodes/McpTrigger/__test__/McpServer.test.ts +0 -532
  47. package/nodes/McpTrigger/__test__/McpTrigger.node.test.ts +0 -171
  48. package/nodes/shared/__test__/utils.test.ts +0 -318
  49. package/nodes/shared/descriptions.ts +0 -65
  50. package/nodes/shared/helpers.ts +0 -31
  51. package/nodes/shared/httpProxyAgent.ts +0 -11
  52. package/nodes/shared/logWrapper.ts +0 -13
  53. package/nodes/shared/schemaParsing.ts +0 -9
  54. package/nodes/shared/sharedFields.ts +0 -20
  55. package/nodes/shared/types.ts +0 -12
  56. package/nodes/shared/utils.ts +0 -296
  57. package/officail/package.json +0 -255
  58. package/tsconfig.json +0 -32
  59. package/tsup.config.ts +0 -16
@@ -1,327 +0,0 @@
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
- }
@@ -1,221 +0,0 @@
1
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
- import { mockDeep } from 'jest-mock-extended';
3
- import type { IExecuteFunctions } from 'n8n-workflow';
4
-
5
- import * as sharedUtils from '../../shared/utils';
6
- import { McpClient } from '../McpClient.node';
7
-
8
- describe('McpClient', () => {
9
- const getAuthHeaders = jest.spyOn(sharedUtils, 'getAuthHeaders');
10
- const connectMcpClient = jest.spyOn(sharedUtils, 'connectMcpClient');
11
- const executeFunctions = mockDeep<IExecuteFunctions>();
12
- const client = mockDeep<Client>();
13
- const defaultParams = {
14
- authentication: 'none',
15
- serverTransport: 'httpStreamable',
16
- endpointUrl: 'https://test.com/mcp',
17
- inputMode: 'json',
18
- jsonInput: { location: 'Berlin' },
19
- 'tool.value': 'get_weather',
20
- options: { timeout: 10000, convertToBinary: false },
21
- };
22
-
23
- beforeEach(() => {
24
- jest.resetAllMocks();
25
-
26
- executeFunctions.getNode.mockReturnValue({
27
- id: '123',
28
- name: 'MCP Client',
29
- type: '@n8n/n8n-nodes-langchain.mcpClient',
30
- typeVersion: 1,
31
- position: [0, 0],
32
- parameters: {},
33
- });
34
- executeFunctions.getInputData.mockReturnValue([{ json: {} }]);
35
- getAuthHeaders.mockResolvedValue({ headers: {} });
36
- connectMcpClient.mockResolvedValue({
37
- ok: true,
38
- result: client,
39
- });
40
- });
41
-
42
- it('should handle json input mode', async () => {
43
- executeFunctions.getNodeParameter.mockImplementation(
44
- (key, _idx, defaultValue) => defaultParams[key as keyof typeof defaultParams] ?? defaultValue,
45
- );
46
- client.callTool.mockResolvedValue({
47
- content: [{ type: 'text', text: 'Weather in Berlin is sunny' }],
48
- });
49
-
50
- const result = await new McpClient().execute.call(executeFunctions);
51
-
52
- expect(result).toEqual([
53
- [
54
- {
55
- json: { content: [{ type: 'text', text: 'Weather in Berlin is sunny' }] },
56
- pairedItem: { item: 0 },
57
- },
58
- ],
59
- ]);
60
- expect(client.callTool).toHaveBeenCalledWith(
61
- { name: 'get_weather', arguments: { location: 'Berlin' } },
62
- undefined,
63
- { timeout: 10000 },
64
- );
65
- });
66
-
67
- it('should handle manual input mode', async () => {
68
- executeFunctions.getNodeParameter.mockImplementation((key, _idx, defaultValue) => {
69
- const params = {
70
- ...defaultParams,
71
- jsonInput: undefined,
72
- inputMode: 'manual',
73
- 'parameters.value': { location: 'Berlin' },
74
- };
75
- return params[key as keyof typeof params] ?? defaultValue;
76
- });
77
- client.callTool.mockResolvedValue({
78
- content: [{ type: 'text', text: 'Weather in Berlin is sunny' }],
79
- });
80
-
81
- const result = await new McpClient().execute.call(executeFunctions);
82
-
83
- expect(result).toEqual([
84
- [
85
- {
86
- json: { content: [{ type: 'text', text: 'Weather in Berlin is sunny' }] },
87
- pairedItem: { item: 0 },
88
- },
89
- ],
90
- ]);
91
- expect(client.callTool).toHaveBeenCalledWith(
92
- { name: 'get_weather', arguments: { location: 'Berlin' } },
93
- undefined,
94
- { timeout: 10000 },
95
- );
96
- });
97
-
98
- it('should try to parse text content as json', async () => {
99
- executeFunctions.getNodeParameter.mockImplementation(
100
- (key, _idx, defaultValue) => defaultParams[key as keyof typeof defaultParams] ?? defaultValue,
101
- );
102
- client.callTool.mockResolvedValue({
103
- content: [{ type: 'text', text: '{"answer": "Weather in Berlin is sunny"}' }],
104
- });
105
-
106
- const result = await new McpClient().execute.call(executeFunctions);
107
-
108
- expect(result).toEqual([
109
- [
110
- {
111
- json: { content: [{ type: 'text', text: { answer: 'Weather in Berlin is sunny' } }] },
112
- pairedItem: { item: 0 },
113
- },
114
- ],
115
- ]);
116
- });
117
-
118
- it('should convert images and audio to binary data when convertToBinary is true', async () => {
119
- executeFunctions.getNodeParameter.mockImplementation(
120
- (key, _idx, defaultValue) =>
121
- ({
122
- ...defaultParams,
123
- jsonInput: { foo: 'bar' },
124
- options: { ...defaultParams.options, convertToBinary: true },
125
- })[key as keyof typeof defaultParams] ?? defaultValue,
126
- );
127
- client.callTool.mockResolvedValue({
128
- content: [
129
- { type: 'image', data: 'abcdef', mimeType: 'image/jpeg' },
130
- { type: 'audio', data: 'ghijkl', mimeType: 'audio/mpeg' },
131
- ],
132
- });
133
- executeFunctions.helpers.prepareBinaryData.mockResolvedValueOnce({
134
- data: 'abcdef',
135
- mimeType: 'image/jpeg',
136
- });
137
- executeFunctions.helpers.prepareBinaryData.mockResolvedValueOnce({
138
- data: 'ghijkl',
139
- mimeType: 'audio/mpeg',
140
- });
141
-
142
- const result = await new McpClient().execute.call(executeFunctions);
143
-
144
- expect(result).toEqual([
145
- [
146
- {
147
- json: {},
148
- binary: {
149
- data_0: {
150
- mimeType: 'image/jpeg',
151
- data: 'abcdef',
152
- },
153
- data_1: {
154
- mimeType: 'audio/mpeg',
155
- data: 'ghijkl',
156
- },
157
- },
158
- pairedItem: { item: 0 },
159
- },
160
- ],
161
- ]);
162
- });
163
-
164
- it('should keep images and audio as json when convertToBinary is false', async () => {
165
- executeFunctions.getNodeParameter.mockImplementation(
166
- (key, _idx, defaultValue) =>
167
- ({
168
- ...defaultParams,
169
- jsonInput: { foo: 'bar' },
170
- options: { ...defaultParams.options, convertToBinary: false },
171
- })[key as keyof typeof defaultParams] ?? defaultValue,
172
- );
173
- client.callTool.mockResolvedValue({
174
- content: [
175
- { type: 'image', data: 'abcdef', mimeType: 'image/jpeg' },
176
- { type: 'audio', data: 'ghijkl', mimeType: 'audio/mpeg' },
177
- ],
178
- });
179
-
180
- const result = await new McpClient().execute.call(executeFunctions);
181
-
182
- expect(result).toEqual([
183
- [
184
- {
185
- json: {
186
- content: [
187
- { type: 'image', data: 'abcdef', mimeType: 'image/jpeg' },
188
- { type: 'audio', data: 'ghijkl', mimeType: 'audio/mpeg' },
189
- ],
190
- },
191
- pairedItem: { item: 0 },
192
- },
193
- ],
194
- ]);
195
- });
196
-
197
- it('should throw an error if the tool call fails', async () => {
198
- executeFunctions.getNodeParameter.mockImplementation(
199
- (key, _idx, defaultValue) => defaultParams[key as keyof typeof defaultParams] ?? defaultValue,
200
- );
201
- client.callTool.mockRejectedValue(new Error('Tool call failed'));
202
-
203
- await expect(new McpClient().execute.call(executeFunctions)).rejects.toThrow(
204
- 'Tool call failed',
205
- );
206
- });
207
-
208
- it('should return an error as json if the tool call fails and continueOnFail is true', async () => {
209
- executeFunctions.getNodeParameter.mockImplementation(
210
- (key, _idx, defaultValue) => defaultParams[key as keyof typeof defaultParams] ?? defaultValue,
211
- );
212
- client.callTool.mockRejectedValue(new Error('Tool call failed'));
213
- executeFunctions.continueOnFail.mockReturnValue(true);
214
-
215
- const result = await new McpClient().execute.call(executeFunctions);
216
-
217
- expect(result).toEqual([
218
- [{ json: { error: { message: 'Tool call failed' } }, pairedItem: { item: 0 } }],
219
- ]);
220
- });
221
- });