Retire caller-supplied numeric request ids against future mints - #3367
Conversation
A caller-supplied id left the dispatcher's in-flight set once its request completed, so the minted-id sequence eventually landed on the same id again - the spec forbids reusing an id within a session. Accepted numeric supplied ids are now retired until the monotonic counter passes them, on both JSONRPCDispatcher and DirectDispatcher. Minting skips retired keys alongside in-flight ones and prunes them as the counter advances, preserving the existing behavior where mints keep using low ids around a still-in-flight supplied id. Fixes modelcontextprotocol#3126
|
Thanks for the contribution. This repository only keeps pull requests open when they're linked to an issue that a maintainer has assigned to the author — CONTRIBUTING.md explains why and how we work. This PR has been closed for now because you aren't currently assigned to #3126. If a maintainer would like this change as a PR from you, they'll assign you to #3126 and this PR will reopen automatically — there's nothing more you need to do. (If you opened the issue, this PR already shows up on its timeline.) There's no need to open a new PR — this one will be reopened. While it's closed, please push any updates as new commits rather than force-pushing, since GitHub can't reopen a PR whose branch has been rewritten. Maintainers: reopening this PR, removing the |
Retire caller-supplied numeric request ids against future mints
Fixes #3126
What
A caller-supplied
CallOptions["request_id"]left the dispatcher's in-flight tracking once its request completed, so the minted-id sequence eventually landed on that same id again — two different requests with one id on the wire, which the spec forbids:Reproduced on
main: supply id1, let it complete, mint three requests → wire ids[1, 1, 2, 3].How
Accepted numeric supplied ids are added to a small retired set on both dispatchers; minting skips
in-flight ∪ retiredand prunes retired entries as the monotonic counter passes them (so the set stays bounded by ids the counter hasn't reached yet).Deliberate deviation from the fix sketched on the issue
The issue proposed advancing the counter at accept time (
max(self._next_id, pending_key)). That variant also burns every id below a supplied one whenever it is accepted while still in flight, flipping the documented contract intest_minted_ids_skip_a_caller_supplied_id_still_in_flightfrom mints[1, 2, 4]to[4, 5, 6]. The retire-set fixes reuse with zero change to existing behavior:1completes → mints[1, 1, 2]❌[2, 3, 4]"7"completes → mints walk 9[1..9]reuses 7 ❌[1..6, 8, 9, 10]"3"in flight → mints[1, 2, 4]If maintainers prefer the simpler accept-time advancement and adjusting the in-flight expectation instead, that tradeoff is yours to call — happy to rework.
Tests
Both regression tests run through the shared
pair_factory, so each coversJSONRPCDispatcherandDirectDispatcher:[2, 3, 4])[1..6, 8, 9, 10])Full gate green locally:
./scripts/test, ruff format/check, pyright.(AI-assisted implementation, prepared with a coding agent and reviewed by me.)