Skip to content

Commit 29de297

Browse files
author
SqlRush
committed
Defer MCP tools through ToolSearch
1 parent bf66fdb commit 29de297

8 files changed

Lines changed: 197 additions & 5 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ M8/M6 补充:compact plan 现在会把 compact 前已发现的 `tool_reference
199199

200200
M8/M2 补充:当 `ToolSearch` 可用且存在 deferred 工具时,conversation request 现在会按官方 dynamic tool loading 过滤请求工具:未发现 deferred 工具不发送 schema,已发现 deferred 工具作为 loaded tool 发送,`ToolSearch` 保持可调用,并在 API messages 前置 `<available-deferred-tools>` 名称列表;没有 deferred 工具时会从请求中移除 `ToolSearch`
201201

202-
M8/M2 补充:conversation request 现在会在发送 beta `defer_loading` / `tool_reference` shape 前执行官方 ToolSearch enablement gate:Haiku 模型、`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`、falsy `ENABLE_TOOL_SEARCH`、`ENABLE_TOOL_SEARCH=auto:100`、未显式启用 ToolSearch 时的非一方 `ANTHROPIC_BASE_URL`、以及 `ENABLE_TOOL_SEARCH=auto` 下 deferred 工具描述体积低于阈值都会回落为标准 inline tool schema;显式 `ENABLE_TOOL_SEARCH=true` 仍可让支持 beta shape 的自定义网关 opt in。auto 模式已接入 Anthropic `/v1/messages/count_tokens` 精确计数路径,成功时减去官方固定 tool overhead 后按 token 阈值判断,失败时会用 Haiku message-create usage 作为官方第二层 fallback,再失败才按官方字符 fallback 计算阈值,覆盖 `auto:N` 百分比、`[1m]` context 和 ant-only context-window override;deferred tool token count 现在按官方 deferred tool name 列表做 in-process memoization,成功值和不可用结果都会缓存。token-count VCR 以及更完整的 MCP 生命周期失效策略仍未宣称完成。
202+
M8/M2 补充:conversation request 现在会在发送 beta `defer_loading` / `tool_reference` shape 前执行官方 ToolSearch enablement gate:Haiku 模型、`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`、falsy `ENABLE_TOOL_SEARCH`、`ENABLE_TOOL_SEARCH=auto:100`、未显式启用 ToolSearch 时的非一方 `ANTHROPIC_BASE_URL`、以及 `ENABLE_TOOL_SEARCH=auto` 下 deferred 工具描述体积低于阈值都会回落为标准 inline tool schema;显式 `ENABLE_TOOL_SEARCH=true` 仍可让支持 beta shape 的自定义网关 opt in。auto 模式已接入 Anthropic `/v1/messages/count_tokens` 精确计数路径,成功时减去官方固定 tool overhead 后按 token 阈值判断,失败时会用 Haiku message-create usage 作为官方第二层 fallback,再失败才按官方字符 fallback 计算阈值,覆盖 `auto:N` 百分比、`[1m]` context 和 ant-only context-window override;deferred tool token count 现在按官方 deferred tool name 列表做 in-process memoization,成功值和不可用结果都会缓存。token-count VCR 仍未宣称完成。
203+
204+
M8/M2 补充:MCP tools 现在按官方 `isMcp` 语义进入 deferred tool pool,除非显式 `always_load` 覆盖;Anthropic tool schema 序列化会为 MCP tools 输出 `defer_loading`,ToolSearch auto 的 token-count 请求也会计入 MCP tools 但以 loaded schema 计数;configured MCP toolset attach/close 会清空 ToolSearch token-count cache,避免 MCP server lifecycle 变化后复用旧计数。token-count VCR 和更深的 delta-attachment pool-change surface 仍未完成。
203205

204206
M8/M2 补充:当本次 request 未启用 `ToolSearch` 时,conversation request 现在会从 API user `tool_result.content` 中剥离历史 `tool_reference` blocks;纯 reference 结果会替换为官方占位文本 `[Tool references removed - tool search not enabled]`,剥离发生在 discovered-tool 扫描之后,避免影响后续 loaded 工具恢复。
205207

docs/claude-code-go-rewrite-plan.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,8 @@ test/parity/ # golden tests against TS/official behavior
294294
- 本轮补充:conversation `BuildRequest` 会扫描历史 `tool_result.content` 中的 `tool_reference`,并把已发现工具在后续 API request 中作为 loaded tool 发送,不再携带 `defer_loading`;扫描兼容运行时 `ToolReference` 值和 transcript/JSON 解码后的 map 形态。完整官方 tool-reference expansion/filtering 仍未宣称完成。
295295
- 本轮补充:compact plan 会把 compact 前已发现的 `tool_reference` 名称快照进 `compactMetadata.preCompactDiscoveredTools`,session transcript alias/resume 转换会保留该 metadata,conversation `BuildRequest` 可在 tool-result 消息被 summary 替换后继续从 compact boundary 恢复已发现工具并取消 `defer_loading`。完整官方 compact/snipping 边界策略仍未宣称完成。
296296
- 本轮补充:当 request registry 同时存在 `ToolSearch` 和 deferred 工具时,conversation request 会按官方 dynamic tool loading 过滤工具列表:未发现 deferred 工具不再发送 schema,已发现 deferred 工具作为 loaded tool 发送,`ToolSearch` 保持可调用,并在首条 API user message 前注入 `<available-deferred-tools>` 名称列表;没有 deferred 工具时会从请求中移除 `ToolSearch`
297-
- 本轮补充:conversation request 现在会在发送 beta `defer_loading` / `tool_reference` shape 前执行官方 ToolSearch enablement gate:Haiku 模型、`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`、falsy `ENABLE_TOOL_SEARCH`、`ENABLE_TOOL_SEARCH=auto:100`、未显式启用 ToolSearch 时的非一方 `ANTHROPIC_BASE_URL`、以及 `ENABLE_TOOL_SEARCH=auto` 下 deferred 工具描述体积低于阈值都会回落为标准 inline tool schema;显式 `ENABLE_TOOL_SEARCH=true` 仍可让支持 beta shape 的自定义网关 opt in。auto 模式已接入 Anthropic `/v1/messages/count_tokens` 精确计数路径,成功时减去官方固定 tool overhead 后按 token 阈值判断,失败时会用 Haiku message-create usage 作为官方第二层 fallback,再失败才按官方字符 fallback 计算阈值,覆盖 `auto:N` 百分比、`[1m]` context 和 ant-only context-window override;deferred tool token count 现在按官方 deferred tool name 列表做 in-process memoization,成功值和不可用结果都会缓存。token-count VCR 以及更完整的 MCP 生命周期失效策略仍未宣称完成。
297+
- 本轮补充:conversation request 现在会在发送 beta `defer_loading` / `tool_reference` shape 前执行官方 ToolSearch enablement gate:Haiku 模型、`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`、falsy `ENABLE_TOOL_SEARCH`、`ENABLE_TOOL_SEARCH=auto:100`、未显式启用 ToolSearch 时的非一方 `ANTHROPIC_BASE_URL`、以及 `ENABLE_TOOL_SEARCH=auto` 下 deferred 工具描述体积低于阈值都会回落为标准 inline tool schema;显式 `ENABLE_TOOL_SEARCH=true` 仍可让支持 beta shape 的自定义网关 opt in。auto 模式已接入 Anthropic `/v1/messages/count_tokens` 精确计数路径,成功时减去官方固定 tool overhead 后按 token 阈值判断,失败时会用 Haiku message-create usage 作为官方第二层 fallback,再失败才按官方字符 fallback 计算阈值,覆盖 `auto:N` 百分比、`[1m]` context 和 ant-only context-window override;deferred tool token count 现在按官方 deferred tool name 列表做 in-process memoization,成功值和不可用结果都会缓存。token-count VCR 仍未宣称完成。
298+
- 本轮补充:MCP tools 现在按官方 `isMcp` 语义进入 deferred tool pool,除非显式 `always_load` 覆盖;Anthropic tool schema 序列化会为 MCP tools 输出 `defer_loading`,ToolSearch auto 的 token-count 请求也会计入 MCP tools 但以 loaded schema 计数;configured MCP toolset attach/close 会清空 ToolSearch token-count cache,避免 MCP server lifecycle 变化后复用旧计数。token-count VCR 和更深的 delta-attachment pool-change surface 仍未宣称完成。
298299
- 本轮补充:当本次 request 未启用 `ToolSearch` 时,API message normalization 会从历史 user `tool_result.content` 中剥离 `tool_reference` blocks;纯 reference 结果会替换成官方占位文本 `[Tool references removed - tool search not enabled]`,但剥离发生在 discovered-tool 扫描之后,不影响后续启用 ToolSearch 时恢复 loaded 工具。
299300
- 本轮补充:Anthropic request tool 转换会保留 contract 的 `strict``eager_input_streaming``cache_control``should_defer`,把 deferred 工具序列化为 API `defer_loading`,并让 `always_load` 覆盖 deferred hint;API tool description 会按 description、prompt、searchHint 顺序 fallback,conversation runner 构造请求时会保留 `Task` 等 deferred tool 的 strict/defer_loading 元数据。完整 deferred/lazy tool discovery 仍未宣称完成。
300301
- 本轮补充:tool executor 会在未通过 `ToolSearch` 发现的 deferred 工具发生输入 schema 校验失败时追加 schema-not-sent 恢复提示,引导模型先调用 `ToolSearch``select:<tool>` 再重试;runner 会把当前 turn messages 传入工具 metadata,提示判断兼容 `tool_reference` 结果和 compact boundary 的已发现工具快照。

docs/first-second-parity-audit.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ Anthropic API 和 conversation:
9494
- When `ToolSearch` is available and deferred tools exist, request construction now follows dynamic tool loading: undiscovered deferred schemas are omitted from `tools`, discovered deferred tools are loaded, `ToolSearch` stays callable, and an `<available-deferred-tools>` user meta message advertises the deferred tool names; if no deferred tools exist, `ToolSearch` is omitted from the request.
9595
- Request construction now also applies the official ToolSearch enablement gates before emitting beta `defer_loading` / `tool_reference` shapes: Haiku models, `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`, falsy `ENABLE_TOOL_SEARCH`, `ENABLE_TOOL_SEARCH=auto:100`, unset ToolSearch behind non-first-party `ANTHROPIC_BASE_URL`, and `ENABLE_TOOL_SEARCH=auto` requests below the deferred-tool size threshold all fall back to standard inline tool schemas; explicit `ENABLE_TOOL_SEARCH=true` can still opt a custom gateway back in. Auto mode now uses the Anthropic `/v1/messages/count_tokens` endpoint when the client supports it, subtracts the official fixed tool overhead, falls back to Haiku message-create usage when count-tokens fails, and then falls back to the official character threshold when token counting is unavailable, including `auto:N` percentages, 1M context suffixes, and ant-only context-window overrides.
9696
- Deferred tool token counts in ToolSearch auto mode are now memoized in-process by the official deferred tool name list key, including both successful counts and unavailable/null-equivalent results.
97+
- MCP tools now follow the official deferred-tool rule: they are deferred unless explicitly marked `always_load`, Anthropic tool schema serialization emits `defer_loading` for MCP tools, ToolSearch auto token counting includes MCP tools as loaded schemas, and configured MCP toolset attach/close invalidates the in-process deferred-tool token-count cache.
9798
- When ToolSearch is inactive for a request, API message normalization strips historical `tool_reference` blocks from user `tool_result` content and replaces pure-reference results with the official placeholder text, while preserving history scanning before stripping so discovered tools can still be loaded when ToolSearch is active.
9899
- Anthropic request tool conversion now preserves contract-level `strict`, `eager_input_streaming`, `cache_control`, and API-level `defer_loading` hints from deferred tool definitions, with `always_load` overriding `should_defer`; it also falls back from `description` to `prompt` to `searchHint` when building API tool descriptions. Runner request construction preserves these fields for tools such as `Task`.
99100
- Tool validation now appends an official-style schema-not-sent recovery hint when an undiscovered deferred tool is invoked with invalid typed input and `ToolSearch` is available, including compact-boundary discovered-tool metadata so already loaded tools do not get a false hint.
@@ -115,7 +116,7 @@ The following items remain incomplete and must not be treated as done:
115116
- Auto mode / YOLO classifier: transcript construction, two-stage classifier, XML/tool-use parsing, prompt dump, denial circuit breaker, model gating, and fallback behavior.
116117
- Interactive permission prompt flow: REPL dialogs, bridge/channel/swarm permission relays, user feedback images, prompt race handling, cancellation.
117118
- Full filesystem permission parity gaps that remain: skill-scope allow suggestions, remaining local/local-jsx slash-command execution wiring beyond the currently covered no-query builtins, command permission UI/SDK surfacing, forked/remote/MCP/bundled SkillTool behavior and remaining plugin SkillTool UI/SDK polish beyond current metadata preservation, skill prompt shell injection, complete auto-memory override policy, and deeper platform-specific Windows/WSL bypass handling.
118-
- Full tool execution parity gaps that remain: complete hook runtime policy beyond the current settings command hook path, MCP elicitation, complete SDK control event surface beyond current progress NDJSON, full deferred/lazy tool discovery beyond current local registry BM25 search, API `defer_loading` hints, model/env/proxy ToolSearch gates, remaining `ENABLE_TOOL_SEARCH=auto` edge behavior such as official token-count VCR and full MCP lifecycle invalidation details, remaining delta-attachment details, mid-call cancellation for concrete tools, background task behavior, telemetry, and concrete tool-specific semantics.
119+
- Full tool execution parity gaps that remain: complete hook runtime policy beyond the current settings command hook path, MCP elicitation, complete SDK control event surface beyond current progress NDJSON, full deferred/lazy tool discovery beyond current local registry BM25 search, remaining ToolSearch model/env/proxy edge gates, remaining `ENABLE_TOOL_SEARCH=auto` edge behavior such as official token-count VCR, remaining delta-attachment and deferred-tool pool-change details, mid-call cancellation for concrete tools, background task behavior, telemetry, and concrete tool-specific semantics.
119120
- Complete Anthropic API parity gaps that remain: dynamic beta-header latching for any remaining provider-specific feature gates beyond prompt-cache/cache-editing/strict-output/1M-context requests, ant-only dump gating and remote `/issue` submission integration beyond the current local context summary, full official cost tracker/session restore lifecycle beyond current transcript usage restore, post-handshake streaming failure semantics, deeper gateway/proxy auth and routing behavior beyond static custom headers, first-party/Bedrock/Vertex/Foundry client setup, deeper OAuth account/profile lifecycle beyond request-time refresh retry, fast-mode retry/cooldown semantics, persistent unattended retry heartbeats, full prompt-cache editing lifecycle, and provider-specific cache behavior.
120121
- Full conversation/query loop: stop hooks, compact/auto-compact, token budget escalation, resume, SDK JSON/NDJSON control events, status updates, rate-limit handling, model switch breadcrumbs, side questions.
121122
- Full settings parity gaps that remain: complete Zod-equivalent validation messages, full remote managed-settings watcher/non-daemon background refresh lifecycle beyond current turn-start and daemon-heartbeat refresh, remaining forked agent/frontmatter execution and UI edge cases beyond current policy metadata filtering, complete marketplace TUI/UI/background lifecycle beyond current local/settings/directory/file/URL catalog/git/github/npm cache manifest load, URL catalog cache fallback, git/github fetch/pull cache refresh, npm pack cache refresh, and background watcher/continuous app-state sync beyond current turn-start local settings reload.

internal/api/anthropic/types.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,19 @@ func ToolFromContract(def contracts.ToolDefinition) ToolDefinition {
5353
Description: toolDescriptionFromContract(def),
5454
InputSchema: def.InputSchema,
5555
Strict: def.Strict,
56-
DeferLoading: def.ShouldDefer && !def.AlwaysLoad,
56+
DeferLoading: toolDeferLoadingFromContract(def),
5757
EagerInputStreaming: def.EagerInputStreaming,
5858
CacheControl: copyCacheControl(def.CacheControl),
5959
}
6060
}
6161

62+
func toolDeferLoadingFromContract(def contracts.ToolDefinition) bool {
63+
if def.AlwaysLoad {
64+
return false
65+
}
66+
return def.ShouldDefer || def.MCP != nil
67+
}
68+
6269
func toolDescriptionFromContract(def contracts.ToolDefinition) string {
6370
for _, value := range []string{def.Description, def.Prompt, def.SearchHint} {
6471
if trimmed := strings.TrimSpace(value); trimmed != "" {

internal/api/anthropic/types_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,26 @@ func TestToolFromContractAlwaysLoadOverridesShouldDefer(t *testing.T) {
5252
}
5353
}
5454

55+
func TestToolFromContractDefersMCPToolsUnlessAlwaysLoad(t *testing.T) {
56+
got := ToolFromContract(contracts.ToolDefinition{
57+
Name: "mcp__github__search",
58+
InputSchema: contracts.JSONSchema{"type": "object"},
59+
MCP: &contracts.MCPToolRef{ServerName: "github", ToolName: "search"},
60+
})
61+
if !got.DeferLoading {
62+
t.Fatalf("defer loading = false, want true for MCP tools")
63+
}
64+
got = ToolFromContract(contracts.ToolDefinition{
65+
Name: "mcp__github__search",
66+
InputSchema: contracts.JSONSchema{"type": "object"},
67+
AlwaysLoad: true,
68+
MCP: &contracts.MCPToolRef{ServerName: "github", ToolName: "search"},
69+
})
70+
if got.DeferLoading {
71+
t.Fatalf("defer loading = true, want false when always_load is set")
72+
}
73+
}
74+
5575
func TestToolFromContractDescriptionFallback(t *testing.T) {
5676
cases := []struct {
5777
name string

internal/conversation/mcp.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ func (r Runner) withConfiguredMCPTools(ctx context.Context) (Runner, func() erro
3131
return r, nil, err
3232
}
3333
closeMCP := configured.ToolSets.Close
34+
if len(configured.ToolSets.Servers) > 0 {
35+
resetDeferredToolTokenCountCache()
36+
closeMCP = func() error {
37+
err := configured.ToolSets.Close()
38+
resetDeferredToolTokenCountCache()
39+
return err
40+
}
41+
}
3442
if len(configured.ToolSets.Tools) == 0 {
3543
return r, closeMCP, nil
3644
}

internal/conversation/request.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,10 @@ func deferredToolNames(definitions []contracts.ToolDefinition) []string {
556556
}
557557

558558
func toolDefinitionDeferred(definition contracts.ToolDefinition) bool {
559-
return definition.ShouldDefer && !definition.AlwaysLoad && !isToolSearchDefinition(definition)
559+
if definition.AlwaysLoad || isToolSearchDefinition(definition) {
560+
return false
561+
}
562+
return definition.MCP != nil || definition.ShouldDefer
560563
}
561564

562565
func hasToolSearchDefinition(definitions []contracts.ToolDefinition) bool {

0 commit comments

Comments
 (0)