ToolSearch generates tool_reference blocks on Bedrock despite API incompatibility
Bug Description
When using Claude Code on AWS Bedrock (CLAUDE_CODE_USE_BEDROCK=1) with multiple MCP servers, ToolSearch activates and generates tool_reference content blocks that Bedrock's API rejects with 400 Tool reference not found in available tools.
The root cause is a mismatch between two code paths: the client-side ToolSearch enablement (which does NOT check provider) and the beta header routing (which deliberately excludes tool-search-tool-2025-10-19 from the SDK betas parameter for Bedrock).
Environment
- Claude Code version: 2.1.38 / 2.1.39
- Platform: macOS (darwin), ARM64
- Provider: AWS Bedrock (
CLAUDE_CODE_USE_BEDROCK=1,us-east-1) - Model:
us.anthropic.claude-opus-4-6-v1 - MCP servers: 6 servers (Sentry, Airtable, Notion, Linear, Context7, Playwright) → 105 tools, ~54,462 tokens of descriptions
Reproduction Steps
- Configure 6+ MCP servers in
~/.claude.json(enough to exceed 10% of context window in tool description tokens) - Set
CLAUDE_CODE_USE_BEDROCK=1with valid AWS credentials - Start a Claude Code session
- Ask Claude to use ToolSearch (or let it trigger automatically via deferred tool loading)
- ToolSearch returns
tool_referencecontent blocks - Next API request to Bedrock includes these blocks → 400 error
Error Message
API Error: 400 Tool reference 'mcp__linear__list_milestones' not found in available tools
Root Cause Analysis (Binary-Level)
The dual beta path
Claude Code has two separate beta-handling paths for Bedrock:
VI()(SDKbetasparameter): Filters OUT items in theeMRset for Bedrock.tool-search-tool-2025-10-19is ineMR, so it's excluded from the SDKbetasarray.
bnR()→T2R()(request bodyanthropic_beta): Returns ONLY items INeMRfor Bedrock.tool-search-tool-2025-10-19IS included and sent via the request body'santhropic_betafield.
// eMR — betas routed through body instead of SDK param for Bedrock
eMR = new Set([
"interleaved-thinking-2025-05-14",
"context-1m-2025-08-07",
"tool-search-tool-2025-10-19", // ← tool search beta
"tool-examples-2025-10-29"
])
// VI() — SDK betas, filters out eMR for Bedrock
VI = memoize((model) => {
let betas = PnR(model);
if (NB() === "bedrock") return betas.filter(b => !eMR.has(b));
return betas;
})
// bnR() — Bedrock body betas, returns only eMR items
bnR = memoize((model) => PnR(model).filter(b => eMR.has(b)))
The ToolSearch enablement gate does NOT check provider
ccT() (isToolSearchEnabled) checks:
HT0(model)— model blocklist (only blocks"haiku")qT0(tools)— ToolSearchTool in tool listA$T()—ENABLE_TOOL_SEARCHenv var modeCP8()— token/character threshold (10% of context)
None of these check the provider. So on Bedrock with Opus, when MCP tool tokens exceed the 10% threshold, ccT() returns true → tools get defer_loading: true → ToolSearch activates → tool_reference blocks are generated.
The explicit Bedrock guard in esB
let G = q ? bH9() : null; // bH9() returns "tool-search-tool-2025-10-19" for Bedrock
if (G && NB() !== "bedrock") { // ← explicit Bedrock block
if (!H.includes(G)) H.push(G);
}
This prevents the tool-search beta from entering the SDK betas array (H) for Bedrock. But G IS added to JT for the body path:
JT = NB() === "bedrock" ? [...bnR(model), ...G ? [G] : []] : [];
hT = T2R(JT); // → anthropic_beta in request body
The result
- Client-side: ToolSearch is enabled, tools are deferred,
tool_referenceblocks are generated - SDK
betas: Does NOT includetool-search-tool-2025-10-19 - Request body
anthropic_beta: DOES includetool-search-tool-2025-10-19 - API response: 400 — the SDK or API rejects
tool_referenceblocks because the SDKbetasparameter doesn't signal support
Debug log evidence
[DEBUG] [ToolSearch:optimistic] mode=tst-auto, ENABLE_TOOL_SEARCH=undefined, result=true
[DEBUG] Auto tool search enabled: 54462 tokens (threshold: 20000, 10% of context) [source: query]
[DEBUG] Dynamic tool loading: 0/105 deferred tools included
ToolSearch activates, all 105 MCP tools are deferred, but 0 are ever successfully loaded — every attempt produces the 400 error.
Current Workaround
Setting ENABLE_TOOL_SEARCH=auto:30 raises the deferral threshold to 30% of context (60,000 tokens). Since our 54,462 tokens of MCP tool descriptions are below this, ToolSearch auto-disables and all tools load inline. No tool_reference blocks are generated, no 400 errors occur.
The tradeoff is ~54K tokens of tool descriptions consumed per request (~27% of the 200K context window).
Suggested Fix
The code should either:
- Check provider in
ccT(): If on Bedrock and the SDKbetasdon't include the tool-search beta, disable deferral to preventtool_referencegeneration.
- Add graceful degradation in
mapToolResultToToolResultBlockParam: When provider is Bedrock, converttool_referenceblocks to text equivalents (similar to whatWuAalready does) instead of passing them raw:
// Pseudocode for graceful degradation
mapToolResultToToolResultBlockParam(T, R) {
if (T.matches.length === 0)
return { type: "tool_result", tool_use_id: R, content: "No matching deferred tools found" };
if (NB() === "bedrock") {
return { type: "tool_result", tool_use_id: R,
content: T.matches.map(A => ({ type: "text", text: `[Loaded tool: ${A}]` })) };
}
return { type: "tool_result", tool_use_id: R,
content: T.matches.map(A => ({ type: "tool_reference", tool_name: A })) };
}
- Remove
tool-search-tool-2025-10-19fromeMR: If Bedrock InvokeModel now supports this beta (AWS docs suggest it does), removing it from the exclusion set would let the standard code path work.
Related Issues
- #24591 — Context compaction fails with multiple subagents (same version, similar error pattern)
- #23490 — MCP tools not available due to tool search experiment (
tengu_tst_kx7) - #22893 —
DISABLE_EXPERIMENTAL_BETASdoesn't suppressadvanced-tool-useheader - #12164 — MCP servers connect but tools not exposed to assistant
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗