fastbrowser_cli 1.0.14 → 1.0.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.
- package/.playwright-mcp/.gitignore +3 -0
- package/README.md +29 -3
- package/dist/fastbrowser_cli/fastbrowser_cli.js +52 -35
- package/dist/fastbrowser_cli/fastbrowser_cli.js.map +1 -1
- package/dist/fastbrowser_cli/libs/http-client.js +3 -7
- package/dist/fastbrowser_cli/libs/http-client.js.map +1 -1
- package/dist/fastbrowser_cli/libs/server-manager.d.ts.map +1 -1
- package/dist/fastbrowser_cli/libs/server-manager.js +31 -29
- package/dist/fastbrowser_cli/libs/server-manager.js.map +1 -1
- package/dist/fastbrowser_httpd/fastbrowser_httpd.js +22 -18
- package/dist/fastbrowser_httpd/fastbrowser_httpd.js.map +1 -1
- package/dist/fastbrowser_httpd/libs/routes.d.ts +2 -2
- package/dist/fastbrowser_httpd/libs/routes.d.ts.map +1 -1
- package/dist/fastbrowser_httpd/libs/routes.js +4 -8
- package/dist/fastbrowser_httpd/libs/routes.js.map +1 -1
- package/dist/fastbrowser_httpd/libs/tool-schemas.js +39 -42
- package/dist/fastbrowser_httpd/libs/tool-schemas.js.map +1 -1
- package/dist/fastbrowser_mcp/fastbrowser_mcp.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/fastbrowser_mcp.js +270 -187
- package/dist/fastbrowser_mcp/fastbrowser_mcp.js.map +1 -1
- package/dist/fastbrowser_mcp/fastbrowser_types.d.ts +5 -0
- package/dist/fastbrowser_mcp/fastbrowser_types.d.ts.map +1 -0
- package/dist/fastbrowser_mcp/fastbrowser_types.js +2 -0
- package/dist/fastbrowser_mcp/fastbrowser_types.js.map +1 -0
- package/dist/{fastweb_mcp/libs/mcp_client.d.ts → fastbrowser_mcp/libs/mcp_client_TOREMOVE.d.ts} +6 -2
- package/dist/fastbrowser_mcp/libs/mcp_client_TOREMOVE.d.ts.map +1 -0
- package/dist/{src/libs/mcp_client.js → fastbrowser_mcp/libs/mcp_client_TOREMOVE.js} +15 -12
- package/dist/fastbrowser_mcp/libs/mcp_client_TOREMOVE.js.map +1 -0
- package/dist/{src/libs/mcp_client.d.ts → fastbrowser_mcp/libs/mcp_my_client.d.ts} +6 -2
- package/dist/fastbrowser_mcp/libs/mcp_my_client.d.ts.map +1 -0
- package/dist/{fastweb_mcp/libs/mcp_client.js → fastbrowser_mcp/libs/mcp_my_client.js} +15 -12
- package/dist/fastbrowser_mcp/libs/mcp_my_client.js.map +1 -0
- package/dist/fastbrowser_mcp/libs/mcp_proxy.d.ts +2 -2
- package/dist/fastbrowser_mcp/libs/mcp_proxy.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_proxy.js +9 -16
- package/dist/fastbrowser_mcp/libs/mcp_proxy.js.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts +35 -0
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts.map +1 -0
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.js +161 -0
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.js.map +1 -0
- package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.d.ts +28 -0
- package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.d.ts.map +1 -0
- package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.js +210 -0
- package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.js.map +1 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.d.ts +10 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.d.ts.map +1 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.js +155 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.js.map +1 -0
- package/dist/fastbrowser_mcp/libs/schemas.js +14 -17
- package/dist/fastbrowser_mcp/libs/schemas.js.map +1 -1
- package/examples/mcp_client_playwright.ts +34 -0
- package/examples/welcometothejungle/wttj-job.ts +180 -0
- package/examples/welcometothejungle/wttj-search.ts +105 -0
- package/outputs/.gitignore +3 -0
- package/package.json +12 -8
- package/src/fastbrowser_cli/fastbrowser_cli.ts +34 -11
- package/src/fastbrowser_cli/libs/server-manager.ts +23 -14
- package/src/fastbrowser_httpd/fastbrowser_httpd.ts +16 -5
- package/src/fastbrowser_httpd/libs/routes.ts +2 -2
- package/src/fastbrowser_mcp/fastbrowser_mcp.ts +324 -150
- package/src/fastbrowser_mcp/fastbrowser_types.ts +4 -0
- package/src/fastbrowser_mcp/libs/{mcp_client.ts → mcp_client_TOREMOVE.ts} +13 -1
- package/src/fastbrowser_mcp/libs/mcp_my_client.ts +128 -0
- package/src/fastbrowser_mcp/libs/mcp_proxy.ts +2 -2
- package/src/fastbrowser_mcp/libs/mcp_target_helper.ts +164 -0
- package/src/fastbrowser_mcp/libs/playwright_a11y_helper.ts +249 -0
- package/src/fastbrowser_mcp/libs/response_formatter.ts +162 -0
- package/src/fastbrowser_mcp/libs/schemas.ts +2 -2
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +10 -22
- package/dist/contrib/fastweb-cli/fastweb-cli.d.ts +0 -3
- package/dist/contrib/fastweb-cli/fastweb-cli.d.ts.map +0 -1
- package/dist/contrib/fastweb-cli/fastweb-cli.js +0 -151
- package/dist/contrib/fastweb-cli/fastweb-cli.js.map +0 -1
- package/dist/contrib/fastweb-cli/http-client.d.ts +0 -7
- package/dist/contrib/fastweb-cli/http-client.d.ts.map +0 -1
- package/dist/contrib/fastweb-cli/http-client.js +0 -51
- package/dist/contrib/fastweb-cli/http-client.js.map +0 -1
- package/dist/contrib/fastweb-http-server/fastweb-http-server.d.ts +0 -3
- package/dist/contrib/fastweb-http-server/fastweb-http-server.d.ts.map +0 -1
- package/dist/contrib/fastweb-http-server/fastweb-http-server.js +0 -82
- package/dist/contrib/fastweb-http-server/fastweb-http-server.js.map +0 -1
- package/dist/contrib/fastweb-http-server/routes.d.ts +0 -6
- package/dist/contrib/fastweb-http-server/routes.d.ts.map +0 -1
- package/dist/contrib/fastweb-http-server/routes.js +0 -41
- package/dist/contrib/fastweb-http-server/routes.js.map +0 -1
- package/dist/contrib/fastweb-http-server/tool-schemas.d.ts +0 -63
- package/dist/contrib/fastweb-http-server/tool-schemas.d.ts.map +0 -1
- package/dist/contrib/fastweb-http-server/tool-schemas.js +0 -61
- package/dist/contrib/fastweb-http-server/tool-schemas.js.map +0 -1
- package/dist/fastbrowser_mcp/libs/mcp_client.d.ts +0 -120
- package/dist/fastbrowser_mcp/libs/mcp_client.d.ts.map +0 -1
- package/dist/fastbrowser_mcp/libs/mcp_client.js +0 -83
- package/dist/fastbrowser_mcp/libs/mcp_client.js.map +0 -1
- package/dist/fastweb_cli/fastweb_cli.d.ts +0 -3
- package/dist/fastweb_cli/fastweb_cli.d.ts.map +0 -1
- package/dist/fastweb_cli/fastweb_cli.js +0 -254
- package/dist/fastweb_cli/fastweb_cli.js.map +0 -1
- package/dist/fastweb_cli/http-client.d.ts +0 -7
- package/dist/fastweb_cli/http-client.d.ts.map +0 -1
- package/dist/fastweb_cli/http-client.js +0 -51
- package/dist/fastweb_cli/http-client.js.map +0 -1
- package/dist/fastweb_cli/libs/http-client.d.ts +0 -7
- package/dist/fastweb_cli/libs/http-client.d.ts.map +0 -1
- package/dist/fastweb_cli/libs/http-client.js +0 -51
- package/dist/fastweb_cli/libs/http-client.js.map +0 -1
- package/dist/fastweb_cli/libs/server-manager.d.ts +0 -12
- package/dist/fastweb_cli/libs/server-manager.d.ts.map +0 -1
- package/dist/fastweb_cli/libs/server-manager.js +0 -194
- package/dist/fastweb_cli/libs/server-manager.js.map +0 -1
- package/dist/fastweb_http_server/fastweb_http_server.d.ts +0 -3
- package/dist/fastweb_http_server/fastweb_http_server.d.ts.map +0 -1
- package/dist/fastweb_http_server/fastweb_http_server.js +0 -82
- package/dist/fastweb_http_server/fastweb_http_server.js.map +0 -1
- package/dist/fastweb_http_server/libs/routes.d.ts +0 -6
- package/dist/fastweb_http_server/libs/routes.d.ts.map +0 -1
- package/dist/fastweb_http_server/libs/routes.js +0 -41
- package/dist/fastweb_http_server/libs/routes.js.map +0 -1
- package/dist/fastweb_http_server/libs/tool-schemas.d.ts +0 -72
- package/dist/fastweb_http_server/libs/tool-schemas.d.ts.map +0 -1
- package/dist/fastweb_http_server/libs/tool-schemas.js +0 -65
- package/dist/fastweb_http_server/libs/tool-schemas.js.map +0 -1
- package/dist/fastweb_http_server/routes.d.ts +0 -6
- package/dist/fastweb_http_server/routes.d.ts.map +0 -1
- package/dist/fastweb_http_server/routes.js +0 -41
- package/dist/fastweb_http_server/routes.js.map +0 -1
- package/dist/fastweb_http_server/tool-schemas.d.ts +0 -63
- package/dist/fastweb_http_server/tool-schemas.d.ts.map +0 -1
- package/dist/fastweb_http_server/tool-schemas.js +0 -61
- package/dist/fastweb_http_server/tool-schemas.js.map +0 -1
- package/dist/fastweb_mcp/fastweb_mcp.d.ts +0 -4
- package/dist/fastweb_mcp/fastweb_mcp.d.ts.map +0 -1
- package/dist/fastweb_mcp/fastweb_mcp.js +0 -417
- package/dist/fastweb_mcp/fastweb_mcp.js.map +0 -1
- package/dist/fastweb_mcp/libs/mcp_client.d.ts.map +0 -1
- package/dist/fastweb_mcp/libs/mcp_client.js.map +0 -1
- package/dist/fastweb_mcp/libs/mcp_proxy.d.ts +0 -10
- package/dist/fastweb_mcp/libs/mcp_proxy.d.ts.map +0 -1
- package/dist/fastweb_mcp/libs/mcp_proxy.js +0 -45
- package/dist/fastweb_mcp/libs/mcp_proxy.js.map +0 -1
- package/dist/fastweb_mcp/libs/schemas.d.ts +0 -28
- package/dist/fastweb_mcp/libs/schemas.d.ts.map +0 -1
- package/dist/fastweb_mcp/libs/schemas.js +0 -38
- package/dist/fastweb_mcp/libs/schemas.js.map +0 -1
- package/dist/src/fastweb_mcp.d.ts +0 -17
- package/dist/src/fastweb_mcp.d.ts.map +0 -1
- package/dist/src/fastweb_mcp.js +0 -342
- package/dist/src/fastweb_mcp.js.map +0 -1
- package/dist/src/libs/mcp_client.d.ts.map +0 -1
- package/dist/src/libs/mcp_client.js.map +0 -1
- package/dist/src/libs/mcp_proxy.d.ts +0 -10
- package/dist/src/libs/mcp_proxy.d.ts.map +0 -1
- package/dist/src/libs/mcp_proxy.js +0 -45
- package/dist/src/libs/mcp_proxy.js.map +0 -1
|
@@ -1,89 +1,65 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
-
var ownKeys = function(o) {
|
|
21
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.QuerySelectorsFirstInputSchema = exports.QuerySelectorFirstInputSchema = exports.QuerySelectorsInputSchema = exports.QuerySelectorInputSchema = void 0;
|
|
2
|
+
// node imports
|
|
3
|
+
import * as Assert from 'node:assert/strict';
|
|
4
|
+
import Fs from 'node:fs';
|
|
5
|
+
import Path from 'node:path';
|
|
38
6
|
// npm imports
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
import { Command, Option } from 'commander';
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import * as A11yParse from "a11y_parse";
|
|
42
10
|
// local imports
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Object.defineProperty(exports, "QuerySelectorsFirstInputSchema", { enumerable: true, get: function () { return schemas_js_1.QuerySelectorsFirstInputSchema; } });
|
|
11
|
+
import { McpMyClient } from "./libs/mcp_my_client.js";
|
|
12
|
+
import { McpProxy } from "./libs/mcp_proxy.js";
|
|
13
|
+
import { ResponseFormatter } from "./libs/response_formatter.js";
|
|
14
|
+
import { QuerySelectorInputSchema, QuerySelectorsInputSchema, QuerySelectorFirstInputSchema, QuerySelectorsFirstInputSchema, } from "./libs/schemas.js";
|
|
15
|
+
import { McpTargetHelper } from './libs/mcp_target_helper.js';
|
|
16
|
+
export { QuerySelectorInputSchema, QuerySelectorsInputSchema, QuerySelectorFirstInputSchema, QuerySelectorsFirstInputSchema, };
|
|
50
17
|
///////////////////////////////////////////////////////////////////////////////
|
|
51
18
|
///////////////////////////////////////////////////////////////////////////////
|
|
52
19
|
//
|
|
53
20
|
///////////////////////////////////////////////////////////////////////////////
|
|
54
21
|
///////////////////////////////////////////////////////////////////////////////
|
|
55
22
|
class MainHelper {
|
|
23
|
+
static async _getA11yText(mcpClient) {
|
|
24
|
+
const mcpTarget = await mcpClient.getMcpTarget();
|
|
25
|
+
const toolConfig = await McpTargetHelper.targetToolTakeSnapshot(mcpTarget);
|
|
26
|
+
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
27
|
+
const snapshotText = await ResponseFormatter.formatTakeSnapshot(mcpTarget, callToolResult);
|
|
28
|
+
// sanity check
|
|
29
|
+
Assert.ok(snapshotText !== undefined, "Snapshot text is empty");
|
|
30
|
+
// return the snapshot text
|
|
31
|
+
return snapshotText;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* TODO to remove this function - just call the .parse
|
|
35
|
+
* @param mcpClient
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
static async _getSnapshotTree(mcpClient) {
|
|
39
|
+
const a11yText = await this._getA11yText(mcpClient);
|
|
40
|
+
// parse the accessibility tree
|
|
41
|
+
const a11yTree = A11yParse.A11yTree.parse(a11yText);
|
|
42
|
+
// return the parsed accessibility tree
|
|
43
|
+
return { snapshotText: a11yText, a11yTree };
|
|
44
|
+
}
|
|
56
45
|
/**
|
|
57
46
|
* Takes a snapshot of the current page to get the latest accessibility tree, and parses it into an AxNode tree structure.
|
|
58
47
|
* @param mcpClient The MCP client to use for taking a snapshot.
|
|
59
48
|
* @returns The parsed accessibility tree as an AxNode.
|
|
60
49
|
*/
|
|
61
50
|
static async _resolveSelectorToUid(mcpClient, selector) {
|
|
51
|
+
// Fast path: if the selector is already a uid selector (e.g. "#1_3"), extract and return the uid directly without
|
|
52
|
+
// taking a snapshot or querying the tree.
|
|
62
53
|
const fastPathMatch = selector.match(/^#([\w-]+)$/);
|
|
63
54
|
if (fastPathMatch !== null)
|
|
64
55
|
return fastPathMatch[1];
|
|
65
|
-
|
|
56
|
+
// Slow path: take a snapshot, parse the tree, and query it with the provided selector to find the uid of the first matching node.
|
|
57
|
+
const { a11yTree } = await this._getSnapshotTree(mcpClient);
|
|
66
58
|
const selectedNodes = A11yParse.A11yQuery.querySelectorAll(a11yTree, selector);
|
|
67
59
|
if (selectedNodes.length === 0)
|
|
68
60
|
throw new Error(`No node matched selector '${selector}'`);
|
|
69
61
|
return selectedNodes[0].uid;
|
|
70
62
|
}
|
|
71
|
-
static async _getSnapshotTree(mcpClient) {
|
|
72
|
-
// take a snapshot to get the latest accessibility tree
|
|
73
|
-
const response = await mcpClient.callTool('take_snapshot', {});
|
|
74
|
-
const responseText = response.content[0];
|
|
75
|
-
if (responseText.type !== "text")
|
|
76
|
-
throw new Error("Unexpected content type");
|
|
77
|
-
// get the snapshot text and remove the first line (snapshot metadata)
|
|
78
|
-
let snapshotText = responseText.text;
|
|
79
|
-
snapshotText = snapshotText.split('\n').slice(1).join('\n');
|
|
80
|
-
// log to console for debugging
|
|
81
|
-
// console.log("Snapshot text content:");
|
|
82
|
-
// console.log(snapshotText);
|
|
83
|
-
// parse the accessibility tree
|
|
84
|
-
const a11yTree = A11yParse.A11yTree.parse(snapshotText);
|
|
85
|
-
return a11yTree;
|
|
86
|
-
}
|
|
87
63
|
/**
|
|
88
64
|
* Formats a list of selected accessibility-tree nodes into the text block used by
|
|
89
65
|
* both querySelector and querySelectorsAll responses.
|
|
@@ -115,7 +91,7 @@ class MainHelper {
|
|
|
115
91
|
* @returns A text representation of the query results.
|
|
116
92
|
*/
|
|
117
93
|
static async querySelectorsAll(mcpClient, querySelectors) {
|
|
118
|
-
const a11yTree = await this._getSnapshotTree(mcpClient);
|
|
94
|
+
const { a11yTree } = await this._getSnapshotTree(mcpClient);
|
|
119
95
|
const responseTexts = [];
|
|
120
96
|
for (const querySelector of querySelectors.selectors) {
|
|
121
97
|
// query the tree with the provided selector
|
|
@@ -139,7 +115,7 @@ class MainHelper {
|
|
|
139
115
|
* @returns A text representation of the first matching node for each selector.
|
|
140
116
|
*/
|
|
141
117
|
static async querySelectors(mcpClient, querySelectors) {
|
|
142
|
-
const a11yTree = await this._getSnapshotTree(mcpClient);
|
|
118
|
+
const { a11yTree } = await this._getSnapshotTree(mcpClient);
|
|
143
119
|
const responseTexts = [];
|
|
144
120
|
for (const querySelector of querySelectors.selectors) {
|
|
145
121
|
const selectedNodes = A11yParse.A11yQuery.querySelectorAll(a11yTree, querySelector.selector);
|
|
@@ -150,90 +126,60 @@ class MainHelper {
|
|
|
150
126
|
}
|
|
151
127
|
///////////////////////////////////////////////////////////////////////////////
|
|
152
128
|
///////////////////////////////////////////////////////////////////////////////
|
|
153
|
-
//
|
|
129
|
+
// Init external tools (i.e. tools that are implemented in terms of calls to the mcpClient, rather than directly registered on the mcpClient)
|
|
154
130
|
///////////////////////////////////////////////////////////////////////////////
|
|
155
131
|
///////////////////////////////////////////////////////////////////////////////
|
|
156
|
-
static async
|
|
132
|
+
static async initExternalTools(mcpServer, mcpClient) {
|
|
133
|
+
const mcpTarget = await mcpClient.getMcpTarget();
|
|
157
134
|
///////////////////////////////////////////////////////////////////////////////
|
|
158
135
|
///////////////////////////////////////////////////////////////////////////////
|
|
159
|
-
//
|
|
136
|
+
// list_pages
|
|
160
137
|
///////////////////////////////////////////////////////////////////////////////
|
|
161
138
|
///////////////////////////////////////////////////////////////////////////////
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
],
|
|
173
|
-
},
|
|
139
|
+
mcpServer.registerTool(McpTargetHelper.EXTERNAL_TOOL_NAME.listPages, {
|
|
140
|
+
description: "List the open pages/tabs in the browser",
|
|
141
|
+
inputSchema: z.object({}),
|
|
142
|
+
}, async () => {
|
|
143
|
+
const toolConfig = await McpTargetHelper.targetToolListPages(mcpTarget);
|
|
144
|
+
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
145
|
+
let outputStr = await ResponseFormatter.formatListPages(mcpTarget, callToolResult);
|
|
146
|
+
return {
|
|
147
|
+
content: [{ type: "text", text: outputStr }],
|
|
148
|
+
};
|
|
174
149
|
});
|
|
175
|
-
// Connect the mcp client to the chrome-devtools-mcp tool, which will allow us to take snapshots and query the accessibility tree.
|
|
176
|
-
await mcpClient.connect();
|
|
177
|
-
if (verbose) {
|
|
178
|
-
// list all the tools available in mcpClient for debugging
|
|
179
|
-
const mcpClientTools = await mcpClient.listTools();
|
|
180
|
-
console.error("Tools available in mcpClient:");
|
|
181
|
-
for (const tool of mcpClientTools) {
|
|
182
|
-
console.error(`- ${tool.name}: ${tool.description}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
150
|
///////////////////////////////////////////////////////////////////////////////
|
|
186
151
|
///////////////////////////////////////////////////////////////////////////////
|
|
187
|
-
//
|
|
152
|
+
// navigate_page
|
|
188
153
|
///////////////////////////////////////////////////////////////////////////////
|
|
189
154
|
///////////////////////////////////////////////////////////////////////////////
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
url:
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
selector: 'heading[level="1"]',
|
|
204
|
-
withAncestors: false,
|
|
205
|
-
limit: 0,
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
});
|
|
209
|
-
console.log(responseText);
|
|
210
|
-
await mcpClient.close();
|
|
211
|
-
process.exit(0);
|
|
212
|
-
}
|
|
155
|
+
mcpServer.registerTool(McpTargetHelper.EXTERNAL_TOOL_NAME.navigatePage, {
|
|
156
|
+
description: "Navigate the current page to a new URL",
|
|
157
|
+
inputSchema: z.object({
|
|
158
|
+
url: z.string().describe("The URL to navigate to"),
|
|
159
|
+
}),
|
|
160
|
+
}, async ({ url }) => {
|
|
161
|
+
const toolConfig = await McpTargetHelper.targetToolNavigatePage(mcpTarget, url);
|
|
162
|
+
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
163
|
+
let outputStr = await ResponseFormatter.formatNavigatePage(mcpTarget, callToolResult);
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: "text", text: outputStr }],
|
|
166
|
+
};
|
|
167
|
+
});
|
|
213
168
|
///////////////////////////////////////////////////////////////////////////////
|
|
214
169
|
///////////////////////////////////////////////////////////////////////////////
|
|
215
|
-
//
|
|
170
|
+
// list_pages
|
|
216
171
|
///////////////////////////////////////////////////////////////////////////////
|
|
217
172
|
///////////////////////////////////////////////////////////////////////////////
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
'close_page',
|
|
229
|
-
'navigate_page',
|
|
230
|
-
'take_snapshot',
|
|
231
|
-
];
|
|
232
|
-
for (const toolName of toolsToProxys) {
|
|
233
|
-
await mcpProxy.proxyToolCall(mcpClient, toolName);
|
|
234
|
-
}
|
|
235
|
-
// Connect the MCP proxy server to start accepting connections from MCP clients (e.g. LLM agents).
|
|
236
|
-
await mcpProxy.connect();
|
|
173
|
+
mcpServer.registerTool(McpTargetHelper.EXTERNAL_TOOL_NAME.takeSnapshot, {
|
|
174
|
+
description: "Take a snapshot of the current page to get the latest accessibility tree",
|
|
175
|
+
inputSchema: z.object({}),
|
|
176
|
+
}, async () => {
|
|
177
|
+
const a11yText = await MainHelper._getA11yText(mcpClient);
|
|
178
|
+
let outputStr = a11yText;
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: outputStr }],
|
|
181
|
+
};
|
|
182
|
+
});
|
|
237
183
|
///////////////////////////////////////////////////////////////////////////////
|
|
238
184
|
///////////////////////////////////////////////////////////////////////////////
|
|
239
185
|
// .querySelectorsAll tool implementation
|
|
@@ -252,13 +198,12 @@ class MainHelper {
|
|
|
252
198
|
`- You can use union: e.g. "heading, link"`,
|
|
253
199
|
`- You can combine selectors: e.g. 'RootWebArea > link[url*="iana"]'`,
|
|
254
200
|
].join('\n');
|
|
255
|
-
|
|
256
|
-
mcpServer.registerTool("querySelectorsAll", {
|
|
201
|
+
mcpServer.registerTool(McpTargetHelper.EXTERNAL_TOOL_NAME.querySelectorsAll, {
|
|
257
202
|
description: [
|
|
258
203
|
`Query the accessibility tree with CSS-like selectors`,
|
|
259
204
|
`Selector syntax: ${cssSelectorLanguageDescription} `,
|
|
260
205
|
].join('\n'),
|
|
261
|
-
inputSchema:
|
|
206
|
+
inputSchema: QuerySelectorsInputSchema,
|
|
262
207
|
}, async (querySelectorsInput) => {
|
|
263
208
|
// query the accessibility tree with the provided selector
|
|
264
209
|
const outputText = await MainHelper.querySelectorsAll(mcpClient, querySelectorsInput);
|
|
@@ -271,12 +216,12 @@ class MainHelper {
|
|
|
271
216
|
// .querySelectors tool implementation
|
|
272
217
|
///////////////////////////////////////////////////////////////////////////////
|
|
273
218
|
///////////////////////////////////////////////////////////////////////////////
|
|
274
|
-
mcpServer.registerTool(
|
|
219
|
+
mcpServer.registerTool(McpTargetHelper.EXTERNAL_TOOL_NAME.querySelectors, {
|
|
275
220
|
description: [
|
|
276
221
|
`Query the accessibility tree with one or more CSS-like selectors. For each selector, returns the first matching node (or "No node found").`,
|
|
277
222
|
`Selector syntax: ${cssSelectorLanguageDescription} `,
|
|
278
223
|
].join('\n'),
|
|
279
|
-
inputSchema:
|
|
224
|
+
inputSchema: QuerySelectorsFirstInputSchema,
|
|
280
225
|
}, async (querySelectorsInput) => {
|
|
281
226
|
const outputText = await MainHelper.querySelectors(mcpClient, querySelectorsInput);
|
|
282
227
|
return {
|
|
@@ -288,10 +233,10 @@ class MainHelper {
|
|
|
288
233
|
// pressKeys tool implementation
|
|
289
234
|
///////////////////////////////////////////////////////////////////////////////
|
|
290
235
|
///////////////////////////////////////////////////////////////////////////////
|
|
291
|
-
mcpServer.registerTool(
|
|
236
|
+
mcpServer.registerTool(McpTargetHelper.EXTERNAL_TOOL_NAME.pressKeys, {
|
|
292
237
|
description: "Press a sequence of keys on the page. E.g. 'Tab', 'Enter', 'ArrowDown'",
|
|
293
|
-
inputSchema:
|
|
294
|
-
keys:
|
|
238
|
+
inputSchema: z.object({
|
|
239
|
+
keys: z.string().describe("The keys to press, in order, comma-separated. E.g. 'Hello, Tab, Enter, ArrowDown'"),
|
|
295
240
|
}),
|
|
296
241
|
}, async ({ keys }) => {
|
|
297
242
|
// Build the list of keys to send, splitting regular characters into individual key presses, but keeping special keys as-is
|
|
@@ -312,11 +257,20 @@ class MainHelper {
|
|
|
312
257
|
console.error("Keys to send:", keysToSend);
|
|
313
258
|
// chrome-devtools-mcp's 'press_key' tool accepts a single 'key' per call — loop through the sequence
|
|
314
259
|
for (const key of keysToSend) {
|
|
315
|
-
await
|
|
260
|
+
const toolConfig = await McpTargetHelper.targetToolPressKey(mcpTarget, key);
|
|
261
|
+
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
262
|
+
// trying to handle the error case
|
|
263
|
+
if (callToolResult.isError) {
|
|
264
|
+
console.error(`Error pressing key '${key}':`, callToolResult.error);
|
|
265
|
+
return {
|
|
266
|
+
content: [{ type: "text", text: `Error pressing key '${key}'` }],
|
|
267
|
+
};
|
|
268
|
+
}
|
|
316
269
|
}
|
|
270
|
+
let outputText = await ResponseFormatter.formatPressKeys(mcpTarget, keysToSend);
|
|
317
271
|
// return a response indicating which keys were pressed
|
|
318
272
|
return {
|
|
319
|
-
content: [{ type: "text", text:
|
|
273
|
+
content: [{ type: "text", text: outputText }],
|
|
320
274
|
};
|
|
321
275
|
});
|
|
322
276
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -324,74 +278,186 @@ class MainHelper {
|
|
|
324
278
|
// click tool implementation
|
|
325
279
|
///////////////////////////////////////////////////////////////////////////////
|
|
326
280
|
///////////////////////////////////////////////////////////////////////////////
|
|
327
|
-
mcpServer.registerTool(
|
|
281
|
+
mcpServer.registerTool(McpTargetHelper.EXTERNAL_TOOL_NAME.click, {
|
|
328
282
|
description: [
|
|
329
283
|
`Click an element selected by an accessibility selector.`,
|
|
330
284
|
`Selector syntax: ${cssSelectorLanguageDescription} `,
|
|
331
285
|
].join('\n'),
|
|
332
286
|
inputSchema: {
|
|
333
|
-
selector:
|
|
287
|
+
selector: z.string().describe('Accessibility selector (e.g. "#1_3" or \'button[name="Submit"]\')'),
|
|
334
288
|
},
|
|
335
289
|
}, async ({ selector }) => {
|
|
336
290
|
const uid = await MainHelper._resolveSelectorToUid(mcpClient, selector);
|
|
337
|
-
|
|
291
|
+
const toolConfig = await McpTargetHelper.targetToolClick(mcpTarget, uid);
|
|
292
|
+
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
293
|
+
let outputText = await ResponseFormatter.formatClick(mcpTarget, callToolResult);
|
|
294
|
+
return {
|
|
295
|
+
content: [{ type: "text", text: outputText }],
|
|
296
|
+
};
|
|
338
297
|
});
|
|
339
298
|
///////////////////////////////////////////////////////////////////////////////
|
|
340
299
|
///////////////////////////////////////////////////////////////////////////////
|
|
341
300
|
// fill_form tool implementation
|
|
342
301
|
///////////////////////////////////////////////////////////////////////////////
|
|
343
302
|
///////////////////////////////////////////////////////////////////////////////
|
|
344
|
-
mcpServer.registerTool(
|
|
303
|
+
mcpServer.registerTool(McpTargetHelper.EXTERNAL_TOOL_NAME.fillForm, {
|
|
345
304
|
description: [
|
|
346
305
|
`Fill one or more form fields, each selected by an accessibility selector.`,
|
|
347
306
|
`Selector syntax: ${cssSelectorLanguageDescription} `,
|
|
348
307
|
].join('\n'),
|
|
349
308
|
inputSchema: {
|
|
350
|
-
elements:
|
|
351
|
-
selector:
|
|
352
|
-
value:
|
|
309
|
+
elements: z.array(z.object({
|
|
310
|
+
selector: z.string().describe('Accessibility selector for the form field'),
|
|
311
|
+
value: z.string().describe('Value to fill into the field'),
|
|
353
312
|
})).describe('Elements to fill, each with a selector and a value'),
|
|
354
313
|
},
|
|
355
314
|
}, async ({ elements }) => {
|
|
356
|
-
const resolved =
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
315
|
+
const resolved = [];
|
|
316
|
+
for (const element of elements) {
|
|
317
|
+
const uid = await MainHelper._resolveSelectorToUid(mcpClient, element.selector);
|
|
318
|
+
resolved.push({
|
|
319
|
+
uid,
|
|
320
|
+
value: element.value,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (mcpTarget === 'chrome_devtools') {
|
|
324
|
+
const toolConfig = await McpTargetHelper.targetToolFillForm(mcpTarget, resolved);
|
|
325
|
+
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
326
|
+
// const callToolResult = await mcpClient.callTool('fill_form', { elements: resolved });
|
|
327
|
+
return callToolResult;
|
|
328
|
+
}
|
|
329
|
+
else if (mcpTarget === 'playwright') {
|
|
330
|
+
const fields = resolved.map((element, index) => ({
|
|
331
|
+
name: `Field ${index + 1}`,
|
|
332
|
+
type: 'textbox', // for simplicity, we assume all fields are textboxes in this example
|
|
333
|
+
ref: element.uid,
|
|
334
|
+
value: element.value,
|
|
335
|
+
}));
|
|
336
|
+
const callToolResult = await mcpClient.callTool('browser_fill_form', { fields: fields });
|
|
337
|
+
return callToolResult;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
341
|
+
}
|
|
361
342
|
});
|
|
362
343
|
///////////////////////////////////////////////////////////////////////////////
|
|
363
344
|
///////////////////////////////////////////////////////////////////////////////
|
|
364
345
|
// .get_current_datetime tool implementation
|
|
365
346
|
///////////////////////////////////////////////////////////////////////////////
|
|
366
347
|
///////////////////////////////////////////////////////////////////////////////
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
348
|
+
const timezoneSchema = {
|
|
349
|
+
timezone: z.string().optional().describe("IANA timezone name (e.g. 'America/New_York'). Defaults to local system timezone."),
|
|
350
|
+
};
|
|
351
|
+
mcpServer.registerTool(McpTargetHelper.EXTERNAL_TOOL_NAME.getCurrentDateTime, {
|
|
352
|
+
description: "Get the current date and time",
|
|
353
|
+
inputSchema: timezoneSchema,
|
|
354
|
+
}, async ({ timezone }) => {
|
|
355
|
+
const date = new Date();
|
|
356
|
+
const options = {
|
|
357
|
+
timeZone: timezone,
|
|
358
|
+
year: "numeric",
|
|
359
|
+
month: "2-digit",
|
|
360
|
+
day: "2-digit",
|
|
361
|
+
hour: "2-digit",
|
|
362
|
+
minute: "2-digit",
|
|
363
|
+
second: "2-digit",
|
|
364
|
+
timeZoneName: "short",
|
|
365
|
+
};
|
|
366
|
+
const formatted = new Intl.DateTimeFormat("en-US", options).format(date);
|
|
367
|
+
return {
|
|
368
|
+
content: [{ type: "text", text: formatted }],
|
|
369
|
+
};
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
373
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
374
|
+
// .commandMcpServer: the main entry point for starting the MCP proxy server, which connects to the MCP
|
|
375
|
+
// target (e.g. chrome-devtools-mcp), proxies tools from the MCP target, and registers external tools that
|
|
376
|
+
// are implemented in terms of calls to the MCP target.
|
|
377
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
378
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
379
|
+
static async commandMcpServer({ mcpTarget, verbose = false, }) {
|
|
380
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
381
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
382
|
+
// mcp client
|
|
383
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
384
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
385
|
+
const { command: mcpCommand, args: mcpArgs } = McpTargetHelper.mcpArgs(mcpTarget);
|
|
386
|
+
// TODO remove this MCPMyClient - confusing
|
|
387
|
+
// Create the mcp client toward the official chrome-devtools-mcp tool, which provides access to a Chrome tab's accessibility
|
|
388
|
+
// tree and allows us to take snapshots and query the tree with selectors.
|
|
389
|
+
const mcpClient = new McpMyClient({
|
|
390
|
+
name: "fastbrowser_mcp_proxy_client",
|
|
391
|
+
version: "1.0.0",
|
|
392
|
+
mcpTarget,
|
|
393
|
+
transport: {
|
|
394
|
+
type: "stdio",
|
|
395
|
+
command: mcpCommand,
|
|
396
|
+
args: mcpArgs,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
// Connect the mcp client to the chrome-devtools-mcp tool, which will allow us to take snapshots and query the accessibility tree.
|
|
400
|
+
await mcpClient.connect();
|
|
401
|
+
// If verbose mode is enabled, list all the tools available in the mcpClient for debugging purposes.
|
|
402
|
+
if (verbose) {
|
|
403
|
+
// list all the tools available in mcpClient for debugging
|
|
404
|
+
const mcpClientTools = await mcpClient.listTools();
|
|
405
|
+
console.error("Tools available in mcpClient:");
|
|
406
|
+
for (const tool of mcpClientTools) {
|
|
407
|
+
console.error(`- ${tool.name}: ${tool.description}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
411
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
412
|
+
//
|
|
413
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
414
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
415
|
+
if (false) {
|
|
416
|
+
await mcpClient.callTool('navigate_page', {
|
|
417
|
+
// url: "https://www.example.com/",
|
|
418
|
+
url: "https://welcometothejungle.com/"
|
|
419
|
+
});
|
|
420
|
+
const responseText = await MainHelper.querySelectorsAll(mcpClient, {
|
|
421
|
+
selectors: [
|
|
422
|
+
{
|
|
423
|
+
selector: "link, button",
|
|
424
|
+
withAncestors: true,
|
|
425
|
+
limit: 0,
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
selector: 'heading[level="1"]',
|
|
429
|
+
withAncestors: false,
|
|
430
|
+
limit: 0,
|
|
431
|
+
},
|
|
432
|
+
],
|
|
433
|
+
});
|
|
434
|
+
console.log(responseText);
|
|
435
|
+
await mcpClient.close();
|
|
436
|
+
process.exit(0);
|
|
437
|
+
}
|
|
438
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
439
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
440
|
+
//
|
|
441
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
442
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
443
|
+
// Create the MCP proxy server, which will expose tools to the MCP client and proxy calls to those tools to
|
|
444
|
+
// the mcpClient connected to the chrome-devtools-mcp.
|
|
445
|
+
const mcpProxy = new McpProxy();
|
|
446
|
+
// Proxy the 'most interesting' tools from the mcpClient to the mcpProxy, so that they can be called from the
|
|
447
|
+
// MCP server (and thus from an LLM agent connected to the MCP server).
|
|
448
|
+
const toolsToProxys = await McpTargetHelper.toolsToProxy(mcpTarget);
|
|
449
|
+
for (const toolName of toolsToProxys) {
|
|
450
|
+
await mcpProxy.proxyToolCall(mcpClient, toolName);
|
|
451
|
+
}
|
|
452
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
453
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
454
|
+
// .querySelectorsAll tool implementation
|
|
455
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
456
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
457
|
+
const mcpServer = await mcpProxy.getMcpServer();
|
|
458
|
+
await MainHelper.initExternalTools(mcpServer, mcpClient);
|
|
459
|
+
// Connect the MCP proxy server to start accepting connections from MCP clients (e.g. LLM agents).
|
|
460
|
+
await mcpProxy.connect();
|
|
395
461
|
}
|
|
396
462
|
}
|
|
397
463
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -400,14 +466,31 @@ class MainHelper {
|
|
|
400
466
|
///////////////////////////////////////////////////////////////////////////////
|
|
401
467
|
///////////////////////////////////////////////////////////////////////////////
|
|
402
468
|
async function main() {
|
|
403
|
-
|
|
469
|
+
// Redirect process.stderr to a log file, so that the MCP communication is not polluted by logs.
|
|
470
|
+
const logFile = Path.resolve(import.meta.dirname, `../../outputs/fastbrowser_mcp_${new Date().toISOString()}.log`);
|
|
471
|
+
const logStream = Fs.createWriteStream(logFile, { flags: 'a' });
|
|
472
|
+
process.stderr.write = logStream.write.bind(logStream);
|
|
473
|
+
// throw Error("This entry point is not meant to be run directly. Please run one of the npm scripts defined in package.json, e.g. 'npm run start:fastbrowser_mcp' or 'npm run inspect:fastbrowser_mcp:chrome_devtools'");
|
|
474
|
+
const program = new Command();
|
|
404
475
|
program
|
|
405
476
|
.command('mcp_server')
|
|
406
477
|
.description('Start the MCP proxy server')
|
|
407
478
|
.option('-v, --verbose', 'Enable verbose logging')
|
|
408
|
-
.
|
|
409
|
-
|
|
479
|
+
.addOption(new Option('-b, --mcp_target <mcpTarget>', 'the MCP> of MCP to run')
|
|
480
|
+
.choices(['chrome_devtools', 'playwright'])
|
|
481
|
+
.default('playwright'))
|
|
482
|
+
.action(async (options) => {
|
|
483
|
+
await MainHelper.commandMcpServer({
|
|
484
|
+
mcpTarget: options.mcp_target,
|
|
485
|
+
verbose: options.verbose,
|
|
486
|
+
});
|
|
410
487
|
});
|
|
488
|
+
// display help if no command is provided
|
|
489
|
+
if (process.argv.length < 3) {
|
|
490
|
+
program.help();
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
// Parse the command-line arguments and execute the appropriate command action.
|
|
411
494
|
program.parse(process.argv);
|
|
412
495
|
}
|
|
413
496
|
///////////////////////////////////////////////////////////////////////////////
|