---
title: "Protected Resource Metadata"
description: "RFC 9728 resource server discovery for OAuth-protected APIs and MCP servers"
url: "https://agentsurface.dev/docs/authentication/protected-resource-metadata"
lastVerified: 2026-09-24
lastModified: 2026-09-25T05:46:01.000Z
---



## Summary [#summary]

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

* `resource`: your API 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 JWT validation
* `bearer_methods_supported`: accepted bearer token 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 OAuth 2.0 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 [#metadata-document-structure]

```json
{
  "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 [#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 embedding registration details directly:

```http
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](/docs/authentication/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 [#nextjs-implementation]

```typescript
// 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 [#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 token exchange

DPoP is not part of this discovery flow in the ratified MCP spec - see [DPoP](/docs/authentication/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

```typescript
// 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();
}
```

<Callout type="info">
  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.
</Callout>

## Minimal Metadata for Simple APIs [#minimal-metadata-for-simple-apis]

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

```json
{
  "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 [#publishing-metadata-in-openapi]

You can reference protected resource metadata in your OpenAPI spec:

```yaml
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 [#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 [#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 [#see-also]

* [DPoP](/docs/authentication/dpop) - sender-constrained tokens
* [Token Exchange](/docs/authentication/token-exchange) - RFC 8693 delegation
* [MCP Authorization](/docs/authentication/mcp-auth-model) - MCP's use of metadata
* [auth.md](/docs/authentication/auth-md) - agentic registration discovery
* [RFC 8414: OAuth 2.0 Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) - base specification
* [RFC 9728: OAuth 2.0 Protected Resource Metadata](https://www.rfc-editor.org/rfc/rfc9728.html) - resource server extension
