n8n-nodes-smart-browser-automation 1.6.26 → 1.6.28
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/dist/nodes/SmartBrowserAutomation/utils/McpConnection.d.ts +10 -0
- package/dist/nodes/SmartBrowserAutomation/utils/McpConnection.js +72 -37
- package/dist/nodes/SmartBrowserAutomation/utils/McpUtils.d.ts +9 -0
- package/dist/nodes/SmartBrowserAutomation/utils/McpUtils.js +58 -0
- package/package.json +1 -1
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
2
|
import { type ISupplyDataFunctions, type IExecuteFunctions } from 'n8n-workflow';
|
|
3
3
|
import { type McpTool } from './McpUtils';
|
|
4
|
+
export type Result<T, E> = {
|
|
5
|
+
ok: true;
|
|
6
|
+
result: T;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
error: E;
|
|
10
|
+
};
|
|
11
|
+
export declare const createResultOk: <T>(result: T) => Result<T, never>;
|
|
12
|
+
export declare const createResultError: <E>(error: E) => Result<never, E>;
|
|
4
13
|
export interface McpConfig {
|
|
5
14
|
mcpEndpoint: string;
|
|
6
15
|
browserMode: string;
|
|
@@ -8,6 +17,7 @@ export interface McpConfig {
|
|
|
8
17
|
transportType?: 'sse' | 'stdio';
|
|
9
18
|
timeout?: number;
|
|
10
19
|
}
|
|
20
|
+
export declare function connectMcpClient(config: McpConfig): Promise<Result<Client, Error>>;
|
|
11
21
|
export declare function connectAndGetTools(_ctx: ISupplyDataFunctions | IExecuteFunctions, config: McpConfig): Promise<{
|
|
12
22
|
client: Client;
|
|
13
23
|
mcpTools: McpTool[];
|
|
@@ -1,61 +1,96 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createResultError = exports.createResultOk = void 0;
|
|
4
|
+
exports.connectMcpClient = connectMcpClient;
|
|
3
5
|
exports.connectAndGetTools = connectAndGetTools;
|
|
4
6
|
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
5
7
|
const sse_js_1 = require("@modelcontextprotocol/sdk/client/sse.js");
|
|
6
8
|
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
+
const createResultOk = (result) => ({ ok: true, result });
|
|
10
|
+
exports.createResultOk = createResultOk;
|
|
11
|
+
const createResultError = (error) => ({ ok: false, error });
|
|
12
|
+
exports.createResultError = createResultError;
|
|
13
|
+
// --- URL Helpers from read.txt ---
|
|
14
|
+
function safeCreateUrl(url, baseUrl) {
|
|
15
|
+
try {
|
|
16
|
+
return (0, exports.createResultOk)(new URL(url, baseUrl));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return (0, exports.createResultError)(error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function normalizeAndValidateUrl(input) {
|
|
23
|
+
// Remove invisible characters first (my addition, keeping robustness)
|
|
24
|
+
const cleanInput = input.replace(/[^\x20-\x7E]/g, '').trim();
|
|
25
|
+
const withProtocol = !/^https?:\/\//i.test(cleanInput) ? `http://${cleanInput}` : cleanInput;
|
|
26
|
+
const parsedUrl = safeCreateUrl(withProtocol);
|
|
27
|
+
if (!parsedUrl.ok) {
|
|
28
|
+
return (0, exports.createResultError)(parsedUrl.error);
|
|
29
|
+
}
|
|
30
|
+
return parsedUrl;
|
|
31
|
+
}
|
|
32
|
+
// --- Main Connection Logic ---
|
|
33
|
+
async function connectMcpClient(config) {
|
|
34
|
+
const endpointUrl = config.mcpEndpoint;
|
|
35
|
+
// Handle Stdio separately as it's not a URL
|
|
36
|
+
// Rough heuristic: if it looks like a command (no / or ://), it's stdio.
|
|
37
|
+
// But users might put "localhost:3000". `normalizeAndValidateUrl` handles localhost:3000 by adding http.
|
|
38
|
+
// We need a way to distinguish.
|
|
39
|
+
// For now, let's assume if it validates as a URL, we try URL.
|
|
40
|
+
// But "python main.py" is not a URL.
|
|
9
41
|
let client;
|
|
10
42
|
let Transport;
|
|
43
|
+
// User explicitly requested "only url" and "client method"
|
|
44
|
+
// We strictly enforce URL validation and connection.
|
|
45
|
+
const validUrl = normalizeAndValidateUrl(endpointUrl);
|
|
46
|
+
if (!validUrl.ok) {
|
|
47
|
+
return (0, exports.createResultError)(new Error(`Invalid MCP Endpoint URL: ${endpointUrl} (Error: ${validUrl.error.message})`));
|
|
48
|
+
}
|
|
49
|
+
const urlObj = validUrl.result;
|
|
50
|
+
// Detect SSE
|
|
51
|
+
// Logic from read.txt
|
|
52
|
+
const isSse = /(^|\/)sse\/?(\?|#|$)/i.test(urlObj.pathname);
|
|
53
|
+
client = new index_js_1.Client({ name: 'n8n-smart-browser-automation', version: '1.0.0' }, { capabilities: {} });
|
|
11
54
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
let urlIsSse = false;
|
|
17
|
-
try {
|
|
18
|
-
const url = new URL(mcpEndpoint);
|
|
19
|
-
urlIsSse = /(^|\/)sse\/?(\?|#|$)/i.test(url.pathname);
|
|
20
|
-
}
|
|
21
|
-
catch (e) {
|
|
22
|
-
return {
|
|
23
|
-
client: null,
|
|
24
|
-
mcpTools: [],
|
|
25
|
-
error: { error: 'Invalid URL', message: `Invalid MCP Endpoint URL: ${mcpEndpoint}` }
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
if (urlIsSse) {
|
|
29
|
-
Transport = new sse_js_1.SSEClientTransport(new URL(mcpEndpoint));
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
Transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(mcpEndpoint));
|
|
33
|
-
}
|
|
55
|
+
if (isSse) {
|
|
56
|
+
Transport = new sse_js_1.SSEClientTransport(urlObj, {
|
|
57
|
+
fetch: fetch,
|
|
58
|
+
});
|
|
34
59
|
}
|
|
35
60
|
else {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
command: 'node',
|
|
39
|
-
args: [mcpEndpoint],
|
|
40
|
-
// Pass env if needed, including CDP_URL if we want the server to pick it up via env
|
|
41
|
-
env: {
|
|
42
|
-
...process.env,
|
|
43
|
-
...(config.cdpEndpoint ? { CDP_URL: config.cdpEndpoint } : {}),
|
|
44
|
-
},
|
|
61
|
+
Transport = new streamableHttp_js_1.StreamableHTTPClientTransport(urlObj, {
|
|
62
|
+
fetch: fetch,
|
|
45
63
|
});
|
|
46
64
|
}
|
|
47
|
-
client = new index_js_1.Client({ name: 'n8n-smart-browser-automation', version: '1.0.0' }, { capabilities: {} });
|
|
48
65
|
await client.connect(Transport);
|
|
49
|
-
|
|
66
|
+
return (0, exports.createResultOk)(client);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
return (0, exports.createResultError)(new Error(`Connection Failed (${isSse ? 'SSE' : 'HTTP'}): ${e.message}`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function connectAndGetTools(_ctx, config) {
|
|
73
|
+
const connResult = await connectMcpClient(config);
|
|
74
|
+
if (!connResult.ok) {
|
|
75
|
+
return {
|
|
76
|
+
client: null,
|
|
77
|
+
mcpTools: [],
|
|
78
|
+
error: { error: 'Connection Failed', message: connResult.error.message }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const client = connResult.result;
|
|
82
|
+
try {
|
|
50
83
|
const result = await client.listTools();
|
|
51
84
|
const tools = (result.tools || []);
|
|
52
85
|
return { client, mcpTools: tools };
|
|
53
86
|
}
|
|
54
|
-
catch (
|
|
87
|
+
catch (e) {
|
|
88
|
+
// Don't forget to close if listing fails
|
|
89
|
+
await client.close();
|
|
55
90
|
return {
|
|
56
91
|
client: null,
|
|
57
92
|
mcpTools: [],
|
|
58
|
-
error: { error: '
|
|
93
|
+
error: { error: 'List Tools Failed', message: e.message }
|
|
59
94
|
};
|
|
60
95
|
}
|
|
61
96
|
}
|
|
@@ -17,6 +17,15 @@ export declare function getSelectedTools({ mode, includeTools, excludeTools, too
|
|
|
17
17
|
export declare const getErrorDescriptionFromToolCall: (result: unknown) => string | undefined;
|
|
18
18
|
export declare const createCallTool: (name: string, client: Client, timeout: number, onError: (error: string) => void) => (args: IDataObject) => Promise<{} | null>;
|
|
19
19
|
export declare function mcpToolToDynamicTool(tool: McpTool, onCallTool: (args: any) => Promise<any>): Promise<DynamicStructuredTool>;
|
|
20
|
+
/**
|
|
21
|
+
* Wraps a DynamicStructuredTool to log its execution to the n8n AI Tool output.
|
|
22
|
+
* This ensures the user sees the tool input and output in the n8n UI.
|
|
23
|
+
*/
|
|
24
|
+
export declare function logWrapper(tool: DynamicStructuredTool, nodeCtx: {
|
|
25
|
+
addOutputData: Function;
|
|
26
|
+
addInputData: Function;
|
|
27
|
+
getNextRunIndex?: Function;
|
|
28
|
+
}): DynamicStructuredTool;
|
|
20
29
|
export declare class McpToolkit extends Toolkit {
|
|
21
30
|
tools: DynamicStructuredTool[];
|
|
22
31
|
constructor(tools: DynamicStructuredTool[]);
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.McpToolkit = exports.createCallTool = exports.getErrorDescriptionFromToolCall = void 0;
|
|
4
4
|
exports.getSelectedTools = getSelectedTools;
|
|
5
5
|
exports.mcpToolToDynamicTool = mcpToolToDynamicTool;
|
|
6
|
+
exports.logWrapper = logWrapper;
|
|
6
7
|
const tools_1 = require("@langchain/core/tools");
|
|
7
8
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
8
9
|
const agents_1 = require("@langchain/classic/agents");
|
|
@@ -81,6 +82,63 @@ async function mcpToolToDynamicTool(tool, onCallTool) {
|
|
|
81
82
|
metadata: { isFromToolkit: true },
|
|
82
83
|
});
|
|
83
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Wraps a DynamicStructuredTool to log its execution to the n8n AI Tool output.
|
|
87
|
+
* This ensures the user sees the tool input and output in the n8n UI.
|
|
88
|
+
*/
|
|
89
|
+
function logWrapper(tool, nodeCtx) {
|
|
90
|
+
// Access internal properties safely via casting if needed
|
|
91
|
+
const toolAny = tool;
|
|
92
|
+
const originalFunc = toolAny.func;
|
|
93
|
+
toolAny.func = async (args) => {
|
|
94
|
+
const toolName = toolAny.name;
|
|
95
|
+
const input = {
|
|
96
|
+
tool: toolName,
|
|
97
|
+
arguments: args,
|
|
98
|
+
};
|
|
99
|
+
let runIndex = 0;
|
|
100
|
+
try {
|
|
101
|
+
// n8n type hack: try to get run index if available
|
|
102
|
+
runIndex = nodeCtx.getNextRunIndex?.() ?? 0;
|
|
103
|
+
}
|
|
104
|
+
catch { }
|
|
105
|
+
// Log Input
|
|
106
|
+
// 2 is the index for NodeConnectionTypes.AiTool usually, check your node definition
|
|
107
|
+
// In SmartBrowserAutomationTools, output[0] = AiTool, output[1] = Main.
|
|
108
|
+
// Wait, NodeConnectionTypes are strings "ai_tool", "main".
|
|
109
|
+
// The `addInputData` returns { index, runIndex }.
|
|
110
|
+
// We need to use the connection name or index.
|
|
111
|
+
// "ai_tool" is typically index 0 if listed first in outputs.
|
|
112
|
+
const { index } = nodeCtx.addInputData('ai_tool', [[{ json: input }]], runIndex);
|
|
113
|
+
try {
|
|
114
|
+
const result = await originalFunc(args);
|
|
115
|
+
const output = {
|
|
116
|
+
tool: toolName,
|
|
117
|
+
result: result,
|
|
118
|
+
};
|
|
119
|
+
// Log Output
|
|
120
|
+
// We wrap in [[ { json: ... } ]] because n8n expects INodeExecutionData[][]
|
|
121
|
+
nodeCtx.addOutputData('ai_tool', index, [[{ json: output }]]);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
// Log Error
|
|
126
|
+
const errorOutput = {
|
|
127
|
+
tool: toolName,
|
|
128
|
+
error: error.message || String(error),
|
|
129
|
+
};
|
|
130
|
+
// Try to log error output
|
|
131
|
+
try {
|
|
132
|
+
nodeCtx.addOutputData('ai_tool', index, [[{ json: errorOutput }]]);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
console.error(`Failed to log error for tool ${toolName}`, e);
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
return tool;
|
|
141
|
+
}
|
|
84
142
|
class McpToolkit extends agents_1.Toolkit {
|
|
85
143
|
tools;
|
|
86
144
|
constructor(tools) {
|