interceptor_browser_evaluate
Execute a JavaScript file in a browser page and retrieve its return value. Supports optional arguments and isolated or main execution worlds.
Instructions
Execute a JS file in the page and return its result. Source is loaded from script_path (absolute path). The file body is wrapped in an arrow function receiving __args (so the file may return value; directly and access the optional args object). Worlds: isolated (default) or main (camoufox-only, requires main_world_eval: true at launch). Cloakbrowser (Chromium): isolated runs in Playwright's utility world (different window, same DOM). main is rejected — use interceptor_browser_inject_init_script for main-world patching there. Camoufox (cloverlabs/FF150): there is no separate isolated world — both permitted modes run in the page's main world. Reads are invisible to the page; mutations (window.x = …, Object.defineProperty, prototype patches) are observable by page scripts. Earlier daijro/FF135 had a Juggler scope that made isolated invisible to the page; that scope was removed in cloverlabs. Verify on your installed build with scripts/camoufox-world-probe.ts. Rate-limit on cloakbrowser before reCAPTCHA: each call emits CDP traffic that behavioural scorers count.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| target_id | Yes | Target ID from interceptor_browser_launch or interceptor_camoufox_launch | |
| script_path | Yes | Absolute path to a .js file. File body is the function body; use `return` to send a value back. | |
| args | No | Optional JSON-serialisable args object, available inside the script as `__args`. | |
| world | No | `isolated` (default) or `main`. On current camoufox build (cloverlabs/FF150) both run in the page's main world — arg is accepted but has no observable effect. | isolated |
| value_max_chars | No | Max characters of the JSON-stringified return value (default: 20000). |
Implementation Reference
- src/tools/devtools.ts:713-789 (handler)Main handler function for the interceptor_browser_evaluate tool. Reads a JS file, wraps it in an arrow function, evaluates it in the target page (isolated or main world), and returns the capped result.
server.tool( "interceptor_browser_evaluate", "Execute a JS file in the page and return its result. " + "Source is loaded from `script_path` (absolute path). The file body is wrapped in an arrow " + "function receiving `__args` (so the file may `return value;` directly and access the optional " + "args object). " + "Worlds: `isolated` (default) or `main` (camoufox-only, requires `main_world_eval: true` at launch). " + "Cloakbrowser (Chromium): `isolated` runs in Playwright's utility world (different `window`, same DOM). " + "`main` is rejected — use `interceptor_browser_inject_init_script` for main-world patching there. " + "Camoufox (cloverlabs/FF150): there is no separate isolated world — both permitted modes run in the page's main world. " + "Reads are invisible to the page; mutations (`window.x = …`, `Object.defineProperty`, prototype patches) are observable by page scripts. " + "Earlier daijro/FF135 had a Juggler scope that made `isolated` invisible to the page; that scope was removed in cloverlabs. " + "Verify on your installed build with `scripts/camoufox-world-probe.ts`. " + "Rate-limit on cloakbrowser before reCAPTCHA: each call emits CDP traffic that behavioural scorers count.", { target_id: z.string().describe("Target ID from interceptor_browser_launch or interceptor_camoufox_launch"), script_path: z.string().describe("Absolute path to a .js file. File body is the function body; use `return` to send a value back."), args: z.record(z.unknown()).optional().describe("Optional JSON-serialisable args object, available inside the script as `__args`."), world: z.enum(["isolated", "main"]).optional().default("isolated") .describe("`isolated` (default) or `main`. On current camoufox build (cloverlabs/FF150) both run in the page's main world — arg is accepted but has no observable effect."), value_max_chars: z.number().optional().default(HARD_VALUE_CAP_CHARS) .describe(`Max characters of the JSON-stringified return value (default: ${HARD_VALUE_CAP_CHARS}).`), }, async ({ target_id, script_path, args, world, value_max_chars }) => { try { if (!isAbsolute(script_path)) { return { content: [{ type: "text", text: JSON.stringify({ status: "error", error: `script_path must be absolute: '${script_path}'` }) }] }; } const source = await readFile(script_path, "utf-8"); const page = await getPageForTarget(target_id); const isCamoufox = isCamoufoxTarget(target_id); if (world === "main") { if (!isCamoufox) { return { content: [{ type: "text", text: JSON.stringify({ status: "error", error: "world: 'main' is only supported on camoufox targets. On cloakbrowser, use interceptor_browser_inject_init_script for main-world patching.", }) }] }; } const entry = getEntry(target_id); const mwEnabled = Boolean((entry.target.details as { main_world_eval?: boolean } | undefined)?.main_world_eval); if (!mwEnabled) { return { content: [{ type: "text", text: JSON.stringify({ status: "error", error: "Camoufox target launched without main_world_eval=true; main-world evaluate is disabled. Relaunch with `main_world_eval: true`.", }) }] }; } } const argsLiteral = JSON.stringify(args ?? {}); const fnExpr = `((__args) => { ${source}\n })(${argsLiteral})`; const pageFunction = world === "main" && isCamoufox ? `mw:${fnExpr}` : fnExpr; const result = await page.evaluate(pageFunction); const serialised = result === undefined ? "" : JSON.stringify(result); const capped = capValue(serialised, Math.max(0, Math.min(HARD_VALUE_CAP_CHARS, Math.trunc(value_max_chars ?? HARD_VALUE_CAP_CHARS)))); return { content: [{ type: "text", text: truncateResult({ status: "success", target_id, world, backend: isCamoufox ? "camoufox" : "cloakbrowser", value: capped.value, value_length: capped.valueLength, value_truncated: capped.truncated, value_max_chars: capped.maxChars, }), }], }; } catch (e) { return { content: [{ type: "text", text: JSON.stringify({ status: "error", error: errorToString(e) }) }] }; } }, ); - src/tools/devtools.ts:728-735 (schema)Zod schema defining the input parameters for the evaluate tool: target_id, script_path, args (optional), world (enum isolated/main), value_max_chars.
target_id: z.string().describe("Target ID from interceptor_browser_launch or interceptor_camoufox_launch"), script_path: z.string().describe("Absolute path to a .js file. File body is the function body; use `return` to send a value back."), args: z.record(z.unknown()).optional().describe("Optional JSON-serialisable args object, available inside the script as `__args`."), world: z.enum(["isolated", "main"]).optional().default("isolated") .describe("`isolated` (default) or `main`. On current camoufox build (cloverlabs/FF150) both run in the page's main world — arg is accepted but has no observable effect."), value_max_chars: z.number().optional().default(HARD_VALUE_CAP_CHARS) .describe(`Max characters of the JSON-stringified return value (default: ${HARD_VALUE_CAP_CHARS}).`), }, - src/tools/devtools.ts:76-86 (helper)Helper function that caps a string value to a maximum number of characters, appending '...' if truncated.
function capValue(value: string, maxChars: number): { value: string; valueLength: number; truncated: boolean; maxChars: number } { const valueLength = value.length; const effectiveMax = Math.max(0, Math.min(HARD_VALUE_CAP_CHARS, Math.trunc(maxChars))); if (effectiveMax === 0) { return { value, valueLength, truncated: false, maxChars: 0 }; } if (valueLength <= effectiveMax) { return { value, valueLength, truncated: false, maxChars: effectiveMax }; } return { value: value.slice(0, effectiveMax) + "...", valueLength, truncated: true, maxChars: effectiveMax }; } - src/browser/session.ts:44-127 (helper)Helper that resolves a target_id to the corresponding Playwright Page object, supporting both Camoufox and Cloakbrowser targets.
return isCamoufoxTargetId(targetId); } async function ensureCamoufoxPage(entry: CamoufoxEntryWithDriver): Promise<Page> { if (entry.page && !entry.page.isClosed()) return entry.page; if (!entry.browser) { entry.browser = await firefox.connect(entry.wsUrl); } let ctx = entry.browser.contexts()[0]; if (!ctx) { // BrowserServer + persistent_context: the persistent context lives // server-side and `Browser.contexts()` from a fresh `firefox.connect()` // returns empty. New contexts created here do NOT inherit the // launch-level proxy, so we have to wire the MITM proxy explicitly // or the firefox process reaches the internet directly and bypasses // capture. Pull the port back out of the entry details (set at // activate() time). const proxyPort = (entry.target.details as { proxyPort?: number } | undefined)?.proxyPort; ctx = await entry.browser.newContext({ ignoreHTTPSErrors: true, ...(proxyPort ? { proxy: { server: `http://127.0.0.1:${proxyPort}` } } : {}), }); } let page = ctx.pages()[0]; if (!page) { page = await ctx.newPage(); } entry.context = ctx; entry.page = page; ensureConsoleBuffer(entry, page); return page; } function ensureConsoleBuffer(entry: CamoufoxEntryWithDriver, page: Page): ConsoleEntry[] { if (!entry.consoleBuffer) entry.consoleBuffer = []; if (entry.consolePage === page) return entry.consoleBuffer; entry.consolePage = page; page.on("console", (msg: ConsoleMessage) => { const loc = msg.location(); entry.consoleBuffer?.push({ type: msg.type(), text: msg.text(), location: loc.url ? `${loc.url}:${loc.lineNumber ?? 0}:${loc.columnNumber ?? 0}` : "", timestamp: Date.now(), }); if (entry.consoleBuffer && entry.consoleBuffer.length > CONSOLE_BUFFER_MAX) { entry.consoleBuffer.splice(0, entry.consoleBuffer.length - CONSOLE_BUFFER_MAX); } }); return entry.consoleBuffer; } export function getEntry(targetId: string): BrowserTargetEntry | CamoufoxEntryWithDriver { if (isCamoufoxTargetId(targetId)) { const cam = getCamoufoxInterceptor(); const entry = cam?.getEntry(targetId) as CamoufoxEntryWithDriver | undefined; if (!entry) throw new Error(`Browser target '${targetId}' not found. Is it still running?`); return entry; } const entry = getBrowserInterceptor().getEntry(targetId); if (!entry) throw new Error(`Browser target '${targetId}' not found. Is it still running?`); return entry; } /** * Cloakbrowser-only entry getter. Use when the caller needs cloakbrowser * internals rather than the shared page/context helpers. Camoufox targets get * a clear error instead of a deep type-mismatch. */ export function getBrowserEntry(targetId: string): BrowserTargetEntry { if (isCamoufoxTargetId(targetId)) { throw new Error( `Internal cloakbrowser-only entry requested for camoufox target ('${targetId}'). ` + "Use the shared browser tools for camoufox targets.", ); } const entry = getBrowserInterceptor().getEntry(targetId); if (!entry) throw new Error(`Browser target '${targetId}' not found. Is it still running?`); return entry; } export async function getPageForTarget(targetId: string): Promise<Page> { - src/browser/session.ts:39-45 (helper)Helper that checks whether a given target ID belongs to a Camoufox browser (prefixed with 'camoufox_').
function isCamoufoxTargetId(targetId: string): boolean { return typeof targetId === "string" && targetId.startsWith("camoufox_"); } export function isCamoufoxTarget(targetId: string): boolean { return isCamoufoxTargetId(targetId); }