/** * mcp/takeover.ts — Browser control tools via direct takeover token map access * * Token is set implicitly per-request from the MCP API key mapping. * No token param needed in tool calls. */ export interface TakeoverBridge { sendCmd(token: string, cmd: string, args: any, timeoutMs?: number): Promise; } let bridge: TakeoverBridge | null = null; let activeToken: string = ""; export function setBridge(b: TakeoverBridge) { bridge = b; } export function setActiveToken(token: string) { activeToken = token; } function getToken(): string { if (!activeToken) throw new Error("No takeover token — MCP key not linked"); return activeToken; } function effectiveToken(breakout?: string): string { const t = getToken(); return breakout ? `${t}-breakout-${breakout}` : t; } async function cmd(command: string, args: any = {}, opts: { breakout?: string; timeout?: number } = {}) { if (!bridge) throw new Error("Takeover bridge not initialized"); return bridge.sendCmd( effectiveToken(opts.breakout), command, args, opts.timeout ?? 10000, ); } export const tools = [ { name: "takeover_cmd", description: "Execute a takeover command on the browser. Commands: boxChain, getStyles, viewport, navigate, reload, resize, querySelector, click, screenshot, getValue, setValue, typeText, listBreakouts, closeBreakout, captureScreen, enableCapture, openBreakout, eval, getConsole.", inputSchema: { type: "object" as const, properties: { cmd: { type: "string", description: "Command name" }, args: { type: "object", description: "Command arguments" }, breakout: { type: "string", description: "Breakout name (derives token automatically)" }, timeout: { type: "number", description: "Timeout in ms (default 10000, max 60000)" }, }, required: ["cmd"], }, }, { name: "takeover_screenshot", description: "Capture a WebRTC screenshot from the browser. Returns base64 JPEG image. Requires enableCapture first.", inputSchema: { type: "object" as const, properties: { breakout: { type: "string", description: "Breakout name" }, quality: { type: "number", description: "JPEG quality 0-1 (default 0.5)" }, }, }, }, { name: "takeover_health", description: "Check browser health: viewport + capture stream status in one call.", inputSchema: { type: "object" as const, properties: { breakout: { type: "string", description: "Breakout name" }, }, }, }, { name: "takeover_open_breakout", description: "Open a breakout test window. User must confirm in browser. 30s timeout.", inputSchema: { type: "object" as const, properties: { name: { type: "string", description: "Breakout name" }, preset: { type: "string", enum: ["mobile", "tablet", "tablet-landscape", "desktop"], description: "Size preset (default: mobile)" }, }, required: ["name"], }, }, { name: "takeover_enable_capture", description: "Enable WebRTC capture on a browser window. User must pick tab. 30s timeout.", inputSchema: { type: "object" as const, properties: { breakout: { type: "string", description: "Breakout name" }, }, }, }, ]; export async function handle(name: string, args: any): Promise { switch (name) { case "takeover_cmd": { const result = await cmd(args.cmd, args.args || {}, { breakout: args.breakout, timeout: args.timeout, }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } case "takeover_screenshot": { const result = await cmd("captureScreen", { quality: args.quality ?? 0.5 }, { breakout: args.breakout, timeout: 15000, }); if (result.error) { return { content: [{ type: "text", text: `Screenshot failed: ${result.error}` }] }; } if (result.result?.dataUrl) { const [header, data] = result.result.dataUrl.split(",", 2); const mimeMatch = header.match(/data:([^;]+)/); return { content: [{ type: "image", data, mimeType: mimeMatch ? mimeMatch[1] : "image/jpeg", }], }; } return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } case "takeover_health": { const opts = { breakout: args.breakout, timeout: 5000 }; const [vp, cap] = await Promise.allSettled([ cmd("viewport", {}, opts), cmd("captureScreen", { quality: 0.1 }, opts), ]); const viewport = vp.status === "fulfilled" ? vp.value : { error: "unreachable" }; const capture = cap.status === "fulfilled" ? (cap.value.result?.dataUrl ? { active: true } : { active: false, reason: cap.value.result?.error || cap.value.error }) : { active: false, reason: "unreachable" }; return { content: [{ type: "text", text: JSON.stringify({ viewport: viewport.result || viewport, capture }, null, 2), }], }; } case "takeover_open_breakout": { const result = await cmd("openBreakout", { name: args.name, preset: args.preset || "mobile", }, { timeout: 30000 }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } case "takeover_enable_capture": { const result = await cmd("enableCapture", {}, { breakout: args.breakout, timeout: 30000, }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } default: throw new Error(`Unknown takeover tool: ${name}`); } }