A tools/list cache is not a session
Tick exposes a local checklist over MCP so Claude and friends read and write the same todos as the Mac app. BuilderHelp and RuleCaddie talk to tools the same way agents do everywhere: list what exists, then call. The July 2026 MCP revision made that path explicitly cacheable and dropped protocol sessions. The trap is treating a cached tools/list the way we used to treat a sticky session — as if the catalog were live connection state, or as if application state still had to hide behind one. A tools/list cache is a freshness hint. It is not a session.
What the protocol stopped owning
Before 2026-07-28, MCP carried an initialize handshake and an Mcp-Session-Id. Clients often keyed tools/list off that session. Gateways needed sticky routing or a shared session store so the next tools/call landed where the handshake had been. That made horizontal deploy feel like a special snowflake of infrastructure for what is, underneath, an HTTP tool surface.
The new core drops the handshake and the session header. Each request carries protocol version, client identity, and capabilities in _meta. Any instance can answer. Round-robin is enough at the protocol layer. If you still need affinity, that is your app’s problem — not a protocol requirement you inherited by speaking MCP.
For Tick, that matches how a local SQLite checklist should behave: the server is the database, not a per-connection memory of which tools Claude was shown five minutes ago. For a remote agent host in front of BuilderHelp-style lookups, it means you can scale tool servers like ordinary APIs instead of sessionful chat sockets.
ttlMs is permission to cache, not permission to freeze
List responses for tools, prompts, and resources now carry ttlMs and cacheScope. Deterministic ordering is required so clients can reuse the catalog and keep upstream prompt caches stable across reconnects. That is the point of SEP-2549: stop refetching a fixed tool set on every turn.
Respect the TTL. Do not invent a longer one because the catalog looked stable in staging. And do not treat TTL expiry as the only invalidation path. notifications/tools/list_changed means re-index now — even if the clock says the previous list is still fresh. A deploy that adds a write_todo tool, or a scope change that removes delete_project, is not something you discover by waiting out ttlMs.
cacheScope matters too. public means the catalog is safe to share across users of that server URL. private means it was shaped by the caller’s auth. Mixing those is how one principal’s reduced tool set becomes another’s prompt, or how a shared CDN entry serves the wrong capability map.
Application state belongs in arguments
Stateless protocol does not mean your product has no state. It means the transport is not where you hide it. The guidance in the spec is the same pattern HTTP APIs already use: a create tool returns an explicit handle — a project_id, a basket_id, a browser_id — and later tools take that handle as an ordinary argument the model can see and pass.
Tick’s checklist is durable in SQLite. The MCP tools should name the project or todo they mean, not assume the connection still remembers the last selected list. BuilderHelp owner-packet lookups should key on the parcel or customer id in the tool args, not on whichever worker handled initialize.
If you previously stuffed “current project” into session memory because the model kept losing context, fix the tool descriptions and the argument schema. A silent session default is how an agent writes a deadline onto the wrong project after a reconnect that looked successful.
What we check before trusting a cached catalog
On client reconnect, reuse the cached tools/list only when the server URL and auth principal match the cache key, the TTL has not expired, and no list_changed notification arrived. Otherwise refetch before the next tools/call. Calling a tool that disappeared from the live catalog is a worse failure than one extra list round-trip.
On the server, return tools in a stable order and advertise an honest ttlMs. If the set varies by OAuth scopes, set cacheScope to private and make sure the list actually filters to what that token can invoke — a cached public list that over-promises is how agents attempt forbidden calls and you spend the afternoon reading authorization noise.
Ship a migration path if you still have sessionful clients: a strict stateless route beside the old one, drain active sessions, then remove the legacy path inside the deprecation window. Do not keep minting Mcp-Session-Id “for compatibility” while also advertising long TTLs. That combination reintroduces the sticky-routing tax the revision was meant to end.
The practical test for Tick and for any agent host we wire: after a deploy that adds or removes a tool, does every connected client see the change without a full app restart, and does a mid-flight multi-step checklist edit still name its project ids in arguments? If either answer is no, the cache is pretending to be a session.
Questions
- Can clients cache tools/list under the 2026-07-28 MCP spec?
- Yes. List responses carry ttlMs and cacheScope so clients can reuse catalogs and keep prompt caches stable. Still re-fetch on notifications/tools/list_changed, and key private catalogs by auth principal — not only by server URL.
- Where should multi-step tool state live if MCP has no session?
- In explicit handles returned by tools and passed as ordinary arguments on later calls — project ids, basket ids, and similar. Durable product state belongs in your datastore; the protocol no longer carries a per-connection session for you.
- Do MCP servers still need sticky load balancing?
- Not for the protocol. Stateless requests can land on any instance. Keep sticky routing only if your application still hides state in process memory — and treat that as debt to remove, not as an MCP requirement.