n8n-nodes-smart-browser-automation 1.6.4 → 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',
@@ -142,74 +143,64 @@ class SmartBrowserAutomation {
142
143
  // Handle Execute Tool operation
143
144
  // Get CDP override or use from credentials
144
145
  const cdpOverride = this.getNodeParameter('cdpOverride', 0, '');
145
- // Check if AI Agent sent Tool_Parameters in input
146
+ // Check if AI Agent sent tool name in input
146
147
  const inputData = items[0]?.json;
147
- const aiToolParams = inputData?.Tool_Parameters;
148
- const aiAction = inputData?.action;
149
- // Determine tool name: prefer AI input, fallback to manual parameter
148
+ // Determine tool name and parameters
150
149
  let toolName;
151
- // Check if Tool_Parameters has browser_action field
152
- if (aiToolParams && typeof aiToolParams === 'object' && 'browser_action' in aiToolParams) {
153
- const browserAction = aiToolParams.browser_action;
154
- toolName = `browser_${browserAction}`;
155
- }
156
- else if (aiAction && typeof aiAction === 'string' && aiAction !== 'sendMessage') {
157
- // AI Agent sent action like "click", "open", etc - map to browser tool
158
- toolName = aiAction === 'open' ? 'browser_navigate' : `browser_${aiAction}`;
150
+ let toolParams;
151
+ // Check if this is from AI Agent (has 'tool' field in json)
152
+ if (inputData?.tool && typeof inputData.tool === 'string') {
153
+ // AI Agent execution - tool name is in item.json.tool
154
+ toolName = inputData.tool;
155
+ // Extract parameters by removing known n8n/AI fields
156
+ const { tool, action, chatInput, sessionId, toolCallId, name, id, arguments: args, ...rest } = inputData;
157
+ // Use 'arguments' field if present, otherwise use remaining fields
158
+ toolParams = args && typeof args === 'object' ? args : rest;
159
159
  }
160
160
  else {
161
- // Manual mode or no AI action
161
+ // Manual execution - use node parameters
162
162
  toolName = this.getNodeParameter('toolName', 0);
163
- }
164
- try {
165
- // Initialize session if not ready
166
- if (!sessionManager.isReady()) {
167
- const cdpUrl = cdpOverride || credentials.cdpEndpoint || '';
168
- await sessionManager.initialize(credentials.mcpEndpoint, !!cdpUrl, cdpUrl);
169
- }
170
- // Handle tool execution
171
- let toolParams;
172
163
  try {
173
- // First check if AI Agent sent Tool_Parameters in input
174
- if (aiToolParams && typeof aiToolParams === 'object') {
175
- toolParams = aiToolParams;
164
+ const rawParams = this.getNodeParameter('toolParameters', 0);
165
+ if (rawParams === undefined || rawParams === null) {
166
+ toolParams = {};
176
167
  }
177
- else {
178
- // Fallback to manual toolParameters field
179
- const rawParams = this.getNodeParameter('toolParameters', 0);
180
- if (rawParams === undefined || rawParams === null) {
168
+ else if (typeof rawParams === 'string') {
169
+ if (!rawParams || rawParams.trim() === '') {
181
170
  toolParams = {};
182
171
  }
183
- else if (typeof rawParams === 'string') {
184
- if (!rawParams || rawParams.trim() === '') {
185
- toolParams = {};
186
- }
187
- else {
188
- toolParams = JSON.parse(rawParams);
189
- }
190
- }
191
- else if (typeof rawParams === 'object') {
192
- toolParams = rawParams;
193
- }
194
172
  else {
195
- try {
196
- toolParams = JSON.parse(JSON.stringify(rawParams));
197
- }
198
- catch (parseError) {
199
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid parameter type: ${typeof rawParams}`);
200
- }
173
+ toolParams = JSON.parse(rawParams);
201
174
  }
202
175
  }
203
- // Ensure toolParams is an object
204
- if (typeof toolParams !== 'object' ||
205
- toolParams === null ||
206
- Array.isArray(toolParams)) {
207
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Tool parameters must be a JSON object');
176
+ else if (typeof rawParams === 'object') {
177
+ toolParams = rawParams;
178
+ }
179
+ else {
180
+ try {
181
+ toolParams = JSON.parse(JSON.stringify(rawParams));
182
+ }
183
+ catch (parseError) {
184
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid parameter type: ${typeof rawParams}`);
185
+ }
208
186
  }
209
187
  }
210
188
  catch (error) {
211
189
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to parse tool parameters: ${error.message}. Make sure the parameters are valid JSON.`);
212
190
  }
191
+ }
192
+ try {
193
+ // Initialize session if not ready
194
+ if (!sessionManager.isReady()) {
195
+ const cdpUrl = cdpOverride || credentials.cdpEndpoint || '';
196
+ await sessionManager.initialize(credentials.mcpEndpoint, !!cdpUrl, cdpUrl);
197
+ }
198
+ // Ensure toolParams is an object
199
+ if (typeof toolParams !== 'object' ||
200
+ toolParams === null ||
201
+ Array.isArray(toolParams)) {
202
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Tool parameters must be a JSON object');
203
+ }
213
204
  // Special handling for browser_connect_cdp
214
205
  if (toolName === 'browser_connect_cdp') {
215
206
  const endpoint = toolParams.endpoint || cdpOverride || credentials.cdpEndpoint;
@@ -231,11 +222,23 @@ class SmartBrowserAutomation {
231
222
  }
232
223
  // Validate tool exists before executing
233
224
  const availableTools = await sessionManager.listTools();
234
- const toolExists = availableTools.some((tool) => tool.name === toolName);
225
+ const selectedTool = availableTools.find((tool) => tool.name === toolName);
226
+ const toolExists = Boolean(selectedTool);
235
227
  if (!toolExists) {
236
228
  const availableToolNames = availableTools.map((t) => t.name).join(', ');
237
229
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Tool '${toolName}' does not exist. Available tools: ${availableToolNames}`);
238
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
+ }
239
242
  // Execute the tool via MCP
240
243
  const result = await sessionManager.callTool(toolName, toolParams);
241
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.4",
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
  }