fastbrowser_cli 1.0.13 → 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.
Files changed (156) hide show
  1. package/.playwright-mcp/.gitignore +3 -0
  2. package/README.md +40 -1
  3. package/dist/fastbrowser_cli/fastbrowser_cli.js +52 -35
  4. package/dist/fastbrowser_cli/fastbrowser_cli.js.map +1 -1
  5. package/dist/fastbrowser_cli/libs/http-client.js +3 -7
  6. package/dist/fastbrowser_cli/libs/http-client.js.map +1 -1
  7. package/dist/fastbrowser_cli/libs/server-manager.d.ts.map +1 -1
  8. package/dist/fastbrowser_cli/libs/server-manager.js +30 -28
  9. package/dist/fastbrowser_cli/libs/server-manager.js.map +1 -1
  10. package/dist/fastbrowser_httpd/fastbrowser_httpd.js +22 -18
  11. package/dist/fastbrowser_httpd/fastbrowser_httpd.js.map +1 -1
  12. package/dist/fastbrowser_httpd/libs/routes.d.ts +2 -2
  13. package/dist/fastbrowser_httpd/libs/routes.d.ts.map +1 -1
  14. package/dist/fastbrowser_httpd/libs/routes.js +4 -8
  15. package/dist/fastbrowser_httpd/libs/routes.js.map +1 -1
  16. package/dist/fastbrowser_httpd/libs/tool-schemas.js +39 -42
  17. package/dist/fastbrowser_httpd/libs/tool-schemas.js.map +1 -1
  18. package/dist/fastbrowser_mcp/fastbrowser_mcp.d.ts.map +1 -1
  19. package/dist/fastbrowser_mcp/fastbrowser_mcp.js +270 -187
  20. package/dist/fastbrowser_mcp/fastbrowser_mcp.js.map +1 -1
  21. package/dist/fastbrowser_mcp/fastbrowser_types.d.ts +5 -0
  22. package/dist/fastbrowser_mcp/fastbrowser_types.d.ts.map +1 -0
  23. package/dist/fastbrowser_mcp/fastbrowser_types.js +2 -0
  24. package/dist/fastbrowser_mcp/fastbrowser_types.js.map +1 -0
  25. package/dist/{fastweb_mcp/libs/mcp_client.d.ts → fastbrowser_mcp/libs/mcp_client_TOREMOVE.d.ts} +6 -2
  26. package/dist/fastbrowser_mcp/libs/mcp_client_TOREMOVE.d.ts.map +1 -0
  27. package/dist/{src/libs/mcp_client.js → fastbrowser_mcp/libs/mcp_client_TOREMOVE.js} +15 -12
  28. package/dist/fastbrowser_mcp/libs/mcp_client_TOREMOVE.js.map +1 -0
  29. package/dist/{src/libs/mcp_client.d.ts → fastbrowser_mcp/libs/mcp_my_client.d.ts} +6 -2
  30. package/dist/fastbrowser_mcp/libs/mcp_my_client.d.ts.map +1 -0
  31. package/dist/{fastweb_mcp/libs/mcp_client.js → fastbrowser_mcp/libs/mcp_my_client.js} +15 -12
  32. package/dist/fastbrowser_mcp/libs/mcp_my_client.js.map +1 -0
  33. package/dist/fastbrowser_mcp/libs/mcp_proxy.d.ts +2 -2
  34. package/dist/fastbrowser_mcp/libs/mcp_proxy.d.ts.map +1 -1
  35. package/dist/fastbrowser_mcp/libs/mcp_proxy.js +9 -16
  36. package/dist/fastbrowser_mcp/libs/mcp_proxy.js.map +1 -1
  37. package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts +35 -0
  38. package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts.map +1 -0
  39. package/dist/fastbrowser_mcp/libs/mcp_target_helper.js +161 -0
  40. package/dist/fastbrowser_mcp/libs/mcp_target_helper.js.map +1 -0
  41. package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.d.ts +28 -0
  42. package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.d.ts.map +1 -0
  43. package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.js +210 -0
  44. package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.js.map +1 -0
  45. package/dist/fastbrowser_mcp/libs/response_formatter.d.ts +10 -0
  46. package/dist/fastbrowser_mcp/libs/response_formatter.d.ts.map +1 -0
  47. package/dist/fastbrowser_mcp/libs/response_formatter.js +155 -0
  48. package/dist/fastbrowser_mcp/libs/response_formatter.js.map +1 -0
  49. package/dist/fastbrowser_mcp/libs/schemas.js +14 -17
  50. package/dist/fastbrowser_mcp/libs/schemas.js.map +1 -1
  51. package/examples/mcp_client_playwright.ts +34 -0
  52. package/examples/welcometothejungle/wttj-job.ts +180 -0
  53. package/examples/welcometothejungle/wttj-search.ts +105 -0
  54. package/outputs/.gitignore +3 -0
  55. package/package.json +14 -9
  56. package/skills/fastbrowser/SKILL.md +2 -2
  57. package/src/fastbrowser_cli/fastbrowser_cli.ts +34 -11
  58. package/src/fastbrowser_cli/libs/server-manager.ts +12 -3
  59. package/src/fastbrowser_httpd/fastbrowser_httpd.ts +16 -5
  60. package/src/fastbrowser_httpd/libs/routes.ts +2 -2
  61. package/src/fastbrowser_mcp/fastbrowser_mcp.ts +324 -150
  62. package/src/fastbrowser_mcp/fastbrowser_types.ts +4 -0
  63. package/src/fastbrowser_mcp/libs/{mcp_client.ts → mcp_client_TOREMOVE.ts} +13 -1
  64. package/src/fastbrowser_mcp/libs/mcp_my_client.ts +128 -0
  65. package/src/fastbrowser_mcp/libs/mcp_proxy.ts +2 -2
  66. package/src/fastbrowser_mcp/libs/mcp_target_helper.ts +164 -0
  67. package/src/fastbrowser_mcp/libs/playwright_a11y_helper.ts +249 -0
  68. package/src/fastbrowser_mcp/libs/response_formatter.ts +162 -0
  69. package/src/fastbrowser_mcp/libs/schemas.ts +2 -2
  70. package/tsconfig.build.json +13 -0
  71. package/tsconfig.json +10 -22
  72. package/dist/contrib/fastweb-cli/fastweb-cli.d.ts +0 -3
  73. package/dist/contrib/fastweb-cli/fastweb-cli.d.ts.map +0 -1
  74. package/dist/contrib/fastweb-cli/fastweb-cli.js +0 -151
  75. package/dist/contrib/fastweb-cli/fastweb-cli.js.map +0 -1
  76. package/dist/contrib/fastweb-cli/http-client.d.ts +0 -7
  77. package/dist/contrib/fastweb-cli/http-client.d.ts.map +0 -1
  78. package/dist/contrib/fastweb-cli/http-client.js +0 -51
  79. package/dist/contrib/fastweb-cli/http-client.js.map +0 -1
  80. package/dist/contrib/fastweb-http-server/fastweb-http-server.d.ts +0 -3
  81. package/dist/contrib/fastweb-http-server/fastweb-http-server.d.ts.map +0 -1
  82. package/dist/contrib/fastweb-http-server/fastweb-http-server.js +0 -82
  83. package/dist/contrib/fastweb-http-server/fastweb-http-server.js.map +0 -1
  84. package/dist/contrib/fastweb-http-server/routes.d.ts +0 -6
  85. package/dist/contrib/fastweb-http-server/routes.d.ts.map +0 -1
  86. package/dist/contrib/fastweb-http-server/routes.js +0 -41
  87. package/dist/contrib/fastweb-http-server/routes.js.map +0 -1
  88. package/dist/contrib/fastweb-http-server/tool-schemas.d.ts +0 -63
  89. package/dist/contrib/fastweb-http-server/tool-schemas.d.ts.map +0 -1
  90. package/dist/contrib/fastweb-http-server/tool-schemas.js +0 -61
  91. package/dist/contrib/fastweb-http-server/tool-schemas.js.map +0 -1
  92. package/dist/fastbrowser_mcp/libs/mcp_client.d.ts +0 -120
  93. package/dist/fastbrowser_mcp/libs/mcp_client.d.ts.map +0 -1
  94. package/dist/fastbrowser_mcp/libs/mcp_client.js +0 -83
  95. package/dist/fastbrowser_mcp/libs/mcp_client.js.map +0 -1
  96. package/dist/fastweb_cli/fastweb_cli.d.ts +0 -3
  97. package/dist/fastweb_cli/fastweb_cli.d.ts.map +0 -1
  98. package/dist/fastweb_cli/fastweb_cli.js +0 -254
  99. package/dist/fastweb_cli/fastweb_cli.js.map +0 -1
  100. package/dist/fastweb_cli/http-client.d.ts +0 -7
  101. package/dist/fastweb_cli/http-client.d.ts.map +0 -1
  102. package/dist/fastweb_cli/http-client.js +0 -51
  103. package/dist/fastweb_cli/http-client.js.map +0 -1
  104. package/dist/fastweb_cli/libs/http-client.d.ts +0 -7
  105. package/dist/fastweb_cli/libs/http-client.d.ts.map +0 -1
  106. package/dist/fastweb_cli/libs/http-client.js +0 -51
  107. package/dist/fastweb_cli/libs/http-client.js.map +0 -1
  108. package/dist/fastweb_cli/libs/server-manager.d.ts +0 -12
  109. package/dist/fastweb_cli/libs/server-manager.d.ts.map +0 -1
  110. package/dist/fastweb_cli/libs/server-manager.js +0 -194
  111. package/dist/fastweb_cli/libs/server-manager.js.map +0 -1
  112. package/dist/fastweb_http_server/fastweb_http_server.d.ts +0 -3
  113. package/dist/fastweb_http_server/fastweb_http_server.d.ts.map +0 -1
  114. package/dist/fastweb_http_server/fastweb_http_server.js +0 -82
  115. package/dist/fastweb_http_server/fastweb_http_server.js.map +0 -1
  116. package/dist/fastweb_http_server/libs/routes.d.ts +0 -6
  117. package/dist/fastweb_http_server/libs/routes.d.ts.map +0 -1
  118. package/dist/fastweb_http_server/libs/routes.js +0 -41
  119. package/dist/fastweb_http_server/libs/routes.js.map +0 -1
  120. package/dist/fastweb_http_server/libs/tool-schemas.d.ts +0 -72
  121. package/dist/fastweb_http_server/libs/tool-schemas.d.ts.map +0 -1
  122. package/dist/fastweb_http_server/libs/tool-schemas.js +0 -65
  123. package/dist/fastweb_http_server/libs/tool-schemas.js.map +0 -1
  124. package/dist/fastweb_http_server/routes.d.ts +0 -6
  125. package/dist/fastweb_http_server/routes.d.ts.map +0 -1
  126. package/dist/fastweb_http_server/routes.js +0 -41
  127. package/dist/fastweb_http_server/routes.js.map +0 -1
  128. package/dist/fastweb_http_server/tool-schemas.d.ts +0 -63
  129. package/dist/fastweb_http_server/tool-schemas.d.ts.map +0 -1
  130. package/dist/fastweb_http_server/tool-schemas.js +0 -61
  131. package/dist/fastweb_http_server/tool-schemas.js.map +0 -1
  132. package/dist/fastweb_mcp/fastweb_mcp.d.ts +0 -4
  133. package/dist/fastweb_mcp/fastweb_mcp.d.ts.map +0 -1
  134. package/dist/fastweb_mcp/fastweb_mcp.js +0 -417
  135. package/dist/fastweb_mcp/fastweb_mcp.js.map +0 -1
  136. package/dist/fastweb_mcp/libs/mcp_client.d.ts.map +0 -1
  137. package/dist/fastweb_mcp/libs/mcp_client.js.map +0 -1
  138. package/dist/fastweb_mcp/libs/mcp_proxy.d.ts +0 -10
  139. package/dist/fastweb_mcp/libs/mcp_proxy.d.ts.map +0 -1
  140. package/dist/fastweb_mcp/libs/mcp_proxy.js +0 -45
  141. package/dist/fastweb_mcp/libs/mcp_proxy.js.map +0 -1
  142. package/dist/fastweb_mcp/libs/schemas.d.ts +0 -28
  143. package/dist/fastweb_mcp/libs/schemas.d.ts.map +0 -1
  144. package/dist/fastweb_mcp/libs/schemas.js +0 -38
  145. package/dist/fastweb_mcp/libs/schemas.js.map +0 -1
  146. package/dist/src/fastweb_mcp.d.ts +0 -17
  147. package/dist/src/fastweb_mcp.d.ts.map +0 -1
  148. package/dist/src/fastweb_mcp.js +0 -342
  149. package/dist/src/fastweb_mcp.js.map +0 -1
  150. package/dist/src/libs/mcp_client.d.ts.map +0 -1
  151. package/dist/src/libs/mcp_client.js.map +0 -1
  152. package/dist/src/libs/mcp_proxy.d.ts +0 -10
  153. package/dist/src/libs/mcp_proxy.d.ts.map +0 -1
  154. package/dist/src/libs/mcp_proxy.js +0 -45
  155. package/dist/src/libs/mcp_proxy.js.map +0 -1
  156. package/tmp/dotclaude/skills/fastbrowser/SKILL.md +0 -214
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Defines the types used in the FastBrowser MCP implementation.
3
+ */
4
+ export type FastBrowserMcpTarget = 'chrome_devtools' | 'playwright';
@@ -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
- export class McpClient {
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 { McpClient } from "./mcp_client.js";
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: McpClient, toolName: string) {
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
+ }