Files
claude-code/packages/builtin-tools/src/tools/WebBrowserTool/WebBrowserTool.ts
2026-04-22 22:38:10 +08:00

170 lines
4.6 KiB
TypeScript

import { z } from 'zod/v4'
import type { ToolResultBlockParam } from 'src/Tool.js'
import { buildTool } from 'src/Tool.js'
import { lazySchema } from 'src/utils/lazySchema.js'
const WEB_BROWSER_TOOL_NAME = 'WebBrowser'
const inputSchema = lazySchema(() =>
z.strictObject({
url: z
.string()
.describe('URL to fetch and extract content from.'),
action: z
.enum(['navigate', 'screenshot'])
.optional()
.describe('Action to perform. "navigate" fetches page content (default). "screenshot" returns a text snapshot of the page.'),
}),
)
type InputSchema = ReturnType<typeof inputSchema>
type BrowserInput = z.infer<InputSchema>
type BrowserOutput = {
title: string
url: string
content?: string
screenshot?: string
}
export const WebBrowserTool = buildTool({
name: WEB_BROWSER_TOOL_NAME,
searchHint: 'web browser navigate url page screenshot click',
maxResultSizeChars: 100_000,
strict: true,
get inputSchema(): InputSchema {
return inputSchema()
},
async description() {
return 'Fetch and read web page content via HTTP'
},
async prompt() {
return `Fetch web pages via HTTP and extract their text content. This is a lightweight browser tool (HTTP fetch, not a full browser engine).
Supported actions:
- navigate: Fetch a URL and extract page title + text content
- screenshot: Same as navigate (returns text snapshot, not a visual screenshot)
Limitations:
- No JavaScript execution — only sees server-rendered HTML
- click/type/scroll require a full browser runtime (not available)
- For full browser interaction, use the Claude-in-Chrome MCP tools instead
Use this for:
- Reading web page content and documentation
- Checking API endpoints that return HTML
- Quick page title/content extraction`
},
isConcurrencySafe() {
return false
},
isReadOnly() {
return true
},
userFacingName() {
return 'Browser'
},
renderToolUseMessage(input: Partial<BrowserInput>) {
const action = input.action ?? 'navigate'
return `Browser ${action}: ${input.url ?? '...'}`
},
mapToolResultToToolResultBlockParam(
content: BrowserOutput,
toolUseID: string,
): ToolResultBlockParam {
return {
tool_use_id: toolUseID,
type: 'tool_result',
content: `${content.title} (${content.url})\n${content.content ?? ''}`,
}
},
async call(input: BrowserInput) {
const action = input.action ?? 'navigate'
if (action === 'navigate' || action === 'screenshot') {
// Fetch the page content via HTTP
try {
const response = await fetch(input.url, {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
redirect: 'follow',
})
if (!response.ok) {
return {
data: {
title: `HTTP ${response.status}`,
url: input.url,
content: `Error: ${response.status} ${response.statusText}`,
},
}
}
const html = await response.text()
// Extract title
const titleMatch = html.match(/<title[^>]*>([^<]*)<\/title>/i)
const title = titleMatch?.[1]?.trim() ?? ''
// Extract text content (strip HTML tags, scripts, styles)
let textContent = html
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim()
// Truncate to reasonable size
if (textContent.length > 50_000) {
textContent = textContent.slice(0, 50_000) + '\n[truncated]'
}
if (action === 'screenshot') {
return {
data: {
title,
url: response.url,
content: `[Text snapshot — visual screenshots require Chrome browser tools]\n\n${textContent}`,
},
}
}
return {
data: {
title,
url: response.url,
content: textContent,
},
}
} catch (err) {
return {
data: {
title: 'Error',
url: input.url,
content: `Failed to fetch: ${err instanceof Error ? err.message : String(err)}`,
},
}
}
}
// Unreachable — schema only allows navigate/screenshot
return {
data: {
title: '',
url: input.url,
content: `Unknown action "${action}".`,
},
}
},
})