Skip to content

fix(anthropic): accept server-side tools on /v1/messages - #2702

Merged
steebchen merged 2 commits into
mainfrom
fix/anthropic-server-side-tools
Jun 16, 2026
Merged

steebchen merged 2 commits into
mainfrom
fix/anthropic-server-side-tools

Conversation

@steebchen

@steebchen steebchen commented Jun 16, 2026 •

Copy link
Copy Markdown
Member

Problem

The native Anthropic /v1/messages endpoint rejected valid requests that included Anthropic server-side tools (e.g. web_search_20250305). Sending Anthropic's documented web search request:

{
  "model": "claude-sonnet-4-6",
  "max_tokens": 4096,
  "messages": [{ "role": "user", "content": "Search the web ..." }],
  "tools": [{ "type": "web_search_20250305", "name": "web_search", "max_uses": 3 }]
}

returned a ZodError:

{"success":false,"error":{"issues":[
  {"code":"invalid_type","path":["tools",0,"description"],"message":"Required"},
  {"code":"invalid_type","path":["tools",0,"input_schema"],"message":"Required"}
],"name":"ZodError"}}

The same request works when sent directly to api.anthropic.com.

Root cause

The endpoint's tool schema (anthropicToolSchema) required description and input_schema on every tool. Server-side tools carry a versioned type and have neither field, so validation failed before the request ever reached a provider.

Fix

  • Split the tool schema into a union: standard custom tools (name + input_schema) and Anthropic server-side tools (type + name + optional config like max_uses, user_location, allowed_domains).
  • In the Anthropic → OpenAI translation, map web_search* server tools to the internal web_search tool that the chat completions endpoint already forwards to Anthropic as web_search_20250305 (preserving max_uses/user_location).
  • Unsupported server tools are dropped with a warning rather than rejecting the whole request.

Tests

Added two unit tests in apps/gateway/src/api.spec.ts:

  • A web_search_20250305 server tool passes validation and is forwarded to the Anthropic provider as a native web_search_20250305 tool.
  • A malformed custom tool (missing input_schema) is still rejected with 400.

pnpm build, pnpm format, and the new tests all pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for Anthropic native server-side tools (such as web search) alongside custom tools
    • Enhanced tool configuration with additional parameters including allowed domains, blocked domains, and user location settings
  • Tests

    • Added comprehensive test coverage for server-side tool handling and custom tool validation

The native Anthropic /v1/messages endpoint validated every tool against a
custom-tool schema requiring `description` and `input_schema`, so server-side
tools like web_search_20250305 (which carry a versioned `type` and neither
field) were rejected with a ZodError before reaching a provider.

Accept Anthropic server-side tools via a union schema and translate
web_search tools into the internal `web_search` tool the chat completions
endpoint already forwards to Anthropic as web_search_20250305. Unsupported
server tools are dropped with a warning rather than rejecting the request.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@steebchen, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 11 minutes and 11 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 06e9c55f-2e5c-453f-a4cf-b68996a4f319

📥 Commits

Reviewing files that changed from the base of the PR and between 3dd02c4 and 4d6ff90.

📒 Files selected for processing (6)
  • apps/gateway/src/anthropic/anthropic.ts
  • apps/gateway/src/api.spec.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/chat/schemas/completions.ts
  • packages/actions/src/prepare-request-body.ts
  • packages/models/src/types.ts

Walkthrough

The PR extends the Anthropic gateway tool pipeline to support server-side tools (e.g., web_search_20250305) alongside existing custom tools. A union Zod schema replaces the prior single custom-tool schema, the tool translation step conditionally maps each tool type, and openaiRequest.tools is now only set for non-empty translated lists. Tests cover both the forwarding and rejection paths.

Changes

Anthropic Server-Side Tool Support

Layer / File(s) Summary
Tool schema union and translation logic
apps/gateway/src/anthropic/anthropic.ts
Replaces the single custom-tool Zod schema with a discriminated union covering custom tools (input_schema) and server-side tools (type + optional allowed_domains, blocked_domains, user_location, cache_control). Reworks the translation step to emit OpenAI function tools for custom tools, internal web_search format for web_search* tools (with optional max_uses/user_location), and drops other server tools with a warning. Guards openaiRequest.tools assignment so it is only set when the translated list is non-empty.
Tool validation tests
apps/gateway/src/api.spec.ts
Adds a /v1/messages test suite: one test mocks the upstream fetch and asserts a web_search_20250305 server-side tool is forwarded with its type present in the upstream request body; a second test asserts a custom tool missing input_schema is rejected with HTTP 400.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: enabling acceptance of server-side tools on the /v1/messages endpoint, which is the core problem being solved.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/anthropic-server-side-tools

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3dd02c4162

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +585 to +592
if (tool.type.startsWith("web_search")) {
return {
type: "web_search",
...(tool.max_uses !== undefined ? { max_uses: tool.max_uses } : {}),
...(tool.user_location
? { user_location: tool.user_location }
: {}),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve server web_search settings

When a native /v1/messages request includes Anthropic's web search tool with allowed_domains/blocked_domains or the newer web_search_20260209 type, this branch collapses it to the generic chat web_search object. Downstream prepareRequestBody for Anthropic always re-emits web_search_20250305 and only copies max_uses, so accepted requests can silently lose domain constraints and downgrade dynamic filtering before reaching Anthropic.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
apps/gateway/src/api.spec.ts (2)

5163-5168: 💤 Low value

Consider verifying field preservation in the forwarded tool.

The test checks that the forwarded tool has type: "web_search_20250305", but doesn't verify that max_uses (sent at line 5151) is preserved. For more robust coverage, consider adding an assertion like:

const webSearchTool = forwardedTools.find(
  (t: { type?: string }) => t.type === "web_search_20250305",
);
expect(webSearchTool).toBeDefined();
expect(webSearchTool.max_uses).toBe(3);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/gateway/src/api.spec.ts` around lines 5163 - 5168, The current test
assertion only verifies that a tool with type "web_search_20250305" exists in
the forwardedTools array but does not verify that the max_uses field is
preserved during forwarding. Enhance the test by finding the specific tool with
type "web_search_20250305" using an additional find call, then add assertions to
verify both that the tool is defined and that its max_uses property equals 3
(matching the value set at line 5151).

5196-5198: 💤 Low value

Consider verifying the error message content.

The test confirms that a malformed custom tool is rejected with a 400 status, but doesn't verify the error message mentions the missing input_schema. For more robust coverage, consider adding:

expect(res.status).toBe(400);
const json = await res.json();
expect(JSON.stringify(json)).toContain("input_schema");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/gateway/src/api.spec.ts` around lines 5196 - 5198, The test for
malformed custom tool rejection only verifies the HTTP status code is 400 but
doesn't validate that the error message specifically mentions the missing
"input_schema" field. Enhance the test by adding assertions that parse the
response JSON and confirm the error message contains the string "input_schema"
to provide more robust coverage of the validation logic. This ensures the API
not only rejects invalid input with the correct status code but also returns
meaningful error details to the client.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/gateway/src/api.spec.ts`:
- Around line 5163-5168: The current test assertion only verifies that a tool
with type "web_search_20250305" exists in the forwardedTools array but does not
verify that the max_uses field is preserved during forwarding. Enhance the test
by finding the specific tool with type "web_search_20250305" using an additional
find call, then add assertions to verify both that the tool is defined and that
its max_uses property equals 3 (matching the value set at line 5151).
- Around line 5196-5198: The test for malformed custom tool rejection only
verifies the HTTP status code is 400 but doesn't validate that the error message
specifically mentions the missing "input_schema" field. Enhance the test by
adding assertions that parse the response JSON and confirm the error message
contains the string "input_schema" to provide more robust coverage of the
validation logic. This ensures the API not only rejects invalid input with the
correct status code but also returns meaningful error details to the client.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 930a17e7-a984-4546-9c11-60655dd13d6b

📥 Commits

Reviewing files that changed from the base of the PR and between dd5610e and 3dd02c4.

📒 Files selected for processing (2)
  • apps/gateway/src/anthropic/anthropic.ts
  • apps/gateway/src/api.spec.ts

@steebchen
steebchen added this pull request to the merge queue Jun 16, 2026
@steebchen
steebchen removed this pull request from the merge queue due to a manual request Jun 16, 2026
Addresses Codex PR feedback: server web_search tools sent to /v1/messages
with allowed_domains/blocked_domains or user_location were accepted but
silently dropped, because the internal WebSearchTool and the Anthropic
prepareRequestBody only carried max_uses.

Thread allowed_domains, blocked_domains, and user_location through the
WebSearchTool type, the chat completions schema, tool extraction, and the
Anthropic provider request (re-emitting user_location with the required
`type: "approximate"`). Harden the /v1/messages tests to assert max_uses,
domain filters, and user_location survive the round-trip, and that a custom
tool missing input_schema is rejected with an input_schema error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@steebchen
steebchen enabled auto-merge June 16, 2026 17:15
@steebchen

Copy link
Copy Markdown
Member Author

Addressed the Codex P2 in 4d6ff90: allowed_domains/blocked_domains and user_location are now threaded through WebSearchTool, the chat completions schema, tool extraction, and the Anthropic prepareRequestBody (re-emitting user_location with the required type: "approximate"), so they're no longer dropped before reaching Anthropic. Tests now assert these survive the round-trip. Also folded in CodeRabbit's nitpicks (assert max_uses preservation and that the rejection error mentions input_schema).

The web_search_20260209 version note I left as-is: the gateway pins the known-good web_search_20250305 rather than forwarding an arbitrary client-supplied version string, which is the safer default until a newer version is explicitly supported.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d6ff90c8f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +585 to +588
if (tool.type.startsWith("web_search")) {
return {
type: "web_search",
...(tool.max_uses !== undefined ? { max_uses: tool.max_uses } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Anthropic web-search response blocks

When a native /v1/messages request reaches this branch and Claude actually performs a web search, Anthropic returns server_tool_use / web_search_tool_result content blocks and text citations in the Messages response (docs). The inner chat path converts those into OpenAI-style text plus message.annotations, but the outer /v1/messages response builder only reconstructs content from message.content and tool_calls, so citations and encrypted search-result content are dropped; Anthropic clients lose the source data needed for display and multi-turn search conversations.

Useful? React with 👍 / 👎.

// These are executed by Anthropic, carry a versioned `type` instead of an
// `input_schema`, and must not be validated as custom tools.
const anthropicServerToolSchema = z.object({
type: z.string(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject explicit custom tools missing schemas

Because the server-tool schema accepts any string type, a request like tools: [{ type: "custom", name: "get_weather", description: "..." }] no longer fails validation for the missing input_schema: it misses the custom-tool union arm, matches this server-tool arm, and is then silently dropped as an unsupported server tool. This regresses malformed custom-tool validation for clients that include the explicit type: "custom" field that this same schema allows for valid custom tools.

Useful? React with 👍 / 👎.

@steebchen
steebchen added this pull request to the merge queue Jun 16, 2026
Merged via the queue into main with commit 62d9b81 Jun 16, 2026
15 of 16 checks passed
@steebchen
steebchen deleted the fix/anthropic-server-side-tools branch June 16, 2026 17:34
steebchen added a commit that referenced this pull request Jun 16, 2026
## Problem

The native Anthropic `/v1/messages` endpoint rejected valid requests
that included Anthropic **server-side tools** (e.g.
`web_search_20250305`). Sending Anthropic's documented web search
request:

```json
{
  "model": "claude-sonnet-4-6",
  "max_tokens": 4096,
  "messages": [{ "role": "user", "content": "Search the web ..." }],
  "tools": [{ "type": "web_search_20250305", "name": "web_search", "max_uses": 3 }]
}
```

returned a `ZodError`:

```json
{"success":false,"error":{"issues":[
  {"code":"invalid_type","path":["tools",0,"description"],"message":"Required"},
  {"code":"invalid_type","path":["tools",0,"input_schema"],"message":"Required"}
],"name":"ZodError"}}
```

The same request works when sent directly to `api.anthropic.com`.

## Root cause

The endpoint's tool schema (`anthropicToolSchema`) required
`description` **and** `input_schema` on **every** tool. Server-side
tools carry a versioned `type` and have neither field, so validation
failed before the request ever reached a provider.

## Fix

- Split the tool schema into a union: standard **custom tools** (`name`
+ `input_schema`) and Anthropic **server-side tools** (`type` + `name` +
optional config like `max_uses`, `user_location`, `allowed_domains`).
- In the Anthropic → OpenAI translation, map `web_search*` server tools
to the internal `web_search` tool that the chat completions endpoint
already forwards to Anthropic as `web_search_20250305` (preserving
`max_uses`/`user_location`).
- Unsupported server tools are dropped with a warning rather than
rejecting the whole request.

## Tests

Added two unit tests in `apps/gateway/src/api.spec.ts`:
- A `web_search_20250305` server tool passes validation and is forwarded
to the Anthropic provider as a native `web_search_20250305` tool.
- A malformed custom tool (missing `input_schema`) is still rejected
with `400`.

`pnpm build`, `pnpm format`, and the new tests all pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added support for Anthropic native server-side tools (such as web
search) alongside custom tools
* Enhanced tool configuration with additional parameters including
allowed domains, blocked domains, and user location settings

* **Tests**
* Added comprehensive test coverage for server-side tool handling and
custom tool validation

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant