Interface RequestStateCodec
- All Known Implementing Classes:
AeadRequestStateCodec
requestState continuation token (MCP 2026-07-28
SEP-2322 Multi-Round-Trip Requests).
Mirrors the "pluggable SPI + built-in default" shape sub-project E established for the client-side
McpResponseCache (not on this module's classpath): this interface plus AeadRequestStateCodec
(the built-in default) is the server-side, seal/unseal counterpart.
Implementations must treat any verification failure (tamper, AAD mismatch, or an implementation-specific
integrity check) as an Optional.empty() return from unseal(String, String) — never a
thrown exception. Expiry is checked by the caller (McpRevision) against
McpRequestState.expiresAtMs(), not by the codec itself, since expiry is a dispatcher-level policy (see
McpMrtrConfig), not a codec integrity concern.
Trust model — requestState is a bearer token. The sealed token is a replayable credential:
anyone who holds it can resume the paused operation until it expires, and it carries no caller identity. A
codec (and every call site) must treat it accordingly — transport only over authenticated TLS, and
never log a requestState. Multi-use within TTL by default: a captured token can be
replayed any number of times within its TTL unless an operator configures a ReplayCache (see
McpMrtrConfig.setReplayCache(ReplayCache)) to enforce single-use — the built-in
InMemoryReplayCache is per-process only, so cross-node single-use requires a shared implementation. A
handler must still write idempotent resume side effects unless it knows a single-use ReplayCache is in
effect; the max-rounds cap bounds chain depth, not replay count (see McpMrtrConfig).
Canonical AAD format. The dispatcher binds each token to the request that produced it by passing a
single canonical AAD string: method + ' ' + protocolVersion + ' ' + target — the
JSON-RPC method name, the negotiated protocol version, and the operation target (the tool name for
tools/call, the prompt name for prompts/get, or the resource uri for
resources/read; empty for any other method), all NUL (U+0000)-separated (see
McpRevision#aad). NUL is chosen because it can never appear in a method name, a protocol-version
literal, or the trailing target field, so the concatenation is unambiguous. The trailing target field binds
the token to the specific operation it paused against, not merely the method, so a token minted while paused
on one tool/prompt/resource cannot be resumed against a different one of the same kind. An implementation
only needs to treat the AAD as opaque bytes; the format is documented here so seal and unseal call sites (and
any custom codec) agree on exactly what is authenticated.
Authenticated principal. Both seal(McpRequestState, String, Principal) and
unseal(String, String, Principal) receive the caller's authenticated Principal — the same
identity the resource-server layer establishes (see McpResourceServerSupport#principal). This is the
seam a hardened codec uses to fold the caller identity into its authenticated data, so a
requestState minted for principal A cannot be resumed by principal B. The built-in
AeadRequestStateCodec binds exactly that: it folds a canonical, deterministic iss|sub identity
(see AeadRequestStateCodec.principalIdentity(Principal)) into its AEAD authenticated data, so a
mismatched principal fails the GCM tag check and unseal(java.lang.String, java.lang.String, java.security.Principal) returns Optional.empty(). The
dispatcher guarantees the principal is delivered to the codec at both seal and unseal; a custom codec
remains free to bind a different identity attribute (subject claim, issuer+subject, full claim set) or none at
all. The principal is nullable: when RS auth is disabled or the caller is anonymous it is seal(McpRequestState, String) / unseal(String, String) convenience overloads simply pass a
-
Method Summary
Modifier and TypeMethodDescriptiondefault Stringseal(McpRequestState state, String aad) No-principal convenience overload ofseal(McpRequestState, String, Principal)(anonymous caller).seal(McpRequestState state, String aad, Principal principal) Seals a payload into an opaque token, exposing the authenticated caller identity to the codec.default Optional<McpRequestState>No-principal convenience overload ofunseal(String, String, Principal)(anonymous caller).Unseals a token, verifying integrity against the supplied AAD, with the authenticated caller identity available to the codec.
-
Method Details
-
seal
Seals a payload into an opaque token, exposing the authenticated caller identity to the codec.- Parameters:
state- The payload to seal. Must not benull .aad- Additional authenticated data (the dispatcher passes the canonicalmethod + ' ' + protocolVersion + ' ' + targetform — see the class Javadoc). Must not benull .principal- The authenticated caller (see the class Javadoc). May benull for an anonymous caller or when resource-server auth is disabled.- Returns:
- The opaque token. Never
null .
-
unseal
Unseals a token, verifying integrity against the supplied AAD, with the authenticated caller identity available to the codec.The
tokenis arbitrary, untrusted client input. It arrives verbatim from the follow-up request'srequestStatefield, so an implementation must assume nothing about its length or content: it must not allocate buffers sized from the raw token, and it must not throw — any malformed, oversized, tampered, or AAD-mismatched input must returnOptional.empty()(see the class Javadoc contract).- Parameters:
token- The token previously returned byseal(McpRequestState, String, Principal). Must not benull .aad- The AAD to verify against. Must match the value passed toseal(McpRequestState, String, Principal)exactly, or verification fails.principal- The authenticated caller (see the class Javadoc). May benull for an anonymous caller or when resource-server auth is disabled. A codec that binds the principal must fail verification when it differs from the sealing principal.- Returns:
- The original payload, or
Optional.empty()if verification fails for any reason (tamper, AAD mismatch, malformed token, principal mismatch).
-
seal
No-principal convenience overload ofseal(McpRequestState, String, Principal)(anonymous caller).- Parameters:
state- The payload to seal. Must not benull .aad- Additional authenticated data. Must not benull .- Returns:
- The opaque token. Never
null .
-
unseal
No-principal convenience overload ofunseal(String, String, Principal)(anonymous caller).- Parameters:
token- The token previously returned byseal(McpRequestState, String). Must not benull .aad- The AAD to verify against.- Returns:
- The original payload, or
Optional.empty()if verification fails for any reason.
-