cbrowser 18.55.1 → 18.56.0
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 -4
- package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
- package/dist/analysis/accessibility-empathy.js +4 -34
- package/dist/analysis/accessibility-empathy.js.map +1 -1
- package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
- package/dist/analysis/agent-ready-audit.js +37 -32
- package/dist/analysis/agent-ready-audit.js.map +1 -1
- package/dist/analysis/competitive-benchmark.d.ts.map +1 -1
- package/dist/analysis/competitive-benchmark.js +4 -13
- package/dist/analysis/competitive-benchmark.js.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +97 -46
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +90 -1
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server-remote.d.ts.map +1 -1
- package/dist/mcp-server-remote.js +93 -118
- package/dist/mcp-server-remote.js.map +1 -1
- package/dist/mcp-tools/base/advanced-interaction-tools.d.ts +14 -0
- package/dist/mcp-tools/base/advanced-interaction-tools.d.ts.map +1 -0
- package/dist/mcp-tools/base/advanced-interaction-tools.js +174 -0
- package/dist/mcp-tools/base/advanced-interaction-tools.js.map +1 -0
- package/dist/mcp-tools/base/audit-tools.d.ts +3 -3
- package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/audit-tools.js +172 -47
- package/dist/mcp-tools/base/audit-tools.js.map +1 -1
- package/dist/mcp-tools/base/browser-management-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/browser-management-tools.js +62 -0
- package/dist/mcp-tools/base/browser-management-tools.js.map +1 -1
- package/dist/mcp-tools/base/browser-state-tools.d.ts +14 -0
- package/dist/mcp-tools/base/browser-state-tools.d.ts.map +1 -0
- package/dist/mcp-tools/base/browser-state-tools.js +229 -0
- package/dist/mcp-tools/base/browser-state-tools.js.map +1 -0
- package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.js +10 -5
- package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
- package/dist/mcp-tools/base/index.d.ts +2 -0
- package/dist/mcp-tools/base/index.d.ts.map +1 -1
- package/dist/mcp-tools/base/index.js +10 -2
- package/dist/mcp-tools/base/index.js.map +1 -1
- package/dist/mcp-tools/base/navigation-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/navigation-tools.js +9 -2
- package/dist/mcp-tools/base/navigation-tools.js.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.js +27 -2
- package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.js +3 -1
- package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
- package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
- package/dist/mcp-tools/tool-categories.js +12 -0
- package/dist/mcp-tools/tool-categories.js.map +1 -1
- package/dist/remediation/llms-txt.js +4 -14
- package/dist/remediation/llms-txt.js.map +1 -1
- package/dist/remediation/structured-data.js +4 -13
- package/dist/remediation/structured-data.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBrowser MCP Tools - Advanced Interaction Tools
|
|
3
|
+
*
|
|
4
|
+
* hover, type_text, press_key, handle_dialog, upload_file, drag
|
|
5
|
+
*
|
|
6
|
+
* @copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com https://cbrowser.ai
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
/**
|
|
11
|
+
* Register advanced interaction tools (6 tools)
|
|
12
|
+
*/
|
|
13
|
+
export function registerAdvancedInteractionTools(server, { getBrowser, getBrowserByToken }) {
|
|
14
|
+
// ── hover ──
|
|
15
|
+
server.registerTool("hover", {
|
|
16
|
+
title: "Hover Over Element",
|
|
17
|
+
description: "Hover over an element to trigger tooltips, dropdowns, or hover states. Uses CSS selector or smart selector.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
selector: z.string().describe("CSS selector or text description of the element to hover"),
|
|
20
|
+
_browserToken: z.string().optional().describe("Browser session token"),
|
|
21
|
+
},
|
|
22
|
+
annotations: { title: "Hover", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
23
|
+
}, async ({ selector, _browserToken }) => {
|
|
24
|
+
let b, token;
|
|
25
|
+
if (getBrowserByToken) {
|
|
26
|
+
const r = await getBrowserByToken(_browserToken);
|
|
27
|
+
b = r.browser;
|
|
28
|
+
token = r.token;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
b = await getBrowser();
|
|
32
|
+
}
|
|
33
|
+
const result = await b.hover(selector);
|
|
34
|
+
return { content: [{ type: "text", text: JSON.stringify({ ...result, _browserToken: token }, null, 2) }] };
|
|
35
|
+
});
|
|
36
|
+
// ── type_text ──
|
|
37
|
+
server.registerTool("type_text", {
|
|
38
|
+
title: "Type Text (Keyboard)",
|
|
39
|
+
description: "Type text character by character using keyboard events. Unlike 'fill', this triggers keydown/keypress/keyup events for each character. Use for inputs that need real keyboard events (autocomplete, search-as-you-type, game inputs).",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
text: z.string().describe("Text to type"),
|
|
42
|
+
delay: z.number().optional().default(50).describe("Delay between keystrokes in ms (default 50)"),
|
|
43
|
+
_browserToken: z.string().optional().describe("Browser session token"),
|
|
44
|
+
},
|
|
45
|
+
annotations: { title: "Type Text", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
46
|
+
}, async ({ text, delay, _browserToken }) => {
|
|
47
|
+
let b, token;
|
|
48
|
+
if (getBrowserByToken) {
|
|
49
|
+
const r = await getBrowserByToken(_browserToken);
|
|
50
|
+
b = r.browser;
|
|
51
|
+
token = r.token;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
b = await getBrowser();
|
|
55
|
+
}
|
|
56
|
+
const page = await b.getPage();
|
|
57
|
+
await page.keyboard.type(text, { delay: delay || 50 });
|
|
58
|
+
return { content: [{ type: "text", text: JSON.stringify({ typed: text, characters: text.length, _browserToken: token }, null, 2) }] };
|
|
59
|
+
});
|
|
60
|
+
// ── press_key ──
|
|
61
|
+
server.registerTool("press_key", {
|
|
62
|
+
title: "Press Key",
|
|
63
|
+
description: "Press a keyboard key (Enter, Tab, Escape, ArrowDown, Backspace, etc). Supports modifier combos: 'Control+a', 'Shift+Enter', 'Meta+c'. Full list: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
key: z.string().describe("Key to press (e.g., 'Enter', 'Tab', 'Escape', 'ArrowDown', 'Control+a', 'Meta+c')"),
|
|
66
|
+
_browserToken: z.string().optional().describe("Browser session token"),
|
|
67
|
+
},
|
|
68
|
+
annotations: { title: "Press Key", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
69
|
+
}, async ({ key, _browserToken }) => {
|
|
70
|
+
let b, token;
|
|
71
|
+
if (getBrowserByToken) {
|
|
72
|
+
const r = await getBrowserByToken(_browserToken);
|
|
73
|
+
b = r.browser;
|
|
74
|
+
token = r.token;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
b = await getBrowser();
|
|
78
|
+
}
|
|
79
|
+
const page = await b.getPage();
|
|
80
|
+
await page.keyboard.press(key);
|
|
81
|
+
return { content: [{ type: "text", text: JSON.stringify({ pressed: key, _browserToken: token }, null, 2) }] };
|
|
82
|
+
});
|
|
83
|
+
// ── handle_dialog ──
|
|
84
|
+
server.registerTool("handle_dialog", {
|
|
85
|
+
title: "Handle JavaScript Dialog",
|
|
86
|
+
description: "Set how to handle the next JavaScript dialog (alert, confirm, prompt). Call BEFORE the action that triggers the dialog. For prompts, provide the text to enter.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
action: z.enum(["accept", "dismiss"]).describe("Accept or dismiss the dialog"),
|
|
89
|
+
promptText: z.string().optional().describe("Text to enter for prompt() dialogs"),
|
|
90
|
+
_browserToken: z.string().optional().describe("Browser session token"),
|
|
91
|
+
},
|
|
92
|
+
annotations: { title: "Handle Dialog", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
93
|
+
}, async ({ action, promptText, _browserToken }) => {
|
|
94
|
+
let b, token;
|
|
95
|
+
if (getBrowserByToken) {
|
|
96
|
+
const r = await getBrowserByToken(_browserToken);
|
|
97
|
+
b = r.browser;
|
|
98
|
+
token = r.token;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
b = await getBrowser();
|
|
102
|
+
}
|
|
103
|
+
const page = await b.getPage();
|
|
104
|
+
// Set up a one-time dialog handler
|
|
105
|
+
const dialogPromise = new Promise((resolve) => {
|
|
106
|
+
page.once("dialog", async (dialog) => {
|
|
107
|
+
const info = { type: dialog.type(), message: dialog.message() };
|
|
108
|
+
if (action === "accept") {
|
|
109
|
+
await dialog.accept(promptText);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
await dialog.dismiss();
|
|
113
|
+
}
|
|
114
|
+
resolve(info);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
// Wait briefly for the dialog (it may already be pending)
|
|
118
|
+
const timeout = new Promise((resolve) => setTimeout(() => resolve(null), 5000));
|
|
119
|
+
const result = await Promise.race([dialogPromise, timeout]);
|
|
120
|
+
if (result) {
|
|
121
|
+
return { content: [{ type: "text", text: JSON.stringify({ handled: true, dialogType: result.type, message: result.message, action, _browserToken: token }, null, 2) }] };
|
|
122
|
+
}
|
|
123
|
+
return { content: [{ type: "text", text: JSON.stringify({ handled: false, message: "No dialog appeared within 5 seconds. Call this BEFORE the action that triggers the dialog.", _browserToken: token }, null, 2) }] };
|
|
124
|
+
});
|
|
125
|
+
// ── upload_file ──
|
|
126
|
+
server.registerTool("upload_file", {
|
|
127
|
+
title: "Upload File",
|
|
128
|
+
description: "Upload file(s) to a file input element. Works with <input type='file'> elements.",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
selector: z.string().describe("CSS selector for the file input element"),
|
|
131
|
+
filePaths: z.array(z.string()).describe("Array of absolute file paths to upload"),
|
|
132
|
+
_browserToken: z.string().optional().describe("Browser session token"),
|
|
133
|
+
},
|
|
134
|
+
annotations: { title: "Upload File", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
135
|
+
}, async ({ selector, filePaths, _browserToken }) => {
|
|
136
|
+
let b, token;
|
|
137
|
+
if (getBrowserByToken) {
|
|
138
|
+
const r = await getBrowserByToken(_browserToken);
|
|
139
|
+
b = r.browser;
|
|
140
|
+
token = r.token;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
b = await getBrowser();
|
|
144
|
+
}
|
|
145
|
+
const page = await b.getPage();
|
|
146
|
+
await page.locator(selector).setInputFiles(filePaths);
|
|
147
|
+
return { content: [{ type: "text", text: JSON.stringify({ uploaded: filePaths, selector, _browserToken: token }, null, 2) }] };
|
|
148
|
+
});
|
|
149
|
+
// ── drag ──
|
|
150
|
+
server.registerTool("drag", {
|
|
151
|
+
title: "Drag and Drop",
|
|
152
|
+
description: "Drag an element to a target location. Simulates mouse press, move, and release.",
|
|
153
|
+
inputSchema: {
|
|
154
|
+
source: z.string().describe("CSS selector for the element to drag"),
|
|
155
|
+
target: z.string().describe("CSS selector for the drop target"),
|
|
156
|
+
_browserToken: z.string().optional().describe("Browser session token"),
|
|
157
|
+
},
|
|
158
|
+
annotations: { title: "Drag and Drop", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
159
|
+
}, async ({ source, target, _browserToken }) => {
|
|
160
|
+
let b, token;
|
|
161
|
+
if (getBrowserByToken) {
|
|
162
|
+
const r = await getBrowserByToken(_browserToken);
|
|
163
|
+
b = r.browser;
|
|
164
|
+
token = r.token;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
b = await getBrowser();
|
|
168
|
+
}
|
|
169
|
+
const page = await b.getPage();
|
|
170
|
+
await page.dragAndDrop(source, target);
|
|
171
|
+
return { content: [{ type: "text", text: JSON.stringify({ dragged: source, droppedOn: target, _browserToken: token }, null, 2) }] };
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=advanced-interaction-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advanced-interaction-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/base/advanced-interaction-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,MAAM,UAAU,gCAAgC,CAC9C,MAAiB,EACjB,EAAE,UAAU,EAAE,iBAAiB,EAA2B;IAG1D,cAAc;IACd,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE;QAC3B,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,6GAA6G;QAC1H,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC;YACzF,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;SACvE;QACD,WAAW,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE;KACzH,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE;QACvC,IAAI,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,iBAAiB,EAAE,CAAC;YAAC,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;YAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAAC,CAAC;aACvG,CAAC;YAAC,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAAC,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACvC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACtH,CAAC,CAAC,CAAC;IAEH,kBAAkB;IAClB,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE;QAC/B,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EAAE,uOAAuO;QACpP,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YACzC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,6CAA6C,CAAC;YAChG,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;SACvE;QACD,WAAW,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE;KAC9H,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE;QAC1C,IAAI,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,iBAAiB,EAAE,CAAC;YAAC,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;YAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAAC,CAAC;aACvG,CAAC;YAAC,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAAC,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC;QACvD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACjJ,CAAC,CAAC,CAAC;IAEH,kBAAkB;IAClB,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE;QAC/B,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE,uOAAuO;QACpP,WAAW,EAAE;YACX,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mFAAmF,CAAC;YAC7G,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;SACvE;QACD,WAAW,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE;KAC9H,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,EAAE,EAAE;QAClC,IAAI,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,iBAAiB,EAAE,CAAC;YAAC,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;YAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAAC,CAAC;aACvG,CAAC;YAAC,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAAC,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACzH,CAAC,CAAC,CAAC;IAEH,sBAAsB;IACtB,MAAM,CAAC,YAAY,CAAC,eAAe,EAAE;QACnC,KAAK,EAAE,0BAA0B;QACjC,WAAW,EAAE,iKAAiK;QAC9K,WAAW,EAAE;YACX,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,8BAA8B,CAAC;YAC9E,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;YAChF,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;SACvE;QACD,WAAW,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE;KAClI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,EAAE;QACjD,IAAI,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,iBAAiB,EAAE,CAAC;YAAC,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;YAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAAC,CAAC;aACvG,CAAC;YAAC,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAAC,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;QAE/B,mCAAmC;QACnC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAoC,CAAC,OAAO,EAAE,EAAE;YAC/E,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;gBACnC,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;gBAChE,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;oBACxB,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBAClC,CAAC;qBAAM,CAAC;oBACN,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;gBACzB,CAAC;gBACD,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,0DAA0D;QAC1D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACtF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;QAE5D,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACpL,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,4FAA4F,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAClO,CAAC,CAAC,CAAC;IAEH,oBAAoB;IACpB,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE;QACjC,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,kFAAkF;QAC/F,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;YACxE,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,wCAAwC,CAAC;YACjF,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;SACvE;QACD,WAAW,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE;KAChI,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,EAAE;QAClD,IAAI,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,iBAAiB,EAAE,CAAC;YAAC,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;YAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAAC,CAAC;aACvG,CAAC;YAAC,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAAC,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAC1I,CAAC,CAAC,CAAC;IAEH,aAAa;IACb,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE;QAC1B,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,iFAAiF;QAC9F,WAAW,EAAE;YACX,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;YACnE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;YAC/D,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;SACvE;QACD,WAAW,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE;KAClI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,EAAE;QAC7C,IAAI,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,iBAAiB,EAAE,CAAC;YAAC,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;YAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAAC,CAAC;aACvG,CAAC;YAAC,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAAC,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACvC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/I,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com https://cbrowser.ai
|
|
5
5
|
* @license MIT
|
|
6
6
|
*/
|
|
7
|
-
import type { McpServer } from "../types.js";
|
|
7
|
+
import type { McpServer, ToolRegistrationContext } from "../types.js";
|
|
8
8
|
/**
|
|
9
|
-
* Register audit tools (4 tools
|
|
9
|
+
* Register audit tools (4 tools + site_cognitive_assessment + visual_cognitive_story)
|
|
10
10
|
*/
|
|
11
|
-
export declare function registerAuditTools(server: McpServer): void;
|
|
11
|
+
export declare function registerAuditTools(server: McpServer, context?: ToolRegistrationContext): void;
|
|
12
12
|
//# sourceMappingURL=audit-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/audit-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"audit-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/audit-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAStE;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CA49B7F"}
|
|
@@ -8,9 +8,10 @@ import { z } from "zod";
|
|
|
8
8
|
import { runAgentReadyAudit, runCompetitiveBenchmark, runEmpathyAudit, runWebMCPReadyAudit, } from "../../analysis/index.js";
|
|
9
9
|
import { listAccessibilityPersonas } from "../../personas.js";
|
|
10
10
|
/**
|
|
11
|
-
* Register audit tools (4 tools
|
|
11
|
+
* Register audit tools (4 tools + site_cognitive_assessment + visual_cognitive_story)
|
|
12
12
|
*/
|
|
13
|
-
export function registerAuditTools(server) {
|
|
13
|
+
export function registerAuditTools(server, context) {
|
|
14
|
+
const getBrowserByToken = context?.getBrowserByToken;
|
|
14
15
|
server.registerTool("agent_ready_audit", {
|
|
15
16
|
title: "Agent-Ready Audit",
|
|
16
17
|
description: "Audit a website for AI-agent friendliness. Analyzes findability, stability, accessibility, and semantics. Returns score (0-100), grade (A-F), issues, and remediation recommendations.",
|
|
@@ -287,6 +288,9 @@ export function registerAuditTools(server) {
|
|
|
287
288
|
proxy: z.string().optional().describe("Proxy server URL for geo-accurate testing (e.g., 'http://user:pass@proxy.example.com:12321'). Routes browser through the proxy so sites see the proxy's IP/location instead of the server's."),
|
|
288
289
|
geoRegion: z.string().optional().describe("Route through a residential proxy in this region. Available: us-west, us-east, us-central, uk, germany, japan. Overrides proxy parameter."),
|
|
289
290
|
device: z.string().optional().describe("Device emulation: 'mobile', 'tablet', 'desktop', or specific device like 'iPhone 15'. Default: desktop."),
|
|
291
|
+
waitAfterLoad: z.number().optional().describe("Extra milliseconds to wait after page loads. Use for sites with client-side translation or deferred rendering (e.g., 3000-5000 for i18n sites)."),
|
|
292
|
+
waitForSelector: z.string().optional().describe("CSS selector to wait for after load. Example: '[data-translated]', '.content-loaded'. Times out gracefully."),
|
|
293
|
+
_browserToken: z.string().optional().describe("Reuse an existing browser session. Essential for testing translated pages: first navigate + click the language selector, then pass the token here to assess the already-translated page. Without this, the tool creates a fresh browser that defaults to English."),
|
|
290
294
|
},
|
|
291
295
|
annotations: {
|
|
292
296
|
title: "Site Cognitive Assessment",
|
|
@@ -295,7 +299,7 @@ export function registerAuditTools(server) {
|
|
|
295
299
|
idempotentHint: false,
|
|
296
300
|
openWorldHint: true,
|
|
297
301
|
},
|
|
298
|
-
}, async ({ url, personas, threshold, userLocation, userTimezone, userLanguage, proxy, geoRegion }) => {
|
|
302
|
+
}, async ({ url, personas, threshold, userLocation, userTimezone, userLanguage, proxy, geoRegion, waitAfterLoad, waitForSelector, _browserToken }) => {
|
|
299
303
|
const startTime = Date.now();
|
|
300
304
|
const personaList = (personas || "first-timer,cognitive-adhd").split(",").map(s => s.trim()).filter(Boolean);
|
|
301
305
|
// Derive locale from userLanguage (e.g., "en-US" → locale "en-US", language "en")
|
|
@@ -312,42 +316,131 @@ export function registerAuditTools(server) {
|
|
|
312
316
|
},
|
|
313
317
|
};
|
|
314
318
|
// ── Gate 1: Bot Detection Probe ──
|
|
319
|
+
let ownsBrowser = true;
|
|
320
|
+
let proxyConfig;
|
|
315
321
|
try {
|
|
316
322
|
const { CBrowser: BrowserClass } = await import("../../browser.js");
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
323
|
+
let browser;
|
|
324
|
+
let token;
|
|
325
|
+
// If _browserToken is provided, reuse existing session (preserves language selection, cookies, etc.)
|
|
326
|
+
if (_browserToken && getBrowserByToken) {
|
|
327
|
+
const session = await getBrowserByToken(_browserToken);
|
|
328
|
+
browser = session.browser;
|
|
329
|
+
token = session.token;
|
|
330
|
+
ownsBrowser = false; // Don't close — it belongs to the session pool
|
|
331
|
+
response._browserToken = token;
|
|
332
|
+
response.sessionReused = true;
|
|
326
333
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
else {
|
|
335
|
+
// Create fresh browser with locale and proxy settings
|
|
336
|
+
// Resolve geoRegion to proxy config (overrides raw proxy)
|
|
337
|
+
if (geoRegion && !proxy) {
|
|
338
|
+
const { getGeoProxy } = await import("../../geo-proxy.js");
|
|
339
|
+
const geoProxy = getGeoProxy(geoRegion);
|
|
340
|
+
if (geoProxy) {
|
|
341
|
+
proxy = `http://${encodeURIComponent(geoProxy.username)}:${encodeURIComponent(geoProxy.password)}@${geoProxy.server.replace('http://', '')}`;
|
|
342
|
+
response.geoRegion = geoRegion;
|
|
343
|
+
}
|
|
336
344
|
}
|
|
337
|
-
|
|
338
|
-
|
|
345
|
+
proxyConfig = proxy ? (() => {
|
|
346
|
+
try {
|
|
347
|
+
const u = new URL(proxy);
|
|
348
|
+
return {
|
|
349
|
+
server: `${u.protocol}//${u.hostname}:${u.port}`,
|
|
350
|
+
...(u.username ? { username: decodeURIComponent(u.username) } : {}),
|
|
351
|
+
...(u.password ? { password: decodeURIComponent(u.password) } : {}),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return { server: proxy };
|
|
356
|
+
}
|
|
357
|
+
})() : undefined;
|
|
358
|
+
browser = new BrowserClass({
|
|
359
|
+
headless: true,
|
|
360
|
+
locale: expectedLocale,
|
|
361
|
+
...(userTimezone ? { timezone: userTimezone } : {}),
|
|
362
|
+
...(proxyConfig ? { proxy: proxyConfig, timeout: 60000 } : {}),
|
|
363
|
+
});
|
|
364
|
+
await browser.launch();
|
|
365
|
+
// Pre-seed localStorage for client-side i18n frameworks
|
|
366
|
+
if (expectedLang !== "en") {
|
|
367
|
+
try {
|
|
368
|
+
const page = await browser.getPage();
|
|
369
|
+
await page.context().addInitScript((lang) => {
|
|
370
|
+
try {
|
|
371
|
+
localStorage.setItem("cbrowser-lang", lang);
|
|
372
|
+
localStorage.setItem("lang", lang);
|
|
373
|
+
localStorage.setItem("locale", lang);
|
|
374
|
+
}
|
|
375
|
+
catch { }
|
|
376
|
+
}, expectedLang);
|
|
377
|
+
}
|
|
378
|
+
catch { }
|
|
339
379
|
}
|
|
340
|
-
}
|
|
341
|
-
const browser = new BrowserClass({
|
|
342
|
-
headless: true,
|
|
343
|
-
locale: expectedLocale,
|
|
344
|
-
...(userTimezone ? { timezone: userTimezone } : {}),
|
|
345
|
-
...(proxyConfig ? { proxy: proxyConfig, timeout: 60000 } : {}),
|
|
346
|
-
});
|
|
347
|
-
await browser.launch();
|
|
380
|
+
}
|
|
348
381
|
try {
|
|
349
|
-
|
|
382
|
+
// For non-English, ensure enough total wait time for client-side translation:
|
|
383
|
+
// waitForSelector catches the marker, then waitAfterLoad gives GT time to finish text swaps
|
|
384
|
+
const effectiveWaitAfterLoad = waitAfterLoad || (expectedLang !== "en" ? 3000 : 0);
|
|
385
|
+
const navResult = await browser.navigate(url, {
|
|
386
|
+
...(effectiveWaitAfterLoad ? { waitAfterLoad: effectiveWaitAfterLoad } : {}),
|
|
387
|
+
...(waitForSelector ? { waitForSelector } : {}),
|
|
388
|
+
});
|
|
350
389
|
const page = await browser.getPage();
|
|
390
|
+
// Verify translation actually happened by checking page content, not just a DOM attribute.
|
|
391
|
+
// The selector may exist (set by our own addInitScript) even when GT failed silently.
|
|
392
|
+
if (expectedLang !== "en") {
|
|
393
|
+
const contentCheck = await page.evaluate(() => {
|
|
394
|
+
// Check VISIBLE body content only (not <title> which can't be client-side translated)
|
|
395
|
+
const h1 = document.querySelector("h1")?.textContent || "";
|
|
396
|
+
const nav = document.querySelector("nav")?.textContent || "";
|
|
397
|
+
const body = document.body?.innerText?.substring(0, 500) || "";
|
|
398
|
+
const visibleText = h1 + " " + nav + " " + body;
|
|
399
|
+
// Check primary nav items that are always above-fold and always visible.
|
|
400
|
+
// Translation is viewport-progressive, so only check items guaranteed
|
|
401
|
+
// to be in the first viewport at 1920x1080.
|
|
402
|
+
const englishIndicators = ["Docs", "Pricing", "Log in", "Sign up"];
|
|
403
|
+
const stillEnglish = englishIndicators.filter(en => visibleText.includes(en)).length;
|
|
404
|
+
// Also check page lang attribute
|
|
405
|
+
const pageLang = document.documentElement.lang || "en";
|
|
406
|
+
return { stillEnglish, totalChecked: englishIndicators.length, pageLang, visibleSample: visibleText.substring(0, 100) };
|
|
407
|
+
}).catch(() => ({ stillEnglish: 0, totalChecked: 0, pageLang: "en", visibleSample: "" }));
|
|
408
|
+
if (contentCheck.stillEnglish >= 2) {
|
|
409
|
+
// Multiple English indicators still present — translation didn't work
|
|
410
|
+
response.translationWarning = {
|
|
411
|
+
expected: expectedLocale,
|
|
412
|
+
actual: "en",
|
|
413
|
+
englishIndicatorsFound: contentCheck.stillEnglish,
|
|
414
|
+
pageLang: contentCheck.pageLang,
|
|
415
|
+
message: `Page content appears to still be in English despite userLanguage="${expectedLocale}". ${contentCheck.stillEnglish}/${contentCheck.totalChecked} English UI strings found in visible content. The site may require a language selector click, not just Accept-Language. Scores reflect English content, not ${expectedLocale}.`,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
else if (contentCheck.stillEnglish === 0) {
|
|
419
|
+
response.translationNote = {
|
|
420
|
+
language: expectedLocale,
|
|
421
|
+
verified: true,
|
|
422
|
+
pageLang: contentCheck.pageLang,
|
|
423
|
+
message: `Page content verified as translated — no English UI indicators found in visible text. Scores reflect ${expectedLocale} content.`,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
response.translationNote = {
|
|
428
|
+
language: expectedLocale,
|
|
429
|
+
verified: "partial",
|
|
430
|
+
pageLang: contentCheck.pageLang,
|
|
431
|
+
englishIndicatorsRemaining: contentCheck.stillEnglish,
|
|
432
|
+
message: `Page mostly translated — ${contentCheck.stillEnglish}/${contentCheck.totalChecked} English UI strings still present in visible content. Scores mostly reflect ${expectedLocale} content.`,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Also surface waitForSelector timeout if relevant
|
|
437
|
+
if (waitForSelector && navResult.waitSelectorTimedOut) {
|
|
438
|
+
response.waitWarning = {
|
|
439
|
+
selector: waitForSelector,
|
|
440
|
+
timedOut: true,
|
|
441
|
+
message: `waitForSelector "${waitForSelector}" timed out — element not found after page load.`,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
351
444
|
const pageTitle = await page.title().catch(() => "");
|
|
352
445
|
const bodyText = await page.evaluate(() => document.body?.innerText?.slice(0, 500) || "").catch(() => "");
|
|
353
446
|
const elementCount = await page.evaluate(() => document.querySelectorAll("a, button, input, select, textarea").length).catch(() => 0);
|
|
@@ -360,7 +453,7 @@ export function registerAuditTools(server) {
|
|
|
360
453
|
const lowerBody = bodyText.toLowerCase();
|
|
361
454
|
const blockSignature = BLOCK_SIGNATURES.find(sig => lowerTitle.includes(sig) || lowerBody.includes(sig));
|
|
362
455
|
const isEmpty = elementCount < 5 && bodyText.length < 100;
|
|
363
|
-
// Language mismatch detection
|
|
456
|
+
// Language mismatch detection — runs regardless of waitForSelector
|
|
364
457
|
const pageLang = await page.evaluate(() => document.documentElement.lang || "").catch(() => "");
|
|
365
458
|
const pageLangShort = pageLang.split("-")[0].toLowerCase();
|
|
366
459
|
const languageMismatch = pageLangShort && pageLangShort !== expectedLang && pageLangShort !== "";
|
|
@@ -382,7 +475,8 @@ export function registerAuditTools(server) {
|
|
|
382
475
|
response.result = "BLOCKED";
|
|
383
476
|
response.message = `Site is bot-blocked (${blockSignature || "empty DOM"}). Cannot assess cognitive experience. Try with stealth mode enabled or use a staging URL.`;
|
|
384
477
|
response.duration = `${Date.now() - startTime}ms`;
|
|
385
|
-
|
|
478
|
+
if (ownsBrowser)
|
|
479
|
+
await browser.close();
|
|
386
480
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
387
481
|
}
|
|
388
482
|
response.gate1 = { status: "accessible", pageTitle, elementCount };
|
|
@@ -412,7 +506,8 @@ export function registerAuditTools(server) {
|
|
|
412
506
|
response.result = "UNQUALIFIED";
|
|
413
507
|
response.message = `Site scored ${score}/100 (grade ${grade}), below the ${threshold} threshold. Fix structural issues before persona testing adds value.`;
|
|
414
508
|
response.duration = `${Date.now() - startTime}ms`;
|
|
415
|
-
|
|
509
|
+
if (ownsBrowser)
|
|
510
|
+
await browser.close();
|
|
416
511
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
417
512
|
}
|
|
418
513
|
}
|
|
@@ -488,17 +583,34 @@ export function registerAuditTools(server) {
|
|
|
488
583
|
response.recommendations = recommendations.slice(0, 5);
|
|
489
584
|
response.result = "COMPLETE";
|
|
490
585
|
response.duration = `${Date.now() - startTime}ms`;
|
|
491
|
-
|
|
586
|
+
if (ownsBrowser)
|
|
587
|
+
await browser.close();
|
|
492
588
|
}
|
|
493
589
|
catch (navErr) {
|
|
494
|
-
|
|
495
|
-
|
|
590
|
+
const errMsg = navErr instanceof Error ? navErr.message : String(navErr);
|
|
591
|
+
const isProxyBlock = errMsg.includes("ERR_TUNNEL_CONNECTION_FAILED") || errMsg.includes("ERR_PROXY_CONNECTION_FAILED");
|
|
592
|
+
const isProxyTimeout = errMsg.includes("ERR_NETWORK_CHANGED") && geoRegion;
|
|
593
|
+
if (isProxyBlock || isProxyTimeout) {
|
|
594
|
+
response.gate1 = {
|
|
595
|
+
status: "proxy_blocked",
|
|
596
|
+
error: errMsg.split("\n")[0],
|
|
597
|
+
geoRegion: geoRegion || undefined,
|
|
598
|
+
};
|
|
599
|
+
response.result = "PROXY_BLOCKED";
|
|
600
|
+
response.message = `The site rejected the residential proxy connection${geoRegion ? ` (${geoRegion})` : ""}. This means ${url} actively blocks proxy/VPN traffic — this is the site's anti-bot defense, not a CBrowser issue.\n\nRecommendations:\n1. Re-run without --geo-region to test from the server's direct IP\n2. Try a different geo region (the site may block specific IP ranges)\n3. If you need geo-accurate results, use a datacenter proxy or test from a machine in the target region`;
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
response.gate1 = { status: "error", error: errMsg };
|
|
604
|
+
response.result = "ERROR";
|
|
605
|
+
}
|
|
496
606
|
response.duration = `${Date.now() - startTime}ms`;
|
|
497
|
-
|
|
607
|
+
if (ownsBrowser)
|
|
608
|
+
await browser.close();
|
|
498
609
|
}
|
|
499
610
|
}
|
|
500
611
|
catch (browserErr) {
|
|
501
|
-
|
|
612
|
+
const errMsg = browserErr instanceof Error ? browserErr.message : String(browserErr);
|
|
613
|
+
response.gate1 = { status: "error", error: errMsg };
|
|
502
614
|
response.result = "ERROR";
|
|
503
615
|
response.duration = `${Date.now() - startTime}ms`;
|
|
504
616
|
}
|
|
@@ -513,6 +625,7 @@ export function registerAuditTools(server) {
|
|
|
513
625
|
persona: z.string().optional().default("cognitive-adhd").describe("Persona name"),
|
|
514
626
|
device: z.string().optional().describe("Device to emulate: 'mobile', 'tablet', 'desktop', or a specific device like 'iPhone 15', 'Pixel 7', 'iPad Pro'. Default: desktop (1920x1080)."),
|
|
515
627
|
useValues: z.boolean().optional().default(false).describe("Enable motivational value influence on saliency map generation, attention scoring, and narrative. Default: false."),
|
|
628
|
+
_browserToken: z.string().optional().describe("Reuse an existing browser session. Useful for testing translated or state-dependent pages."),
|
|
516
629
|
},
|
|
517
630
|
annotations: {
|
|
518
631
|
title: "Visual Cognitive Story",
|
|
@@ -521,18 +634,29 @@ export function registerAuditTools(server) {
|
|
|
521
634
|
idempotentHint: false,
|
|
522
635
|
openWorldHint: true,
|
|
523
636
|
},
|
|
524
|
-
}, async ({ url, persona, device, useValues }) => {
|
|
637
|
+
}, async ({ url, persona, device, useValues, _browserToken }) => {
|
|
525
638
|
const startTime = Date.now();
|
|
526
639
|
const { join } = await import("path");
|
|
527
640
|
const { tmpdir } = await import("os");
|
|
528
641
|
const { writeFileSync, mkdirSync, existsSync, unlinkSync } = await import("fs");
|
|
642
|
+
let ownsVcsBrowser = true;
|
|
529
643
|
try {
|
|
530
644
|
const { CBrowser: BrowserClass } = await import("../../browser.js");
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
645
|
+
let browser;
|
|
646
|
+
let vcsToken;
|
|
647
|
+
if (_browserToken && getBrowserByToken) {
|
|
648
|
+
const session = await getBrowserByToken(_browserToken);
|
|
649
|
+
browser = session.browser;
|
|
650
|
+
vcsToken = session.token;
|
|
651
|
+
ownsVcsBrowser = false;
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
browser = new BrowserClass({
|
|
655
|
+
headless: true,
|
|
656
|
+
...(device ? { device: device.toLowerCase() } : { viewportWidth: 1920, viewportHeight: 1080 }),
|
|
657
|
+
});
|
|
658
|
+
await browser.launch();
|
|
659
|
+
}
|
|
536
660
|
try {
|
|
537
661
|
await browser.navigate(url);
|
|
538
662
|
await new Promise(r => setTimeout(r, 2000));
|
|
@@ -820,7 +944,8 @@ export function registerAuditTools(server) {
|
|
|
820
944
|
return { content };
|
|
821
945
|
}
|
|
822
946
|
finally {
|
|
823
|
-
|
|
947
|
+
if (ownsVcsBrowser)
|
|
948
|
+
await browser.close();
|
|
824
949
|
}
|
|
825
950
|
}
|
|
826
951
|
catch (err) {
|