n8n-nodes-smart-browser-automation 1.6.15 → 1.6.18
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.
|
@@ -50,10 +50,15 @@ class BrowserSessionManager {
|
|
|
50
50
|
}
|
|
51
51
|
async initialize(mcpEndpoint, useCDP, cdpEndpoint) {
|
|
52
52
|
// Validate endpoint inputs early to avoid confusing runtime errors
|
|
53
|
-
|
|
53
|
+
let trimmedMcpEndpoint = String(mcpEndpoint ?? '').trim();
|
|
54
54
|
if (!trimmedMcpEndpoint) {
|
|
55
55
|
throw new Error('MCP Endpoint is required');
|
|
56
56
|
}
|
|
57
|
+
// Be forgiving: if user passes host:port without scheme, assume http://
|
|
58
|
+
// Examples: localhost:3000, 127.0.0.1:3000, example.com:8080
|
|
59
|
+
if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmedMcpEndpoint)) {
|
|
60
|
+
trimmedMcpEndpoint = `http://${trimmedMcpEndpoint}`;
|
|
61
|
+
}
|
|
57
62
|
if (/^wss?:\/\//i.test(trimmedMcpEndpoint)) {
|
|
58
63
|
throw new Error(`Invalid MCP Endpoint: "${trimmedMcpEndpoint}". ` +
|
|
59
64
|
'MCP Endpoint must be an http(s) URL (for SSE or Streamable HTTP). ' +
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type INodeType, type INodeTypeDescription, type ISupplyDataFunctions, type SupplyData } from 'n8n-workflow';
|
|
1
|
+
import { type IExecuteFunctions, type INodeExecutionData, type INodeType, type INodeTypeDescription, type ISupplyDataFunctions, type SupplyData } from 'n8n-workflow';
|
|
2
2
|
export declare class SmartBrowserAutomationTools implements INodeType {
|
|
3
3
|
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
4
5
|
supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData>;
|
|
5
6
|
}
|
|
@@ -55,6 +55,26 @@ async function importToolkitBase() {
|
|
|
55
55
|
// n8n uses Toolkit from @langchain/classic/agents internally.
|
|
56
56
|
return await Promise.resolve().then(() => __importStar(require('@langchain/classic/agents')));
|
|
57
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
|
+
}
|
|
58
78
|
class SmartBrowserAutomationTools {
|
|
59
79
|
description = {
|
|
60
80
|
displayName: 'Smart Browser Automation Tools',
|
|
@@ -66,8 +86,8 @@ class SmartBrowserAutomationTools {
|
|
|
66
86
|
name: 'Browser Automation Tools',
|
|
67
87
|
},
|
|
68
88
|
inputs: [],
|
|
69
|
-
outputs: [n8n_workflow_1.NodeConnectionTypes.AiTool],
|
|
70
|
-
outputNames: ['Tools'],
|
|
89
|
+
outputs: [n8n_workflow_1.NodeConnectionTypes.AiTool, n8n_workflow_1.NodeConnectionTypes.Main],
|
|
90
|
+
outputNames: ['Tools', 'Debug'],
|
|
71
91
|
icon: 'file:smartBrowserAutomation.svg',
|
|
72
92
|
credentials: [
|
|
73
93
|
{
|
|
@@ -86,6 +106,44 @@ class SmartBrowserAutomationTools {
|
|
|
86
106
|
},
|
|
87
107
|
],
|
|
88
108
|
};
|
|
109
|
+
async execute() {
|
|
110
|
+
const node = this.getNode();
|
|
111
|
+
const sessionManager = BrowserSessionManager_1.default.getInstance();
|
|
112
|
+
const itemIndex = 0;
|
|
113
|
+
try {
|
|
114
|
+
const credentials = await this.getCredentials('smartBrowserAutomationApi');
|
|
115
|
+
const cdpOverride = this.getNodeParameter('cdpOverride', itemIndex, '');
|
|
116
|
+
const useCDP = credentials.browserMode === 'cdp';
|
|
117
|
+
const cdpUrl = (cdpOverride || credentials.cdpEndpoint || '').trim();
|
|
118
|
+
await sessionManager.initialize(credentials.mcpEndpoint, useCDP, useCDP ? cdpUrl : undefined);
|
|
119
|
+
const mcpTools = await sessionManager.listTools();
|
|
120
|
+
const debugJson = {
|
|
121
|
+
mcpEndpoint: credentials.mcpEndpoint,
|
|
122
|
+
browserMode: credentials.browserMode,
|
|
123
|
+
useCDP,
|
|
124
|
+
cdpEndpoint: useCDP ? cdpUrl : undefined,
|
|
125
|
+
toolCount: mcpTools.length,
|
|
126
|
+
tools: mcpTools.map((t) => ({
|
|
127
|
+
name: t.name,
|
|
128
|
+
description: t.description ?? '',
|
|
129
|
+
inputSchema: t.inputSchema ?? undefined,
|
|
130
|
+
})),
|
|
131
|
+
};
|
|
132
|
+
return [[], [{ json: debugJson }]];
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const err = error;
|
|
136
|
+
const debugJson = {
|
|
137
|
+
error: true,
|
|
138
|
+
message: err?.message ? String(err.message) : String(err),
|
|
139
|
+
code: err?.code ? String(err.code) : undefined,
|
|
140
|
+
};
|
|
141
|
+
throw new n8n_workflow_1.NodeOperationError(node, debugJson.message, { itemIndex, description: JSON.stringify(debugJson) });
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
await sessionManager.close();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
89
147
|
async supplyData(itemIndex) {
|
|
90
148
|
const node = this.getNode();
|
|
91
149
|
const credentials = await this.getCredentials('smartBrowserAutomationApi');
|
|
@@ -99,6 +157,12 @@ class SmartBrowserAutomationTools {
|
|
|
99
157
|
catch (error) {
|
|
100
158
|
throw new n8n_workflow_1.NodeOperationError(node, `Failed to connect to MCP server: ${error.message}`, {
|
|
101
159
|
itemIndex,
|
|
160
|
+
description: JSON.stringify({
|
|
161
|
+
mcpEndpoint: credentials.mcpEndpoint,
|
|
162
|
+
browserMode: credentials.browserMode,
|
|
163
|
+
useCDP,
|
|
164
|
+
cdpEndpoint: useCDP ? cdpUrl : undefined,
|
|
165
|
+
}),
|
|
102
166
|
});
|
|
103
167
|
}
|
|
104
168
|
const mcpTools = await sessionManager.listTools();
|
|
@@ -107,14 +171,37 @@ class SmartBrowserAutomationTools {
|
|
|
107
171
|
}
|
|
108
172
|
const { DynamicStructuredTool } = await importDynamicStructuredTool();
|
|
109
173
|
const { Toolkit } = await importToolkitBase();
|
|
174
|
+
const ctx = this;
|
|
110
175
|
const tools = await Promise.all(mcpTools.map(async (tool) => {
|
|
111
176
|
const schema = await (0, jsonSchemaToZod_1.jsonSchemaToZod)(tool.inputSchema);
|
|
177
|
+
const toolName = tool.name;
|
|
178
|
+
const toolDescription = (tool.description ?? '');
|
|
112
179
|
return new DynamicStructuredTool({
|
|
113
|
-
name:
|
|
114
|
-
description:
|
|
180
|
+
name: toolName,
|
|
181
|
+
description: toolDescription,
|
|
115
182
|
schema,
|
|
116
183
|
func: async (args) => {
|
|
117
|
-
|
|
184
|
+
// Make tool calls visible in n8n execution UI
|
|
185
|
+
const input = {
|
|
186
|
+
tool: toolName,
|
|
187
|
+
arguments: args,
|
|
188
|
+
};
|
|
189
|
+
let runIndex = 0;
|
|
190
|
+
try {
|
|
191
|
+
runIndex = ctx.getNextRunIndex?.() ?? 0;
|
|
192
|
+
}
|
|
193
|
+
catch { }
|
|
194
|
+
const { index } = ctx.addInputData(n8n_workflow_1.NodeConnectionTypes.AiTool, [[{ json: input }]], runIndex);
|
|
195
|
+
try {
|
|
196
|
+
const result = await sessionManager.callTool(toolName, args);
|
|
197
|
+
const output = { tool: toolName, result };
|
|
198
|
+
ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, index, [[{ json: output }]]);
|
|
199
|
+
return formatMcpToolResult(result);
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, index, e);
|
|
203
|
+
throw e;
|
|
204
|
+
}
|
|
118
205
|
},
|
|
119
206
|
metadata: { isFromToolkit: true },
|
|
120
207
|
});
|