Agent Surface

Authentication

Protected Resource Metadata

RFC 9728 resource server discovery for OAuth-protected APIs and MCP servers

Last verified 2026-09-24

Summary

Publish your resource server's metadata at /.well-known/oauth-protected-resource so that clients, agents, and external services can discover your authorization requirements at runtime. Clients use this metadata to validate against the correct JWKS and learn which you support. RFC 9728 does not standardize a DPoP-requirement field; if you require , advertise it separately (see the note below).

  • resource: your or MCP server's resource URI
  • authorization_servers: list of trusted identity providers
  • scopes_supported: all scopes your API recognizes
  • jwks_uri: public key endpoint for validation
  • bearer_methods_supported: accepted transport methods
  • Cache with Cache-Control: max-age=3600

When an MCP client, external agent, or service needs to call your API, it must know: which authorization servers to trust, which scopes you support, and where to validate JWTs. RFC 8414 Authorization Server Metadata defines the authorization server metadata endpoint. RFC 9728 Protected Resource Metadata defines the metadata that a resource server exposes about itself.

Publish this metadata at /.well-known/oauth-protected-resource to allow clients (including MCP) to discover your auth requirements and validate tokens correctly.

Metadata Document Structure

{
  "resource": "https://api.example.com",
  "authorization_servers": ["https://auth.example.com/"],
  "scopes_supported": [
    "invoices:read",
    "invoices:write",
    "customers:read",
    "customers:write",
    "payments:read",
    "payments:write"
  ],
  "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
  "bearer_methods_supported": ["header"],
  "resource_signing_alg_values_supported": ["RS256", "ES256"],
  "resource_name": "Example API"
}

Key fields:

  • resource - your API's base URI or MCP endpoint resource identifier
  • authorization_servers - list of auth servers that can issue tokens you accept
  • scopes_supported - all scopes your API recognizes
  • jwks_uri - where to fetch public keys for JWT validation
  • bearer_methods_supported - accepted bearer token locations, usually ["header"]
  • resource_signing_alg_values_supported - algorithms used when the resource signs responses or metadata
  • resource_name - human-readable resource name for client UI

Authorization-server capabilities such as grant_types_supported, response_types_supported, token endpoints, and token endpoint auth methods belong in the authorization server metadata document, not the protected resource metadata document.

Agent Registration Discovery

If your service supports agentic user registration through auth.md, keep the split explicit:

  • Protected-resource metadata tells the agent which resource it is calling and which authorization servers protect it.
  • Authorization server metadata advertises the optional agent_auth block with identity, claim, and provider-event endpoints. Token revocation remains a top-level OAuth endpoint.
  • auth.md gives agents a readable walkthrough of the same flow, including supported registration methods, scopes, error recovery, and contact links.

A protected resource can point agents toward the registration path without registration details directly:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"

The authorization server metadata then advertises supported agent registration flows through its optional agent_auth block - see auth.md for the full field-by-field example and the current registration model. Treat this metadata as discovery only; authorization still depends on validating the issued bearer token on every request.

Next.js Implementation

// app/.well-known/oauth-protected-resource/route.ts
import { NextResponse } from "next/server";

export async function GET() {
  const metadata = {
    resource: "https://api.example.com",
    authorization_servers: ["https://auth.example.com/"],
    scopes_supported: [
      "invoices:read",
      "invoices:write",
      "customers:read",
      "customers:write",
      "payments:read",
      "payments:write",
    ],
    jwks_uri: "https://auth.example.com/.well-known/jwks.json",
    bearer_methods_supported: ["header"],
    resource_signing_alg_values_supported: ["RS256", "ES256"],
    resource_name: "Example API",
  };

  return NextResponse.json(metadata, {
    headers: { "Cache-Control": "public, max-age=3600" },
  });
}

Serve it at https://api.example.com/.well-known/oauth-protected-resource (standard location) or https://api.example.com/.well-known/oauth-protected-resource.json (alternative).

How MCP Uses This

MCP clients (this metadata discovery has been part of the spec since the 2025-11-25 revision) fetch this metadata to:

  1. Discover which authorization servers can issue tokens
  2. Validate tokens against the correct JWKS endpoint
  3. Learn which scopes the API supports
  4. Use the correct token endpoint for

DPoP is not part of this discovery flow in the ratified MCP spec - see DPoP for why. If your resource server independently requires DPoP (an OAuth-layer decision, not an MCP one), advertise it through a field you define and document in your own metadata, since RFC 9728 does not standardize one.

When an MCP server calls your resource server, it:

  1. Fetches /.well-known/oauth-protected-resource
  2. Validates the resource identifier matches
  3. Fetches the JWKS from the declared jwks_uri
  4. On every request, validates the JWT signature against JWKS and verifies iss and aud claims
  5. Includes a DPoP proof only if your resource server independently requires sender-constrained tokens
// MCP client example (simplified)
async function callResourceServer(token: string, endpoint: string) {
  // Fetch metadata
  const metadata = await fetch("https://api.example.com/.well-known/oauth-protected-resource").then(
    (r) => r.json(),
  );

  // Fetch JWKS
  const jwks = await fetch(metadata.jwks_uri).then((r) => r.json());

  // Validate token before sending
  // (server-side validation happens too)

  // If DPoP is required by this resource or token, generate a proof
  let dpopProof = null;
  if (requiresDpop(metadata)) {
    dpopProof = await generateDPopProof("GET", endpoint);
  }

  const headers: Record<string, string> = {
    Authorization: `Bearer ${token}`,
  };
  if (dpopProof) {
    headers["DPoP"] = dpopProof;
  }

  const res = await fetch(endpoint, { headers });
  return res.json();
}

Current 2026-07-28 behavior: the metadata document itself is unchanged, but two client obligations around it tighten. Clients must validate the iss parameter on the authorization response against the issuer they discovered through this document, per RFC 9207 (SEP-2468), and must store client credentials keyed by that issuer rather than by resource, re-registering when a resource's authorization_servers entry changes (SEP-2352). A stable, correct issuer in your authorization server metadata matters more than before: changing it invalidates every client credential registered against it.

Minimal Metadata for Simple APIs

If you're not using DPoP or token exchange yet, a minimal metadata document is acceptable:

{
  "resource": "https://api.example.com",
  "authorization_servers": ["https://auth.example.com/"],
  "scopes_supported": ["read", "write"],
  "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
  "bearer_methods_supported": ["header"]
}

Publishing Metadata in OpenAPI

You can reference protected resource metadata in your spec:

openapi: 3.1.0
info:
  title: Example API
  version: 1.0.0
servers:
  - url: https://api.example.com
components:
  securitySchemes:
    oauth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://auth.example.com/token
          scopes:
            invoices:read: Read invoices
            invoices:write: Create and update invoices
paths:
  /invoices:
    get:
      security:
        - oauth2: [invoices:read]
      responses:
        "200":
          description: List of invoices

x-oauth-protected-resource: /.well-known/oauth-protected-resource

This allows OpenAPI tools to automatically discover your OAuth requirements.

Cache and Update Strategy

The metadata document is relatively stable but can change when you:

  • Add new scopes
  • Support DPoP
  • Add token exchange support
  • Change authorization servers

Clients typically cache metadata for 1 hour (specify Cache-Control: max-age=3600). When you make changes, the cache expires naturally. For immediate propagation, rotate the URI or notify clients.

Checklist

  • Endpoint is served at /.well-known/oauth-protected-resource
  • resource matches your API's base URI or MCP endpoint resource identifier
  • authorization_servers includes all trusted identity providers
  • scopes_supported is exhaustive and matches your API's permission model
  • jwks_uri points to a valid JWKS endpoint with current public keys
  • bearer_methods_supported is explicit and does not permit query-string bearer tokens
  • Authorization server metadata advertises the grants and token endpoint methods clients can use
  • Response includes Cache-Control header with appropriate TTL
  • Metadata is served with Content-Type: application/json

See Also

On this page