firefox-devtools-mcp 0.4.0 → 0.5.2
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 +4 -0
- package/dist/index.js +201 -341
- package/package.json +2 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -12192,6 +12192,9 @@ var init_core = __esm({
|
|
|
12192
12192
|
if (this.options.profilePath) {
|
|
12193
12193
|
firefoxOptions.setProfile(this.options.profilePath);
|
|
12194
12194
|
}
|
|
12195
|
+
if (this.options.acceptInsecureCerts) {
|
|
12196
|
+
firefoxOptions.setAcceptInsecureCerts(true);
|
|
12197
|
+
}
|
|
12195
12198
|
this.driver = await new Builder().forBrowser(Browser.FIREFOX).setFirefoxOptions(firefoxOptions).build();
|
|
12196
12199
|
log("\u2705 Firefox launched with BiDi");
|
|
12197
12200
|
this.currentContextId = await this.driver.getWindowHandle();
|
|
@@ -12579,10 +12582,42 @@ var init_network = __esm({
|
|
|
12579
12582
|
*/
|
|
12580
12583
|
parseHeaders(headers) {
|
|
12581
12584
|
const result = {};
|
|
12585
|
+
const normalizeValue = (value) => {
|
|
12586
|
+
if (value === null || value === void 0) {
|
|
12587
|
+
return null;
|
|
12588
|
+
}
|
|
12589
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
12590
|
+
return String(value);
|
|
12591
|
+
}
|
|
12592
|
+
if (Array.isArray(value)) {
|
|
12593
|
+
const parts = value.map((item) => normalizeValue(item)).filter((item) => !!item);
|
|
12594
|
+
return parts.length > 0 ? parts.join(", ") : null;
|
|
12595
|
+
}
|
|
12596
|
+
if (typeof value === "object") {
|
|
12597
|
+
const obj = value;
|
|
12598
|
+
if ("value" in obj) {
|
|
12599
|
+
return normalizeValue(obj.value);
|
|
12600
|
+
}
|
|
12601
|
+
if ("bytes" in obj) {
|
|
12602
|
+
return normalizeValue(obj.bytes);
|
|
12603
|
+
}
|
|
12604
|
+
try {
|
|
12605
|
+
return JSON.stringify(obj);
|
|
12606
|
+
} catch {
|
|
12607
|
+
return null;
|
|
12608
|
+
}
|
|
12609
|
+
}
|
|
12610
|
+
return String(value);
|
|
12611
|
+
};
|
|
12582
12612
|
if (Array.isArray(headers)) {
|
|
12583
12613
|
for (const h of headers) {
|
|
12584
|
-
|
|
12585
|
-
|
|
12614
|
+
const name = h?.name ? String(h.name).toLowerCase() : "";
|
|
12615
|
+
if (!name) {
|
|
12616
|
+
continue;
|
|
12617
|
+
}
|
|
12618
|
+
const normalizedValue = normalizeValue(h?.value);
|
|
12619
|
+
if (normalizedValue !== null) {
|
|
12620
|
+
result[name] = normalizedValue;
|
|
12586
12621
|
}
|
|
12587
12622
|
}
|
|
12588
12623
|
}
|
|
@@ -13157,7 +13192,7 @@ var MAX_ATTR_LENGTH;
|
|
|
13157
13192
|
var init_formatter = __esm({
|
|
13158
13193
|
"src/firefox/snapshot/formatter.ts"() {
|
|
13159
13194
|
"use strict";
|
|
13160
|
-
MAX_ATTR_LENGTH =
|
|
13195
|
+
MAX_ATTR_LENGTH = 30;
|
|
13161
13196
|
}
|
|
13162
13197
|
});
|
|
13163
13198
|
|
|
@@ -13784,9 +13819,6 @@ var init_firefox = __esm({
|
|
|
13784
13819
|
});
|
|
13785
13820
|
|
|
13786
13821
|
// src/utils/response-helpers.ts
|
|
13787
|
-
function estimateTokens(text) {
|
|
13788
|
-
return Math.ceil(text.length / 4);
|
|
13789
|
-
}
|
|
13790
13822
|
function truncateText(text, maxChars, suffix = "\n\n[... truncated - exceeded size limit]") {
|
|
13791
13823
|
if (text.length <= maxChars) {
|
|
13792
13824
|
return text;
|
|
@@ -13868,18 +13900,15 @@ var init_response_helpers = __esm({
|
|
|
13868
13900
|
|
|
13869
13901
|
// src/tools/pages.ts
|
|
13870
13902
|
function formatPageList(tabs, selectedIdx) {
|
|
13871
|
-
const lines = [`\u{1F4C4} Open pages (${tabs.length} total, selected: [${selectedIdx}]):`];
|
|
13872
13903
|
if (tabs.length === 0) {
|
|
13873
|
-
|
|
13874
|
-
}
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
lines.push(` ${url}`);
|
|
13882
|
-
}
|
|
13904
|
+
return "\u{1F4C4} No pages";
|
|
13905
|
+
}
|
|
13906
|
+
const lines = [`\u{1F4C4} ${tabs.length} pages (selected: ${selectedIdx})`];
|
|
13907
|
+
for (const tab of tabs) {
|
|
13908
|
+
const idx = tabs.indexOf(tab);
|
|
13909
|
+
const marker = idx === selectedIdx ? ">" : " ";
|
|
13910
|
+
const title = (tab.title || "Untitled").substring(0, 40);
|
|
13911
|
+
lines.push(`${marker}[${idx}] ${title}`);
|
|
13883
13912
|
}
|
|
13884
13913
|
return lines.join("\n");
|
|
13885
13914
|
}
|
|
@@ -13904,14 +13933,7 @@ async function handleNewPage(args2) {
|
|
|
13904
13933
|
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
13905
13934
|
const firefox3 = await getFirefox2();
|
|
13906
13935
|
const newIdx = await firefox3.createNewPage(url);
|
|
13907
|
-
|
|
13908
|
-
const tabs = firefox3.getTabs();
|
|
13909
|
-
const newTab = tabs[newIdx];
|
|
13910
|
-
return successResponse(
|
|
13911
|
-
`\u2705 Created new page [${newIdx}] and navigated to: ${url}
|
|
13912
|
-
Title: ${newTab?.title || "Loading..."}
|
|
13913
|
-
Total pages: ${tabs.length}`
|
|
13914
|
-
);
|
|
13936
|
+
return successResponse(`\u2705 new page [${newIdx}] \u2192 ${url}`);
|
|
13915
13937
|
} catch (error) {
|
|
13916
13938
|
return errorResponse(error);
|
|
13917
13939
|
}
|
|
@@ -13932,10 +13954,7 @@ async function handleNavigatePage(args2) {
|
|
|
13932
13954
|
throw new Error("No page selected");
|
|
13933
13955
|
}
|
|
13934
13956
|
await firefox3.navigate(url);
|
|
13935
|
-
return successResponse(
|
|
13936
|
-
`\u2705 Navigated page [${selectedIdx}] to: ${url}
|
|
13937
|
-
Previous URL: ${page.url}`
|
|
13938
|
-
);
|
|
13957
|
+
return successResponse(`\u2705 [${selectedIdx}] \u2192 ${url}`);
|
|
13939
13958
|
} catch (error) {
|
|
13940
13959
|
return errorResponse(error);
|
|
13941
13960
|
}
|
|
@@ -13948,47 +13967,30 @@ async function handleSelectPage(args2) {
|
|
|
13948
13967
|
await firefox3.refreshTabs();
|
|
13949
13968
|
const tabs = firefox3.getTabs();
|
|
13950
13969
|
let selectedIdx;
|
|
13951
|
-
let selectionMethod;
|
|
13952
13970
|
if (typeof pageIdx === "number") {
|
|
13953
13971
|
selectedIdx = pageIdx;
|
|
13954
|
-
selectionMethod = "by index";
|
|
13955
13972
|
} else if (url && typeof url === "string") {
|
|
13956
13973
|
const urlLower = url.toLowerCase();
|
|
13957
13974
|
const foundIdx = tabs.findIndex((tab) => tab.url?.toLowerCase().includes(urlLower));
|
|
13958
13975
|
if (foundIdx === -1) {
|
|
13959
|
-
throw new Error(
|
|
13960
|
-
`No page found with URL matching "${url}". Use list_pages to see all available pages.`
|
|
13961
|
-
);
|
|
13976
|
+
throw new Error(`No page matching URL "${url}"`);
|
|
13962
13977
|
}
|
|
13963
13978
|
selectedIdx = foundIdx;
|
|
13964
|
-
selectionMethod = `by URL pattern "${url}"`;
|
|
13965
13979
|
} else if (title && typeof title === "string") {
|
|
13966
13980
|
const titleLower = title.toLowerCase();
|
|
13967
13981
|
const foundIdx = tabs.findIndex((tab) => tab.title?.toLowerCase().includes(titleLower));
|
|
13968
13982
|
if (foundIdx === -1) {
|
|
13969
|
-
throw new Error(
|
|
13970
|
-
`No page found with title matching "${title}". Use list_pages to see all available pages.`
|
|
13971
|
-
);
|
|
13983
|
+
throw new Error(`No page matching title "${title}"`);
|
|
13972
13984
|
}
|
|
13973
13985
|
selectedIdx = foundIdx;
|
|
13974
|
-
selectionMethod = `by title pattern "${title}"`;
|
|
13975
13986
|
} else {
|
|
13976
|
-
throw new Error(
|
|
13977
|
-
"At least one of pageIdx, url, or title must be provided. Use list_pages to see available pages."
|
|
13978
|
-
);
|
|
13987
|
+
throw new Error("Provide pageIdx, url, or title");
|
|
13979
13988
|
}
|
|
13980
|
-
|
|
13981
|
-
|
|
13982
|
-
throw new Error(
|
|
13983
|
-
`Page at index ${selectedIdx} not found. Use list_pages to see valid indices.`
|
|
13984
|
-
);
|
|
13989
|
+
if (!tabs[selectedIdx]) {
|
|
13990
|
+
throw new Error(`Page [${selectedIdx}] not found`);
|
|
13985
13991
|
}
|
|
13986
13992
|
await firefox3.selectTab(selectedIdx);
|
|
13987
|
-
return successResponse(
|
|
13988
|
-
`\u2705 Selected page [${selectedIdx}] ${selectionMethod}
|
|
13989
|
-
Title: ${page.title || "Untitled"}
|
|
13990
|
-
URL: ${page.url || "about:blank"}`
|
|
13991
|
-
);
|
|
13993
|
+
return successResponse(`\u2705 selected [${selectedIdx}]`);
|
|
13992
13994
|
} catch (error) {
|
|
13993
13995
|
return errorResponse(error);
|
|
13994
13996
|
}
|
|
@@ -14008,10 +14010,7 @@ async function handleClosePage(args2) {
|
|
|
14008
14010
|
throw new Error(`Page with index ${pageIdx} not found`);
|
|
14009
14011
|
}
|
|
14010
14012
|
await firefox3.closeTab(pageIdx);
|
|
14011
|
-
return successResponse(
|
|
14012
|
-
`\u2705 Closed page [${pageIdx}]: ${pageToClose.title}
|
|
14013
|
-
${pageToClose.url}`
|
|
14014
|
-
);
|
|
14013
|
+
return successResponse(`\u2705 closed [${pageIdx}]`);
|
|
14015
14014
|
} catch (error) {
|
|
14016
14015
|
return errorResponse(error);
|
|
14017
14016
|
}
|
|
@@ -14023,7 +14022,7 @@ var init_pages2 = __esm({
|
|
|
14023
14022
|
init_response_helpers();
|
|
14024
14023
|
listPagesTool = {
|
|
14025
14024
|
name: "list_pages",
|
|
14026
|
-
description: "List
|
|
14025
|
+
description: "List open tabs (index, title, URL). Selected tab is marked.",
|
|
14027
14026
|
inputSchema: {
|
|
14028
14027
|
type: "object",
|
|
14029
14028
|
properties: {}
|
|
@@ -14031,13 +14030,13 @@ var init_pages2 = __esm({
|
|
|
14031
14030
|
};
|
|
14032
14031
|
newPageTool = {
|
|
14033
14032
|
name: "new_page",
|
|
14034
|
-
description: "Open
|
|
14033
|
+
description: "Open new tab at URL. Returns tab index.",
|
|
14035
14034
|
inputSchema: {
|
|
14036
14035
|
type: "object",
|
|
14037
14036
|
properties: {
|
|
14038
14037
|
url: {
|
|
14039
14038
|
type: "string",
|
|
14040
|
-
description: "URL
|
|
14039
|
+
description: "Target URL"
|
|
14041
14040
|
}
|
|
14042
14041
|
},
|
|
14043
14042
|
required: ["url"]
|
|
@@ -14045,13 +14044,13 @@ var init_pages2 = __esm({
|
|
|
14045
14044
|
};
|
|
14046
14045
|
navigatePageTool = {
|
|
14047
14046
|
name: "navigate_page",
|
|
14048
|
-
description: "Navigate
|
|
14047
|
+
description: "Navigate selected tab to URL.",
|
|
14049
14048
|
inputSchema: {
|
|
14050
14049
|
type: "object",
|
|
14051
14050
|
properties: {
|
|
14052
14051
|
url: {
|
|
14053
14052
|
type: "string",
|
|
14054
|
-
description: "URL
|
|
14053
|
+
description: "Target URL"
|
|
14055
14054
|
}
|
|
14056
14055
|
},
|
|
14057
14056
|
required: ["url"]
|
|
@@ -14059,21 +14058,21 @@ var init_pages2 = __esm({
|
|
|
14059
14058
|
};
|
|
14060
14059
|
selectPageTool = {
|
|
14061
14060
|
name: "select_page",
|
|
14062
|
-
description: "Select
|
|
14061
|
+
description: "Select active tab by index, URL, or title. Index takes precedence.",
|
|
14063
14062
|
inputSchema: {
|
|
14064
14063
|
type: "object",
|
|
14065
14064
|
properties: {
|
|
14066
14065
|
pageIdx: {
|
|
14067
14066
|
type: "number",
|
|
14068
|
-
description: "
|
|
14067
|
+
description: "Tab index (0-based, most reliable)"
|
|
14069
14068
|
},
|
|
14070
14069
|
url: {
|
|
14071
14070
|
type: "string",
|
|
14072
|
-
description:
|
|
14071
|
+
description: "URL substring (case-insensitive)"
|
|
14073
14072
|
},
|
|
14074
14073
|
title: {
|
|
14075
14074
|
type: "string",
|
|
14076
|
-
description:
|
|
14075
|
+
description: "Title substring (case-insensitive)"
|
|
14077
14076
|
}
|
|
14078
14077
|
},
|
|
14079
14078
|
required: []
|
|
@@ -14081,13 +14080,13 @@ var init_pages2 = __esm({
|
|
|
14081
14080
|
};
|
|
14082
14081
|
closePageTool = {
|
|
14083
14082
|
name: "close_page",
|
|
14084
|
-
description: "Close
|
|
14083
|
+
description: "Close tab by index.",
|
|
14085
14084
|
inputSchema: {
|
|
14086
14085
|
type: "object",
|
|
14087
14086
|
properties: {
|
|
14088
14087
|
pageIdx: {
|
|
14089
14088
|
type: "number",
|
|
14090
|
-
description: "
|
|
14089
|
+
description: "Tab index to close"
|
|
14091
14090
|
}
|
|
14092
14091
|
},
|
|
14093
14092
|
required: ["pageIdx"]
|
|
@@ -14222,7 +14221,7 @@ Total messages: ${totalCount}${filterInfo.length > 0 ? `, Filters: ${filterInfo.
|
|
|
14222
14221
|
}
|
|
14223
14222
|
if (truncated) {
|
|
14224
14223
|
output += `
|
|
14225
|
-
|
|
14224
|
+
[+${filteredCount - messages.length} more]`;
|
|
14226
14225
|
}
|
|
14227
14226
|
return successResponse(output);
|
|
14228
14227
|
} catch (error) {
|
|
@@ -14235,11 +14234,7 @@ async function handleClearConsoleMessages(_args) {
|
|
|
14235
14234
|
const firefox3 = await getFirefox2();
|
|
14236
14235
|
const count = (await firefox3.getConsoleMessages()).length;
|
|
14237
14236
|
firefox3.clearConsoleMessages();
|
|
14238
|
-
return successResponse(
|
|
14239
|
-
`Cleared ${count} console message(s) from buffer.
|
|
14240
|
-
|
|
14241
|
-
You can now capture fresh console output from new page activity.`
|
|
14242
|
-
);
|
|
14237
|
+
return successResponse(`\u2705 cleared ${count} messages`);
|
|
14243
14238
|
} catch (error) {
|
|
14244
14239
|
return errorResponse(error);
|
|
14245
14240
|
}
|
|
@@ -14251,42 +14246,42 @@ var init_console2 = __esm({
|
|
|
14251
14246
|
init_response_helpers();
|
|
14252
14247
|
listConsoleMessagesTool = {
|
|
14253
14248
|
name: "list_console_messages",
|
|
14254
|
-
description: "List console messages
|
|
14249
|
+
description: "List console messages. Supports filtering by level, time, text, source.",
|
|
14255
14250
|
inputSchema: {
|
|
14256
14251
|
type: "object",
|
|
14257
14252
|
properties: {
|
|
14258
14253
|
level: {
|
|
14259
14254
|
type: "string",
|
|
14260
14255
|
enum: ["debug", "info", "warn", "error"],
|
|
14261
|
-
description: "Filter by
|
|
14256
|
+
description: "Filter by level"
|
|
14262
14257
|
},
|
|
14263
14258
|
limit: {
|
|
14264
14259
|
type: "number",
|
|
14265
|
-
description: "
|
|
14260
|
+
description: "Max messages (default: 50)"
|
|
14266
14261
|
},
|
|
14267
14262
|
sinceMs: {
|
|
14268
14263
|
type: "number",
|
|
14269
|
-
description: "Only
|
|
14264
|
+
description: "Only last N ms"
|
|
14270
14265
|
},
|
|
14271
14266
|
textContains: {
|
|
14272
14267
|
type: "string",
|
|
14273
|
-
description: "
|
|
14268
|
+
description: "Text filter (case-insensitive)"
|
|
14274
14269
|
},
|
|
14275
14270
|
source: {
|
|
14276
14271
|
type: "string",
|
|
14277
|
-
description:
|
|
14272
|
+
description: "Filter by source"
|
|
14278
14273
|
},
|
|
14279
14274
|
format: {
|
|
14280
14275
|
type: "string",
|
|
14281
14276
|
enum: ["text", "json"],
|
|
14282
|
-
description: "Output format
|
|
14277
|
+
description: "Output format (default: text)"
|
|
14283
14278
|
}
|
|
14284
14279
|
}
|
|
14285
14280
|
}
|
|
14286
14281
|
};
|
|
14287
14282
|
clearConsoleMessagesTool = {
|
|
14288
14283
|
name: "clear_console_messages",
|
|
14289
|
-
description: "Clear
|
|
14284
|
+
description: "Clear collected console messages.",
|
|
14290
14285
|
inputSchema: {
|
|
14291
14286
|
type: "object",
|
|
14292
14287
|
properties: {}
|
|
@@ -14398,15 +14393,9 @@ async function handleListNetworkRequests(args2) {
|
|
|
14398
14393
|
const statusInfo = req.status ? `[${req.status}${req.statusText ? " " + req.statusText : ""}]` : "[pending]";
|
|
14399
14394
|
return `${req.id} | ${req.method} ${req.url} ${statusInfo}${req.isXHR ? " (XHR)" : ""}`;
|
|
14400
14395
|
});
|
|
14401
|
-
const
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
"Network Requests:",
|
|
14405
|
-
...formattedRequests,
|
|
14406
|
-
"",
|
|
14407
|
-
"TIP: Use the request ID (first column) with get_network_request for full details."
|
|
14408
|
-
].join("\n");
|
|
14409
|
-
return successResponse(summary);
|
|
14396
|
+
const header = `\u{1F4E1} ${requests.length} requests${hasMore ? ` (limit ${limit})` : ""}
|
|
14397
|
+
`;
|
|
14398
|
+
return successResponse(header + formattedRequests.join("\n"));
|
|
14410
14399
|
} else if (detail === "min") {
|
|
14411
14400
|
const minData = limitedRequests.map((req) => ({
|
|
14412
14401
|
id: req.id,
|
|
@@ -14419,8 +14408,7 @@ async function handleListNetworkRequests(args2) {
|
|
|
14419
14408
|
duration: req.timings?.duration
|
|
14420
14409
|
}));
|
|
14421
14410
|
return successResponse(
|
|
14422
|
-
|
|
14423
|
-
|
|
14411
|
+
`\u{1F4E1} ${requests.length} requests${hasMore ? ` (limit ${limit})` : ""}
|
|
14424
14412
|
` + JSON.stringify(minData, null, 2)
|
|
14425
14413
|
);
|
|
14426
14414
|
} else {
|
|
@@ -14437,15 +14425,12 @@ async function handleListNetworkRequests(args2) {
|
|
|
14437
14425
|
responseHeaders: truncateHeaders(req.responseHeaders)
|
|
14438
14426
|
}));
|
|
14439
14427
|
return successResponse(
|
|
14440
|
-
|
|
14441
|
-
|
|
14428
|
+
`\u{1F4E1} ${requests.length} requests${hasMore ? ` (limit ${limit})` : ""}
|
|
14442
14429
|
` + JSON.stringify(fullData, null, 2)
|
|
14443
14430
|
);
|
|
14444
14431
|
}
|
|
14445
14432
|
} catch (error) {
|
|
14446
|
-
return errorResponse(
|
|
14447
|
-
`Failed to list network requests: ${error instanceof Error ? error.message : String(error)}`
|
|
14448
|
-
);
|
|
14433
|
+
return errorResponse(error instanceof Error ? error : new Error(String(error)));
|
|
14449
14434
|
}
|
|
14450
14435
|
}
|
|
14451
14436
|
async function handleGetNetworkRequest(args2) {
|
|
@@ -14456,9 +14441,7 @@ async function handleGetNetworkRequest(args2) {
|
|
|
14456
14441
|
format = "text"
|
|
14457
14442
|
} = args2;
|
|
14458
14443
|
if (!id && !url) {
|
|
14459
|
-
return errorResponse(
|
|
14460
|
-
'Either "id" or "url" parameter is required.\n\nTIP: Call list_network_requests first and use the returned ID for reliable lookup.'
|
|
14461
|
-
);
|
|
14444
|
+
return errorResponse("id or url required");
|
|
14462
14445
|
}
|
|
14463
14446
|
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
14464
14447
|
const firefox3 = await getFirefox2();
|
|
@@ -14467,29 +14450,16 @@ async function handleGetNetworkRequest(args2) {
|
|
|
14467
14450
|
if (id) {
|
|
14468
14451
|
request = requests.find((req) => req.id === id);
|
|
14469
14452
|
if (!request) {
|
|
14470
|
-
return errorResponse(
|
|
14471
|
-
`No network request found with ID: ${id}
|
|
14472
|
-
|
|
14473
|
-
TIP: The request may have been cleared. Call list_network_requests to see available requests.`
|
|
14474
|
-
);
|
|
14453
|
+
return errorResponse(`ID ${id} not found`);
|
|
14475
14454
|
}
|
|
14476
14455
|
} else if (url) {
|
|
14477
14456
|
const matches = requests.filter((req) => req.url === url);
|
|
14478
14457
|
if (matches.length === 0) {
|
|
14479
|
-
return errorResponse(
|
|
14480
|
-
`No network request found with URL: ${url}
|
|
14481
|
-
|
|
14482
|
-
TIP: Use list_network_requests to see available requests.`
|
|
14483
|
-
);
|
|
14458
|
+
return errorResponse(`URL not found: ${url}`);
|
|
14484
14459
|
}
|
|
14485
14460
|
if (matches.length > 1) {
|
|
14486
|
-
const
|
|
14487
|
-
return errorResponse(
|
|
14488
|
-
`Multiple requests (${matches.length}) found with URL: ${url}
|
|
14489
|
-
|
|
14490
|
-
Please use one of these IDs with the "id" parameter:
|
|
14491
|
-
` + matchInfo
|
|
14492
|
-
);
|
|
14461
|
+
const ids = matches.map((req) => req.id).join(", ");
|
|
14462
|
+
return errorResponse(`Multiple matches, use id: ${ids}`);
|
|
14493
14463
|
}
|
|
14494
14464
|
request = matches[0];
|
|
14495
14465
|
}
|
|
@@ -14512,11 +14482,9 @@ Please use one of these IDs with the "id" parameter:
|
|
|
14512
14482
|
if (format === "json") {
|
|
14513
14483
|
return jsonResponse(details);
|
|
14514
14484
|
}
|
|
14515
|
-
return successResponse(
|
|
14485
|
+
return successResponse(JSON.stringify(details, null, 2));
|
|
14516
14486
|
} catch (error) {
|
|
14517
|
-
return errorResponse(
|
|
14518
|
-
`Failed to get network request: ${error instanceof Error ? error.message : String(error)}`
|
|
14519
|
-
);
|
|
14487
|
+
return errorResponse(error instanceof Error ? error : new Error(String(error)));
|
|
14520
14488
|
}
|
|
14521
14489
|
}
|
|
14522
14490
|
var listNetworkRequestsTool, getNetworkRequestTool;
|
|
@@ -14526,82 +14494,82 @@ var init_network2 = __esm({
|
|
|
14526
14494
|
init_response_helpers();
|
|
14527
14495
|
listNetworkRequestsTool = {
|
|
14528
14496
|
name: "list_network_requests",
|
|
14529
|
-
description: "List
|
|
14497
|
+
description: "List network requests. Returns IDs for get_network_request.",
|
|
14530
14498
|
inputSchema: {
|
|
14531
14499
|
type: "object",
|
|
14532
14500
|
properties: {
|
|
14533
14501
|
limit: {
|
|
14534
14502
|
type: "number",
|
|
14535
|
-
description: "
|
|
14503
|
+
description: "Max requests (default: 50)"
|
|
14536
14504
|
},
|
|
14537
14505
|
sinceMs: {
|
|
14538
14506
|
type: "number",
|
|
14539
|
-
description: "
|
|
14507
|
+
description: "Only last N ms"
|
|
14540
14508
|
},
|
|
14541
14509
|
urlContains: {
|
|
14542
14510
|
type: "string",
|
|
14543
|
-
description: "
|
|
14511
|
+
description: "URL filter (case-insensitive)"
|
|
14544
14512
|
},
|
|
14545
14513
|
method: {
|
|
14546
14514
|
type: "string",
|
|
14547
|
-
description: "
|
|
14515
|
+
description: "HTTP method filter"
|
|
14548
14516
|
},
|
|
14549
14517
|
status: {
|
|
14550
14518
|
type: "number",
|
|
14551
|
-
description: "
|
|
14519
|
+
description: "Exact status code"
|
|
14552
14520
|
},
|
|
14553
14521
|
statusMin: {
|
|
14554
14522
|
type: "number",
|
|
14555
|
-
description: "
|
|
14523
|
+
description: "Min status code"
|
|
14556
14524
|
},
|
|
14557
14525
|
statusMax: {
|
|
14558
14526
|
type: "number",
|
|
14559
|
-
description: "
|
|
14527
|
+
description: "Max status code"
|
|
14560
14528
|
},
|
|
14561
14529
|
isXHR: {
|
|
14562
14530
|
type: "boolean",
|
|
14563
|
-
description: "
|
|
14531
|
+
description: "XHR/fetch only"
|
|
14564
14532
|
},
|
|
14565
14533
|
resourceType: {
|
|
14566
14534
|
type: "string",
|
|
14567
|
-
description: "
|
|
14535
|
+
description: "Resource type filter"
|
|
14568
14536
|
},
|
|
14569
14537
|
sortBy: {
|
|
14570
14538
|
type: "string",
|
|
14571
14539
|
enum: ["timestamp", "duration", "status"],
|
|
14572
|
-
description: "Sort
|
|
14540
|
+
description: "Sort field (default: timestamp)"
|
|
14573
14541
|
},
|
|
14574
14542
|
detail: {
|
|
14575
14543
|
type: "string",
|
|
14576
14544
|
enum: ["summary", "min", "full"],
|
|
14577
|
-
description: "
|
|
14545
|
+
description: "Detail level (default: summary)"
|
|
14578
14546
|
},
|
|
14579
14547
|
format: {
|
|
14580
14548
|
type: "string",
|
|
14581
14549
|
enum: ["text", "json"],
|
|
14582
|
-
description: "Output format
|
|
14550
|
+
description: "Output format (default: text)"
|
|
14583
14551
|
}
|
|
14584
14552
|
}
|
|
14585
14553
|
}
|
|
14586
14554
|
};
|
|
14587
14555
|
getNetworkRequestTool = {
|
|
14588
14556
|
name: "get_network_request",
|
|
14589
|
-
description: "Get
|
|
14557
|
+
description: "Get request details by ID. URL lookup as fallback.",
|
|
14590
14558
|
inputSchema: {
|
|
14591
14559
|
type: "object",
|
|
14592
14560
|
properties: {
|
|
14593
14561
|
id: {
|
|
14594
14562
|
type: "string",
|
|
14595
|
-
description: "
|
|
14563
|
+
description: "Request ID from list_network_requests"
|
|
14596
14564
|
},
|
|
14597
14565
|
url: {
|
|
14598
14566
|
type: "string",
|
|
14599
|
-
description: "
|
|
14567
|
+
description: "URL fallback (may match multiple)"
|
|
14600
14568
|
},
|
|
14601
14569
|
format: {
|
|
14602
14570
|
type: "string",
|
|
14603
14571
|
enum: ["text", "json"],
|
|
14604
|
-
description: "Output format
|
|
14572
|
+
description: "Output format (default: text)"
|
|
14605
14573
|
}
|
|
14606
14574
|
}
|
|
14607
14575
|
}
|
|
@@ -14609,6 +14577,20 @@ var init_network2 = __esm({
|
|
|
14609
14577
|
}
|
|
14610
14578
|
});
|
|
14611
14579
|
|
|
14580
|
+
// src/utils/uid-helpers.ts
|
|
14581
|
+
function handleUidError(error, uid) {
|
|
14582
|
+
const errorMsg = error.message;
|
|
14583
|
+
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID") || errorMsg.includes("not found")) {
|
|
14584
|
+
return new Error(`${uid} stale/invalid. Call take_snapshot first.`);
|
|
14585
|
+
}
|
|
14586
|
+
return error;
|
|
14587
|
+
}
|
|
14588
|
+
var init_uid_helpers = __esm({
|
|
14589
|
+
"src/utils/uid-helpers.ts"() {
|
|
14590
|
+
"use strict";
|
|
14591
|
+
}
|
|
14592
|
+
});
|
|
14593
|
+
|
|
14612
14594
|
// src/tools/snapshot.ts
|
|
14613
14595
|
async function handleTakeSnapshot(args2) {
|
|
14614
14596
|
try {
|
|
@@ -14635,28 +14617,19 @@ async function handleTakeSnapshot(args2) {
|
|
|
14635
14617
|
const lines = formattedText.split("\n");
|
|
14636
14618
|
const truncated = lines.length > maxLines;
|
|
14637
14619
|
const displayLines = truncated ? lines.slice(0, maxLines) : lines;
|
|
14638
|
-
let output =
|
|
14620
|
+
let output = `\u{1F4F8} Snapshot (id=${snapshot.json.snapshotId})`;
|
|
14639
14621
|
if (wasCapped) {
|
|
14640
|
-
output +=
|
|
14641
|
-
|
|
14642
|
-
`;
|
|
14622
|
+
output += ` [maxLines capped: ${TOKEN_LIMITS.MAX_SNAPSHOT_LINES_CAP}]`;
|
|
14643
14623
|
}
|
|
14644
|
-
output += "\u2550\u2550\u2550 HOW TO USE THIS SNAPSHOT \u2550\u2550\u2550\n";
|
|
14645
|
-
output += "\u2022 To interact with elements: use click_by_uid, hover_by_uid, or fill_by_uid with the UID\n";
|
|
14646
|
-
output += "\u2022 After navigation: always call take_snapshot again (UIDs become stale)\n";
|
|
14647
|
-
output += "\u2022 On stale UID errors: call take_snapshot \u2192 retry your action\n";
|
|
14648
|
-
output += "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n";
|
|
14649
|
-
output += `Snapshot ID: ${snapshot.json.snapshotId}
|
|
14650
|
-
`;
|
|
14651
14624
|
if (snapshot.json.truncated) {
|
|
14652
|
-
output += "
|
|
14625
|
+
output += " [DOM truncated]";
|
|
14653
14626
|
}
|
|
14654
|
-
output += "\n";
|
|
14627
|
+
output += "\n\n";
|
|
14655
14628
|
output += displayLines.join("\n");
|
|
14656
14629
|
if (truncated) {
|
|
14657
14630
|
output += `
|
|
14658
14631
|
|
|
14659
|
-
|
|
14632
|
+
[+${lines.length - maxLines} lines, use maxLines to see more]`;
|
|
14660
14633
|
}
|
|
14661
14634
|
return successResponse(output);
|
|
14662
14635
|
} catch (error) {
|
|
@@ -14679,20 +14652,9 @@ async function handleResolveUidToSelector(args2) {
|
|
|
14679
14652
|
const firefox3 = await getFirefox2();
|
|
14680
14653
|
try {
|
|
14681
14654
|
const selector = firefox3.resolveUidToSelector(uid);
|
|
14682
|
-
return successResponse(
|
|
14683
|
-
|
|
14684
|
-
${selector}`);
|
|
14655
|
+
return successResponse(`${uid} \u2192 ${selector}`);
|
|
14685
14656
|
} catch (error) {
|
|
14686
|
-
|
|
14687
|
-
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID") || errorMsg.includes("not found")) {
|
|
14688
|
-
throw new Error(
|
|
14689
|
-
`UID "${uid}" is from an old snapshot or not found.
|
|
14690
|
-
|
|
14691
|
-
The page structure may have changed since the snapshot was taken.
|
|
14692
|
-
Please call take_snapshot to get fresh UIDs and try again.`
|
|
14693
|
-
);
|
|
14694
|
-
}
|
|
14695
|
-
throw error;
|
|
14657
|
+
throw handleUidError(error, uid);
|
|
14696
14658
|
}
|
|
14697
14659
|
} catch (error) {
|
|
14698
14660
|
return errorResponse(error);
|
|
@@ -14703,9 +14665,7 @@ async function handleClearSnapshot(_args) {
|
|
|
14703
14665
|
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
14704
14666
|
const firefox3 = await getFirefox2();
|
|
14705
14667
|
firefox3.clearSnapshot();
|
|
14706
|
-
return successResponse(
|
|
14707
|
-
"\u{1F9F9} Snapshot cache cleared.\n\nFor the next UID-dependent action, take a fresh snapshot first."
|
|
14708
|
-
);
|
|
14668
|
+
return successResponse("\u{1F9F9} Snapshot cleared");
|
|
14709
14669
|
} catch (error) {
|
|
14710
14670
|
return errorResponse(error);
|
|
14711
14671
|
}
|
|
@@ -14715,41 +14675,42 @@ var init_snapshot2 = __esm({
|
|
|
14715
14675
|
"src/tools/snapshot.ts"() {
|
|
14716
14676
|
"use strict";
|
|
14717
14677
|
init_response_helpers();
|
|
14678
|
+
init_uid_helpers();
|
|
14718
14679
|
DEFAULT_SNAPSHOT_LINES = 100;
|
|
14719
14680
|
takeSnapshotTool = {
|
|
14720
14681
|
name: "take_snapshot",
|
|
14721
|
-
description: "Capture
|
|
14682
|
+
description: "Capture DOM snapshot with stable UIDs. Retake after navigation.",
|
|
14722
14683
|
inputSchema: {
|
|
14723
14684
|
type: "object",
|
|
14724
14685
|
properties: {
|
|
14725
14686
|
maxLines: {
|
|
14726
14687
|
type: "number",
|
|
14727
|
-
description: "
|
|
14688
|
+
description: "Max lines (default: 100)"
|
|
14728
14689
|
},
|
|
14729
14690
|
includeAttributes: {
|
|
14730
14691
|
type: "boolean",
|
|
14731
|
-
description: "Include
|
|
14692
|
+
description: "Include ARIA attributes (default: false)"
|
|
14732
14693
|
},
|
|
14733
14694
|
includeText: {
|
|
14734
14695
|
type: "boolean",
|
|
14735
|
-
description: "Include text
|
|
14696
|
+
description: "Include text (default: true)"
|
|
14736
14697
|
},
|
|
14737
14698
|
maxDepth: {
|
|
14738
14699
|
type: "number",
|
|
14739
|
-
description: "
|
|
14700
|
+
description: "Max tree depth"
|
|
14740
14701
|
}
|
|
14741
14702
|
}
|
|
14742
14703
|
}
|
|
14743
14704
|
};
|
|
14744
14705
|
resolveUidToSelectorTool = {
|
|
14745
14706
|
name: "resolve_uid_to_selector",
|
|
14746
|
-
description: "Resolve
|
|
14707
|
+
description: "Resolve UID to CSS selector. Fails if stale.",
|
|
14747
14708
|
inputSchema: {
|
|
14748
14709
|
type: "object",
|
|
14749
14710
|
properties: {
|
|
14750
14711
|
uid: {
|
|
14751
14712
|
type: "string",
|
|
14752
|
-
description: "
|
|
14713
|
+
description: "UID from snapshot"
|
|
14753
14714
|
}
|
|
14754
14715
|
},
|
|
14755
14716
|
required: ["uid"]
|
|
@@ -14757,7 +14718,7 @@ var init_snapshot2 = __esm({
|
|
|
14757
14718
|
};
|
|
14758
14719
|
clearSnapshotTool = {
|
|
14759
14720
|
name: "clear_snapshot",
|
|
14760
|
-
description: "Clear
|
|
14721
|
+
description: "Clear snapshot cache. Usually not needed.",
|
|
14761
14722
|
inputSchema: {
|
|
14762
14723
|
type: "object",
|
|
14763
14724
|
properties: {}
|
|
@@ -14767,18 +14728,6 @@ var init_snapshot2 = __esm({
|
|
|
14767
14728
|
});
|
|
14768
14729
|
|
|
14769
14730
|
// src/tools/input.ts
|
|
14770
|
-
function handleUidError(error, uid) {
|
|
14771
|
-
const errorMsg = error.message;
|
|
14772
|
-
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID") || errorMsg.includes("not found")) {
|
|
14773
|
-
return new Error(
|
|
14774
|
-
`UID "${uid}" is stale or invalid.
|
|
14775
|
-
|
|
14776
|
-
The page may have changed since the snapshot was taken.
|
|
14777
|
-
Please call take_snapshot to get fresh UIDs and try again.`
|
|
14778
|
-
);
|
|
14779
|
-
}
|
|
14780
|
-
return error;
|
|
14781
|
-
}
|
|
14782
14731
|
async function handleClickByUid(args2) {
|
|
14783
14732
|
try {
|
|
14784
14733
|
const { uid, dblClick } = args2;
|
|
@@ -14789,9 +14738,7 @@ async function handleClickByUid(args2) {
|
|
|
14789
14738
|
const firefox3 = await getFirefox2();
|
|
14790
14739
|
try {
|
|
14791
14740
|
await firefox3.clickByUid(uid, dblClick);
|
|
14792
|
-
return successResponse(
|
|
14793
|
-
`\u2705 ${dblClick ? "Double-clicked" : "Clicked"} element with UID "${uid}"`
|
|
14794
|
-
);
|
|
14741
|
+
return successResponse(`\u2705 ${dblClick ? "dblclick" : "click"} ${uid}`);
|
|
14795
14742
|
} catch (error) {
|
|
14796
14743
|
throw handleUidError(error, uid);
|
|
14797
14744
|
}
|
|
@@ -14809,7 +14756,7 @@ async function handleHoverByUid(args2) {
|
|
|
14809
14756
|
const firefox3 = await getFirefox2();
|
|
14810
14757
|
try {
|
|
14811
14758
|
await firefox3.hoverByUid(uid);
|
|
14812
|
-
return successResponse(`\u2705
|
|
14759
|
+
return successResponse(`\u2705 hover ${uid}`);
|
|
14813
14760
|
} catch (error) {
|
|
14814
14761
|
throw handleUidError(error, uid);
|
|
14815
14762
|
}
|
|
@@ -14830,10 +14777,7 @@ async function handleFillByUid(args2) {
|
|
|
14830
14777
|
const firefox3 = await getFirefox2();
|
|
14831
14778
|
try {
|
|
14832
14779
|
await firefox3.fillByUid(uid, value);
|
|
14833
|
-
return successResponse(
|
|
14834
|
-
`\u2705 Filled element with UID "${uid}"
|
|
14835
|
-
Value: ${value.substring(0, 50)}${value.length > 50 ? "..." : ""}`
|
|
14836
|
-
);
|
|
14780
|
+
return successResponse(`\u2705 fill ${uid}`);
|
|
14837
14781
|
} catch (error) {
|
|
14838
14782
|
throw handleUidError(error, uid);
|
|
14839
14783
|
}
|
|
@@ -14854,16 +14798,11 @@ async function handleDragByUidToUid(args2) {
|
|
|
14854
14798
|
const firefox3 = await getFirefox2();
|
|
14855
14799
|
try {
|
|
14856
14800
|
await firefox3.dragByUidToUid(fromUid, toUid);
|
|
14857
|
-
return successResponse(`\u2705
|
|
14801
|
+
return successResponse(`\u2705 drag ${fromUid}\u2192${toUid}`);
|
|
14858
14802
|
} catch (error) {
|
|
14859
14803
|
const errorMsg = error.message;
|
|
14860
14804
|
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID")) {
|
|
14861
|
-
throw new Error(
|
|
14862
|
-
`One or both UIDs (from: "${fromUid}", to: "${toUid}") are stale or invalid.
|
|
14863
|
-
|
|
14864
|
-
The page may have changed since the snapshot was taken.
|
|
14865
|
-
Please call take_snapshot to get fresh UIDs and try again.`
|
|
14866
|
-
);
|
|
14805
|
+
throw new Error(`UIDs stale/invalid. Call take_snapshot first.`);
|
|
14867
14806
|
}
|
|
14868
14807
|
throw error;
|
|
14869
14808
|
}
|
|
@@ -14889,21 +14828,11 @@ async function handleFillFormByUid(args2) {
|
|
|
14889
14828
|
const firefox3 = await getFirefox2();
|
|
14890
14829
|
try {
|
|
14891
14830
|
await firefox3.fillFormByUid(elements);
|
|
14892
|
-
return successResponse(
|
|
14893
|
-
`\u2705 Filled ${elements.length} form field(s):
|
|
14894
|
-
` + elements.map(
|
|
14895
|
-
(el) => ` - ${el.uid}: ${el.value.substring(0, 30)}${el.value.length > 30 ? "..." : ""}`
|
|
14896
|
-
).join("\n")
|
|
14897
|
-
);
|
|
14831
|
+
return successResponse(`\u2705 filled ${elements.length} fields`);
|
|
14898
14832
|
} catch (error) {
|
|
14899
14833
|
const errorMsg = error.message;
|
|
14900
14834
|
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID")) {
|
|
14901
|
-
throw new Error(
|
|
14902
|
-
`One or more UIDs are stale or invalid.
|
|
14903
|
-
|
|
14904
|
-
The page may have changed since the snapshot was taken.
|
|
14905
|
-
Please call take_snapshot to get fresh UIDs and try again.`
|
|
14906
|
-
);
|
|
14835
|
+
throw new Error(`UIDs stale/invalid. Call take_snapshot first.`);
|
|
14907
14836
|
}
|
|
14908
14837
|
throw error;
|
|
14909
14838
|
}
|
|
@@ -14924,26 +14853,17 @@ async function handleUploadFileByUid(args2) {
|
|
|
14924
14853
|
const firefox3 = await getFirefox2();
|
|
14925
14854
|
try {
|
|
14926
14855
|
await firefox3.uploadFileByUid(uid, filePath);
|
|
14927
|
-
return successResponse(`\u2705
|
|
14928
|
-
File: ${filePath}`);
|
|
14856
|
+
return successResponse(`\u2705 upload ${uid}`);
|
|
14929
14857
|
} catch (error) {
|
|
14930
14858
|
const errorMsg = error.message;
|
|
14931
14859
|
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID")) {
|
|
14932
14860
|
throw handleUidError(error, uid);
|
|
14933
14861
|
}
|
|
14934
14862
|
if (errorMsg.includes("not a file input") || errorMsg.includes('type="file"')) {
|
|
14935
|
-
throw new Error(
|
|
14936
|
-
`Element with UID "${uid}" is not an <input type="file"> element.
|
|
14937
|
-
|
|
14938
|
-
Please ensure the UID points to a file input element.`
|
|
14939
|
-
);
|
|
14863
|
+
throw new Error(`${uid} is not a file input`);
|
|
14940
14864
|
}
|
|
14941
14865
|
if (errorMsg.includes("hidden") || errorMsg.includes("not visible")) {
|
|
14942
|
-
throw new Error(
|
|
14943
|
-
`File input element with UID "${uid}" is hidden or not interactable.
|
|
14944
|
-
|
|
14945
|
-
Some file inputs are hidden and cannot be directly interacted with.`
|
|
14946
|
-
);
|
|
14866
|
+
throw new Error(`${uid} is hidden/not interactable`);
|
|
14947
14867
|
}
|
|
14948
14868
|
throw error;
|
|
14949
14869
|
}
|
|
@@ -14956,19 +14876,20 @@ var init_input = __esm({
|
|
|
14956
14876
|
"src/tools/input.ts"() {
|
|
14957
14877
|
"use strict";
|
|
14958
14878
|
init_response_helpers();
|
|
14879
|
+
init_uid_helpers();
|
|
14959
14880
|
clickByUidTool = {
|
|
14960
14881
|
name: "click_by_uid",
|
|
14961
|
-
description: "Click
|
|
14882
|
+
description: "Click element by UID. Set dblClick for double-click.",
|
|
14962
14883
|
inputSchema: {
|
|
14963
14884
|
type: "object",
|
|
14964
14885
|
properties: {
|
|
14965
14886
|
uid: {
|
|
14966
14887
|
type: "string",
|
|
14967
|
-
description: "
|
|
14888
|
+
description: "Element UID from snapshot"
|
|
14968
14889
|
},
|
|
14969
14890
|
dblClick: {
|
|
14970
14891
|
type: "boolean",
|
|
14971
|
-
description: "
|
|
14892
|
+
description: "Double-click (default: false)"
|
|
14972
14893
|
}
|
|
14973
14894
|
},
|
|
14974
14895
|
required: ["uid"]
|
|
@@ -14976,13 +14897,13 @@ var init_input = __esm({
|
|
|
14976
14897
|
};
|
|
14977
14898
|
hoverByUidTool = {
|
|
14978
14899
|
name: "hover_by_uid",
|
|
14979
|
-
description: "Hover over
|
|
14900
|
+
description: "Hover over element by UID.",
|
|
14980
14901
|
inputSchema: {
|
|
14981
14902
|
type: "object",
|
|
14982
14903
|
properties: {
|
|
14983
14904
|
uid: {
|
|
14984
14905
|
type: "string",
|
|
14985
|
-
description: "
|
|
14906
|
+
description: "Element UID from snapshot"
|
|
14986
14907
|
}
|
|
14987
14908
|
},
|
|
14988
14909
|
required: ["uid"]
|
|
@@ -14990,17 +14911,17 @@ var init_input = __esm({
|
|
|
14990
14911
|
};
|
|
14991
14912
|
fillByUidTool = {
|
|
14992
14913
|
name: "fill_by_uid",
|
|
14993
|
-
description: "Fill
|
|
14914
|
+
description: "Fill text input/textarea by UID.",
|
|
14994
14915
|
inputSchema: {
|
|
14995
14916
|
type: "object",
|
|
14996
14917
|
properties: {
|
|
14997
14918
|
uid: {
|
|
14998
14919
|
type: "string",
|
|
14999
|
-
description: "
|
|
14920
|
+
description: "Input element UID from snapshot"
|
|
15000
14921
|
},
|
|
15001
14922
|
value: {
|
|
15002
14923
|
type: "string",
|
|
15003
|
-
description: "
|
|
14924
|
+
description: "Text to fill"
|
|
15004
14925
|
}
|
|
15005
14926
|
},
|
|
15006
14927
|
required: ["uid", "value"]
|
|
@@ -15008,17 +14929,17 @@ var init_input = __esm({
|
|
|
15008
14929
|
};
|
|
15009
14930
|
dragByUidToUidTool = {
|
|
15010
14931
|
name: "drag_by_uid_to_uid",
|
|
15011
|
-
description: "
|
|
14932
|
+
description: "Drag element to another (HTML5 drag events).",
|
|
15012
14933
|
inputSchema: {
|
|
15013
14934
|
type: "object",
|
|
15014
14935
|
properties: {
|
|
15015
14936
|
fromUid: {
|
|
15016
14937
|
type: "string",
|
|
15017
|
-
description: "
|
|
14938
|
+
description: "Source element UID"
|
|
15018
14939
|
},
|
|
15019
14940
|
toUid: {
|
|
15020
14941
|
type: "string",
|
|
15021
|
-
description: "
|
|
14942
|
+
description: "Target element UID"
|
|
15022
14943
|
}
|
|
15023
14944
|
},
|
|
15024
14945
|
required: ["fromUid", "toUid"]
|
|
@@ -15026,23 +14947,23 @@ var init_input = __esm({
|
|
|
15026
14947
|
};
|
|
15027
14948
|
fillFormByUidTool = {
|
|
15028
14949
|
name: "fill_form_by_uid",
|
|
15029
|
-
description: "Fill multiple form fields
|
|
14950
|
+
description: "Fill multiple form fields at once.",
|
|
15030
14951
|
inputSchema: {
|
|
15031
14952
|
type: "object",
|
|
15032
14953
|
properties: {
|
|
15033
14954
|
elements: {
|
|
15034
14955
|
type: "array",
|
|
15035
|
-
description: "Array of
|
|
14956
|
+
description: "Array of {uid, value} pairs",
|
|
15036
14957
|
items: {
|
|
15037
14958
|
type: "object",
|
|
15038
14959
|
properties: {
|
|
15039
14960
|
uid: {
|
|
15040
14961
|
type: "string",
|
|
15041
|
-
description: "
|
|
14962
|
+
description: "Field UID"
|
|
15042
14963
|
},
|
|
15043
14964
|
value: {
|
|
15044
14965
|
type: "string",
|
|
15045
|
-
description: "
|
|
14966
|
+
description: "Field value"
|
|
15046
14967
|
}
|
|
15047
14968
|
},
|
|
15048
14969
|
required: ["uid", "value"]
|
|
@@ -15054,17 +14975,17 @@ var init_input = __esm({
|
|
|
15054
14975
|
};
|
|
15055
14976
|
uploadFileByUidTool = {
|
|
15056
14977
|
name: "upload_file_by_uid",
|
|
15057
|
-
description:
|
|
14978
|
+
description: "Upload file to file input by UID.",
|
|
15058
14979
|
inputSchema: {
|
|
15059
14980
|
type: "object",
|
|
15060
14981
|
properties: {
|
|
15061
14982
|
uid: {
|
|
15062
14983
|
type: "string",
|
|
15063
|
-
description: "
|
|
14984
|
+
description: "File input UID from snapshot"
|
|
15064
14985
|
},
|
|
15065
14986
|
filePath: {
|
|
15066
14987
|
type: "string",
|
|
15067
|
-
description: "Local
|
|
14988
|
+
description: "Local file path"
|
|
15068
14989
|
}
|
|
15069
14990
|
},
|
|
15070
14991
|
required: ["uid", "filePath"]
|
|
@@ -15074,36 +14995,16 @@ var init_input = __esm({
|
|
|
15074
14995
|
});
|
|
15075
14996
|
|
|
15076
14997
|
// src/tools/screenshot.ts
|
|
15077
|
-
function buildScreenshotResponse(base64Png,
|
|
14998
|
+
function buildScreenshotResponse(base64Png, label) {
|
|
15078
14999
|
const sizeKB = Math.round(base64Png.length / 1024);
|
|
15079
|
-
const estimatedTokens = estimateTokens(base64Png);
|
|
15080
15000
|
if (base64Png.length > TOKEN_LIMITS.MAX_SCREENSHOT_CHARS) {
|
|
15081
15001
|
const truncatedData = base64Png.slice(0, TOKEN_LIMITS.MAX_SCREENSHOT_CHARS);
|
|
15082
|
-
return successResponse(
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
\u26A0\uFE0F Screenshot truncated (~${Math.round(estimatedTokens / 1e3)}k tokens exceeds limit)
|
|
15086
|
-
Only first ${Math.round(TOKEN_LIMITS.MAX_SCREENSHOT_CHARS / 1024)}KB shown.
|
|
15087
|
-
TIP: For full screenshots, use a dedicated screenshot tool or save to file.
|
|
15088
|
-
|
|
15089
|
-
Base64 PNG data (truncated):
|
|
15090
|
-
${truncatedData}
|
|
15091
|
-
|
|
15092
|
-
[...truncated]`
|
|
15093
|
-
);
|
|
15094
|
-
}
|
|
15095
|
-
let warning = "";
|
|
15096
|
-
if (base64Png.length > TOKEN_LIMITS.WARNING_THRESHOLD_CHARS) {
|
|
15097
|
-
warning = `\u26A0\uFE0F Large screenshot (~${Math.round(estimatedTokens / 1e3)}k tokens) - may fill context quickly
|
|
15098
|
-
|
|
15099
|
-
`;
|
|
15002
|
+
return successResponse(`\u{1F4F8} ${label} (${sizeKB}KB) [truncated]
|
|
15003
|
+
${truncatedData}`);
|
|
15100
15004
|
}
|
|
15101
|
-
|
|
15102
|
-
|
|
15103
|
-
|
|
15104
|
-
` + warning + `Base64 PNG data:
|
|
15105
|
-
${base64Png}`
|
|
15106
|
-
);
|
|
15005
|
+
const warn = base64Png.length > TOKEN_LIMITS.WARNING_THRESHOLD_CHARS ? " [large]" : "";
|
|
15006
|
+
return successResponse(`\u{1F4F8} ${label} (${sizeKB}KB)${warn}
|
|
15007
|
+
${base64Png}`);
|
|
15107
15008
|
}
|
|
15108
15009
|
async function handleScreenshotPage(_args) {
|
|
15109
15010
|
try {
|
|
@@ -15111,44 +15012,29 @@ async function handleScreenshotPage(_args) {
|
|
|
15111
15012
|
const firefox3 = await getFirefox2();
|
|
15112
15013
|
const base64Png = await firefox3.takeScreenshotPage();
|
|
15113
15014
|
if (!base64Png || typeof base64Png !== "string") {
|
|
15114
|
-
throw new Error("
|
|
15015
|
+
throw new Error("Invalid screenshot data");
|
|
15115
15016
|
}
|
|
15116
|
-
return buildScreenshotResponse(base64Png, "
|
|
15017
|
+
return buildScreenshotResponse(base64Png, "page");
|
|
15117
15018
|
} catch (error) {
|
|
15118
|
-
return errorResponse(
|
|
15119
|
-
new Error(
|
|
15120
|
-
`Failed to capture page screenshot: ${error.message}
|
|
15121
|
-
|
|
15122
|
-
The page may not be fully loaded or accessible.`
|
|
15123
|
-
)
|
|
15124
|
-
);
|
|
15019
|
+
return errorResponse(error);
|
|
15125
15020
|
}
|
|
15126
15021
|
}
|
|
15127
15022
|
async function handleScreenshotByUid(args2) {
|
|
15128
15023
|
try {
|
|
15129
15024
|
const { uid } = args2;
|
|
15130
15025
|
if (!uid || typeof uid !== "string") {
|
|
15131
|
-
throw new Error("uid
|
|
15026
|
+
throw new Error("uid required");
|
|
15132
15027
|
}
|
|
15133
15028
|
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
15134
15029
|
const firefox3 = await getFirefox2();
|
|
15135
15030
|
try {
|
|
15136
15031
|
const base64Png = await firefox3.takeScreenshotByUid(uid);
|
|
15137
15032
|
if (!base64Png || typeof base64Png !== "string") {
|
|
15138
|
-
throw new Error("
|
|
15033
|
+
throw new Error("Invalid screenshot data");
|
|
15139
15034
|
}
|
|
15140
|
-
return buildScreenshotResponse(base64Png,
|
|
15035
|
+
return buildScreenshotResponse(base64Png, uid);
|
|
15141
15036
|
} catch (error) {
|
|
15142
|
-
|
|
15143
|
-
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID") || errorMsg.includes("not found")) {
|
|
15144
|
-
throw new Error(
|
|
15145
|
-
`UID "${uid}" is stale or invalid.
|
|
15146
|
-
|
|
15147
|
-
The page may have changed since the snapshot was taken.
|
|
15148
|
-
Please call take_snapshot to get fresh UIDs and try again.`
|
|
15149
|
-
);
|
|
15150
|
-
}
|
|
15151
|
-
throw error;
|
|
15037
|
+
throw handleUidError(error, uid);
|
|
15152
15038
|
}
|
|
15153
15039
|
} catch (error) {
|
|
15154
15040
|
return errorResponse(error);
|
|
@@ -15159,9 +15045,10 @@ var init_screenshot = __esm({
|
|
|
15159
15045
|
"src/tools/screenshot.ts"() {
|
|
15160
15046
|
"use strict";
|
|
15161
15047
|
init_response_helpers();
|
|
15048
|
+
init_uid_helpers();
|
|
15162
15049
|
screenshotPageTool = {
|
|
15163
15050
|
name: "screenshot_page",
|
|
15164
|
-
description: "Capture
|
|
15051
|
+
description: "Capture page screenshot as base64 PNG.",
|
|
15165
15052
|
inputSchema: {
|
|
15166
15053
|
type: "object",
|
|
15167
15054
|
properties: {}
|
|
@@ -15169,13 +15056,13 @@ var init_screenshot = __esm({
|
|
|
15169
15056
|
};
|
|
15170
15057
|
screenshotByUidTool = {
|
|
15171
15058
|
name: "screenshot_by_uid",
|
|
15172
|
-
description: "Capture
|
|
15059
|
+
description: "Capture element screenshot by UID as base64 PNG.",
|
|
15173
15060
|
inputSchema: {
|
|
15174
15061
|
type: "object",
|
|
15175
15062
|
properties: {
|
|
15176
15063
|
uid: {
|
|
15177
15064
|
type: "string",
|
|
15178
|
-
description: "
|
|
15065
|
+
description: "Element UID from snapshot"
|
|
15179
15066
|
}
|
|
15180
15067
|
},
|
|
15181
15068
|
required: ["uid"]
|
|
@@ -15192,17 +15079,11 @@ async function handleAcceptDialog(args2) {
|
|
|
15192
15079
|
const firefox3 = await getFirefox2();
|
|
15193
15080
|
try {
|
|
15194
15081
|
await firefox3.acceptDialog(promptText);
|
|
15195
|
-
|
|
15196
|
-
if (promptText) {
|
|
15197
|
-
message += ` with text: "${promptText}"`;
|
|
15198
|
-
}
|
|
15199
|
-
return successResponse(message);
|
|
15082
|
+
return successResponse(promptText ? `\u2705 Accepted: "${promptText}"` : "\u2705 Accepted");
|
|
15200
15083
|
} catch (error) {
|
|
15201
15084
|
const errorMsg = error.message;
|
|
15202
15085
|
if (errorMsg.includes("no such alert") || errorMsg.includes("No dialog")) {
|
|
15203
|
-
throw new Error(
|
|
15204
|
-
"No active dialog found.\n\nDialogs must be accepted shortly after they appear. Make sure a dialog is currently visible on the page."
|
|
15205
|
-
);
|
|
15086
|
+
throw new Error("No active dialog");
|
|
15206
15087
|
}
|
|
15207
15088
|
throw error;
|
|
15208
15089
|
}
|
|
@@ -15216,13 +15097,11 @@ async function handleDismissDialog(_args) {
|
|
|
15216
15097
|
const firefox3 = await getFirefox2();
|
|
15217
15098
|
try {
|
|
15218
15099
|
await firefox3.dismissDialog();
|
|
15219
|
-
return successResponse("\u2705
|
|
15100
|
+
return successResponse("\u2705 Dismissed");
|
|
15220
15101
|
} catch (error) {
|
|
15221
15102
|
const errorMsg = error.message;
|
|
15222
15103
|
if (errorMsg.includes("no such alert") || errorMsg.includes("No dialog")) {
|
|
15223
|
-
throw new Error(
|
|
15224
|
-
"No active dialog found.\n\nDialogs must be dismissed shortly after they appear. Make sure a dialog is currently visible on the page."
|
|
15225
|
-
);
|
|
15104
|
+
throw new Error("No active dialog");
|
|
15226
15105
|
}
|
|
15227
15106
|
throw error;
|
|
15228
15107
|
}
|
|
@@ -15243,19 +15122,9 @@ async function handleNavigateHistory(args2) {
|
|
|
15243
15122
|
} else {
|
|
15244
15123
|
await firefox3.navigateForward();
|
|
15245
15124
|
}
|
|
15246
|
-
return successResponse(
|
|
15247
|
-
`\u2705 Navigated ${direction} in history
|
|
15248
|
-
|
|
15249
|
-
\u26A0\uFE0F UIDs from previous snapshots are now stale. Call take_snapshot before using UID-based actions.`
|
|
15250
|
-
);
|
|
15125
|
+
return successResponse(`\u2705 ${direction}`);
|
|
15251
15126
|
} catch (error) {
|
|
15252
|
-
return errorResponse(
|
|
15253
|
-
new Error(
|
|
15254
|
-
`Failed to navigate ${args2.direction || "in history"}: ${error.message}
|
|
15255
|
-
|
|
15256
|
-
The page may not have history in this direction available.`
|
|
15257
|
-
)
|
|
15258
|
-
);
|
|
15127
|
+
return errorResponse(error);
|
|
15259
15128
|
}
|
|
15260
15129
|
}
|
|
15261
15130
|
async function handleSetViewportSize(args2) {
|
|
@@ -15270,19 +15139,9 @@ async function handleSetViewportSize(args2) {
|
|
|
15270
15139
|
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
15271
15140
|
const firefox3 = await getFirefox2();
|
|
15272
15141
|
await firefox3.setViewportSize(width, height);
|
|
15273
|
-
return successResponse(
|
|
15274
|
-
`\u2705 Viewport size set to ${width}x${height} pixels
|
|
15275
|
-
|
|
15276
|
-
NOTE: Actual viewport may differ slightly in some browser modes (e.g., headless).`
|
|
15277
|
-
);
|
|
15142
|
+
return successResponse(`\u2705 ${width}x${height}`);
|
|
15278
15143
|
} catch (error) {
|
|
15279
|
-
return errorResponse(
|
|
15280
|
-
new Error(
|
|
15281
|
-
`Failed to set viewport size: ${error.message}
|
|
15282
|
-
|
|
15283
|
-
Some browser configurations may not support precise viewport sizing.`
|
|
15284
|
-
)
|
|
15285
|
-
);
|
|
15144
|
+
return errorResponse(error);
|
|
15286
15145
|
}
|
|
15287
15146
|
}
|
|
15288
15147
|
var acceptDialogTool, dismissDialogTool, navigateHistoryTool, setViewportSizeTool;
|
|
@@ -15292,20 +15151,20 @@ var init_utilities = __esm({
|
|
|
15292
15151
|
init_response_helpers();
|
|
15293
15152
|
acceptDialogTool = {
|
|
15294
15153
|
name: "accept_dialog",
|
|
15295
|
-
description: "Accept
|
|
15154
|
+
description: "Accept browser dialog. Provide promptText for prompts.",
|
|
15296
15155
|
inputSchema: {
|
|
15297
15156
|
type: "object",
|
|
15298
15157
|
properties: {
|
|
15299
15158
|
promptText: {
|
|
15300
15159
|
type: "string",
|
|
15301
|
-
description: "Text
|
|
15160
|
+
description: "Text for prompt dialogs"
|
|
15302
15161
|
}
|
|
15303
15162
|
}
|
|
15304
15163
|
}
|
|
15305
15164
|
};
|
|
15306
15165
|
dismissDialogTool = {
|
|
15307
15166
|
name: "dismiss_dialog",
|
|
15308
|
-
description: "Dismiss
|
|
15167
|
+
description: "Dismiss browser dialog.",
|
|
15309
15168
|
inputSchema: {
|
|
15310
15169
|
type: "object",
|
|
15311
15170
|
properties: {}
|
|
@@ -15313,14 +15172,14 @@ var init_utilities = __esm({
|
|
|
15313
15172
|
};
|
|
15314
15173
|
navigateHistoryTool = {
|
|
15315
15174
|
name: "navigate_history",
|
|
15316
|
-
description: "Navigate
|
|
15175
|
+
description: "Navigate history back/forward. UIDs become stale.",
|
|
15317
15176
|
inputSchema: {
|
|
15318
15177
|
type: "object",
|
|
15319
15178
|
properties: {
|
|
15320
15179
|
direction: {
|
|
15321
15180
|
type: "string",
|
|
15322
15181
|
enum: ["back", "forward"],
|
|
15323
|
-
description: "
|
|
15182
|
+
description: "back or forward"
|
|
15324
15183
|
}
|
|
15325
15184
|
},
|
|
15326
15185
|
required: ["direction"]
|
|
@@ -15328,17 +15187,17 @@ var init_utilities = __esm({
|
|
|
15328
15187
|
};
|
|
15329
15188
|
setViewportSizeTool = {
|
|
15330
15189
|
name: "set_viewport_size",
|
|
15331
|
-
description: "Set
|
|
15190
|
+
description: "Set viewport dimensions in pixels.",
|
|
15332
15191
|
inputSchema: {
|
|
15333
15192
|
type: "object",
|
|
15334
15193
|
properties: {
|
|
15335
15194
|
width: {
|
|
15336
15195
|
type: "number",
|
|
15337
|
-
description: "
|
|
15196
|
+
description: "Width in pixels"
|
|
15338
15197
|
},
|
|
15339
15198
|
height: {
|
|
15340
15199
|
type: "number",
|
|
15341
|
-
description: "
|
|
15200
|
+
description: "Height in pixels"
|
|
15342
15201
|
}
|
|
15343
15202
|
},
|
|
15344
15203
|
required: ["width", "height"]
|
|
@@ -15824,7 +15683,8 @@ async function getFirefox() {
|
|
|
15824
15683
|
profilePath: args.profilePath ?? void 0,
|
|
15825
15684
|
viewport: args.viewport ?? void 0,
|
|
15826
15685
|
args: args.firefoxArg ?? void 0,
|
|
15827
|
-
startUrl: args.startUrl ?? void 0
|
|
15686
|
+
startUrl: args.startUrl ?? void 0,
|
|
15687
|
+
acceptInsecureCerts: args.acceptInsecureCerts
|
|
15828
15688
|
};
|
|
15829
15689
|
firefox2 = new FirefoxClient(options);
|
|
15830
15690
|
await firefox2.connect();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firefox-devtools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for Firefox DevTools automation",
|
|
5
5
|
"author": "freema",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"inspector:dev": "NODE_ENV=development npx @modelcontextprotocol/inspector npx tsx src/index.ts",
|
|
29
29
|
"test": "vitest",
|
|
30
30
|
"test:run": "vitest run",
|
|
31
|
+
"test:unit": "vitest run --exclude 'tests/integration/**'",
|
|
31
32
|
"test:coverage": "vitest run --coverage",
|
|
32
33
|
"test:watch": "vitest watch",
|
|
33
34
|
"test:ui": "vitest --ui",
|