Skip to content

SEP: HTTP Message Signing for MCP Client Authentication#2752

Open
njdawn wants to merge 8 commits into
modelcontextprotocol:mainfrom
njdawn:feature/sep-http-message-signing
Open

SEP: HTTP Message Signing for MCP Client Authentication#2752
njdawn wants to merge 8 commits into
modelcontextprotocol:mainfrom
njdawn:feature/sep-http-message-signing

Conversation

@njdawn

@njdawn njdawn commented May 19, 2026

Copy link
Copy Markdown

Summary

Adds a draft SEP proposing RFC 9421 HTTP Message Signatures as an optional, additive proof-of-possession client-authentication mechanism for MCP. It supersedes the dormant SEP-1415 with the technical-feedback fixes from that thread, and is complementary to (not in competition with) SEP-1932 (DPoP).

Why this proposal

MCP currently authenticates clients with bearer tokens (OAuth access tokens, API keys, MCP-Session-Id). That model has well-known structural weaknesses that the rest of the web has been migrating away from:

  1. Secrets are re-exposed to the server on every request. A signed request, by contrast, never sends the private key — the server sees only signatures it cannot replay or repurpose.
  2. No protection against TLS-terminating intermediaries. CDNs, reverse proxies, and corporate egress inspection see bearers in plaintext. A signed request is verifiable end-to-end above TLS termination.
  3. No request integrity. Bearer auth says nothing about the body; a signature over (@method, @target-uri, content-digest, mcp-protocol-version, mcp-session-id) binds the credential to this specific request.
  4. No replay protection. Captured bearers are valid until rotation; created + nonce collapse that window to single-digit minutes.
  5. No verifiable client identity. The signature tag provides a stable, unforgeable client identifier across key rotations.
  6. Principle of least exposure. Stops handing clients access tokens really intended for server↔resource-server communication.

The acute use case is wallet-based and high-stakes authentication — MCP servers brokering on-chain transactions, payments, secret stores, or production database writes. A leaked bearer in those settings drains a wallet or exfiltrates secrets with no recovery. Proof-of-possession lets the wallet key itself become the natural MCP client credential.

What's different from SEP-1415

The 1415 thread surfaced a series of RFC-compliance issues that this draft addresses explicitly, with citations:

Issue from 1415 thread Raised by Resolution in this SEP
alg parameter creates substitution-attack risk @jricher Prohibited (§2.2). Algorithm derived from JWK kty + crv per RFC 9421 §7.3.5
created listed as a signed component @jricher, @dadrus Corrected — created is a signature parameter (§2.2), not a component
Custom JSON-RPC error envelope for algorithm negotiation @dadrus Replaced with standard Accept-Signature response header (§3.3)
Server-side full-signature cache @dadrus Replaced with (tag, nonce) cache (§2.3)
No init-less story (vs SEP-1372) @fsiyavud Signature-Agent header (draft-meunier-http-message-signatures-directory) for first contact (§2.6.1)
MCP-Protocol-Version not in signature @fsiyavud Required signed component (§2.1)
No stable identity across key rotation @dadrus RFC 9421 tag parameter as stable client ID (§2.5); signatures/rotateKey method (§3.4)
Ambiguity about session-bound vs stateless @spacewander Both flows specified: cnf in clientInfo for sessioned, Signature-Agent for stateless (§2.6)

Relationship to in-flight work

Status

  • Filed without a sponsor; seeking sponsor per the PR-based SEP workflow.
  • Reference implementations (server-side Rust middleware on rmcp; client-side TypeScript fetch wrapper on the official SDK) planned and will be linked once a sponsor advances this to In-Review.

Checklist

  • I have read the MCP Documentation
  • SEP follows the format defined in SEP-1850
  • Sponsor assigned (seeking)
  • Reference implementation linked (planned, see §Reference implementation)

cc: potential reviewers from the closed 1415 thread who provided technical feedback: @dickhardt @jricher @dadrus @fsiyavud @spacewander @covia-dev — your input shaped this draft.

njdawn and others added 2 commits May 19, 2026 15:42
Adds a draft SEP proposing RFC 9421 HTTP Message Signatures as an
optional, additive proof-of-possession client-authentication mechanism
for MCP. Supersedes the dormant SEP-1415 with technical-feedback fixes:
correct RFC 9421 component/parameter semantics, prohibition of the
`alg` parameter, Accept-Signature negotiation, nonce-based replay
protection, Signature-Agent key distribution for init-less mode, and a
stable-identity `tag` that survives key rotation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per SEP-1850, rename the file from 0000- to 2752- now that the PR
number is known, and update the header to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Reformatted the SEP markdown per prettier (italics, table spacing).
- Ran `npm run generate:seps` to produce docs/seps/2752-*.mdx and
  update docs/seps/index.mdx + docs/docs.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@njdawn njdawn requested review from a team as code owners May 19, 2026 21:01
njdawn and others added 3 commits May 20, 2026 14:09
Restructures Client behavior into §4.1 (Signer abstraction) and §4.2
(Requirements). Explicitly states that RFC 9421 doesn't constrain
*where* signing happens, and enumerates valid signer locations:
in-process non-extractable, OS keychain, ssh-agent-style local broker
(ssh-agent / gpg-agent / authsome), hardware tokens, remote KMS.

Addresses the threat model raised by @zriyansh in discussion modelcontextprotocol#2797:
PoP at the protocol layer does not by itself defend against an LLM
that can be coerced to print env vars or shell history, but pairing
PoP with an out-of-process signer (where the agent process never
holds the key) does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chopmob-cloud

This comment was marked as spam.

@dadrus

dadrus commented May 31, 2026

Copy link
Copy Markdown

Thank you @njdawn for picking this topic up again. Regarding the identity and the key management of the client: Currently, there are multiple concurrent RFC drafts in place, addressing this topic.

One of these is the HTTP Message Signatures Directory from the Web Bot Auth WG, which I mentioned in the closed SEP and which you adopted here.

Another option comes from the WIMSE WG. In the WIMSE Workload-to-Workload Authentication with HTTP Message Signatures, WIMSE profiles HMS and introduces a Workload-Identity-Token which carries the actual key material for signature verification purposes, and also bind the identity of the workload to that key material.

And yet another option comes from @dickhardt in the recent AAuth draft.

All of them have their strength and weaknesses. E.g. AAuth and Web Bot Auth directory do not require a central identity management system, but the WIMSE approach does. AAuth is an extension for OAuth2, while the others are not. Etc.

Given that, it would be helpful to more explicitly position this proposal:

  • Which of these directions are we aligning with (if any)?
  • Are we intentionally optimizing for one of the trade-offs (e.g., decentralized key discovery and identity managment vs. centrally managed identities)? Later is imo problematic in the agentic world.
  • Or is the goal to remain compatible with multiple approaches? So, to define a more pluggable model here, so that different mechanisms can be supported without locking us into one approach too early?

@dadrus

dadrus commented May 31, 2026

Copy link
Copy Markdown

@chopmob-cloud

RFC 9421 as client authentication is the right call. The bearer-token structural weaknesses you've listed match what we see in production: TLS-terminating intermediaries (CDNs, cloud load balancers) see bearers in plaintext, and captured bearers are replayable indefinitely.

While this is true, this issue can also be addressed by making use of DPoP or MTLS based PoP. None of these two are capable of establishing a separate identity for the agent. There are only about PoP. The aim of the closed SEP, and in my understanding, also of this one, is to go beyond pure PoP, and to enable each party in the chain to understand the identity and authority not only of the last caller, but of every actor in the call chain. This IMO will ultimately allow building some sort of PIC model to defeat the confused deputy problem everybody is looking for.

@chopmob-cloud

This comment was marked as spam.

@njdawn

njdawn commented Jun 7, 2026

Copy link
Copy Markdown
Author

Thanks @dadrus — this is the right question to settle before asking for a sponsor, and your "centralized identity is problematic in the agentic world" point is the one I want to design around.

Position: pluggable on identity & key discovery, opinionated only on the wire profile. This SEP should specify how a request is signed and verified — covered components, the alg-prohibition, created/nonce replay, keyid == JWK thumbprint, the tag stable-identity rule — and should not hard-wire where the verifying identity comes from. Those are separable, and conflating them is what would lock us in too early.

Concretely, I'd like to reframe §2.6 ("Key distribution") as an extensible set of key-resolution methods over the same signing profile:

  • Signature-Agent (Web Bot Auth HMS Directory) — MUST-implement baseline. Decentralized, no central identity authority, works init-less (SEP-1372). This is the default precisely because it doesn't require a central registry.
  • cnf in clientInfo — session-bound convenience for the initialized path.
  • WIMSE Workload-Identity-Token and Hardt's AAuth assertion — registered OPTIONAL methods. Both can ride in the keyid-resolution slot without touching the signing base: WIMSE for deployments that do want centrally-managed workload identity (enterprise), AAuth for the OAuth-extension path. A server advertises which methods it accepts via the capabilities block (§3.1).

So the answer to your three options is "the third, deliberately": multiple approaches, but with a decentralized default rather than a neutral one — Signature-Agent is the floor, central-identity mechanisms are opt-in for the deployments that need them.

On the call-chain identity / confused-deputy thread with @chopmob-cloud: I agree that's the deeper prize, but I think it belongs above this PoP layer, not inside it — the SEP scopes authorization out (§"What this SEP does not address"). What this SEP should do is leave the right hooks: the stable tag plus the signed content-digest are exactly what a PIC / receipt-chain layer binds to per actor. I'd rather under-claim here and let a follow-up (or WIMSE) own multi-hop attribution than pull full call-chain attestation into a client-auth SEP.

I've added an "Identity model & extensibility" subsection making the key-resolution-method registry explicit, with Signature-Agent as the MUST baseline and WIMSE/AAuth as registered optional methods. Does decentralized-default-with-registered-central-options land where you'd want it?

@njdawn

njdawn commented Jun 7, 2026

Copy link
Copy Markdown
Author

Thanks @chopmob-cloud — the re-serialization failure mode is real and worth addressing explicitly: RFC 9421 content-digest is over the exact octets (RFC 9530), so an intermediary that re-serializes the JSON (key reorder, whitespace, unicode normalization) invalidates the signature even though the semantic payload is unchanged.

I want to keep the baseline byte-exact and add JCS as an opt-in, rather than swap the default:

  • Baseline (MUST): RFC 9530 byte digest. It's what RFC 9421+9530 standardize and what off-the-shelf proxies and signing libraries already emit, and for the common MCP topology — JSON-RPC over a single Streamable-HTTP hop to the server — a re-serializing intermediary isn't in the path. Mandating JCS for everyone imports a canonicalization dependency with its own footguns (number/exponent canonicalization, unicode) that most deployments don't need.
  • Optional, negotiated: a JCS (RFC 8785) content digest. For topologies that do relay through re-serializing intermediaries (your PEF/receipt layer is the motivating case), an additional covered component computed over the JCS-canonicalized payload, advertised + selected via the capabilities block (§3.1) so both sides agree before relying on it. Baseline stays byte-exact; the canonical-digest variant is there where the topology demands it.

I've added this as an optional negotiated component (§2.7) and cited draft-hopley-x402-payment-evidence-frame as prior art for the covered-components design. Your mcp-session-id point is already in the baseline (§2.1) for exactly the session-replay reason you flagged. Would a negotiated optional JCS digest cover the re-serialization case you're seeing in production?

…edback)

Addresses the review thread:

- §2.6 reframed as an extensible registry of key-resolution methods over a
  fixed signing profile, with an "Identity model & extensibility" subsection.
  Signature-Agent (Web Bot Auth) is the MUST baseline (decentralized default);
  WIMSE Workload-Identity-Token and AAuth are registered optional methods for
  centrally-managed identity (per @dadrus). Call-chain attribution stays out of
  scope; tag + content-digest are the hooks a PIC/receipt layer binds to.
- §2.7 adds an optional, capability-negotiated JCS (RFC 8785) content digest for
  re-serializing relay topologies, with the RFC 9530 byte digest as the MUST
  baseline (per @chopmob-cloud); cites the x402 payment-evidence-frame draft.
- §3.1 capabilities gain keyResolution + contentDigest negotiation fields.
- References: RFC 8785, WIMSE, AAuth, x402 PEF.
@njdawn

njdawn commented Jun 7, 2026

Copy link
Copy Markdown
Author

Seeking a sponsor / Auth WG agenda slot

Per the PR-based SEP workflow this needs a core-maintainer sponsor to advance Draft → In-Review. The reviewer feedback above is now resolved in-text, and a reference implementation exists, so I'd like to put it in front of the Auth WG.

This draft was written specifically to clear the two concerns the Auth WG raised against SEP-1415:

  1. "Forces all servers into a stateful init()-as-registration pattern / blocks stateless implementations." — Addressed. The Signature-Agent header (§2.6.1, Web Bot Auth HMS Directory) is the baseline and carries the key on every request, so verification is fully stateless and init-less (compatible with SEP-1372). The cnf-at-initialize binding (§2.6.2) is an optional convenience, not the required path — no client-registration step is imposed.
  2. "Preference for additive-to-OAuth, not an alternative." — This SEP is additive and OAuth-agnostic: it layers PoP on top of existing bearer/OAuth without replacing it (requiresSignature: false by default), and is explicitly complementary to DPoP (SEP-1932: DPoP Profile for MCP #1932) — DPoP for the OAuth-bound case, RFC 9421 for the non-OAuth / wallet / API-key case. They ship together; this is not a pivot on the authorization spec.

Reference implementation (the §"Reference implementation" deliverable): server-side RFC 9421 verification as an axum middleware on the rmcp Streamable-HTTP transport — structured-field parse, Signature-Agent data: JWKS resolution, keyid == RFC 7638 thumbprint, created window, (tag, nonce) replay cache, content-digest match, signature-base reconstruction, Ed25519 verify, 401 + Accept-Signature negotiation, with unit tests. Additive: unsigned requests pass through. It runs in production behind a wallet-brokering MCP server (the acute use case in §Motivation). I'll link the public repo once it's mirrored; happy to walk through it live.

@D-McAdams — since you're shepherding the DPoP profile (#1932) and this is the complementary non-OAuth half of the same PoP story, would you (or another Auth WG maintainer — @aaronpk?) be open to sponsoring, or to a slot on the next Auth WG agenda to discuss? I can present the stateless Signature-Agent flow and the reference impl.

@D-McAdams

Copy link
Copy Markdown

Hey, saw the mention, took a read through, and wanted to give some context. I think you'll run into significant questions with this approach.

On proof-of-possession: The Auth WG debated this space, including HTTP Message Signatures, and landed on DPoP. The bar for revisiting that decision is high. (The MCP authorization spec is intentionally opinionated. Having two competing PoP mechanisms in the core spec creates implementation burden and ambiguity.)

On the broader authentication model: The proposal alludes to a broader topic of whether MCP can support a non-OAuth authentication model, such as with decentralized identity and crypto wallet authentication. The working group has made a deliberate choice to standardize on OAuth. Autonomous agent authentication, including the workload-to-workload cases, is covered by OAuth workload identity federation (SPIFFE, WIMSE, etc.).

Overall, a non-OAuth authentication model will face significant resistance in the Auth WG. If there's a community with interest in wallet-native or decentralized-identity MCP flows, another path that doesn't require changing the core spec is MCP's extension model (https://modelcontextprotocol.io/extensions/overview). That's likely a faster and more realistic path for this work.

@njdawn

njdawn commented Jun 7, 2026

Copy link
Copy Markdown
Author

@D-McAdams thanks for feedback, very helpful.

Do you have 15 mins to chat? Will show you specific set of use-cases I (+crypto/wallet) community interested in + demo, and get your take on what best to do to proceed. Email is nprasad@moonpay.com

@njdawn

njdawn commented Jun 7, 2026

Copy link
Copy Markdown
Author

That's a fair call, and the extension path makes sense to me — thanks for pointing it there rather than just closing the door.

I'd like to take you up on it. Reframing this as an Extensions Track proposal (per SEP-2133) rather than a core change actually resolves the two concerns directly: it's opt-in and disabled by default, so there's no second PoP mechanism imposed on the core spec, and it touches nothing in the authorization spec — it negotiates purely via capabilities.extensions["io.modelcontextprotocol/http-message-signatures"], with no core schema change. It sits naturally in ext-auth alongside OAuth Client Credentials (which already does client-signed JWT assertions), and I'm happy to keep it explicitly complementary to DPoP — DPoP for the OAuth-bound path, this for the non-OAuth / API-key / wallet-native case.

I can satisfy the Extensions-Track bar: there's already a reference implementation in an official SDK — an axum-on-rmcp (Rust) verifier — and a TypeScript client wrapper on @modelcontextprotocol/sdk to follow.

Two asks, whichever is easiest for you:

  1. Would the MCP Authorization WG be the right home to associate this with, and would you be open to greenlighting an experimental-ext-http-message-signatures repo under it to incubate? Happy to do the legwork and bring the reference impl.
  2. If a short WG agenda slot is easier than back-and-forth here, I'll take that.

I'll rework this PR into Extensions-Track form (identifier, capabilities.extensions negotiation, dropping the core-schema section) so it reflects the right shape regardless.

Per Auth WG feedback (DPoP is the core PoP mechanism; non-OAuth auth faces
resistance in core), repackage this as the opt-in MCP extension
`io.modelcontextprotocol/http-message-signatures` rather than a core change:

- Type: Standards Track -> Extensions Track; add Extension ID + Working Group.
- §3.1: negotiate via `capabilities.extensions` (SEP-2133) instead of a new core
  `ServerCapabilities.authentication` field; settings object documented; opt-in,
  disabled by default, graceful fallback to core bearer/OAuth.
- §2.6.2: carry `cnf` in the extension settings object, not core `clientInfo`.
- §5: "Core schema impact: none" — drop the core schema changes entirely;
  `signatures/rotateKey` scoped/namespaced to the extension.
- Abstract + relationship table: framed as additive, complementary to DPoP
  (SEP-1932), sibling to OAuth Client Credentials in ext-auth.
- New "Extension governance (SEP-2133)" section: identifier, ext-auth home,
  experimental incubation under the MCP Authorization WG, Apache-2.0, reference
  impl satisfies the Extensions Track prerequisite.
- index + References updated.
@localden localden added SEP draft SEP proposal with a sponsor. labels Jun 8, 2026
@localden localden added proposal SEP proposal without a sponsor. and removed draft SEP proposal with a sponsor. labels Jun 8, 2026
@dadrus

dadrus commented Jun 8, 2026

Copy link
Copy Markdown

@njdawn: Thank you very much for the detailed answer.

Does decentralized-default-with-registered-central-options land where you'd want it?

Yep 😀. I tried to describe the options in a neutral way. But indeed, personally, I prefer a pluggable approach and am also in favor of a decentralisation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

proposal SEP proposal without a sponsor. SEP

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants