fastbrowser_cli 1.0.14 → 1.0.16
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 +30 -28
- 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 +12 -3
- 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
|
@@ -4,6 +4,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
4
4
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
5
|
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
6
6
|
import type { CallToolResult, Prompt, Resource, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { FastBrowserMcpTarget } from "../fastbrowser_types.js";
|
|
7
8
|
|
|
8
9
|
export type StdioConfig = {
|
|
9
10
|
type: "stdio";
|
|
@@ -23,6 +24,7 @@ export type McpTransportConfig = StdioConfig | HttpConfig;
|
|
|
23
24
|
export interface McpClientOptions {
|
|
24
25
|
name: string;
|
|
25
26
|
version: string;
|
|
27
|
+
mcpTarget: FastBrowserMcpTarget;
|
|
26
28
|
transport: McpTransportConfig;
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -32,16 +34,26 @@ export interface McpClientOptions {
|
|
|
32
34
|
///////////////////////////////////////////////////////////////////////////////
|
|
33
35
|
///////////////////////////////////////////////////////////////////////////////
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
// TODO remove this function - it seems to do vastly nothing - more confusing that helpful
|
|
38
|
+
// - i use the McpServer directly, and i got a wrapper for the client... discrepancy is confusing
|
|
39
|
+
export class McpMyClient {
|
|
36
40
|
private readonly client: Client;
|
|
37
41
|
private transport?: Transport;
|
|
42
|
+
private mcpTarget: FastBrowserMcpTarget;
|
|
38
43
|
private connected = false;
|
|
39
44
|
|
|
40
45
|
constructor(private readonly options: McpClientOptions) {
|
|
46
|
+
this.mcpTarget = options.mcpTarget;
|
|
41
47
|
this.client = new Client({
|
|
42
48
|
name: options.name,
|
|
43
49
|
version: options.version,
|
|
44
50
|
});
|
|
51
|
+
|
|
52
|
+
debugger
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async getMcpTarget(): Promise<FastBrowserMcpTarget> {
|
|
56
|
+
return this.mcpTarget;
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
async connect(): Promise<void> {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// node import
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
6
|
+
import type { CallToolResult, Prompt, Resource, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { FastBrowserMcpTarget } from "../fastbrowser_types.js";
|
|
8
|
+
|
|
9
|
+
export type StdioConfig = {
|
|
10
|
+
type: "stdio";
|
|
11
|
+
command: string;
|
|
12
|
+
args?: string[];
|
|
13
|
+
env?: Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type HttpConfig = {
|
|
17
|
+
type: "http";
|
|
18
|
+
url: string;
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type McpTransportConfig = StdioConfig | HttpConfig;
|
|
23
|
+
|
|
24
|
+
export interface McpClientOptions {
|
|
25
|
+
name: string;
|
|
26
|
+
version: string;
|
|
27
|
+
mcpTarget: FastBrowserMcpTarget;
|
|
28
|
+
transport: McpTransportConfig;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
32
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
33
|
+
//
|
|
34
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
35
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
36
|
+
|
|
37
|
+
// TODO remove this function - it seems to do vastly nothing - more confusing that helpful
|
|
38
|
+
// - i use the McpServer directly, and i got a wrapper for the client... discrepancy is confusing
|
|
39
|
+
export class McpMyClient {
|
|
40
|
+
private readonly client: Client;
|
|
41
|
+
private transport?: Transport;
|
|
42
|
+
private mcpTarget: FastBrowserMcpTarget;
|
|
43
|
+
private connected = false;
|
|
44
|
+
|
|
45
|
+
constructor(private readonly options: McpClientOptions) {
|
|
46
|
+
this.mcpTarget = options.mcpTarget;
|
|
47
|
+
this.client = new Client({
|
|
48
|
+
name: options.name,
|
|
49
|
+
version: options.version,
|
|
50
|
+
});
|
|
51
|
+
// debugger
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async getMcpTarget(): Promise<FastBrowserMcpTarget> {
|
|
55
|
+
return this.mcpTarget;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async connect(): Promise<void> {
|
|
59
|
+
if (this.connected) return;
|
|
60
|
+
|
|
61
|
+
this.transport = this.createTransport(this.options.transport);
|
|
62
|
+
await this.client.connect(this.transport);
|
|
63
|
+
this.connected = true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async close(): Promise<void> {
|
|
67
|
+
if (!this.connected) return;
|
|
68
|
+
await this.client.close();
|
|
69
|
+
this.connected = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async listTools(): Promise<Tool[]> {
|
|
73
|
+
this.assertConnected();
|
|
74
|
+
const { tools } = await this.client.listTools();
|
|
75
|
+
return tools;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async callTool(
|
|
79
|
+
name: string,
|
|
80
|
+
args: Record<string, unknown> = {},
|
|
81
|
+
): Promise<CallToolResult> {
|
|
82
|
+
this.assertConnected();
|
|
83
|
+
return this.client.callTool({ name, arguments: args }) as Promise<CallToolResult>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async listResources(): Promise<Resource[]> {
|
|
87
|
+
this.assertConnected();
|
|
88
|
+
const { resources } = await this.client.listResources();
|
|
89
|
+
return resources;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async readResource(uri: string): Promise<unknown> {
|
|
93
|
+
this.assertConnected();
|
|
94
|
+
return this.client.readResource({ uri });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async listPrompts(): Promise<Prompt[]> {
|
|
98
|
+
this.assertConnected();
|
|
99
|
+
const { prompts } = await this.client.listPrompts();
|
|
100
|
+
return prompts;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getPrompt(name: string, args: Record<string, string> = {}) {
|
|
104
|
+
this.assertConnected();
|
|
105
|
+
return this.client.getPrompt({ name, arguments: args });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private createTransport(config: McpTransportConfig): Transport {
|
|
109
|
+
switch (config.type) {
|
|
110
|
+
case "stdio":
|
|
111
|
+
return new StdioClientTransport({
|
|
112
|
+
command: config.command,
|
|
113
|
+
args: config.args ?? [],
|
|
114
|
+
env: config.env,
|
|
115
|
+
});
|
|
116
|
+
case "http":
|
|
117
|
+
return new StreamableHTTPClientTransport(new URL(config.url), {
|
|
118
|
+
requestInit: { headers: config.headers },
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private assertConnected(): void {
|
|
124
|
+
if (!this.connected) {
|
|
125
|
+
throw new Error("McpClient is not connected. Call connect() first.");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -7,7 +7,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7
7
|
import { convertJsonSchemaToZod } from 'zod-from-json-schema';
|
|
8
8
|
|
|
9
9
|
// local imports
|
|
10
|
-
import {
|
|
10
|
+
import { McpMyClient } from "./mcp_my_client.js";
|
|
11
11
|
|
|
12
12
|
export class McpProxy {
|
|
13
13
|
private _mcpServer: McpServer;
|
|
@@ -31,7 +31,7 @@ export class McpProxy {
|
|
|
31
31
|
return this._mcpServer;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
async proxyToolCall(mcpClient:
|
|
34
|
+
async proxyToolCall(mcpClient: McpMyClient, toolName: string) {
|
|
35
35
|
const mcpClientTools = await mcpClient.listTools()
|
|
36
36
|
const mcpClientTool = mcpClientTools.find((tool) => tool.name === toolName);
|
|
37
37
|
Assert.ok(mcpClientTool !== undefined, `Tool ${toolName} not found in mcp client tools`)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { ta } from "zod/locales";
|
|
2
|
+
import { FastBrowserMcpTarget } from "../fastbrowser_types.js";
|
|
3
|
+
|
|
4
|
+
export type TargetToolConfig = {
|
|
5
|
+
toolName: string;
|
|
6
|
+
toolArgs: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class McpTargetHelper {
|
|
10
|
+
static EXTERNAL_TOOL_NAME = {
|
|
11
|
+
listPages: "list_pages",
|
|
12
|
+
newPage: "new_page",
|
|
13
|
+
closePage: "close_page",
|
|
14
|
+
takeSnapshot: "take_snapshot",
|
|
15
|
+
navigatePage: "navigate_page",
|
|
16
|
+
querySelectorsAll: "querySelectorsAll",
|
|
17
|
+
querySelectors: "querySelectors",
|
|
18
|
+
pressKeys: "pressKeys",
|
|
19
|
+
click: "click",
|
|
20
|
+
fillForm: "fill_form",
|
|
21
|
+
getCurrentDateTime: "get_current_datetime",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
25
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
26
|
+
// For configuration
|
|
27
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
28
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
29
|
+
|
|
30
|
+
static mcpArgs(mcpTarget: FastBrowserMcpTarget): { command: string; args: string[] } {
|
|
31
|
+
let mcpCommand: string;
|
|
32
|
+
let mcpArgs: string[];
|
|
33
|
+
if (mcpTarget === 'chrome_devtools') {
|
|
34
|
+
mcpCommand = "npx";
|
|
35
|
+
mcpArgs = ["chrome-devtools-mcp@latest", "--autoconnect", "--no-performance-crux", "--no-usage-statistics"];
|
|
36
|
+
} else if (mcpTarget === 'playwright') {
|
|
37
|
+
// npx @playwright/mcp@latest --cdp-endpoint=chrome
|
|
38
|
+
// npx @playwright/mcp@latest --cdp-endpoint=http://localhost:9222
|
|
39
|
+
// npx @playwright/mcp@latest --extension
|
|
40
|
+
//
|
|
41
|
+
// https://playwright.dev/mcp/configuration/browser-extension#connect-via-browser-extension
|
|
42
|
+
// https://github.com/microsoft/playwright/tree/main/packages/extension
|
|
43
|
+
mcpCommand = "npx";
|
|
44
|
+
mcpArgs = ["@playwright/mcp", "--extension"];
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error(`Unsupported MCP type: ${mcpTarget}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { command: mcpCommand, args: mcpArgs };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static async toolsToProxy(mcpTarget: FastBrowserMcpTarget): Promise<string[]> {
|
|
53
|
+
const toolsToProxys: string[] = []
|
|
54
|
+
if (mcpTarget === 'chrome_devtools') {
|
|
55
|
+
toolsToProxys.push(...[
|
|
56
|
+
// 'list_pages',
|
|
57
|
+
// 'new_page',
|
|
58
|
+
// 'close_page',
|
|
59
|
+
// 'navigate_page',
|
|
60
|
+
// 'take_snapshot',
|
|
61
|
+
])
|
|
62
|
+
} else if (mcpTarget === 'playwright') {
|
|
63
|
+
toolsToProxys.push(...[
|
|
64
|
+
// 'browser_tabs',
|
|
65
|
+
// 'browser_navigate',
|
|
66
|
+
// 'browser_snapshot',
|
|
67
|
+
])
|
|
68
|
+
} else {
|
|
69
|
+
throw new Error(`Unsupported MCP type: ${mcpTarget}`);
|
|
70
|
+
}
|
|
71
|
+
return toolsToProxys;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
75
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
76
|
+
// For each target tool
|
|
77
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
78
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
static async targetToolListPages(mcpTarget: FastBrowserMcpTarget): Promise<TargetToolConfig> {
|
|
82
|
+
if (mcpTarget === 'chrome_devtools') {
|
|
83
|
+
return {
|
|
84
|
+
toolName: 'list_pages', toolArgs: {
|
|
85
|
+
// No arguments needed for listing pages in Chrome DevTools MCP
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
} else if (mcpTarget === 'playwright') {
|
|
89
|
+
return {
|
|
90
|
+
toolName: 'browser_tabs', toolArgs: {
|
|
91
|
+
action: 'list',
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
} else {
|
|
95
|
+
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static async targetToolNavigatePage(mcpTarget: FastBrowserMcpTarget, url: string): Promise<TargetToolConfig> {
|
|
100
|
+
if (mcpTarget === 'chrome_devtools') {
|
|
101
|
+
return {
|
|
102
|
+
toolName: 'navigate_page', toolArgs: {
|
|
103
|
+
url
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
} else if (mcpTarget === 'playwright') {
|
|
107
|
+
return {
|
|
108
|
+
toolName: 'browser_navigate', toolArgs: {
|
|
109
|
+
url,
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
} else {
|
|
113
|
+
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static async targetToolTakeSnapshot(mcpTarget: FastBrowserMcpTarget): Promise<TargetToolConfig> {
|
|
118
|
+
if (mcpTarget === 'chrome_devtools') {
|
|
119
|
+
return { toolName: 'take_snapshot', toolArgs: {} };
|
|
120
|
+
} else if (mcpTarget === 'playwright') {
|
|
121
|
+
return { toolName: 'browser_snapshot', toolArgs: {} };
|
|
122
|
+
} else {
|
|
123
|
+
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static async targetToolPressKey(mcpTarget: FastBrowserMcpTarget, key: string): Promise<TargetToolConfig> {
|
|
128
|
+
if (mcpTarget === 'chrome_devtools') {
|
|
129
|
+
return {
|
|
130
|
+
toolName: 'press_key', toolArgs: {
|
|
131
|
+
key: key,
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
} else if (mcpTarget === 'playwright') {
|
|
135
|
+
return {
|
|
136
|
+
toolName: 'browser_press_key', toolArgs: {
|
|
137
|
+
key: key,
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
} else {
|
|
141
|
+
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static async targetToolClick(mcpTarget: FastBrowserMcpTarget, uid: string): Promise<TargetToolConfig> {
|
|
146
|
+
if (mcpTarget === 'chrome_devtools') {
|
|
147
|
+
return { toolName: 'click', toolArgs: { uid } };
|
|
148
|
+
} else if (mcpTarget === 'playwright') {
|
|
149
|
+
return { toolName: 'browser_click', toolArgs: { ref: uid } };
|
|
150
|
+
} else {
|
|
151
|
+
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static async targetToolFillForm(mcpTarget: FastBrowserMcpTarget, elements: { uid: string; value: string }[]): Promise<TargetToolConfig> {
|
|
156
|
+
if (mcpTarget === 'chrome_devtools') {
|
|
157
|
+
return { toolName: 'fill_form', toolArgs: { elements } };
|
|
158
|
+
} else if (mcpTarget === 'playwright') {
|
|
159
|
+
throw new Error(`Fill form tool is not supported for Playwright MCP target yet`);
|
|
160
|
+
} else {
|
|
161
|
+
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3
|
+
// Convert Playwright's get-text-snapshot format to chrome-devtools-mcp's
|
|
4
|
+
// take_snapshot body format, so that the rest of the pipeline (A11yParse +
|
|
5
|
+
// selector engine) can be reused unchanged.
|
|
6
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8
|
+
|
|
9
|
+
type Bracket =
|
|
10
|
+
| { kind: 'attr'; key: string; value: string }
|
|
11
|
+
| { kind: 'flag'; name: string };
|
|
12
|
+
|
|
13
|
+
type ParsedLine =
|
|
14
|
+
| { kind: 'parent_attr'; key: string; value: string }
|
|
15
|
+
| { kind: 'static_text'; text: string }
|
|
16
|
+
| {
|
|
17
|
+
kind: 'node';
|
|
18
|
+
role: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
uid?: string;
|
|
21
|
+
attrs: Array<[string, string]>;
|
|
22
|
+
flags: string[];
|
|
23
|
+
value?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type EmittedNode = {
|
|
27
|
+
indent: number;
|
|
28
|
+
uid: string;
|
|
29
|
+
role: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
attrs: Array<[string, string]>;
|
|
32
|
+
flags: string[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export class PlaywrightA11yConverter {
|
|
36
|
+
/**
|
|
37
|
+
* Convert Playwright's `get-text-snapshot` output to the body of chrome-devtools-mcp's `take_snapshot`.
|
|
38
|
+
*
|
|
39
|
+
* Playwright emits a YAML-like tree with `[ref=eN]` markers, e.g.
|
|
40
|
+
* ```
|
|
41
|
+
* - link "Welcome" [ref=e7] [cursor=pointer]:
|
|
42
|
+
* - /url: /fr
|
|
43
|
+
* - img "Welcome" [ref=e8]
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* chrome-devtools-mcp emits one node per line with `uid=...`, e.g.
|
|
47
|
+
* ```
|
|
48
|
+
* uid=e7 link "Welcome" url="/fr"
|
|
49
|
+
* uid=e8 image "Welcome"
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* The output omits the `## Latest page snapshot` metadata line, so it can be passed directly
|
|
53
|
+
* to `A11yParse.A11yTree.parse()`.
|
|
54
|
+
*/
|
|
55
|
+
static convertToChromeDevtools(playwrightText: string): string {
|
|
56
|
+
const lines = playwrightText.split('\n');
|
|
57
|
+
const stack: EmittedNode[] = [];
|
|
58
|
+
const allNodes: EmittedNode[] = [];
|
|
59
|
+
let synthCounter = 0;
|
|
60
|
+
const synth = () => `s${++synthCounter}`;
|
|
61
|
+
|
|
62
|
+
for (const rawLine of lines) {
|
|
63
|
+
if (rawLine.trim().length === 0) continue;
|
|
64
|
+
|
|
65
|
+
const indentMatch = /^( *)/.exec(rawLine);
|
|
66
|
+
const leadingSpaces = indentMatch !== null ? indentMatch[1].length : 0;
|
|
67
|
+
const indent = Math.floor(leadingSpaces / 2);
|
|
68
|
+
|
|
69
|
+
const trimmed = rawLine.slice(leadingSpaces);
|
|
70
|
+
if (!trimmed.startsWith('- ')) continue;
|
|
71
|
+
const body = trimmed.slice(2);
|
|
72
|
+
|
|
73
|
+
while (stack.length > 0 && stack[stack.length - 1].indent >= indent) {
|
|
74
|
+
stack.pop();
|
|
75
|
+
}
|
|
76
|
+
const parent = stack[stack.length - 1] as EmittedNode | undefined;
|
|
77
|
+
|
|
78
|
+
const parsed = PlaywrightA11yConverter._parseLine(body);
|
|
79
|
+
|
|
80
|
+
if (parsed.kind === 'parent_attr') {
|
|
81
|
+
if (parent !== undefined) {
|
|
82
|
+
parent.attrs.push([parsed.key, parsed.value]);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (parsed.kind === 'static_text') {
|
|
88
|
+
const node: EmittedNode = {
|
|
89
|
+
indent,
|
|
90
|
+
uid: synth(),
|
|
91
|
+
role: 'StaticText',
|
|
92
|
+
name: parsed.text,
|
|
93
|
+
attrs: [],
|
|
94
|
+
flags: [],
|
|
95
|
+
};
|
|
96
|
+
allNodes.push(node);
|
|
97
|
+
stack.push(node);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const uid = parsed.uid !== undefined ? parsed.uid : synth();
|
|
102
|
+
const attrs: Array<[string, string]> = [...parsed.attrs];
|
|
103
|
+
if (parsed.value !== undefined) {
|
|
104
|
+
attrs.push(['value', parsed.value]);
|
|
105
|
+
}
|
|
106
|
+
const node: EmittedNode = {
|
|
107
|
+
indent,
|
|
108
|
+
uid,
|
|
109
|
+
role: parsed.role,
|
|
110
|
+
name: parsed.name,
|
|
111
|
+
attrs,
|
|
112
|
+
flags: parsed.flags,
|
|
113
|
+
};
|
|
114
|
+
allNodes.push(node);
|
|
115
|
+
stack.push(node);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return allNodes.map((node) => PlaywrightA11yConverter._stringifyNode(node)).join('\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
122
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
123
|
+
// Internal helpers
|
|
124
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
125
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
126
|
+
|
|
127
|
+
private static _parseLine(body: string): ParsedLine {
|
|
128
|
+
if (body.startsWith('/url:')) {
|
|
129
|
+
return { kind: 'parent_attr', key: 'url', value: body.slice('/url:'.length).trim() };
|
|
130
|
+
}
|
|
131
|
+
if (body.startsWith('/placeholder:')) {
|
|
132
|
+
return { kind: 'parent_attr', key: 'placeholder', value: body.slice('/placeholder:'.length).trim() };
|
|
133
|
+
}
|
|
134
|
+
if (body.startsWith('text:')) {
|
|
135
|
+
const text = PlaywrightA11yConverter._unquoteIfQuoted(body.slice('text:'.length).trim());
|
|
136
|
+
return { kind: 'static_text', text };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let i = 0;
|
|
140
|
+
const roleMatch = /^(\w+)/.exec(body);
|
|
141
|
+
if (roleMatch === null) {
|
|
142
|
+
return { kind: 'node', role: 'unknown', attrs: [], flags: [] };
|
|
143
|
+
}
|
|
144
|
+
const role = roleMatch[1];
|
|
145
|
+
i = role.length;
|
|
146
|
+
|
|
147
|
+
const skipSpaces = (): void => {
|
|
148
|
+
while (i < body.length && body[i] === ' ') i++;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
skipSpaces();
|
|
152
|
+
|
|
153
|
+
let name: string | undefined;
|
|
154
|
+
if (body[i] === '"') {
|
|
155
|
+
const parsed = PlaywrightA11yConverter._readQuotedString(body, i);
|
|
156
|
+
name = parsed.value;
|
|
157
|
+
i = parsed.next;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const brackets: Bracket[] = [];
|
|
161
|
+
skipSpaces();
|
|
162
|
+
while (body[i] === '[') {
|
|
163
|
+
const closeIdx = body.indexOf(']', i);
|
|
164
|
+
if (closeIdx === -1) break;
|
|
165
|
+
const content = body.slice(i + 1, closeIdx);
|
|
166
|
+
const eqIdx = content.indexOf('=');
|
|
167
|
+
if (eqIdx >= 0) {
|
|
168
|
+
brackets.push({
|
|
169
|
+
kind: 'attr',
|
|
170
|
+
key: content.slice(0, eqIdx),
|
|
171
|
+
value: content.slice(eqIdx + 1),
|
|
172
|
+
});
|
|
173
|
+
} else {
|
|
174
|
+
brackets.push({ kind: 'flag', name: content });
|
|
175
|
+
}
|
|
176
|
+
i = closeIdx + 1;
|
|
177
|
+
skipSpaces();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let value: string | undefined;
|
|
181
|
+
if (body[i] === ':') {
|
|
182
|
+
i++;
|
|
183
|
+
const rest = body.slice(i).trim();
|
|
184
|
+
if (rest.length > 0) {
|
|
185
|
+
value = PlaywrightA11yConverter._unquoteIfQuoted(rest);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let uid: string | undefined;
|
|
190
|
+
const attrs: Array<[string, string]> = [];
|
|
191
|
+
const flags: string[] = [];
|
|
192
|
+
for (const b of brackets) {
|
|
193
|
+
if (b.kind === 'attr') {
|
|
194
|
+
if (b.key === 'ref') {
|
|
195
|
+
uid = b.value;
|
|
196
|
+
} else if (b.key === 'cursor') {
|
|
197
|
+
// CSS cursor — not a meaningful a11y attribute
|
|
198
|
+
} else {
|
|
199
|
+
attrs.push([b.key, b.value]);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
flags.push(b.name);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { kind: 'node', role, name, uid, attrs, flags, value };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private static _readQuotedString(input: string, start: number): { value: string; next: number } {
|
|
210
|
+
let i = start + 1;
|
|
211
|
+
let result = '';
|
|
212
|
+
while (i < input.length) {
|
|
213
|
+
const ch = input[i];
|
|
214
|
+
if (ch === '\\' && i + 1 < input.length) {
|
|
215
|
+
result += input[i + 1];
|
|
216
|
+
i += 2;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (ch === '"') {
|
|
220
|
+
return { value: result, next: i + 1 };
|
|
221
|
+
}
|
|
222
|
+
result += ch;
|
|
223
|
+
i++;
|
|
224
|
+
}
|
|
225
|
+
return { value: result, next: i };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private static _unquoteIfQuoted(s: string): string {
|
|
229
|
+
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
|
|
230
|
+
const inner = s.slice(1, -1);
|
|
231
|
+
return inner.replace(/\\(.)/g, '$1');
|
|
232
|
+
}
|
|
233
|
+
return s;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private static _stringifyNode(node: EmittedNode): string {
|
|
237
|
+
const pad = ' '.repeat(node.indent);
|
|
238
|
+
const name = node.name !== undefined ? ` "${PlaywrightA11yConverter._escapeQuotes(node.name)}"` : '';
|
|
239
|
+
const parts: string[] = [];
|
|
240
|
+
for (const flag of node.flags) parts.push(flag);
|
|
241
|
+
for (const [k, v] of node.attrs) parts.push(`${k}="${PlaywrightA11yConverter._escapeQuotes(v)}"`);
|
|
242
|
+
const tail = parts.length > 0 ? ' ' + parts.join(' ') : '';
|
|
243
|
+
return `${pad}uid=${node.uid} ${node.role}${name}${tail}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private static _escapeQuotes(s: string): string {
|
|
247
|
+
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
248
|
+
}
|
|
249
|
+
}
|