# Your tools/list is stamped. That is not the same as cached.

> Source: <https://dev.to/wolfejam/your-toolslist-is-stamped-that-is-not-the-same-as-cached-2j0f>
> Published: 2026-08-20 11:00:00+00:00

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.*
