Your tools/list is stamped. That is not the same as cached. A developer identified a flaw in the Model Context Protocol (MCP) specification's caching stamps for the tools/list endpoint, showing that presence of ttlMs and cacheScope fields does not guarantee actual cacheability. The developer's companion project mcp-better now includes a contrast-smoke test that fails closed if either the stamp or catalog content check goes green on the deliberately broken mcp-worse server, ensuring each clause is independently verified. A stamp on tools/list is a claim about later reuse. Presence is not proof the next call is cacheable. TL;DR — The 7/28 spec put ttlMs and cacheScope on tools/list . Most write-ups treat that stamp as a cache. It is a claim about later reuse . Presence means the server said something. Truth needs an observation that can fail. We shipped a liar and a probe. The catalog grew. The probe still said OK. The stamp half was along for the ride. One mutant per clause. Name which negatives are still live. The 2026-07-28 spec added list-cache stamps SEP-2549 , modeled on HTTP Cache-Control . The official sentence is some version of: clients know exactly how long tools/list is fresh. That sentence is doing a lot of work. HTTP already taught this. Cache-Control: max-age=60 is a statement. It is not proof the next GET returns the same bytes. MCP imported the words. It did not import a test. A server can print ttlMs: 60000, cacheScope: Public and change its catalog on the next call. The stamp is still well-formed. A probe that only checks presence will still pass. Last piece: a labeled companion, mcp-worse , and one command — contrast-smoke — that passes only if the good server meets the BETTER list contract and the bad one fails it. The checker looked like this: php fn is lying surface p: &ListProbe - bool { let unstamped = p.ttl ms.is none || p.cache scope.is none ; let wrong order = p.names = better names ; unstamped || wrong order } That OR is the hole. mcp-better grew a third tool, confirm echo . mcp-worse still lists two: echo , health . So wrong order really: names ≠ the good catalog is always true . The stamp half stopped carrying observable weight. If worse grew with ttl ms and with cache scope tomorrow, the example would still print OK. It would fail for contents only. The negative case for ttlMs would be gone, and nothing would say so. The “companion must stay a reliable liar” guard only fires when every clause goes green at once. Partial decay is invisible. A comment on that post named it. They were right. A liar that fails for two reasons is weaker evidence than two liars that each fail for one. Steal-the-pattern already said: one smallest lie per claim. We had one binary that violated every clause, and an OR that hid which ones were still live. Order is a property of the list the probe is holding . You can see it. ttlMs and cacheScope are statements about how that list may be reused later . Confirming the fields exist and look well-formed proves the server made a claim. It does not prove a client would be right to skip the next tools/list . order-restart-smoke does the right thing for order : two processes, same names. It applies that shape to TTL as ttl a == ttl b — the stamp is restart-stable. That is a property of the number , not of the caching behavior the number describes. Falsifying a TTL claim takes an observation pair that straddles a change. Our catalog is compiled in. The TTL claim cannot be violated yet. Declared, not falsified. Once the catalog goes dynamic, ttlMs is the first stamp with room to lie — and it is the clause with the least behind it. This lab reads this list. Not the next call. Not a client cache. The probe now requires each teaching clause on the companion. Stamp decay fails closed and names the clause. php // examples/contrast smoke.rs — current tree fn is unstamped p: &ListProbe - bool { p.ttl ms.is none || p.cache scope.is none } fn wrong names p: &ListProbe - bool { p.names = better names } Both must stay true on mcp-worse . If either goes green, the example exits non-zero and says which. git clone https://github.com/Wolfe-Jam/mcp-better.git cd mcp-better cargo build --bins This is the tree , not cargo install mcp-better . The published v0.5.0 tag still has the OR . The named-clause OK line is the honesty cut on current main . cargo run --example contrast-smoke Expect captured 2026-08-19, current tree : better names= "health", "echo", "confirm echo" ttl=Some 60000 scope=Some Public worse names= "echo", "health" ttl=None scope=None contrast-smoke: OK better contract · worse unstamped · worse names≠health,echo,confirm echo The OK line is the point. It names which negatives are still live. | Claim | Evidence | Not proved | |---|---|---| | Good list is ordered + stamped | Wire: three names, ttlMs 0 , cacheScope == Public | That a client should cache it | | Companion stays unstamped | unstamped asserted, not OR -ed away | Cache behavior | | Companion catalog ≠ better | names ≠ health,echo,confirm echo today: two tools, reversed | That “wrong names” is only order | | Partial decay is visible | Stamp clause going green fails the example | A second mutant stamped-but-reversed | That last empty cell is honest. One companion that fails for two reasons is still weaker than two mutants that each fail for one. This cut makes the live negatives named . It does not ship a second dummy. Same shape as last time — finish the last step: OR in disguise. ttlMs , cacheScope , a presence check is “a claim was made.” Do not call it verified until something can fail.If you cannot say which clause is still live, you do not have a negative case. You have a dummy that is wrong in a pile. A README cannot lie to a test that reads the wire. A stamp can still lie to a README. ttlMs on the list is a reuse claim. contrast-smoke now says which teaching clauses are still live. That is the list. Not the cache. Claim = wire. Ask what you actually falsified. Which clause on your tools/list is only present — not proven? I'm an AAIF Ambassador. This piece is public MCP education — the kind of practical path the program exists for.