@playwright/mcp 0.0.36-alpha-2025-09-04 → 0.0.37-alpha-2025-09-08
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/README.md +81 -46
- package/cli.js +7 -1
- package/config.d.ts +24 -0
- package/index.js +1 -1
- package/package.json +14 -39
- package/lib/browser/browserContextFactory.js +0 -251
- package/lib/browser/browserServerBackend.js +0 -79
- package/lib/browser/codegen.js +0 -54
- package/lib/browser/config.js +0 -263
- package/lib/browser/context.js +0 -223
- package/lib/browser/response.js +0 -165
- package/lib/browser/sessionLog.js +0 -126
- package/lib/browser/tab.js +0 -251
- package/lib/browser/tools/common.js +0 -57
- package/lib/browser/tools/console.js +0 -35
- package/lib/browser/tools/dialogs.js +0 -49
- package/lib/browser/tools/evaluate.js +0 -88
- package/lib/browser/tools/files.js +0 -46
- package/lib/browser/tools/form.js +0 -92
- package/lib/browser/tools/install.js +0 -57
- package/lib/browser/tools/keyboard.js +0 -113
- package/lib/browser/tools/mouse.js +0 -101
- package/lib/browser/tools/navigate.js +0 -56
- package/lib/browser/tools/network.js +0 -43
- package/lib/browser/tools/pdf.js +0 -76
- package/lib/browser/tools/screenshot.js +0 -115
- package/lib/browser/tools/snapshot.js +0 -175
- package/lib/browser/tools/tabs.js +0 -61
- package/lib/browser/tools/tool.js +0 -37
- package/lib/browser/tools/utils.js +0 -79
- package/lib/browser/tools/verify.js +0 -172
- package/lib/browser/tools/wait.js +0 -57
- package/lib/browser/tools.js +0 -61
- package/lib/extension/cdpRelay.js +0 -395
- package/lib/extension/extensionContextFactory.js +0 -93
- package/lib/extension/protocol.js +0 -21
- package/lib/index.js +0 -75
- package/lib/log.js +0 -28
- package/lib/package.js +0 -24
- package/lib/program.js +0 -161
- package/lib/sdk/bundle.js +0 -79
- package/lib/sdk/http.js +0 -175
- package/lib/sdk/inProcessTransport.js +0 -67
- package/lib/sdk/manualPromise.js +0 -113
- package/lib/sdk/mdb.js +0 -237
- package/lib/sdk/proxyBackend.js +0 -141
- package/lib/sdk/server.js +0 -164
- package/lib/sdk/tool.js +0 -36
- package/lib/vscode/host.js +0 -199
- package/lib/vscode/main.js +0 -97
package/README.md
CHANGED
|
@@ -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>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
--
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
--
|
|
198
|
-
--
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
--
|
|
203
|
-
--
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
--
|
|
207
|
-
|
|
208
|
-
--
|
|
209
|
-
|
|
210
|
-
--
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
--
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
--
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
--
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
require('
|
|
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.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playwright/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37-alpha-2025-09-08",
|
|
4
4
|
"description": "Playwright Tools for MCP",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,19 +15,15 @@
|
|
|
15
15
|
},
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
17
|
"scripts": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"check-deps": "node utils/check-deps.js",
|
|
22
|
-
"update-readme": "node utils/update-readme.js",
|
|
23
|
-
"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 .",
|
|
24
21
|
"test": "playwright test",
|
|
25
22
|
"ctest": "playwright test --project=chrome",
|
|
26
23
|
"ftest": "playwright test --project=firefox",
|
|
27
24
|
"wtest": "playwright test --project=webkit",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"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"
|
|
31
27
|
},
|
|
32
28
|
"exports": {
|
|
33
29
|
"./package.json": "./package.json",
|
|
@@ -37,37 +33,16 @@
|
|
|
37
33
|
}
|
|
38
34
|
},
|
|
39
35
|
"dependencies": {
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"dotenv": "^17.2.0",
|
|
43
|
-
"mime": "^4.0.7",
|
|
44
|
-
"playwright": "1.56.0-alpha-1756505518000",
|
|
45
|
-
"playwright-core": "1.56.0-alpha-1756505518000",
|
|
46
|
-
"ws": "^8.18.1"
|
|
47
|
-
},
|
|
48
|
-
"devDependencies": {
|
|
49
|
-
"@anthropic-ai/sdk": "^0.57.0",
|
|
50
|
-
"@eslint/eslintrc": "^3.2.0",
|
|
51
|
-
"@eslint/js": "^9.19.0",
|
|
52
|
-
"@modelcontextprotocol/sdk": "^1.16.0",
|
|
53
|
-
"@playwright/test": "1.56.0-alpha-1756505518000",
|
|
54
|
-
"@stylistic/eslint-plugin": "^3.0.1",
|
|
55
|
-
"@types/debug": "^4.1.12",
|
|
56
|
-
"@types/node": "^22.13.10",
|
|
57
|
-
"@types/ws": "^8.18.1",
|
|
58
|
-
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
|
59
|
-
"@typescript-eslint/parser": "^8.26.1",
|
|
60
|
-
"@typescript-eslint/utils": "^8.26.1",
|
|
61
|
-
"esbuild": "^0.20.1",
|
|
62
|
-
"eslint": "^9.19.0",
|
|
63
|
-
"eslint-plugin-import": "^2.31.0",
|
|
64
|
-
"eslint-plugin-notice": "^1.0.0",
|
|
65
|
-
"openai": "^5.10.2",
|
|
66
|
-
"typescript": "^5.8.2",
|
|
67
|
-
"zod": "^3.24.1",
|
|
68
|
-
"zod-to-json-schema": "^3.24.4"
|
|
36
|
+
"playwright": "1.56.0-alpha-2025-09-06",
|
|
37
|
+
"playwright-core": "1.56.0-alpha-2025-09-06"
|
|
69
38
|
},
|
|
70
39
|
"bin": {
|
|
71
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"
|
|
72
47
|
}
|
|
73
48
|
}
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) Microsoft Corporation.
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
-
if (k2 === undefined) k2 = k;
|
|
19
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
-
}
|
|
23
|
-
Object.defineProperty(o, k2, desc);
|
|
24
|
-
}) : (function(o, m, k, k2) {
|
|
25
|
-
if (k2 === undefined) k2 = k;
|
|
26
|
-
o[k2] = m[k];
|
|
27
|
-
}));
|
|
28
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
-
}) : function(o, v) {
|
|
31
|
-
o["default"] = v;
|
|
32
|
-
});
|
|
33
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
-
var ownKeys = function(o) {
|
|
35
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
-
var ar = [];
|
|
37
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
-
return ar;
|
|
39
|
-
};
|
|
40
|
-
return ownKeys(o);
|
|
41
|
-
};
|
|
42
|
-
return function (mod) {
|
|
43
|
-
if (mod && mod.__esModule) return mod;
|
|
44
|
-
var result = {};
|
|
45
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
-
__setModuleDefault(result, mod);
|
|
47
|
-
return result;
|
|
48
|
-
};
|
|
49
|
-
})();
|
|
50
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
51
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
52
|
-
};
|
|
53
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
-
exports.contextFactory = contextFactory;
|
|
55
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
56
|
-
const fs_1 = __importDefault(require("fs"));
|
|
57
|
-
const net_1 = __importDefault(require("net"));
|
|
58
|
-
const path_1 = __importDefault(require("path"));
|
|
59
|
-
const playwright = __importStar(require("playwright"));
|
|
60
|
-
// @ts-ignore
|
|
61
|
-
const index_1 = require("playwright-core/lib/server/registry/index");
|
|
62
|
-
// @ts-ignore
|
|
63
|
-
const server_1 = require("playwright-core/lib/server");
|
|
64
|
-
const log_1 = require("../log");
|
|
65
|
-
const config_1 = require("./config");
|
|
66
|
-
function contextFactory(config) {
|
|
67
|
-
if (config.browser.remoteEndpoint)
|
|
68
|
-
return new RemoteContextFactory(config);
|
|
69
|
-
if (config.browser.cdpEndpoint)
|
|
70
|
-
return new CdpContextFactory(config);
|
|
71
|
-
if (config.browser.isolated)
|
|
72
|
-
return new IsolatedContextFactory(config);
|
|
73
|
-
return new PersistentContextFactory(config);
|
|
74
|
-
}
|
|
75
|
-
class BaseContextFactory {
|
|
76
|
-
constructor(name, config) {
|
|
77
|
-
this._logName = name;
|
|
78
|
-
this.config = config;
|
|
79
|
-
}
|
|
80
|
-
async _obtainBrowser(clientInfo) {
|
|
81
|
-
if (this._browserPromise)
|
|
82
|
-
return this._browserPromise;
|
|
83
|
-
(0, log_1.testDebug)(`obtain browser (${this._logName})`);
|
|
84
|
-
this._browserPromise = this._doObtainBrowser(clientInfo);
|
|
85
|
-
void this._browserPromise.then(browser => {
|
|
86
|
-
browser.on('disconnected', () => {
|
|
87
|
-
this._browserPromise = undefined;
|
|
88
|
-
});
|
|
89
|
-
}).catch(() => {
|
|
90
|
-
this._browserPromise = undefined;
|
|
91
|
-
});
|
|
92
|
-
return this._browserPromise;
|
|
93
|
-
}
|
|
94
|
-
async _doObtainBrowser(clientInfo) {
|
|
95
|
-
throw new Error('Not implemented');
|
|
96
|
-
}
|
|
97
|
-
async createContext(clientInfo) {
|
|
98
|
-
(0, log_1.testDebug)(`create browser context (${this._logName})`);
|
|
99
|
-
const browser = await this._obtainBrowser(clientInfo);
|
|
100
|
-
const browserContext = await this._doCreateContext(browser);
|
|
101
|
-
return { browserContext, close: () => this._closeBrowserContext(browserContext, browser) };
|
|
102
|
-
}
|
|
103
|
-
async _doCreateContext(browser) {
|
|
104
|
-
throw new Error('Not implemented');
|
|
105
|
-
}
|
|
106
|
-
async _closeBrowserContext(browserContext, browser) {
|
|
107
|
-
(0, log_1.testDebug)(`close browser context (${this._logName})`);
|
|
108
|
-
if (browser.contexts().length === 1)
|
|
109
|
-
this._browserPromise = undefined;
|
|
110
|
-
await browserContext.close().catch(log_1.logUnhandledError);
|
|
111
|
-
if (browser.contexts().length === 0) {
|
|
112
|
-
(0, log_1.testDebug)(`close browser (${this._logName})`);
|
|
113
|
-
await browser.close().catch(log_1.logUnhandledError);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
class IsolatedContextFactory extends BaseContextFactory {
|
|
118
|
-
constructor(config) {
|
|
119
|
-
super('isolated', config);
|
|
120
|
-
}
|
|
121
|
-
async _doObtainBrowser(clientInfo) {
|
|
122
|
-
await injectCdpPort(this.config.browser);
|
|
123
|
-
const browserType = playwright[this.config.browser.browserName];
|
|
124
|
-
return browserType.launch({
|
|
125
|
-
tracesDir: await startTraceServer(this.config, clientInfo.rootPath),
|
|
126
|
-
...this.config.browser.launchOptions,
|
|
127
|
-
handleSIGINT: false,
|
|
128
|
-
handleSIGTERM: false,
|
|
129
|
-
}).catch(error => {
|
|
130
|
-
if (error.message.includes('Executable doesn\'t exist'))
|
|
131
|
-
throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
|
|
132
|
-
throw error;
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
async _doCreateContext(browser) {
|
|
136
|
-
return browser.newContext(this.config.browser.contextOptions);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
class CdpContextFactory extends BaseContextFactory {
|
|
140
|
-
constructor(config) {
|
|
141
|
-
super('cdp', config);
|
|
142
|
-
}
|
|
143
|
-
async _doObtainBrowser() {
|
|
144
|
-
return playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint);
|
|
145
|
-
}
|
|
146
|
-
async _doCreateContext(browser) {
|
|
147
|
-
return this.config.browser.isolated ? await browser.newContext() : browser.contexts()[0];
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
class RemoteContextFactory extends BaseContextFactory {
|
|
151
|
-
constructor(config) {
|
|
152
|
-
super('remote', config);
|
|
153
|
-
}
|
|
154
|
-
async _doObtainBrowser() {
|
|
155
|
-
const url = new URL(this.config.browser.remoteEndpoint);
|
|
156
|
-
url.searchParams.set('browser', this.config.browser.browserName);
|
|
157
|
-
if (this.config.browser.launchOptions)
|
|
158
|
-
url.searchParams.set('launch-options', JSON.stringify(this.config.browser.launchOptions));
|
|
159
|
-
return playwright[this.config.browser.browserName].connect(String(url));
|
|
160
|
-
}
|
|
161
|
-
async _doCreateContext(browser) {
|
|
162
|
-
return browser.newContext();
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
class PersistentContextFactory {
|
|
166
|
-
constructor(config) {
|
|
167
|
-
this.name = 'persistent';
|
|
168
|
-
this.description = 'Create a new persistent browser context';
|
|
169
|
-
this._userDataDirs = new Set();
|
|
170
|
-
this.config = config;
|
|
171
|
-
}
|
|
172
|
-
async createContext(clientInfo) {
|
|
173
|
-
var _a;
|
|
174
|
-
await injectCdpPort(this.config.browser);
|
|
175
|
-
(0, log_1.testDebug)('create browser context (persistent)');
|
|
176
|
-
const userDataDir = (_a = this.config.browser.userDataDir) !== null && _a !== void 0 ? _a : await this._createUserDataDir(clientInfo.rootPath);
|
|
177
|
-
const tracesDir = await startTraceServer(this.config, clientInfo.rootPath);
|
|
178
|
-
this._userDataDirs.add(userDataDir);
|
|
179
|
-
(0, log_1.testDebug)('lock user data dir', userDataDir);
|
|
180
|
-
const browserType = playwright[this.config.browser.browserName];
|
|
181
|
-
for (let i = 0; i < 5; i++) {
|
|
182
|
-
try {
|
|
183
|
-
const browserContext = await browserType.launchPersistentContext(userDataDir, {
|
|
184
|
-
tracesDir,
|
|
185
|
-
...this.config.browser.launchOptions,
|
|
186
|
-
...this.config.browser.contextOptions,
|
|
187
|
-
handleSIGINT: false,
|
|
188
|
-
handleSIGTERM: false,
|
|
189
|
-
});
|
|
190
|
-
const close = () => this._closeBrowserContext(browserContext, userDataDir);
|
|
191
|
-
return { browserContext, close };
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
194
|
-
if (error.message.includes('Executable doesn\'t exist'))
|
|
195
|
-
throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
|
|
196
|
-
if (error.message.includes('ProcessSingleton') || error.message.includes('Invalid URL')) {
|
|
197
|
-
// User data directory is already in use, try again.
|
|
198
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
throw error;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
|
|
205
|
-
}
|
|
206
|
-
async _closeBrowserContext(browserContext, userDataDir) {
|
|
207
|
-
(0, log_1.testDebug)('close browser context (persistent)');
|
|
208
|
-
(0, log_1.testDebug)('release user data dir', userDataDir);
|
|
209
|
-
await browserContext.close().catch(() => { });
|
|
210
|
-
this._userDataDirs.delete(userDataDir);
|
|
211
|
-
(0, log_1.testDebug)('close browser context complete (persistent)');
|
|
212
|
-
}
|
|
213
|
-
async _createUserDataDir(rootPath) {
|
|
214
|
-
var _a, _b, _c, _d;
|
|
215
|
-
const dir = (_a = process.env.PWMCP_PROFILES_DIR_FOR_TEST) !== null && _a !== void 0 ? _a : index_1.registryDirectory;
|
|
216
|
-
const browserToken = (_c = (_b = this.config.browser.launchOptions) === null || _b === void 0 ? void 0 : _b.channel) !== null && _c !== void 0 ? _c : (_d = this.config.browser) === null || _d === void 0 ? void 0 : _d.browserName;
|
|
217
|
-
// Hesitant putting hundreds of files into the user's workspace, so using it for hashing instead.
|
|
218
|
-
const rootPathToken = rootPath ? `-${createHash(rootPath)}` : '';
|
|
219
|
-
const result = path_1.default.join(dir, `mcp-${browserToken}${rootPathToken}`);
|
|
220
|
-
await fs_1.default.promises.mkdir(result, { recursive: true });
|
|
221
|
-
return result;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
async function injectCdpPort(browserConfig) {
|
|
225
|
-
if (browserConfig.browserName === 'chromium')
|
|
226
|
-
browserConfig.launchOptions.cdpPort = await findFreePort();
|
|
227
|
-
}
|
|
228
|
-
async function findFreePort() {
|
|
229
|
-
return new Promise((resolve, reject) => {
|
|
230
|
-
const server = net_1.default.createServer();
|
|
231
|
-
server.listen(0, () => {
|
|
232
|
-
const { port } = server.address();
|
|
233
|
-
server.close(() => resolve(port));
|
|
234
|
-
});
|
|
235
|
-
server.on('error', reject);
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
async function startTraceServer(config, rootPath) {
|
|
239
|
-
if (!config.saveTrace)
|
|
240
|
-
return undefined;
|
|
241
|
-
const tracesDir = await (0, config_1.outputFile)(config, rootPath, `traces-${Date.now()}`);
|
|
242
|
-
const server = await (0, server_1.startTraceViewerServer)();
|
|
243
|
-
const urlPrefix = server.urlPrefix('human-readable');
|
|
244
|
-
const url = urlPrefix + '/trace/index.html?trace=' + tracesDir + '/trace.json';
|
|
245
|
-
// eslint-disable-next-line no-console
|
|
246
|
-
console.error('\nTrace viewer listening on ' + url);
|
|
247
|
-
return tracesDir;
|
|
248
|
-
}
|
|
249
|
-
function createHash(data) {
|
|
250
|
-
return crypto_1.default.createHash('sha256').update(data).digest('hex').slice(0, 7);
|
|
251
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) Microsoft Corporation.
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.BrowserServerBackend = void 0;
|
|
19
|
-
const url_1 = require("url");
|
|
20
|
-
const context_1 = require("./context");
|
|
21
|
-
const log_1 = require("../log");
|
|
22
|
-
const response_1 = require("./response");
|
|
23
|
-
const sessionLog_1 = require("./sessionLog");
|
|
24
|
-
const tools_1 = require("./tools");
|
|
25
|
-
const tool_1 = require("../sdk/tool");
|
|
26
|
-
class BrowserServerBackend {
|
|
27
|
-
constructor(config, factory) {
|
|
28
|
-
this._config = config;
|
|
29
|
-
this._browserContextFactory = factory;
|
|
30
|
-
this._tools = (0, tools_1.filteredTools)(config);
|
|
31
|
-
}
|
|
32
|
-
async initialize(server, clientVersion, roots) {
|
|
33
|
-
var _a;
|
|
34
|
-
let rootPath;
|
|
35
|
-
if (roots.length > 0) {
|
|
36
|
-
const firstRootUri = (_a = roots[0]) === null || _a === void 0 ? void 0 : _a.uri;
|
|
37
|
-
const url = firstRootUri ? new URL(firstRootUri) : undefined;
|
|
38
|
-
rootPath = url ? (0, url_1.fileURLToPath)(url) : undefined;
|
|
39
|
-
}
|
|
40
|
-
this._sessionLog = this._config.saveSession ? await sessionLog_1.SessionLog.create(this._config, rootPath) : undefined;
|
|
41
|
-
this._context = new context_1.Context({
|
|
42
|
-
tools: this._tools,
|
|
43
|
-
config: this._config,
|
|
44
|
-
browserContextFactory: this._browserContextFactory,
|
|
45
|
-
sessionLog: this._sessionLog,
|
|
46
|
-
clientInfo: { ...clientVersion, rootPath },
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
async listTools() {
|
|
50
|
-
return this._tools.map(tool => (0, tool_1.toMcpTool)(tool.schema));
|
|
51
|
-
}
|
|
52
|
-
async callTool(name, rawArguments) {
|
|
53
|
-
var _a;
|
|
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_1.Response(context, name, parsedArguments);
|
|
60
|
-
context.setRunningTool(name);
|
|
61
|
-
try {
|
|
62
|
-
await tool.handle(context, parsedArguments, response);
|
|
63
|
-
await response.finish();
|
|
64
|
-
(_a = this._sessionLog) === null || _a === void 0 ? void 0 : _a.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
|
-
var _a;
|
|
76
|
-
void ((_a = this._context) === null || _a === void 0 ? void 0 : _a.dispose().catch(log_1.logUnhandledError));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
exports.BrowserServerBackend = BrowserServerBackend;
|