cbrowser 19.3.7 → 19.3.9
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/dist/analysis/accessibility-empathy.d.ts.map +1 -1
- package/dist/analysis/accessibility-empathy.js +10 -1
- package/dist/analysis/accessibility-empathy.js.map +1 -1
- package/dist/analysis/agent-ready-audit.d.ts +9 -0
- package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
- package/dist/analysis/agent-ready-audit.js +79 -6
- package/dist/analysis/agent-ready-audit.js.map +1 -1
- package/dist/analysis/bug-hunter.d.ts.map +1 -1
- package/dist/analysis/bug-hunter.js +27 -10
- package/dist/analysis/bug-hunter.js.map +1 -1
- package/dist/analysis/chaos-testing.d.ts.map +1 -1
- package/dist/analysis/chaos-testing.js +12 -3
- package/dist/analysis/chaos-testing.js.map +1 -1
- package/dist/analysis/competitive-benchmark.d.ts +50 -7
- package/dist/analysis/competitive-benchmark.d.ts.map +1 -1
- package/dist/analysis/competitive-benchmark.js +226 -106
- package/dist/analysis/competitive-benchmark.js.map +1 -1
- package/dist/analysis/page-understanding.d.ts +2 -0
- package/dist/analysis/page-understanding.d.ts.map +1 -1
- package/dist/analysis/page-understanding.js +104 -17
- package/dist/analysis/page-understanding.js.map +1 -1
- package/dist/artifact-store.d.ts +24 -2
- package/dist/artifact-store.d.ts.map +1 -1
- package/dist/artifact-store.js +34 -4
- package/dist/artifact-store.js.map +1 -1
- package/dist/browser/session-manager.d.ts.map +1 -1
- package/dist/browser/session-manager.js +3 -0
- package/dist/browser/session-manager.js.map +1 -1
- package/dist/browser/site-profile-manager.d.ts +17 -1
- package/dist/browser/site-profile-manager.d.ts.map +1 -1
- package/dist/browser/site-profile-manager.js +49 -12
- package/dist/browser/site-profile-manager.js.map +1 -1
- package/dist/browser.d.ts +58 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +377 -50
- package/dist/browser.js.map +1 -1
- package/dist/charge-scope.d.ts.map +1 -1
- package/dist/charge-scope.js +6 -4
- package/dist/charge-scope.js.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -2
- package/dist/config.js.map +1 -1
- package/dist/mcp-server-remote.d.ts +27 -1
- package/dist/mcp-server-remote.d.ts.map +1 -1
- package/dist/mcp-server-remote.js +160 -87
- package/dist/mcp-server-remote.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +4 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools/base/analysis-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/analysis-tools.js +5 -0
- package/dist/mcp-tools/base/analysis-tools.js.map +1 -1
- package/dist/mcp-tools/base/audit-tools.d.ts +14 -0
- package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/audit-tools.js +17 -8
- package/dist/mcp-tools/base/audit-tools.js.map +1 -1
- package/dist/mcp-tools/base/browser-state-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/browser-state-tools.js +18 -27
- package/dist/mcp-tools/base/browser-state-tools.js.map +1 -1
- package/dist/mcp-tools/base/capture-tools.d.ts +18 -0
- package/dist/mcp-tools/base/capture-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/capture-tools.js +109 -55
- package/dist/mcp-tools/base/capture-tools.js.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.js +43 -16
- package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
- package/dist/mcp-tools/base/marketing-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/marketing-tools.js +8 -4
- package/dist/mcp-tools/base/marketing-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 +14 -3
- package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
- package/dist/mcp-tools/base/remediation-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/remediation-tools.js +10 -2
- package/dist/mcp-tools/base/remediation-tools.js.map +1 -1
- package/dist/mcp-tools/base/session-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/session-tools.js +2 -0
- package/dist/mcp-tools/base/session-tools.js.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.d.ts +13 -0
- package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.js +45 -16
- package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.d.ts.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.js +59 -13
- package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
- package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -1
- package/dist/mcp-tools/persona-lifecycle-tools.js +4 -2
- package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -1
- package/dist/mcp-tools/tier-gate.d.ts +4 -4
- package/dist/mcp-tools/tier-gate.d.ts.map +1 -1
- package/dist/mcp-tools/tier-gate.js +41 -19
- package/dist/mcp-tools/tier-gate.js.map +1 -1
- package/dist/mcp-tools/tool-categories.d.ts +11 -0
- package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
- package/dist/mcp-tools/tool-categories.js +27 -1
- package/dist/mcp-tools/tool-categories.js.map +1 -1
- package/dist/mcp-tools/ui-resources.d.ts.map +1 -1
- package/dist/mcp-tools/ui-resources.js +32 -10
- package/dist/mcp-tools/ui-resources.js.map +1 -1
- package/dist/personas.d.ts +14 -0
- package/dist/personas.d.ts.map +1 -1
- package/dist/personas.js +26 -0
- package/dist/personas.js.map +1 -1
- package/dist/recording/engine.d.ts.map +1 -1
- package/dist/recording/engine.js +2 -1
- package/dist/recording/engine.js.map +1 -1
- package/dist/remediation/patch-generator.d.ts.map +1 -1
- package/dist/remediation/patch-generator.js +7 -1
- package/dist/remediation/patch-generator.js.map +1 -1
- package/dist/remediation/structured-data.d.ts +73 -1
- package/dist/remediation/structured-data.d.ts.map +1 -1
- package/dist/remediation/structured-data.js +239 -63
- package/dist/remediation/structured-data.js.map +1 -1
- package/dist/request-context.d.ts +18 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +56 -0
- package/dist/request-context.js.map +1 -0
- package/dist/site-model/manager.d.ts +14 -1
- package/dist/site-model/manager.d.ts.map +1 -1
- package/dist/site-model/manager.js +24 -1
- package/dist/site-model/manager.js.map +1 -1
- package/dist/types.d.ts +90 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/values/index.d.ts +1 -1
- package/dist/values/index.d.ts.map +1 -1
- package/dist/values/index.js +1 -1
- package/dist/values/index.js.map +1 -1
- package/dist/values/persona-values.d.ts +16 -0
- package/dist/values/persona-values.d.ts.map +1 -1
- package/dist/values/persona-values.js +56 -4
- package/dist/values/persona-values.js.map +1 -1
- package/dist/visual/ab-comparison.d.ts +22 -0
- package/dist/visual/ab-comparison.d.ts.map +1 -1
- package/dist/visual/ab-comparison.js +36 -9
- package/dist/visual/ab-comparison.js.map +1 -1
- package/dist/visual/cognitive-transport.d.ts +15 -1
- package/dist/visual/cognitive-transport.d.ts.map +1 -1
- package/dist/visual/cognitive-transport.js +42 -12
- package/dist/visual/cognitive-transport.js.map +1 -1
- package/dist/visual/cross-browser.d.ts +8 -0
- package/dist/visual/cross-browser.d.ts.map +1 -1
- package/dist/visual/cross-browser.js +8 -4
- package/dist/visual/cross-browser.js.map +1 -1
- package/dist/visual/distance-metrics.d.ts +11 -2
- package/dist/visual/distance-metrics.d.ts.map +1 -1
- package/dist/visual/distance-metrics.js +44 -5
- package/dist/visual/distance-metrics.js.map +1 -1
- package/dist/visual/regression.d.ts +61 -0
- package/dist/visual/regression.d.ts.map +1 -1
- package/dist/visual/regression.js +123 -50
- package/dist/visual/regression.js.map +1 -1
- package/dist/visual/responsive.d.ts +8 -0
- package/dist/visual/responsive.d.ts.map +1 -1
- package/dist/visual/responsive.js +95 -17
- package/dist/visual/responsive.js.map +1 -1
- 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
|
|
49
|
-
//
|
|
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)
|
|
@@ -157,6 +161,7 @@ export async function getBrowserByToken(token) {
|
|
|
157
161
|
browser: sessionBrowser,
|
|
158
162
|
createdAt: now,
|
|
159
163
|
lastActivity: now,
|
|
164
|
+
accountId: scopedAccountId(),
|
|
160
165
|
});
|
|
161
166
|
const newToken = generateBrowserToken(sessionId);
|
|
162
167
|
console.log(`[Token] New browser session ${sessionId.substring(0, 8)}... → token ${newToken}`);
|
|
@@ -168,6 +173,13 @@ export async function getBrowserByToken(token) {
|
|
|
168
173
|
const MAX_CONCURRENT_SESSIONS = parseInt(process.env.MAX_CONCURRENT_SESSIONS || "20", 10);
|
|
169
174
|
const SESSION_IDLE_TIMEOUT_MS = parseInt(process.env.SESSION_IDLE_TIMEOUT_MS || "300000", 10); // 5 min
|
|
170
175
|
const SESSION_MEMORY_LIMIT_MB = parseInt(process.env.SESSION_MEMORY_LIMIT_MB || "800", 10); // 800MB per session
|
|
176
|
+
/** Thrown when a session id names a browser another account created. */
|
|
177
|
+
export class SessionOwnedElsewhereError extends Error {
|
|
178
|
+
constructor() {
|
|
179
|
+
super("This MCP session id is not available to this caller. Reconnect to start a new session, or call navigate() without a _browserToken.");
|
|
180
|
+
this.name = "SessionOwnedElsewhereError";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
171
183
|
// Active sessions: sessionId -> SessionInfo
|
|
172
184
|
const activeSessions = new Map();
|
|
173
185
|
// Track recently expired sessions for clear error messaging
|
|
@@ -179,7 +191,7 @@ const EXPIRED_SESSION_RETENTION_MS = 600000; // 10 min
|
|
|
179
191
|
* Each session gets its own CBrowser with isolated context.
|
|
180
192
|
* If a session expired, transparently create a new one (low-friction reconnect).
|
|
181
193
|
*/
|
|
182
|
-
async function getSessionBrowser(sessionId) {
|
|
194
|
+
export async function getSessionBrowser(sessionId) {
|
|
183
195
|
// Check if this session was recently expired - auto-recover by creating new session
|
|
184
196
|
const expiredInfo = expiredSessions.get(sessionId);
|
|
185
197
|
if (expiredInfo) {
|
|
@@ -189,9 +201,16 @@ async function getSessionBrowser(sessionId) {
|
|
|
189
201
|
expiredSessions.delete(sessionId);
|
|
190
202
|
// Fall through to create new session below
|
|
191
203
|
}
|
|
192
|
-
// Check if session already exists
|
|
204
|
+
// Check if session already exists. The id is the client's mcp-session-id
|
|
205
|
+
// header on the /mcp path, so holding it proves nothing: this map is one per
|
|
206
|
+
// process and returned the browser (cookies, logged-in pages) to any caller
|
|
207
|
+
// presenting the id. A session is now reused only by the account scope that
|
|
208
|
+
// created it; unscoped callers keep the old behaviour among themselves.
|
|
209
|
+
// (tenant, 2026-10-10)
|
|
193
210
|
const existing = activeSessions.get(sessionId);
|
|
194
211
|
if (existing) {
|
|
212
|
+
if (existing.accountId !== scopedAccountId())
|
|
213
|
+
throw new SessionOwnedElsewhereError();
|
|
195
214
|
existing.lastActivity = Date.now();
|
|
196
215
|
return existing.browser;
|
|
197
216
|
}
|
|
@@ -215,6 +234,7 @@ async function getSessionBrowser(sessionId) {
|
|
|
215
234
|
browser: sessionBrowser,
|
|
216
235
|
createdAt: Date.now(),
|
|
217
236
|
lastActivity: Date.now(),
|
|
237
|
+
accountId: scopedAccountId(),
|
|
218
238
|
});
|
|
219
239
|
console.log(`[Session] Created new session ${sessionId.substring(0, 8)}... (${activeSessions.size}/${MAX_CONCURRENT_SESSIONS} active)`);
|
|
220
240
|
return sessionBrowser;
|
|
@@ -437,6 +457,10 @@ async function getBrowser() {
|
|
|
437
457
|
headless: true,
|
|
438
458
|
persistent: true,
|
|
439
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(),
|
|
440
464
|
});
|
|
441
465
|
}
|
|
442
466
|
return browser;
|
|
@@ -448,7 +472,20 @@ async function getBrowser() {
|
|
|
448
472
|
*/
|
|
449
473
|
export async function getTokenlessBrowser() {
|
|
450
474
|
const accountId = scopedAccountId();
|
|
451
|
-
return accountId === undefined ? getBrowser() : getSessionBrowser(
|
|
475
|
+
return accountId === undefined ? getBrowser() : getSessionBrowser(`${ACCOUNT_SESSION_PREFIX}${accountId}`);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Session ids in this namespace name an account's own tokenless browser. On the
|
|
479
|
+
* /mcp path the session id is the client's mcp-session-id header, and account
|
|
480
|
+
* ids are small integers, so 19.3.7 let any caller send "account:2" and drive
|
|
481
|
+
* account 2's browser, or claim the id first and lock the owner out. A client
|
|
482
|
+
* header in this namespace is ignored, as if no session id had been sent.
|
|
483
|
+
* (19.3.8, 2026-10-10)
|
|
484
|
+
*/
|
|
485
|
+
export const ACCOUNT_SESSION_PREFIX = "account:";
|
|
486
|
+
export function sessionIdFromHeader(header) {
|
|
487
|
+
const id = Array.isArray(header) ? header[0] : header;
|
|
488
|
+
return id && !id.startsWith(ACCOUNT_SESSION_PREFIX) ? id : undefined;
|
|
452
489
|
}
|
|
453
490
|
// Transport storage for stateful sessions
|
|
454
491
|
const transports = new Map();
|
|
@@ -1055,6 +1092,13 @@ function sendUnauthorized(res, message) {
|
|
|
1055
1092
|
}),
|
|
1056
1093
|
}));
|
|
1057
1094
|
}
|
|
1095
|
+
/**
|
|
1096
|
+
* The tier a request is gated at.
|
|
1097
|
+
* Priority: resolved from API key > CBROWSER_TIER env > null (self-hosted, no gating).
|
|
1098
|
+
*/
|
|
1099
|
+
function effectiveTier(resolvedTier) {
|
|
1100
|
+
return resolvedTier ?? process.env.CBROWSER_TIER ?? null;
|
|
1101
|
+
}
|
|
1058
1102
|
/**
|
|
1059
1103
|
* Configure all CBrowser tools on an MCP server instance.
|
|
1060
1104
|
* This is shared between stdio and HTTP transports.
|
|
@@ -1072,8 +1116,7 @@ function configureMcpTools(server, customRegisterTools, sessionId, resolvedTier)
|
|
|
1072
1116
|
? () => getSessionBrowser(sessionId)
|
|
1073
1117
|
: getTokenlessBrowser;
|
|
1074
1118
|
const context = { getBrowser: sessionGetBrowser, getBrowserByToken };
|
|
1075
|
-
|
|
1076
|
-
const tier = resolvedTier ?? process.env.CBROWSER_TIER ?? null;
|
|
1119
|
+
const tier = effectiveTier(resolvedTier);
|
|
1077
1120
|
if (customRegisterTools) {
|
|
1078
1121
|
// Set tier BEFORE custom registration so createGatedServer uses resolved tier
|
|
1079
1122
|
setTierGate(tier);
|
|
@@ -1084,30 +1127,30 @@ function configureMcpTools(server, customRegisterTools, sessionId, resolvedTier)
|
|
|
1084
1127
|
}
|
|
1085
1128
|
}
|
|
1086
1129
|
/**
|
|
1087
|
-
*
|
|
1130
|
+
* Counts this server prints about itself, all derived, never typed.
|
|
1088
1131
|
*
|
|
1089
|
-
*
|
|
1090
|
-
*
|
|
1132
|
+
* Every number below used to be a literal, and they disagreed with each other
|
|
1133
|
+
* and with tools/list: the instructions said "120 tools across 16 categories",
|
|
1134
|
+
* docs://tools-overview said 105 in its description and "120 across 11" in its
|
|
1135
|
+
* body, persona_creation was "(6 tools)" with nine registered, and
|
|
1136
|
+
* docs://personas said 21 while listing 6 of the 10 general personas. Measured
|
|
1137
|
+
* 2026-10-10: tools/list on the enterprise server returned 130 tools, 126 of
|
|
1138
|
+
* them in TOOL_CATEGORIES' 20 categories and 4 in none (B33).
|
|
1091
1139
|
*/
|
|
1092
|
-
function
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
tools: { listChanged: true },
|
|
1107
|
-
prompts: {},
|
|
1108
|
-
resources: {},
|
|
1109
|
-
},
|
|
1110
|
-
instructions: `CBrowser — cognitive browser automation with 120 tools across 16 categories.
|
|
1140
|
+
function catalogCounts() {
|
|
1141
|
+
return { tools: categorizedToolNames().length, categories: TOOL_CATEGORIES.length };
|
|
1142
|
+
}
|
|
1143
|
+
/** One line per specialist category, counted from TOOL_CATEGORIES. */
|
|
1144
|
+
function specialistCategoryLines() {
|
|
1145
|
+
return TOOL_CATEGORIES
|
|
1146
|
+
.filter((cat) => cat.tier > 1)
|
|
1147
|
+
.map((cat) => `• ${cat.name} (${cat.tools.length} tool${cat.tools.length === 1 ? "" : "s"}) — ${cat.tools.slice(0, 5).join(", ")}${cat.tools.length > 5 ? "..." : ""}`)
|
|
1148
|
+
.join("\n");
|
|
1149
|
+
}
|
|
1150
|
+
/** The server instructions sent on initialize. Exported so the counts can be tested. */
|
|
1151
|
+
export function buildServerInstructions() {
|
|
1152
|
+
const { tools, categories } = catalogCounts();
|
|
1153
|
+
return `CBrowser — cognitive browser automation with ${tools} tools across ${categories} categories.
|
|
1111
1154
|
|
|
1112
1155
|
CORE TOOLS (use these first):
|
|
1113
1156
|
• navigate, click, fill, scroll, screenshot, extract — browser control
|
|
@@ -1120,17 +1163,7 @@ CORE TOOLS (use these first):
|
|
|
1120
1163
|
• security_audit — MCP tool injection scanning
|
|
1121
1164
|
|
|
1122
1165
|
SPECIALIST CATEGORIES (use when the task requires them):
|
|
1123
|
-
|
|
1124
|
-
• cognitive_transport (7 tools) — cognitive_distance, cognitive_coverage, cognitive_interpolate, cognitive_load_estimate, compare_personas...
|
|
1125
|
-
• testing (5 tools) — nl_test_inline, nl_test_file, detect_flaky_tests, coverage_map, repair_test
|
|
1126
|
-
• site_knowledge (5 tools) — site_model_status, site_model_query, site_profile_list/delete/status
|
|
1127
|
-
• bug_hunting (3 tools) — hunt_bugs, chaos_test, competitive_benchmark
|
|
1128
|
-
• performance (3 tools) — perf_baseline, perf_regression, list_baselines
|
|
1129
|
-
• sessions (4 tools) — save_session, load_session, list_sessions, delete_session
|
|
1130
|
-
• marketing (4 tools) — marketing_campaign_create/run/report, marketing_personas_list
|
|
1131
|
-
• persona_creation (6 tools) — persona_create_start, questionnaire, from_description
|
|
1132
|
-
• enterprise_security (8 tools) — web_security_scan, stealth_enable/disable/check, cloudflare_detect/wait
|
|
1133
|
-
• enterprise_marketing (6 tools) — marketing_influence_matrix, lever_analysis, funnel_analyze, audience_discover
|
|
1166
|
+
${specialistCategoryLines()}
|
|
1134
1167
|
|
|
1135
1168
|
TIPS:
|
|
1136
1169
|
• Pass _browserToken between tool calls to maintain browser state
|
|
@@ -1149,7 +1182,56 @@ SELF-HELP:
|
|
|
1149
1182
|
• 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
|
|
1150
1183
|
• question_answer searches 290+ pages of CBrowser documentation and returns a grounded answer
|
|
1151
1184
|
• Use it before guessing. Example: question_answer(question: "What is the difference between viewport and full_page scope?")
|
|
1152
|
-
• Use it to explain results to the user. Example: question_answer(question: "What does a CTC of 1.6 with readability bottleneck mean?")
|
|
1185
|
+
• Use it to explain results to the user. Example: question_answer(question: "What does a CTC of 1.6 with readability bottleneck mean?")`;
|
|
1186
|
+
}
|
|
1187
|
+
/** Body of docs://tools-overview, counted from TOOL_CATEGORIES. */
|
|
1188
|
+
export function buildToolsOverviewText() {
|
|
1189
|
+
const { tools, categories } = catalogCounts();
|
|
1190
|
+
const list = TOOL_CATEGORIES.map((cat) => `${cat.name} (${cat.tools.length})`).join(", ");
|
|
1191
|
+
return `CBrowser provides ${tools} MCP tools across ${categories} categories: ${list}. Visit https://cbrowser.ai/docs/Tools-Overview for the complete reference.`;
|
|
1192
|
+
}
|
|
1193
|
+
/** Body of docs://personas, listed from the persona maps themselves. */
|
|
1194
|
+
export function buildPersonasOverviewText() {
|
|
1195
|
+
const groups = [
|
|
1196
|
+
["Cognitive", Object.keys(BUILTIN_PERSONAS)],
|
|
1197
|
+
["Accessibility", Object.keys(ACCESSIBILITY_PERSONAS)],
|
|
1198
|
+
["Emotional", Object.keys(EMOTIONAL_PERSONAS)],
|
|
1199
|
+
["AI agent", Object.keys(AGENT_PERSONAS)],
|
|
1200
|
+
];
|
|
1201
|
+
const total = groups.reduce((n, [, names]) => n + names.length, 0);
|
|
1202
|
+
const body = groups.map(([label, names]) => `${label} (${names.length}): ${names.join(", ")}`).join(". ");
|
|
1203
|
+
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.`;
|
|
1204
|
+
}
|
|
1205
|
+
/** Number of built-in personas across every map docs://personas lists. */
|
|
1206
|
+
function builtinPersonaCount() {
|
|
1207
|
+
return [BUILTIN_PERSONAS, ACCESSIBILITY_PERSONAS, EMOTIONAL_PERSONAS, AGENT_PERSONAS]
|
|
1208
|
+
.reduce((n, map) => n + Object.keys(map).length, 0);
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Create a configured MCP server instance
|
|
1212
|
+
*
|
|
1213
|
+
* @param customRegisterTools - Optional custom tool registration
|
|
1214
|
+
* @param sessionId - Optional session ID for session isolation
|
|
1215
|
+
*/
|
|
1216
|
+
function createMcpServer(customRegisterTools, sessionId, resolvedTier) {
|
|
1217
|
+
const server = new McpServer({
|
|
1218
|
+
name: "cbrowser",
|
|
1219
|
+
title: "CBrowser — Cognitive Browser Automation",
|
|
1220
|
+
version: VERSION,
|
|
1221
|
+
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.`,
|
|
1222
|
+
websiteUrl: "https://cbrowser.ai",
|
|
1223
|
+
icons: [
|
|
1224
|
+
{ src: "https://cbrowser.ai/icon-192.png", sizes: ["192x192"] },
|
|
1225
|
+
{ src: "https://cbrowser.ai/icon-512.png", sizes: ["512x512"] },
|
|
1226
|
+
{ src: "https://cbrowser.ai/logo.svg", sizes: ["1080x1080"] },
|
|
1227
|
+
],
|
|
1228
|
+
}, {
|
|
1229
|
+
capabilities: {
|
|
1230
|
+
tools: { listChanged: true },
|
|
1231
|
+
prompts: {},
|
|
1232
|
+
resources: {},
|
|
1233
|
+
},
|
|
1234
|
+
instructions: buildServerInstructions(),
|
|
1153
1235
|
});
|
|
1154
1236
|
// Register prompts for common workflows
|
|
1155
1237
|
server.prompt("audit-site", "Run a comprehensive site audit with empathy, agent-readiness, and visual baseline", async () => ({
|
|
@@ -1181,17 +1263,17 @@ SELF-HELP:
|
|
|
1181
1263
|
}));
|
|
1182
1264
|
registerUiResources(server);
|
|
1183
1265
|
// Register resources
|
|
1184
|
-
server.resource("docs://tools-overview", "docs://tools-overview", { description:
|
|
1266
|
+
server.resource("docs://tools-overview", "docs://tools-overview", { description: `Overview of all ${catalogCounts().tools} CBrowser MCP tools organized by category`, mimeType: "text/plain" }, async () => ({
|
|
1185
1267
|
contents: [{
|
|
1186
1268
|
uri: "docs://tools-overview",
|
|
1187
|
-
text:
|
|
1269
|
+
text: buildToolsOverviewText(),
|
|
1188
1270
|
mimeType: "text/plain",
|
|
1189
1271
|
}],
|
|
1190
1272
|
}));
|
|
1191
|
-
server.resource("docs://personas", "docs://personas", { description:
|
|
1273
|
+
server.resource("docs://personas", "docs://personas", { description: `List of ${builtinPersonaCount()} built-in personas`, mimeType: "text/plain" }, async () => ({
|
|
1192
1274
|
contents: [{
|
|
1193
1275
|
uri: "docs://personas",
|
|
1194
|
-
text:
|
|
1276
|
+
text: buildPersonasOverviewText(),
|
|
1195
1277
|
mimeType: "text/plain",
|
|
1196
1278
|
}],
|
|
1197
1279
|
}));
|
|
@@ -1532,7 +1614,13 @@ export async function startRemoteMcpServer(options) {
|
|
|
1532
1614
|
console.log(` - Enterprise: unlimited`);
|
|
1533
1615
|
}
|
|
1534
1616
|
console.log(`Listening on ${host}:${port}`);
|
|
1535
|
-
|
|
1617
|
+
// Every HTTP request runs in its own identity context, so the key hash, tier
|
|
1618
|
+
// and API key the /mcp branch sets for it belong to it alone. They were module
|
|
1619
|
+
// globals, set while the request's server was built and read when its tool
|
|
1620
|
+
// ran, several awaits later; an overlapping request from another account
|
|
1621
|
+
// overwrote them in between. The context starts as an unauthenticated caller
|
|
1622
|
+
// at the deployment's default tier. (sec-identity, 2026-10-10)
|
|
1623
|
+
const httpServer = createServer((req, res) => withRequestIdentity({ keyHash: null, tier: effectiveTier(null), apiKey: undefined }, async () => {
|
|
1536
1624
|
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
1537
1625
|
// Security headers (defense in depth)
|
|
1538
1626
|
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
@@ -1874,11 +1962,14 @@ if (urlParams.get('error')) {
|
|
|
1874
1962
|
}
|
|
1875
1963
|
// llms.txt - AI-readable documentation (WebMCP standard)
|
|
1876
1964
|
if (url.pathname === "/llms.txt" || url.pathname === "/.well-known/llms.txt") {
|
|
1965
|
+
// Count and category list derived from TOOL_CATEGORIES. The hand-written
|
|
1966
|
+
// block said 91 tools, and its per-category numbers did not match the
|
|
1967
|
+
// names it listed ("Visual Testing (6)" named five). (B33, 2026-10-10)
|
|
1877
1968
|
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
1878
1969
|
res.end(`# CBrowser MCP Server
|
|
1879
1970
|
# AI-powered browser automation with cognitive user simulation
|
|
1880
1971
|
|
|
1881
|
-
> CBrowser is an MCP server that provides
|
|
1972
|
+
> CBrowser is an MCP server that provides ${catalogCounts().tools} tools for browser automation,
|
|
1882
1973
|
> visual testing, cognitive user simulation, and accessibility auditing.
|
|
1883
1974
|
> It simulates how real humans think, struggle, and give up on websites.
|
|
1884
1975
|
|
|
@@ -1901,17 +1992,8 @@ POST /mcp - Model Context Protocol endpoint (JSON-RPC 2.0 over SSE)
|
|
|
1901
1992
|
- API Key authentication for programmatic access
|
|
1902
1993
|
- Demo server (demo.cbrowser.ai): Rate-limited, no auth required
|
|
1903
1994
|
|
|
1904
|
-
## Tool Categories (
|
|
1905
|
-
|
|
1906
|
-
- Interaction (5): click, smart_click, fill, scroll, press_key
|
|
1907
|
-
- Analysis (4): analyze_page, extract, find_element_by_intent, assert
|
|
1908
|
-
- Visual Testing (6): visual_baseline, visual_regression, cross_browser_test, responsive_test, ab_comparison
|
|
1909
|
-
- Cognitive (3): cognitive_journey_init, cognitive_journey_update_state, list_cognitive_personas
|
|
1910
|
-
- Personas (10): compare_personas_init, persona_create, persona_traits_list, etc.
|
|
1911
|
-
- Testing (5): nl_test_file, nl_test_inline, repair_test, detect_flaky_tests, coverage_map
|
|
1912
|
-
- Audit (4): agent_ready_audit, competitive_benchmark, empathy_audit, webmcp_ready_audit
|
|
1913
|
-
- Session (4): save_session, load_session, list_sessions, delete_session
|
|
1914
|
-
- Browser (4): browser_health, browser_recover, reset_browser, status
|
|
1995
|
+
## Tool Categories (${catalogCounts().tools} tools)
|
|
1996
|
+
${TOOL_CATEGORIES.map((cat) => `- ${cat.name} (${cat.tools.length}): ${cat.tools.join(", ")}`).join("\n")}
|
|
1915
1997
|
|
|
1916
1998
|
## Links
|
|
1917
1999
|
- Documentation: https://cbrowser.ai/docs
|
|
@@ -1958,8 +2040,8 @@ ${VERSION}
|
|
|
1958
2040
|
}
|
|
1959
2041
|
}</code></pre>
|
|
1960
2042
|
|
|
1961
|
-
<h2>Available Tools (
|
|
1962
|
-
<p>This server provides
|
|
2043
|
+
<h2>Available Tools (${catalogCounts().tools})</h2>
|
|
2044
|
+
<p>This server provides ${catalogCounts().tools} MCP tools for browser automation, testing, and analysis.</p>
|
|
1963
2045
|
<ul>
|
|
1964
2046
|
<li><strong>Navigation:</strong> navigate, screenshot</li>
|
|
1965
2047
|
<li><strong>Interaction:</strong> click, smart_click, fill, scroll, press_key</li>
|
|
@@ -2195,7 +2277,7 @@ ${VERSION}
|
|
|
2195
2277
|
// Get or create session
|
|
2196
2278
|
// v18.14.0: Use client's mcp-session-id for browser session correlation
|
|
2197
2279
|
// This fixes DOM interaction desync where navigate worked but other tools saw blank page
|
|
2198
|
-
const existingSessionId = req.headers["mcp-session-id"];
|
|
2280
|
+
const existingSessionId = sessionIdFromHeader(req.headers["mcp-session-id"]);
|
|
2199
2281
|
let transport;
|
|
2200
2282
|
let browserSessionId;
|
|
2201
2283
|
// First, determine the browser session ID (independent of transport mode)
|
|
@@ -2224,6 +2306,21 @@ ${VERSION}
|
|
|
2224
2306
|
// Log session handling for debugging (truncate ID for readability)
|
|
2225
2307
|
const shortId = browserSessionId.substring(0, 8);
|
|
2226
2308
|
console.log(`[Session] ${sessionSource}: ${shortId}... (active: ${activeSessions.size})`);
|
|
2309
|
+
// This request's caller, written into its own identity context (entered at
|
|
2310
|
+
// createServer above). Set before the transport branch so a reused
|
|
2311
|
+
// stateful transport gets it too: that path used to run on whatever the
|
|
2312
|
+
// last request to build a server had left in the globals. Building a new
|
|
2313
|
+
// server below sets the tier again during registration, as it always did.
|
|
2314
|
+
// (sec-identity, 2026-10-10)
|
|
2315
|
+
const requestApiKey = extractApiKey(req);
|
|
2316
|
+
const resolvedTier = requestApiKey ? await resolveApiKeyTier(requestApiKey) : null;
|
|
2317
|
+
// Key hash for usage tracking: a cbk_ key's own hash, or the key an OAuth
|
|
2318
|
+
// token was issued for; null otherwise. The same derivation as billing.
|
|
2319
|
+
setActiveKeyHash(await resolveRequestKeyHash(req));
|
|
2320
|
+
// Raw key for account-scoped features (custom personas, etc.)
|
|
2321
|
+
const { setSessionApiKey } = await import("./mcp-tools/base/cognitive-tools.js");
|
|
2322
|
+
setSessionApiKey(requestApiKey || undefined);
|
|
2323
|
+
setTierGate(effectiveTier(resolvedTier));
|
|
2227
2324
|
// Now handle transport (stateful mode caches transport, stateless creates new each time)
|
|
2228
2325
|
const hasExistingTransport = existingSessionId ? transports.has(existingSessionId) : false;
|
|
2229
2326
|
if (sessionMode === "stateful" && existingSessionId && hasExistingTransport) {
|
|
@@ -2237,30 +2334,6 @@ ${VERSION}
|
|
|
2237
2334
|
transport = new StreamableHTTPServerTransport({
|
|
2238
2335
|
sessionIdGenerator: sessionMode === "stateful" ? () => browserSessionId : undefined,
|
|
2239
2336
|
});
|
|
2240
|
-
// Resolve pricing tier from API key (cbk_ keys → CMS lookup)
|
|
2241
|
-
const requestApiKey = extractApiKey(req);
|
|
2242
|
-
const resolvedTier = requestApiKey ? await resolveApiKeyTier(requestApiKey) : null;
|
|
2243
|
-
// Set key hash for usage tracking
|
|
2244
|
-
if (requestApiKey?.startsWith("cbk_")) {
|
|
2245
|
-
const { createHash } = await import("crypto");
|
|
2246
|
-
setActiveKeyHash(createHash("sha256").update(requestApiKey).digest("hex"));
|
|
2247
|
-
}
|
|
2248
|
-
else if (requestApiKey) {
|
|
2249
|
-
// Check token→keyHash map (OAuth tokens that aren't raw cbk_ keys)
|
|
2250
|
-
const mapped = tokenToKeyHash.get(requestApiKey);
|
|
2251
|
-
if (mapped && mapped.expiresAt > Date.now()) {
|
|
2252
|
-
setActiveKeyHash(mapped.keyHash);
|
|
2253
|
-
}
|
|
2254
|
-
else {
|
|
2255
|
-
setActiveKeyHash(null);
|
|
2256
|
-
}
|
|
2257
|
-
}
|
|
2258
|
-
else {
|
|
2259
|
-
setActiveKeyHash(null);
|
|
2260
|
-
}
|
|
2261
|
-
// Set session API key for account-scoped features (custom personas, etc.)
|
|
2262
|
-
const { setSessionApiKey } = await import("./mcp-tools/base/cognitive-tools.js");
|
|
2263
|
-
setSessionApiKey(requestApiKey || undefined);
|
|
2264
2337
|
// Create and connect server with session isolation and tier gating
|
|
2265
2338
|
const server = createMcpServer(options?.registerTools, browserSessionId, resolvedTier);
|
|
2266
2339
|
// Allow extension with additional tools (for Enterprise)
|
|
@@ -2318,7 +2391,7 @@ ${VERSION}
|
|
|
2318
2391
|
// 404 for other paths
|
|
2319
2392
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
2320
2393
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
2321
|
-
});
|
|
2394
|
+
}));
|
|
2322
2395
|
httpServer.listen(port, host, () => {
|
|
2323
2396
|
void (async () => {
|
|
2324
2397
|
console.log(`\nCognitive Browser Remote MCP Server running at http://${host}:${port}`);
|