n8n-nodes-smart-browser-automation 1.6.5 → 1.6.6

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/README.md CHANGED
@@ -54,13 +54,14 @@ Add new credentials for **Smart Browser Automation API**:
54
54
 
55
55
  ### Mode 1: AI Agent (Automatic)
56
56
 
57
- Use with n8n's **AI Agent** node to let AI decide which browser tools to use:
57
+ Use with n8n's **AI Agent** node to let AI decide which browser tools to use.
58
58
 
59
- 1. Add **Smart Browser Automation** node
60
- 2. Select **Mode**: `AI Agent (Auto)`
61
- 3. Operation: `Initialize Session`
62
- 4. Connect to **AI Agent** node
63
- 5. AI can now use all browser tools automatically
59
+ Use the dedicated tool node so the agent can discover each MCP tool and its JSON-schema arguments:
60
+
61
+ 1. Add **Smart Browser Automation Tools** node
62
+ 2. Configure credentials (MCP endpoint + browser mode)
63
+ 3. Connect its **Tools** output to **AI Agent**
64
+ 4. The agent can now call MCP tools (e.g. `browser_navigate`, `browser_click`) with correct argument schemas
64
65
 
65
66
  **Example AI Agent Workflow**:
66
67
  ```
@@ -13,6 +13,7 @@ declare class BrowserSessionManager {
13
13
  private constructor();
14
14
  static getInstance(): BrowserSessionManager;
15
15
  initialize(mcpEndpoint: string, useCDP: boolean, cdpEndpoint?: string): Promise<MCPTool[]>;
16
+ private getAllTools;
16
17
  callTool(toolName: string, toolArgs?: any): Promise<any>;
17
18
  getTools(): MCPTool[];
18
19
  listTools(): Promise<MCPTool[]>;
@@ -21,6 +21,7 @@ class BrowserSessionManager {
21
21
  // Only initialize if not already done or config changed
22
22
  if (this.isInitialized &&
23
23
  this.config.mcpEndpoint === mcpEndpoint &&
24
+ this.config.useCDP === useCDP &&
24
25
  this.config.cdpEndpoint === cdpEndpoint) {
25
26
  return this.tools;
26
27
  }
@@ -64,13 +65,42 @@ class BrowserSessionManager {
64
65
  throw new Error(`Failed to connect to MCP server via ${transportType} at ${mcpEndpoint}. Error: ${error.message}`);
65
66
  }
66
67
  this.isInitialized = true;
67
- this.config = { mcpEndpoint, cdpEndpoint };
68
- // Fetch available tools from MCP server
69
- const toolsResponse = await this.mcpClient.listTools();
70
- await this.mcpClient.callTool({ name: 'browser_connect_cdp', arguments: { endpoint: cdpEndpoint || '' } });
71
- this.tools = toolsResponse.tools;
68
+ this.config = { mcpEndpoint, useCDP, cdpEndpoint };
69
+ // Fetch available tools from MCP server (handles pagination)
70
+ this.tools = await this.getAllTools();
71
+ // If requested, connect to an existing browser via CDP
72
+ if (useCDP) {
73
+ if (!cdpEndpoint || String(cdpEndpoint).trim() === '') {
74
+ throw new Error('CDP endpoint is required when Browser Mode is CDP Connection');
75
+ }
76
+ const hasConnectTool = this.tools.some((t) => t.name === 'browser_connect_cdp');
77
+ if (hasConnectTool) {
78
+ const result = await this.mcpClient.callTool({
79
+ name: 'browser_connect_cdp',
80
+ arguments: { endpoint: cdpEndpoint },
81
+ });
82
+ if (result?.isError) {
83
+ throw new Error(`Failed to connect via CDP: ${JSON.stringify(result)}`);
84
+ }
85
+ }
86
+ }
72
87
  return this.tools;
73
88
  }
89
+ async getAllTools(cursor) {
90
+ if (!this.mcpClient) {
91
+ throw new Error('MCP client not initialized');
92
+ }
93
+ // SDK supports optional cursor param; keep it loosely typed for compatibility.
94
+ const response = await (cursor
95
+ ? this.mcpClient.listTools({ cursor })
96
+ : this.mcpClient.listTools());
97
+ const tools = (response?.tools ?? []);
98
+ const nextCursor = response?.nextCursor;
99
+ if (nextCursor) {
100
+ return tools.concat(await this.getAllTools(nextCursor));
101
+ }
102
+ return tools;
103
+ }
74
104
  async callTool(toolName, toolArgs) {
75
105
  if (!this.mcpClient || !this.isInitialized) {
76
106
  throw new Error('MCP client not initialized. Please configure credentials first.');
@@ -84,6 +114,9 @@ class BrowserSessionManager {
84
114
  });
85
115
  // Log the response for debugging
86
116
  console.log(`[MCP] Tool "${toolName}" response:`, JSON.stringify(result, null, 2));
117
+ if (result?.isError) {
118
+ throw new Error(typeof result?.toolResult === 'string' ? result.toolResult : `Tool returned error: ${JSON.stringify(result)}`);
119
+ }
87
120
  return result;
88
121
  }
89
122
  catch (error) {
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SmartBrowserAutomation = void 0;
7
7
  const n8n_workflow_1 = require("n8n-workflow");
8
8
  const BrowserSessionManager_1 = __importDefault(require("./BrowserSessionManager"));
9
+ const jsonSchemaToZod_1 = require("./jsonSchemaToZod");
9
10
  class SmartBrowserAutomation {
10
11
  description = {
11
12
  displayName: 'Smart Browser Automation',
@@ -221,11 +222,23 @@ class SmartBrowserAutomation {
221
222
  }
222
223
  // Validate tool exists before executing
223
224
  const availableTools = await sessionManager.listTools();
224
- const toolExists = availableTools.some((tool) => tool.name === toolName);
225
+ const selectedTool = availableTools.find((tool) => tool.name === toolName);
226
+ const toolExists = Boolean(selectedTool);
225
227
  if (!toolExists) {
226
228
  const availableToolNames = availableTools.map((t) => t.name).join(', ');
227
229
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Tool '${toolName}' does not exist. Available tools: ${availableToolNames}`);
228
230
  }
231
+ // Validate tool parameters against the MCP tool JSON schema (when provided)
232
+ if (selectedTool?.inputSchema) {
233
+ try {
234
+ const schema = await (0, jsonSchemaToZod_1.jsonSchemaToZod)(selectedTool.inputSchema);
235
+ schema.parse(toolParams);
236
+ }
237
+ catch (validationError) {
238
+ const details = validationError?.issues ? JSON.stringify(validationError.issues) : validationError.message;
239
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid tool parameters for '${toolName}'.`, { description: details });
240
+ }
241
+ }
229
242
  // Execute the tool via MCP
230
243
  const result = await sessionManager.callTool(toolName, toolParams);
231
244
  returnData.push({
@@ -0,0 +1,5 @@
1
+ import { type INodeType, type INodeTypeDescription, type ISupplyDataFunctions, type SupplyData } from 'n8n-workflow';
2
+ export declare class SmartBrowserAutomationTools implements INodeType {
3
+ description: INodeTypeDescription;
4
+ supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData>;
5
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ /* eslint-disable n8n-nodes-base/node-class-description-inputs-wrong-regular-node */
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
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.SmartBrowserAutomationTools = void 0;
42
+ const n8n_workflow_1 = require("n8n-workflow");
43
+ const BrowserSessionManager_1 = __importDefault(require("./BrowserSessionManager"));
44
+ const jsonSchemaToZod_1 = require("./jsonSchemaToZod");
45
+ class SmartBrowserAutomationTools {
46
+ description = {
47
+ displayName: 'Smart Browser Automation Tools',
48
+ name: 'smartBrowserAutomationTools',
49
+ group: ['transform'],
50
+ version: 1,
51
+ description: 'Expose Smart Browser Automation MCP tools (with JSON-schema args) to AI Agents',
52
+ defaults: {
53
+ name: 'Browser Automation Tools',
54
+ },
55
+ inputs: [],
56
+ outputs: [{ type: n8n_workflow_1.NodeConnectionTypes.AiTool, displayName: 'Tools' }],
57
+ icon: 'file:smartBrowserAutomation.svg',
58
+ credentials: [
59
+ {
60
+ name: 'smartBrowserAutomationApi',
61
+ required: true,
62
+ },
63
+ ],
64
+ properties: [
65
+ {
66
+ displayName: 'CDP Endpoint Override',
67
+ name: 'cdpOverride',
68
+ type: 'string',
69
+ default: '',
70
+ placeholder: 'ws://localhost:9222',
71
+ description: 'Override the CDP endpoint from credentials (only used in CDP mode)',
72
+ },
73
+ ],
74
+ };
75
+ async supplyData(itemIndex) {
76
+ const node = this.getNode();
77
+ const credentials = await this.getCredentials('smartBrowserAutomationApi');
78
+ const sessionManager = BrowserSessionManager_1.default.getInstance();
79
+ const cdpOverride = this.getNodeParameter('cdpOverride', itemIndex, '');
80
+ const useCDP = credentials.browserMode === 'cdp';
81
+ const cdpUrl = (cdpOverride || credentials.cdpEndpoint || '').trim();
82
+ try {
83
+ await sessionManager.initialize(credentials.mcpEndpoint, useCDP, useCDP ? cdpUrl : undefined);
84
+ }
85
+ catch (error) {
86
+ throw new n8n_workflow_1.NodeOperationError(node, `Failed to connect to MCP server: ${error.message}`, {
87
+ itemIndex,
88
+ });
89
+ }
90
+ const mcpTools = await sessionManager.listTools();
91
+ if (!mcpTools.length) {
92
+ throw new n8n_workflow_1.NodeOperationError(node, 'MCP Server returned no tools', { itemIndex });
93
+ }
94
+ const { DynamicStructuredTool } = await Promise.resolve().then(() => __importStar(require('langchain/tools')));
95
+ const { Toolkit } = await Promise.resolve().then(() => __importStar(require('langchain/agents')));
96
+ const tools = await Promise.all(mcpTools.map(async (tool) => {
97
+ const schema = await (0, jsonSchemaToZod_1.jsonSchemaToZod)(tool.inputSchema);
98
+ return new DynamicStructuredTool({
99
+ name: tool.name,
100
+ description: tool.description ?? '',
101
+ schema,
102
+ func: async (args) => {
103
+ return await sessionManager.callTool(tool.name, args);
104
+ },
105
+ metadata: { isFromToolkit: true },
106
+ });
107
+ }));
108
+ class SmartBrowserAutomationToolkit extends Toolkit {
109
+ tools;
110
+ constructor(tools) {
111
+ super();
112
+ this.tools = tools;
113
+ }
114
+ }
115
+ return {
116
+ response: new SmartBrowserAutomationToolkit(tools),
117
+ closeFunction: async () => await sessionManager.close(),
118
+ };
119
+ }
120
+ }
121
+ exports.SmartBrowserAutomationTools = SmartBrowserAutomationTools;
@@ -0,0 +1,16 @@
1
+ type JsonSchema = {
2
+ type?: string | string[];
3
+ properties?: Record<string, JsonSchema>;
4
+ required?: string[];
5
+ items?: JsonSchema;
6
+ enum?: Array<string | number | boolean | null>;
7
+ anyOf?: JsonSchema[];
8
+ oneOf?: JsonSchema[];
9
+ allOf?: JsonSchema[];
10
+ additionalProperties?: boolean | JsonSchema;
11
+ default?: unknown;
12
+ description?: string;
13
+ format?: string;
14
+ };
15
+ export type { JsonSchema };
16
+ export declare function jsonSchemaToZod(schema: JsonSchema | undefined): Promise<any>;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.jsonSchemaToZod = jsonSchemaToZod;
37
+ async function jsonSchemaToZod(schema) {
38
+ const { z } = await Promise.resolve().then(() => __importStar(require('zod')));
39
+ const toZod = (s) => {
40
+ if (!s)
41
+ return z.any();
42
+ if (Array.isArray(s.oneOf) && s.oneOf.length) {
43
+ return z.union(s.oneOf.map((x) => toZod(x)));
44
+ }
45
+ if (Array.isArray(s.anyOf) && s.anyOf.length) {
46
+ return z.union(s.anyOf.map((x) => toZod(x)));
47
+ }
48
+ if (Array.isArray(s.allOf) && s.allOf.length) {
49
+ return s.allOf.reduce((acc, cur) => acc.and(toZod(cur)), toZod(s.allOf[0]));
50
+ }
51
+ if (Array.isArray(s.enum) && s.enum.length) {
52
+ const values = s.enum;
53
+ const allStrings = values.every((v) => typeof v === 'string');
54
+ if (allStrings) {
55
+ return z.enum(values);
56
+ }
57
+ return z.union(values.map((v) => z.literal(v)));
58
+ }
59
+ const type = s.type;
60
+ const types = Array.isArray(type) ? type : type ? [type] : [];
61
+ const allowsNull = types.includes('null');
62
+ const effectiveTypes = types.filter((t) => t !== 'null');
63
+ let base;
64
+ switch (effectiveTypes[0]) {
65
+ case 'string':
66
+ base = z.string();
67
+ break;
68
+ case 'integer':
69
+ base = z.number().int();
70
+ break;
71
+ case 'number':
72
+ base = z.number();
73
+ break;
74
+ case 'boolean':
75
+ base = z.boolean();
76
+ break;
77
+ case 'array':
78
+ base = z.array(toZod(s.items));
79
+ break;
80
+ case 'object': {
81
+ const shape = {};
82
+ const props = s.properties ?? {};
83
+ const required = new Set(s.required ?? []);
84
+ for (const [key, value] of Object.entries(props)) {
85
+ const zodProp = toZod(value);
86
+ shape[key] = required.has(key) ? zodProp : zodProp.optional();
87
+ }
88
+ base = z.object(shape);
89
+ if (s.additionalProperties === false) {
90
+ base = base.strict();
91
+ }
92
+ else if (s.additionalProperties && typeof s.additionalProperties === 'object') {
93
+ base = base.catchall(toZod(s.additionalProperties));
94
+ }
95
+ else {
96
+ base = base.passthrough();
97
+ }
98
+ break;
99
+ }
100
+ default:
101
+ base = z.any();
102
+ }
103
+ if (allowsNull)
104
+ base = base.nullable();
105
+ return base;
106
+ };
107
+ return toZod(schema);
108
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-smart-browser-automation",
3
- "version": "1.6.5",
3
+ "version": "1.6.6",
4
4
  "description": "n8n node for AI-driven browser automation using MCP",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
@@ -37,7 +37,8 @@
37
37
  "dist/credentials/SmartBrowserAutomationApi.credentials.js"
38
38
  ],
39
39
  "nodes": [
40
- "dist/nodes/SmartBrowserAutomation/SmartBrowserAutomation.node.js"
40
+ "dist/nodes/SmartBrowserAutomation/SmartBrowserAutomation.node.js",
41
+ "dist/nodes/SmartBrowserAutomation/SmartBrowserAutomationTools.node.js"
41
42
  ]
42
43
  },
43
44
  "devDependencies": {
@@ -55,6 +56,7 @@
55
56
  "n8n-workflow": "*"
56
57
  },
57
58
  "dependencies": {
58
- "@modelcontextprotocol/sdk": "^1.17.0"
59
+ "@modelcontextprotocol/sdk": "^1.17.0",
60
+ "zod": "^3.23.8"
59
61
  }
60
62
  }