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.
Files changed (71) hide show
  1. package/dist/McpClient/McpClient.node.js +333 -0
  2. package/dist/McpClient/McpClient.node.js.map +1 -0
  3. package/dist/McpClient/listSearch.js +58 -0
  4. package/dist/McpClient/listSearch.js.map +1 -0
  5. package/dist/McpClient/resourceMapping.js +61 -0
  6. package/dist/McpClient/resourceMapping.js.map +1 -0
  7. package/dist/McpClient/utils.js +248 -0
  8. package/dist/McpClient/utils.js.map +1 -0
  9. package/dist/McpClientTool/McpClientTool.node.js +417 -0
  10. package/dist/McpClientTool/McpClientTool.node.js.map +1 -0
  11. package/dist/McpClientTool/loadOptions.js +61 -0
  12. package/dist/McpClientTool/loadOptions.js.map +1 -0
  13. package/dist/McpClientTool/types.js +17 -0
  14. package/dist/McpClientTool/types.js.map +1 -0
  15. package/dist/McpClientTool/utils.js +120 -0
  16. package/dist/McpClientTool/utils.js.map +1 -0
  17. package/dist/McpTrigger/FlushingTransport.js +61 -0
  18. package/dist/McpTrigger/FlushingTransport.js.map +1 -0
  19. package/dist/McpTrigger/McpServer.js +246 -0
  20. package/dist/McpTrigger/McpServer.js.map +1 -0
  21. package/dist/McpTrigger/McpTrigger.node.js +196 -0
  22. package/dist/McpTrigger/McpTrigger.node.js.map +1 -0
  23. package/dist/shared/descriptions.js +89 -0
  24. package/dist/shared/descriptions.js.map +1 -0
  25. package/dist/shared/helpers.js +47 -0
  26. package/dist/shared/helpers.js.map +1 -0
  27. package/dist/shared/httpProxyAgent.js +31 -0
  28. package/dist/shared/httpProxyAgent.js.map +1 -0
  29. package/dist/shared/logWrapper.js +31 -0
  30. package/dist/shared/logWrapper.js.map +1 -0
  31. package/dist/shared/schemaParsing.js +32 -0
  32. package/dist/shared/schemaParsing.js.map +1 -0
  33. package/dist/shared/sharedFields.js +41 -0
  34. package/dist/shared/sharedFields.js.map +1 -0
  35. package/dist/shared/types.js +17 -0
  36. package/dist/shared/types.js.map +1 -0
  37. package/dist/shared/utils.js +231 -0
  38. package/dist/shared/utils.js.map +1 -0
  39. package/jest.config.js +24 -0
  40. package/nodes/McpClient/McpClient.node.ts +327 -0
  41. package/nodes/McpClient/__test__/McpClient.node.test.ts +221 -0
  42. package/nodes/McpClient/__test__/utils.test.ts +302 -0
  43. package/nodes/McpClient/listSearch.ts +48 -0
  44. package/nodes/McpClient/resourceMapping.ts +48 -0
  45. package/nodes/McpClient/utils.ts +281 -0
  46. package/nodes/McpClientTool/McpClientTool.node.ts +468 -0
  47. package/nodes/McpClientTool/__test__/McpClientTool.node.test.ts +730 -0
  48. package/nodes/McpClientTool/loadOptions.ts +45 -0
  49. package/nodes/McpClientTool/types.ts +1 -0
  50. package/nodes/McpClientTool/utils.ts +116 -0
  51. package/nodes/McpTrigger/FlushingTransport.ts +61 -0
  52. package/nodes/McpTrigger/McpServer.ts +317 -0
  53. package/nodes/McpTrigger/McpTrigger.node.ts +204 -0
  54. package/nodes/McpTrigger/__test__/FlushingTransport.test.ts +102 -0
  55. package/nodes/McpTrigger/__test__/McpServer.test.ts +532 -0
  56. package/nodes/McpTrigger/__test__/McpTrigger.node.test.ts +171 -0
  57. package/nodes/mcp.dark.svg +7 -0
  58. package/nodes/mcp.svg +7 -0
  59. package/nodes/shared/__test__/utils.test.ts +318 -0
  60. package/nodes/shared/descriptions.ts +65 -0
  61. package/nodes/shared/helpers.ts +31 -0
  62. package/nodes/shared/httpProxyAgent.ts +11 -0
  63. package/nodes/shared/logWrapper.ts +13 -0
  64. package/nodes/shared/schemaParsing.ts +9 -0
  65. package/nodes/shared/sharedFields.ts +20 -0
  66. package/nodes/shared/types.ts +12 -0
  67. package/nodes/shared/utils.ts +296 -0
  68. package/officail/package.json +255 -0
  69. package/package.json +46 -0
  70. package/tsconfig.json +32 -0
  71. package/tsup.config.ts +11 -0
@@ -0,0 +1,221 @@
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
+ });
@@ -0,0 +1,302 @@
1
+ import type { JSONSchema7Definition, JSONSchema7 } from 'json-schema';
2
+
3
+ import {
4
+ convertJsonSchemaToResourceMapperFields,
5
+ jsonSchemaTypeToDefaultValue,
6
+ jsonSchemaTypeToFieldType,
7
+ } from '../utils';
8
+
9
+ describe('utils', () => {
10
+ describe('jsonSchemaTypeToFieldType', () => {
11
+ it.each([
12
+ [{ schema: { type: 'string', format: 'date-time' } as JSONSchema7, expected: 'dateTime' }],
13
+ [{ schema: { type: 'string' } as JSONSchema7, expected: 'string' }],
14
+ [{ schema: { type: 'number' } as JSONSchema7, expected: 'number' }],
15
+ [{ schema: { type: 'integer' } as JSONSchema7, expected: 'number' }],
16
+ [{ schema: { type: 'boolean' } as JSONSchema7, expected: 'boolean' }],
17
+ [{ schema: { type: 'array' } as JSONSchema7, expected: 'array' }],
18
+ [{ schema: { type: 'object' } as JSONSchema7, expected: 'object' }],
19
+ ])('should return the correct field type for the schema', ({ schema, expected }) => {
20
+ expect(jsonSchemaTypeToFieldType(schema)).toEqual(expected);
21
+ });
22
+ });
23
+
24
+ describe('jsonSchemaTypeToDefaultValue', () => {
25
+ it.each([
26
+ [{ schema: false as JSONSchema7Definition, expected: null }],
27
+ [{ schema: true as JSONSchema7Definition, expected: 'any' }],
28
+ [{ schema: { type: 'string' } as JSONSchema7Definition, expected: 'string' }],
29
+ [{ schema: { type: 'number' } as JSONSchema7Definition, expected: 0 }],
30
+ [{ schema: { type: 'integer' } as JSONSchema7Definition, expected: 0 }],
31
+ [{ schema: { type: 'number', minimum: -1 } as JSONSchema7Definition, expected: -1 }],
32
+ [{ schema: { type: 'number', maximum: 1 } as JSONSchema7Definition, expected: 1 }],
33
+ [{ schema: { type: 'boolean' } as JSONSchema7Definition, expected: false }],
34
+ [
35
+ {
36
+ schema: { type: 'string', format: 'date-time' } as JSONSchema7Definition,
37
+ expected: '2025-01-01T00:00:00Z',
38
+ },
39
+ ],
40
+ [
41
+ {
42
+ schema: { type: 'string', format: 'uri' } as JSONSchema7Definition,
43
+ expected: 'https://example.com',
44
+ },
45
+ ],
46
+ [
47
+ {
48
+ schema: { type: 'string', format: 'url' } as JSONSchema7Definition,
49
+ expected: 'https://example.com',
50
+ },
51
+ ],
52
+ [
53
+ {
54
+ schema: { type: 'string', format: 'date' } as JSONSchema7Definition,
55
+ expected: '2025-01-01',
56
+ },
57
+ ],
58
+ [
59
+ {
60
+ schema: { type: 'string', format: 'time' } as JSONSchema7Definition,
61
+ expected: '00:00:00',
62
+ },
63
+ ],
64
+ [{ schema: { type: 'array' } as JSONSchema7Definition, expected: [] }],
65
+ [
66
+ {
67
+ schema: { type: 'array', items: { type: 'string' } } as JSONSchema7Definition,
68
+ expected: ['string'],
69
+ },
70
+ ],
71
+ [
72
+ {
73
+ schema: {
74
+ type: 'array',
75
+ items: [{ type: 'number' }, { type: 'string' }],
76
+ } as JSONSchema7Definition,
77
+ expected: [0, 'string'],
78
+ },
79
+ ],
80
+ [
81
+ {
82
+ schema: {
83
+ type: 'object',
84
+ properties: {
85
+ name: { type: 'string' },
86
+ age: { type: 'number' },
87
+ },
88
+ required: ['name', 'age'],
89
+ additionalProperties: false,
90
+ } as JSONSchema7Definition,
91
+ expected: { name: 'string', age: 0 },
92
+ },
93
+ ],
94
+ [
95
+ {
96
+ schema: {
97
+ type: 'object',
98
+ properties: {
99
+ name: { type: 'string' },
100
+ age: { type: 'number' },
101
+ },
102
+ required: ['name', 'age'],
103
+ additionalProperties: { type: 'string' },
104
+ } as JSONSchema7Definition,
105
+ expected: { name: 'string', age: 0, '<additionalProperty>': 'string' },
106
+ },
107
+ ],
108
+ [
109
+ {
110
+ schema: {
111
+ type: 'string',
112
+ enum: ['foo', 'bar'],
113
+ } as JSONSchema7Definition,
114
+ expected: 'foo',
115
+ },
116
+ ],
117
+ [
118
+ {
119
+ schema: {
120
+ oneOf: [{ type: 'string' }, { type: 'number' }],
121
+ } as JSONSchema7Definition,
122
+ expected: 'string',
123
+ },
124
+ ],
125
+ [
126
+ {
127
+ schema: {
128
+ anyOf: [{ type: 'string' }, { type: 'number' }],
129
+ } as JSONSchema7Definition,
130
+ expected: 'string',
131
+ },
132
+ ],
133
+ [
134
+ {
135
+ schema: {
136
+ allOf: [
137
+ {
138
+ type: 'object',
139
+ properties: {
140
+ age: { type: 'number' },
141
+ },
142
+ required: ['age'],
143
+ additionalProperties: false,
144
+ },
145
+ {
146
+ type: 'object',
147
+ properties: {
148
+ name: { type: 'string' },
149
+ },
150
+ required: ['name'],
151
+ additionalProperties: false,
152
+ },
153
+ ],
154
+ } as JSONSchema7Definition,
155
+ expected: { age: 0, name: 'string' },
156
+ },
157
+ ],
158
+ ])('should return the correct default value for the schema', ({ schema, expected }) => {
159
+ expect(jsonSchemaTypeToDefaultValue(schema)).toEqual(expected);
160
+ });
161
+ });
162
+
163
+ describe('convertJsonSchemaToResourceMapperFields', () => {
164
+ it.each([
165
+ [
166
+ {
167
+ schema: { type: 'string' } as JSONSchema7,
168
+ expected: [],
169
+ },
170
+ ],
171
+ [
172
+ {
173
+ schema: { type: 'object' } as JSONSchema7,
174
+ expected: [],
175
+ },
176
+ ],
177
+ [
178
+ {
179
+ schema: {
180
+ type: 'object',
181
+ properties: { name: { type: 'string' }, age: { type: 'number' } },
182
+ required: ['name'],
183
+ } as JSONSchema7,
184
+ expected: [
185
+ {
186
+ id: 'name',
187
+ displayName: 'name',
188
+ defaultMatch: false,
189
+ required: true,
190
+ display: true,
191
+ type: 'string',
192
+ },
193
+ {
194
+ id: 'age',
195
+ displayName: 'age',
196
+ defaultMatch: false,
197
+ required: false,
198
+ display: true,
199
+ type: 'number',
200
+ },
201
+ ],
202
+ },
203
+ ],
204
+ [
205
+ {
206
+ schema: { type: 'object', properties: { name: true, age: false } } as JSONSchema7,
207
+ expected: [
208
+ {
209
+ id: 'name',
210
+ displayName: 'name',
211
+ defaultMatch: false,
212
+ required: false,
213
+ display: true,
214
+ type: 'string',
215
+ },
216
+ ],
217
+ },
218
+ ],
219
+ ])(
220
+ 'should return the correct resource mapper fields for the schema',
221
+ ({ schema, expected }) => {
222
+ expect(convertJsonSchemaToResourceMapperFields(schema)).toEqual(expected);
223
+ },
224
+ );
225
+
226
+ it.each([
227
+ [
228
+ {
229
+ schema: {
230
+ type: 'object',
231
+ properties: { names: { type: 'array', items: { type: 'string' } } },
232
+ required: ['names'],
233
+ } as JSONSchema7,
234
+ expected: [
235
+ {
236
+ id: 'names',
237
+ displayName: 'names',
238
+ defaultMatch: false,
239
+ required: true,
240
+ display: true,
241
+ type: 'array',
242
+ defaultValue: JSON.stringify(['string'], null, 2),
243
+ },
244
+ ],
245
+ },
246
+ ],
247
+ [
248
+ {
249
+ schema: {
250
+ type: 'object',
251
+ properties: {
252
+ user: {
253
+ type: 'object',
254
+ properties: { name: { type: 'string' }, age: { type: 'number' } },
255
+ },
256
+ },
257
+ required: ['user'],
258
+ } as JSONSchema7,
259
+ expected: [
260
+ {
261
+ id: 'user',
262
+ displayName: 'user',
263
+ defaultMatch: false,
264
+ required: true,
265
+ display: true,
266
+ type: 'object',
267
+ defaultValue: JSON.stringify({ name: 'string', age: 0 }, null, 2),
268
+ },
269
+ ],
270
+ },
271
+ ],
272
+ ])('should add defaultValue for arrays and objects', ({ schema, expected }) => {
273
+ expect(convertJsonSchemaToResourceMapperFields(schema)).toEqual(expected);
274
+ });
275
+
276
+ it('should add options for enums', () => {
277
+ const schema = {
278
+ type: 'object',
279
+ properties: { color: { type: 'string', enum: ['red', 'green', 'blue'] } },
280
+ } as JSONSchema7;
281
+ const expected = [
282
+ {
283
+ id: 'color',
284
+ displayName: 'color',
285
+ defaultMatch: false,
286
+ required: false,
287
+ display: true,
288
+ type: 'options',
289
+ options: [
290
+ // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
291
+ { name: 'red', value: 'red' },
292
+ // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
293
+ { name: 'green', value: 'green' },
294
+ // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
295
+ { name: 'blue', value: 'blue' },
296
+ ],
297
+ },
298
+ ];
299
+ expect(convertJsonSchemaToResourceMapperFields(schema)).toEqual(expected);
300
+ });
301
+ });
302
+ });
@@ -0,0 +1,48 @@
1
+ import type { ILoadOptionsFunctions, INodeListSearchResult } from 'n8n-workflow';
2
+
3
+ import type { McpAuthenticationOption, McpServerTransport } from '../shared/types';
4
+ import {
5
+ connectMcpClient,
6
+ getAuthHeaders,
7
+ mapToNodeOperationError,
8
+ tryRefreshOAuth2Token,
9
+ } from '../shared/utils';
10
+
11
+ export async function getTools(
12
+ this: ILoadOptionsFunctions,
13
+ filter?: string,
14
+ paginationToken?: string,
15
+ ): Promise<INodeListSearchResult> {
16
+ const authentication = this.getNodeParameter('authentication') as McpAuthenticationOption;
17
+ const serverTransport = this.getNodeParameter('serverTransport') as McpServerTransport;
18
+ const endpointUrl = this.getNodeParameter('endpointUrl') as string;
19
+ const node = this.getNode();
20
+ const { headers } = await getAuthHeaders(this, authentication);
21
+ const client = await connectMcpClient({
22
+ serverTransport,
23
+ endpointUrl,
24
+ headers,
25
+ name: node.type,
26
+ version: node.typeVersion,
27
+ onUnauthorized: async (headers) => await tryRefreshOAuth2Token(this, authentication, headers),
28
+ });
29
+
30
+ if (!client.ok) {
31
+ throw mapToNodeOperationError(node, client.error);
32
+ }
33
+
34
+ const result = await client.result.listTools({ cursor: paginationToken });
35
+ const tools = filter
36
+ ? result.tools.filter((tool) => tool.name.toLowerCase().includes(filter.toLowerCase()))
37
+ : result.tools;
38
+
39
+ return {
40
+ results: tools.map((tool) => ({
41
+ name: tool.name,
42
+ value: tool.name,
43
+ description: tool.description,
44
+ inputSchema: tool.inputSchema,
45
+ })),
46
+ paginationToken: result.nextCursor,
47
+ };
48
+ }
@@ -0,0 +1,48 @@
1
+ import type { ILoadOptionsFunctions, ResourceMapperFields } from 'n8n-workflow';
2
+ import { NodeOperationError } from 'n8n-workflow';
3
+
4
+ import { convertJsonSchemaToResourceMapperFields } from './utils';
5
+ import type { McpAuthenticationOption, McpServerTransport } from '../shared/types';
6
+ import {
7
+ getAuthHeaders,
8
+ connectMcpClient,
9
+ getAllTools,
10
+ tryRefreshOAuth2Token,
11
+ mapToNodeOperationError,
12
+ } from '../shared/utils';
13
+
14
+ export async function getToolParameters(
15
+ this: ILoadOptionsFunctions,
16
+ ): Promise<ResourceMapperFields> {
17
+ const toolId = this.getNodeParameter('tool', 0, {
18
+ extractValue: true,
19
+ }) as string;
20
+ const authentication = this.getNodeParameter('authentication') as McpAuthenticationOption;
21
+ const serverTransport = this.getNodeParameter('serverTransport') as McpServerTransport;
22
+ const endpointUrl = this.getNodeParameter('endpointUrl') as string;
23
+ const node = this.getNode();
24
+ const { headers } = await getAuthHeaders(this, authentication);
25
+ const client = await connectMcpClient({
26
+ serverTransport,
27
+ endpointUrl,
28
+ headers,
29
+ name: node.type,
30
+ version: node.typeVersion,
31
+ onUnauthorized: async (headers) => await tryRefreshOAuth2Token(this, authentication, headers),
32
+ });
33
+
34
+ if (!client.ok) {
35
+ throw mapToNodeOperationError(node, client.error);
36
+ }
37
+
38
+ const result = await getAllTools(client.result);
39
+ const tool = result.find((tool) => tool.name === toolId);
40
+ if (!tool) {
41
+ throw new NodeOperationError(this.getNode(), 'Tool not found');
42
+ }
43
+
44
+ const fields = convertJsonSchemaToResourceMapperFields(tool.inputSchema);
45
+ return {
46
+ fields,
47
+ };
48
+ }