cbrowser 18.55.1 → 18.57.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.
Files changed (68) hide show
  1. package/README.md +4 -4
  2. package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
  3. package/dist/analysis/accessibility-empathy.js +4 -34
  4. package/dist/analysis/accessibility-empathy.js.map +1 -1
  5. package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
  6. package/dist/analysis/agent-ready-audit.js +37 -32
  7. package/dist/analysis/agent-ready-audit.js.map +1 -1
  8. package/dist/analysis/competitive-benchmark.d.ts.map +1 -1
  9. package/dist/analysis/competitive-benchmark.js +4 -13
  10. package/dist/analysis/competitive-benchmark.js.map +1 -1
  11. package/dist/browser.d.ts.map +1 -1
  12. package/dist/browser.js +97 -46
  13. package/dist/browser.js.map +1 -1
  14. package/dist/cli.js +90 -1
  15. package/dist/cli.js.map +1 -1
  16. package/dist/mcp-server-remote.d.ts.map +1 -1
  17. package/dist/mcp-server-remote.js +93 -118
  18. package/dist/mcp-server-remote.js.map +1 -1
  19. package/dist/mcp-tools/base/advanced-interaction-tools.d.ts +14 -0
  20. package/dist/mcp-tools/base/advanced-interaction-tools.d.ts.map +1 -0
  21. package/dist/mcp-tools/base/advanced-interaction-tools.js +174 -0
  22. package/dist/mcp-tools/base/advanced-interaction-tools.js.map +1 -0
  23. package/dist/mcp-tools/base/audit-tools.d.ts +3 -3
  24. package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
  25. package/dist/mcp-tools/base/audit-tools.js +179 -47
  26. package/dist/mcp-tools/base/audit-tools.js.map +1 -1
  27. package/dist/mcp-tools/base/browser-management-tools.d.ts.map +1 -1
  28. package/dist/mcp-tools/base/browser-management-tools.js +62 -0
  29. package/dist/mcp-tools/base/browser-management-tools.js.map +1 -1
  30. package/dist/mcp-tools/base/browser-state-tools.d.ts +14 -0
  31. package/dist/mcp-tools/base/browser-state-tools.d.ts.map +1 -0
  32. package/dist/mcp-tools/base/browser-state-tools.js +229 -0
  33. package/dist/mcp-tools/base/browser-state-tools.js.map +1 -0
  34. package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
  35. package/dist/mcp-tools/base/cognitive-tools.js +10 -5
  36. package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
  37. package/dist/mcp-tools/base/index.d.ts +2 -0
  38. package/dist/mcp-tools/base/index.d.ts.map +1 -1
  39. package/dist/mcp-tools/base/index.js +10 -2
  40. package/dist/mcp-tools/base/index.js.map +1 -1
  41. package/dist/mcp-tools/base/navigation-tools.d.ts.map +1 -1
  42. package/dist/mcp-tools/base/navigation-tools.js +9 -2
  43. package/dist/mcp-tools/base/navigation-tools.js.map +1 -1
  44. package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
  45. package/dist/mcp-tools/base/persona-comparison-tools.js +27 -2
  46. package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
  47. package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
  48. package/dist/mcp-tools/base/visual-testing-tools.js +3 -1
  49. package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
  50. package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
  51. package/dist/mcp-tools/tool-categories.js +12 -0
  52. package/dist/mcp-tools/tool-categories.js.map +1 -1
  53. package/dist/remediation/llms-txt.js +4 -14
  54. package/dist/remediation/llms-txt.js.map +1 -1
  55. package/dist/remediation/structured-data.js +4 -13
  56. package/dist/remediation/structured-data.js.map +1 -1
  57. package/dist/types.d.ts +16 -0
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/types.js.map +1 -1
  60. package/dist/visual/cognitive-transport-chain.d.ts +6 -0
  61. package/dist/visual/cognitive-transport-chain.d.ts.map +1 -1
  62. package/dist/visual/cognitive-transport-chain.js +41 -0
  63. package/dist/visual/cognitive-transport-chain.js.map +1 -1
  64. package/dist/visual/cognitive-transport.d.ts +6 -0
  65. package/dist/visual/cognitive-transport.d.ts.map +1 -1
  66. package/dist/visual/cognitive-transport.js +164 -0
  67. package/dist/visual/cognitive-transport.js.map +1 -1
  68. 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":"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"}
@@ -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: agent_ready_audit, competitive_benchmark, empathy_audit, webmcp_ready_audit)
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;AAS7C;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAo2B1D"}
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,CAo+B7F"}
@@ -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: agent_ready_audit, competitive_benchmark, empathy_audit, webmcp_ready_audit)
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
- // Set browser locale to match user's expected language
318
- // Resolve geoRegion to proxy config (overrides raw proxy)
319
- if (geoRegion && !proxy) {
320
- const { getGeoProxy } = await import("../../geo-proxy.js");
321
- const geoProxy = getGeoProxy(geoRegion);
322
- if (geoProxy) {
323
- proxy = `http://${encodeURIComponent(geoProxy.username)}:${encodeURIComponent(geoProxy.password)}@${geoProxy.server.replace('http://', '')}`;
324
- response.geoRegion = geoRegion;
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
- // Parse proxy URL if provided (format: socks5://user:pass@host:port or http://host:port)
328
- const proxyConfig = proxy ? (() => {
329
- try {
330
- const u = new URL(proxy);
331
- return {
332
- server: `${u.protocol}//${u.hostname}:${u.port}`,
333
- ...(u.username ? { username: decodeURIComponent(u.username) } : {}),
334
- ...(u.password ? { password: decodeURIComponent(u.password) } : {}),
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
- catch {
338
- return { server: proxy };
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
- })() : undefined;
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
- const navResult = await browser.navigate(url);
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
- await browser.close();
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
- await browser.close();
509
+ if (ownsBrowser)
510
+ await browser.close();
416
511
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
417
512
  }
418
513
  }
@@ -474,6 +569,13 @@ export function registerAuditTools(server) {
474
569
  visualComplexity: Math.round(pageMetrics.visualComplexity * 1000) / 1000,
475
570
  interactiveElements: pageMetrics.interactiveElementCount,
476
571
  choiceCount: pageMetrics.choiceCount,
572
+ // Text readability metrics (v18.56)
573
+ avgWordLength: Math.round(pageMetrics.avgWordLength * 1000) / 1000,
574
+ avgSentenceLength: Math.round(pageMetrics.avgSentenceLength * 1000) / 1000,
575
+ lexicalDiversity: Math.round(pageMetrics.lexicalDiversity * 1000) / 1000,
576
+ longWordRatio: Math.round(pageMetrics.longWordRatio * 1000) / 1000,
577
+ technicalDensity: Math.round(pageMetrics.technicalDensity * 1000) / 1000,
578
+ scriptFamily: pageMetrics.scriptFamily || "alphabetic",
477
579
  };
478
580
  if (pageUnderstanding)
479
581
  response.pageUnderstanding = pageUnderstanding;
@@ -488,17 +590,34 @@ export function registerAuditTools(server) {
488
590
  response.recommendations = recommendations.slice(0, 5);
489
591
  response.result = "COMPLETE";
490
592
  response.duration = `${Date.now() - startTime}ms`;
491
- await browser.close();
593
+ if (ownsBrowser)
594
+ await browser.close();
492
595
  }
493
596
  catch (navErr) {
494
- response.gate1 = { status: "error", error: navErr instanceof Error ? navErr.message : String(navErr) };
495
- response.result = "ERROR";
597
+ const errMsg = navErr instanceof Error ? navErr.message : String(navErr);
598
+ const isProxyBlock = errMsg.includes("ERR_TUNNEL_CONNECTION_FAILED") || errMsg.includes("ERR_PROXY_CONNECTION_FAILED");
599
+ const isProxyTimeout = errMsg.includes("ERR_NETWORK_CHANGED") && geoRegion;
600
+ if (isProxyBlock || isProxyTimeout) {
601
+ response.gate1 = {
602
+ status: "proxy_blocked",
603
+ error: errMsg.split("\n")[0],
604
+ geoRegion: geoRegion || undefined,
605
+ };
606
+ response.result = "PROXY_BLOCKED";
607
+ 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`;
608
+ }
609
+ else {
610
+ response.gate1 = { status: "error", error: errMsg };
611
+ response.result = "ERROR";
612
+ }
496
613
  response.duration = `${Date.now() - startTime}ms`;
497
- await browser.close();
614
+ if (ownsBrowser)
615
+ await browser.close();
498
616
  }
499
617
  }
500
618
  catch (browserErr) {
501
- response.gate1 = { status: "error", error: browserErr instanceof Error ? browserErr.message : String(browserErr) };
619
+ const errMsg = browserErr instanceof Error ? browserErr.message : String(browserErr);
620
+ response.gate1 = { status: "error", error: errMsg };
502
621
  response.result = "ERROR";
503
622
  response.duration = `${Date.now() - startTime}ms`;
504
623
  }
@@ -513,6 +632,7 @@ export function registerAuditTools(server) {
513
632
  persona: z.string().optional().default("cognitive-adhd").describe("Persona name"),
514
633
  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
634
  useValues: z.boolean().optional().default(false).describe("Enable motivational value influence on saliency map generation, attention scoring, and narrative. Default: false."),
635
+ _browserToken: z.string().optional().describe("Reuse an existing browser session. Useful for testing translated or state-dependent pages."),
516
636
  },
517
637
  annotations: {
518
638
  title: "Visual Cognitive Story",
@@ -521,18 +641,29 @@ export function registerAuditTools(server) {
521
641
  idempotentHint: false,
522
642
  openWorldHint: true,
523
643
  },
524
- }, async ({ url, persona, device, useValues }) => {
644
+ }, async ({ url, persona, device, useValues, _browserToken }) => {
525
645
  const startTime = Date.now();
526
646
  const { join } = await import("path");
527
647
  const { tmpdir } = await import("os");
528
648
  const { writeFileSync, mkdirSync, existsSync, unlinkSync } = await import("fs");
649
+ let ownsVcsBrowser = true;
529
650
  try {
530
651
  const { CBrowser: BrowserClass } = await import("../../browser.js");
531
- const browser = new BrowserClass({
532
- headless: true,
533
- ...(device ? { device: device.toLowerCase() } : { viewportWidth: 1920, viewportHeight: 1080 }),
534
- });
535
- await browser.launch();
652
+ let browser;
653
+ let vcsToken;
654
+ if (_browserToken && getBrowserByToken) {
655
+ const session = await getBrowserByToken(_browserToken);
656
+ browser = session.browser;
657
+ vcsToken = session.token;
658
+ ownsVcsBrowser = false;
659
+ }
660
+ else {
661
+ browser = new BrowserClass({
662
+ headless: true,
663
+ ...(device ? { device: device.toLowerCase() } : { viewportWidth: 1920, viewportHeight: 1080 }),
664
+ });
665
+ await browser.launch();
666
+ }
536
667
  try {
537
668
  await browser.navigate(url);
538
669
  await new Promise(r => setTimeout(r, 2000));
@@ -820,7 +951,8 @@ export function registerAuditTools(server) {
820
951
  return { content };
821
952
  }
822
953
  finally {
823
- await browser.close();
954
+ if (ownsVcsBrowser)
955
+ await browser.close();
824
956
  }
825
957
  }
826
958
  catch (err) {