@cotestdev/mcp_playwright 0.0.35 → 0.0.36
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 +12 -12
- package/lib/mcp/browser/browserServerBackend.js +24 -12
- package/lib/mcp/browser/config.js +37 -7
- package/lib/mcp/browser/context.js +13 -61
- package/lib/mcp/browser/response.js +183 -251
- package/lib/mcp/browser/sessionLog.js +19 -104
- package/lib/mcp/browser/tab.js +49 -28
- package/lib/mcp/browser/tools/common.js +4 -4
- package/lib/mcp/browser/tools/console.js +20 -3
- package/lib/mcp/browser/tools/dialogs.js +0 -1
- package/lib/mcp/browser/tools/evaluate.js +6 -4
- package/lib/mcp/browser/tools/install.js +4 -1
- package/lib/mcp/browser/tools/keyboard.js +75 -8
- package/lib/mcp/browser/tools/mouse.js +59 -7
- package/lib/mcp/browser/tools/navigate.js +48 -5
- package/lib/mcp/browser/tools/network.js +21 -3
- package/lib/mcp/browser/tools/pdf.js +4 -3
- package/lib/mcp/browser/tools/runCode.js +6 -10
- package/lib/mcp/browser/tools/screenshot.js +8 -26
- package/lib/mcp/browser/tools/snapshot.js +38 -22
- package/lib/mcp/browser/tools/tabs.js +8 -8
- package/lib/mcp/browser/tools/tool.js +3 -6
- package/lib/mcp/browser/tools/tracing.js +3 -3
- package/lib/mcp/browser/tools/utils.js +2 -2
- package/lib/mcp/browser/tools/verify.js +4 -4
- package/lib/mcp/browser/tools/wait.js +1 -1
- package/lib/mcp/browser/tools.js +2 -2
- package/lib/mcp/extension/extensionContextFactory.js +2 -2
- package/lib/mcp/program.js +3 -2
- package/lib/mcp/terminal/cli.js +4 -216
- package/lib/mcp/terminal/command.js +56 -0
- package/lib/mcp/terminal/commands.js +528 -0
- package/lib/mcp/terminal/daemon.js +42 -25
- package/lib/mcp/terminal/helpGenerator.js +152 -0
- package/lib/mcp/terminal/program.js +434 -0
- package/lib/mcp/terminal/socketConnection.js +2 -4
- package/lib/mcpBundleImpl/index.js +44 -44
- package/lib/util.js +3 -6
- package/package.json +1 -1
|
@@ -56,7 +56,7 @@ function contextFactory(config) {
|
|
|
56
56
|
}
|
|
57
57
|
function identityBrowserContextFactory(browserContext) {
|
|
58
58
|
return {
|
|
59
|
-
createContext: async (clientInfo, abortSignal,
|
|
59
|
+
createContext: async (clientInfo, abortSignal, options) => {
|
|
60
60
|
return {
|
|
61
61
|
browserContext,
|
|
62
62
|
close: async () => {
|
|
@@ -70,11 +70,11 @@ class BaseContextFactory {
|
|
|
70
70
|
this._logName = name;
|
|
71
71
|
this.config = config;
|
|
72
72
|
}
|
|
73
|
-
async _obtainBrowser(clientInfo) {
|
|
73
|
+
async _obtainBrowser(clientInfo, options) {
|
|
74
74
|
if (this._browserPromise)
|
|
75
75
|
return this._browserPromise;
|
|
76
76
|
(0, import_log.testDebug)(`obtain browser (${this._logName})`);
|
|
77
|
-
this._browserPromise = this._doObtainBrowser(clientInfo);
|
|
77
|
+
this._browserPromise = this._doObtainBrowser(clientInfo, options);
|
|
78
78
|
void this._browserPromise.then((browser) => {
|
|
79
79
|
browser.on("disconnected", () => {
|
|
80
80
|
this._browserPromise = void 0;
|
|
@@ -84,12 +84,12 @@ class BaseContextFactory {
|
|
|
84
84
|
});
|
|
85
85
|
return this._browserPromise;
|
|
86
86
|
}
|
|
87
|
-
async _doObtainBrowser(clientInfo) {
|
|
87
|
+
async _doObtainBrowser(clientInfo, options) {
|
|
88
88
|
throw new Error("Not implemented");
|
|
89
89
|
}
|
|
90
|
-
async createContext(clientInfo) {
|
|
90
|
+
async createContext(clientInfo, _, options) {
|
|
91
91
|
(0, import_log.testDebug)(`create browser context (${this._logName})`);
|
|
92
|
-
const browser = await this._obtainBrowser(clientInfo);
|
|
92
|
+
const browser = await this._obtainBrowser(clientInfo, options);
|
|
93
93
|
const browserContext = await this._doCreateContext(browser, clientInfo);
|
|
94
94
|
await addInitScript(browserContext, this.config.browser.initScript);
|
|
95
95
|
return {
|
|
@@ -115,7 +115,7 @@ class IsolatedContextFactory extends BaseContextFactory {
|
|
|
115
115
|
constructor(config) {
|
|
116
116
|
super("isolated", config);
|
|
117
117
|
}
|
|
118
|
-
async _doObtainBrowser(clientInfo) {
|
|
118
|
+
async _doObtainBrowser(clientInfo, options) {
|
|
119
119
|
await injectCdpPort(this.config.browser);
|
|
120
120
|
const browserType = playwright[this.config.browser.browserName];
|
|
121
121
|
const tracesDir = await computeTracesDir(this.config, clientInfo);
|
|
@@ -172,7 +172,7 @@ class PersistentContextFactory {
|
|
|
172
172
|
this._userDataDirs = /* @__PURE__ */ new Set();
|
|
173
173
|
this.config = config;
|
|
174
174
|
}
|
|
175
|
-
async createContext(clientInfo) {
|
|
175
|
+
async createContext(clientInfo, abortSignal, options) {
|
|
176
176
|
await injectCdpPort(this.config.browser);
|
|
177
177
|
(0, import_log.testDebug)("create browser context (persistent)");
|
|
178
178
|
const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo);
|
|
@@ -277,10 +277,10 @@ class SharedContextFactory {
|
|
|
277
277
|
constructor(baseFactory) {
|
|
278
278
|
this._baseFactory = baseFactory;
|
|
279
279
|
}
|
|
280
|
-
async createContext(clientInfo, abortSignal,
|
|
280
|
+
async createContext(clientInfo, abortSignal, options) {
|
|
281
281
|
if (!this._contextPromise) {
|
|
282
282
|
(0, import_log.testDebug)("create shared browser context");
|
|
283
|
-
this._contextPromise = this._baseFactory.createContext(clientInfo, abortSignal,
|
|
283
|
+
this._contextPromise = this._baseFactory.createContext(clientInfo, abortSignal, options);
|
|
284
284
|
}
|
|
285
285
|
const { browserContext } = await this._contextPromise;
|
|
286
286
|
(0, import_log.testDebug)(`shared context client connected`);
|
|
@@ -306,12 +306,12 @@ class SharedContextFactory {
|
|
|
306
306
|
async function computeTracesDir(config, clientInfo) {
|
|
307
307
|
if (!config.saveTrace && !config.capabilities?.includes("tracing"))
|
|
308
308
|
return;
|
|
309
|
-
return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code",
|
|
309
|
+
return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", title: "Collecting trace" });
|
|
310
310
|
}
|
|
311
311
|
async function browserContextOptionsFromConfig(config, clientInfo) {
|
|
312
312
|
const result = { ...config.browser.contextOptions };
|
|
313
313
|
if (config.saveVideo) {
|
|
314
|
-
const dir = await (0, import_config.outputFile)(config, clientInfo, `videos`, { origin: "code",
|
|
314
|
+
const dir = await (0, import_config.outputFile)(config, clientInfo, `videos`, { origin: "code", title: "Saving video" });
|
|
315
315
|
result.recordVideo = {
|
|
316
316
|
dir,
|
|
317
317
|
size: config.saveVideo
|
|
@@ -28,10 +28,11 @@ var import_sessionLog = require("./sessionLog");
|
|
|
28
28
|
var import_tools = require("./tools");
|
|
29
29
|
var import_tool = require("../sdk/tool");
|
|
30
30
|
class BrowserServerBackend {
|
|
31
|
-
constructor(config, factory) {
|
|
31
|
+
constructor(config, factory, options = {}) {
|
|
32
32
|
this._config = config;
|
|
33
33
|
this._browserContextFactory = factory;
|
|
34
|
-
this._tools = (0, import_tools.filteredTools)(config);
|
|
34
|
+
this._tools = options.allTools ? import_tools.browserTools : (0, import_tools.filteredTools)(config);
|
|
35
|
+
this._isStructuredOutput = options.structuredOutput ?? false;
|
|
35
36
|
}
|
|
36
37
|
async initialize(clientInfo) {
|
|
37
38
|
this._sessionLog = this._config.saveSession ? await import_sessionLog.SessionLog.create(this._config, clientInfo) : void 0;
|
|
@@ -47,25 +48,36 @@ class BrowserServerBackend {
|
|
|
47
48
|
}
|
|
48
49
|
async callTool(name, rawArguments) {
|
|
49
50
|
const tool = this._tools.find((tool2) => tool2.schema.name === name);
|
|
50
|
-
if (!tool)
|
|
51
|
-
|
|
51
|
+
if (!tool) {
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: `### Error
|
|
54
|
+
Tool "${name}" not found` }],
|
|
55
|
+
isError: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
52
58
|
const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
|
|
53
59
|
const context = this._context;
|
|
54
|
-
const response =
|
|
55
|
-
response.logBegin();
|
|
60
|
+
const response = import_response.Response.create(context, name, parsedArguments);
|
|
56
61
|
context.setRunningTool(name);
|
|
62
|
+
let responseObject;
|
|
57
63
|
try {
|
|
58
64
|
await tool.handle(context, parsedArguments, response);
|
|
59
|
-
await response.
|
|
60
|
-
this.
|
|
65
|
+
const sections = await response.build();
|
|
66
|
+
if (this._isStructuredOutput)
|
|
67
|
+
responseObject = await (0, import_response.serializeStructuredResponse)(sections);
|
|
68
|
+
else
|
|
69
|
+
responseObject = await (0, import_response.serializeResponse)(context, sections, context.firstRootPath());
|
|
70
|
+
this._sessionLog?.logResponse(name, parsedArguments, responseObject);
|
|
61
71
|
} catch (error) {
|
|
62
|
-
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: "text", text: `### Error
|
|
74
|
+
${String(error)}` }],
|
|
75
|
+
isError: true
|
|
76
|
+
};
|
|
63
77
|
} finally {
|
|
64
78
|
context.setRunningTool(void 0);
|
|
65
79
|
}
|
|
66
|
-
|
|
67
|
-
const _meta = rawArguments?._meta;
|
|
68
|
-
return response.serialize({ _meta });
|
|
80
|
+
return responseObject;
|
|
69
81
|
}
|
|
70
82
|
serverClosed() {
|
|
71
83
|
void this._context?.dispose().catch(import_log.logUnhandledError);
|
|
@@ -55,8 +55,7 @@ const defaultConfig = {
|
|
|
55
55
|
browserName: "chromium",
|
|
56
56
|
launchOptions: {
|
|
57
57
|
channel: "chrome",
|
|
58
|
-
headless: import_os.default.platform() === "linux" && !process.env.DISPLAY
|
|
59
|
-
chromiumSandbox: true
|
|
58
|
+
headless: import_os.default.platform() === "linux" && !process.env.DISPLAY
|
|
60
59
|
},
|
|
61
60
|
contextOptions: {
|
|
62
61
|
viewport: null
|
|
@@ -72,13 +71,30 @@ const defaultConfig = {
|
|
|
72
71
|
server: {},
|
|
73
72
|
saveTrace: false,
|
|
74
73
|
snapshot: {
|
|
75
|
-
mode: "incremental"
|
|
74
|
+
mode: "incremental",
|
|
75
|
+
output: "stdout"
|
|
76
76
|
},
|
|
77
77
|
timeouts: {
|
|
78
78
|
action: 5e3,
|
|
79
79
|
navigation: 6e4
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
|
+
const defaultDaemonConfig = (cliOptions) => mergeConfig(defaultConfig, {
|
|
83
|
+
browser: {
|
|
84
|
+
userDataDir: "<daemon-data-dir>",
|
|
85
|
+
launchOptions: {
|
|
86
|
+
headless: !cliOptions.daemonHeaded
|
|
87
|
+
},
|
|
88
|
+
contextOptions: {
|
|
89
|
+
viewport: cliOptions.daemonHeaded ? null : { width: 1280, height: 720 }
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
outputMode: "file",
|
|
93
|
+
codegen: "none",
|
|
94
|
+
snapshot: {
|
|
95
|
+
mode: "full"
|
|
96
|
+
}
|
|
97
|
+
});
|
|
82
98
|
async function resolveConfig(config) {
|
|
83
99
|
return mergeConfig(defaultConfig, config);
|
|
84
100
|
}
|
|
@@ -86,10 +102,22 @@ async function resolveCLIConfig(cliOptions) {
|
|
|
86
102
|
const configInFile = await loadConfig(cliOptions.config);
|
|
87
103
|
const envOverrides = configFromEnv();
|
|
88
104
|
const cliOverrides = configFromCLIOptions(cliOptions);
|
|
89
|
-
let result = defaultConfig;
|
|
105
|
+
let result = cliOptions.daemon ? defaultDaemonConfig(cliOptions) : defaultConfig;
|
|
90
106
|
result = mergeConfig(result, configInFile);
|
|
91
107
|
result = mergeConfig(result, envOverrides);
|
|
92
108
|
result = mergeConfig(result, cliOverrides);
|
|
109
|
+
if (cliOptions.daemon)
|
|
110
|
+
result.skillMode = true;
|
|
111
|
+
if (result.browser.userDataDir === "<daemon-data-dir>") {
|
|
112
|
+
const browserToken = result.browser.launchOptions?.channel ?? result.browser?.browserName;
|
|
113
|
+
result.browser.userDataDir = `${cliOptions.daemonDataDir}-${browserToken}`;
|
|
114
|
+
}
|
|
115
|
+
if (result.browser.browserName === "chromium" && result.browser.launchOptions.chromiumSandbox === void 0) {
|
|
116
|
+
if (process.platform === "linux")
|
|
117
|
+
result.browser.launchOptions.chromiumSandbox = result.browser.launchOptions.channel !== "chromium";
|
|
118
|
+
else
|
|
119
|
+
result.browser.launchOptions.chromiumSandbox = true;
|
|
120
|
+
}
|
|
93
121
|
await validateConfig(result);
|
|
94
122
|
return result;
|
|
95
123
|
}
|
|
@@ -137,8 +165,8 @@ function configFromCLIOptions(cliOptions) {
|
|
|
137
165
|
executablePath: cliOptions.executablePath,
|
|
138
166
|
headless: cliOptions.headless
|
|
139
167
|
};
|
|
140
|
-
if (cliOptions.sandbox
|
|
141
|
-
launchOptions.chromiumSandbox =
|
|
168
|
+
if (cliOptions.sandbox !== void 0)
|
|
169
|
+
launchOptions.chromiumSandbox = cliOptions.sandbox;
|
|
142
170
|
if (cliOptions.proxyServer) {
|
|
143
171
|
launchOptions.proxy = {
|
|
144
172
|
server: cliOptions.proxyServer
|
|
@@ -187,12 +215,14 @@ function configFromCLIOptions(cliOptions) {
|
|
|
187
215
|
blockedOrigins: cliOptions.blockedOrigins
|
|
188
216
|
},
|
|
189
217
|
allowUnrestrictedFileAccess: cliOptions.allowUnrestrictedFileAccess,
|
|
218
|
+
codegen: cliOptions.codegen,
|
|
190
219
|
saveSession: cliOptions.saveSession,
|
|
191
220
|
saveTrace: cliOptions.saveTrace,
|
|
192
221
|
saveVideo: cliOptions.saveVideo,
|
|
193
222
|
secrets: cliOptions.secrets,
|
|
194
223
|
sharedBrowserContext: cliOptions.sharedBrowserContext,
|
|
195
224
|
snapshot: cliOptions.snapshotMode ? { mode: cliOptions.snapshotMode } : void 0,
|
|
225
|
+
outputMode: cliOptions.outputMode,
|
|
196
226
|
outputDir: cliOptions.outputDir,
|
|
197
227
|
imageResponses: cliOptions.imageResponses,
|
|
198
228
|
testIdAttribute: cliOptions.testIdAttribute,
|
|
@@ -268,7 +298,7 @@ function outputDir(config, clientInfo) {
|
|
|
268
298
|
async function outputFile(config, clientInfo, fileName, options) {
|
|
269
299
|
const file = await resolveFile(config, clientInfo, fileName, options);
|
|
270
300
|
await import_fs.default.promises.mkdir(import_path.default.dirname(file), { recursive: true });
|
|
271
|
-
(0, import_utilsBundle.debug)("pw:mcp:file")(options.
|
|
301
|
+
(0, import_utilsBundle.debug)("pw:mcp:file")(options.title, file);
|
|
272
302
|
return file;
|
|
273
303
|
}
|
|
274
304
|
async function resolveFile(config, clientInfo, fileName, options) {
|
|
@@ -28,8 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var context_exports = {};
|
|
30
30
|
__export(context_exports, {
|
|
31
|
-
Context: () => Context
|
|
32
|
-
InputRecorder: () => InputRecorder
|
|
31
|
+
Context: () => Context
|
|
33
32
|
});
|
|
34
33
|
module.exports = __toCommonJS(context_exports);
|
|
35
34
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
@@ -67,7 +66,7 @@ class Context {
|
|
|
67
66
|
}
|
|
68
67
|
currentTabOrDie() {
|
|
69
68
|
if (!this._currentTab)
|
|
70
|
-
throw new Error(
|
|
69
|
+
throw new Error("No open pages available.");
|
|
71
70
|
return this._currentTab;
|
|
72
71
|
}
|
|
73
72
|
async newTab() {
|
|
@@ -162,12 +161,12 @@ class Context {
|
|
|
162
161
|
return browserContext;
|
|
163
162
|
}
|
|
164
163
|
_ensureBrowserContext() {
|
|
165
|
-
if (
|
|
166
|
-
this._browserContextPromise
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
164
|
+
if (this._browserContextPromise)
|
|
165
|
+
return this._browserContextPromise;
|
|
166
|
+
this._browserContextPromise = this._setupBrowserContext();
|
|
167
|
+
this._browserContextPromise.catch(() => {
|
|
168
|
+
this._browserContextPromise = void 0;
|
|
169
|
+
});
|
|
171
170
|
return this._browserContextPromise;
|
|
172
171
|
}
|
|
173
172
|
async _setupBrowserContext() {
|
|
@@ -175,7 +174,7 @@ class Context {
|
|
|
175
174
|
throw new Error("Another browser context is being closed.");
|
|
176
175
|
if (this.config.testIdAttribute)
|
|
177
176
|
import_playwright_core.selectors.setTestIdAttribute(this.config.testIdAttribute);
|
|
178
|
-
const result = await this._browserContextFactory.createContext(this._clientInfo, this._abortController.signal, this._runningToolName);
|
|
177
|
+
const result = await this._browserContextFactory.createContext(this._clientInfo, this._abortController.signal, { toolName: this._runningToolName });
|
|
179
178
|
const { browserContext } = result;
|
|
180
179
|
if (!this.config.allowUnrestrictedFileAccess) {
|
|
181
180
|
if (typeof browserContext._setAllowedProtocols === "function")
|
|
@@ -184,8 +183,6 @@ class Context {
|
|
|
184
183
|
browserContext._setAllowedDirectories(allRootPaths(this._clientInfo));
|
|
185
184
|
}
|
|
186
185
|
await this._setupRequestInterception(browserContext);
|
|
187
|
-
if (this.sessionLog)
|
|
188
|
-
await InputRecorder.create(this, browserContext);
|
|
189
186
|
for (const page of browserContext.pages())
|
|
190
187
|
this._onPageCreated(page);
|
|
191
188
|
browserContext.on("page", (page) => this._onPageCreated(page));
|
|
@@ -207,6 +204,9 @@ class Context {
|
|
|
207
204
|
code: `process.env['${secretName}']`
|
|
208
205
|
};
|
|
209
206
|
}
|
|
207
|
+
firstRootPath() {
|
|
208
|
+
return allRootPaths(this._clientInfo)[0];
|
|
209
|
+
}
|
|
210
210
|
}
|
|
211
211
|
function allRootPaths(clientInfo) {
|
|
212
212
|
const paths = [];
|
|
@@ -236,55 +236,7 @@ function originOrHostGlob(originOrHost) {
|
|
|
236
236
|
}
|
|
237
237
|
return `*://${originOrHost}/**`;
|
|
238
238
|
}
|
|
239
|
-
class InputRecorder {
|
|
240
|
-
constructor(context, browserContext) {
|
|
241
|
-
this._context = context;
|
|
242
|
-
this._browserContext = browserContext;
|
|
243
|
-
}
|
|
244
|
-
static async create(context, browserContext) {
|
|
245
|
-
const recorder = new InputRecorder(context, browserContext);
|
|
246
|
-
await recorder._initialize();
|
|
247
|
-
return recorder;
|
|
248
|
-
}
|
|
249
|
-
async _initialize() {
|
|
250
|
-
const sessionLog = this._context.sessionLog;
|
|
251
|
-
await this._browserContext._enableRecorder({
|
|
252
|
-
mode: "recording",
|
|
253
|
-
recorderMode: "api"
|
|
254
|
-
}, {
|
|
255
|
-
actionAdded: (page, data, code) => {
|
|
256
|
-
if (this._context.isRunningTool())
|
|
257
|
-
return;
|
|
258
|
-
const tab = import_tab.Tab.forPage(page);
|
|
259
|
-
if (tab)
|
|
260
|
-
sessionLog.logUserAction(data.action, tab, code, false);
|
|
261
|
-
},
|
|
262
|
-
actionUpdated: (page, data, code) => {
|
|
263
|
-
if (this._context.isRunningTool())
|
|
264
|
-
return;
|
|
265
|
-
const tab = import_tab.Tab.forPage(page);
|
|
266
|
-
if (tab)
|
|
267
|
-
sessionLog.logUserAction(data.action, tab, code, true);
|
|
268
|
-
},
|
|
269
|
-
signalAdded: (page, data) => {
|
|
270
|
-
if (this._context.isRunningTool())
|
|
271
|
-
return;
|
|
272
|
-
if (data.signal.name !== "navigation")
|
|
273
|
-
return;
|
|
274
|
-
const tab = import_tab.Tab.forPage(page);
|
|
275
|
-
const navigateAction = {
|
|
276
|
-
name: "navigate",
|
|
277
|
-
url: data.signal.url,
|
|
278
|
-
signals: []
|
|
279
|
-
};
|
|
280
|
-
if (tab)
|
|
281
|
-
sessionLog.logUserAction(navigateAction, tab, `await page.goto('${data.signal.url}');`, false);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
239
|
// Annotate the CommonJS export names for ESM import in node:
|
|
287
240
|
0 && (module.exports = {
|
|
288
|
-
Context
|
|
289
|
-
InputRecorder
|
|
241
|
+
Context
|
|
290
242
|
});
|