cbrowser 19.3.8 → 19.4.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 (159) hide show
  1. package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
  2. package/dist/analysis/accessibility-empathy.js +10 -1
  3. package/dist/analysis/accessibility-empathy.js.map +1 -1
  4. package/dist/analysis/agent-ready-audit.d.ts +9 -0
  5. package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
  6. package/dist/analysis/agent-ready-audit.js +79 -6
  7. package/dist/analysis/agent-ready-audit.js.map +1 -1
  8. package/dist/analysis/bug-hunter.d.ts.map +1 -1
  9. package/dist/analysis/bug-hunter.js +27 -10
  10. package/dist/analysis/bug-hunter.js.map +1 -1
  11. package/dist/analysis/chaos-testing.d.ts.map +1 -1
  12. package/dist/analysis/chaos-testing.js +12 -3
  13. package/dist/analysis/chaos-testing.js.map +1 -1
  14. package/dist/analysis/competitive-benchmark.d.ts +50 -7
  15. package/dist/analysis/competitive-benchmark.d.ts.map +1 -1
  16. package/dist/analysis/competitive-benchmark.js +226 -106
  17. package/dist/analysis/competitive-benchmark.js.map +1 -1
  18. package/dist/analysis/page-understanding.d.ts +2 -0
  19. package/dist/analysis/page-understanding.d.ts.map +1 -1
  20. package/dist/analysis/page-understanding.js +104 -17
  21. package/dist/analysis/page-understanding.js.map +1 -1
  22. package/dist/artifact-store.d.ts +24 -2
  23. package/dist/artifact-store.d.ts.map +1 -1
  24. package/dist/artifact-store.js +34 -4
  25. package/dist/artifact-store.js.map +1 -1
  26. package/dist/browser/session-manager.d.ts.map +1 -1
  27. package/dist/browser/session-manager.js +3 -0
  28. package/dist/browser/session-manager.js.map +1 -1
  29. package/dist/browser/site-profile-manager.d.ts +17 -1
  30. package/dist/browser/site-profile-manager.d.ts.map +1 -1
  31. package/dist/browser/site-profile-manager.js +49 -12
  32. package/dist/browser/site-profile-manager.js.map +1 -1
  33. package/dist/browser.d.ts +58 -1
  34. package/dist/browser.d.ts.map +1 -1
  35. package/dist/browser.js +377 -50
  36. package/dist/browser.js.map +1 -1
  37. package/dist/charge-scope.d.ts.map +1 -1
  38. package/dist/charge-scope.js +6 -4
  39. package/dist/charge-scope.js.map +1 -1
  40. package/dist/cli.js +3 -0
  41. package/dist/cli.js.map +1 -1
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/config.js +8 -2
  44. package/dist/config.js.map +1 -1
  45. package/dist/mcp-server-remote.d.ts +26 -1
  46. package/dist/mcp-server-remote.d.ts.map +1 -1
  47. package/dist/mcp-server-remote.js +159 -89
  48. package/dist/mcp-server-remote.js.map +1 -1
  49. package/dist/mcp-server.d.ts.map +1 -1
  50. package/dist/mcp-server.js +4 -0
  51. package/dist/mcp-server.js.map +1 -1
  52. package/dist/mcp-tools/base/analysis-tools.d.ts.map +1 -1
  53. package/dist/mcp-tools/base/analysis-tools.js +5 -0
  54. package/dist/mcp-tools/base/analysis-tools.js.map +1 -1
  55. package/dist/mcp-tools/base/audit-tools.d.ts +14 -0
  56. package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
  57. package/dist/mcp-tools/base/audit-tools.js +17 -8
  58. package/dist/mcp-tools/base/audit-tools.js.map +1 -1
  59. package/dist/mcp-tools/base/browser-state-tools.d.ts.map +1 -1
  60. package/dist/mcp-tools/base/browser-state-tools.js +18 -27
  61. package/dist/mcp-tools/base/browser-state-tools.js.map +1 -1
  62. package/dist/mcp-tools/base/capture-tools.d.ts +18 -0
  63. package/dist/mcp-tools/base/capture-tools.d.ts.map +1 -1
  64. package/dist/mcp-tools/base/capture-tools.js +109 -55
  65. package/dist/mcp-tools/base/capture-tools.js.map +1 -1
  66. package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
  67. package/dist/mcp-tools/base/cognitive-tools.js +43 -16
  68. package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
  69. package/dist/mcp-tools/base/marketing-tools.d.ts.map +1 -1
  70. package/dist/mcp-tools/base/marketing-tools.js +8 -4
  71. package/dist/mcp-tools/base/marketing-tools.js.map +1 -1
  72. package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
  73. package/dist/mcp-tools/base/persona-comparison-tools.js +14 -3
  74. package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
  75. package/dist/mcp-tools/base/remediation-tools.d.ts.map +1 -1
  76. package/dist/mcp-tools/base/remediation-tools.js +10 -2
  77. package/dist/mcp-tools/base/remediation-tools.js.map +1 -1
  78. package/dist/mcp-tools/base/session-tools.d.ts.map +1 -1
  79. package/dist/mcp-tools/base/session-tools.js +2 -0
  80. package/dist/mcp-tools/base/session-tools.js.map +1 -1
  81. package/dist/mcp-tools/base/visual-testing-tools.d.ts +13 -0
  82. package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
  83. package/dist/mcp-tools/base/visual-testing-tools.js +45 -16
  84. package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
  85. package/dist/mcp-tools/persona-creation-tools.d.ts.map +1 -1
  86. package/dist/mcp-tools/persona-creation-tools.js +59 -13
  87. package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
  88. package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -1
  89. package/dist/mcp-tools/persona-lifecycle-tools.js +4 -2
  90. package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -1
  91. package/dist/mcp-tools/tier-gate.d.ts +4 -4
  92. package/dist/mcp-tools/tier-gate.d.ts.map +1 -1
  93. package/dist/mcp-tools/tier-gate.js +41 -19
  94. package/dist/mcp-tools/tier-gate.js.map +1 -1
  95. package/dist/mcp-tools/tool-categories.d.ts +11 -0
  96. package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
  97. package/dist/mcp-tools/tool-categories.js +27 -1
  98. package/dist/mcp-tools/tool-categories.js.map +1 -1
  99. package/dist/mcp-tools/ui-resources.d.ts.map +1 -1
  100. package/dist/mcp-tools/ui-resources.js +32 -10
  101. package/dist/mcp-tools/ui-resources.js.map +1 -1
  102. package/dist/personas.d.ts +14 -0
  103. package/dist/personas.d.ts.map +1 -1
  104. package/dist/personas.js +26 -0
  105. package/dist/personas.js.map +1 -1
  106. package/dist/recording/engine.d.ts.map +1 -1
  107. package/dist/recording/engine.js +2 -1
  108. package/dist/recording/engine.js.map +1 -1
  109. package/dist/remediation/patch-generator.d.ts.map +1 -1
  110. package/dist/remediation/patch-generator.js +7 -1
  111. package/dist/remediation/patch-generator.js.map +1 -1
  112. package/dist/remediation/structured-data.d.ts +73 -1
  113. package/dist/remediation/structured-data.d.ts.map +1 -1
  114. package/dist/remediation/structured-data.js +239 -63
  115. package/dist/remediation/structured-data.js.map +1 -1
  116. package/dist/request-context.d.ts +18 -0
  117. package/dist/request-context.d.ts.map +1 -0
  118. package/dist/request-context.js +56 -0
  119. package/dist/request-context.js.map +1 -0
  120. package/dist/site-model/manager.d.ts +14 -1
  121. package/dist/site-model/manager.d.ts.map +1 -1
  122. package/dist/site-model/manager.js +24 -1
  123. package/dist/site-model/manager.js.map +1 -1
  124. package/dist/types.d.ts +90 -2
  125. package/dist/types.d.ts.map +1 -1
  126. package/dist/types.js.map +1 -1
  127. package/dist/values/index.d.ts +1 -1
  128. package/dist/values/index.d.ts.map +1 -1
  129. package/dist/values/index.js +1 -1
  130. package/dist/values/index.js.map +1 -1
  131. package/dist/values/persona-values.d.ts +16 -0
  132. package/dist/values/persona-values.d.ts.map +1 -1
  133. package/dist/values/persona-values.js +56 -4
  134. package/dist/values/persona-values.js.map +1 -1
  135. package/dist/visual/ab-comparison.d.ts +22 -0
  136. package/dist/visual/ab-comparison.d.ts.map +1 -1
  137. package/dist/visual/ab-comparison.js +36 -9
  138. package/dist/visual/ab-comparison.js.map +1 -1
  139. package/dist/visual/cognitive-transport.d.ts +15 -1
  140. package/dist/visual/cognitive-transport.d.ts.map +1 -1
  141. package/dist/visual/cognitive-transport.js +42 -12
  142. package/dist/visual/cognitive-transport.js.map +1 -1
  143. package/dist/visual/cross-browser.d.ts +8 -0
  144. package/dist/visual/cross-browser.d.ts.map +1 -1
  145. package/dist/visual/cross-browser.js +8 -4
  146. package/dist/visual/cross-browser.js.map +1 -1
  147. package/dist/visual/distance-metrics.d.ts +11 -2
  148. package/dist/visual/distance-metrics.d.ts.map +1 -1
  149. package/dist/visual/distance-metrics.js +44 -5
  150. package/dist/visual/distance-metrics.js.map +1 -1
  151. package/dist/visual/regression.d.ts +61 -0
  152. package/dist/visual/regression.d.ts.map +1 -1
  153. package/dist/visual/regression.js +123 -50
  154. package/dist/visual/regression.js.map +1 -1
  155. package/dist/visual/responsive.d.ts +8 -0
  156. package/dist/visual/responsive.d.ts.map +1 -1
  157. package/dist/visual/responsive.js +95 -17
  158. package/dist/visual/responsive.js.map +1 -1
  159. package/package.json +1 -1
@@ -40,16 +40,18 @@ import { createRemoteJWKSet, jwtVerify } from "jose";
40
40
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
41
41
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
42
42
  import { CBrowser } from "./browser.js";
43
- import { ensureDirectories } from "./config.js";
43
+ import { ensureDirectories, getDataDir } from "./config.js";
44
44
  // Version from package.json - single source of truth
45
45
  import { VERSION } from "./version.js";
46
46
  // Modular MCP tools (v17.5.0)
47
47
  // getActiveKeyHash is deliberately no longer imported: billing identity is per-request
48
- // (see resolveRequestKeyHash). setActiveKeyHash stays for the usage-logging path, which
49
- // is per-session rather than per-charge.
48
+ // (see resolveRequestKeyHash). setActiveKeyHash, setActiveTier and setSessionApiKey
49
+ // write the calling request's own identity context (request-context.ts), which every
50
+ // HTTP request enters at createServer below. (sec-identity, 2026-10-10)
50
51
  import { registerAllPublicTools, setRemoteMode, setHostedMode, setActiveTier as setTierGate, setActiveKeyHash, isToolAccessible, upgradePrompt } from "./mcp-tools/index.js";
51
52
  import { withPersonaScope, scopedAccountId } from "./persona-scope.js";
52
53
  import { withChargeScope } from "./charge-scope.js";
54
+ import { withRequestIdentity } from "./request-context.js";
53
55
  /**
54
56
  * keyHash -> accountId, so a request can be scoped to the tenant that owns it.
55
57
  *
@@ -76,6 +78,8 @@ export async function resolveRequestAccountId(req) {
76
78
  const after = accountCache.get(keyHash);
77
79
  return after && after.expiresAt > Date.now() ? after.accountId : null;
78
80
  }
81
+ import { TOOL_CATEGORIES, categorizedToolNames } from "./mcp-tools/tool-categories.js";
82
+ import { BUILTIN_PERSONAS, ACCESSIBILITY_PERSONAS, EMOTIONAL_PERSONAS, AGENT_PERSONAS } from "./personas.js";
79
83
  // Shared browser instance (single Chromium process)
80
84
  let browser = null;
81
85
  // Stealth state (enterprise integration)
@@ -453,6 +457,10 @@ async function getBrowser() {
453
457
  headless: true,
454
458
  persistent: true,
455
459
  proxy: proxyConfig,
460
+ // The shared legacy browser belongs to no account, so its files stay
461
+ // in the process data dir even if it is first built inside a scope;
462
+ // the config default is now the caller's directory. (tenant, 2026-10-10)
463
+ dataDir: getDataDir(),
456
464
  });
457
465
  }
458
466
  return browser;
@@ -639,6 +647,35 @@ const tierCache = new Map();
639
647
  const MAX_REQUEST_BODY_BYTES = 4 * 1024 * 1024;
640
648
  /** Bound the CMS deduct call; an unanswered one used to hang the whole request. */
641
649
  const CREDIT_DEDUCT_TIMEOUT_MS = 5_000;
650
+ /**
651
+ * "none" on the enterprise server: enterprise is unlimited use (Alexa, 2026-10-12).
652
+ * The deduct call is still made, because the CMS deduct route is also the domain,
653
+ * tier and URL gate; `charge=none` makes the CMS price the call at 0. Set once at
654
+ * startup from `RemoteMcpServerOptions.chargeCredits`.
655
+ */
656
+ let creditChargeMode = "charge";
657
+ export function setCreditChargeMode(mode) {
658
+ creditChargeMode = mode;
659
+ }
660
+ /**
661
+ * Did this deduct response actually take credits? Decides whether the call enters a
662
+ * charge scope (where an isError result is refunded). A 0-credit deduct (charge=none,
663
+ * or a free tool) took nothing, and the CMS refund route credits the catalog price
664
+ * without finding the matching deduct, so refunding it would mint credits. A CMS that
665
+ * does not report `cost` keeps the old behaviour.
666
+ */
667
+ export function chargedByDeduct(httpOk, data) {
668
+ return httpOk && data.allowed === true && data.cost !== 0;
669
+ }
670
+ /** Query string for POST /api/credits/deduct. */
671
+ export function deductQuery(keyHash, toolName, targetUrl) {
672
+ return new URLSearchParams({
673
+ key_hash: keyHash,
674
+ tool: toolName,
675
+ ...(targetUrl ? { target_url: targetUrl } : {}),
676
+ ...(creditChargeMode === "none" ? { charge: "none" } : {}),
677
+ }).toString();
678
+ }
642
679
  const TIER_CACHE_TTL_MS = 30 * 1000;
643
680
  /**
644
681
  * Extract the raw API key from an incoming HTTP request.
@@ -1084,6 +1121,13 @@ function sendUnauthorized(res, message) {
1084
1121
  }),
1085
1122
  }));
1086
1123
  }
1124
+ /**
1125
+ * The tier a request is gated at.
1126
+ * Priority: resolved from API key > CBROWSER_TIER env > null (self-hosted, no gating).
1127
+ */
1128
+ function effectiveTier(resolvedTier) {
1129
+ return resolvedTier ?? process.env.CBROWSER_TIER ?? null;
1130
+ }
1087
1131
  /**
1088
1132
  * Configure all CBrowser tools on an MCP server instance.
1089
1133
  * This is shared between stdio and HTTP transports.
@@ -1101,8 +1145,7 @@ function configureMcpTools(server, customRegisterTools, sessionId, resolvedTier)
1101
1145
  ? () => getSessionBrowser(sessionId)
1102
1146
  : getTokenlessBrowser;
1103
1147
  const context = { getBrowser: sessionGetBrowser, getBrowserByToken };
1104
- // Tier priority: resolved from API key > CBROWSER_TIER env > null (self-hosted, no gating)
1105
- const tier = resolvedTier ?? process.env.CBROWSER_TIER ?? null;
1148
+ const tier = effectiveTier(resolvedTier);
1106
1149
  if (customRegisterTools) {
1107
1150
  // Set tier BEFORE custom registration so createGatedServer uses resolved tier
1108
1151
  setTierGate(tier);
@@ -1113,30 +1156,30 @@ function configureMcpTools(server, customRegisterTools, sessionId, resolvedTier)
1113
1156
  }
1114
1157
  }
1115
1158
  /**
1116
- * Create a configured MCP server instance
1159
+ * Counts this server prints about itself, all derived, never typed.
1117
1160
  *
1118
- * @param customRegisterTools - Optional custom tool registration
1119
- * @param sessionId - Optional session ID for session isolation
1161
+ * Every number below used to be a literal, and they disagreed with each other
1162
+ * and with tools/list: the instructions said "120 tools across 16 categories",
1163
+ * docs://tools-overview said 105 in its description and "120 across 11" in its
1164
+ * body, persona_creation was "(6 tools)" with nine registered, and
1165
+ * docs://personas said 21 while listing 6 of the 10 general personas. Measured
1166
+ * 2026-10-10: tools/list on the enterprise server returned 130 tools, 126 of
1167
+ * them in TOOL_CATEGORIES' 20 categories and 4 in none (B33).
1120
1168
  */
1121
- function createMcpServer(customRegisterTools, sessionId, resolvedTier) {
1122
- const server = new McpServer({
1123
- name: "cbrowser",
1124
- title: "CBrowser — Cognitive Browser Automation",
1125
- version: VERSION,
1126
- description: "AI-powered browser automation with cognitive user simulation. 120 MCP tools for navigation, visual testing, accessibility auditing, persona-based testing, site knowledge learning, and security scanning. Simulates how real humans think, struggle, and give up on your site.",
1127
- websiteUrl: "https://cbrowser.ai",
1128
- icons: [
1129
- { src: "https://cbrowser.ai/icon-192.png", sizes: ["192x192"] },
1130
- { src: "https://cbrowser.ai/icon-512.png", sizes: ["512x512"] },
1131
- { src: "https://cbrowser.ai/logo.svg", sizes: ["1080x1080"] },
1132
- ],
1133
- }, {
1134
- capabilities: {
1135
- tools: { listChanged: true },
1136
- prompts: {},
1137
- resources: {},
1138
- },
1139
- instructions: `CBrowser — cognitive browser automation with 120 tools across 16 categories.
1169
+ function catalogCounts() {
1170
+ return { tools: categorizedToolNames().length, categories: TOOL_CATEGORIES.length };
1171
+ }
1172
+ /** One line per specialist category, counted from TOOL_CATEGORIES. */
1173
+ function specialistCategoryLines() {
1174
+ return TOOL_CATEGORIES
1175
+ .filter((cat) => cat.tier > 1)
1176
+ .map((cat) => `• ${cat.name} (${cat.tools.length} tool${cat.tools.length === 1 ? "" : "s"}) — ${cat.tools.slice(0, 5).join(", ")}${cat.tools.length > 5 ? "..." : ""}`)
1177
+ .join("\n");
1178
+ }
1179
+ /** The server instructions sent on initialize. Exported so the counts can be tested. */
1180
+ export function buildServerInstructions() {
1181
+ const { tools, categories } = catalogCounts();
1182
+ return `CBrowser — cognitive browser automation with ${tools} tools across ${categories} categories.
1140
1183
 
1141
1184
  CORE TOOLS (use these first):
1142
1185
  • navigate, click, fill, scroll, screenshot, extract — browser control
@@ -1149,17 +1192,7 @@ CORE TOOLS (use these first):
1149
1192
  • security_audit — MCP tool injection scanning
1150
1193
 
1151
1194
  SPECIALIST CATEGORIES (use when the task requires them):
1152
- • visual_testing (9 tools) — visual_baseline, visual_regression, cross_browser_test, responsive_test, ab_comparison, transport_map, attention_analysis...
1153
- • cognitive_transport (7 tools) — cognitive_distance, cognitive_coverage, cognitive_interpolate, cognitive_load_estimate, compare_personas...
1154
- • testing (5 tools) — nl_test_inline, nl_test_file, detect_flaky_tests, coverage_map, repair_test
1155
- • site_knowledge (5 tools) — site_model_status, site_model_query, site_profile_list/delete/status
1156
- • bug_hunting (3 tools) — hunt_bugs, chaos_test, competitive_benchmark
1157
- • performance (3 tools) — perf_baseline, perf_regression, list_baselines
1158
- • sessions (4 tools) — save_session, load_session, list_sessions, delete_session
1159
- • marketing (4 tools) — marketing_campaign_create/run/report, marketing_personas_list
1160
- • persona_creation (6 tools) — persona_create_start, questionnaire, from_description
1161
- • enterprise_security (8 tools) — web_security_scan, stealth_enable/disable/check, cloudflare_detect/wait
1162
- • enterprise_marketing (6 tools) — marketing_influence_matrix, lever_analysis, funnel_analyze, audience_discover
1195
+ ${specialistCategoryLines()}
1163
1196
 
1164
1197
  TIPS:
1165
1198
  • Pass _browserToken between tool calls to maintain browser state
@@ -1178,7 +1211,56 @@ SELF-HELP:
1178
1211
  • If you are unsure what a tool does, what a score means, which persona to use, or how tools work together — call question_answer with your question
1179
1212
  • question_answer searches 290+ pages of CBrowser documentation and returns a grounded answer
1180
1213
  • Use it before guessing. Example: question_answer(question: "What is the difference between viewport and full_page scope?")
1181
- • Use it to explain results to the user. Example: question_answer(question: "What does a CTC of 1.6 with readability bottleneck mean?")`,
1214
+ • Use it to explain results to the user. Example: question_answer(question: "What does a CTC of 1.6 with readability bottleneck mean?")`;
1215
+ }
1216
+ /** Body of docs://tools-overview, counted from TOOL_CATEGORIES. */
1217
+ export function buildToolsOverviewText() {
1218
+ const { tools, categories } = catalogCounts();
1219
+ const list = TOOL_CATEGORIES.map((cat) => `${cat.name} (${cat.tools.length})`).join(", ");
1220
+ return `CBrowser provides ${tools} MCP tools across ${categories} categories: ${list}. Visit https://cbrowser.ai/docs/Tools-Overview for the complete reference.`;
1221
+ }
1222
+ /** Body of docs://personas, listed from the persona maps themselves. */
1223
+ export function buildPersonasOverviewText() {
1224
+ const groups = [
1225
+ ["Cognitive", Object.keys(BUILTIN_PERSONAS)],
1226
+ ["Accessibility", Object.keys(ACCESSIBILITY_PERSONAS)],
1227
+ ["Emotional", Object.keys(EMOTIONAL_PERSONAS)],
1228
+ ["AI agent", Object.keys(AGENT_PERSONAS)],
1229
+ ];
1230
+ const total = groups.reduce((n, [, names]) => n + names.length, 0);
1231
+ const body = groups.map(([label, names]) => `${label} (${names.length}): ${names.join(", ")}`).join(". ");
1232
+ return `CBrowser includes ${total} built-in personas: ${body}. Each human persona has 26 cognitive traits; agent personas describe AI agents with agent traits instead. Visit https://cbrowser.ai/docs/Persona-Index for details.`;
1233
+ }
1234
+ /** Number of built-in personas across every map docs://personas lists. */
1235
+ function builtinPersonaCount() {
1236
+ return [BUILTIN_PERSONAS, ACCESSIBILITY_PERSONAS, EMOTIONAL_PERSONAS, AGENT_PERSONAS]
1237
+ .reduce((n, map) => n + Object.keys(map).length, 0);
1238
+ }
1239
+ /**
1240
+ * Create a configured MCP server instance
1241
+ *
1242
+ * @param customRegisterTools - Optional custom tool registration
1243
+ * @param sessionId - Optional session ID for session isolation
1244
+ */
1245
+ function createMcpServer(customRegisterTools, sessionId, resolvedTier) {
1246
+ const server = new McpServer({
1247
+ name: "cbrowser",
1248
+ title: "CBrowser — Cognitive Browser Automation",
1249
+ version: VERSION,
1250
+ description: `AI-powered browser automation with cognitive user simulation. ${catalogCounts().tools} MCP tools for navigation, visual testing, accessibility auditing, persona-based testing, site knowledge learning, and security scanning. Simulates how real humans think, struggle, and give up on your site.`,
1251
+ websiteUrl: "https://cbrowser.ai",
1252
+ icons: [
1253
+ { src: "https://cbrowser.ai/icon-192.png", sizes: ["192x192"] },
1254
+ { src: "https://cbrowser.ai/icon-512.png", sizes: ["512x512"] },
1255
+ { src: "https://cbrowser.ai/logo.svg", sizes: ["1080x1080"] },
1256
+ ],
1257
+ }, {
1258
+ capabilities: {
1259
+ tools: { listChanged: true },
1260
+ prompts: {},
1261
+ resources: {},
1262
+ },
1263
+ instructions: buildServerInstructions(),
1182
1264
  });
1183
1265
  // Register prompts for common workflows
1184
1266
  server.prompt("audit-site", "Run a comprehensive site audit with empathy, agent-readiness, and visual baseline", async () => ({
@@ -1210,17 +1292,17 @@ SELF-HELP:
1210
1292
  }));
1211
1293
  registerUiResources(server);
1212
1294
  // Register resources
1213
- server.resource("docs://tools-overview", "docs://tools-overview", { description: "Overview of all 105 CBrowser MCP tools organized by category", mimeType: "text/plain" }, async () => ({
1295
+ server.resource("docs://tools-overview", "docs://tools-overview", { description: `Overview of all ${catalogCounts().tools} CBrowser MCP tools organized by category`, mimeType: "text/plain" }, async () => ({
1214
1296
  contents: [{
1215
1297
  uri: "docs://tools-overview",
1216
- text: "CBrowser provides 120 MCP tools across 11 categories: Browser Automation, Visual Testing, Cognitive Journeys, Site Knowledge, Accessibility, Persona System, Testing, Performance, Security, Session Management, and Marketing. Visit https://cbrowser.ai/docs/Tools-Overview for the complete reference.",
1298
+ text: buildToolsOverviewText(),
1217
1299
  mimeType: "text/plain",
1218
1300
  }],
1219
1301
  }));
1220
- server.resource("docs://personas", "docs://personas", { description: "List of 21 built-in personas with cognitive traits", mimeType: "text/plain" }, async () => ({
1302
+ server.resource("docs://personas", "docs://personas", { description: `List of ${builtinPersonaCount()} built-in personas`, mimeType: "text/plain" }, async () => ({
1221
1303
  contents: [{
1222
1304
  uri: "docs://personas",
1223
- text: "CBrowser includes 21 personas: Cognitive (6): power-user, first-timer, mobile-user, elderly-user, impatient-user, screen-reader-user. Accessibility (11): motor-impairment-tremor, low-vision-magnified, cognitive-adhd, dyslexic-user, deaf-user, elderly-low-vision, color-blind-deuteranopia, autism-spectrum, intellectual-disability, aphasia-receptive, dyscalculia. Emotional (4): anxious-user, confident-user, emotional-user, stoic-user. Each persona has 26 cognitive traits. Visit https://cbrowser.ai/docs/Persona-Index for details.",
1305
+ text: buildPersonasOverviewText(),
1224
1306
  mimeType: "text/plain",
1225
1307
  }],
1226
1308
  }));
@@ -1370,11 +1452,7 @@ async function handleMcpRequest(req, res, transport) {
1370
1452
  }
1371
1453
  if (keyHash) {
1372
1454
  const cmsUrl = process.env.CMS_URL || "http://localhost:3200";
1373
- const deductParams = new URLSearchParams({
1374
- key_hash: keyHash,
1375
- tool: toolName,
1376
- ...(targetUrl ? { target_url: targetUrl } : {}),
1377
- });
1455
+ const deductParams = deductQuery(keyHash, toolName, targetUrl);
1378
1456
  try {
1379
1457
  const creditRes = await fetch(`${cmsUrl}/api/credits/deduct?${deductParams}`, {
1380
1458
  method: "POST",
@@ -1415,7 +1493,7 @@ async function handleMcpRequest(req, res, transport) {
1415
1493
  // above either returned already (allowed:false) or are fail-open
1416
1494
  // (a response with no `allowed` field at all, e.g. an internal-auth
1417
1495
  // failure) — neither charged the account, so neither sets this.
1418
- if (creditRes.ok && data.allowed === true) {
1496
+ if (chargedByDeduct(creditRes.ok, data)) {
1419
1497
  chargedKeyHash = keyHash;
1420
1498
  chargedTool = toolName;
1421
1499
  }
@@ -1515,6 +1593,7 @@ async function handleMcpRequest(req, res, transport) {
1515
1593
  * @param options - Optional configuration including tool extension callback
1516
1594
  */
1517
1595
  export async function startRemoteMcpServer(options) {
1596
+ setCreditChargeMode(options?.chargeCredits === false ? "none" : "charge");
1518
1597
  // Auto-initialize all data directories on server start
1519
1598
  ensureDirectories();
1520
1599
  // Session reaper + rate-limit sweeper. Started HERE rather than at module
@@ -1561,7 +1640,13 @@ export async function startRemoteMcpServer(options) {
1561
1640
  console.log(` - Enterprise: unlimited`);
1562
1641
  }
1563
1642
  console.log(`Listening on ${host}:${port}`);
1564
- const httpServer = createServer(async (req, res) => {
1643
+ // Every HTTP request runs in its own identity context, so the key hash, tier
1644
+ // and API key the /mcp branch sets for it belong to it alone. They were module
1645
+ // globals, set while the request's server was built and read when its tool
1646
+ // ran, several awaits later; an overlapping request from another account
1647
+ // overwrote them in between. The context starts as an unauthenticated caller
1648
+ // at the deployment's default tier. (sec-identity, 2026-10-10)
1649
+ const httpServer = createServer((req, res) => withRequestIdentity({ keyHash: null, tier: effectiveTier(null), apiKey: undefined }, async () => {
1565
1650
  const url = new URL(req.url || "/", `http://${req.headers.host}`);
1566
1651
  // Security headers (defense in depth)
1567
1652
  res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
@@ -1903,11 +1988,14 @@ if (urlParams.get('error')) {
1903
1988
  }
1904
1989
  // llms.txt - AI-readable documentation (WebMCP standard)
1905
1990
  if (url.pathname === "/llms.txt" || url.pathname === "/.well-known/llms.txt") {
1991
+ // Count and category list derived from TOOL_CATEGORIES. The hand-written
1992
+ // block said 91 tools, and its per-category numbers did not match the
1993
+ // names it listed ("Visual Testing (6)" named five). (B33, 2026-10-10)
1906
1994
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
1907
1995
  res.end(`# CBrowser MCP Server
1908
1996
  # AI-powered browser automation with cognitive user simulation
1909
1997
 
1910
- > CBrowser is an MCP server that provides 91 tools for browser automation,
1998
+ > CBrowser is an MCP server that provides ${catalogCounts().tools} tools for browser automation,
1911
1999
  > visual testing, cognitive user simulation, and accessibility auditing.
1912
2000
  > It simulates how real humans think, struggle, and give up on websites.
1913
2001
 
@@ -1930,17 +2018,8 @@ POST /mcp - Model Context Protocol endpoint (JSON-RPC 2.0 over SSE)
1930
2018
  - API Key authentication for programmatic access
1931
2019
  - Demo server (demo.cbrowser.ai): Rate-limited, no auth required
1932
2020
 
1933
- ## Tool Categories (91 tools)
1934
- - Navigation (1): navigate
1935
- - Interaction (5): click, smart_click, fill, scroll, press_key
1936
- - Analysis (4): analyze_page, extract, find_element_by_intent, assert
1937
- - Visual Testing (6): visual_baseline, visual_regression, cross_browser_test, responsive_test, ab_comparison
1938
- - Cognitive (3): cognitive_journey_init, cognitive_journey_update_state, list_cognitive_personas
1939
- - Personas (10): compare_personas_init, persona_create, persona_traits_list, etc.
1940
- - Testing (5): nl_test_file, nl_test_inline, repair_test, detect_flaky_tests, coverage_map
1941
- - Audit (4): agent_ready_audit, competitive_benchmark, empathy_audit, webmcp_ready_audit
1942
- - Session (4): save_session, load_session, list_sessions, delete_session
1943
- - Browser (4): browser_health, browser_recover, reset_browser, status
2021
+ ## Tool Categories (${catalogCounts().tools} tools)
2022
+ ${TOOL_CATEGORIES.map((cat) => `- ${cat.name} (${cat.tools.length}): ${cat.tools.join(", ")}`).join("\n")}
1944
2023
 
1945
2024
  ## Links
1946
2025
  - Documentation: https://cbrowser.ai/docs
@@ -1987,8 +2066,8 @@ ${VERSION}
1987
2066
  }
1988
2067
  }</code></pre>
1989
2068
 
1990
- <h2>Available Tools (91)</h2>
1991
- <p>This server provides 91 MCP tools for browser automation, testing, and analysis.</p>
2069
+ <h2>Available Tools (${catalogCounts().tools})</h2>
2070
+ <p>This server provides ${catalogCounts().tools} MCP tools for browser automation, testing, and analysis.</p>
1992
2071
  <ul>
1993
2072
  <li><strong>Navigation:</strong> navigate, screenshot</li>
1994
2073
  <li><strong>Interaction:</strong> click, smart_click, fill, scroll, press_key</li>
@@ -2253,6 +2332,21 @@ ${VERSION}
2253
2332
  // Log session handling for debugging (truncate ID for readability)
2254
2333
  const shortId = browserSessionId.substring(0, 8);
2255
2334
  console.log(`[Session] ${sessionSource}: ${shortId}... (active: ${activeSessions.size})`);
2335
+ // This request's caller, written into its own identity context (entered at
2336
+ // createServer above). Set before the transport branch so a reused
2337
+ // stateful transport gets it too: that path used to run on whatever the
2338
+ // last request to build a server had left in the globals. Building a new
2339
+ // server below sets the tier again during registration, as it always did.
2340
+ // (sec-identity, 2026-10-10)
2341
+ const requestApiKey = extractApiKey(req);
2342
+ const resolvedTier = requestApiKey ? await resolveApiKeyTier(requestApiKey) : null;
2343
+ // Key hash for usage tracking: a cbk_ key's own hash, or the key an OAuth
2344
+ // token was issued for; null otherwise. The same derivation as billing.
2345
+ setActiveKeyHash(await resolveRequestKeyHash(req));
2346
+ // Raw key for account-scoped features (custom personas, etc.)
2347
+ const { setSessionApiKey } = await import("./mcp-tools/base/cognitive-tools.js");
2348
+ setSessionApiKey(requestApiKey || undefined);
2349
+ setTierGate(effectiveTier(resolvedTier));
2256
2350
  // Now handle transport (stateful mode caches transport, stateless creates new each time)
2257
2351
  const hasExistingTransport = existingSessionId ? transports.has(existingSessionId) : false;
2258
2352
  if (sessionMode === "stateful" && existingSessionId && hasExistingTransport) {
@@ -2266,30 +2360,6 @@ ${VERSION}
2266
2360
  transport = new StreamableHTTPServerTransport({
2267
2361
  sessionIdGenerator: sessionMode === "stateful" ? () => browserSessionId : undefined,
2268
2362
  });
2269
- // Resolve pricing tier from API key (cbk_ keys → CMS lookup)
2270
- const requestApiKey = extractApiKey(req);
2271
- const resolvedTier = requestApiKey ? await resolveApiKeyTier(requestApiKey) : null;
2272
- // Set key hash for usage tracking
2273
- if (requestApiKey?.startsWith("cbk_")) {
2274
- const { createHash } = await import("crypto");
2275
- setActiveKeyHash(createHash("sha256").update(requestApiKey).digest("hex"));
2276
- }
2277
- else if (requestApiKey) {
2278
- // Check token→keyHash map (OAuth tokens that aren't raw cbk_ keys)
2279
- const mapped = tokenToKeyHash.get(requestApiKey);
2280
- if (mapped && mapped.expiresAt > Date.now()) {
2281
- setActiveKeyHash(mapped.keyHash);
2282
- }
2283
- else {
2284
- setActiveKeyHash(null);
2285
- }
2286
- }
2287
- else {
2288
- setActiveKeyHash(null);
2289
- }
2290
- // Set session API key for account-scoped features (custom personas, etc.)
2291
- const { setSessionApiKey } = await import("./mcp-tools/base/cognitive-tools.js");
2292
- setSessionApiKey(requestApiKey || undefined);
2293
2363
  // Create and connect server with session isolation and tier gating
2294
2364
  const server = createMcpServer(options?.registerTools, browserSessionId, resolvedTier);
2295
2365
  // Allow extension with additional tools (for Enterprise)
@@ -2347,7 +2417,7 @@ ${VERSION}
2347
2417
  // 404 for other paths
2348
2418
  res.writeHead(404, { "Content-Type": "application/json" });
2349
2419
  res.end(JSON.stringify({ error: "Not found" }));
2350
- });
2420
+ }));
2351
2421
  httpServer.listen(port, host, () => {
2352
2422
  void (async () => {
2353
2423
  console.log(`\nCognitive Browser Remote MCP Server running at http://${host}:${port}`);