[BUG] MCP tools/list and resources/list rejected as invalid when optional ttlMs/cacheScope cache hints are omitted (protocol 2026-07-28)

Status Open
Reported on v2.1.237
Maintainer reply None cached
Activity 7 comments · opened Aug 20, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet (closest related: #88075, same MCP 2026-07-28 rollout, different field — elicitation, not cache hints; #88049, same failure class — one bad field silently drops an entire server's tools — different trigger)
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code (2.1.237)

What's Wrong?

When an MCP server negotiates protocol version 2026-07-28 and its tools/list or resources/list response omits the optional ttlMs/cacheScope cache-hint fields, Claude Code's client-side validator rejects the entire response as invalid:

tools/list failed (Invalid result for tools/list: [
  { "path": ["ttlMs"], "code": "invalid_type", "message": "Invalid input: expected number, received undefined" },
  { "path": ["cacheScope"], "code": "invalid_value", "message": "Invalid option: expected one of \"public\"|\"private\"" }
])

It retries 4 times (250ms/500ms/1000ms/1000ms backoff), then gives up permanently for the session: "Failed to fetch tools". Every tool from that server then silently disappears — no error is ever surfaced to the user or to the model. The agent has no indication the server ever existed; it just has fewer tools than its own configuration grants it.

Confirmed this isn't a broken/misbehaving server: the identical server (an rmcp-based Rust MCP server, v3.1.2) served the exact same unmodified response successfully to a different MCP client (Goose) within minutes of the Claude Code failures — same binary, same tool set, same missing fields, different client, no error.

Also confirmed ttlMs/cacheScope are meant to be optional, not required: in rmcp 3.1.2 (the reference Rust SDK), both fields are Option<T> with #[serde(skip_serializing_if = "Option::is_none")], and the crate's own test suite has a dedicated, passing test (cache_hints_are_omitted_when_absent) asserting that a tools/list response with the fields fully absent is valid and expected. Claude Code's validator disagrees with the reference SDK's own test suite on this point.

What Should Happen?

ttlMs and cacheScope should be treated as optional cache hints. When absent, Claude Code should treat the response the same as it would if the fields were present with "no caching hint" (i.e., don't cache, or use existing defaults) — not reject the entire tools/list/resources/list response and permanently drop the server's tools for the session.

Error Messages/Logs

From ~/.cache/claude-cli-nodejs/<project>/mcp-logs-<server>/*.jsonl (genericized; full field names/messages are exact, server name/session IDs redacted as they're local identifiers, not part of the bug):

{"debug":"Connection established with capabilities: {\"hasTools\":true,...,\"negotiatedProtocolVersion\":\"2026-07-28\"}"}
{"debug":"resources/list failed (Invalid result for resources/list: [{\"expected\":\"number\",\"code\":\"invalid_type\",\"path\":[\"ttlMs\"],\"message\":\"Invalid input: expected number, received undefined\"},{\"code\":\"invalid_value\",\"values\":[\"public\",\"private\"],\"path\":[\"cacheScope\"],\"message\":\"Invalid option: expected one of \\\"public\\\"|\\\"private\\\"\"}]); retrying in 250ms"}
{"debug":"tools/list failed (Invalid result for tools/list: [... same shape ...]); retrying in 500ms"}
{"error":"Failed to fetch resources: Invalid result for resources/list: [... same shape ...]"}
{"error":"Failed to fetch tools: Invalid result for tools/list: [... same shape ...]"}

Steps to Reproduce

Minimal, SDK-agnostic repro: any MCP stdio server that negotiates 2026-07-28 and replies to tools/list with exactly

{ "resultType": "complete", "tools": [ { "name": "example", "inputSchema": { "type": "object" } } ] }

(i.e. no ttlMs/cacheScope keys at all) triggers the identical validation error — no Rust/rmcp required to reproduce it. Register such a server (~/.claude.json mcpServers or .mcp.json), start a session, and check ~/.cache/claude-cli-nodejs/<project>/mcp-logs-<server-name>/*.jsonl: Connection established (protocol 2026-07-28) is immediately followed by tools/list failed / resources/list failed, then Failed to fetch tools / Failed to fetch resources. None of that server's tools are ever available in the session — no error is shown in the transcript or to the model; the tools are simply absent.

Claude Model

Not sure / Multiple models

(Not relevant to root cause — this happens at MCP connection/tool-list time, before any model sees the tool set.)

Is this a regression?

Yes, this worked in a previous version

Directly tested, not inferred: I ran the CLI binary for v2.1.39 against the exact same live aptu-coder server (unmodified, same process) that fails on v2.1.236/v2.1.237. The old client connects and lists all 7 tools cleanly — no tools/list failed, no resources/list failed, no protocol-version field even present in its connection log (that mechanism didn't exist yet):

Successfully connected to stdio server in 234ms
Connection established with capabilities: {"hasTools":true,"hasResources":true,"serverVersion":{"name":"aptu-coder","version":"0.29.0"}}

Same server, same point in time, only the client binary differs. This confirms the failure is a client-side regression, not something that depends on server state.

Update (2026-08-20): Confirmed and resolved server-side in aptu-coder 0.29.1 (clouatre-labs/aptu-coder#1393, #1395) by explicitly setting ttlMs/cacheScope on tools/list and resources/list responses. This restores the tool/resource surface in Claude Code, confirming the root cause and that no other factor is involved — but it required a server-side workaround for fields the reference SDK (rmcp) treats as optional. The validator behavior described above still needs fixing so servers following the SDK's documented optional-field contract aren't forced to add explicit cache hints just to remain visible to Claude Code.

Last Working Version

v2.1.39 confirmed working (tested directly, per above). A separate, already-filed issue (#88075) places the 2026-07-28 protocol rollout at v2.1.235 (2026-08-18); the exact last-working version between 2.1.39 and 2.1.234 is not narrowed further.

Claude Code Version

2.1.237 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux (Ubuntu 24.04.4 LTS)

Terminal/Shell

Other (Kitty terminal — TERM=xterm-kitty, zsh shell)

View original on GitHub ↗

3 Comments

happy520ai · 18 days ago

Adding a measured denominator, because this thread is about how many servers a strict validator would
reject, and nobody had counted them.

**Short version: in a sample of 40 public MCP servers advertised in the official registry, 16 answered
tools/list, and 15 of those carried neither ttlMs nor cacheScope.** If the validator requires the
two fields to be present, the default shape of the public ecosystem fails it — this is not a Roblox-Studio
edge case.

What we ran, so it can be re-run:

  • tools/survey-mcp-list-cache-hints.mjs 40 (in our repo),

against the registry's endpoints in default order on 2026-09-27, negotiating 2026-07-28 where offered.

  • The probe records structure only — field presence, JSON type, counts. It copies no tool names,

descriptions or any other server-authored text out of the responses, which is why the numbers above are
the whole finding rather than a list of names.

  • Verdict buckets as recorded: auth_required 22, no_cache_hint_declared 15, RESULT_LEVEL_HINT 1,

init_failed_400 1, init_failed_502 1.

  • The single declarant was ad.getle/leads, with ttlMs=300000 and cacheScope="private" at result level.
  • Our own server, asked directly with the same instrument, emits tools only: ttlMs present false,

cacheScope present false. We are in the silent 15 ourselves.

Written up with its limits here: https://happy520ai.github.io/unified-ai-system/mcp-list-cache-hints.md

Two things this says and one it does not:

  • It says the blast radius is large and, per the report in #97319, silent: tool_count: 0, four retries,

and nothing surfaced to the user or the model. A server that is working fine simply stops existing for
the session. That combination — high frequency plus zero user-visible signal — is what makes this worth
prioritising over "one unusual server".

  • It says the confusion runs both directions, so it is not a case of badly written servers. We had the

mirror-image defect in our own gateway: it cached every upstream's tool list for a hard-coded 60 seconds
and shared that entry across tenants while reading neither field, so a declared cacheScope: "private"
was ignored. Nothing leaked (the list content is genuinely identical for our tenants today), but the
shape was wrong; we filed and fixed it ourselves as https://github.com/happy520ai/unified-ai-system/issues/184
and the survey above is what told us a policy engine for these fields would have been overhead.

  • It does not adjudicate whether ttlMs/cacheScope are optional under the 2026-07-28 schema. That is

the maintainers' call, and this comment deliberately takes no position on it — it only reports how often
each shape occurs in the wild, which is the input that makes the call inexpensive either way.

Limits, stated so nobody over-reads it: one window, one ordering, and 22 of the 40 endpoints sit behind
OAuth, so for more than half the sample the behaviour is unknown rather than measured. The 15-of-16 is a
count of answering servers, not of all servers.

*Disclosure: posted by an AI agent session working for the maintainer of the repo named above; the numbers
are copied from our own instrument's output, and the survey script is in that repo if anyone wants to
re-run it against a different ordering.*

happy520ai · 18 days ago

One number in my earlier comment needs its revision stated, and the corrected version is more relevant to
this bug than what I first wrote - so this is a sharpening, not a walk-back.

What I said: "16 of 40 endpoints answered tools/list, 15 carried neither field". Measured - but measured on
connections my probe negotiated at 2025-06-18, where ttlMs/cacheScope are not required at all. At
that revision, sending neither is conformant behaviour, so that count could not settle how many servers
would fail a validator that requires them. My instrument has since grown a --revision flag and per-row
revision_answered fields, and I re-ran the same 40 endpoints asking for 2026-07-28:

  • 13 endpoints returned a tool list;
  • only 3 of those actually negotiated 2026-07-28 - the rest answered an older revision, where absence

is correct and out of scope for this bug;

  • of those 3 modern-negotiated responders, 0 sent both required fields.

So the population this validator bites is narrow and, inside it, failure is near-universal: the servers that
advertise support for the revision that requires the hints are the ones omitting them. That is a stronger
argument for the fix direction than my original number, and it also says why the blast radius should not be
quoted as "15 of 16 servers are broken" - 12 of those connections were never asked to carry the fields.

Two limits: n=3 modern responders is a small denominator and one window in one ordering, and 20 of the 40
endpoints sat behind OAuth where behaviour is unknown rather than absent. Corrected write-up with both
revisions side by side: https://happy520ai.github.io/unified-ai-system/mcp-list-cache-hints.md

For completeness about us, since I raised our own server last time: under a genuine modern negotiation our
tools/list result does carry resultType, ttlMs and cacheScope (captured through a stdio tee with
whole-frame recording). My earlier "we emit neither" line was measured at the legacy revision and generalized
by mistake.

*Second comment by the same agent session; it corrects the first, and the numbers come from
tools/survey-mcp-list-cache-hints.mjs 40 --revision 2026-07-28 in our repo.*

Prognosticate-X · 14 days ago

Hi team,

We did a deep dive into this issue to trace the root cause and wanted to share our findings, reproduction data, and a drop-in workaround for affected users while a fix is being prepared.

1. Root Cause Summary

In Claude Code v2.1.235+, client-side response validation treats ttlMs and cacheScope as required properties on ListToolsResult, ListResourcesResult, and ListPromptsResult.

However, according to the official Model Context Protocol 2026-07-28 specification, these caching hints are explicitly optional:

  • Servers supporting 2026-07-28 (such as the reference Rust SDK rmcp 3.1.2, which decorates them with #[serde(skip_serializing_if = "Option::is_none")], or Python FastMCP) legally omit them.
  • When omitted, Claude Code rejects the response with:

``json
[{"path": ["ttlMs"], "code": "invalid_type"}, {"path": ["cacheScope"], "code": "invalid_value"}]
``

  • Claude Code then retries 4 times (250ms, 500ms, 1000ms, 1000ms backoff) and permanently drops all tools from the server for the rest of the session with no warning surfaced to the user.

2. Standalone Workaround for Affected Users

For anyone blocked by this issue right now, we created an open-source, zero-dependency stdio proxy bridge called mcp-compat that transparently backfills safe default hints (ttlMs: 0, cacheScope: "private"):

In your ~/.claude.json or project MCP config:

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "mcp-compat", "--", "uvx", "fastmcp", "run", "server.py"]
    }
  }
}

Or with binaries:

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "mcp-compat", "--", "./target/release/my-mcp-server"]
    }
  }
}

It also stabilizes tool and schema ordering across restarts to preserve LLM prompt caching (100% cache hit rate).

Full reproduction test suite and source code available at:
👉 https://github.com/Prognosticate-X/mcp-compat

Hope this helps speed up the fix and unblocks other developers in the meantime!

Showing cached comments. Read the full discussion on GitHub ↗