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

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.
@@ -12,6 +12,7 @@ declare class BrowserSessionManager {
12
12
  private tools;
13
13
  private constructor();
14
14
  static getInstance(): BrowserSessionManager;
15
+ private parseUrl;
15
16
  initialize(mcpEndpoint: string, useCDP: boolean, cdpEndpoint?: string): Promise<MCPTool[]>;
16
17
  private getAllTools;
17
18
  callTool(toolName: string, toolArgs?: any): Promise<any>;
@@ -48,6 +48,17 @@ class BrowserSessionManager {
48
48
  }
49
49
  return BrowserSessionManager.instance;
50
50
  }
51
+ /*
52
+ * Safe URL parser helper
53
+ */
54
+ parseUrl(url, description) {
55
+ try {
56
+ return new URL(url);
57
+ }
58
+ catch (error) {
59
+ throw new Error(`Invalid ${description}: "${url}". Please ensure it is a valid URL (e.g. starting with http:// or https://).`);
60
+ }
61
+ }
51
62
  async initialize(mcpEndpoint, useCDP, cdpEndpoint) {
52
63
  // Validate endpoint inputs early to avoid confusing runtime errors
53
64
  let trimmedMcpEndpoint = String(mcpEndpoint ?? '').trim();
@@ -55,10 +66,11 @@ class BrowserSessionManager {
55
66
  throw new Error('MCP Endpoint is required');
56
67
  }
57
68
  // Be forgiving: if user passes host:port without scheme, assume http://
58
- // Examples: localhost:3000, 127.0.0.1:3000, example.com:8080
59
69
  if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmedMcpEndpoint)) {
60
70
  trimmedMcpEndpoint = `http://${trimmedMcpEndpoint}`;
61
71
  }
72
+ // Validate URL format immediately
73
+ const mcpUrlObj = this.parseUrl(trimmedMcpEndpoint, 'MCP Endpoint');
62
74
  if (/^wss?:\/\//i.test(trimmedMcpEndpoint)) {
63
75
  throw new Error(`Invalid MCP Endpoint: "${trimmedMcpEndpoint}". ` +
64
76
  'MCP Endpoint must be an http(s) URL (for SSE or Streamable HTTP). ' +
@@ -73,69 +85,82 @@ class BrowserSessionManager {
73
85
  throw new Error(`Invalid CDP Endpoint: "${trimmedCdp}". CDP Endpoint must be a ws(s) URL (e.g. ws://localhost:9222 or wss://.../devtools/...).`);
74
86
  }
75
87
  }
76
- // Only initialize if not already done or config changed
77
- if (this.isInitialized &&
88
+ // Check checks if we are already initialized with the same config
89
+ // BUT if we are using CDP, we want to ensure we call the connect tool again
90
+ // to make sure the browser session is active (referencing user request).
91
+ const isConfigMatch = this.isInitialized &&
78
92
  this.config.mcpEndpoint === mcpEndpoint &&
79
93
  this.config.useCDP === useCDP &&
80
- this.config.cdpEndpoint === cdpEndpoint) {
94
+ this.config.cdpEndpoint === cdpEndpoint;
95
+ if (isConfigMatch && !useCDP) {
96
+ // For non-CDP (standard MCP tools), we can safely return cached tools
81
97
  return this.tools;
82
98
  }
83
- // Close existing session if config changed
84
- if (this.mcpClient) {
85
- await this.close();
86
- }
87
- // Initialize MCP client
88
- this.mcpClient = new index_js_1.Client({ name: 'n8n-browser-automation', version: '1.0.0' }, { capabilities: {} });
89
- // Determine transport based on endpoint type
90
- const isUrl = trimmedMcpEndpoint.startsWith('http://') || trimmedMcpEndpoint.startsWith('https://');
91
- let urlIsSse = false;
92
- if (isUrl) {
93
- try {
94
- urlIsSse = /(^|\/)sse\/?(\?|#|$)/i.test(new URL(trimmedMcpEndpoint).pathname);
95
- }
96
- catch (e) {
97
- throw new Error(`Invalid MCP Endpoint: "${trimmedMcpEndpoint}". MCP Endpoint must be a valid http(s) URL (or a local file path for stdio).`);
98
- }
99
- }
100
- if (isUrl) {
101
- if (urlIsSse) {
102
- // Connect via SSE
103
- const { SSEClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/sse.js')));
104
- this.transport = new SSEClientTransport(new URL(trimmedMcpEndpoint));
105
- }
106
- else {
107
- // Connect via Streamable HTTP
108
- const { StreamableHTTPClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/streamableHttp.js')));
109
- this.transport = new StreamableHTTPClientTransport(new URL(trimmedMcpEndpoint));
99
+ // If config changed, or strict strict re-connect is needed (though we try to reuse client if endpoint is same)
100
+ if (this.config.mcpEndpoint !== mcpEndpoint || !this.mcpClient) {
101
+ // Full re-initialization needed
102
+ if (this.mcpClient) {
103
+ await this.close();
110
104
  }
105
+ this.isInitialized = false;
111
106
  }
112
107
  else {
113
- // Connect via Stdio (lazy import so environments that only use SSE don't need stdio deps)
114
- const { StdioClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/stdio.js')));
115
- this.transport = new StdioClientTransport({
116
- command: 'node',
117
- args: [mcpEndpoint],
118
- env: {
119
- ...process.env,
120
- ...(useCDP && cdpEndpoint ? { CDP_URL: cdpEndpoint } : {}),
121
- },
122
- });
108
+ // MCP endpoint is same, just need to re-run tools logic (like CDP connect)
109
+ // We can skip client creation
123
110
  }
124
- try {
125
- await this.mcpClient.connect(this.transport);
126
- // Stability delay for HTTP connections to ensure session is fully open
127
- // Remote servers often need a moment to register the session before accepting POST calls
111
+ if (!this.mcpClient) {
112
+ // Initialize MCP client
113
+ this.mcpClient = new index_js_1.Client({ name: 'n8n-browser-automation', version: '1.0.0' }, { capabilities: {} });
114
+ // Determine transport based on endpoint type
115
+ const isUrl = trimmedMcpEndpoint.startsWith('http://') || trimmedMcpEndpoint.startsWith('https://');
116
+ let urlIsSse = false;
128
117
  if (isUrl) {
129
- if (process.env.NODE_ENV !== 'production') {
130
- console.log(`[MCP] Connected to ${trimmedMcpEndpoint}. Waiting for session stabilization...`);
118
+ try {
119
+ urlIsSse = /(^|\/)sse\/?(\?|#|$)/i.test(mcpUrlObj.pathname);
120
+ }
121
+ catch (e) {
122
+ throw new Error(`Invalid MCP Endpoint path: "${trimmedMcpEndpoint}".`);
131
123
  }
132
- await new Promise(resolve => setTimeout(resolve, 2000));
133
124
  }
134
- }
135
- catch (error) {
136
- this.isInitialized = false;
137
- const transportType = isUrl ? (urlIsSse ? 'SSE' : 'Streamable HTTP') : 'Stdio';
138
- throw new Error(`Failed to connect to MCP server via ${transportType} at ${trimmedMcpEndpoint}. Error: ${error.message}`);
125
+ if (isUrl) {
126
+ if (urlIsSse) {
127
+ // Connect via SSE
128
+ const { SSEClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/sse.js')));
129
+ this.transport = new SSEClientTransport(new URL(trimmedMcpEndpoint)); // Safe because we parsed it above
130
+ }
131
+ else {
132
+ // Connect via Streamable HTTP
133
+ const { StreamableHTTPClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/streamableHttp.js')));
134
+ this.transport = new StreamableHTTPClientTransport(new URL(trimmedMcpEndpoint)); // Safe because we parsed it above
135
+ }
136
+ }
137
+ else {
138
+ // Connect via Stdio (lazy import so environments that only use SSE don't need stdio deps)
139
+ const { StdioClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/stdio.js')));
140
+ this.transport = new StdioClientTransport({
141
+ command: 'node',
142
+ args: [mcpEndpoint],
143
+ env: {
144
+ ...process.env,
145
+ ...(useCDP && cdpEndpoint ? { CDP_URL: cdpEndpoint } : {}),
146
+ },
147
+ });
148
+ }
149
+ try {
150
+ await this.mcpClient.connect(this.transport);
151
+ // Stability delay for HTTP connections to ensure session is fully open
152
+ if (isUrl) {
153
+ if (process.env.NODE_ENV !== 'production') {
154
+ console.log(`[MCP] Connected to ${trimmedMcpEndpoint}. Waiting for session stabilization...`);
155
+ }
156
+ await new Promise(resolve => setTimeout(resolve, 2000));
157
+ }
158
+ }
159
+ catch (error) {
160
+ this.isInitialized = false;
161
+ const transportType = isUrl ? (urlIsSse ? 'SSE' : 'Streamable HTTP') : 'Stdio';
162
+ throw new Error(`Failed to connect to MCP server via ${transportType} at ${trimmedMcpEndpoint}. Error: ${error.message}`);
163
+ }
139
164
  }
140
165
  this.isInitialized = true;
141
166
  this.config = { mcpEndpoint, useCDP, cdpEndpoint };
@@ -86,8 +86,8 @@ class SmartBrowserAutomationTools {
86
86
  name: 'Browser Automation Tools',
87
87
  },
88
88
  inputs: [],
89
- outputs: [n8n_workflow_1.NodeConnectionTypes.AiTool],
90
- outputNames: ['Tools'],
89
+ outputs: [n8n_workflow_1.NodeConnectionTypes.AiTool, n8n_workflow_1.NodeConnectionTypes.Main],
90
+ outputNames: ['Tools', 'Debug'],
91
91
  icon: 'file:smartBrowserAutomation.svg',
92
92
  credentials: [
93
93
  {
@@ -140,7 +140,7 @@ class SmartBrowserAutomationTools {
140
140
  const e = error;
141
141
  const mode = useCDP ? 'cdp' : 'launch';
142
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}): ${e.message}`, {
143
+ throw new n8n_workflow_1.NodeOperationError(node, `Failed to connect to MCP. ${details}. Original error: ${e.message}`, {
144
144
  itemIndex,
145
145
  description: details,
146
146
  });
@@ -149,10 +149,26 @@ class SmartBrowserAutomationTools {
149
149
  if (!mcpTools.length) {
150
150
  throw new n8n_workflow_1.NodeOperationError(node, 'MCP Server returned no tools', { itemIndex });
151
151
  }
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
156
+ const debugJson = {
157
+ mcpEndpoint,
158
+ browserMode: credentials.browserMode,
159
+ cdpEndpoint: useCDP ? cdpUrl : undefined,
160
+ initializationMode: 'strict_cdp_connect',
161
+ toolCount: exposedTools.length,
162
+ tools: exposedTools.map((t) => ({
163
+ name: t.name,
164
+ description: t.description ?? '',
165
+ })),
166
+ };
167
+ this.addOutputData(n8n_workflow_1.NodeConnectionTypes.Main, 0, [[{ json: debugJson }]]);
152
168
  const { DynamicStructuredTool } = await importDynamicStructuredTool();
153
169
  const { Toolkit } = await importToolkitBase();
154
170
  const ctx = this;
155
- const tools = await Promise.all(mcpTools.map(async (tool) => {
171
+ const tools = await Promise.all(exposedTools.map(async (tool) => {
156
172
  const schema = await (0, jsonSchemaToZod_1.jsonSchemaToZod)(tool.inputSchema);
157
173
  const toolName = tool.name;
158
174
  const toolDescription = (tool.description ?? '');
@@ -173,13 +189,33 @@ class SmartBrowserAutomationTools {
173
189
  catch { }
174
190
  const { index } = ctx.addInputData(n8n_workflow_1.NodeConnectionTypes.AiTool, [[{ json: input }]], runIndex);
175
191
  try {
192
+ console.log(`[AI Agent] Executing tool ${toolName} with args:`, JSON.stringify(args));
176
193
  const result = await sessionManager.callTool(toolName, args);
177
194
  const output = { tool: toolName, result };
178
- ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, index, [[{ json: output }]]);
195
+ // Ensure the output is an array of INodeExecutionData
196
+ const executionData = [[{ json: output }]];
197
+ ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, index, executionData);
179
198
  return formatMcpToolResult(result);
180
199
  }
181
200
  catch (e) {
182
- ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, index, 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
183
219
  throw e;
184
220
  }
185
221
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-smart-browser-automation",
3
- "version": "1.6.23",
3
+ "version": "1.6.25",
4
4
  "description": "n8n node for AI-driven browser automation using MCP",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
@@ -63,4 +63,4 @@
63
63
  "@modelcontextprotocol/sdk": "1.17.0",
64
64
  "zod": "3.25.76"
65
65
  }
66
- }
66
+ }