feat: add request-state key rotation - #1128
Merged
DaleSeo merged 11 commits intoAug 28, 2026
Merged
Conversation
DaleSeo
reviewed
Aug 8, 2026
…te-key-rotation # Conflicts: # crates/rmcp/src/model/request_state.rs
DaleSeo
approved these changes
Aug 28, 2026
This was referenced Aug 27, 2026
Merged
Closed
|
Reading through your merged PR on request-state key rotation, because our marketplace has a per-agent-secret + hash-chained mutation shape that would need the same story eventually. Concrete question, not a pitch: given the rs1 fallback flag, how long do you recommend keeping it on in production before removing, and is there a wire-visible signal for "safe to disable" (all in-flight rs1 exchanges drained)? Keyless read at ergonia.works/api/arena |
jmagar
added a commit
to dinglebear-ai/rust-sdk
that referenced
this pull request
Sep 4, 2026
* fix: allow concurrent streamable http requests (modelcontextprotocol#1186) * fix: allow concurrent streamable http requests * fix: keep streamable http recovery responsive Keep cancellation and replies available while old session POSTs finish. Bound the wait for old POSTs and the replacement initialization handshake. Do not retry interrupted POSTs because the server may have processed them. Add regressions for recovery, queued cancellation, control timeouts, and server replies needed by active requests. * fix: preserve response and cancellation ordering * refactor: clarify streamable http control flow * fix: match stream responses against pending request ids Match responses against all pending requests before removing a stream registration. Keep distinct numeric and string ids separate while preserving the existing fallback for servers that stringify numeric ids. Add a mixed-id regression and keep a separate registration owner alive in the abandoned-cancellation test. * feat: make streamable http control timeouts configurable * fix(transport): fall back after sessionless HTTP discover rejections (modelcontextprotocol#1211) * fix(transport): fall back after HTTP discover rejection * fix(transport): limit legacy fallback to sessionless probes * style: apply nightly rustfmt import ordering * ci: pin GitHub Actions to commit SHAs (modelcontextprotocol#1216) * ci: scope release token permissions to jobs (modelcontextprotocol#1220) * ci: default workflow tokens to read-only contents (modelcontextprotocol#1218) * ci: remove coverage job write permission (modelcontextprotocol#1219) * ci: pin release-plz fork revision (modelcontextprotocol#1221) * feat: add request-state key rotation (modelcontextprotocol#1128) * feat: add request-state key rotation * docs: streamline request-state codec documentation * fix: harden request-state fallback verification * refactor: refine request-state keyring API * docs: make request-state rotation guidance self-contained * docs: streamline request-state keyring rustdocs * test: streamline request-state keyring coverage * docs: restore request-state key rotation doctest * chore: remove manual changelog entry * ci: reduce dependabot update noise (modelcontextprotocol#1184) * docs: use auto lifecycle in HTTP example (modelcontextprotocol#1187) * chore(deps): bump taiki-e/install-action from 2.85.13 to 2.86.7 (modelcontextprotocol#1230) Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.13 to 2.86.7. - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](taiki-e/install-action@82cd3e7...b6ff580) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.86.7 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add typed custom request responses --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Nick Cooper <142361983+nickcoai@users.noreply.github.com> Co-authored-by: King Star <mcxin.y@gmail.com> Co-authored-by: Jack Amadeo <jackamadeo@block.xyz> Co-authored-by: camillelawrence <gocamille@users.noreply.github.com> Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds targeted key selection and rolling-safe signing-key rotation to
RequestStateCodecfor SEP-2322requestStatevalues.Closes #1096.
Motivation and Context
The existing
rs1.<body>.<tag>format identifies the wire format but not the key that produced the HMAC. Rotating a deployment-wide request-state key therefore either invalidates every in-flight multi-round exchange or requires applications to trial-open each token against every retained key.That is particularly difficult for multi-replica deployments, where every replica that may continue an MRTR exchange must share the same signing keys and cannot safely switch them atomically.
How
Added an
rs2.<base64url(kid)>.<base64url(expiry || payload)>.<base64url(tag)>format. The key id is included in the HMAC input, allowing opening to select one key directly while preventing key-id substitution.Added
RequestStateCodec::new_with_keyring(active_kid, keys)for keyed sealing and targeted verification.active_kidselects the signing key, while any configured key can verify anrs2value naming that key.Key ids are opaque, case-sensitive UTF-8 strings limited to 255 bytes. They are authenticated but visible to clients and are not confidential.
Preserved
RequestStateCodec::new()and the existingrs1wire format byte-for-byte.Added
with_rs1_signing()andwith_rs1_fallback()for a rolling-safe migration:rs1with the old key.rs2, retaining the old key as anrs1fallback.Legacy fallback verification evaluates every configured
rs1fallback before returning, so the HMAC count does not reveal which fallback matched. The rustdocs recommend keeping this set small and temporary.Added distinct HMAC domains for
rs1andrs2, with length-prefixed key ids and associated data.Added explicit errors for unknown key ids, invalid wire key ids, and invalid keyring configuration. Configuration messages are documented as diagnostics rather than values applications should match programmatically.
The rustdocs require applications to map all token-opening failures to one client-visible error.
Added wire-format, key-selection, migration, and key-retirement guidance to the codec rustdocs and README.
How Has This Been Tested?
cargo test -p rmcp --features request-state model::request_state::tests --lib— 34 tests passed.cargo test -p rmcp --features request-state --doc— 33 doctests passed, including the staged-rotation example.cargo clippy --all-targets --all-features -- -D warningsrs1output is unchanged and independently verify the newrs2format.Breaking Changes
None. Existing
RequestStateCodec::new()callers continue to emit and accept the samers1format. Keyrings andrs2are opt-in behind the existingrequest-statefeature.New public API:
RequestStateCodec::new_with_keyringRequestStateCodec::with_rs1_signingRequestStateCodec::with_rs1_fallbackRequestStateError::{UnknownKeyId, InvalidKeyId, InvalidKeyring}RequestStateErrorremains#[non_exhaustive], so adding these variants does not require downstream callers to update exhaustive matches.Types of changes
Checklist
Additional context
Note that the codec intentionally does not provide runtime key reload or a default TTL. Applications must retain old verification keys until the maximum request-state lifetime has elapsed before retiring them.