playwright 1.56.0-alpha-2025-09-03 → 1.56.0-alpha-2025-09-04
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 +2 -2
- package/ThirdPartyNotices.txt +3 -3
- package/lib/index.js +2 -2
- package/lib/matchers/toBeTruthy.js +3 -3
- package/lib/matchers/toEqual.js +3 -3
- package/lib/matchers/toMatchText.js +3 -3
- package/lib/mcp/browser/actions.d.js +16 -0
- package/lib/mcp/browser/browserContextFactory.js +227 -0
- package/lib/mcp/browser/browserServerBackend.js +82 -0
- package/lib/mcp/browser/codegen.js +64 -0
- package/lib/mcp/browser/config.js +279 -0
- package/lib/mcp/browser/context.js +229 -0
- package/lib/mcp/browser/response.js +163 -0
- package/lib/mcp/browser/sessionLog.js +160 -0
- package/lib/mcp/browser/tab.js +256 -0
- package/lib/mcp/browser/tools/common.js +63 -0
- package/lib/mcp/browser/tools/console.js +41 -0
- package/lib/mcp/browser/tools/dialogs.js +55 -0
- package/lib/mcp/browser/tools/evaluate.js +70 -0
- package/lib/mcp/browser/tools/files.js +52 -0
- package/lib/mcp/browser/tools/form.js +73 -0
- package/lib/mcp/browser/tools/install.js +69 -0
- package/lib/mcp/browser/tools/keyboard.js +95 -0
- package/lib/mcp/browser/tools/mouse.js +107 -0
- package/lib/mcp/browser/tools/navigate.js +62 -0
- package/lib/mcp/browser/tools/network.js +49 -0
- package/lib/mcp/{sdk/call.js → browser/tools/pdf.js} +27 -18
- package/lib/mcp/browser/tools/screenshot.js +94 -0
- package/lib/mcp/browser/tools/snapshot.js +162 -0
- package/lib/mcp/browser/tools/tabs.js +67 -0
- package/lib/mcp/browser/tools/tool.js +49 -0
- package/lib/mcp/browser/tools/utils.js +90 -0
- package/lib/mcp/browser/tools/verify.js +154 -0
- package/lib/mcp/browser/tools/wait.js +63 -0
- package/lib/mcp/browser/tools.js +42 -83
- package/lib/mcp/config.d.js +16 -0
- package/lib/mcp/extension/cdpRelay.js +348 -0
- package/lib/mcp/extension/extensionContextFactory.js +75 -0
- package/lib/mcp/{browser/tool.js → extension/protocol.js} +6 -8
- package/lib/mcp/index.js +61 -0
- package/lib/mcp/log.js +35 -0
- package/lib/mcp/program.js +96 -0
- package/lib/mcp/sdk/bundle.js +11 -1
- package/lib/mcp/sdk/exports.js +2 -2
- package/lib/mcp/sdk/http.js +21 -6
- package/lib/mcp/sdk/mdb.js +16 -15
- package/lib/mcp/sdk/proxyBackend.js +7 -6
- package/lib/mcp/sdk/server.js +6 -6
- package/lib/mcp/sdk/tool.js +1 -1
- package/lib/mcp/{browser/backend.js → test/browserBackend.js} +7 -9
- package/lib/mcp/test/browserTool.js +30 -0
- package/lib/mcp/test/browserTools.js +120 -0
- package/lib/mcp/test/{backend.js → testBackend.js} +12 -12
- package/lib/mcp/test/{context.js → testContext.js} +6 -6
- package/lib/mcp/test/{tool.js → testTool.js} +6 -6
- package/lib/mcp/test/{tools.js → testTools.js} +7 -7
- package/lib/mcpBundleImpl.js +11 -11
- package/lib/program.js +4 -4
- package/package.json +6 -3
package/lib/mcp/log.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var log_exports = {};
|
|
20
|
+
__export(log_exports, {
|
|
21
|
+
logUnhandledError: () => logUnhandledError,
|
|
22
|
+
testDebug: () => testDebug
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(log_exports);
|
|
25
|
+
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
26
|
+
const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
|
|
27
|
+
function logUnhandledError(error) {
|
|
28
|
+
errorsDebug(error);
|
|
29
|
+
}
|
|
30
|
+
const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
|
|
31
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
32
|
+
0 && (module.exports = {
|
|
33
|
+
logUnhandledError,
|
|
34
|
+
testDebug
|
|
35
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
25
|
+
var mcpServer = __toESM(require("./sdk/server"));
|
|
26
|
+
var import_config = require("./browser/config");
|
|
27
|
+
var import_context = require("./browser/context");
|
|
28
|
+
var import_browserContextFactory = require("./browser/browserContextFactory");
|
|
29
|
+
var import_proxyBackend = require("./sdk/proxyBackend");
|
|
30
|
+
var import_browserServerBackend = require("./browser/browserServerBackend");
|
|
31
|
+
var import_extensionContextFactory = require("./extension/extensionContextFactory");
|
|
32
|
+
const packageJSON = require("../../package.json");
|
|
33
|
+
import_utilsBundle.program.version("Version " + packageJSON.version).name("Playwright MCP").option("--allowed-origins <origins>", "semicolon-separated list of origins to allow the browser to request. Default is to allow all.", import_config.semicolonSeparatedList).option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.", import_config.semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--config <path>", "path to the configuration file.").option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".').option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280, 720"').addOption(new import_utilsBundle.ProgramOption("--connect-tool", "Allow to switch between different browser connection methods.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).action(async (options) => {
|
|
34
|
+
setupExitWatchdog();
|
|
35
|
+
if (options.vision) {
|
|
36
|
+
console.error("The --vision option is deprecated, use --caps=vision instead");
|
|
37
|
+
options.caps = "vision";
|
|
38
|
+
}
|
|
39
|
+
const config = await (0, import_config.resolveCLIConfig)(options);
|
|
40
|
+
const browserContextFactory = (0, import_browserContextFactory.contextFactory)(config);
|
|
41
|
+
const extensionContextFactory = new import_extensionContextFactory.ExtensionContextFactory(config.browser.launchOptions.channel || "chrome", config.browser.userDataDir, config.browser.launchOptions.executablePath);
|
|
42
|
+
if (options.extension) {
|
|
43
|
+
const serverBackendFactory = {
|
|
44
|
+
name: "Playwright w/ extension",
|
|
45
|
+
nameInConfig: "playwright-extension",
|
|
46
|
+
version: packageJSON.version,
|
|
47
|
+
create: () => new import_browserServerBackend.BrowserServerBackend(config, extensionContextFactory)
|
|
48
|
+
};
|
|
49
|
+
await mcpServer.start(serverBackendFactory, config.server);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (options.connectTool) {
|
|
53
|
+
const providers = [
|
|
54
|
+
{
|
|
55
|
+
name: "default",
|
|
56
|
+
description: "Starts standalone browser",
|
|
57
|
+
connect: () => mcpServer.wrapInProcess(new import_browserServerBackend.BrowserServerBackend(config, browserContextFactory))
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "extension",
|
|
61
|
+
description: "Connect to a browser using the Playwright MCP extension",
|
|
62
|
+
connect: () => mcpServer.wrapInProcess(new import_browserServerBackend.BrowserServerBackend(config, extensionContextFactory))
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
const factory2 = {
|
|
66
|
+
name: "Playwright w/ switch",
|
|
67
|
+
nameInConfig: "playwright-switch",
|
|
68
|
+
version: packageJSON.version,
|
|
69
|
+
create: () => new import_proxyBackend.ProxyBackend(providers)
|
|
70
|
+
};
|
|
71
|
+
await mcpServer.start(factory2, config.server);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const factory = {
|
|
75
|
+
name: "Playwright",
|
|
76
|
+
nameInConfig: "playwright",
|
|
77
|
+
version: packageJSON.version,
|
|
78
|
+
create: () => new import_browserServerBackend.BrowserServerBackend(config, browserContextFactory)
|
|
79
|
+
};
|
|
80
|
+
await mcpServer.start(factory, config.server);
|
|
81
|
+
});
|
|
82
|
+
function setupExitWatchdog() {
|
|
83
|
+
let isExiting = false;
|
|
84
|
+
const handleExit = async () => {
|
|
85
|
+
if (isExiting)
|
|
86
|
+
return;
|
|
87
|
+
isExiting = true;
|
|
88
|
+
setTimeout(() => process.exit(0), 15e3);
|
|
89
|
+
await import_context.Context.disposeAll();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
};
|
|
92
|
+
process.stdin.on("close", handleExit);
|
|
93
|
+
process.on("SIGINT", handleExit);
|
|
94
|
+
process.on("SIGTERM", handleExit);
|
|
95
|
+
}
|
|
96
|
+
void import_utilsBundle.program.parseAsync(process.argv);
|
package/lib/mcp/sdk/bundle.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var bundle_exports = {};
|
|
20
30
|
__export(bundle_exports, {
|
|
@@ -33,7 +43,7 @@ __export(bundle_exports, {
|
|
|
33
43
|
zodToJsonSchema: () => zodToJsonSchema
|
|
34
44
|
});
|
|
35
45
|
module.exports = __toCommonJS(bundle_exports);
|
|
36
|
-
|
|
46
|
+
var bundle = __toESM(require("../../mcpBundleImpl"));
|
|
37
47
|
const zodToJsonSchema = bundle.zodToJsonSchema;
|
|
38
48
|
const Client = bundle.Client;
|
|
39
49
|
const Server = bundle.Server;
|
package/lib/mcp/sdk/exports.js
CHANGED
|
@@ -20,7 +20,7 @@ __reExport(exports_exports, require("./proxyBackend"), module.exports);
|
|
|
20
20
|
__reExport(exports_exports, require("./server"), module.exports);
|
|
21
21
|
__reExport(exports_exports, require("./tool"), module.exports);
|
|
22
22
|
__reExport(exports_exports, require("./http"), module.exports);
|
|
23
|
-
__reExport(exports_exports, require("./
|
|
23
|
+
__reExport(exports_exports, require("./mdb"), module.exports);
|
|
24
24
|
// Annotate the CommonJS export names for ESM import in node:
|
|
25
25
|
0 && (module.exports = {
|
|
26
26
|
...require("./inProcessTransport"),
|
|
@@ -28,5 +28,5 @@ __reExport(exports_exports, require("./call"), module.exports);
|
|
|
28
28
|
...require("./server"),
|
|
29
29
|
...require("./tool"),
|
|
30
30
|
...require("./http"),
|
|
31
|
-
...require("./
|
|
31
|
+
...require("./mdb")
|
|
32
32
|
});
|
package/lib/mcp/sdk/http.js
CHANGED
|
@@ -37,12 +37,13 @@ var import_assert = __toESM(require("assert"));
|
|
|
37
37
|
var import_http = __toESM(require("http"));
|
|
38
38
|
var import_crypto = __toESM(require("crypto"));
|
|
39
39
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
40
|
-
var
|
|
41
|
-
var
|
|
40
|
+
var mcpBundle = __toESM(require("./bundle"));
|
|
41
|
+
var mcpServer = __toESM(require("./server"));
|
|
42
42
|
const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
|
|
43
43
|
async function startHttpServer(config, abortSignal) {
|
|
44
44
|
const { host, port } = config;
|
|
45
45
|
const httpServer = import_http.default.createServer();
|
|
46
|
+
decorateServer(httpServer);
|
|
46
47
|
await new Promise((resolve, reject) => {
|
|
47
48
|
httpServer.on("error", reject);
|
|
48
49
|
abortSignal?.addEventListener("abort", () => {
|
|
@@ -91,10 +92,10 @@ async function handleSSE(serverBackendFactory, req, res, url, sessions) {
|
|
|
91
92
|
}
|
|
92
93
|
return await transport.handlePostMessage(req, res);
|
|
93
94
|
} else if (req.method === "GET") {
|
|
94
|
-
const transport = new
|
|
95
|
+
const transport = new mcpBundle.SSEServerTransport("/sse", res);
|
|
95
96
|
sessions.set(transport.sessionId, transport);
|
|
96
97
|
testDebug(`create SSE session: ${transport.sessionId}`);
|
|
97
|
-
await
|
|
98
|
+
await mcpServer.connect(serverBackendFactory, transport, false);
|
|
98
99
|
res.on("close", () => {
|
|
99
100
|
testDebug(`delete SSE session: ${transport.sessionId}`);
|
|
100
101
|
sessions.delete(transport.sessionId);
|
|
@@ -116,11 +117,11 @@ async function handleStreamable(serverBackendFactory, req, res, sessions) {
|
|
|
116
117
|
return await transport.handleRequest(req, res);
|
|
117
118
|
}
|
|
118
119
|
if (req.method === "POST") {
|
|
119
|
-
const transport = new
|
|
120
|
+
const transport = new mcpBundle.StreamableHTTPServerTransport({
|
|
120
121
|
sessionIdGenerator: () => import_crypto.default.randomUUID(),
|
|
121
122
|
onsessioninitialized: async (sessionId2) => {
|
|
122
123
|
testDebug(`create http session: ${transport.sessionId}`);
|
|
123
|
-
await
|
|
124
|
+
await mcpServer.connect(serverBackendFactory, transport, true);
|
|
124
125
|
sessions.set(sessionId2, transport);
|
|
125
126
|
}
|
|
126
127
|
});
|
|
@@ -136,6 +137,20 @@ async function handleStreamable(serverBackendFactory, req, res, sessions) {
|
|
|
136
137
|
res.statusCode = 400;
|
|
137
138
|
res.end("Invalid request");
|
|
138
139
|
}
|
|
140
|
+
function decorateServer(server) {
|
|
141
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
142
|
+
server.on("connection", (socket) => {
|
|
143
|
+
sockets.add(socket);
|
|
144
|
+
socket.once("close", () => sockets.delete(socket));
|
|
145
|
+
});
|
|
146
|
+
const close = server.close;
|
|
147
|
+
server.close = (callback) => {
|
|
148
|
+
for (const socket of sockets)
|
|
149
|
+
socket.destroy();
|
|
150
|
+
sockets.clear();
|
|
151
|
+
return close.call(server, callback);
|
|
152
|
+
};
|
|
153
|
+
}
|
|
139
154
|
// Annotate the CommonJS export names for ESM import in node:
|
|
140
155
|
0 && (module.exports = {
|
|
141
156
|
httpAddressToString,
|
package/lib/mcp/sdk/mdb.js
CHANGED
|
@@ -35,15 +35,14 @@ __export(mdb_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(mdb_exports);
|
|
36
36
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
37
37
|
var import_utils = require("playwright-core/lib/utils");
|
|
38
|
-
var
|
|
39
|
-
var
|
|
40
|
-
var
|
|
41
|
-
var
|
|
42
|
-
var
|
|
43
|
-
var mcpHttp = __toESM(require("./http.js"));
|
|
44
|
-
var import_server = require("./server.js");
|
|
38
|
+
var import_tool = require("./tool");
|
|
39
|
+
var mcpBundle = __toESM(require("./bundle"));
|
|
40
|
+
var mcpServer = __toESM(require("./server"));
|
|
41
|
+
var mcpHttp = __toESM(require("./http"));
|
|
42
|
+
var import_server = require("./server");
|
|
45
43
|
const mdbDebug = (0, import_utilsBundle.debug)("pw:mcp:mdb");
|
|
46
44
|
const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
|
|
45
|
+
const z = mcpBundle.z;
|
|
47
46
|
class MDBBackend {
|
|
48
47
|
constructor(topLevelBackend) {
|
|
49
48
|
this._stack = [];
|
|
@@ -73,6 +72,8 @@ class MDBBackend {
|
|
|
73
72
|
await entry.client.close();
|
|
74
73
|
entry = this._stack[0];
|
|
75
74
|
}
|
|
75
|
+
if (!entry)
|
|
76
|
+
throw new Error(`Tool ${name} not found in the tool stack`);
|
|
76
77
|
const resultPromise = new import_utils.ManualPromise();
|
|
77
78
|
entry.resultPromise = resultPromise;
|
|
78
79
|
this._client().callTool({
|
|
@@ -103,14 +104,14 @@ class MDBBackend {
|
|
|
103
104
|
}
|
|
104
105
|
async _pushTools(params) {
|
|
105
106
|
mdbDebug("pushing tools to the stack", params.mcpUrl);
|
|
106
|
-
const transport = new
|
|
107
|
+
const transport = new mcpBundle.StreamableHTTPClientTransport(new URL(params.mcpUrl));
|
|
107
108
|
await this._pushClient(transport, params.introMessage);
|
|
108
109
|
return { content: [{ type: "text", text: "Tools pushed" }] };
|
|
109
110
|
}
|
|
110
111
|
async _pushClient(transport, introMessage) {
|
|
111
112
|
mdbDebug("pushing client to the stack");
|
|
112
113
|
const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" });
|
|
113
|
-
client.setRequestHandler(
|
|
114
|
+
client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
|
|
114
115
|
await client.connect(transport);
|
|
115
116
|
mdbDebug("connected to the new client");
|
|
116
117
|
const { tools } = await client.listTools();
|
|
@@ -131,9 +132,9 @@ const pushToolsSchema = (0, import_tool.defineToolSchema)({
|
|
|
131
132
|
name: "mdb_push_tools",
|
|
132
133
|
title: "Push MCP tools to the tools stack",
|
|
133
134
|
description: "Push MCP tools to the tools stack",
|
|
134
|
-
inputSchema:
|
|
135
|
-
mcpUrl:
|
|
136
|
-
introMessage:
|
|
135
|
+
inputSchema: z.object({
|
|
136
|
+
mcpUrl: z.string(),
|
|
137
|
+
introMessage: z.string().optional()
|
|
137
138
|
}),
|
|
138
139
|
type: "readOnly"
|
|
139
140
|
});
|
|
@@ -144,7 +145,7 @@ async function runMainBackend(backendFactory, options) {
|
|
|
144
145
|
create: () => mdbBackend
|
|
145
146
|
};
|
|
146
147
|
const url = await startAsHttp(factory, { port: options?.port || 0 });
|
|
147
|
-
process.env.
|
|
148
|
+
process.env.PLAYWRIGHT_DEBUGGER_MCP = url;
|
|
148
149
|
if (options?.port !== void 0)
|
|
149
150
|
return url;
|
|
150
151
|
await mcpServer.connect(factory, new mcpBundle.StdioServerTransport(), false);
|
|
@@ -161,8 +162,8 @@ async function runOnPauseBackendLoop(mdbUrl, backend, introMessage) {
|
|
|
161
162
|
await mcpHttp.installHttpTransport(httpServer, factory);
|
|
162
163
|
const url = mcpHttp.httpAddressToString(httpServer.address());
|
|
163
164
|
const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" });
|
|
164
|
-
client.setRequestHandler(
|
|
165
|
-
const transport = new
|
|
165
|
+
client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
|
|
166
|
+
const transport = new mcpBundle.StreamableHTTPClientTransport(new URL(mdbUrl));
|
|
166
167
|
await client.connect(transport);
|
|
167
168
|
const pushToolsResult = await client.callTool({
|
|
168
169
|
name: pushToolsSchema.name,
|
|
@@ -32,8 +32,9 @@ __export(proxyBackend_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(proxyBackend_exports);
|
|
34
34
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
35
|
-
var
|
|
35
|
+
var mcpBundle = __toESM(require("./bundle"));
|
|
36
36
|
const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
|
|
37
|
+
const { z, zodToJsonSchema } = mcpBundle;
|
|
37
38
|
class ProxyBackend {
|
|
38
39
|
constructor(mcpProviders) {
|
|
39
40
|
this._roots = [];
|
|
@@ -89,8 +90,8 @@ Error: ${error}
|
|
|
89
90
|
"Connect to a browser using one of the available methods:",
|
|
90
91
|
...this._mcpProviders.map((factory) => `- "${factory.name}": ${factory.description}`)
|
|
91
92
|
].join("\n"),
|
|
92
|
-
inputSchema:
|
|
93
|
-
name:
|
|
93
|
+
inputSchema: zodToJsonSchema(z.object({
|
|
94
|
+
name: z.enum(this._mcpProviders.map((factory) => factory.name)).default(this._mcpProviders[0].name).describe("The method to use to connect to the browser")
|
|
94
95
|
}), { strictUnions: true }),
|
|
95
96
|
annotations: {
|
|
96
97
|
title: "Connect to a browser context",
|
|
@@ -102,14 +103,14 @@ Error: ${error}
|
|
|
102
103
|
async _setCurrentClient(factory) {
|
|
103
104
|
await this._currentClient?.close();
|
|
104
105
|
this._currentClient = void 0;
|
|
105
|
-
const client = new
|
|
106
|
+
const client = new mcpBundle.Client({ name: "Playwright MCP Proxy", version: "0.0.0" });
|
|
106
107
|
client.registerCapabilities({
|
|
107
108
|
roots: {
|
|
108
109
|
listRoots: true
|
|
109
110
|
}
|
|
110
111
|
});
|
|
111
|
-
client.setRequestHandler(
|
|
112
|
-
client.setRequestHandler(
|
|
112
|
+
client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._roots }));
|
|
113
|
+
client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
|
|
113
114
|
const transport = await factory.connect();
|
|
114
115
|
await client.connect(transport);
|
|
115
116
|
this._currentClient = client;
|
package/lib/mcp/sdk/server.js
CHANGED
|
@@ -35,9 +35,9 @@ __export(server_exports, {
|
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(server_exports);
|
|
37
37
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
38
|
-
var
|
|
39
|
-
var import_inProcessTransport = require("./inProcessTransport");
|
|
38
|
+
var mcpBundle = __toESM(require("./bundle"));
|
|
40
39
|
var import_http = require("./http");
|
|
40
|
+
var import_inProcessTransport = require("./inProcessTransport");
|
|
41
41
|
const serverDebug = (0, import_utilsBundle.debug)("pw:mcp:server");
|
|
42
42
|
const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
|
|
43
43
|
async function connect(factory, transport, runHeartbeat) {
|
|
@@ -52,19 +52,19 @@ function createServer(name, version, backend, runHeartbeat) {
|
|
|
52
52
|
let initializedPromiseResolve = () => {
|
|
53
53
|
};
|
|
54
54
|
const initializedPromise = new Promise((resolve) => initializedPromiseResolve = resolve);
|
|
55
|
-
const server = new
|
|
55
|
+
const server = new mcpBundle.Server({ name, version }, {
|
|
56
56
|
capabilities: {
|
|
57
57
|
tools: {}
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
|
-
server.setRequestHandler(
|
|
60
|
+
server.setRequestHandler(mcpBundle.ListToolsRequestSchema, async () => {
|
|
61
61
|
serverDebug("listTools");
|
|
62
62
|
await initializedPromise;
|
|
63
63
|
const tools = await backend.listTools();
|
|
64
64
|
return { tools };
|
|
65
65
|
});
|
|
66
66
|
let heartbeatRunning = false;
|
|
67
|
-
server.setRequestHandler(
|
|
67
|
+
server.setRequestHandler(mcpBundle.CallToolRequestSchema, async (request) => {
|
|
68
68
|
serverDebug("callTool", request);
|
|
69
69
|
await initializedPromise;
|
|
70
70
|
if (runHeartbeat && !heartbeatRunning) {
|
|
@@ -120,7 +120,7 @@ function addServerListener(server, event, listener) {
|
|
|
120
120
|
}
|
|
121
121
|
async function start(serverBackendFactory, options) {
|
|
122
122
|
if (options.port === void 0) {
|
|
123
|
-
await connect(serverBackendFactory, new
|
|
123
|
+
await connect(serverBackendFactory, new mcpBundle.StdioServerTransport(), false);
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
126
|
const httpServer = await (0, import_http.startHttpServer)(options);
|
package/lib/mcp/sdk/tool.js
CHANGED
|
@@ -22,7 +22,7 @@ __export(tool_exports, {
|
|
|
22
22
|
toMcpTool: () => toMcpTool
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(tool_exports);
|
|
25
|
-
var import_bundle = require("
|
|
25
|
+
var import_bundle = require("../sdk/bundle");
|
|
26
26
|
function toMcpTool(tool) {
|
|
27
27
|
return {
|
|
28
28
|
name: tool.name,
|
|
@@ -26,19 +26,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
mod
|
|
27
27
|
));
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var
|
|
30
|
-
__export(
|
|
29
|
+
var browserBackend_exports = {};
|
|
30
|
+
__export(browserBackend_exports, {
|
|
31
31
|
BrowserBackend: () => BrowserBackend,
|
|
32
32
|
runBrowserBackendOnError: () => runBrowserBackendOnError
|
|
33
33
|
});
|
|
34
|
-
module.exports = __toCommonJS(
|
|
34
|
+
module.exports = __toCommonJS(browserBackend_exports);
|
|
35
35
|
var mcp = __toESM(require("../sdk/exports"));
|
|
36
36
|
var mcpBundle = __toESM(require("../sdk/bundle"));
|
|
37
|
-
var
|
|
38
|
-
var import_exports = require("../sdk/exports");
|
|
39
|
-
var import_mdb = require("../sdk/mdb");
|
|
37
|
+
var import_browserTools = require("./browserTools");
|
|
40
38
|
var import_util = require("../../util");
|
|
41
|
-
const tools = [
|
|
39
|
+
const tools = [import_browserTools.snapshot, import_browserTools.pickLocator, import_browserTools.evaluate];
|
|
42
40
|
class BrowserBackend {
|
|
43
41
|
constructor(page) {
|
|
44
42
|
this.name = "Playwright";
|
|
@@ -65,7 +63,7 @@ class BrowserBackend {
|
|
|
65
63
|
return await tool.handle(this._page, parsedArguments);
|
|
66
64
|
}
|
|
67
65
|
}
|
|
68
|
-
const doneToolSchema =
|
|
66
|
+
const doneToolSchema = mcp.defineToolSchema({
|
|
69
67
|
name: "done",
|
|
70
68
|
title: "Done",
|
|
71
69
|
description: "Done",
|
|
@@ -84,7 +82,7 @@ ${snapshot2}
|
|
|
84
82
|
|
|
85
83
|
### Task
|
|
86
84
|
Try recovering from the error prior to continuing, use following tools to recover: ${tools.map((tool) => tool.schema.name).join(", ")}`;
|
|
87
|
-
await
|
|
85
|
+
await mcp.runOnPauseBackendLoop(process.env.PLAYWRIGHT_MDB_URL, new BrowserBackend(page), introMessage);
|
|
88
86
|
}
|
|
89
87
|
// Annotate the CommonJS export names for ESM import in node:
|
|
90
88
|
0 && (module.exports = {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var browserTool_exports = {};
|
|
20
|
+
__export(browserTool_exports, {
|
|
21
|
+
defineBrowserTool: () => defineBrowserTool
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(browserTool_exports);
|
|
24
|
+
function defineBrowserTool(tool) {
|
|
25
|
+
return tool;
|
|
26
|
+
}
|
|
27
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
28
|
+
0 && (module.exports = {
|
|
29
|
+
defineBrowserTool
|
|
30
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var browserTools_exports = {};
|
|
30
|
+
__export(browserTools_exports, {
|
|
31
|
+
elementSchema: () => elementSchema,
|
|
32
|
+
evaluate: () => evaluate,
|
|
33
|
+
pickLocator: () => pickLocator,
|
|
34
|
+
snapshot: () => snapshot
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(browserTools_exports);
|
|
37
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
38
|
+
var import_browserTool = require("./browserTool.js");
|
|
39
|
+
var mcpBundle = __toESM(require("../sdk/bundle"));
|
|
40
|
+
const { z } = mcpBundle;
|
|
41
|
+
const snapshot = (0, import_browserTool.defineBrowserTool)({
|
|
42
|
+
schema: {
|
|
43
|
+
name: "playwright_test_browser_snapshot",
|
|
44
|
+
title: "Capture page snapshot",
|
|
45
|
+
description: "Capture page snapshot for debugging",
|
|
46
|
+
inputSchema: z.object({}),
|
|
47
|
+
type: "readOnly"
|
|
48
|
+
},
|
|
49
|
+
handle: async (page, params) => {
|
|
50
|
+
const snapshot2 = await page._snapshotForAI();
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: snapshot2
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const elementSchema = z.object({
|
|
62
|
+
element: z.string().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
63
|
+
ref: z.string().describe("Exact target element reference from the page snapshot")
|
|
64
|
+
});
|
|
65
|
+
const pickLocator = (0, import_browserTool.defineBrowserTool)({
|
|
66
|
+
schema: {
|
|
67
|
+
name: "playwright_test_generate_locator",
|
|
68
|
+
title: "Create locator for element",
|
|
69
|
+
description: "Generate locator for the given element to use in tests",
|
|
70
|
+
inputSchema: elementSchema,
|
|
71
|
+
type: "readOnly"
|
|
72
|
+
},
|
|
73
|
+
handle: async (page, params) => {
|
|
74
|
+
const locator = await refLocator(page, params);
|
|
75
|
+
try {
|
|
76
|
+
const { resolvedSelector } = await locator._resolveSelector();
|
|
77
|
+
const locatorString = (0, import_utils.asLocator)("javascript", resolvedSelector);
|
|
78
|
+
return { content: [{ type: "text", text: locatorString }] };
|
|
79
|
+
} catch (e) {
|
|
80
|
+
throw new Error(`Ref not found, likely because element was removed. Use ${snapshot.schema.name} to see what elements are currently on the page.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
const evaluateSchema = z.object({
|
|
85
|
+
function: z.string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
|
|
86
|
+
element: z.string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
87
|
+
ref: z.string().optional().describe("Exact target element reference from the page snapshot")
|
|
88
|
+
});
|
|
89
|
+
const evaluate = (0, import_browserTool.defineBrowserTool)({
|
|
90
|
+
schema: {
|
|
91
|
+
name: "playwright_test_evaluate_on_pause",
|
|
92
|
+
title: "Evaluate in page",
|
|
93
|
+
description: "Evaluate JavaScript expression on page or element",
|
|
94
|
+
inputSchema: evaluateSchema,
|
|
95
|
+
type: "destructive"
|
|
96
|
+
},
|
|
97
|
+
handle: async (page, params) => {
|
|
98
|
+
let locator;
|
|
99
|
+
if (params.ref && params.element)
|
|
100
|
+
locator = await refLocator(page, { ref: params.ref, element: params.element });
|
|
101
|
+
const receiver = locator ?? page;
|
|
102
|
+
const result = await receiver._evaluateFunction(params.function);
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) || "undefined" }]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
async function refLocator(page, elementRef) {
|
|
109
|
+
const snapshot2 = await page._snapshotForAI();
|
|
110
|
+
if (!snapshot2.includes(`[ref=${elementRef.ref}]`))
|
|
111
|
+
throw new Error(`Ref ${elementRef.ref} not found in the current page snapshot. Try capturing new snapshot.`);
|
|
112
|
+
return page.locator(`aria-ref=${elementRef.ref}`).describe(elementRef.element);
|
|
113
|
+
}
|
|
114
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
115
|
+
0 && (module.exports = {
|
|
116
|
+
elementSchema,
|
|
117
|
+
evaluate,
|
|
118
|
+
pickLocator,
|
|
119
|
+
snapshot
|
|
120
|
+
});
|