Overview
Agents can search the web and fetch pages in real time using Anthropic’s server tools — web_search and web_fetch. These are not custom ToolDefinition tools like connector tools; they run server-side within the Anthropic API and return results (including citations) as part of the streamed response.
Web tools are workspace-scoped: an admin enables them per workspace and configures an allow-list of domains the agent can access.
Workspace configuration
Three fields on the workspaces table control web access:
| Field | Type | Default | Description |
|---|
web_search_enabled | boolean | true | Enables the web_search server tool |
web_fetch_enabled | boolean | true | Enables the web_fetch server tool |
web_allowed_domains | string[] | ['help.sap.com'] | Domain allow-list (e.g. ['help.sap.com', '*.wikipedia.org']) |
Both a toggle and at least one domain are required. If web_search_enabled is true but web_allowed_domains is empty or null, the tool is not added to the request. This prevents accidental unrestricted web access.
Domain validation
Domains are validated by validateWebDomains() in src/routes/workspaces.ts on create and update. Rules:
- No scheme prefix — use
sap.com, not https://sap.com
- ASCII characters only
- Wildcard subdomains supported —
*.example.com matches any subdomain
- Must match domain format: alphanumeric segments separated by dots, optional
*. prefix
Domain restrictions are enforced server-side by the Anthropic API, not by our code. The allowed_domains array is passed directly in the server tool configuration.
Settings UI
Workspace admins configure web access in Settings → Web Access. Each workspace has:
- Web Search checkbox — toggles
web_search_enabled
- Web Fetch checkbox — toggles
web_fetch_enabled
- Allowed Domains textarea — one domain per line (e.g.
en.wikipedia.org, help.sap.com)
Web tools are built separately as serverTools and then merged into the tools array sent to Anthropic. In src/agents/agent.ts:
const serverTools = [];
if (this.webSearchEnabled && hasWebDomains) {
serverTools.push({
type: 'web_search_20250305',
name: 'web_search',
max_uses: 5,
allowed_domains: this.webAllowedDomains,
});
}
if (this.webFetchEnabled && hasWebDomains) {
serverTools.push({
type: 'web_fetch_20250910',
name: 'web_fetch',
max_uses: 10,
allowed_domains: this.webAllowedDomains,
});
}
| Tool | API type | Max uses per turn | Purpose |
|---|
web_search | web_search_20250305 | 5 | Search the web, returns ranked results |
web_fetch | web_fetch_20250910 | 10 | Fetch and read a specific URL |
web_fetch requires the beta header anthropic-beta: web-fetch-2025-09-10 in the request options. This is added automatically when serverTools includes a web_fetch entry.
Config refresh
Web configuration is not persisted to Durable Object storage. Instead, the agent holds webSearchEnabled, webFetchEnabled, and webAllowedDomains as in-memory fields that are refreshed from the incoming request payload on every:
POST /agents/:id/message — message request
POST /agents/:id/approve — approval request
This means workspace admins can toggle web access or update domains without restarting the agent — changes take effect on the next message.
Web tool results arrive as special content block types during streaming:
Web search results
When Claude uses web_search, the stream emits a web_search_tool_result content block containing an array of web_search_result entries (each with url, title, page_age). The agent extracts these and broadcasts a tool_update WebSocket message so the frontend can display them in the ToolCard.
Web fetch results
When Claude uses web_fetch, the stream emits a web_fetch_tool_result content block with url and content title. These are similarly broadcast as tool_update messages.
No approval required
Web tools are read-only and execute automatically — they do not go through the approval flow.
Citations
When Claude’s response references information from web search or fetch results, the API includes citations on text content blocks. Each citation has:
interface Citation {
type: string; // e.g. 'web_search_result'
url?: string;
title?: string;
cited_text?: string;
}
In processResponse(), citations are extracted from each text block and collected across all text blocks in a response. They are stored on the Message object as citations?: Citation[].
Frontend rendering
MessageBubble.tsx renders a CitationList component below the message text when citations are present:
- Citations are deduplicated by URL — if the same source is cited multiple times, it appears once
- Displayed as a “Sources” section with numbered links (
[1], [2], …)
- Links open in a new tab (
target="_blank", rel="noopener noreferrer")
- Falls back to displaying the raw URL when no title is available
System prompt
When web tools are active, a conditional section is appended to the agent’s system prompt:
# Web Search and Web Fetch
You have access to real-time web search and page fetching, restricted to
approved domains for this workspace. Use web search when:
- The user asks about current events, recent updates, or time-sensitive information
- You need to verify SAP documentation, OSS notes, or community solutions
- Looking up error codes, patches, or known issues
Use web fetch when the user provides a URL or when search results contain
a page you need to read in full.
Always cite your sources — citations are included automatically from search results.
This section only appears when at least one web tool is enabled and domains are configured.
| Aspect | SAP tools | Web tools |
|---|
| Definition | ToolDefinition interface in tool-definitions.ts | Anthropic server tool protocol |
| Execution | Client-side via execute() function | Server-side within Anthropic API |
| Registration | sapTools record + buildSapTools() | serverTools array on API request |
| Approval | Write tools require approval | Always auto-execute (read-only) |
| Domain control | N/A | allowed_domains enforced by API |
| Results | Returned as tool execution output | Streamed as special content blocks |
| Citations | N/A | Automatically included on text blocks |