Skip to main content
The proxy emulates Anthropic’s web_search_20250305 tool, allowing Claude Code to search the web for current information. This happens transparently when you enable web search in Claude Code.

How web search works

When Claude Code sends a request with the web_search_20250305 tool enabled:
  1. The proxy detects the web search tool in the request
  2. When the model returns a web_search tool call, the proxy executes the search
  3. Search results are formatted and fed back to the model
  4. This loop continues until the model has enough information or reaches the max search limit
  5. The final response includes all content and search results
The proxy handles web search internally using a non-streaming loop, even when Claude Code requests streaming responses. This ensures search results are properly integrated.

Search providers

The proxy uses a fallback chain of search providers to ensure reliability:
1

Brave Search API (optional)

The highest quality results, but requires an API key. Free tier provides 2,000 queries per month.
export BRAVE_API_KEY=your_api_key_here
Get your free API key at api.search.brave.com
2

DuckDuckGo Lite (default)

Free HTML search with no API key required. Used automatically if Brave API is not configured.The proxy parses DuckDuckGo Lite’s HTML to extract search results.
3

DuckDuckGo Instant Answer (fallback)

Used if DuckDuckGo Lite fails (e.g., due to CAPTCHA). Provides instant answers and related topics.

Search provider implementation

Here’s how the proxy selects and executes searches:
async function executeWebSearch(query) {
  console.log(`  🔍 Executing web search: "${query}"`)

  if (BRAVE_API_KEY) {
    const results = await braveSearch(query)
    if (results && results.length > 0) return results
    console.log(`  ⚠ Brave Search failed, trying DuckDuckGo Lite...`)
  }

  const ddgLiteResults = await duckDuckGoLiteSearch(query)
  if (ddgLiteResults && ddgLiteResults.length > 0) return ddgLiteResults

  console.log(`  ⚠ DuckDuckGo Lite failed, trying instant answer API...`)
  const instantResults = await duckDuckGoInstantAnswer(query)
  if (instantResults && instantResults.length > 0) return instantResults

  console.log(`  ⚠ All search providers failed`)
  return []
}
The proxy automatically falls through to the next provider if one fails, ensuring maximum reliability.

Configuring search providers

Brave Search provides the highest quality results with structured data:
# Get your free API key
# Visit: https://api.search.brave.com/

# Set the environment variable
export BRAVE_API_KEY=BSA_YOUR_API_KEY_HERE

# Start the proxy
./scripts/launch.sh
The Brave Search implementation:
async function braveSearch(query) {
  const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${WEB_SEARCH_MAX_RESULTS}`
  const res = await fetch(url, {
    headers: {
      "Accept": "application/json",
      "Accept-Encoding": "gzip",
      "X-Subscription-Token": BRAVE_API_KEY,
    },
  })
  const data = await res.json()
  const results = (data.web?.results || []).slice(0, WEB_SEARCH_MAX_RESULTS)
  return results.map((r) => ({
    type: "web_search_result",
    url: r.url,
    title: r.title || "",
    encrypted_content: Buffer.from(r.description || "").toString("base64"),
    page_age: r.age || null,
  }))
}
Brave Search results include page age information, helping the model understand result freshness.

Search result limits

Control the number of search results returned per query:
WEB_SEARCH_MAX_RESULTS
integer
default:"5"
Maximum number of search results to return per query.
# Get more results per search
export WEB_SEARCH_MAX_RESULTS=10
./scripts/launch.sh
# Get fewer results (faster, less context)
export WEB_SEARCH_MAX_RESULTS=3
./scripts/launch.sh
The proxy uses this value consistently across all search providers:
const WEB_SEARCH_MAX_RESULTS = parseInt(process.env.WEB_SEARCH_MAX_RESULTS || "5", 10)
More results provide better context but increase response time and token usage. The default of 5 balances quality and performance.

Web search loop

The proxy implements a multi-turn search loop to allow the model to perform multiple searches:
async function handleWebSearchLoop(openaiReq, token, maxSearches) {
  const contentBlocks = [] // Accumulated Anthropic content blocks
  let searchCount = 0
  
  for (let iteration = 0; iteration < (maxSearches || 5) + 1; iteration++) {
    const response = await collectCopilotResponse(currentReq, token)
    
    // Check if there's a web_search tool call
    const webSearchCall = choice.message?.tool_calls?.find(
      (tc) => tc.function?.name === "web_search"
    )
    
    if (!webSearchCall || searchCount >= (maxSearches || 5)) {
      // No more searches needed
      break
    }
    
    // Execute the search
    searchCount++
    const searchResults = await executeWebSearch(searchQuery)
    
    // Add results to content blocks
    contentBlocks.push({
      type: "server_tool_use",
      id: toolUseId,
      name: "web_search",
      input: { query: searchQuery },
    })
    
    contentBlocks.push({
      type: "web_search_tool_result",
      tool_use_id: toolUseId,
      content: searchResults,
    })
    
    // Feed results back to the model for next iteration
  }
  
  return { contentBlocks, lastResponse, searchCount }
}
The model can perform up to 5 searches per request by default (configurable via the web_search_20250305 tool’s max_uses parameter).

Result format

Search results are returned in Anthropic’s web_search_result format:
{
  "type": "web_search_result",
  "url": "https://example.com/page",
  "title": "Example Page Title",
  "encrypted_content": "QmFzZTY0IGVuY29kZWQgY29udGVudA==",
  "page_age": "2024-01-15"
}
type
string
required
Always "web_search_result"
url
string
required
The URL of the search result
title
string
required
The title of the page or search result
encrypted_content
string
required
Base64-encoded snippet or description from the search result
page_age
string | null
Optional timestamp or age indicator (only available with Brave Search)

Logging and debugging

The proxy logs all web search activity to the console:
 claude-opus-4-6 claude-opus-4.6 | stream | 2 messages | 🔍 web_search
  🔍 Web search enabled (max_uses: 5)
  🔍 Executing web search: "latest news about AI"
 Brave Search returned 5 results
 Response sent (1 web searches performed)
You can see:
  • When web search is enabled
  • Each search query executed
  • Which provider returned results
  • Total number of searches performed
Watch the proxy logs to understand how Claude Code is using web search and troubleshoot any issues.

Error handling

The proxy gracefully handles search failures:
If all search providers fail, the proxy returns an empty result:
{
  type: "web_search_tool_result_error",
  error_code: "unavailable"
}
The model receives this and can continue without search results.
The proxy detects CAPTCHA responses and automatically falls back:
if (html.includes("captcha") || html.includes("anomaly")) {
  console.log(`  ⚠ DDG Lite returned CAPTCHA`)
  return null
}
If you exceed your Brave API quota, the proxy automatically falls back to DuckDuckGo:
 Brave API error: 429
 Brave Search failed, trying DuckDuckGo Lite...
 DDG Lite returned 5 results
Network failures are caught and logged, with automatic fallback:
try {
  const results = await braveSearch(query)
  if (results && results.length > 0) return results
} catch (err) {
  console.log(`  ⚠ Brave Search error: ${err.message}`)
  return null
}

Performance considerations

Response time

Web searches add latency to responses. Each search takes 1-3 seconds depending on the provider.

Token usage

Search results consume input tokens. More results = higher token usage.

Multiple searches

The model can perform up to 5 searches per request, potentially adding 5-15 seconds.

Rate limits

Brave API free tier: 2,000 queries/month. DuckDuckGo has no official limits but may CAPTCHA on heavy usage.

Best practices

For reliable, high-quality search results, configure the Brave API:
export BRAVE_API_KEY=your_api_key
DuckDuckGo is great for testing but may be less reliable at scale.
For quick lookups, use fewer results:
export WEB_SEARCH_MAX_RESULTS=3
For research tasks, use more:
export WEB_SEARCH_MAX_RESULTS=10
Watch the proxy logs to see how many searches are being performed:
# Docker
docker compose logs -f

# Direct Node.js
# Logs appear in the terminal where proxy.mjs is running

Next steps

Model selection

Choose the best model for your task

Launching Claude

Different ways to start Claude Code