MCP requestState Token Designer

webhook-signature-verifier checks a signature somebody else produced, against a published vendor scheme. This tool does the opposite job. It designs the binding set that goes inside an MCP requestState token, mints one with real WebCrypto HMAC-SHA-256 in front of you, and computes what each binding does and does not prevent. Its entire point is a case where every signature check is correct and a correct signature is still not the answer.

This is a faithful reading of normative text made executable, not a discovery. The Multi Round-Trip Requests page of the Model Context Protocol specification, version 2026-07-28, states the trap itself, in one sentence, inside a warning callout. Everybody will skim it. So here it is running: press Replay 5 times and read five PASS results.

Bindings

Each binding is independent. Turn one off and the residual table below changes on the spot, so the page teaches which binding stops which attack instead of handing you a fixed recipe.

The request being bound

Residual table

This is the primary output. Every row is computed live, by minting and verifying real tokens with the current binding set, not looked up in a table of stored answers.

AttackResultStopped by

Replay the identical token

Same principal, same method, same params, inside the TTL. The token is not modified in any way. Every check the token can perform is performed, and every one of them is correct.

    The token

    Minted value

    -

    Decoded payload

    Shown here because you are the server. A client MUST NOT do this: the specification says the value is opaque to clients.

    -

    Request digest input

    -

    What the specification actually says

    Quoted verbatim from the Multi Round-Trip Requests page of the Model Context Protocol specification, version 2026-07-28.

    The state is opaque, and hostile

    An opaque string meaningful only to the server. Clients MUST NOT inspect, parse, modify, or make any assumptions about its contents.
    If a client request contains a requestState field, servers MUST treat requestState as an attacker-controlled input. If requestState influences authorization, resource access, or business logic, servers MUST protect its integrity (e.g. HMAC or AEAD) and MUST reject state that fails verification. Integrity protection MAY be omitted only when tampering can cause nothing worse than request failure.

    The three replay bindings

    To prevent replay, servers SHOULD include the following inside the integrity-protected requestState payload and verify each on receipt:

    Three bindings are listed under that SHOULD. Integrity protection is a separate MUST, in the paragraph above it. The four checkboxes at the top of this page are those three plus integrity.

    The trap, in the specification's own words

    Note that these measures bound the replay window and prevent cross-user and cross-request reuse, but do not by themselves guarantee single-use. Servers for which a given requestState must be consumed at most once (e.g., one-time redemptions) MUST enforce that invariant server-side.

    On the published page that paragraph sits in a callout marked as a warning, attached to the third bullet above.

    Why the state passes through hostile hands at all

    Because requestState passes through the client, malicious or compromised clients could attempt to modify it to alter server behavior, bypass authorization checks, or corrupt server logic. Servers MUST validate request state as described in the server requirements above.

    Source: modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr

    The AEAD variant

    The specification permits either construction: servers MUST protect the integrity of the state, e.g. HMAC or AEAD. This page implements the HMAC one, because HMAC-SHA-256 is what WebCrypto hands you with no key-agreement ceremony, and because the payload stays legible in the panel above, which is the entire point of a teaching tool.

    An AEAD build, for example AES-GCM, differs in exactly two ways that matter here. First, the payload is encrypted, so the principal, the expiry and the digest stop being readable by anyone holding the token, which is worth having when the state carries internal identifiers. Second, the same bindings still have to be inside the plaintext and still have to be checked on receipt, and not one row of the residual table changes. Confidentiality is not single-use. The specification's own example writes the field as a placeholder reading "AEAD-protected blob".

    The encoding is explicitly your business: Servers are free to encode the state in any format (e.g. base64-encoded JSON, encrypted JWT, serialized binary).

    Notes on the construction