{"slug": "the-mcp-sdk-just-shipped-dpop-and-scope-challenges-here-s-what-to-do-about-it", "title": "The MCP SDK just shipped DPoP and scope challenges. Here's what to do about it.", "summary": "The official Model Context Protocol TypeScript SDK released version 2.1.0, adding DPoP sender-constrained access tokens (RFC 9449, shipped as SEP-1932) and request-time OAuth scope challenges from the 2026-07-28 authorization spec. The release covers @modelcontextprotocol/core, client, server, and node, adding an OAuthClientProvider.dpop() method returning a DpopSession on the client side and a scopeChallenge callback on the server side that sends an HTTP 403 with a WWW-Authenticate header before a handler runs. The SDK also bounds request body size and JSON-RPC batch length in Streamable HTTP handling, closing a gap where a stolen bearer token could be used by anyone holding a copy.", "body_md": "# The MCP SDK just shipped DPoP and scope challenges. Here's what to do about it.\n\nA first look at how the official Model Context Protocol SDK is turning the 2026-07-28 spec's authorization hardening into code, and what it means for anyone running an MCP server.\n\nFor most of this year, the MCP authorization spec has described a stronger security model faster than any SDK could implement it. That gap closed a little this week. The 2.1.0 release of the official TypeScript SDK (`@modelcontextprotocol/core`, `client`, `server`, and `node`) adds two features straight out of the 2026-07-28 spec's authorization hardening work: DPoP sender-constrained access tokens and request-time OAuth scope challenges. Neither is optional plumbing. Both change how you should be thinking about token theft and permission scope on any MCP server you run today.\n\n**At a glance:**\n\n## Why bearer tokens were the weak point\n\nMCP servers are, by design, reachable by an agent over the network, often from infrastructure you don't fully control: a developer's laptop, a third-party host, a CI runner. A plain OAuth bearer token is a bearer instrument. Anyone who gets a copy of it, through a logging mistake, a compromised dependency, or a leaked environment variable, can use it exactly as the legitimate client would. There's no way for the resource server to tell the difference.\n\nDPoP (RFC 9449, shipped in MCP as SEP-1932) closes that gap by binding the access token to a key pair the client holds. Every request carries a signed proof that the caller possesses the private key the token was issued to. A stolen token without the matching key is worthless.\n\n## What actually shipped\n\nOn the client side, the SDK adds an `OAuthClientProvider.dpop()` method that returns a `DpopSession`. Implement it and the SDK takes care of the rest: `generateDpopKeyPair`, `accessTokenHash`, and `isDpopNonceChallenge` are exposed as helpers, and the existing auth flow methods, `auth()`, `exchangeAuthorization()`, `refreshAuthorization()`, and `fetchToken()`, all sign a DPoP proof into the token request automatically. If the authorization server responds with a `use_dpop_nonce` challenge, the SDK retries once with the nonce and reapplies client authentication. Streamable HTTP, SSE, and `withOAuth` transports all present the resulting token correctly without extra wiring.\n\nRoughly, the flow looks like this:\n\nA minimal client-side implementation is small:\n\n(Check the SDK's current type definitions for the exact `DpopSession` shape before shipping this. The release is brand new and details may shift.)\n\nOn the server side, `@modelcontextprotocol/server` and `@modelcontextprotocol/node` add a `scopeChallenge` callback that registers alongside a tool, resource, or prompt. When it returns a challenge, the transport sends a proper HTTP 403 with a `WWW-Authenticate` header before the handler runs or an SSE stream opens, rather than letting a partially authorized call leak tool behavior. The header is built with the same formatter used for the `resource_metadata` parameter, and `requireBearerAuth`/` verifyBearerToken` now attach `resourceMetadataUrl` to the `AuthInfo` object they return, so a client always knows where to go to re-authorize.\n\nFor the common case, static scope requirements, there's a helper so you don't have to write the check by hand:\n\nFor anything conditional, the callback can inspect the caller's existing scopes and decide at call time:\n\nAlongside the auth work, the release also bounds request body size and JSON-RPC batch length in the Streamable HTTP handling, which matters for the same reason: an MCP server accepting unbounded input from a network caller is a server that hasn't fully priced in what \"reachable by an agent\" means.\n\n## What this means if you're running an MCP server\n\nIf your MCP server issues or validates OAuth tokens today, this release gives you two concrete things to go do, not just read about.\n\nFirst, decide whether your authorization server needs to support DPoP now or can wait for the next revision. The spec doesn't mandate DPoP yet, but the client-side SDK support just went from theoretical to a five-line integration. Once client authors start calling `dpop()` by default, an authorization server that only understands bearer tokens is the one place in the chain still assuming the old threat model.\n\nSecond, look at where your server currently treats \"has a valid token\" as equivalent to \"is allowed to do this specific thing.\" Scope challenges exist so a server can grant a narrow token up front and ask for more only when a specific tool call needs it, rather than forcing a client to request every possible scope at authorization time. That's a better default for anything destructive: a tool that deletes records, sends money, or writes to a shared resource should live behind its own scope, challenged for at call time, not bundled into whatever scope got granted at login.\n\nNeither of these is a rewrite. They're the kind of change that's cheap today and expensive to retrofit once a client population has settled on assuming bearer tokens are good enough. The SDK just removed the excuse for waiting.", "url": "https://wpnews.pro/news/the-mcp-sdk-just-shipped-dpop-and-scope-challenges-here-s-what-to-do-about-it", "canonical_source": "https://workos.com/blog/mcp-sdk-dpop-and-scope-challenges", "published_at": "2026-09-29 00:00:00+00:00", "updated_at": "2026-09-29 14:22:10.565938+00:00", "lang": "en", "topics": ["agent-protocols", "ai-agents", "ai-safety", "developer-tools", "ai-tools"], "entities": ["Model Context Protocol", "@modelcontextprotocol/core", "@modelcontextprotocol/client", "@modelcontextprotocol/server", "@modelcontextprotocol/node", "DPoP", "OAuth", "SEP-1932"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/the-mcp-sdk-just-shipped-dpop-and-scope-challenges-here-s-what-to-do-about-it", "markdown": "https://wpnews.pro/news/the-mcp-sdk-just-shipped-dpop-and-scope-challenges-here-s-what-to-do-about-it.md", "text": "https://wpnews.pro/news/the-mcp-sdk-just-shipped-dpop-and-scope-challenges-here-s-what-to-do-about-it.txt", "jsonld": "https://wpnews.pro/news/the-mcp-sdk-just-shipped-dpop-and-scope-challenges-here-s-what-to-do-about-it.jsonld"}}