@cotestdev/mcp_playwright 0.0.12 → 0.0.15
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 +49 -13
- package/lib/mcp/browser/browserServerBackend.js +5 -2
- package/lib/mcp/browser/config.js +95 -23
- package/lib/mcp/browser/context.js +28 -3
- package/lib/mcp/browser/response.js +240 -57
- package/lib/mcp/browser/sessionLog.js +1 -1
- package/lib/mcp/browser/tab.js +96 -69
- package/lib/mcp/browser/tools/common.js +8 -8
- package/lib/mcp/browser/tools/console.js +6 -3
- package/lib/mcp/browser/tools/dialogs.js +13 -13
- package/lib/mcp/browser/tools/evaluate.js +9 -20
- package/lib/mcp/browser/tools/files.js +10 -5
- package/lib/mcp/browser/tools/form.js +11 -22
- package/lib/mcp/browser/tools/install.js +3 -3
- package/lib/mcp/browser/tools/keyboard.js +12 -12
- package/lib/mcp/browser/tools/mouse.js +14 -14
- package/lib/mcp/browser/tools/navigate.js +5 -5
- package/lib/mcp/browser/tools/network.js +16 -5
- package/lib/mcp/browser/tools/pdf.js +7 -18
- package/lib/mcp/browser/tools/runCode.js +77 -0
- package/lib/mcp/browser/tools/screenshot.js +44 -33
- package/lib/mcp/browser/tools/snapshot.js +42 -33
- package/lib/mcp/browser/tools/tabs.js +7 -10
- package/lib/mcp/browser/tools/tool.js +8 -7
- package/lib/mcp/browser/tools/tracing.js +4 -4
- package/lib/mcp/browser/tools/utils.js +50 -52
- package/lib/mcp/browser/tools/verify.js +23 -34
- package/lib/mcp/browser/tools/wait.js +6 -6
- package/lib/mcp/browser/tools.js +4 -3
- package/lib/mcp/cli.js +17 -17
- package/lib/mcp/extension/cdpRelay.js +1 -1
- package/lib/mcp/extension/extensionContextFactory.js +4 -3
- package/lib/mcp/log.js +2 -2
- package/lib/mcp/program.js +21 -29
- package/lib/mcp/sdk/exports.js +1 -5
- package/lib/mcp/sdk/http.js +37 -50
- package/lib/mcp/sdk/server.js +61 -9
- package/lib/mcp/sdk/tool.js +5 -4
- package/lib/mcp/test/browserBackend.js +67 -61
- package/lib/mcp/test/generatorTools.js +122 -0
- package/lib/mcp/test/plannerTools.js +144 -0
- package/lib/mcp/test/seed.js +82 -0
- package/lib/mcp/test/streams.js +10 -7
- package/lib/mcp/test/testBackend.js +44 -24
- package/lib/mcp/test/testContext.js +243 -14
- package/lib/mcp/test/testTools.js +23 -109
- package/lib/util.js +12 -6
- package/package.json +1 -1
|
@@ -18,17 +18,24 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var response_exports = {};
|
|
20
20
|
__export(response_exports, {
|
|
21
|
-
|
|
21
|
+
RenderedResponse: () => RenderedResponse,
|
|
22
|
+
Response: () => Response,
|
|
23
|
+
parseResponse: () => parseResponse,
|
|
24
|
+
requestDebug: () => requestDebug
|
|
22
25
|
});
|
|
23
26
|
module.exports = __toCommonJS(response_exports);
|
|
27
|
+
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
24
28
|
var import_tab = require("./tab");
|
|
29
|
+
const requestDebug = (0, import_utilsBundle.debug)("pw:mcp:request");
|
|
25
30
|
class Response {
|
|
26
31
|
constructor(context, toolName, toolArgs) {
|
|
27
32
|
this._result = [];
|
|
28
33
|
this._code = [];
|
|
29
34
|
this._images = [];
|
|
35
|
+
this._files = [];
|
|
30
36
|
this._includeSnapshot = "none";
|
|
31
37
|
this._includeTabs = false;
|
|
38
|
+
this._includeMetaOnly = false;
|
|
32
39
|
this._context = context;
|
|
33
40
|
this.toolName = toolName;
|
|
34
41
|
this.toolArgs = toolArgs;
|
|
@@ -58,13 +65,29 @@ class Response {
|
|
|
58
65
|
images() {
|
|
59
66
|
return this._images;
|
|
60
67
|
}
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
async addFile(fileName, options) {
|
|
69
|
+
const resolvedFile = await this._context.outputFile(fileName, options);
|
|
70
|
+
this._files.push({ fileName: resolvedFile, title: options.reason });
|
|
71
|
+
return resolvedFile;
|
|
72
|
+
}
|
|
73
|
+
setIncludeSnapshot() {
|
|
74
|
+
this._includeSnapshot = this._context.config.snapshot.mode;
|
|
75
|
+
}
|
|
76
|
+
setIncludeFullSnapshot() {
|
|
77
|
+
this._includeSnapshot = "full";
|
|
63
78
|
}
|
|
64
79
|
setIncludeTabs() {
|
|
65
80
|
this._includeTabs = true;
|
|
66
81
|
}
|
|
82
|
+
setIncludeModalStates(modalStates) {
|
|
83
|
+
this._includeModalStates = modalStates;
|
|
84
|
+
}
|
|
85
|
+
setIncludeMetaOnly() {
|
|
86
|
+
this._includeMetaOnly = true;
|
|
87
|
+
}
|
|
67
88
|
async finish() {
|
|
89
|
+
if (this._tabSnapshot)
|
|
90
|
+
return;
|
|
68
91
|
if (this._includeSnapshot !== "none" && this._context.currentTab())
|
|
69
92
|
this._tabSnapshot = await this._context.currentTabOrDie().captureSnapshot();
|
|
70
93
|
for (const tab of this._context.tabs())
|
|
@@ -73,89 +96,110 @@ class Response {
|
|
|
73
96
|
tabSnapshot() {
|
|
74
97
|
return this._tabSnapshot;
|
|
75
98
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
logBegin() {
|
|
100
|
+
if (requestDebug.enabled)
|
|
101
|
+
requestDebug(this.toolName, this.toolArgs);
|
|
102
|
+
}
|
|
103
|
+
logEnd() {
|
|
104
|
+
if (requestDebug.enabled)
|
|
105
|
+
requestDebug(this.serialize());
|
|
106
|
+
}
|
|
107
|
+
render() {
|
|
108
|
+
const renderedResponse = new RenderedResponse();
|
|
109
|
+
if (this._result.length)
|
|
110
|
+
renderedResponse.results.push(...this._result);
|
|
111
|
+
if (this._code.length)
|
|
112
|
+
renderedResponse.code.push(...this._code);
|
|
113
|
+
if (this._includeSnapshot !== "none" || this._includeTabs) {
|
|
114
|
+
const tabsMarkdown = renderTabsMarkdown(this._context.tabs(), this._includeTabs);
|
|
115
|
+
if (tabsMarkdown.length)
|
|
116
|
+
renderedResponse.states.tabs = tabsMarkdown.join("\n");
|
|
89
117
|
}
|
|
90
|
-
if (this._includeSnapshot !== "none" || this._includeTabs)
|
|
91
|
-
response.push(...renderTabsMarkdown(this._context.tabs(), this._includeTabs));
|
|
92
118
|
if (this._tabSnapshot?.modalStates.length) {
|
|
93
|
-
|
|
94
|
-
|
|
119
|
+
const modalStatesMarkdown = (0, import_tab.renderModalStates)(this._tabSnapshot.modalStates);
|
|
120
|
+
renderedResponse.states.modal = modalStatesMarkdown.join("\n");
|
|
95
121
|
} else if (this._tabSnapshot) {
|
|
96
|
-
|
|
97
|
-
|
|
122
|
+
renderTabSnapshot(this._tabSnapshot, this._includeSnapshot, renderedResponse);
|
|
123
|
+
} else if (this._includeModalStates) {
|
|
124
|
+
const modalStatesMarkdown = (0, import_tab.renderModalStates)(this._includeModalStates);
|
|
125
|
+
renderedResponse.states.modal = modalStatesMarkdown.join("\n");
|
|
126
|
+
}
|
|
127
|
+
if (this._files.length) {
|
|
128
|
+
const lines = [];
|
|
129
|
+
for (const file of this._files)
|
|
130
|
+
lines.push(`- [${file.title}](${file.fileName})`);
|
|
131
|
+
renderedResponse.updates.push({ category: "files", content: lines.join("\n") });
|
|
98
132
|
}
|
|
133
|
+
return this._context.config.secrets ? renderedResponse.redact(this._context.config.secrets) : renderedResponse;
|
|
134
|
+
}
|
|
135
|
+
serialize(options = {}) {
|
|
136
|
+
const renderedResponse = this.render();
|
|
137
|
+
const includeMeta = options._meta && "dev.lowire/history" in options._meta && "dev.lowire/state" in options._meta;
|
|
138
|
+
const _meta = includeMeta ? renderedResponse.asMeta() : void 0;
|
|
99
139
|
const content = [
|
|
100
|
-
{
|
|
140
|
+
{
|
|
141
|
+
type: "text",
|
|
142
|
+
text: renderedResponse.asText(this._includeMetaOnly ? { categories: ["files"] } : void 0)
|
|
143
|
+
}
|
|
101
144
|
];
|
|
102
|
-
this.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return;
|
|
108
|
-
for (const item of content) {
|
|
109
|
-
if (item.type !== "text")
|
|
110
|
-
continue;
|
|
111
|
-
for (const [secretName, secretValue] of Object.entries(this._context.config.secrets))
|
|
112
|
-
item.text = item.text.replaceAll(secretValue, `<secret>${secretName}</secret>`);
|
|
145
|
+
if (this._includeMetaOnly)
|
|
146
|
+
return { _meta, content, isError: this._isError };
|
|
147
|
+
if (this._context.config.imageResponses !== "omit") {
|
|
148
|
+
for (const image of this._images)
|
|
149
|
+
content.push({ type: "image", data: image.data.toString("base64"), mimeType: image.contentType });
|
|
113
150
|
}
|
|
151
|
+
return {
|
|
152
|
+
_meta,
|
|
153
|
+
content,
|
|
154
|
+
isError: this._isError
|
|
155
|
+
};
|
|
114
156
|
}
|
|
115
157
|
}
|
|
116
|
-
function renderTabSnapshot(tabSnapshot,
|
|
117
|
-
|
|
158
|
+
function renderTabSnapshot(tabSnapshot, includeSnapshot, response) {
|
|
159
|
+
if (tabSnapshot.consoleMessages.length) {
|
|
160
|
+
const lines2 = [];
|
|
161
|
+
for (const message of tabSnapshot.consoleMessages)
|
|
162
|
+
lines2.push(`- ${trim(message.toString(), 100)}`);
|
|
163
|
+
response.updates.push({ category: "console", content: lines2.join("\n") });
|
|
164
|
+
}
|
|
118
165
|
if (tabSnapshot.downloads.length) {
|
|
119
|
-
|
|
166
|
+
const lines2 = [];
|
|
120
167
|
for (const entry of tabSnapshot.downloads) {
|
|
121
168
|
if (entry.finished)
|
|
122
|
-
|
|
169
|
+
lines2.push(`- Downloaded file ${entry.download.suggestedFilename()} to ${entry.outputFile}`);
|
|
123
170
|
else
|
|
124
|
-
|
|
171
|
+
lines2.push(`- Downloading file ${entry.download.suggestedFilename()} ...`);
|
|
125
172
|
}
|
|
126
|
-
|
|
173
|
+
response.updates.push({ category: "downloads", content: lines2.join("\n") });
|
|
174
|
+
}
|
|
175
|
+
if (includeSnapshot === "incremental" && tabSnapshot.ariaSnapshotDiff === "") {
|
|
176
|
+
return;
|
|
127
177
|
}
|
|
128
|
-
lines
|
|
178
|
+
const lines = [];
|
|
129
179
|
lines.push(`- Page URL: ${tabSnapshot.url}`);
|
|
130
180
|
lines.push(`- Page Title: ${tabSnapshot.title}`);
|
|
131
|
-
if (
|
|
132
|
-
lines.push(`- Page Snapshot Diff:`);
|
|
133
|
-
lines.push(tabSnapshot.formattedAriaSnapshotDiff);
|
|
134
|
-
} else {
|
|
181
|
+
if (includeSnapshot !== "none") {
|
|
135
182
|
lines.push(`- Page Snapshot:`);
|
|
136
183
|
lines.push("```yaml");
|
|
137
|
-
|
|
184
|
+
if (includeSnapshot === "incremental" && tabSnapshot.ariaSnapshotDiff !== void 0)
|
|
185
|
+
lines.push(tabSnapshot.ariaSnapshotDiff);
|
|
186
|
+
else
|
|
187
|
+
lines.push(tabSnapshot.ariaSnapshot);
|
|
138
188
|
lines.push("```");
|
|
139
189
|
}
|
|
140
|
-
|
|
190
|
+
response.states.page = lines.join("\n");
|
|
141
191
|
}
|
|
142
192
|
function renderTabsMarkdown(tabs, force = false) {
|
|
143
193
|
if (tabs.length === 1 && !force)
|
|
144
194
|
return [];
|
|
145
|
-
if (!tabs.length)
|
|
146
|
-
return [
|
|
147
|
-
|
|
148
|
-
'No open tabs. Use the "browser_navigate" tool to navigate to a page first.',
|
|
149
|
-
""
|
|
150
|
-
];
|
|
151
|
-
}
|
|
152
|
-
const lines = ["### Open tabs"];
|
|
195
|
+
if (!tabs.length)
|
|
196
|
+
return ['No open tabs. Use the "browser_navigate" tool to navigate to a page first.'];
|
|
197
|
+
const lines = [];
|
|
153
198
|
for (let i = 0; i < tabs.length; i++) {
|
|
154
199
|
const tab = tabs[i];
|
|
155
200
|
const current = tab.isCurrentTab() ? " (current)" : "";
|
|
156
201
|
lines.push(`- ${i}:${current} [${tab.lastTitle()}] (${tab.page.url()})`);
|
|
157
202
|
}
|
|
158
|
-
lines.push("");
|
|
159
203
|
return lines;
|
|
160
204
|
}
|
|
161
205
|
function trim(text, maxLength) {
|
|
@@ -163,7 +207,146 @@ function trim(text, maxLength) {
|
|
|
163
207
|
return text;
|
|
164
208
|
return text.slice(0, maxLength) + "...";
|
|
165
209
|
}
|
|
210
|
+
class RenderedResponse {
|
|
211
|
+
constructor(copy) {
|
|
212
|
+
this.states = {};
|
|
213
|
+
this.updates = [];
|
|
214
|
+
this.results = [];
|
|
215
|
+
this.code = [];
|
|
216
|
+
if (copy) {
|
|
217
|
+
this.states = copy.states;
|
|
218
|
+
this.updates = copy.updates;
|
|
219
|
+
this.results = copy.results;
|
|
220
|
+
this.code = copy.code;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
asText(filter) {
|
|
224
|
+
const text = [];
|
|
225
|
+
if (this.results.length)
|
|
226
|
+
text.push(`### Result
|
|
227
|
+
${this.results.join("\n")}
|
|
228
|
+
`);
|
|
229
|
+
if (this.code.length)
|
|
230
|
+
text.push(`### Ran Playwright code
|
|
231
|
+
${this.code.join("\n")}
|
|
232
|
+
`);
|
|
233
|
+
for (const { category, content } of this.updates) {
|
|
234
|
+
if (filter && !filter.categories.includes(category))
|
|
235
|
+
continue;
|
|
236
|
+
if (!content.trim())
|
|
237
|
+
continue;
|
|
238
|
+
switch (category) {
|
|
239
|
+
case "console":
|
|
240
|
+
text.push(`### New console messages
|
|
241
|
+
${content}
|
|
242
|
+
`);
|
|
243
|
+
break;
|
|
244
|
+
case "downloads":
|
|
245
|
+
text.push(`### Downloads
|
|
246
|
+
${content}
|
|
247
|
+
`);
|
|
248
|
+
break;
|
|
249
|
+
case "files":
|
|
250
|
+
text.push(`### Files
|
|
251
|
+
${content}
|
|
252
|
+
`);
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
for (const [category, value] of Object.entries(this.states)) {
|
|
257
|
+
if (filter && !filter.categories.includes(category))
|
|
258
|
+
continue;
|
|
259
|
+
if (!value.trim())
|
|
260
|
+
continue;
|
|
261
|
+
switch (category) {
|
|
262
|
+
case "page":
|
|
263
|
+
text.push(`### Page state
|
|
264
|
+
${value}
|
|
265
|
+
`);
|
|
266
|
+
break;
|
|
267
|
+
case "tabs":
|
|
268
|
+
text.push(`### Open tabs
|
|
269
|
+
${value}
|
|
270
|
+
`);
|
|
271
|
+
break;
|
|
272
|
+
case "modal":
|
|
273
|
+
text.push(`### Modal state
|
|
274
|
+
${value}
|
|
275
|
+
`);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return text.join("\n");
|
|
280
|
+
}
|
|
281
|
+
asMeta() {
|
|
282
|
+
const codeUpdate = this.code.length ? { category: "code", content: this.code.join("\n") } : void 0;
|
|
283
|
+
const resultUpdate = this.results.length ? { category: "result", content: this.results.join("\n") } : void 0;
|
|
284
|
+
const updates = [resultUpdate, codeUpdate, ...this.updates].filter(Boolean);
|
|
285
|
+
return {
|
|
286
|
+
"dev.lowire/history": updates,
|
|
287
|
+
"dev.lowire/state": { ...this.states }
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
redact(secrets) {
|
|
291
|
+
const redactText = (text) => {
|
|
292
|
+
for (const [secretName, secretValue] of Object.entries(secrets))
|
|
293
|
+
text = text.replaceAll(secretValue, `<secret>${secretName}</secret>`);
|
|
294
|
+
return text;
|
|
295
|
+
};
|
|
296
|
+
const updates = this.updates.map((update) => ({ ...update, content: redactText(update.content) }));
|
|
297
|
+
const results = this.results.map((result) => redactText(result));
|
|
298
|
+
const code = this.code.map((code2) => redactText(code2));
|
|
299
|
+
const states = Object.fromEntries(Object.entries(this.states).map(([key, value]) => [key, redactText(value)]));
|
|
300
|
+
return new RenderedResponse({ states, updates, results, code });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function parseSections(text) {
|
|
304
|
+
const sections = /* @__PURE__ */ new Map();
|
|
305
|
+
const sectionHeaders = text.split(/^### /m).slice(1);
|
|
306
|
+
for (const section of sectionHeaders) {
|
|
307
|
+
const firstNewlineIndex = section.indexOf("\n");
|
|
308
|
+
if (firstNewlineIndex === -1)
|
|
309
|
+
continue;
|
|
310
|
+
const sectionName = section.substring(0, firstNewlineIndex);
|
|
311
|
+
const sectionContent = section.substring(firstNewlineIndex + 1).trim();
|
|
312
|
+
sections.set(sectionName, sectionContent);
|
|
313
|
+
}
|
|
314
|
+
return sections;
|
|
315
|
+
}
|
|
316
|
+
function parseResponse(response) {
|
|
317
|
+
if (response.content?.[0].type !== "text")
|
|
318
|
+
return void 0;
|
|
319
|
+
const text = response.content[0].text;
|
|
320
|
+
const sections = parseSections(text);
|
|
321
|
+
const result = sections.get("Result");
|
|
322
|
+
const code = sections.get("Ran Playwright code");
|
|
323
|
+
const tabs = sections.get("Open tabs");
|
|
324
|
+
const pageState = sections.get("Page state");
|
|
325
|
+
const consoleMessages = sections.get("New console messages");
|
|
326
|
+
const modalState = sections.get("Modal state");
|
|
327
|
+
const downloads = sections.get("Downloads");
|
|
328
|
+
const files = sections.get("Files");
|
|
329
|
+
const codeNoFrame = code?.replace(/^```js\n/, "").replace(/\n```$/, "");
|
|
330
|
+
const isError = response.isError;
|
|
331
|
+
const attachments = response.content.slice(1);
|
|
332
|
+
return {
|
|
333
|
+
result,
|
|
334
|
+
code: codeNoFrame,
|
|
335
|
+
tabs,
|
|
336
|
+
pageState,
|
|
337
|
+
consoleMessages,
|
|
338
|
+
modalState,
|
|
339
|
+
downloads,
|
|
340
|
+
files,
|
|
341
|
+
isError,
|
|
342
|
+
attachments,
|
|
343
|
+
_meta: response._meta
|
|
344
|
+
};
|
|
345
|
+
}
|
|
166
346
|
// Annotate the CommonJS export names for ESM import in node:
|
|
167
347
|
0 && (module.exports = {
|
|
168
|
-
|
|
348
|
+
RenderedResponse,
|
|
349
|
+
Response,
|
|
350
|
+
parseResponse,
|
|
351
|
+
requestDebug
|
|
169
352
|
});
|
|
@@ -44,7 +44,7 @@ class SessionLog {
|
|
|
44
44
|
this._file = import_path.default.join(this._folder, "session.md");
|
|
45
45
|
}
|
|
46
46
|
static async create(config, clientInfo) {
|
|
47
|
-
const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`, { origin: "code" });
|
|
47
|
+
const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`, { origin: "code", reason: "Saving session" });
|
|
48
48
|
await import_fs.default.promises.mkdir(sessionFolder, { recursive: true });
|
|
49
49
|
console.error(`Session: ${sessionFolder}`);
|
|
50
50
|
return new SessionLog(sessionFolder);
|