n8n-nodes-smart-browser-automation 1.6.24 → 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.
|
@@ -85,70 +85,82 @@ class BrowserSessionManager {
|
|
|
85
85
|
throw new Error(`Invalid CDP Endpoint: "${trimmedCdp}". CDP Endpoint must be a ws(s) URL (e.g. ws://localhost:9222 or wss://.../devtools/...).`);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
//
|
|
89
|
-
if
|
|
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 &&
|
|
90
92
|
this.config.mcpEndpoint === mcpEndpoint &&
|
|
91
93
|
this.config.useCDP === useCDP &&
|
|
92
|
-
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
|
|
93
97
|
return this.tools;
|
|
94
98
|
}
|
|
95
|
-
//
|
|
96
|
-
if (this.mcpClient) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.mcpClient = new index_js_1.Client({ name: 'n8n-browser-automation', version: '1.0.0' }, { capabilities: {} });
|
|
101
|
-
// Determine transport based on endpoint type
|
|
102
|
-
const isUrl = trimmedMcpEndpoint.startsWith('http://') || trimmedMcpEndpoint.startsWith('https://');
|
|
103
|
-
let urlIsSse = false;
|
|
104
|
-
if (isUrl) {
|
|
105
|
-
try {
|
|
106
|
-
urlIsSse = /(^|\/)sse\/?(\?|#|$)/i.test(mcpUrlObj.pathname);
|
|
107
|
-
}
|
|
108
|
-
catch (e) {
|
|
109
|
-
// Should be caught by parseUrl above, but just in case
|
|
110
|
-
throw new Error(`Invalid MCP Endpoint path: "${trimmedMcpEndpoint}".`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (isUrl) {
|
|
114
|
-
if (urlIsSse) {
|
|
115
|
-
// Connect via SSE
|
|
116
|
-
const { SSEClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/sse.js')));
|
|
117
|
-
this.transport = new SSEClientTransport(new URL(trimmedMcpEndpoint)); // Safe because we parsed it above
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
// Connect via Streamable HTTP
|
|
121
|
-
const { StreamableHTTPClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/streamableHttp.js')));
|
|
122
|
-
this.transport = new StreamableHTTPClientTransport(new URL(trimmedMcpEndpoint)); // Safe because we parsed it above
|
|
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();
|
|
123
104
|
}
|
|
105
|
+
this.isInitialized = false;
|
|
124
106
|
}
|
|
125
107
|
else {
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
this.transport = new StdioClientTransport({
|
|
129
|
-
command: 'node',
|
|
130
|
-
args: [mcpEndpoint],
|
|
131
|
-
env: {
|
|
132
|
-
...process.env,
|
|
133
|
-
...(useCDP && cdpEndpoint ? { CDP_URL: cdpEndpoint } : {}),
|
|
134
|
-
},
|
|
135
|
-
});
|
|
108
|
+
// MCP endpoint is same, just need to re-run tools logic (like CDP connect)
|
|
109
|
+
// We can skip client creation
|
|
136
110
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
//
|
|
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;
|
|
141
117
|
if (isUrl) {
|
|
142
|
-
|
|
143
|
-
|
|
118
|
+
try {
|
|
119
|
+
urlIsSse = /(^|\/)sse\/?(\?|#|$)/i.test(mcpUrlObj.pathname);
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
throw new Error(`Invalid MCP Endpoint path: "${trimmedMcpEndpoint}".`);
|
|
144
123
|
}
|
|
145
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
146
124
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
}
|
|
152
164
|
}
|
|
153
165
|
this.isInitialized = true;
|
|
154
166
|
this.config = { mcpEndpoint, useCDP, cdpEndpoint };
|
|
@@ -149,13 +149,17 @@ 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');
|
|
152
155
|
// Emit a debug item on the Main output so users can see endpoints and tool list in the UI
|
|
153
156
|
const debugJson = {
|
|
154
157
|
mcpEndpoint,
|
|
155
158
|
browserMode: credentials.browserMode,
|
|
156
159
|
cdpEndpoint: useCDP ? cdpUrl : undefined,
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
initializationMode: 'strict_cdp_connect',
|
|
161
|
+
toolCount: exposedTools.length,
|
|
162
|
+
tools: exposedTools.map((t) => ({
|
|
159
163
|
name: t.name,
|
|
160
164
|
description: t.description ?? '',
|
|
161
165
|
})),
|
|
@@ -164,7 +168,7 @@ class SmartBrowserAutomationTools {
|
|
|
164
168
|
const { DynamicStructuredTool } = await importDynamicStructuredTool();
|
|
165
169
|
const { Toolkit } = await importToolkitBase();
|
|
166
170
|
const ctx = this;
|
|
167
|
-
const tools = await Promise.all(
|
|
171
|
+
const tools = await Promise.all(exposedTools.map(async (tool) => {
|
|
168
172
|
const schema = await (0, jsonSchemaToZod_1.jsonSchemaToZod)(tool.inputSchema);
|
|
169
173
|
const toolName = tool.name;
|
|
170
174
|
const toolDescription = (tool.description ?? '');
|