claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
757 lines • 37.6 kB
JavaScript
/**
* Browser MCP Tools
*
* CLI integration for @claude-flow/browser package.
* Provides browser automation tools for web navigation, interaction, and data extraction.
*/
import { readFileSync, existsSync } from 'node:fs';
import { validateIdentifier, validateText } from './validate-input.js';
// Session registry for multi-session support
const browserSessions = new Map();
/**
* Execute agent-browser CLI command.
* Tries global agent-browser first, falls back to npx if ENOENT.
*/
async function execBrowserCommand(args, session = 'default') {
const { execFileSync } = await import('child_process');
const fullArgs = ['--session', session, '--json', ...args];
let result;
try {
result = execFileSync('agent-browser', fullArgs, {
encoding: 'utf-8',
timeout: 30000,
});
}
catch (error) {
const err = error;
if (err.code === 'ENOENT') {
try {
result = execFileSync('npx', ['--yes', 'agent-browser', ...fullArgs], {
encoding: 'utf-8',
timeout: 60000,
});
}
catch (npxError) {
const npxErr = npxError;
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: npxErr.code === 'ENOENT'
? 'Neither agent-browser nor npx found. Install with: npm i -g agent-browser'
: npxErr instanceof Error ? npxErr.message : String(npxError),
}),
}],
isError: true,
};
}
}
else {
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: err instanceof Error ? err.message : String(error),
}),
}],
isError: true,
};
}
}
let data;
try {
data = JSON.parse(result);
}
catch {
data = result.trim();
}
const sessionInfo = browserSessions.get(session);
if (sessionInfo) {
sessionInfo.lastActivity = new Date().toISOString();
}
return {
content: [{
type: 'text',
text: JSON.stringify(data, null, 2),
}],
};
}
/**
* Read a sysctl value, returning the trimmed string or null.
*/
function readSysctl(name) {
try {
const p = `/proc/sys/kernel/${name}`;
if (existsSync(p))
return readFileSync(p, 'utf-8').trim();
}
catch { /* not Linux or can't read */ }
return null;
}
/**
* Detect if Linux needs --no-sandbox for Chrome.
* Checks both legacy userns flag and Ubuntu 24.04+ AppArmor restriction.
*/
function needsNoSandbox() {
if (process.platform !== 'linux')
return false;
return readSysctl('unprivileged_userns_clone') === '0' ||
readSysctl('apparmor_restrict_unprivileged_userns') === '1';
}
/**
* Browser MCP Tools
*/
export const browserTools = [
// ==========================================================================
// Navigation Tools
// ==========================================================================
{
name: 'browser_open',
description: 'Navigate browser to a URL Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['navigation', 'web'],
inputSchema: {
type: 'object',
properties: {
url: { type: 'string', description: 'URL to navigate to' },
session: { type: 'string', description: 'Session ID (default: "default")' },
waitUntil: {
type: 'string',
enum: ['load', 'domcontentloaded', 'networkidle'],
description: 'Wait condition',
},
args: {
type: 'array',
items: { type: 'string' },
description: 'Additional Chrome launch args (e.g., ["--no-sandbox", "--disable-dev-shm-usage"])',
},
},
required: ['url'],
},
handler: async (input) => {
const vUrl = validateText(input.url, 'url');
if (!vUrl.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vUrl.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { url, session, waitUntil } = input;
const launchArgs = input.args || [];
if (needsNoSandbox() && !launchArgs.includes('--no-sandbox')) {
launchArgs.push('--no-sandbox');
}
const args = ['open', url];
if (waitUntil)
args.push('--wait-until', waitUntil);
if (launchArgs.length > 0)
args.push('--args', launchArgs.join(','));
// Create session if new
const sessionId = session || 'default';
if (!browserSessions.has(sessionId)) {
browserSessions.set(sessionId, {
sessionId,
createdAt: new Date().toISOString(),
lastActivity: new Date().toISOString(),
});
}
return execBrowserCommand(args, sessionId);
},
},
{
name: 'browser_back',
description: 'Navigate back in browser history Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['navigation'],
inputSchema: {
type: 'object',
properties: {
session: { type: 'string', description: 'Session ID' },
},
},
handler: async (input) => {
const { session } = input;
return execBrowserCommand(['back'], session);
},
},
{
name: 'browser_forward',
description: 'Navigate forward in browser history Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['navigation'],
inputSchema: {
type: 'object',
properties: {
session: { type: 'string', description: 'Session ID' },
},
},
handler: async (input) => {
const { session } = input;
return execBrowserCommand(['forward'], session);
},
},
{
name: 'browser_reload',
description: 'Reload the current page Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['navigation'],
inputSchema: {
type: 'object',
properties: {
session: { type: 'string', description: 'Session ID' },
},
},
handler: async (input) => {
const { session } = input;
return execBrowserCommand(['reload'], session);
},
},
{
name: 'browser_close',
description: 'Close the browser session Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['navigation'],
inputSchema: {
type: 'object',
properties: {
session: { type: 'string', description: 'Session ID' },
},
},
handler: async (input) => {
const { session } = input;
const sessionId = session || 'default';
browserSessions.delete(sessionId);
return execBrowserCommand(['close'], sessionId);
},
},
// ==========================================================================
// Snapshot Tools (AI-Optimized)
// ==========================================================================
{
name: 'browser_snapshot',
description: 'Get AI-optimized accessibility tree snapshot with element refs (@e1, @e2, etc.) Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['snapshot', 'ai'],
inputSchema: {
type: 'object',
properties: {
session: { type: 'string', description: 'Session ID' },
interactive: { type: 'boolean', description: 'Only interactive elements (-i flag)' },
compact: { type: 'boolean', description: 'Remove empty structural elements (-c flag)' },
depth: { type: 'number', description: 'Limit tree depth (-d flag)' },
selector: { type: 'string', description: 'Scope to CSS selector (-s flag)' },
},
},
handler: async (input) => {
if (input.selector) {
const vSel = validateText(input.selector, 'selector');
if (!vSel.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vSel.error }) }], isError: true };
}
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { session, interactive, compact, depth, selector } = input;
const args = ['snapshot'];
if (interactive)
args.push('-i');
if (compact)
args.push('-c');
if (depth)
args.push('-d', String(depth));
if (selector)
args.push('-s', selector);
return execBrowserCommand(args, session);
},
},
{
name: 'browser_screenshot',
description: 'Capture screenshot of the page Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['snapshot', 'screenshot'],
inputSchema: {
type: 'object',
properties: {
session: { type: 'string', description: 'Session ID' },
path: { type: 'string', description: 'Save path (returns base64 if not specified)' },
fullPage: { type: 'boolean', description: 'Capture full page' },
},
},
handler: async (input) => {
if (input.path) {
const vP = validateText(input.path, 'path');
if (!vP.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vP.error }) }], isError: true };
}
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { session, path, fullPage } = input;
const args = ['screenshot'];
if (path)
args.push(path);
if (fullPage)
args.push('--full');
return execBrowserCommand(args, session);
},
},
// ==========================================================================
// Interaction Tools
// ==========================================================================
{
name: 'browser_click',
description: 'Click an element using ref (@e1) or CSS selector Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['interaction'],
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
session: { type: 'string', description: 'Session ID' },
button: { type: 'string', enum: ['left', 'right', 'middle'], description: 'Mouse button' },
count: { type: 'number', description: 'Click count (2 for double-click)' },
},
required: ['target'],
},
handler: async (input) => {
const vTarget = validateText(input.target, 'target');
if (!vTarget.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vTarget.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { target, session, button, count } = input;
const args = ['click', target];
if (button)
args.push('--button', button);
if (count)
args.push('--count', String(count));
return execBrowserCommand(args, session);
},
},
{
name: 'browser_fill',
description: 'Clear and fill an input element Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['interaction', 'form'],
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
value: { type: 'string', description: 'Value to fill' },
session: { type: 'string', description: 'Session ID' },
},
required: ['target', 'value'],
},
handler: async (input) => {
const vTarget = validateText(input.target, 'target');
if (!vTarget.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vTarget.error }) }], isError: true };
const vValue = validateText(input.value, 'value');
if (!vValue.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vValue.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { target, value, session } = input;
return execBrowserCommand(['fill', target, value], session);
},
},
{
name: 'browser_type',
description: 'Type text with key events (for autocomplete, etc.) Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['interaction', 'form'],
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Element ref or CSS selector' },
text: { type: 'string', description: 'Text to type' },
session: { type: 'string', description: 'Session ID' },
delay: { type: 'number', description: 'Delay between keystrokes (ms)' },
},
required: ['target', 'text'],
},
handler: async (input) => {
const vTarget = validateText(input.target, 'target');
if (!vTarget.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vTarget.error }) }], isError: true };
const vText = validateText(input.text, 'text');
if (!vText.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vText.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { target, text, session, delay } = input;
const args = ['type', target, text];
if (delay)
args.push('--delay', String(delay));
return execBrowserCommand(args, session);
},
},
{
name: 'browser_press',
description: 'Press a keyboard key Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['interaction'],
inputSchema: {
type: 'object',
properties: {
key: { type: 'string', description: 'Key to press (Enter, Tab, Escape, etc.)' },
session: { type: 'string', description: 'Session ID' },
},
required: ['key'],
},
handler: async (input) => {
const vKey = validateText(input.key, 'key');
if (!vKey.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vKey.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { key, session } = input;
return execBrowserCommand(['press', key], session);
},
},
{
name: 'browser_hover',
description: 'Hover over an element using ref (@e1) or CSS selector Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['interaction'],
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
session: { type: 'string', description: 'Session ID' },
},
required: ['target'],
},
handler: async (input) => {
const vTarget = validateText(input.target, 'target');
if (!vTarget.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vTarget.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { target, session } = input;
return execBrowserCommand(['hover', target], session);
},
},
{
name: 'browser_select',
description: 'Select an option from a dropdown Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['interaction', 'form'],
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Select element ref or CSS selector' },
value: { type: 'string', description: 'Option value to select' },
session: { type: 'string', description: 'Session ID' },
},
required: ['target', 'value'],
},
handler: async (input) => {
const vTarget = validateText(input.target, 'target');
if (!vTarget.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vTarget.error }) }], isError: true };
const vValue = validateText(input.value, 'value');
if (!vValue.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vValue.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { target, value, session } = input;
return execBrowserCommand(['select', target, value], session);
},
},
{
name: 'browser_check',
description: 'Check a checkbox Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['interaction', 'form'],
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Checkbox ref or CSS selector' },
session: { type: 'string', description: 'Session ID' },
},
required: ['target'],
},
handler: async (input) => {
const vTarget = validateText(input.target, 'target');
if (!vTarget.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vTarget.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { target, session } = input;
return execBrowserCommand(['check', target], session);
},
},
{
name: 'browser_uncheck',
description: 'Uncheck a checkbox Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['interaction', 'form'],
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Checkbox ref or CSS selector' },
session: { type: 'string', description: 'Session ID' },
},
required: ['target'],
},
handler: async (input) => {
const vTarget = validateText(input.target, 'target');
if (!vTarget.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vTarget.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { target, session } = input;
return execBrowserCommand(['uncheck', target], session);
},
},
{
name: 'browser_scroll',
description: 'Scroll the page Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['interaction'],
inputSchema: {
type: 'object',
properties: {
direction: { type: 'string', enum: ['up', 'down', 'left', 'right'], description: 'Scroll direction' },
amount: { type: 'number', description: 'Scroll amount in pixels' },
session: { type: 'string', description: 'Session ID' },
},
required: ['direction'],
},
handler: async (input) => {
const { direction, amount, session } = input;
const args = ['scroll', direction];
if (amount)
args.push(String(amount));
return execBrowserCommand(args, session);
},
},
// ==========================================================================
// Information Retrieval Tools
// ==========================================================================
{
name: 'browser_get-text',
description: 'Get text content of an element Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['info'],
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Element ref or CSS selector' },
session: { type: 'string', description: 'Session ID' },
},
required: ['target'],
},
handler: async (input) => {
const vTarget = validateText(input.target, 'target');
if (!vTarget.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vTarget.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { target, session } = input;
return execBrowserCommand(['get', 'text', target], session);
},
},
{
name: 'browser_get-value',
description: 'Get value of an input element Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['info', 'form'],
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Input element ref or CSS selector' },
session: { type: 'string', description: 'Session ID' },
},
required: ['target'],
},
handler: async (input) => {
const vTarget = validateText(input.target, 'target');
if (!vTarget.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vTarget.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { target, session } = input;
return execBrowserCommand(['get', 'value', target], session);
},
},
{
name: 'browser_get-title',
description: 'Get the page title Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['info'],
inputSchema: {
type: 'object',
properties: {
session: { type: 'string', description: 'Session ID' },
},
},
handler: async (input) => {
const { session } = input;
return execBrowserCommand(['get', 'title'], session);
},
},
{
name: 'browser_get-url',
description: 'Get the current URL Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['info'],
inputSchema: {
type: 'object',
properties: {
session: { type: 'string', description: 'Session ID' },
},
},
handler: async (input) => {
const { session } = input;
return execBrowserCommand(['get', 'url'], session);
},
},
// ==========================================================================
// Wait Tools
// ==========================================================================
{
name: 'browser_wait',
description: 'Wait for a condition Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['wait'],
inputSchema: {
type: 'object',
properties: {
selector: { type: 'string', description: 'CSS selector to wait for' },
text: { type: 'string', description: 'Text to wait for' },
url: { type: 'string', description: 'URL pattern to wait for' },
timeout: { type: 'number', description: 'Wait timeout in ms' },
session: { type: 'string', description: 'Session ID' },
},
},
handler: async (input) => {
if (input.selector) {
const vSel = validateText(input.selector, 'selector');
if (!vSel.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vSel.error }) }], isError: true };
}
if (input.text) {
const vT = validateText(input.text, 'text');
if (!vT.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vT.error }) }], isError: true };
}
if (input.url) {
const vU = validateText(input.url, 'url');
if (!vU.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vU.error }) }], isError: true };
}
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { selector, text, url, timeout, session } = input;
const args = ['wait'];
if (selector)
args.push(selector);
if (text)
args.push('--text', text);
if (url)
args.push('--url', url);
if (timeout)
args.push(String(timeout));
return execBrowserCommand(args, session);
},
},
// ==========================================================================
// JavaScript Execution
// ==========================================================================
{
name: 'browser_eval',
description: 'Execute JavaScript in page context Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['eval', 'js'],
inputSchema: {
type: 'object',
properties: {
script: { type: 'string', description: 'JavaScript code to execute' },
session: { type: 'string', description: 'Session ID' },
},
required: ['script'],
},
handler: async (input) => {
const vScript = validateText(input.script, 'script');
if (!vScript.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vScript.error }) }], isError: true };
if (input.session) {
const vS = validateIdentifier(input.session, 'session');
if (!vS.valid)
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
}
const { script, session } = input;
return execBrowserCommand(['eval', script], session);
},
},
// ==========================================================================
// Session Management
// ==========================================================================
{
name: 'browser_session-list',
description: 'List active browser sessions Use when native WebFetch is wrong because you need real browser automation — JS-heavy SPA scraping, login flows with cookie reuse, replay against DOM-drifted versions, AIDefence PII gating before content reaches Claude. For static HTML pages, native WebFetch is faster and free.',
category: 'browser',
tags: ['session'],
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const sessions = Array.from(browserSessions.values());
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
sessions,
count: sessions.length,
}, null, 2),
}],
};
},
},
];
export default browserTools;
//# sourceMappingURL=browser-tools.js.map