MCP authorization

MCP OAuth 2.1 authorization server

Secure an MCP server with an authorization server you host. theAuth implements the discovery, registration and token steps MCP clients expect, and adds agent identity and delegation chains behind the token.

RFCs
9728 8707 8414 7591
PKCE
S256 only
Spec
MCP 2025-11-25
Resource module
0 deps

Discover, register, get a token

What an MCP client does, step by step

The client starts knowing only the MCP server URL. Everything else is discovered.

An MCP client starts with only the MCP server URL, discovers the authorization server from metadata, registers, and authorizes with PKCE S256. theAuth issues the token, and the MCP server validates its signature, audience, scope and expiry before it runs the tool.
  1. Client to resource server

    Call without a token

    The MCP server answers 401 with a WWW-Authenticate: Bearer header that points at its resource metadata (RFC 6750, RFC 9728).

  2. Client to resource server

    Read protected resource metadata

    GET /.well-known/oauth-protected-resource names the resource and its authorization servers (RFC 9728).

  3. Client to authorization server

    Read server metadata

    GET /.well-known/oauth-authorization-server lists endpoints and code_challenge_methods_supported: ["S256"] (RFC 8414).

  4. Client to authorization server

    Register

    Dynamic registration (RFC 7591), or in Go a CIMD client whose client_id is an https URL the server fetches (MCP spec 2025-11-25).

  5. Client to authorization server

    Authorize with PKCE

    The client sends a PKCE S256 challenge and a resource indicator (RFC 8707). The user consents and a code returns.

  6. Client to authorization server

    Exchange the code

    The code and verifier go to the token endpoint. The access token is bound to the audience.

  7. Client to resource server

    Call with the token

    The MCP server validates signature, audience, scope and expiry before running the tool.

Endpoint URLs are advertised in the metadata, so clients do not hard-code them. The TypeScript server serves /mcp/register, /mcp/authorize and /mcp/token. The Go server serves /oauth/register, /oauth/authorize and /oauth/token.

Go

CIMD, rotation and the resource module

  • CIMD. When enabled, a client_id that is an https URL is resolved by fetching a metadata document. The document's client_id must equal the URL it was fetched from. The trust policy defaults to deny all; you opt in with AllowHTTPSHosts or AllowAnyHTTPS.
  • Refresh rotation with family revocation. Presenting an already-used refresh token revokes the whole token family (RFC 9700).
  • mcpresource. A standalone Go module with no dependencies outside the standard library. It checks the JWT against a cached JWKS, enforces audience, and walks the RFC 8693 actor chain through introspection. Revocation reaches it within the cache TTL, 60 seconds by default.
  • More Go grants. RFC 8693 token exchange, 9449 DPoP, 9126 PAR, 9101 JAR and 9509 CIBA.
main.go
// Resource server: go get github.com/glincker/theauth-go/mcpresource
v := mcpresource.New(
    "https://mcp.example.com",
    mcpresource.WithJWKS("https://as.example.com/oauth/jwks"),
    mcpresource.WithIntrospection(
        "https://as.example.com/oauth/introspect",
        "mcp-client-id", "mcp-client-secret",
    ),
)
r := chi.NewRouter()
r.Use(v.Middleware) // JWT validation, audience check, actor-chain walk
r.Get("/tools/run", func(w http.ResponseWriter, r *http.Request) {
    p, _ := v.Principal(r.Context())
    // p.Subject (user), p.Actor (final agent), p.ActorChain (delegation path)
})

Runnable example: examples/mcp-server.

auth.ts
import { createTheAuth } from "@glinr/theauth";

const auth = await createTheAuth({
  database: { provider: "postgres", url: process.env.DATABASE_URL },
  mcp: {
    enabled: true,
    issuer: "https://auth.yourapp.com",
    baseUrl: "https://auth.yourapp.com",
    signingSecret: process.env.MCP_SIGNING_SECRET,
  },
});

TypeScript

Enable it in the core

Mount the MCP routes through a framework adapter, then validate tokens with auth.mcp.validate(token) or the adapter's withMcpAuth middleware. TypeScript access tokens are HS256 at+jwt signed with your secret, so validation happens in the same deployment. The Go server signs with Ed25519 and publishes a JWKS, which lets separate resource servers verify tokens on their own.

Where it fits

What differs from other options

Other identity products also document MCP OAuth support, so the protocol alone is not the difference. What theAuth adds:

Choose another tool if

you already run a hosted identity provider that meets your MCP needs and you do not need agent-level identity or self-hosting.

Get started

Give your first agent an identity.

Install the package, create an agent with scoped permissions, and read its first audit record. The core runs on Postgres, SQLite, MySQL or D1, and the Go module needs a single go get.

  • npm install @glinr/theauth
  • go get github.com/glincker/theauth-go
Or skip hosting with theAuth Cloud