@playwright/mcp 0.0.36 → 0.0.37-alpha-2025-09-09

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 (61) hide show
  1. package/README.md +82 -47
  2. package/cli.js +7 -1
  3. package/config.d.ts +24 -0
  4. package/index.d.ts +1 -1
  5. package/index.js +2 -2
  6. package/package.json +14 -40
  7. package/lib/browserContextFactory.js +0 -211
  8. package/lib/browserServerBackend.js +0 -77
  9. package/lib/config.js +0 -246
  10. package/lib/context.js +0 -226
  11. package/lib/extension/cdpRelay.js +0 -358
  12. package/lib/extension/extensionContextFactory.js +0 -56
  13. package/lib/extension/protocol.js +0 -18
  14. package/lib/index.js +0 -40
  15. package/lib/loop/loop.js +0 -69
  16. package/lib/loop/loopClaude.js +0 -152
  17. package/lib/loop/loopOpenAI.js +0 -141
  18. package/lib/loop/main.js +0 -60
  19. package/lib/loopTools/context.js +0 -67
  20. package/lib/loopTools/main.js +0 -54
  21. package/lib/loopTools/perform.js +0 -32
  22. package/lib/loopTools/snapshot.js +0 -29
  23. package/lib/loopTools/tool.js +0 -18
  24. package/lib/mcp/http.js +0 -135
  25. package/lib/mcp/inProcessTransport.js +0 -72
  26. package/lib/mcp/manualPromise.js +0 -111
  27. package/lib/mcp/mdb.js +0 -198
  28. package/lib/mcp/proxyBackend.js +0 -104
  29. package/lib/mcp/server.js +0 -123
  30. package/lib/mcp/tool.js +0 -32
  31. package/lib/program.js +0 -132
  32. package/lib/response.js +0 -165
  33. package/lib/sessionLog.js +0 -121
  34. package/lib/tab.js +0 -249
  35. package/lib/tools/common.js +0 -55
  36. package/lib/tools/console.js +0 -33
  37. package/lib/tools/dialogs.js +0 -47
  38. package/lib/tools/evaluate.js +0 -53
  39. package/lib/tools/files.js +0 -44
  40. package/lib/tools/form.js +0 -57
  41. package/lib/tools/install.js +0 -53
  42. package/lib/tools/keyboard.js +0 -78
  43. package/lib/tools/mouse.js +0 -99
  44. package/lib/tools/navigate.js +0 -54
  45. package/lib/tools/network.js +0 -41
  46. package/lib/tools/pdf.js +0 -40
  47. package/lib/tools/screenshot.js +0 -79
  48. package/lib/tools/snapshot.js +0 -139
  49. package/lib/tools/tabs.js +0 -59
  50. package/lib/tools/tool.js +0 -33
  51. package/lib/tools/utils.js +0 -74
  52. package/lib/tools/verify.js +0 -137
  53. package/lib/tools/wait.js +0 -55
  54. package/lib/tools.js +0 -54
  55. package/lib/utils/codegen.js +0 -49
  56. package/lib/utils/fileUtils.js +0 -36
  57. package/lib/utils/guid.js +0 -22
  58. package/lib/utils/log.js +0 -21
  59. package/lib/utils/package.js +0 -20
  60. package/lib/vscode/host.js +0 -128
  61. package/lib/vscode/main.js +0 -62
package/README.md CHANGED
@@ -76,7 +76,7 @@ For more information, see the [Codex MCP documentation](https://github.com/opena
76
76
 
77
77
  #### Click the button to install:
78
78
 
79
- [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=Playwright&config=eyJjb21tYW5kIjoibnB4IEBwbGF5d3JpZ2h0L21jcEBsYXRlc3QifQ%3D%3D)
79
+ [<img src="https://cursor.com/deeplink/mcp-install-dark.svg" alt="Install in Cursor">](cursor://anysphere.cursor-deeplink/mcp/install?name=Playwright&config=eyJjb21tYW5kIjoibnB4IEBwbGF5d3JpZ2h0L21jcEBsYXRlc3QifQ%3D%3D)
80
80
 
81
81
  #### Or install manually:
82
82
 
@@ -180,52 +180,65 @@ Playwright MCP server supports following arguments. They can be provided in the
180
180
 
181
181
  ```
182
182
  > npx @playwright/mcp@latest --help
183
- --allowed-origins <origins> semicolon-separated list of origins to allow the
184
- browser to request. Default is to allow all.
185
- --blocked-origins <origins> semicolon-separated list of origins to block the
186
- browser from requesting. Blocklist is evaluated
187
- before allowlist. If used without the allowlist,
188
- requests not matching the blocklist are still
189
- allowed.
190
- --block-service-workers block service workers
191
- --browser <browser> browser or chrome channel to use, possible
192
- values: chrome, firefox, webkit, msedge.
193
- --caps <caps> comma-separated list of additional capabilities
194
- to enable, possible values: vision, pdf.
195
- --cdp-endpoint <endpoint> CDP endpoint to connect to.
196
- --config <path> path to the configuration file.
197
- --device <device> device to emulate, for example: "iPhone 15"
198
- --executable-path <path> path to the browser executable.
199
- --extension Connect to a running browser instance
200
- (Edge/Chrome only). Requires the "Playwright MCP
201
- Bridge" browser extension to be installed.
202
- --headless run browser in headless mode, headed by default
203
- --host <host> host to bind server to. Default is localhost. Use
204
- 0.0.0.0 to bind to all interfaces.
205
- --ignore-https-errors ignore https errors
206
- --isolated keep the browser profile in memory, do not save
207
- it to disk.
208
- --image-responses <mode> whether to send image responses to the client.
209
- Can be "allow" or "omit", Defaults to "allow".
210
- --no-sandbox disable the sandbox for all process types that
211
- are normally sandboxed.
212
- --output-dir <path> path to the directory for output files.
213
- --port <port> port to listen on for SSE transport.
214
- --proxy-bypass <bypass> comma-separated domains to bypass proxy, for
215
- example ".com,chromium.org,.domain.com"
216
- --proxy-server <proxy> specify proxy server, for example
217
- "http://myproxy:3128" or "socks5://myproxy:8080"
218
- --save-session Whether to save the Playwright MCP session into
219
- the output directory.
220
- --save-trace Whether to save the Playwright Trace of the
221
- session into the output directory.
222
- --storage-state <path> path to the storage state file for isolated
223
- sessions.
224
- --user-agent <ua string> specify user agent string
225
- --user-data-dir <path> path to the user data directory. If not
226
- specified, a temporary directory will be created.
227
- --viewport-size <size> specify browser viewport size in pixels, for
228
- example "1280, 720"
183
+ --allowed-origins <origins> semicolon-separated list of origins to allow
184
+ the browser to request. Default is to allow
185
+ all.
186
+ --blocked-origins <origins> semicolon-separated list of origins to block
187
+ the browser from requesting. Blocklist is
188
+ evaluated before allowlist. If used without
189
+ the allowlist, requests not matching the
190
+ blocklist are still allowed.
191
+ --block-service-workers block service workers
192
+ --browser <browser> browser or chrome channel to use, possible
193
+ values: chrome, firefox, webkit, msedge.
194
+ --caps <caps> comma-separated list of additional
195
+ capabilities to enable, possible values:
196
+ vision, pdf.
197
+ --cdp-endpoint <endpoint> CDP endpoint to connect to.
198
+ --cdp-header <headers...> CDP headers to send with the connect request,
199
+ multiple can be specified.
200
+ --config <path> path to the configuration file.
201
+ --device <device> device to emulate, for example: "iPhone 15"
202
+ --executable-path <path> path to the browser executable.
203
+ --extension Connect to a running browser instance
204
+ (Edge/Chrome only). Requires the "Playwright
205
+ MCP Bridge" browser extension to be installed.
206
+ --headless run browser in headless mode, headed by
207
+ default
208
+ --host <host> host to bind server to. Default is localhost.
209
+ Use 0.0.0.0 to bind to all interfaces.
210
+ --ignore-https-errors ignore https errors
211
+ --isolated keep the browser profile in memory, do not
212
+ save it to disk.
213
+ --image-responses <mode> whether to send image responses to the client.
214
+ Can be "allow" or "omit", Defaults to "allow".
215
+ --no-sandbox disable the sandbox for all process types that
216
+ are normally sandboxed.
217
+ --output-dir <path> path to the directory for output files.
218
+ --port <port> port to listen on for SSE transport.
219
+ --proxy-bypass <bypass> comma-separated domains to bypass proxy, for
220
+ example ".com,chromium.org,.domain.com"
221
+ --proxy-server <proxy> specify proxy server, for example
222
+ "http://myproxy:3128" or
223
+ "socks5://myproxy:8080"
224
+ --save-session Whether to save the Playwright MCP session
225
+ into the output directory.
226
+ --save-trace Whether to save the Playwright Trace of the
227
+ session into the output directory.
228
+ --secrets <path> path to a file containing secrets in the
229
+ dotenv format
230
+ --storage-state <path> path to the storage state file for isolated
231
+ sessions.
232
+ --timeout-action <timeout> specify action timeout in milliseconds,
233
+ defaults to 5000ms
234
+ --timeout-navigation <timeout> specify navigation timeout in milliseconds,
235
+ defaults to 60000ms
236
+ --user-agent <ua string> specify user agent string
237
+ --user-data-dir <path> path to the user data directory. If not
238
+ specified, a temporary directory will be
239
+ created.
240
+ --viewport-size <size> specify browser viewport size in pixels, for
241
+ example "1280, 720"
229
242
  ```
230
243
 
231
244
  <!--- End of options generated section -->
@@ -442,6 +455,7 @@ http.createServer(async (req, res) => {
442
455
  - `ref` (string): Exact target element reference from the page snapshot
443
456
  - `doubleClick` (boolean, optional): Whether to perform a double click instead of a single click
444
457
  - `button` (string, optional): Button to click, defaults to left
458
+ - `modifiers` (array, optional): Modifier keys to press
445
459
  - Read-only: **false**
446
460
 
447
461
  <!-- NOTE: This has been generated via update-readme.js -->
@@ -752,5 +766,26 @@ http.createServer(async (req, res) => {
752
766
 
753
767
  </details>
754
768
 
769
+ <details>
770
+ <summary><b>Tracing (opt-in via --caps=tracing)</b></summary>
771
+
772
+ <!-- NOTE: This has been generated via update-readme.js -->
773
+
774
+ - **browser_start_tracing**
775
+ - Title: Start tracing
776
+ - Description: Start trace recording
777
+ - Parameters: None
778
+ - Read-only: **true**
779
+
780
+ <!-- NOTE: This has been generated via update-readme.js -->
781
+
782
+ - **browser_stop_tracing**
783
+ - Title: Stop tracing
784
+ - Description: Stop trace recording
785
+ - Parameters: None
786
+ - Read-only: **true**
787
+
788
+ </details>
789
+
755
790
 
756
791
  <!--- End of tools generated section -->
package/cli.js CHANGED
@@ -15,4 +15,10 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import './lib/program.js';
18
+ const { program } = require('playwright-core/lib/utilsBundle');
19
+ const { decorateCommand } = require('playwright/lib/mcp/program');
20
+
21
+ const packageJSON = require('./package.json');
22
+ const p = program.version('Version ' + packageJSON.version).name('Playwright MCP');
23
+ decorateCommand(p, packageJSON.version)
24
+ void program.parseAsync(process.argv);
package/config.d.ts CHANGED
@@ -59,6 +59,11 @@ export type Config = {
59
59
  */
60
60
  cdpEndpoint?: string;
61
61
 
62
+ /**
63
+ * CDP headers to send with the connect request.
64
+ */
65
+ cdpHeaders?: Record<string, string>;
66
+
62
67
  /**
63
68
  * Remote endpoint to connect to an existing Playwright server.
64
69
  */
@@ -95,6 +100,13 @@ export type Config = {
95
100
  */
96
101
  saveTrace?: boolean;
97
102
 
103
+ /**
104
+ * Secrets are used to prevent LLM from getting sensitive data while
105
+ * automating scenarios such as authentication.
106
+ * Prefer the browser.contextOptions.storageState over secrets file as a more secure alternative.
107
+ */
108
+ secrets?: Record<string, string>;
109
+
98
110
  /**
99
111
  * The directory to save output files.
100
112
  */
@@ -112,6 +124,18 @@ export type Config = {
112
124
  blockedOrigins?: string[];
113
125
  };
114
126
 
127
+ timeouts?: {
128
+ /*
129
+ * Configures default action timeout: https://playwright.dev/docs/api/class-page#page-set-default-timeout. Defaults to 5000ms.
130
+ */
131
+ action?: number;
132
+
133
+ /*
134
+ * Configures default navigation timeout: https://playwright.dev/docs/api/class-page#page-set-default-navigation-timeout. Defaults to 60000ms.
135
+ */
136
+ navigation?: number;
137
+ };
138
+
115
139
  /**
116
140
  * Whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them.
117
141
  */
package/index.d.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
19
- import type { Config } from './config.js';
19
+ import type { Config } from './config';
20
20
  import type { BrowserContext } from 'playwright';
21
21
 
22
22
  export declare function createConnection(config?: Config, contextGetter?: () => Promise<BrowserContext>): Promise<Server>;
package/index.js CHANGED
@@ -15,5 +15,5 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import { createConnection } from './lib/index.js';
19
- export { createConnection };
18
+ const { createConnection } = require('playwright/lib/mcp/index');
19
+ module.exports = { createConnection };
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "@playwright/mcp",
3
- "version": "0.0.36",
3
+ "version": "0.0.37-alpha-2025-09-09",
4
4
  "description": "Playwright Tools for MCP",
5
- "type": "module",
6
5
  "repository": {
7
6
  "type": "git",
8
7
  "url": "git+https://github.com/microsoft/playwright-mcp.git"
@@ -16,19 +15,15 @@
16
15
  },
17
16
  "license": "Apache-2.0",
18
17
  "scripts": {
19
- "build": "tsc",
20
- "lint": "npm run update-readme && npm run check-deps && eslint . && tsc --noEmit",
21
- "lint-fix": "eslint . --fix",
22
- "check-deps": "node utils/check-deps.js",
23
- "update-readme": "node utils/update-readme.js",
24
- "watch": "tsc --watch",
18
+ "lint": "npm run update-readme",
19
+ "update-readme": "node update-readme.js",
20
+ "docker-build": "docker build --no-cache -t playwright-mcp-dev:latest .",
25
21
  "test": "playwright test",
26
22
  "ctest": "playwright test --project=chrome",
27
23
  "ftest": "playwright test --project=firefox",
28
24
  "wtest": "playwright test --project=webkit",
29
- "run-server": "node lib/browserServer.js",
30
- "clean": "rm -rf lib",
31
- "npm-publish": "npm run clean && npm run build && npm run test && npm publish"
25
+ "dtest": "MCP_IN_DOCKER=1 playwright test --project=chromium-docker",
26
+ "npm-publish": "npm run clean && npm run test && npm publish"
32
27
  },
33
28
  "exports": {
34
29
  "./package.json": "./package.json",
@@ -38,37 +33,16 @@
38
33
  }
39
34
  },
40
35
  "dependencies": {
41
- "@modelcontextprotocol/sdk": "^1.16.0",
42
- "commander": "^13.1.0",
43
- "debug": "^4.4.1",
44
- "dotenv": "^17.2.0",
45
- "mime": "^4.0.7",
46
- "playwright": "1.56.0-alpha-1756505518000",
47
- "playwright-core": "1.56.0-alpha-1756505518000",
48
- "ws": "^8.18.1",
49
- "zod": "^3.24.1",
50
- "zod-to-json-schema": "^3.24.4"
51
- },
52
- "devDependencies": {
53
- "@anthropic-ai/sdk": "^0.57.0",
54
- "@eslint/eslintrc": "^3.2.0",
55
- "@eslint/js": "^9.19.0",
56
- "@playwright/test": "1.56.0-alpha-1756505518000",
57
- "@stylistic/eslint-plugin": "^3.0.1",
58
- "@types/debug": "^4.1.12",
59
- "@types/node": "^22.13.10",
60
- "@types/ws": "^8.18.1",
61
- "@typescript-eslint/eslint-plugin": "^8.26.1",
62
- "@typescript-eslint/parser": "^8.26.1",
63
- "@typescript-eslint/utils": "^8.26.1",
64
- "esbuild": "^0.20.1",
65
- "eslint": "^9.19.0",
66
- "eslint-plugin-import": "^2.31.0",
67
- "eslint-plugin-notice": "^1.0.0",
68
- "openai": "^5.10.2",
69
- "typescript": "^5.8.2"
36
+ "playwright": "1.56.0-alpha-2025-09-06",
37
+ "playwright-core": "1.56.0-alpha-2025-09-06"
70
38
  },
71
39
  "bin": {
72
40
  "mcp-server-playwright": "cli.js"
41
+ },
42
+ "devDependencies": {
43
+ "@modelcontextprotocol/sdk": "^1.17.5",
44
+ "@playwright/test": "1.56.0-alpha-2025-09-06",
45
+ "@types/node": "^24.3.0",
46
+ "zod-to-json-schema": "^3.24.6"
73
47
  }
74
48
  }
@@ -1,211 +0,0 @@
1
- /**
2
- * Copyright (c) Microsoft Corporation.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- import fs from 'fs';
17
- import net from 'net';
18
- import path from 'path';
19
- import * as playwright from 'playwright';
20
- // @ts-ignore
21
- import { registryDirectory } from 'playwright-core/lib/server/registry/index';
22
- // @ts-ignore
23
- import { startTraceViewerServer } from 'playwright-core/lib/server';
24
- import { logUnhandledError, testDebug } from './utils/log.js';
25
- import { createHash } from './utils/guid.js';
26
- import { outputFile } from './config.js';
27
- export function contextFactory(config) {
28
- if (config.browser.remoteEndpoint)
29
- return new RemoteContextFactory(config);
30
- if (config.browser.cdpEndpoint)
31
- return new CdpContextFactory(config);
32
- if (config.browser.isolated)
33
- return new IsolatedContextFactory(config);
34
- return new PersistentContextFactory(config);
35
- }
36
- class BaseContextFactory {
37
- config;
38
- _logName;
39
- _browserPromise;
40
- constructor(name, config) {
41
- this._logName = name;
42
- this.config = config;
43
- }
44
- async _obtainBrowser(clientInfo) {
45
- if (this._browserPromise)
46
- return this._browserPromise;
47
- testDebug(`obtain browser (${this._logName})`);
48
- this._browserPromise = this._doObtainBrowser(clientInfo);
49
- void this._browserPromise.then(browser => {
50
- browser.on('disconnected', () => {
51
- this._browserPromise = undefined;
52
- });
53
- }).catch(() => {
54
- this._browserPromise = undefined;
55
- });
56
- return this._browserPromise;
57
- }
58
- async _doObtainBrowser(clientInfo) {
59
- throw new Error('Not implemented');
60
- }
61
- async createContext(clientInfo) {
62
- testDebug(`create browser context (${this._logName})`);
63
- const browser = await this._obtainBrowser(clientInfo);
64
- const browserContext = await this._doCreateContext(browser);
65
- return { browserContext, close: () => this._closeBrowserContext(browserContext, browser) };
66
- }
67
- async _doCreateContext(browser) {
68
- throw new Error('Not implemented');
69
- }
70
- async _closeBrowserContext(browserContext, browser) {
71
- testDebug(`close browser context (${this._logName})`);
72
- if (browser.contexts().length === 1)
73
- this._browserPromise = undefined;
74
- await browserContext.close().catch(logUnhandledError);
75
- if (browser.contexts().length === 0) {
76
- testDebug(`close browser (${this._logName})`);
77
- await browser.close().catch(logUnhandledError);
78
- }
79
- }
80
- }
81
- class IsolatedContextFactory extends BaseContextFactory {
82
- constructor(config) {
83
- super('isolated', config);
84
- }
85
- async _doObtainBrowser(clientInfo) {
86
- await injectCdpPort(this.config.browser);
87
- const browserType = playwright[this.config.browser.browserName];
88
- return browserType.launch({
89
- tracesDir: await startTraceServer(this.config, clientInfo.rootPath),
90
- ...this.config.browser.launchOptions,
91
- handleSIGINT: false,
92
- handleSIGTERM: false,
93
- }).catch(error => {
94
- if (error.message.includes('Executable doesn\'t exist'))
95
- throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
96
- throw error;
97
- });
98
- }
99
- async _doCreateContext(browser) {
100
- return browser.newContext(this.config.browser.contextOptions);
101
- }
102
- }
103
- class CdpContextFactory extends BaseContextFactory {
104
- constructor(config) {
105
- super('cdp', config);
106
- }
107
- async _doObtainBrowser() {
108
- return playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint);
109
- }
110
- async _doCreateContext(browser) {
111
- return this.config.browser.isolated ? await browser.newContext() : browser.contexts()[0];
112
- }
113
- }
114
- class RemoteContextFactory extends BaseContextFactory {
115
- constructor(config) {
116
- super('remote', config);
117
- }
118
- async _doObtainBrowser() {
119
- const url = new URL(this.config.browser.remoteEndpoint);
120
- url.searchParams.set('browser', this.config.browser.browserName);
121
- if (this.config.browser.launchOptions)
122
- url.searchParams.set('launch-options', JSON.stringify(this.config.browser.launchOptions));
123
- return playwright[this.config.browser.browserName].connect(String(url));
124
- }
125
- async _doCreateContext(browser) {
126
- return browser.newContext();
127
- }
128
- }
129
- class PersistentContextFactory {
130
- config;
131
- name = 'persistent';
132
- description = 'Create a new persistent browser context';
133
- _userDataDirs = new Set();
134
- constructor(config) {
135
- this.config = config;
136
- }
137
- async createContext(clientInfo) {
138
- await injectCdpPort(this.config.browser);
139
- testDebug('create browser context (persistent)');
140
- const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo.rootPath);
141
- const tracesDir = await startTraceServer(this.config, clientInfo.rootPath);
142
- this._userDataDirs.add(userDataDir);
143
- testDebug('lock user data dir', userDataDir);
144
- const browserType = playwright[this.config.browser.browserName];
145
- for (let i = 0; i < 5; i++) {
146
- try {
147
- const browserContext = await browserType.launchPersistentContext(userDataDir, {
148
- tracesDir,
149
- ...this.config.browser.launchOptions,
150
- ...this.config.browser.contextOptions,
151
- handleSIGINT: false,
152
- handleSIGTERM: false,
153
- });
154
- const close = () => this._closeBrowserContext(browserContext, userDataDir);
155
- return { browserContext, close };
156
- }
157
- catch (error) {
158
- if (error.message.includes('Executable doesn\'t exist'))
159
- throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
160
- if (error.message.includes('ProcessSingleton') || error.message.includes('Invalid URL')) {
161
- // User data directory is already in use, try again.
162
- await new Promise(resolve => setTimeout(resolve, 1000));
163
- continue;
164
- }
165
- throw error;
166
- }
167
- }
168
- throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
169
- }
170
- async _closeBrowserContext(browserContext, userDataDir) {
171
- testDebug('close browser context (persistent)');
172
- testDebug('release user data dir', userDataDir);
173
- await browserContext.close().catch(() => { });
174
- this._userDataDirs.delete(userDataDir);
175
- testDebug('close browser context complete (persistent)');
176
- }
177
- async _createUserDataDir(rootPath) {
178
- const dir = process.env.PWMCP_PROFILES_DIR_FOR_TEST ?? registryDirectory;
179
- const browserToken = this.config.browser.launchOptions?.channel ?? this.config.browser?.browserName;
180
- // Hesitant putting hundreds of files into the user's workspace, so using it for hashing instead.
181
- const rootPathToken = rootPath ? `-${createHash(rootPath)}` : '';
182
- const result = path.join(dir, `mcp-${browserToken}${rootPathToken}`);
183
- await fs.promises.mkdir(result, { recursive: true });
184
- return result;
185
- }
186
- }
187
- async function injectCdpPort(browserConfig) {
188
- if (browserConfig.browserName === 'chromium')
189
- browserConfig.launchOptions.cdpPort = await findFreePort();
190
- }
191
- async function findFreePort() {
192
- return new Promise((resolve, reject) => {
193
- const server = net.createServer();
194
- server.listen(0, () => {
195
- const { port } = server.address();
196
- server.close(() => resolve(port));
197
- });
198
- server.on('error', reject);
199
- });
200
- }
201
- async function startTraceServer(config, rootPath) {
202
- if (!config.saveTrace)
203
- return undefined;
204
- const tracesDir = await outputFile(config, rootPath, `traces-${Date.now()}`);
205
- const server = await startTraceViewerServer();
206
- const urlPrefix = server.urlPrefix('human-readable');
207
- const url = urlPrefix + '/trace/index.html?trace=' + tracesDir + '/trace.json';
208
- // eslint-disable-next-line no-console
209
- console.error('\nTrace viewer listening on ' + url);
210
- return tracesDir;
211
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * Copyright (c) Microsoft Corporation.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- import { fileURLToPath } from 'url';
17
- import { Context } from './context.js';
18
- import { logUnhandledError } from './utils/log.js';
19
- import { Response } from './response.js';
20
- import { SessionLog } from './sessionLog.js';
21
- import { filteredTools } from './tools.js';
22
- import { toMcpTool } from './mcp/tool.js';
23
- export class BrowserServerBackend {
24
- _tools;
25
- _context;
26
- _sessionLog;
27
- _config;
28
- _browserContextFactory;
29
- constructor(config, factory) {
30
- this._config = config;
31
- this._browserContextFactory = factory;
32
- this._tools = filteredTools(config);
33
- }
34
- async initialize(server, clientVersion, roots) {
35
- let rootPath;
36
- if (roots.length > 0) {
37
- const firstRootUri = roots[0]?.uri;
38
- const url = firstRootUri ? new URL(firstRootUri) : undefined;
39
- rootPath = url ? fileURLToPath(url) : undefined;
40
- }
41
- this._sessionLog = this._config.saveSession ? await SessionLog.create(this._config, rootPath) : undefined;
42
- this._context = new Context({
43
- tools: this._tools,
44
- config: this._config,
45
- browserContextFactory: this._browserContextFactory,
46
- sessionLog: this._sessionLog,
47
- clientInfo: { ...clientVersion, rootPath },
48
- });
49
- }
50
- async listTools() {
51
- return this._tools.map(tool => toMcpTool(tool.schema));
52
- }
53
- async callTool(name, rawArguments) {
54
- const tool = this._tools.find(tool => tool.schema.name === name);
55
- if (!tool)
56
- throw new Error(`Tool "${name}" not found`);
57
- const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
58
- const context = this._context;
59
- const response = new Response(context, name, parsedArguments);
60
- context.setRunningTool(name);
61
- try {
62
- await tool.handle(context, parsedArguments, response);
63
- await response.finish();
64
- this._sessionLog?.logResponse(response);
65
- }
66
- catch (error) {
67
- response.addError(String(error));
68
- }
69
- finally {
70
- context.setRunningTool(undefined);
71
- }
72
- return response.serialize();
73
- }
74
- serverClosed() {
75
- void this._context?.dispose().catch(logUnhandledError);
76
- }
77
- }