n8n-nodes-smart-browser-automation 1.6.25 → 1.6.27

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.
@@ -1,12 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.SmartBrowserAutomation = void 0;
7
4
  const n8n_workflow_1 = require("n8n-workflow");
8
- const BrowserSessionManager_1 = __importDefault(require("./BrowserSessionManager"));
9
5
  const jsonSchemaToZod_1 = require("./jsonSchemaToZod");
6
+ const McpConnection_1 = require("./utils/McpConnection");
10
7
  class SmartBrowserAutomation {
11
8
  description = {
12
9
  displayName: 'Smart Browser Automation',
@@ -97,9 +94,18 @@ class SmartBrowserAutomation {
97
94
  async getAvailableTools() {
98
95
  try {
99
96
  const credentials = await this.getCredentials('smartBrowserAutomationApi');
100
- const sessionManager = BrowserSessionManager_1.default.getInstance();
101
- await sessionManager.initialize(credentials.mcpEndpoint, false, '');
102
- const tools = await sessionManager.listTools();
97
+ // Removed sessionManager usage
98
+ // Use temporary config for listing options
99
+ const config = {
100
+ mcpEndpoint: (credentials.mcpEndpoint || '').trim(),
101
+ browserMode: credentials.browserMode,
102
+ };
103
+ const { client, mcpTools, error } = await (0, McpConnection_1.connectAndGetTools)(this, config);
104
+ if (error)
105
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message);
106
+ // Close immediately as we just need list
107
+ await client.close();
108
+ const tools = mcpTools;
103
109
  return tools.map((tool) => ({
104
110
  name: tool.name.split('_').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' '),
105
111
  value: tool.name,
@@ -118,13 +124,25 @@ class SmartBrowserAutomation {
118
124
  const items = this.getInputData();
119
125
  const returnData = [];
120
126
  const credentials = await this.getCredentials('smartBrowserAutomationApi');
121
- const sessionManager = BrowserSessionManager_1.default.getInstance();
122
127
  const operation = this.getNodeParameter('operation', 0);
123
128
  // Handle List Tools operation
124
129
  if (operation === 'listTools') {
125
130
  try {
126
- await sessionManager.initialize(credentials.mcpEndpoint, false, '');
127
- const tools = await sessionManager.listTools();
131
+ // Use new McpConnection logic
132
+ const mcpOverride = this.getNodeParameter('mcpEndpointOverride', 0, '');
133
+ const cdpOverride = this.getNodeParameter('cdpOverride', 0, '');
134
+ const browserMode = credentials.browserMode;
135
+ const config = {
136
+ mcpEndpoint: (mcpOverride || credentials.mcpEndpoint || '').trim(),
137
+ browserMode: browserMode,
138
+ cdpEndpoint: (cdpOverride || credentials.cdpEndpoint || '').trim(),
139
+ };
140
+ const { client, mcpTools, error } = await (0, McpConnection_1.connectAndGetTools)(this, config);
141
+ if (error)
142
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message);
143
+ // Close client after listing since this is just a listing operation
144
+ await client.close();
145
+ const tools = mcpTools;
128
146
  returnData.push({
129
147
  json: {
130
148
  tools: tools.map((tool) => ({
@@ -191,10 +209,19 @@ class SmartBrowserAutomation {
191
209
  }
192
210
  try {
193
211
  // Initialize session if not ready
194
- if (!sessionManager.isReady()) {
195
- const cdpUrl = cdpOverride || credentials.cdpEndpoint || '';
196
- await sessionManager.initialize(credentials.mcpEndpoint, !!cdpUrl, cdpUrl);
197
- }
212
+ // Connect via McpConnection
213
+ const mcpOverride = this.getNodeParameter('mcpEndpointOverride', 0, '');
214
+ const browserMode = credentials.browserMode; // from credentials
215
+ // We might need to handle CDP Override if it's passed in tool params (legacy)
216
+ const cdpUrl = cdpOverride || credentials.cdpEndpoint || '';
217
+ const config = {
218
+ mcpEndpoint: (mcpOverride || credentials.mcpEndpoint || '').trim(),
219
+ browserMode: browserMode,
220
+ cdpEndpoint: cdpUrl.trim(),
221
+ };
222
+ const { client, mcpTools, error } = await (0, McpConnection_1.connectAndGetTools)(this, config);
223
+ if (error)
224
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message);
198
225
  // Ensure toolParams is an object
199
226
  if (typeof toolParams !== 'object' ||
200
227
  toolParams === null ||
@@ -207,9 +234,15 @@ class SmartBrowserAutomation {
207
234
  if (!endpoint) {
208
235
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'CDP endpoint is required. Provide it in tool parameters or CDP Endpoint Override field.');
209
236
  }
210
- // Reinitialize with new CDP endpoint
211
- await sessionManager.close();
212
- await sessionManager.initialize(credentials.mcpEndpoint, true, endpoint);
237
+ // Reinitialize with new CDP endpoint - Create new client
238
+ await client.close(); // Close existing listing client
239
+ const newConfig = { ...config, cdpEndpoint: endpoint };
240
+ const { client: newClient, error: connError } = await (0, McpConnection_1.connectAndGetTools)(this, newConfig);
241
+ if (connError) {
242
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to reconnect with new CDP endpoint: ${connError.message}`);
243
+ }
244
+ // We just verify connection here via ListTools or similar implicitly done by connectAndGetTools
245
+ await newClient.close();
213
246
  returnData.push({
214
247
  json: {
215
248
  result: {
@@ -221,7 +254,7 @@ class SmartBrowserAutomation {
221
254
  return [returnData];
222
255
  }
223
256
  // Validate tool exists before executing
224
- const availableTools = await sessionManager.listTools();
257
+ const availableTools = mcpTools;
225
258
  const selectedTool = availableTools.find((tool) => tool.name === toolName);
226
259
  const toolExists = Boolean(selectedTool);
227
260
  if (!toolExists) {
@@ -240,7 +273,11 @@ class SmartBrowserAutomation {
240
273
  }
241
274
  }
242
275
  // Execute the tool via MCP
243
- const result = await sessionManager.callTool(toolName, toolParams);
276
+ const result = await client.callTool({
277
+ name: toolName,
278
+ arguments: toolParams
279
+ });
280
+ await client.close();
244
281
  returnData.push({
245
282
  json: { result },
246
283
  });
@@ -1,80 +1,11 @@
1
1
  "use strict";
2
2
  /* eslint-disable n8n-nodes-base/node-class-description-inputs-wrong-regular-node */
3
3
  /* eslint-disable n8n-nodes-base/node-class-description-outputs-wrong */
4
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
- if (k2 === undefined) k2 = k;
6
- var desc = Object.getOwnPropertyDescriptor(m, k);
7
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
- desc = { enumerable: true, get: function() { return m[k]; } };
9
- }
10
- Object.defineProperty(o, k2, desc);
11
- }) : (function(o, m, k, k2) {
12
- if (k2 === undefined) k2 = k;
13
- o[k2] = m[k];
14
- }));
15
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
- Object.defineProperty(o, "default", { enumerable: true, value: v });
17
- }) : function(o, v) {
18
- o["default"] = v;
19
- });
20
- var __importStar = (this && this.__importStar) || (function () {
21
- var ownKeys = function(o) {
22
- ownKeys = Object.getOwnPropertyNames || function (o) {
23
- var ar = [];
24
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
- return ar;
26
- };
27
- return ownKeys(o);
28
- };
29
- return function (mod) {
30
- if (mod && mod.__esModule) return mod;
31
- var result = {};
32
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
- __setModuleDefault(result, mod);
34
- return result;
35
- };
36
- })();
37
- var __importDefault = (this && this.__importDefault) || function (mod) {
38
- return (mod && mod.__esModule) ? mod : { "default": mod };
39
- };
40
4
  Object.defineProperty(exports, "__esModule", { value: true });
41
5
  exports.SmartBrowserAutomationTools = void 0;
42
6
  const n8n_workflow_1 = require("n8n-workflow");
43
- const BrowserSessionManager_1 = __importDefault(require("./BrowserSessionManager"));
44
- const jsonSchemaToZod_1 = require("./jsonSchemaToZod");
45
- async function importDynamicStructuredTool() {
46
- try {
47
- return await Promise.resolve().then(() => __importStar(require('langchain/tools')));
48
- }
49
- catch {
50
- // Some n8n installations ship a langchain build where subpath exports differ.
51
- return await Promise.resolve().then(() => __importStar(require('@langchain/core/tools')));
52
- }
53
- }
54
- async function importToolkitBase() {
55
- // n8n uses Toolkit from @langchain/classic/agents internally.
56
- return await Promise.resolve().then(() => __importStar(require('@langchain/classic/agents')));
57
- }
58
- function formatMcpToolResult(result) {
59
- if (result === null || result === undefined)
60
- return '';
61
- if (typeof result === 'string')
62
- return result;
63
- const content = result?.content;
64
- if (Array.isArray(content)) {
65
- const texts = content
66
- .map((c) => (c?.type === 'text' ? String(c?.text ?? '') : ''))
67
- .filter((t) => t.length > 0);
68
- if (texts.length)
69
- return texts.join('\n');
70
- }
71
- try {
72
- return JSON.stringify(result);
73
- }
74
- catch {
75
- return String(result);
76
- }
77
- }
7
+ const McpUtils_1 = require("./utils/McpUtils");
8
+ const McpConnection_1 = require("./utils/McpConnection");
78
9
  class SmartBrowserAutomationTools {
79
10
  description = {
80
11
  displayName: 'Smart Browser Automation Tools',
@@ -117,46 +48,60 @@ class SmartBrowserAutomationTools {
117
48
  async supplyData(itemIndex) {
118
49
  const node = this.getNode();
119
50
  const credentials = await this.getCredentials('smartBrowserAutomationApi');
120
- const sessionManager = BrowserSessionManager_1.default.getInstance();
121
51
  const mcpOverride = this.getNodeParameter('mcpEndpointOverride', itemIndex, '');
122
52
  const cdpOverride = this.getNodeParameter('cdpOverride', itemIndex, '');
123
- const useCDP = credentials.browserMode === 'cdp';
124
- const cdpUrl = (cdpOverride || credentials.cdpEndpoint || '').trim();
125
- const mcpEndpoint = (mcpOverride || credentials.mcpEndpoint || '').trim();
126
- if (!mcpEndpoint) {
127
- throw new n8n_workflow_1.NodeOperationError(node, 'MCP Endpoint is required in credentials', {
128
- itemIndex,
129
- description: 'Configure MCP Endpoint in Smart Browser Automation credentials (e.g. http://localhost:3000 or https://host/sse)',
130
- });
131
- }
132
- // Basic pre-validation so users see a clear error in the UI
133
- if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(mcpEndpoint)) {
134
- throw new n8n_workflow_1.NodeOperationError(node, `MCP Endpoint looks incomplete: "${mcpEndpoint}" (add http:// or https://)`, { itemIndex });
135
- }
136
- try {
137
- await sessionManager.initialize(mcpEndpoint, useCDP, useCDP ? cdpUrl : undefined);
53
+ const browserMode = credentials.browserMode;
54
+ const useCDP = browserMode === 'cdp';
55
+ const config = {
56
+ mcpEndpoint: (mcpOverride || credentials.mcpEndpoint || '').trim(),
57
+ browserMode: browserMode,
58
+ cdpEndpoint: (cdpOverride || credentials.cdpEndpoint || '').trim(),
59
+ timeout: 60000 // Default timeout
60
+ };
61
+ if (!config.mcpEndpoint) {
62
+ throw new n8n_workflow_1.NodeOperationError(node, 'MCP Endpoint is required in credentials', { itemIndex });
138
63
  }
139
- catch (error) {
140
- const e = error;
141
- const mode = useCDP ? 'cdp' : 'launch';
142
- const details = `mcp=${mcpEndpoint} | mode=${mode}${useCDP ? ` | cdp=${cdpUrl || 'unset'}` : ''}`;
143
- throw new n8n_workflow_1.NodeOperationError(node, `Failed to connect to MCP. ${details}. Original error: ${e.message}`, {
144
- itemIndex,
145
- description: details,
146
- });
64
+ // Connect to MCP Server
65
+ const { client, mcpTools, error } = await (0, McpConnection_1.connectAndGetTools)(this, config);
66
+ if (error) {
67
+ throw new n8n_workflow_1.NodeOperationError(node, error.message, { itemIndex });
147
68
  }
148
- const mcpTools = await sessionManager.listTools();
149
- if (!mcpTools.length) {
69
+ if (!mcpTools?.length) {
150
70
  throw new n8n_workflow_1.NodeOperationError(node, 'MCP Server returned no tools', { itemIndex });
151
71
  }
152
- // Filter out 'browser_connect_cdp' from the tools list if we are managing it automatically
153
- // This prevents the AI from trying to connect manually and confusing the state
154
- const exposedTools = mcpTools.filter((t) => t.name !== 'browser_connect_cdp');
155
- // Emit a debug item on the Main output so users can see endpoints and tool list in the UI
72
+ // *** Smart Browser Automation Specific Logic ***
73
+ // If we are in CDP mode, we MUST ensure the connection is established immediately.
74
+ // We call 'browser_connect_cdp' now, during initialization.
75
+ if (useCDP) {
76
+ const connectToolName = 'browser_connect_cdp';
77
+ const connectTool = mcpTools.find(t => t.name === connectToolName);
78
+ if (connectTool && config.cdpEndpoint) {
79
+ try {
80
+ console.log(`[Init] Auto-connecting to CDP: ${config.cdpEndpoint}`);
81
+ const connectResult = await client.callTool({
82
+ name: connectToolName,
83
+ arguments: { endpoint: config.cdpEndpoint }
84
+ });
85
+ if (connectResult.isError) {
86
+ throw new n8n_workflow_1.NodeOperationError(node, `CDP Connection failed: ${JSON.stringify(connectResult)}`, { itemIndex });
87
+ }
88
+ console.log(`[Init] Connected successfully.`);
89
+ }
90
+ catch (connErr) {
91
+ throw new n8n_workflow_1.NodeOperationError(node, `Failed to auto-connect to CDP: ${connErr.message}`, { itemIndex });
92
+ }
93
+ }
94
+ else if (!config.cdpEndpoint) {
95
+ throw new n8n_workflow_1.NodeOperationError(node, 'CDP Endpoint is missing for CDP mode connection.', { itemIndex });
96
+ }
97
+ }
98
+ // Filter out the connect tool so the AI doesn't see it
99
+ const exposedTools = mcpTools.filter(t => t.name !== 'browser_connect_cdp');
100
+ // Debug Output
156
101
  const debugJson = {
157
- mcpEndpoint,
158
- browserMode: credentials.browserMode,
159
- cdpEndpoint: useCDP ? cdpUrl : undefined,
102
+ mcpEndpoint: config.mcpEndpoint,
103
+ browserMode: config.browserMode,
104
+ cdpEndpoint: config.cdpEndpoint,
160
105
  initializationMode: 'strict_cdp_connect',
161
106
  toolCount: exposedTools.length,
162
107
  tools: exposedTools.map((t) => ({
@@ -164,79 +109,32 @@ class SmartBrowserAutomationTools {
164
109
  description: t.description ?? '',
165
110
  })),
166
111
  };
112
+ // Using 'this.addOutputData' as per recent fix
167
113
  this.addOutputData(n8n_workflow_1.NodeConnectionTypes.Main, 0, [[{ json: debugJson }]]);
168
- const { DynamicStructuredTool } = await importDynamicStructuredTool();
169
- const { Toolkit } = await importToolkitBase();
170
- const ctx = this;
171
- const tools = await Promise.all(exposedTools.map(async (tool) => {
172
- const schema = await (0, jsonSchemaToZod_1.jsonSchemaToZod)(tool.inputSchema);
173
- const toolName = tool.name;
174
- const toolDescription = (tool.description ?? '');
175
- return new DynamicStructuredTool({
176
- name: toolName,
177
- description: toolDescription,
178
- schema,
179
- func: async (args) => {
180
- // Make tool calls visible in n8n execution UI
181
- const input = {
182
- tool: toolName,
183
- arguments: args,
184
- };
185
- let runIndex = 0;
186
- try {
187
- runIndex = ctx.getNextRunIndex?.() ?? 0;
188
- }
189
- catch { }
190
- const { index } = ctx.addInputData(n8n_workflow_1.NodeConnectionTypes.AiTool, [[{ json: input }]], runIndex);
191
- try {
192
- console.log(`[AI Agent] Executing tool ${toolName} with args:`, JSON.stringify(args));
193
- const result = await sessionManager.callTool(toolName, args);
194
- const output = { tool: toolName, result };
195
- // Ensure the output is an array of INodeExecutionData
196
- const executionData = [[{ json: output }]];
197
- ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, index, executionData);
198
- return formatMcpToolResult(result);
199
- }
200
- catch (e) {
201
- const errorMsg = e.message || String(e);
202
- console.error(`[AI Agent] Tool ${toolName} failed:`, errorMsg);
203
- // Fix: properly format error for addOutputData
204
- const errorOutput = [[{
205
- json: {
206
- tool: toolName,
207
- error: errorMsg,
208
- stack: e.stack
209
- }
210
- }]];
211
- // Safely attempt to report error to UI
212
- try {
213
- ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, index, errorOutput);
214
- }
215
- catch (addError) {
216
- console.error('[AI Agent] Failed to add error output:', addError);
217
- }
218
- // Re-throw so the AI Agent knows it failed
219
- throw e;
220
- }
221
- },
222
- metadata: { isFromToolkit: true },
223
- });
114
+ // Create LangChain Tools
115
+ // Map MCP tools to DynamicStructuredTools using the official utility pattern
116
+ const dynamicTools = await Promise.all(exposedTools.map(async (tool) => {
117
+ return await (0, McpUtils_1.mcpToolToDynamicTool)(tool, (0, McpUtils_1.createCallTool)(tool.name, client, 60000, (errorMessage) => {
118
+ // Error callback similar to official implementation
119
+ const errorNode = new n8n_workflow_1.NodeOperationError(node, errorMessage, { itemIndex });
120
+ // We need to report this error on the AiTool output if possible,
121
+ // or maybe just log it since the createCallTool helper handles return values.
122
+ // The official node uses void this.addOutputData(...)
123
+ try {
124
+ this.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, itemIndex, errorNode);
125
+ }
126
+ catch (e) {
127
+ console.error('Failed to report error to AiTool output', e);
128
+ }
129
+ }));
224
130
  }));
225
- class SmartBrowserAutomationToolkit extends Toolkit {
226
- _tools;
227
- constructor(_tools) {
228
- super();
229
- this._tools = _tools;
230
- }
231
- getTools() {
232
- return this._tools;
233
- }
234
- }
131
+ const toolkit = new McpUtils_1.McpToolkit(dynamicTools);
132
+ // Return the toolkit and the cleanup function
235
133
  return {
236
- response: new SmartBrowserAutomationToolkit(tools),
134
+ response: toolkit,
237
135
  closeFunction: async () => {
238
- // Called by n8n when Agent execution finishes
239
- await sessionManager.close();
136
+ console.log('[Cleanup] Closing MCP Client');
137
+ await client.close();
240
138
  },
241
139
  };
242
140
  }
@@ -0,0 +1,18 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { type ISupplyDataFunctions, type IExecuteFunctions } from 'n8n-workflow';
3
+ import { type McpTool } from './McpUtils';
4
+ export interface McpConfig {
5
+ mcpEndpoint: string;
6
+ browserMode: string;
7
+ cdpEndpoint?: string;
8
+ transportType?: 'sse' | 'stdio';
9
+ timeout?: number;
10
+ }
11
+ export declare function connectAndGetTools(_ctx: ISupplyDataFunctions | IExecuteFunctions, config: McpConfig): Promise<{
12
+ client: Client;
13
+ mcpTools: McpTool[];
14
+ error?: {
15
+ error: string;
16
+ message: string;
17
+ };
18
+ }>;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.connectAndGetTools = connectAndGetTools;
4
+ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
5
+ const sse_js_1 = require("@modelcontextprotocol/sdk/client/sse.js");
6
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
7
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
8
+ async function connectAndGetTools(_ctx, config) {
9
+ let client;
10
+ let Transport;
11
+ try {
12
+ const mcpEndpoint = config.mcpEndpoint.trim();
13
+ const isUrl = mcpEndpoint.startsWith('http://') || mcpEndpoint.startsWith('https://');
14
+ if (isUrl) {
15
+ // Check for SSE
16
+ let urlIsSse = false;
17
+ try {
18
+ const url = new URL(mcpEndpoint);
19
+ urlIsSse = /(^|\/)sse\/?(\?|#|$)/i.test(url.pathname);
20
+ }
21
+ catch (e) {
22
+ return {
23
+ client: null,
24
+ mcpTools: [],
25
+ error: { error: 'Invalid URL', message: `Invalid MCP Endpoint URL: ${mcpEndpoint}` }
26
+ };
27
+ }
28
+ if (urlIsSse) {
29
+ Transport = new sse_js_1.SSEClientTransport(new URL(mcpEndpoint));
30
+ }
31
+ else {
32
+ Transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(mcpEndpoint));
33
+ }
34
+ }
35
+ else {
36
+ // Stdio
37
+ Transport = new stdio_js_1.StdioClientTransport({
38
+ command: 'node',
39
+ args: [mcpEndpoint],
40
+ // Pass env if needed, including CDP_URL if we want the server to pick it up via env
41
+ env: {
42
+ ...process.env,
43
+ ...(config.cdpEndpoint ? { CDP_URL: config.cdpEndpoint } : {}),
44
+ },
45
+ });
46
+ }
47
+ client = new index_js_1.Client({ name: 'n8n-smart-browser-automation', version: '1.0.0' }, { capabilities: {} });
48
+ await client.connect(Transport);
49
+ // List tools
50
+ const result = await client.listTools();
51
+ const tools = (result.tools || []);
52
+ return { client, mcpTools: tools };
53
+ }
54
+ catch (err) {
55
+ return {
56
+ client: null,
57
+ mcpTools: [],
58
+ error: { error: 'Connection Failed', message: err.message || String(err) }
59
+ };
60
+ }
61
+ }
@@ -0,0 +1,32 @@
1
+ import { DynamicStructuredTool } from '@langchain/core/tools';
2
+ import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import { Toolkit } from '@langchain/classic/agents';
4
+ import { type IDataObject } from 'n8n-workflow';
5
+ export type McpToolIncludeMode = 'all' | 'selected' | 'except';
6
+ export interface McpTool {
7
+ name: string;
8
+ description?: string;
9
+ inputSchema?: any;
10
+ }
11
+ export declare function getSelectedTools({ mode, includeTools, excludeTools, tools, }: {
12
+ mode: McpToolIncludeMode;
13
+ includeTools?: string[];
14
+ excludeTools?: string[];
15
+ tools: McpTool[];
16
+ }): McpTool[];
17
+ export declare const getErrorDescriptionFromToolCall: (result: unknown) => string | undefined;
18
+ export declare const createCallTool: (name: string, client: Client, timeout: number, onError: (error: string) => void) => (args: IDataObject) => Promise<{} | null>;
19
+ export declare function mcpToolToDynamicTool(tool: McpTool, onCallTool: (args: any) => Promise<any>): Promise<DynamicStructuredTool>;
20
+ /**
21
+ * Wraps a DynamicStructuredTool to log its execution to the n8n AI Tool output.
22
+ * This ensures the user sees the tool input and output in the n8n UI.
23
+ */
24
+ export declare function logWrapper(tool: DynamicStructuredTool, nodeCtx: {
25
+ addOutputData: Function;
26
+ addInputData: Function;
27
+ getNextRunIndex?: Function;
28
+ }): DynamicStructuredTool;
29
+ export declare class McpToolkit extends Toolkit {
30
+ tools: DynamicStructuredTool[];
31
+ constructor(tools: DynamicStructuredTool[]);
32
+ }
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.McpToolkit = exports.createCallTool = exports.getErrorDescriptionFromToolCall = void 0;
4
+ exports.getSelectedTools = getSelectedTools;
5
+ exports.mcpToolToDynamicTool = mcpToolToDynamicTool;
6
+ exports.logWrapper = logWrapper;
7
+ const tools_1 = require("@langchain/core/tools");
8
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
9
+ const agents_1 = require("@langchain/classic/agents");
10
+ const zod_1 = require("zod");
11
+ const jsonSchemaToZod_1 = require("../jsonSchemaToZod");
12
+ function getSelectedTools({ mode, includeTools, excludeTools, tools, }) {
13
+ switch (mode) {
14
+ case 'selected': {
15
+ if (!includeTools?.length)
16
+ return tools;
17
+ const include = new Set(includeTools);
18
+ return tools.filter((tool) => include.has(tool.name));
19
+ }
20
+ case 'except': {
21
+ const except = new Set(excludeTools ?? []);
22
+ return tools.filter((tool) => !except.has(tool.name));
23
+ }
24
+ case 'all':
25
+ default:
26
+ return tools;
27
+ }
28
+ }
29
+ const getErrorDescriptionFromToolCall = (result) => {
30
+ if (result && typeof result === 'object') {
31
+ if ('content' in result && Array.isArray(result.content)) {
32
+ const errorMessage = result.content.find((content) => content && typeof content === 'object' && typeof content.text === 'string')?.text;
33
+ return errorMessage;
34
+ }
35
+ else if ('toolResult' in result && typeof result.toolResult === 'string') {
36
+ return result.toolResult;
37
+ }
38
+ if ('message' in result && typeof result.message === 'string') {
39
+ return result.message;
40
+ }
41
+ }
42
+ return undefined;
43
+ };
44
+ exports.getErrorDescriptionFromToolCall = getErrorDescriptionFromToolCall;
45
+ const createCallTool = (name, client, timeout, onError) => async (args) => {
46
+ let result;
47
+ function handleError(error) {
48
+ const errorDescription = (0, exports.getErrorDescriptionFromToolCall)(error) ?? `Failed to execute tool "${name}"`;
49
+ onError(errorDescription);
50
+ return errorDescription;
51
+ }
52
+ try {
53
+ result = await client.callTool({ name, arguments: args }, types_js_1.CompatibilityCallToolResultSchema, {
54
+ timeout,
55
+ });
56
+ }
57
+ catch (error) {
58
+ return handleError(error);
59
+ }
60
+ if (result.isError) {
61
+ return handleError(result);
62
+ }
63
+ if (result.toolResult !== undefined) {
64
+ return result.toolResult;
65
+ }
66
+ if (result.content !== undefined) {
67
+ return result.content;
68
+ }
69
+ return result;
70
+ };
71
+ exports.createCallTool = createCallTool;
72
+ async function mcpToolToDynamicTool(tool, onCallTool) {
73
+ // Use our existing jsonSchemaToZod implementation
74
+ const rawSchema = await (0, jsonSchemaToZod_1.jsonSchemaToZod)(tool.inputSchema);
75
+ // Ensure we always have an object schema for structured tools
76
+ const objectSchema = rawSchema instanceof zod_1.z.ZodObject ? rawSchema : zod_1.z.object({ value: rawSchema });
77
+ return new tools_1.DynamicStructuredTool({
78
+ name: tool.name,
79
+ description: tool.description ?? '',
80
+ schema: objectSchema,
81
+ func: onCallTool,
82
+ metadata: { isFromToolkit: true },
83
+ });
84
+ }
85
+ /**
86
+ * Wraps a DynamicStructuredTool to log its execution to the n8n AI Tool output.
87
+ * This ensures the user sees the tool input and output in the n8n UI.
88
+ */
89
+ function logWrapper(tool, nodeCtx) {
90
+ // Access internal properties safely via casting if needed
91
+ const toolAny = tool;
92
+ const originalFunc = toolAny.func;
93
+ toolAny.func = async (args) => {
94
+ const toolName = toolAny.name;
95
+ const input = {
96
+ tool: toolName,
97
+ arguments: args,
98
+ };
99
+ let runIndex = 0;
100
+ try {
101
+ // n8n type hack: try to get run index if available
102
+ runIndex = nodeCtx.getNextRunIndex?.() ?? 0;
103
+ }
104
+ catch { }
105
+ // Log Input
106
+ // 2 is the index for NodeConnectionTypes.AiTool usually, check your node definition
107
+ // In SmartBrowserAutomationTools, output[0] = AiTool, output[1] = Main.
108
+ // Wait, NodeConnectionTypes are strings "ai_tool", "main".
109
+ // The `addInputData` returns { index, runIndex }.
110
+ // We need to use the connection name or index.
111
+ // "ai_tool" is typically index 0 if listed first in outputs.
112
+ const { index } = nodeCtx.addInputData('ai_tool', [[{ json: input }]], runIndex);
113
+ try {
114
+ const result = await originalFunc(args);
115
+ const output = {
116
+ tool: toolName,
117
+ result: result,
118
+ };
119
+ // Log Output
120
+ // We wrap in [[ { json: ... } ]] because n8n expects INodeExecutionData[][]
121
+ nodeCtx.addOutputData('ai_tool', index, [[{ json: output }]]);
122
+ return result;
123
+ }
124
+ catch (error) {
125
+ // Log Error
126
+ const errorOutput = {
127
+ tool: toolName,
128
+ error: error.message || String(error),
129
+ };
130
+ // Try to log error output
131
+ try {
132
+ nodeCtx.addOutputData('ai_tool', index, [[{ json: errorOutput }]]);
133
+ }
134
+ catch (e) {
135
+ console.error(`Failed to log error for tool ${toolName}`, e);
136
+ }
137
+ throw error;
138
+ }
139
+ };
140
+ return tool;
141
+ }
142
+ class McpToolkit extends agents_1.Toolkit {
143
+ tools;
144
+ constructor(tools) {
145
+ super();
146
+ this.tools = tools;
147
+ }
148
+ }
149
+ exports.McpToolkit = McpToolkit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-smart-browser-automation",
3
- "version": "1.6.25",
3
+ "version": "1.6.27",
4
4
  "description": "n8n node for AI-driven browser automation using MCP",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",