@cotestdev/mcp_playwright 0.0.47 → 0.0.49
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/lib/mcp/browser/browserContextFactory.js +1 -1
- package/lib/mcp/browser/browserServerBackend.js +3 -5
- package/lib/mcp/browser/config.js +4 -2
- package/lib/mcp/browser/context.js +1 -0
- package/lib/mcp/browser/response.js +21 -38
- package/lib/mcp/browser/tools/console.js +1 -1
- package/lib/mcp/browser/tools/network.js +1 -1
- package/lib/mcp/browser/tools/pdf.js +1 -1
- package/lib/mcp/browser/tools/runCode.js +3 -1
- package/lib/mcp/browser/tools/screenshot.js +1 -1
- package/lib/mcp/browser/tools/storage.js +68 -0
- package/lib/mcp/browser/tools/tracing.js +2 -2
- package/lib/mcp/browser/tools/video.js +71 -0
- package/lib/mcp/browser/tools.js +7 -3
- package/lib/mcp/extension/cdpRelay.js +3 -0
- package/lib/mcp/program.js +15 -12
- package/lib/mcp/terminal/command.js +13 -0
- package/lib/mcp/terminal/commands.js +32 -12
- package/lib/mcp/terminal/daemon.js +41 -7
- package/lib/mcp/terminal/helpGenerator.js +1 -0
- package/lib/mcp/terminal/program.js +187 -193
- package/lib/mcp/terminal/socketConnection.js +9 -3
- package/package.json +2 -2
|
@@ -304,7 +304,7 @@ class SharedContextFactory {
|
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
async function computeTracesDir(config, clientInfo) {
|
|
307
|
-
if (!config.saveTrace && !config.capabilities?.includes("
|
|
307
|
+
if (!config.saveTrace && !config.capabilities?.includes("devtools"))
|
|
308
308
|
return;
|
|
309
309
|
return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", title: "Collecting trace" });
|
|
310
310
|
}
|
|
@@ -32,7 +32,6 @@ class BrowserServerBackend {
|
|
|
32
32
|
this._config = config;
|
|
33
33
|
this._browserContextFactory = factory;
|
|
34
34
|
this._tools = options.allTools ? import_tools.browserTools : (0, import_tools.filteredTools)(config);
|
|
35
|
-
this._isStructuredOutput = options.structuredOutput ?? false;
|
|
36
35
|
}
|
|
37
36
|
async initialize(clientInfo) {
|
|
38
37
|
this._sessionLog = this._config.saveSession ? await import_sessionLog.SessionLog.create(this._config, clientInfo) : void 0;
|
|
@@ -42,6 +41,7 @@ class BrowserServerBackend {
|
|
|
42
41
|
sessionLog: this._sessionLog,
|
|
43
42
|
clientInfo
|
|
44
43
|
});
|
|
44
|
+
this._context.onBrowserContextClosed = () => this.onBrowserContextClosed?.();
|
|
45
45
|
}
|
|
46
46
|
async listTools() {
|
|
47
47
|
return this._tools.map((tool) => (0, import_tool.toMcpTool)(tool.schema));
|
|
@@ -56,6 +56,7 @@ Tool "${name}" not found` }],
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
|
|
59
|
+
const cwd = rawArguments?._meta && typeof rawArguments?._meta === "object" && rawArguments._meta?.cwd;
|
|
59
60
|
const context = this._context;
|
|
60
61
|
const response = import_response.Response.create(context, name, parsedArguments);
|
|
61
62
|
context.setRunningTool(name);
|
|
@@ -63,10 +64,7 @@ Tool "${name}" not found` }],
|
|
|
63
64
|
try {
|
|
64
65
|
await tool.handle(context, parsedArguments, response);
|
|
65
66
|
const sections = await response.build();
|
|
66
|
-
|
|
67
|
-
responseObject = await (0, import_response.serializeStructuredResponse)(sections);
|
|
68
|
-
else
|
|
69
|
-
responseObject = await (0, import_response.serializeResponse)(context, sections, context.firstRootPath());
|
|
67
|
+
responseObject = await (0, import_response.serializeResponse)(context, sections, cwd ?? context.firstRootPath());
|
|
70
68
|
this._sessionLog?.logResponse(name, parsedArguments, responseObject);
|
|
71
69
|
} catch (error) {
|
|
72
70
|
return {
|
|
@@ -81,7 +81,8 @@ const defaultConfig = {
|
|
|
81
81
|
};
|
|
82
82
|
const defaultDaemonConfig = (cliOptions) => mergeConfig(defaultConfig, {
|
|
83
83
|
browser: {
|
|
84
|
-
userDataDir: "<daemon-data-dir>",
|
|
84
|
+
userDataDir: cliOptions.extension ? void 0 : "<daemon-data-dir>",
|
|
85
|
+
// Use default user profile with extension.
|
|
85
86
|
launchOptions: {
|
|
86
87
|
headless: !cliOptions.daemonHeaded
|
|
87
88
|
},
|
|
@@ -90,7 +91,6 @@ const defaultDaemonConfig = (cliOptions) => mergeConfig(defaultConfig, {
|
|
|
90
91
|
}
|
|
91
92
|
},
|
|
92
93
|
outputMode: "file",
|
|
93
|
-
codegen: "none",
|
|
94
94
|
snapshot: {
|
|
95
95
|
mode: "full"
|
|
96
96
|
}
|
|
@@ -201,6 +201,7 @@ function configFromCLIOptions(cliOptions) {
|
|
|
201
201
|
initPage: cliOptions.initPage,
|
|
202
202
|
initScript: cliOptions.initScript
|
|
203
203
|
},
|
|
204
|
+
extension: cliOptions.extension,
|
|
204
205
|
server: {
|
|
205
206
|
port: cliOptions.port,
|
|
206
207
|
host: cliOptions.host,
|
|
@@ -249,6 +250,7 @@ function configFromEnv() {
|
|
|
249
250
|
options.consoleLevel = enumParser("--console-level", ["error", "warning", "info", "debug"], process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
|
|
250
251
|
options.device = envToString(process.env.PLAYWRIGHT_MCP_DEVICE);
|
|
251
252
|
options.executablePath = envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
|
|
253
|
+
options.extension = envToBoolean(process.env.PLAYWRIGHT_MCP_EXTENSION);
|
|
252
254
|
options.grantPermissions = commaSeparatedList(process.env.PLAYWRIGHT_MCP_GRANT_PERMISSIONS);
|
|
253
255
|
options.headless = envToBoolean(process.env.PLAYWRIGHT_MCP_HEADLESS);
|
|
254
256
|
options.host = envToString(process.env.PLAYWRIGHT_MCP_HOST);
|
|
@@ -186,6 +186,7 @@ class Context {
|
|
|
186
186
|
for (const page of browserContext.pages())
|
|
187
187
|
this._onPageCreated(page);
|
|
188
188
|
browserContext.on("page", (page) => this._onPageCreated(page));
|
|
189
|
+
browserContext.on("close", () => this.onBrowserContextClosed?.());
|
|
189
190
|
if (this.config.saveTrace) {
|
|
190
191
|
await browserContext.tracing.start({
|
|
191
192
|
name: "trace-" + Date.now(),
|
|
@@ -33,8 +33,7 @@ __export(response_exports, {
|
|
|
33
33
|
renderTabMarkdown: () => renderTabMarkdown,
|
|
34
34
|
renderTabsMarkdown: () => renderTabsMarkdown,
|
|
35
35
|
requestDebug: () => requestDebug,
|
|
36
|
-
serializeResponse: () => serializeResponse
|
|
37
|
-
serializeStructuredResponse: () => serializeStructuredResponse
|
|
36
|
+
serializeResponse: () => serializeResponse
|
|
38
37
|
});
|
|
39
38
|
module.exports = __toCommonJS(response_exports);
|
|
40
39
|
var import_fs = __toESM(require("fs"));
|
|
@@ -63,7 +62,7 @@ class Response {
|
|
|
63
62
|
addTextResult(text) {
|
|
64
63
|
this._results.push({ title: "", text });
|
|
65
64
|
}
|
|
66
|
-
|
|
65
|
+
addResult(title, data, file) {
|
|
67
66
|
this._results.push({
|
|
68
67
|
text: typeof data === "string" ? data : void 0,
|
|
69
68
|
data: typeof data === "string" ? void 0 : data,
|
|
@@ -87,8 +86,8 @@ class Response {
|
|
|
87
86
|
async build() {
|
|
88
87
|
const rootPath = this._context.firstRootPath();
|
|
89
88
|
const sections = [];
|
|
90
|
-
const addSection = (title) => {
|
|
91
|
-
const section = { title, content: [], isError: title === "Error" };
|
|
89
|
+
const addSection = (title, codeframe) => {
|
|
90
|
+
const section = { title, content: [], isError: title === "Error", codeframe };
|
|
92
91
|
sections.push(section);
|
|
93
92
|
return section.content;
|
|
94
93
|
};
|
|
@@ -101,7 +100,7 @@ class Response {
|
|
|
101
100
|
content.push(...this._results);
|
|
102
101
|
}
|
|
103
102
|
if (this._context.config.codegen !== "none" && this._code.length) {
|
|
104
|
-
const content = addSection("Ran Playwright code");
|
|
103
|
+
const content = addSection("Ran Playwright code", "js");
|
|
105
104
|
for (const code of this._code)
|
|
106
105
|
content.push({ text: code, title: "code" });
|
|
107
106
|
}
|
|
@@ -120,7 +119,7 @@ class Response {
|
|
|
120
119
|
content.push({ text: (0, import_tab.renderModalStates)(this._context.config, tabSnapshot.modalStates).join("\n"), title: "Modal state" });
|
|
121
120
|
}
|
|
122
121
|
if (tabSnapshot && this._includeSnapshot !== "none") {
|
|
123
|
-
const content = addSection("Snapshot");
|
|
122
|
+
const content = addSection("Snapshot", "yaml");
|
|
124
123
|
const snapshot = this._includeSnapshot === "full" ? tabSnapshot.ariaSnapshot : tabSnapshot.ariaSnapshotDiff ?? tabSnapshot.ariaSnapshot;
|
|
125
124
|
content.push({ text: snapshot, title: "snapshot", file: { prefix: "page", ext: "yml", suggestedFilename: this._includeSnapshotFileName } });
|
|
126
125
|
}
|
|
@@ -177,7 +176,7 @@ function parseSections(text) {
|
|
|
177
176
|
}
|
|
178
177
|
return sections;
|
|
179
178
|
}
|
|
180
|
-
async function serializeResponse(context, sections,
|
|
179
|
+
async function serializeResponse(context, sections, relativeTo) {
|
|
181
180
|
const redactText = (text2) => {
|
|
182
181
|
for (const [secretName, secretValue] of Object.entries(context.config.secrets ?? {}))
|
|
183
182
|
text2 = text2.replaceAll(secretValue, `<secret>${secretName}</secret>`);
|
|
@@ -186,29 +185,29 @@ async function serializeResponse(context, sections, rootPath) {
|
|
|
186
185
|
const text = [];
|
|
187
186
|
for (const section of sections) {
|
|
188
187
|
text.push(`### ${section.title}`);
|
|
188
|
+
const codeframe = [];
|
|
189
189
|
for (const result of section.content) {
|
|
190
|
-
if (
|
|
191
|
-
if (result.text !== void 0)
|
|
192
|
-
text.push(result.text);
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
if (result.file.suggestedFilename || context.config.outputMode === "file" || result.data) {
|
|
190
|
+
if (result.file && (result.file.suggestedFilename || context.config.outputMode === "file" || result.data)) {
|
|
196
191
|
const generatedFileName = await context.outputFile((0, import_utils.dateAsFileName)(result.file.prefix, result.file.ext), { origin: "code", title: section.title });
|
|
197
192
|
const fileName = result.file.suggestedFilename ? await context.outputFile(result.file.suggestedFilename, { origin: "llm", title: section.title }) : generatedFileName;
|
|
198
|
-
text.push(`- [${result.title}](${
|
|
193
|
+
text.push(`- [${result.title}](${relativeTo ? import_path.default.relative(relativeTo, fileName) : fileName})`);
|
|
199
194
|
if (result.data)
|
|
200
195
|
await import_fs.default.promises.writeFile(fileName, result.data, "utf-8");
|
|
201
196
|
else
|
|
202
197
|
await import_fs.default.promises.writeFile(fileName, result.text);
|
|
203
198
|
} else {
|
|
204
|
-
if (result.
|
|
205
|
-
|
|
206
|
-
${result.text}
|
|
207
|
-
\`\`\``);
|
|
208
|
-
else
|
|
209
|
-
text.push(result.text);
|
|
199
|
+
if (result.text !== void 0)
|
|
200
|
+
codeframe.push(result.text);
|
|
210
201
|
}
|
|
211
202
|
}
|
|
203
|
+
if (codeframe?.length) {
|
|
204
|
+
if (section.codeframe)
|
|
205
|
+
text.push(`\`\`\`${section.codeframe}
|
|
206
|
+
${codeframe.join("\n")}
|
|
207
|
+
\`\`\``);
|
|
208
|
+
else
|
|
209
|
+
text.push(codeframe.join("\n"));
|
|
210
|
+
}
|
|
212
211
|
}
|
|
213
212
|
const content = [
|
|
214
213
|
{
|
|
@@ -227,21 +226,6 @@ ${result.text}
|
|
|
227
226
|
...sections.some((section) => section.isError) ? { isError: true } : {}
|
|
228
227
|
};
|
|
229
228
|
}
|
|
230
|
-
async function serializeStructuredResponse(sections) {
|
|
231
|
-
for (const section of sections) {
|
|
232
|
-
for (const result of section.content) {
|
|
233
|
-
if (!result.data)
|
|
234
|
-
continue;
|
|
235
|
-
result.isBase64 = true;
|
|
236
|
-
result.text = result.data.toString("base64");
|
|
237
|
-
result.data = void 0;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
content: [{ type: "text", text: "", _meta: { sections } }],
|
|
242
|
-
isError: sections.some((section) => section.isError)
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
229
|
function parseResponse(response) {
|
|
246
230
|
if (response.content?.[0].type !== "text")
|
|
247
231
|
return void 0;
|
|
@@ -279,6 +263,5 @@ function parseResponse(response) {
|
|
|
279
263
|
renderTabMarkdown,
|
|
280
264
|
renderTabsMarkdown,
|
|
281
265
|
requestDebug,
|
|
282
|
-
serializeResponse
|
|
283
|
-
serializeStructuredResponse
|
|
266
|
+
serializeResponse
|
|
284
267
|
});
|
|
@@ -38,7 +38,7 @@ const console = (0, import_tool.defineTabTool)({
|
|
|
38
38
|
handle: async (tab, params, response) => {
|
|
39
39
|
const messages = await tab.consoleMessages(params.level);
|
|
40
40
|
const text = messages.map((message) => message.toString()).join("\n");
|
|
41
|
-
|
|
41
|
+
response.addResult("Console", text, { prefix: "console", ext: "log", suggestedFilename: params.filename });
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
const consoleClear = (0, import_tool.defineTabTool)({
|
|
@@ -43,7 +43,7 @@ const requests = (0, import_tool.defineTabTool)({
|
|
|
43
43
|
if (rendered)
|
|
44
44
|
text.push(rendered);
|
|
45
45
|
}
|
|
46
|
-
|
|
46
|
+
response.addResult("Network", text.join("\n"), { prefix: "network", ext: "log", suggestedFilename: params.filename });
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
49
|
const networkClear = (0, import_tool.defineTabTool)({
|
|
@@ -40,7 +40,7 @@ const pdf = (0, import_tool.defineTabTool)({
|
|
|
40
40
|
handle: async (tab, params, response) => {
|
|
41
41
|
const data = await tab.page.pdf();
|
|
42
42
|
const suggestedFilename = params.filename ?? (0, import_utils2.dateAsFileName)("page", "pdf");
|
|
43
|
-
|
|
43
|
+
response.addResult("Page as pdf", data, { prefix: "page", ext: "pdf", suggestedFilename });
|
|
44
44
|
response.addCode(`await page.pdf(${(0, import_utils.formatObject)({ path: suggestedFilename })});`);
|
|
45
45
|
}
|
|
46
46
|
});
|
|
@@ -93,7 +93,9 @@ const runScript = (0, import_tool.defineTabTool)({
|
|
|
93
93
|
const runner = import_ai_runner_fake.Runner.NewInstance(params.projectId, params.testId);
|
|
94
94
|
await runner.init(tab.page, tab.page.context(), params.params);
|
|
95
95
|
const result = await runner.runScript(params.testId, params.code);
|
|
96
|
-
|
|
96
|
+
const code = `// Returns the out parameters of the reusable test
|
|
97
|
+
const result = await runner.reuseTest('${params.testId}');`;
|
|
98
|
+
response.addCode(code);
|
|
97
99
|
response.addTextResult(`Out Parameters: ${JSON.stringify(result)}`);
|
|
98
100
|
}
|
|
99
101
|
});
|
|
@@ -65,7 +65,7 @@ const screenshot = (0, import_tool.defineTabTool)({
|
|
|
65
65
|
else
|
|
66
66
|
response.addCode(`await page.screenshot(${(0, import_utils2.formatObject)({ ...options, path: suggestedFilename })});`);
|
|
67
67
|
const contentType = fileType === "png" ? "image/png" : "image/jpeg";
|
|
68
|
-
|
|
68
|
+
response.addResult(`Screenshot of ${screenshotTarget}`, data, { prefix: ref ? "element" : "page", ext: fileType, suggestedFilename, contentType });
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
71
|
function scaleImageToFitMessage(buffer, imageType) {
|
|
@@ -0,0 +1,68 @@
|
|
|
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 storage_exports = {};
|
|
20
|
+
__export(storage_exports, {
|
|
21
|
+
default: () => storage_default
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(storage_exports);
|
|
24
|
+
var import_mcpBundle = require("../../../mcpBundle");
|
|
25
|
+
var import_tool = require("./tool");
|
|
26
|
+
var import_utils = require("./utils");
|
|
27
|
+
const storageState = (0, import_tool.defineTool)({
|
|
28
|
+
capability: "storage",
|
|
29
|
+
schema: {
|
|
30
|
+
name: "browser_storage_state",
|
|
31
|
+
title: "Save storage state",
|
|
32
|
+
description: "Save storage state (cookies, local storage) to a file for later reuse",
|
|
33
|
+
inputSchema: import_mcpBundle.z.object({
|
|
34
|
+
filename: import_mcpBundle.z.string().optional().describe("File name to save the storage state to. Defaults to `storage-state-{timestamp}.json` if not specified.")
|
|
35
|
+
}),
|
|
36
|
+
type: "readOnly"
|
|
37
|
+
},
|
|
38
|
+
handle: async (context, params, response) => {
|
|
39
|
+
const browserContext = await context.ensureBrowserContext();
|
|
40
|
+
const state = await browserContext.storageState();
|
|
41
|
+
const suggestedFilename = params.filename || (0, import_utils.dateAsFileName)("storage-state", "json");
|
|
42
|
+
const serializedState = JSON.stringify(state, null, 2);
|
|
43
|
+
response.addResult("Storage state", serializedState, { prefix: "storage-state", ext: "json", suggestedFilename });
|
|
44
|
+
response.addCode(`await page.context().storageState({ path: '${suggestedFilename}' });`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const setStorageState = (0, import_tool.defineTool)({
|
|
48
|
+
capability: "storage",
|
|
49
|
+
schema: {
|
|
50
|
+
name: "browser_set_storage_state",
|
|
51
|
+
title: "Restore storage state",
|
|
52
|
+
description: "Restore storage state (cookies, local storage) from a file. This clears existing cookies and local storage before restoring.",
|
|
53
|
+
inputSchema: import_mcpBundle.z.object({
|
|
54
|
+
filename: import_mcpBundle.z.string().describe("Path to the storage state file to restore from")
|
|
55
|
+
}),
|
|
56
|
+
type: "action"
|
|
57
|
+
},
|
|
58
|
+
handle: async (context, params, response) => {
|
|
59
|
+
const browserContext = await context.ensureBrowserContext();
|
|
60
|
+
await browserContext.setStorageState(params.filename);
|
|
61
|
+
response.addTextResult(`Storage state restored from ${params.filename}`);
|
|
62
|
+
response.addCode(`await page.context().setStorageState('${params.filename}');`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
var storage_default = [
|
|
66
|
+
storageState,
|
|
67
|
+
setStorageState
|
|
68
|
+
];
|
|
@@ -24,7 +24,7 @@ module.exports = __toCommonJS(tracing_exports);
|
|
|
24
24
|
var import_mcpBundle = require("../../../mcpBundle");
|
|
25
25
|
var import_tool = require("./tool");
|
|
26
26
|
const tracingStart = (0, import_tool.defineTool)({
|
|
27
|
-
capability: "
|
|
27
|
+
capability: "devtools",
|
|
28
28
|
schema: {
|
|
29
29
|
name: "browser_start_tracing",
|
|
30
30
|
title: "Start tracing",
|
|
@@ -51,7 +51,7 @@ ${traceLegend}`);
|
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
53
|
const tracingStop = (0, import_tool.defineTool)({
|
|
54
|
-
capability: "
|
|
54
|
+
capability: "devtools",
|
|
55
55
|
schema: {
|
|
56
56
|
name: "browser_stop_tracing",
|
|
57
57
|
title: "Stop tracing",
|
|
@@ -0,0 +1,71 @@
|
|
|
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 video_exports = {};
|
|
20
|
+
__export(video_exports, {
|
|
21
|
+
default: () => video_default
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(video_exports);
|
|
24
|
+
var import_mcpBundle = require("../../../mcpBundle");
|
|
25
|
+
var import_tool = require("./tool");
|
|
26
|
+
var import_utils = require("./utils");
|
|
27
|
+
const startVideo = (0, import_tool.defineTabTool)({
|
|
28
|
+
capability: "devtools",
|
|
29
|
+
schema: {
|
|
30
|
+
name: "browser_start_video",
|
|
31
|
+
title: "Start video",
|
|
32
|
+
description: "Start video recording",
|
|
33
|
+
inputSchema: import_mcpBundle.z.object({
|
|
34
|
+
size: import_mcpBundle.z.object({
|
|
35
|
+
width: import_mcpBundle.z.number().describe("Video width"),
|
|
36
|
+
height: import_mcpBundle.z.number().describe("Video height")
|
|
37
|
+
}).optional().describe("Video size")
|
|
38
|
+
}),
|
|
39
|
+
type: "readOnly"
|
|
40
|
+
},
|
|
41
|
+
handle: async (tab, params, response) => {
|
|
42
|
+
await tab.page.video().start({ size: params.size });
|
|
43
|
+
response.addTextResult("Video recording started.");
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
const stopVideo = (0, import_tool.defineTabTool)({
|
|
47
|
+
capability: "devtools",
|
|
48
|
+
schema: {
|
|
49
|
+
name: "browser_stop_video",
|
|
50
|
+
title: "Stop video",
|
|
51
|
+
description: "Stop video recording",
|
|
52
|
+
inputSchema: import_mcpBundle.z.object({
|
|
53
|
+
filename: import_mcpBundle.z.string().optional().describe("Filename to save the video")
|
|
54
|
+
}),
|
|
55
|
+
type: "readOnly"
|
|
56
|
+
},
|
|
57
|
+
handle: async (tab, params, response) => {
|
|
58
|
+
let videoPath;
|
|
59
|
+
if (params.filename) {
|
|
60
|
+
const suggestedFilename = params.filename ?? (0, import_utils.dateAsFileName)("video", "webm");
|
|
61
|
+
videoPath = await tab.context.outputFile(suggestedFilename, { origin: "llm", title: "Saving video" });
|
|
62
|
+
}
|
|
63
|
+
await tab.page.video().stop({ path: videoPath });
|
|
64
|
+
const tmpPath = await tab.page.video().path();
|
|
65
|
+
response.addTextResult(`Video recording stopped: ${videoPath ?? tmpPath}`);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
var video_default = [
|
|
69
|
+
startVideo,
|
|
70
|
+
stopVideo
|
|
71
|
+
];
|
package/lib/mcp/browser/tools.js
CHANGED
|
@@ -47,10 +47,12 @@ var import_pdf = __toESM(require("./tools/pdf"));
|
|
|
47
47
|
var import_runCode = __toESM(require("./tools/runCode"));
|
|
48
48
|
var import_snapshot = __toESM(require("./tools/snapshot"));
|
|
49
49
|
var import_screenshot = __toESM(require("./tools/screenshot"));
|
|
50
|
+
var import_storage = __toESM(require("./tools/storage"));
|
|
50
51
|
var import_tabs = __toESM(require("./tools/tabs"));
|
|
51
52
|
var import_tracing = __toESM(require("./tools/tracing"));
|
|
52
|
-
var import_wait = __toESM(require("./tools/wait"));
|
|
53
53
|
var import_verify = __toESM(require("./tools/verify"));
|
|
54
|
+
var import_video = __toESM(require("./tools/video"));
|
|
55
|
+
var import_wait = __toESM(require("./tools/wait"));
|
|
54
56
|
const browserTools = [
|
|
55
57
|
...import_common.default,
|
|
56
58
|
...import_console.default,
|
|
@@ -67,10 +69,12 @@ const browserTools = [
|
|
|
67
69
|
...import_runCode.default,
|
|
68
70
|
...import_screenshot.default,
|
|
69
71
|
...import_snapshot.default,
|
|
72
|
+
...import_storage.default,
|
|
70
73
|
...import_tabs.default,
|
|
71
74
|
...import_tracing.default,
|
|
72
|
-
...
|
|
73
|
-
...
|
|
75
|
+
...import_verify.default,
|
|
76
|
+
...import_video.default,
|
|
77
|
+
...import_wait.default
|
|
74
78
|
];
|
|
75
79
|
function filteredTools(config) {
|
|
76
80
|
return browserTools.filter((tool) => tool.capability.startsWith("core") || config.capabilities?.includes(tool.capability)).filter((tool) => !tool.skillOnly);
|
|
@@ -32,6 +32,7 @@ __export(cdpRelay_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(cdpRelay_exports);
|
|
34
34
|
var import_child_process = require("child_process");
|
|
35
|
+
var import_os = __toESM(require("os"));
|
|
35
36
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
36
37
|
var import_registry = require("playwright-core/lib/server/registry/index");
|
|
37
38
|
var import_utils = require("playwright-core/lib/utils");
|
|
@@ -104,6 +105,8 @@ class CDPRelayServer {
|
|
|
104
105
|
const args = [];
|
|
105
106
|
if (this._userDataDir)
|
|
106
107
|
args.push(`--user-data-dir=${this._userDataDir}`);
|
|
108
|
+
if (import_os.default.platform() === "linux" && this._browserChannel === "chromium")
|
|
109
|
+
args.push("--no-sandbox");
|
|
107
110
|
args.push(href);
|
|
108
111
|
(0, import_child_process.spawn)(executablePath, args, {
|
|
109
112
|
windowsHide: true,
|
package/lib/mcp/program.js
CHANGED
|
@@ -42,13 +42,15 @@ var import_browserContextFactory = require("./browser/browserContextFactory");
|
|
|
42
42
|
var import_browserServerBackend = require("./browser/browserServerBackend");
|
|
43
43
|
var import_extensionContextFactory = require("./extension/extensionContextFactory");
|
|
44
44
|
function decorateCommand(command, version) {
|
|
45
|
-
command.option("--allowed-hosts <hosts...>", "comma-separated list of hosts this server is allowed to serve from. Defaults to the host the server is bound to. Pass '*' to disable the host check.", import_config.commaSeparatedList).option("--allowed-origins <origins>", "semicolon-separated list of TRUSTED origins to allow the browser to request. Default is to allow all.\nImportant: *does not* serve as a security boundary and *does not* affect redirects. ", import_config.semicolonSeparatedList).option("--allow-unrestricted-file-access", "allow access to files outside of the workspace roots. Also allows unrestricted access to file:// URLs. By default access to file system is restricted to workspace root directories (or cwd if no roots are configured) only, and navigation to file:// URLs is blocked.").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.\nImportant: *does not* serve as a security boundary and *does not* affect redirects.", 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("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--codegen <lang>", 'specify the language to use for code generation, possible values: "typescript", "none". Default is "typescript".', import_config.enumParser.bind(null, "--codegen", ["none", "typescript"])).option("--config <path>", "path to the configuration file.").option("--console-level <level>", 'level of console messages to return: "error", "warning", "info", "debug". Each level includes the messages of more severe levels.', import_config.enumParser.bind(null, "--console-level", ["error", "warning", "info", "debug"])).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("--grant-permissions <permissions...>", 'List of permissions to grant to the browser context, for example "geolocation", "clipboard-read", "clipboard-write".', import_config.commaSeparatedList).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("--init-page <path...>", "path to TypeScript file to evaluate on Playwright page object").option("--init-script <path...>", "path to JavaScript file to add as an initialization script. The script will be evaluated in every page before any of the page's scripts. Can be specified multiple times.").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".', import_config.enumParser.bind(null, "--image-responses", ["allow", "omit"])).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("--output-mode <mode>", 'whether to save snapshots, console messages, network logs to a file or to the standard output. Can be "file" or "stdout". Default is "stdout".', import_config.enumParser.bind(null, "--output-mode", ["file", "stdout"])).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("--sandbox", "enable the sandbox for all process types that are normally not sandboxed.").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("--save-video <size>", 'Whether to save the video of the session into the output directory. For example "--save-video=800x600"', import_config.resolutionParser.bind(null, "--save-video")).option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--shared-browser-context", "reuse the same browser context between all connected HTTP clients.").option("--snapshot-mode <mode>", 'when taking snapshots for responses, specifies the mode to use. Can be "incremental", "full", or "none". Default is incremental.').option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--test-id-attribute <attribute>", 'specify the attribute to use for test ids, defaults to "data-testid"').option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).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 "1280x720"', import_config.resolutionParser.bind(null, "--viewport-size")).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon <socket>", "run as daemon").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-data-dir <path>", "path to the daemon data directory.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-headed", "run daemon in headed mode").hideHelp()).action(async (options) => {
|
|
45
|
+
command.option("--allowed-hosts <hosts...>", "comma-separated list of hosts this server is allowed to serve from. Defaults to the host the server is bound to. Pass '*' to disable the host check.", import_config.commaSeparatedList).option("--allowed-origins <origins>", "semicolon-separated list of TRUSTED origins to allow the browser to request. Default is to allow all.\nImportant: *does not* serve as a security boundary and *does not* affect redirects. ", import_config.semicolonSeparatedList).option("--allow-unrestricted-file-access", "allow access to files outside of the workspace roots. Also allows unrestricted access to file:// URLs. By default access to file system is restricted to workspace root directories (or cwd if no roots are configured) only, and navigation to file:// URLs is blocked.").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.\nImportant: *does not* serve as a security boundary and *does not* affect redirects.", 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, devtools.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--codegen <lang>", 'specify the language to use for code generation, possible values: "typescript", "none". Default is "typescript".', import_config.enumParser.bind(null, "--codegen", ["none", "typescript"])).option("--config <path>", "path to the configuration file.").option("--console-level <level>", 'level of console messages to return: "error", "warning", "info", "debug". Each level includes the messages of more severe levels.', import_config.enumParser.bind(null, "--console-level", ["error", "warning", "info", "debug"])).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("--grant-permissions <permissions...>", 'List of permissions to grant to the browser context, for example "geolocation", "clipboard-read", "clipboard-write".', import_config.commaSeparatedList).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("--init-page <path...>", "path to TypeScript file to evaluate on Playwright page object").option("--init-script <path...>", "path to JavaScript file to add as an initialization script. The script will be evaluated in every page before any of the page's scripts. Can be specified multiple times.").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".', import_config.enumParser.bind(null, "--image-responses", ["allow", "omit"])).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("--output-mode <mode>", 'whether to save snapshots, console messages, network logs to a file or to the standard output. Can be "file" or "stdout". Default is "stdout".', import_config.enumParser.bind(null, "--output-mode", ["file", "stdout"])).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("--sandbox", "enable the sandbox for all process types that are normally not sandboxed.").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("--save-video <size>", 'Whether to save the video of the session into the output directory. For example "--save-video=800x600"', import_config.resolutionParser.bind(null, "--save-video")).option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--shared-browser-context", "reuse the same browser context between all connected HTTP clients.").option("--snapshot-mode <mode>", 'when taking snapshots for responses, specifies the mode to use. Can be "incremental", "full", or "none". Default is incremental.').option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--test-id-attribute <attribute>", 'specify the attribute to use for test ids, defaults to "data-testid"').option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).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 "1280x720"', import_config.resolutionParser.bind(null, "--viewport-size")).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon <socket>", "run as daemon").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-data-dir <path>", "path to the daemon data directory.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-headed", "run daemon in headed mode").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-version <version>", "version of this daemon").hideHelp()).action(async (options) => {
|
|
46
46
|
options.sandbox = options.sandbox === true ? void 0 : false;
|
|
47
47
|
(0, import_watchdog.setupExitWatchdog)();
|
|
48
48
|
if (options.vision) {
|
|
49
49
|
console.error("The --vision option is deprecated, use --caps=vision instead");
|
|
50
50
|
options.caps = "vision";
|
|
51
51
|
}
|
|
52
|
+
if (options.caps?.includes("tracing"))
|
|
53
|
+
options.caps.push("devtools");
|
|
52
54
|
const config = await (0, import_config.resolveCLIConfig)(options);
|
|
53
55
|
if (config.saveVideo && !checkFfmpeg()) {
|
|
54
56
|
console.error(import_utilsBundle.colors.red(`
|
|
@@ -62,25 +64,26 @@ Please run the command below. It will install a local copy of ffmpeg and will no
|
|
|
62
64
|
}
|
|
63
65
|
const browserContextFactory = (0, import_browserContextFactory.contextFactory)(config);
|
|
64
66
|
const extensionContextFactory = new import_extensionContextFactory.ExtensionContextFactory(config.browser.launchOptions.channel || "chrome", config.browser.userDataDir, config.browser.launchOptions.executablePath);
|
|
65
|
-
if (options.
|
|
67
|
+
if (options.daemon) {
|
|
68
|
+
const contextFactory2 = config.extension ? extensionContextFactory : browserContextFactory;
|
|
66
69
|
const serverBackendFactory = {
|
|
67
|
-
name: "Playwright
|
|
68
|
-
nameInConfig: "playwright-
|
|
70
|
+
name: "Playwright",
|
|
71
|
+
nameInConfig: "playwright-daemon",
|
|
69
72
|
version,
|
|
70
|
-
create: () => new import_browserServerBackend.BrowserServerBackend(config,
|
|
73
|
+
create: () => new import_browserServerBackend.BrowserServerBackend(config, contextFactory2, { allTools: true })
|
|
71
74
|
};
|
|
72
|
-
await
|
|
75
|
+
const socketPath = await (0, import_daemon.startMcpDaemonServer)(options.daemon, serverBackendFactory, options.daemonVersion);
|
|
76
|
+
console.error(`Daemon server listening on ${socketPath}`);
|
|
73
77
|
return;
|
|
74
78
|
}
|
|
75
|
-
if (
|
|
79
|
+
if (config.extension) {
|
|
76
80
|
const serverBackendFactory = {
|
|
77
|
-
name: "Playwright",
|
|
78
|
-
nameInConfig: "playwright-
|
|
81
|
+
name: "Playwright w/ extension",
|
|
82
|
+
nameInConfig: "playwright-extension",
|
|
79
83
|
version,
|
|
80
|
-
create: () => new import_browserServerBackend.BrowserServerBackend(config,
|
|
84
|
+
create: () => new import_browserServerBackend.BrowserServerBackend(config, extensionContextFactory)
|
|
81
85
|
};
|
|
82
|
-
|
|
83
|
-
console.error(`Daemon server listening on ${socketPath}`);
|
|
86
|
+
await mcpServer.start(serverBackendFactory, config.server);
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
86
89
|
const factory = {
|
|
@@ -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 command_exports = {};
|
|
20
30
|
__export(command_exports, {
|
|
@@ -22,10 +32,13 @@ __export(command_exports, {
|
|
|
22
32
|
parseCommand: () => parseCommand
|
|
23
33
|
});
|
|
24
34
|
module.exports = __toCommonJS(command_exports);
|
|
35
|
+
var import_path = __toESM(require("path"));
|
|
25
36
|
function declareCommand(command) {
|
|
26
37
|
return command;
|
|
27
38
|
}
|
|
28
39
|
function parseCommand(command, args) {
|
|
40
|
+
if (args.filename)
|
|
41
|
+
args.filename = import_path.default.resolve(args.outputDir, args.filename);
|
|
29
42
|
const shape = command.args ? command.args.shape : {};
|
|
30
43
|
const argv = args["_"];
|
|
31
44
|
const options = command.options?.parse({ ...args, _: void 0 }) ?? {};
|