new·Earn with mozg — 20% of every monthSend somebody here and take a fifth of every plan payment they make, for as long as they keep paying — not a bounty on the first invoice. Your handle is the link, the window is thirty days, and the commission lands on your balance the second they pay. Free to join: if you have signed in, you already have the link. mozg.sh/earnall news →
mozg.beta
Sign in

Better Auth · Plugins · all subjects

mcp plugin

26 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

MCP plugin enables OAuth provider for Model Context Protocol clients

The MCP plugin turns a Better Auth server into an OAuth authorization server and protected resource for Model Context Protocol (MCP) clients. It is built on the OAuth 2.1 Provider and allows MCP clients to discover endpoints and obtain resource-bound access tokens through standard OAuth flows.

MCP plugin installation requirements

Install @better-auth/mcp, @better-auth/cimd, @modelcontextprotocol/server (version 2 of the official MCP TypeScript SDK), and zod. The JWT plugin is required alongside MCP and provides the stable signing key for ID tokens and access tokens, and exposes the /jwks endpoint that resource servers use to verify them.

MCP plugin required configuration options

The MCP plugin requires three configuration options: loginPage (path to login page where users are redirected for authentication), consentPage (path to consent page where users grant requested scopes), and resource (the protected resource identifier that access tokens are bound to, must be an HTTPS URL with no query, fragment, or credentials; HTTP accepted only on loopback hosts for local development).

MCP plugin schema tables

The MCP plugin uses the same schema as the OAuth Provider plugin: oauthClient, oauthAccessToken, oauthRefreshToken, oauthConsent, and oauthClientAssertion tables.

MCP plugin OAuth endpoints

The mcp() plugin serves standard OAuth 2.1 endpoints under /oauth2/*: /oauth2/authorize (Authorization), /oauth2/token (Token), /oauth2/register (Dynamic registration, when explicitly enabled), and /oauth2/userinfo (UserInfo).

MCP plugin discovery endpoints follow RFC standards

Discovery follows OAuth 2.0 Authorization Server Metadata (RFC 8414) and Protected Resource Metadata (RFC 9728). The well-known URLs are derived from the issuer, so a server with a base path serves them at the issuer-inserted location rather than the bare root. Discovery advertises client_id_metadata_document_supported only when cimd() is installed, and registration_endpoint only when DCR is enabled.

Do not register separate oauthProvider plugin with MCP

mcp() is the OAuth provider. Do not also register a separate oauthProvider() plugin in the same app.

MCP plugin default refreshTokenReuseInterval

For every client configured through mcp(), the plugin defaults refreshTokenReuseInterval to 30 seconds. This lets a client retry a refresh with the old token and receive the same rotated token response when another request already consumed that refresh token. Set refreshTokenReuseInterval: 0 on mcp() to disable the overlap window.

MCP plugin OAuth configuration options

Common MCP/OAuth provider configuration options: scopes (string[], default: ["openid", "profile", "email", "offline_access"]), resources (Array<string | OAuthResourceInput>), accessTokenExpiresIn (number, default: 3600 seconds), idTokenExpiresIn (number, default: 36000 seconds), refreshTokenExpiresIn (number, default: 2592000 seconds), refreshTokenReuseInterval (number, default: 30 for MCP), and codeExpiresIn (number, default: 600 seconds).

MCP plugin registers resource as default client-registration resource

mcp() registers the resource identifier as a default client-registration resource. A dynamically registered client is linked to the MCP resource even when its registration request omits the non-standard resources field. Any clientRegistrationDefaultResources provided are preserved, and the MCP resource is appended once.

Enable DCR fallback for older MCP clients

If you deliberately support older clients that still require DCR, enable both provider controls explicitly: set allowDynamicClientRegistration: true and allowUnauthenticatedClientRegistration: true. The registration endpoint is absent from discovery unless DCR is enabled.

MCP 2026-07-28 uses stateless request-response model

MCP 2026-07-28 uses a stateless request and response model: every client JSON-RPC request or notification is an independent HTTP POST, and the server does not maintain a protocol-level session between requests.

requireMcpAuth wraps MCP route handlers

Use requireMcpAuth to wrap the MCP TypeScript SDK v2's POST handler. It reads the Authorization header, verifies access tokens against the authorization server's JWKS, and checks the signature, issuer, audience, and expiry. It also enforces RFC 9449 Demonstrating Proof of Possession (DPoP) when an access token is DPoP-bound.

requireMcpAuth unauthenticated request response

Unauthenticated requests to requireMcpAuth receive a JSON-RPC 401 with the RFC 9728 WWW-Authenticate header, so MCP clients can start the authorization flow. Tokens missing a required scope receive a 403 with an RFC 6750 insufficient_scope challenge, which MCP clients use to step up their authorization.

requireMcpAuth passes verified claims as callback argument

The requireMcpAuth wrapper passes the verified access-token claims as a second callback argument to the handler, not as a database record. requireMcpAuth never exposes a refresh token and checks the access token locally against the JWKS without a database round trip.

requireMcpAuth DPoP replay protection

DPoP replay protection in requireMcpAuth uses the auth instance's database adapter by default so it works across server instances. Pass dpop.replayStore only when you need a different shared store.

requireMcpAuth default issuer and JWKS resolution

By default, requireMcpAuth reads the server's resolved Better Auth URL from the auth context and uses it as the expected issuer, resource, and JWKS base. Override the issuer or JWKS URL when jwt.issuer is custom or the authorization server runs separately.

requireMcpAuth configuration options

requireMcpAuth accepts options: resource (protected resource identifier), issuer (override when jwt.issuer is custom), jwksUrl (override JWKS URL), challengeScopes (scopes advertised in 401 challenge), and requiredScopes (enforced against token's scope claim; token missing any scope receives 403 insufficient_scope challenge).

createInsufficientScopeError for dynamic scope requirements

When required scopes depend on the request (one tool needs more than another), throw createInsufficientScopeError from the handler to challenge for exactly those scopes: createInsufficientScopeError(["mcp:admin"]).

createMcpProtectedRequestHandler for remote MCP servers

createMcpProtectedRequestHandler verifies tokens against Better Auth server's JWKS, so the MCP route can run anywhere as long as it can reach that JWKS URL. When the resource server runs separately from the authorization server, use createMcpProtectedRequestHandler with explicit verification options instead of requireMcpAuth.

createMcpProtectedRequestHandler configuration options

createMcpProtectedRequestHandler accepts options: issuer (required, authorization server issuer), audience (required, protected resource identifier), jwksUrl (required, JWKS URL), requiredScopes (scopes to enforce), and challengeScopes (scope hints advertised on unauthenticated challenges).

MCP plugin with device authorization for CLI

The same authorization server can let a CLI authorize through a browser without opening a local callback listener. Add oauthDeviceAuthorization({ verificationUri: "/device" }) alongside mcp(). mcp() is already the OAuth Provider, so this composition does not add a separate oauthProvider() plugin.

MCP protected resource metadata RFC 9728

The RFC 9728 /.well-known/oauth-protected-resource document is served automatically at the well-known root and the resource-path-inserted alias. It tells MCP clients which authorization server protects the resource, which scopes it supports, which resource identifier their access tokens must be bound to, and which DPoP proof algorithms are supported.

MCP plugin does not enable DCR implicitly

MCP deprecates Dynamic Client Registration (DCR), so Better Auth never enables DCR implicitly. Only enable it when deliberately supporting older clients that still require it.

MCP protocol version 2026-07-28 and legacy protocol handling

Better Auth owns OAuth discovery, client registration and discovery, PKCE authorization, resource binding, token issuance and refresh, and protected-resource challenges. The official MCP TypeScript SDK v2 owns the stateless MCP protocol and transport. Configure servers with legacy: "reject" and pin SDK client version negotiation to 2026-07-28 when adopting this profile.

MCP resource cannot contain query, fragment, or credentials

The resource identifier must be an HTTPS URL with no query, fragment, or credentials; HTTP is accepted only on loopback hosts for local development. A resource whose URL requires a query component cannot use mcp(), requireMcpAuth, or createMcpProtectedRequestHandler.

Give your agent this brain