MCP Agent Identity: One Spec Shipped, Three Still Open

The MCP roadmap names four agent identity workstreams. One has shipped, and it authenticates the employee behind the agent rather than the agent itself.

The Model Context Protocol roadmap published on August 22 names agent identity and enterprise-ready security as a priority workstream, with four specific pieces: DPoP, Workload Identity Federation, the ID-JAG grant behind Enterprise-Managed Authorization, and standard token exchange. I went through the spec repos to see how much of that you can actually build against today. The answer is one of the four, and it authenticates the human employee rather than the agent.

That is not a complaint about the process. Spec work is slow because getting it wrong is expensive. But there is a gap between what the roadmap names and what ships, and if you are putting agents in production this quarter, the gap is yours to fill at a different layer.

What has actually shipped

MCP’s auth extensions live in their own repo. The whole surface is two files:

gh api "repos/modelcontextprotocol/ext-auth/git/trees/main?recursive=1" \
  --jq '.tree[] | select(.path | endswith(".mdx")) | .path'
specification/draft/oauth-client-credentials.mdx
specification/stable/enterprise-managed-authorization.mdx

One stable specification and one draft. Everything else on the identity roadmap is still a proposal.

Enterprise-Managed Authorization authenticates the employee

The stable extension, io.modelcontextprotocol/enterprise-managed-authorization, is good work and solves a real problem. Its own description of that problem is worth reading closely:

Instead of each employee authorizing each MCP server individually, the organization’s IT or security team manages access policies in one place.

The flow is a browser redirect to the enterprise IdP, a user login, an ID token issued to the MCP client, and then an exchange of that ID token for an Identity Assertion JWT Authorization Grant, the ID-JAG. The client presents the ID-JAG to the MCP authorization server and receives an access token. The IdP evaluates group membership and conditional access policy at the exchange step, so access is grantable and revocable from one admin console.

Read the participant list in that flow and the shape of the thing becomes clear. There is a browser. There is a user who logs in. The subject being authenticated is an employee, and the agent is software acting with that employee’s delegated authority.

For a developer running an MCP client on a laptop, that is exactly right. For an agent running unattended in a namespace at 3 AM, there is no browser and no employee to redirect. The extension was not built for that case and does not claim to be.

The client credentials draft still wants a stored secret

The draft client credentials extension is the machine-to-machine option, and it is honest about its own prerequisite:

This flow requires pre-registered client credentials, which are typically established out-of-band through administrative channels. Dynamic Client Registration is not used in this flow.

Pre-registered and established out-of-band means somebody creates a credential and puts it somewhere the agent can read. That is the design I argued against in the secretless agents post: whatever issues the credential, the agent process ends up holding it, which is precisely what the LiteLLM .pth payload harvested when it swept environment variables and .env files. The draft does recommend JWT client authentication over a shared secret, which is meaningfully better. It is still a registered client with a key it has to keep.

DPoP and Workload Identity Federation are still pull requests

The two proposals that would give an agent an identity of its own are both open:

for pr in 1932 1933; do
  gh api "repos/modelcontextprotocol/modelcontextprotocol/pulls/$pr" \
    --jq '"#\(.number) \(.title) | \(.state) | opened \(.created_at[0:10]) | updated \(.updated_at[0:10])"'
done
#1932 SEP-1932: DPoP Profile for MCP | open | opened 2025-12-05 | updated 2026-08-31
#1933 SEP-1933: Workload Identity Federation | open | opened 2025-12-05 | updated 2026-08-24

Both were opened on the same day in December 2025. Both are still being actively revised, which is the healthy reading of those update dates. Both also still have TBD where the pull request template asks whether the proposal has been tested in a real application.

SEP-1932 would apply DPoP (RFC 9449) to bind an access token to a key pair the client controls, so a token that leaks cannot be replayed by whoever picked it up. That is the difference between a bearer token and a sender-constrained one, and it is the single change that would make a stolen MCP token much less useful.

SEP-1933 is the one I did not expect. Its motivation reads like the argument I made in the SPIFFE post, written by somebody else in the spec repo:

Workloads in environments such as Kubernetes, SPIFFE/SPIRE, and cloud-native runtimes already receive short-lived, cryptographically verifiable JWTs that reflect their runtime identity. Requiring these workloads to additionally register as OAuth clients or manage long-lived secrets introduces unnecessary complexity and weakens security posture.

The same document describes the existing mechanisms as “primarily designed around human-centric OAuth flows or statically registered client credentials.” That is the spec repo’s own assessment of the two things that shipped, and it matches what the files say.

The one piece that did move this week

Enterprise-Managed Authorization landed months ago, but its identity-provider guidance is still being written. Pull request #3306, “Document identity provider support for enterprise managed auth,” opened August 26 and was updated on September 1. If you are trying to work out which IdPs can actually issue an ID-JAG today, that PR is the document you want and it is not merged.

The other one worth watching is SEP-2643, structured authorization denials, updated September 2 after ninety review comments. It proposes a JSON-RPC envelope for a denial that carries a failure classification, a retry correlation handle, and remediation hints, so a client can be told what to do about a refusal instead of just being refused.

That matters more than it sounds. Right now, when policy blocks a tool, the usual implementation filters the tool out of tools/list, and a model that calls it anyway gets back:

-32602 Unknown tool

The model reads that as a tool that does not exist, so it improvises around the gap rather than asking for authorization. I hit exactly this while testing whether an AGENTS.md file can act as a security control. A denial that says “denied, here is how to get approved” is a different conversation with the model than a denial that looks like a typo.

What to do in the meantime

The parts of agent identity you need now exist a layer below the protocol, at the gateway and in the mesh:

  1. Give the workload an attested identity, not a secret. SPIRE issues an X.509 SVID to a process based on attested properties, so there is no key file to mount and nothing in the process worth stealing. The SPIFFE demo runs the agent, the gateway, and the model upstream this way on docker compose.
  2. Terminate the provider credential outside the agent. The gateway holds the API key and attaches it on the way out, so a compromised dependency inside the agent gets nothing.
  3. Authorize on the verified identity, not on a claim in a header. A SPIFFE ID established during the TLS handshake is something the caller cannot set or omit, which makes it safe to write policy against. A team: research claim in a self-issued JWT is not.
  4. Keep authentication and authorization separate. A valid identity answers who is calling. Whether that caller may invoke a given tool is a policy decision, and it belongs somewhere you can change without reissuing identities.

None of that conflicts with where the spec is heading. SEP-1933 exists to standardize the handoff so an MCP server can validate a platform-issued workload JWT directly, instead of every deployment terminating identity at a gateway and forwarding a different credential inward. When it lands, the substrate work is what it federates from.

What I am watching

Three things would change this picture. SEP-1932 or SEP-1933 moving to draft in the extensions repo, which would make agent identity something you can write against rather than argue about. #3306 merging, which would tell you which IdPs actually issue an ID-JAG. And SEP-2643 landing, which would turn a policy denial into information the model can act on.

Until then, the honest summary is that MCP has a shipped answer for who the employee is and no shipped answer for who the agent is. If your agents run unattended, you are solving that yourself, and the place to solve it is the workload boundary.

You can check all of this in about a minute with the two commands above. Spec state changes, and a post about spec state has a shelf life.

Frequently asked questions

Does MCP have a spec for agent identity?

Not for the agent itself yet. The auth extensions repo ships one stable specification, Enterprise-Managed Authorization, which authenticates the employee through their corporate IdP and hands the client an ID-JAG to exchange for an access token. The two proposals that would give a workload its own verifiable identity, DPoP (SEP-1932) and Workload Identity Federation (SEP-1933), are still open pull requests.

What is ID-JAG in MCP Enterprise-Managed Authorization?

An Identity Assertion JWT Authorization Grant. The MCP client logs the user in at the enterprise IdP, exchanges the resulting ID token for an ID-JAG, then presents the ID-JAG to the MCP authorization server to get an access token. The IdP evaluates organizational policy at the exchange step, which is what makes access centrally grantable and centrally revocable.

Can an MCP server authenticate an agent without a stored secret today?

Not through a stable MCP extension. The only machine-to-machine option in the repo is the OAuth client credentials draft, and it requires pre-registered credentials established out-of-band, so something still holds a secret. Short-lived workload identity is available a layer down instead, at the gateway or service mesh, using SPIFFE SVIDs or gateway-minted JWTs.

Why does a denied MCP tool call return a generic error?

Because the protocol has no structured way to say why. A gated tool is usually filtered out of tools/list, so a model that calls it gets -32602 Unknown tool, which reads as a missing tool rather than a policy decision. SEP-2643 proposes an authorization denial envelope carrying failure classification and remediation hints. It is still open.