Which to reach for

ToolUse when
web_searchYou need a synthesized answer plus source URLs and don’t know which page holds the fact.
read on a URLYou already have the URL and want clean text without JavaScript.
browserThe page needs JS, auth, form fills, or interactive clicks. Tabs persist across calls.

One query, dispatched through the first available provider in a configured chain: Anthropic, Brave, Codex, Exa, Gemini, Jina, Kagi, Kimi, Parallel, Perplexity, SearXNG, Synthetic, Tavily, Z.AI. The result is a unified SearchResponse; provider order is set once in config, not per call. Optional recency (day, week, month, year) is honored by the providers that support it. Gated by web_search.enabled.

web_search query="bun workspaces hoisting behaviour" recency="month"

read against a URL

Pass any http:// or https:// URL to read. Reader-mode is the default: articles, GitHub issues and PRs, Stack Overflow, Wikipedia, NPM pages, arXiv, RSS/Atom feeds, JSON endpoints, and PDFs come back as clean text or markdown. Append :raw to get untouched HTML; append a line selector (:50-100, :50+150) to paginate through the cached output. Bare host:port URLs collide with the selector grammar; add a trailing slash before the selector.

# reader-mode markdown
read https://example.com/docs/api
 
# raw HTML, then a line range over the cached fetch
read https://example.com/page:raw
read https://example.com/page:200-400

Reach for read over browser whenever the content is static. It’s faster, cheaper, and the output is already prose-shaped for the model.

browser

A real Chromium tab driven through Puppeteer. Three actions:

open Acquires (or reuses) a named tab. name defaults to "main". Optional url navigates after the tab is ready; viewport sets dimensions; dialogs auto-accepts or auto-dismisses alert/confirm/ beforeunload. run Executes async JS against an existing tab. code is the body of an async function with page, browser, tab, display, assert, and wait in scope. The return value is JSON-stringified into the tool result.

close Releases a tab by name, or every tab with all: true. For spawned-app browsers, kill: true terminates the process tree.

Tabs survive across run calls and across in-process subagents. Open once, reuse many times. The tab helper exposes observe() for an accessibility snapshot with stable element ids, plus click, fill, type, press, select, uploadFile, waitForUrl, waitForResponse, screenshot, and extract.

Default to tab.observe() over tab.screenshot() for understanding page state — the snapshot returns structured data with element ids you can act on. Screenshot only when visual appearance matters.

browser open name=docs url=https://example.com/login
browser run  name=docs code=`
  const obs = await tab.observe();
  const link;
`
``` = obs.elements.find(e => e.role === "link" && e.name === "Sign in");
  await (await tab.id(link.id)).click();
  await tab.fill('input[name=email]', 'me@example.com');
  await tab.click('text/Continue');
`
browser close name=docs

Instead of launching headless, attach to a running Chromium app via CDP or by spawning an Electron binary:

browser open name=cursor app={path: "/Applications/Cursor.app/Contents/MacOS/Cursor"}
browser open name=devtools app={cdp_url: "http://127.0.0.1:9222"}

For PRs and issues specifically, prefer the URL schemes documented on GitHub — they cache automatically and read like local files.\n

Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send