- Status: Draft
- Type: Extensions Track
- Extension ID:
io.modelcontextprotocol/http-message-signatures - Working Group: MCP Authorization
- Created: 2026-05-19
- Author(s): Neeraj Prasad (@njdawn)
- PR: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2752
Imported from upstream. Proposed as modelcontextprotocol#2752 by @njdawn on 2026-05-19, where it was still open on 2026-09-25. Accepted into omcp on its technical merit. The text is the author's; only the status, the sponsor field, and documentation links were changed on import.
Reviewed 2026-09-25 — not yet accepted. Held as a draft because no prototype a reviewer can run is cited. Acceptance needs a prototype a reviewer can run, with its setup and the data to run it; Final also needs conformance tests and the reference documentation chapter.
Abstract
This SEP defines an optional, additive client-authentication mechanism for MCP based on RFC 9421 HTTP Message Signatures. It enables MCP servers to authenticate clients with cryptographic proof-of-possession of a private key — without requiring the client to ever transmit a long-lived secret over the wire and without modifying any existing OAuth or MCP-Session-Id flow.
This SEP is filed as an Extensions Track proposal per SEP-2133: it is not a core-protocol change. It is the optional, opt-in extension io.modelcontextprotocol/http-message-signatures (home: the ext-auth repository), negotiated through capabilities.extensions, disabled by default, with no change to the core schema. It is complementary to — not a replacement for — OAuth and DPoP (SEP-1932): DPoP for the OAuth-bound path, this extension for the non-OAuth / API-key / wallet-native case.
The proposal is a refinement of the now-dormant SEP-1415, incorporating the technical feedback from that thread:
- Signed-component list and signature parameters corrected to comply with RFC 9421 §2 and §3.
- The
algsignature parameter is prohibited; algorithm is derived from key material (per RFC 9421 §3.2 and the security considerations of §7). - Algorithm negotiation uses the standard
Accept-Signatureresponse field, not a custom JSON-RPC error envelope. - Replay protection uses RFC 9421's
nonce+createdmechanism rather than mandatory server-side signature caching. - Key distribution supports both an
initialize-bound flow and an init-less flow compatible with SEP-1372 (MCP-Protocol-Version-header bootstrap), via theSignature-Agentheader from draft-meunier-http-message-signatures-directory. - A signature
tagis defined as the stable client identifier so that keys can be rotated without losing identity continuity.
Motivation
Why MCP needs cryptographic client authentication
Today MCP authenticates clients with bearer tokens (OAuth access tokens, API keys, MCP-Session-Id). A bearer is, by definition, whoever holds the string is the client. That model has well-understood structural weaknesses that the rest of the web stack has been migrating away from:
-
Secrets are continuously re-exposed to the server. Every request hands the full credential back. A compromised, malicious, or merely curious server endpoint sees the material the client uses to authenticate everywhere it presents that token. Cryptographic authentication inverts this: the private key never leaves the client. The server only ever sees signatures over specific requests — values that cannot be replayed or repurposed.
-
No protection against man-in-the-middle / intermediary trust. Bearer tokens rely entirely on TLS for confidentiality. Anything that legitimately terminates TLS along the path — CDNs, reverse proxies, corporate egress inspection, logging middleware — sees the bearer in plaintext and can act as the client until rotation. A signed request is verifiable end-to-end: an intermediary can read the request but cannot mint a new signed request without the private key. This is the property mTLS provides at the transport layer, achievable above TLS termination, which is where most production deployments actually need it.
-
No request integrity. Bearer auth says nothing about the body. A signature over
(@method, @target-uri, content-digest, mcp-session-id, mcp-protocol-version)binds the credential to this specific request: changing the body, method, URI, session ID, or protocol version invalidates the signature. Tampering by a compromised proxy or malicious middlebox is detectable rather than silent. -
No replay protection. A captured bearer can be replayed indefinitely until rotation, against any endpoint, with any payload. Signatures with
created+nonceconstrain each signed request to a freshness window (default 300s) and a single use, collapsing the leak blast radius from "until rotated" to single-digit minutes. -
No verifiable client identity. The bearer model gives the server no cryptographic way to answer "is this the same client instance I talked to before?" The signature
tag(this SEP) andkeyid(RFC 9421) give a stable, unforgeable client identifier without requiring a heavyweight OAuth dance — enabling per-client rate limits, scoped permissions, audit logs, and anomaly detection. -
Principle of least exposure. Current OAuth flows leave MCP clients in possession of access tokens really intended for server↔resource-server communication. Proof-of-possession lets the spec stop handing clients credentials they didn't need in the first place.
The same arguments drove the web's adoption of WebAuthn over passwords, DPoP over bare bearer tokens in OAuth 2.1, and HTTP Message Signatures in payment and federation protocols. MCP is currently the outlier.
Where this matters most: wallet-based and high-stakes authentication
A growing class of MCP servers brokers access to wallets and other high-value credentials on behalf of agents — signing EVM/Solana transactions, executing swaps, retrieving API keys, performing on-chain transfers, writing to production databases. For these servers the gaps above stop being theoretical:
- A leaked bearer drains a wallet or exfiltrates secrets up to whatever policy limit exists. There is no recovery — the operation is on-chain or the data is out.
- Without request-body binding, an intercepted approval for
swap 100 USDC → ETHis indistinguishable from an attacker minting a freshtransfer everything → attackeragainst the same token. - Per-client policies (code or LLM-based) are predicated on "this is client X" — which today reduces to "whoever holds the bearer."
Proof-of-possession solves all three: the request body is signed, the client is the keyholder, the bearer becomes worthless on its own. As a corollary, the wallet key itself becomes the natural MCP client credential — the agent's authenticator is the same key material it already uses to sign on-chain.
This generalizes to any MCP server fronting non-revocable side effects: payments, on-chain operations, production writes, secret stores. As more financial and agentic infrastructure integrates over MCP (e.g. via Grok and Claude.ai connectors), the spec needs a proof-of-possession story that ships.
Relationship to existing proposals
| Proposal | Status | Mechanism | Relationship |
|---|---|---|---|
| SEP-2133 | Final | Extensions framework | This proposal is an Extensions Track extension under 2133 — not a core change |
| SEP-1415 | Dormant | RFC 9421 | Picks up 1415's mechanism with the technical-feedback fixes, repackaged as an extension |
| SEP-1932 | Open (PR) | RFC 9449 (DPoP) | Complementary — DPoP for the OAuth-bound path, this for non-OAuth / wallet. No overlap in core |
| SEP-1036 | Final | URL elicitation | Orthogonal — concerns user data; this concerns client identity |
| SEP-1372 | Open | Init-less MCP | This extension works in both initialized and init-less modes |
| ext-auth (OAuth Client Credentials) | Final | OAuth 2.0 CC | Sibling extension in the same repo; this adds PoP for the non-OAuth case |
As an opt-in extension this is additive and disabled by default. Servers and clients that do not negotiate it are unaffected.
Specification
1. Conformance language
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, MAY are to be interpreted as described in BCP 14 when, and only when, they appear in all capitals.
2. Profile of RFC 9421
2.1 Signed components
Every signed MCP request MUST sign the following components (RFC 9421 §2):
| Component | Source | Notes |
|---|---|---|
@method |
Derived | HTTP method |
@target-uri |
Derived | Full request URI |
content-digest |
HTTP field | SHA-256 of body per RFC 9530. Required even for empty bodies (digest of empty input). |
mcp-protocol-version |
HTTP field | MUST be included when present (per SEP-1372 init-less mode). |
mcp-session-id |
HTTP field | MUST be included for all post-initialize requests. MUST NOT be included on the initialize request itself or on init-less first requests, since no session is bound yet. |
Servers MUST reject signatures whose Signature-Input does not cover all applicable components above.
2.2 Signature parameters
The following RFC 9421 signature parameters MUST be present:
created: Unix epoch seconds at signature creation time. Servers MUST reject signatures whosecreatedis more than300seconds in the past or more than60seconds in the future, accounting for clock skew.nonce: A per-request unique value. MUST be at least 96 bits of cryptographic randomness, base64url-encoded.keyid: The JWK Thumbprint per RFC 7638 of the public verification key.tag: The stable client identifier (see §2.5). For first contact, equalskeyid; thereafter remains stable across key rotation.
The alg parameter MUST NOT be sent and MUST be ignored if present. The signature algorithm is derived from the public key material (see §2.4), in line with RFC 9421 §7.3.5's guidance against alg-substitution attacks.
2.3 Replay protection
Servers MUST maintain a nonce cache scoped to (tag, time-window) and MUST reject any request whose (tag, nonce) tuple has been seen within 2 × max_clock_skew + freshness_window (default: 660 seconds).
Servers SHOULD NOT cache full signature values; the nonce-based check is sufficient and is constant-size per client.
2.4 Key material
Public keys are conveyed as JWKs per RFC 7517. Implementations MUST support at least the following curve/algorithm pairing:
- Ed25519 (
kty: "OKP", crv: "Ed25519") — RECOMMENDED default. Algorithm:ed25519per RFC 9421 §3.3.6.
Implementations MAY additionally support:
- ECDSA P-256 (
kty: "EC", crv: "P-256") withecdsa-p256-sha256. - ECDSA P-384 (
kty: "EC", crv: "P-384") withecdsa-p384-sha384.
The signature algorithm is determined by the JWK kty + crv combination. Servers MUST NOT accept a signature whose computed algorithm does not match the algorithm registered for the presented keyid.
The keyid value MUST equal the JWK Thumbprint (RFC 7638) of the presented JWK; servers MUST reject any mismatch.
2.5 Stable client identity via tag
The tag parameter (RFC 9421 §2.3) carries a stable client identifier. It is opaque to the protocol and MUST be:
- Generated by the client on first contact (one-time, persistent per logical client instance).
- Cryptographically bound to the initial key by being signed in the first request that introduces the key.
- Stable across key rotations (a rotation request signs a new key under the old key + same
tag; see §3.4).
This separates identity (the tag) from proof material (the key), letting servers retain audit trails, rate-limit buckets, and per-client policies through key changes.
2.6 Key distribution
Identity model & extensibility
This SEP is opinionated on the wire profile (§2.1–2.5: covered components, the alg prohibition, replay, keyid == JWK Thumbprint, the stable tag) and pluggable on identity and key discovery. How a request is signed and verified is fixed; where the server learns the verifying key — and any richer identity attached to it — is an extensible set of key-resolution methods over that same signing base.
Conforming servers MUST implement the Signature-Agent baseline (§2.6.1) and MAY accept additional registered methods, advertising the set they accept via the capabilities block (§3.1). The design intent is a decentralized default — no central identity authority required — with centrally-managed identity available opt-in for deployments that want it:
| Method | Identity model | Status |
|---|---|---|
Signature-Agent (Web Bot Auth HMS Directory) |
Decentralized; key self-published | MUST (baseline) |
cnf in clientInfo |
Session-bound, decentralized | MAY |
WIMSE Workload-Identity-Token |
Centrally-issued workload identity | MAY (registered) |
| AAuth assertion | OAuth-extension identity | MAY (registered) |
Identity beyond "the keyholder" — multi-hop / call-chain attribution to defeat the confused-deputy problem — is out of scope (§"What this SEP does not address"); the stable tag and the signed content-digest are the hooks a PIC / receipt-chain layer (or WIMSE) binds to per actor.
2.6.1 Signature-Agent header (RECOMMENDED baseline, works in all modes)
Per draft-meunier-http-message-signatures-directory, clients MAY include a Signature-Agent header on any signed request:
Signature-Agent: "data:application/http-message-signatures-directory+json;base64,<base64url(JWKS)>"
For desktop / local clients that cannot host a JWKS URL, the data: form is REQUIRED. For hosted clients, the URL form (Signature-Agent: "https://client.example/.well-known/http-message-signatures-directory") is permitted.
This is the only key-distribution method that works for init-less first contact (SEP-1372).
2.6.2 cnf in the extension settings (initialize-bound mode)
Clients that perform initialize MAY additionally bind a key by carrying a cnf (confirmation) member in this extension's settings object under capabilities.extensions (§3.1) — not in core clientInfo, so no core schema is touched:
{
"method": "initialize",
"params": {
"capabilities": {
"extensions": {
"io.modelcontextprotocol/http-message-signatures": {
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "3TQjAzEPW-sE81J4eWUuI2ZQCRJKELwdco_cJa7e9pM"
}
}
}
}
},
"clientInfo": { "name": "example-client", "version": "1.0.0" }
}
}
The initialize request itself MUST be signed with the corresponding private key. The server binds (mcp-session-id, jwk-thumbprint, tag) for the lifetime of the session.
2.6.3 Registered optional methods (WIMSE, AAuth)
Deployments that need centrally-managed or federated identity MAY carry it in the key-resolution slot without changing the signing base:
- WIMSE
Workload-Identity-Token(draft-ietf-wimse-http-signature) — the token carries the verification key material and binds it to a workload identity. Suited to enterprise / multi-workload deployments with an identity authority. - AAuth assertion (draft-hardt-aauth-protocol) — for deployments already extending OAuth 2.
A server is not required to implement these; it advertises which it accepts via §3.1. In all cases the signing profile (§2.1–2.5) is unchanged — only keyid resolution differs.
2.7 Optional canonical (JCS) content digest
The baseline content-digest (§2.1) is the RFC 9530 digest over the exact request octets. An intermediary that re-serializes the JSON body (key reorder, whitespace, unicode normalization) invalidates that digest even when the payload is semantically unchanged — relevant for receipt / evidence-relay topologies (e.g. draft-hopley-x402-payment-evidence-frame).
For those topologies, servers and clients MAY negotiate an additional covered component carrying a digest computed over the JCS-canonicalized (RFC 8785) payload, advertised + selected via the capabilities block (§3.1) so both sides agree before relying on it. The byte-exact content-digest remains the MUST baseline; the JCS variant is purely additive, for deployments whose path includes re-serializing relays. JCS is not mandated by default — it imports canonicalization edge cases (number/exponent form, unicode) that the common single-hop MCP topology does not need.
3. Server behavior
3.1 Capability advertisement
This extension is negotiated through the capabilities.extensions map defined by SEP-2133 — not through a new core ServerCapabilities field. The server advertises the extension identifier with a settings object in the initialize response; the client does the same in the request:
{
"capabilities": {
"extensions": {
"io.modelcontextprotocol/http-message-signatures": {
"algorithms": ["ed25519"],
"requiresSignature": false,
"keyResolution": ["signature-agent", "cnf"],
"contentDigest": ["rfc9530"]
}
}
}
}
Settings object (an empty {} is permitted and means "supported, with defaults"):
algorithms— accepted algorithms in server preference order ("ed25519"baseline;"ecdsa-p256-sha256"/"ecdsa-p384-sha384"optional).requiresSignature— whentrue, the server rejects unsigned requests; defaults tofalse.keyResolution— accepted key-resolution methods (§2.6);"signature-agent"is the baseline,"cnf"/"wimse"/"aauth"optional.contentDigest— accepted content-digest profiles (§2.7); defaults to["rfc9530"].
Per SEP-2133 the extension is disabled by default and requires explicit opt-in. A server that sets requiresSignature: true MUST reject unsigned requests with HTTP 401 Unauthorized and an Accept-Signature header (§3.3); otherwise it MUST gracefully fall back to core bearer/OAuth behavior for clients that do not negotiate the extension.
3.2 Verification flow
On receiving a signed request, the server MUST:
- Parse
Signature-InputandSignatureheaders (RFC 9421 §4.2). - Resolve the public key for the
keyid:- First, consult the session-bound key (if a session exists).
- Otherwise, parse
Signature-Agent. - Otherwise, reject with
401.
- Verify the
keyidequals the JWK Thumbprint of the resolved key. - Verify
createdis within[now - 300s, now + 60s]. - Verify
(tag, nonce)has not been seen. - Verify all required components from §2.1 are present in
Signature-Input. - Verify the signature cryptographically using the algorithm derived from the key (§2.4).
- Record
(tag, nonce)in the replay cache before responding.
Failures in steps 1–7 MUST produce HTTP 401 Unauthorized with Accept-Signature (§3.3) and, optionally, a JSON-RPC error body describing which check failed.
3.3 Algorithm and component negotiation via Accept-Signature
When rejecting a signed request for protocol-level reasons (missing component, unsupported algorithm, unsigned-but-required), the server MUST include an Accept-Signature header (RFC 9421 §5.1) describing what it expects:
HTTP/1.1 401 Unauthorized
Accept-Signature: sig1=("@method" "@target-uri" "content-digest" "mcp-protocol-version" "mcp-session-id");created;nonce;tag="mcp-server.example"
This replaces the custom JSON-RPC error envelope proposed in SEP-1415, keeping signature-layer negotiation at the signature layer.
When multiple algorithms are acceptable, the server MUST advertise them via the capabilities mechanism (§3.1) and MAY repeat the Accept-Signature header per algorithm.
3.4 Key rotation
A client rotates its key by sending a signatures/rotateKey request signed with both the old and new keys (two Signature entries: sig-old and sig-new):
{
"method": "signatures/rotateKey",
"params": {
"newKey": { "kty": "OKP", "crv": "Ed25519", "x": "..." }
}
}
The server MUST verify both signatures, that the tag matches, and that the new keyid equals the new JWK's thumbprint. On success the server replaces the bound key and SHOULD retain audit history.
4. Client behavior
4.1 Signer abstraction
This SEP does not constrain where signing happens. RFC 9421 only requires that the request carries a valid Signature under the public key the server has bound for the client. Conforming client implementations SHOULD treat signing as an abstract operation that can be satisfied by any of the following, interchangeably:
- In-process key material held in a non-extractable form (e.g. WebCrypto
SubtleCryptowithextractable: false). - OS-managed key store (macOS Keychain, Windows DPAPI / CNG, Linux Secret Service / kernel keyring).
- An out-of-process local signer following an
ssh-agent-style pattern — the agent process delegates signing to a separate process over a Unix socket or named pipe, and never sees the private key itself. Reference instantiations includessh-agent,gpg-agent, hardware-backed wallet daemons, and projects like authsome. - Hardware tokens (YubiKey, Ledger, TPM, Secure Enclave).
- A remote KMS (AWS KMS, GCP KMS, HashiCorp Vault Transit) reached over an authenticated channel.
The out-of-process variants are explicitly recommended for deployments where the surrounding agent process executes untrusted LLM-generated code, since the model cannot exfiltrate key material it never held. PoP at the protocol layer does not by itself defend against an attacker who can coerce the agent process to print env vars or shell history; pairing PoP with an out-of-process signer does.
4.2 Requirements
Clients SHOULD:
- Generate one keypair per
(MCP server origin, logical client identity). - Persist private key material in one of the storage classes above; never in plaintext config.
- Generate a fresh
nonceper request from a CSPRNG. - Implement the
Accept-Signaturenegotiation loop (single retry on401if the server's requirements were unmet).
Clients MUST NOT:
- Reuse
noncevalues. - Send the
algsignature parameter. - Send the private key over the wire under any circumstances.
5. Core schema impact: none
As an Extensions Track extension (SEP-2133) this proposal makes no change to the core MCP schema. All negotiation rides the existing capabilities.extensions map (§3.1); the key material (cnf), algorithm set, key-resolution methods, and content-digest profile all live inside this extension's settings object — not in core ClientCapabilities, ServerCapabilities, or Implementation.
The only method the extension introduces is signatures/rotateKey (§3.4). It is scoped to this extension and MUST be routed only when the extension has been negotiated; implementations SHOULD treat it as namespaced to the extension identifier to avoid collisions.
6. Example signed request
POST /mcp HTTP/1.1
Host: server.example.com
Content-Type: application/json
Content-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
MCP-Protocol-Version: 2026-03-26
MCP-Session-Id: sess_abc123
Signature-Agent: "data:application/http-message-signatures-directory+json;base64,eyJrZXlzIjpbey4uLn1dfQ=="
Signature-Input: sig1=("@method" "@target-uri" "content-digest" "mcp-protocol-version" "mcp-session-id");created=1747641600;nonce="V8wA3pX2qF7nL1cR";keyid="7CECy79zsOm80SK3EgvHmsLM39PfUmbfSTxqOLhfgsM";tag="agent-instance-9f3e"
Signature: sig1=:LAH8BjcfcOcLojiuOBFWBo0hWDdMOGvAdEBM0h/MIRoNfj6Ae5OOKlEOUcKTOvXsyHkC5bV9D6m9QQDnR3QxDw==:
{"jsonrpc":"2.0","id":"req_42","method":"tools/call","params":{"name":"wallet_sign_swap","arguments":{}}}
Note that alg is absent (derived from the Ed25519 JWK), created and nonce are parameters (not components), and tag provides the stable client identity.
Rationale
Why RFC 9421 and not DPoP (SEP-1932)
DPoP (RFC 9449) and HTTP Message Signatures (RFC 9421) solve overlapping but distinct problems:
| Concern | DPoP | RFC 9421 (this SEP) |
|---|---|---|
| Bind credential to a specific request | Yes | Yes |
| Requires OAuth flow | Yes (sits inside one) | No |
| Works for non-OAuth MCP servers (API key, anonymous) | No | Yes |
| Signs full request including body | Optional, often not | Yes (content-digest required) |
| Industry adoption beyond OAuth | Low | Higher (federation, payments, Web Bot Auth) |
| Layered above TLS termination | Yes | Yes |
These should ship together: DPoP for the OAuth-bound case (where SEP-1932 already specifies it), RFC 9421 for the broader case where MCP servers want PoP without dragging in an OAuth dependency. Most wallet/payment use cases fall in the latter bucket.
Why fix SEP-1415 rather than replace it
The original 1415 proposal is fundamentally correct in mechanism — RFC 9421 is the right tool — but the discussion thread surfaced a series of small but blocking RFC-compliance bugs (incorrect signed-component lists, alg parameter usage, custom error envelopes, missing init-less story). This SEP keeps the mechanism and fixes each one explicitly, with citations to the underlying RFCs and the original thread comments.
Alternative: signed JSON-RPC envelopes
Considered and rejected. Signing the JSON-RPC payload alone misses HTTP-layer integrity (headers, URI, method), is incompatible with existing HTTP signing tooling and proxies, and forces every transport (stdio, WebSocket, Streamable HTTP) to re-invent a parallel scheme. RFC 9421 is HTTP-specific by design; stdio-only deployments are out of scope for this SEP and can be addressed in a follow-up if needed.
Alternative: mTLS
Considered and rejected as primary mechanism. mTLS is incompatible with the vast majority of MCP deployments that terminate TLS at a CDN or load balancer, and is impractical for local desktop clients. mTLS remains a valid additional layer where the deployment supports it.
Backward compatibility
Fully additive:
- Servers that do not implement this SEP ignore unknown headers (
Signature,Signature-Input,Signature-Agent,Content-Digest) and continue to function on existing bearer/OAuth auth. - Clients that do not implement this SEP send unsigned requests; servers that do not set
requiresSignature: trueaccept both. - The
cnffield onclientInfois optional; servers that don't understand it ignore it per the general capabilities-negotiation rules. - No existing message format changes.
Security considerations
Threat coverage
| Threat | Without this SEP | With this SEP |
|---|---|---|
| Bearer-token theft | Full client compromise | Bearer alone insufficient; signature still required |
| Request replay | Full replay surface | Bounded by 300s + nonce cache |
| Request tampering by proxy | Undetectable | Detected (content-digest covered) |
| Session-ID hijack | Full takeover | Hijacker lacks private key |
| Server-side credential exfiltration | Token usable anywhere | Only past signatures observable; key never exposed |
| TLS-terminating intermediary | Sees bearer | Sees signed requests it cannot forge |
New attack surface
- Compromised client key store. Same residual risk as any PoP scheme; mitigated by OS keychain / HSM storage and key rotation (§3.4).
- Nonce cache exhaustion / DoS. Bounded by per-
tagrate limiting and the small per-entry cost (a single(tag, nonce)tuple per accepted request, evicted after the freshness window). algsubstitution. Explicitly prevented by §2.2's prohibition of thealgparameter and algorithm derivation from key material.keyidconfusion. Prevented by §2.4 requiringkeyid == JWK Thumbprintand a single canonical thumbprint computation.
What this SEP does not address
- Authorization. Message signing proves the request came from the keyholder; it says nothing about what that keyholder is allowed to do. Authorization is the job of OAuth scopes, ext-auth, or per-tool policy.
- User identity. A signed request proves a specific client instance sent the request. Tying that to a human user remains the job of OAuth / OIDC.
- Server-side authentication of the server to the client. TLS / OOB pinning continues to handle this. A future SEP could profile RFC 9421 signed responses if there is demand.
Reference implementation
A reference implementation is planned in two parts:
- Server-side (Rust): an
axum::middlewarelayer wrapping thermcpStreamable-HTTP transport, verifying RFC 9421 signatures per §2 and emittingAccept-Signatureper §3.3. - Client-side (TypeScript): a fetch wrapper for the official
@modelcontextprotocol/sdkthat signs outbound requests using@waqas-shoukat/http-message-signaturesor equivalent, with Ed25519 keys held in the browser'sSubtleCryptonon-extractable form or a desktop OS keychain.
Both implementations will be linked from this SEP once Draft → In-Review transitions and a sponsor is assigned.
Open questions
- Should
signatures/rotateKeybe its own RPC method, or should rotation reuseinitializewithcnf? - Should the spec mandate Ed25519, or only RECOMMEND it (current text)?
- Should the freshness window (300s) be server-configurable and advertised in capabilities, or fixed by the spec?
- Is a future signed-response counterpart in scope for a separate SEP, or should we leave that to mTLS / TLS pinning indefinitely?
Extension governance (SEP-2133)
| Field | Value |
|---|---|
| Extension identifier | io.modelcontextprotocol/http-message-signatures |
| Home repository | modelcontextprotocol/ext-auth (on acceptance) |
| Incubation | experimental-ext-http-message-signatures (proposed), associated with the MCP Authorization Working Group |
| Working Group | MCP Authorization |
| License | Apache-2.0 |
| Stability | Experimental during incubation; opt-in, disabled by default |
Per SEP-2133 this is an Extensions Track proposal: reviewed by the Core Maintainers for inclusion as an official extension, and thereafter evolving independently under the ext-auth maintainers and the Authorization WG. It is OAuth-agnostic and complementary to DPoP (SEP-1932), sitting beside OAuth Client Credentials in ext-auth. A reference implementation in an official SDK is provided (see Reference implementation), satisfying the Extensions Track prerequisite. Breaking changes will use a new identifier (e.g. io.modelcontextprotocol/http-message-signatures-v2); non-breaking changes are negotiated through the settings object (§3.1).
References
- RFC 9421 — HTTP Message Signatures
- RFC 9530 — Digest Fields
- RFC 9449 — DPoP
- RFC 7517 — JSON Web Key
- RFC 7638 — JWK Thumbprint
- RFC 8785 — JSON Canonicalization Scheme (JCS)
- draft-meunier-http-message-signatures-directory-03
- draft-ietf-wimse-http-signature — WIMSE Workload-to-Workload Authentication
- draft-hardt-aauth-protocol-02 — AAuth
- draft-hopley-x402-payment-evidence-frame — Payment Evidence Frame (covered-components prior art)
- SEP-2133 — Extensions framework (this proposal is an Extensions Track extension under it)
- SEP-1415 — original (dormant) proposal
- SEP-1932 — DPoP profile
- SEP-1372 — init-less MCP