McpAgent, and workers-oauth-provider makes the Worker a full OAuth 2.1 authorization server. Crucially, its registered-client storage is Workers KV — durable across instances — so the ephemeral-client failure that bites memory-backed frameworks doesn’t happen here. This page covers what to choose for MCP Manager and the one gotcha KV introduces; Cloudflare’s docs are authoritative.
Serve Streamable HTTP from McpAgent
AnMcpAgent (a Durable Object, bound as MCP_OBJECT) exposes two factories: MyMCP.serve('/mcp') for Streamable HTTP and MyMCP.serveSSE('/sse') for the legacy SSE transport. Point MCP Manager at the /mcp endpoint. See Build a Remote MCP server and the transport page.
Auth with workers-oauth-provider
workers-oauth-provider makes the Worker a full OAuth 2.1 authorization server (with PKCE) and auto-publishes the RFC 8414 (/.well-known/oauth-authorization-server) and RFC 9728 (/.well-known/oauth-protected-resource) metadata. Dynamic client registration (RFC 7591) turns on when you set the optional clientRegistrationEndpoint — set it so MCP Manager can self-register. You wire an upstream IdP handler (Google, GitHub, Auth0, Stytch, WorkOS) as the defaultHandler, and the Worker mints its own MCP tokens after the upstream login.
Storage: durable by default, but eventually consistent
Registered clients, grants, and tokens live in Workers KV (theOAUTH_KV binding), a global namespace. Because it’s KV and not process memory, a client MCP Manager registers is visible to every Worker isolate — so the ephemeral-client failure that plagues memory-backed frameworks does not happen here. Dynamically-registered clients expire per clientRegistrationTTL (default 90 days).
The trade-off is KV’s eventual consistency. A client or token written on one edge may not be visible on another for a short window, so a token or authorize call made immediately after registration can briefly return invalid_client or 401 and then clear on its own. Build a little tolerance (retry/backoff) into anything that registers and immediately uses a client.
MCP Manager compatibility checklist
Set clientRegistrationEndpoint
clientRegistrationEndpoint
so MCP Manager can self-register via
DCR. Without it, registration is
off.Bind MCP_OBJECT and OAUTH_KV
MCP_OBJECT)
and the KV namespace (OAUTH_KV)
bindings must exist in wrangler —
a missing binding is a common setup
failure.Allow confidential-client registration
disallowPublicClientRegistration: true, that’s fine — just don’t
expect public (no-secret) client
registration to work.Cloudflare gotchas
Transient invalid_client right after registering
Transient invalid_client right after registering
/sse vs /mcp routing
/sse vs /mcp routing
serve('/mcp') for Streamable HTTP,
serveSSE('/sse') for legacy. When
fronting both under OAuth, use the
apiHandlers map form rather than a
single apiHandler.scopesSupported is advisory only
scopesSupported is advisory only
scopesSupported field only advertises scopes in metadata — it doesn’t restrict what a client may request. Enforce real authorization in your authorize handler, not by relying on that field..png?fit=max&auto=format&n=gKqTvJPtsRi2bLNx&q=85&s=8abbce3efb590630de2102c43d32aadf)
.png?fit=max&auto=format&n=Dy9YsIECUbR9JZiT&q=85&s=a1f404cd7f7aeb1727c89d81137ae1ac)