Protected Resource Metadata
RFC 9728 and RFC 8414 extension for resource server discovery
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, determine if DPoP is required, and learn which scopes you support.
resource_url: your API's base URIauthorization_servers: list of trusted identity providersscopes_supported: all scopes your API recognizesjwks_uri: public key endpoint for JWT validationproof_types_supported: DPoP requirements and algorithms- 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, where to validate JWTs, and whether you require DPoP. RFC 8414 OAuth 2.0 Authorization Server Metadata defines the auth server metadata endpoint. RFC 9728 Protected Resource Metadata (the extension) 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_url": "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",
"response_types_supported": [
"token"
],
"proof_types_supported": {
"dpop": {
"alg_values_supported": [
"ES256",
"RS256"
]
}
},
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
],
"grant_types_supported": [
"client_credentials",
"urn:ietf:params:oauth:grant-type:token-exchange"
],
"oauth_protected_resource_version": "1.0"
}Key fields:
resource_url— your API's base URIauthorization_servers— list of auth servers that can issue tokens you acceptscopes_supported— all scopes your API recognizesjwks_uri— where to fetch public keys for JWT validationresponse_types_supported— typically just["token"]for agentsproof_types_supported— if you require DPoP, include this section with supported algorithmsgrant_types_supported— which OAuth grants clients can use (usuallyclient_credentialsandtoken-exchange)token_endpoint_auth_methods_supported— how clients authenticate to the token endpoint
Next.js Implementation
// pages/api/.well-known/oauth-protected-resource.ts
import { NextApiRequest, NextApiResponse } from 'next';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}
const metadata = {
resource_url: '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',
response_types_supported: ['token'],
proof_types_supported: {
dpop: {
alg_values_supported: ['ES256', 'RS256'],
},
},
grant_types_supported: [
'client_credentials',
'urn:ietf:params:oauth:grant-type:token-exchange',
],
token_endpoint_auth_methods_supported: [
'client_secret_basic',
'client_secret_post',
],
oauth_protected_resource_version: '1.0',
};
res.setHeader('Content-Type', 'application/json');
res.setHeader('Cache-Control', 'public, max-age=3600');
return res.status(200).json(metadata);
}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 2025-11-25 clients fetch this metadata to:
- Discover which authorization servers can issue tokens
- Validate tokens against the correct JWKS endpoint
- Determine if DPoP is required
- Learn which scopes the API supports
- Use the correct token endpoint for token exchange
When an MCP server calls your resource server, it:
- Fetches
/.well-known/oauth-protected-resource - Validates the resource URL matches
- Fetches the JWKS from the declared
jwks_uri - On every request, validates the JWT signature against JWKS and verifies
issandaudclaims - If
proof_types_supported.dpopis present, includes a DPoP proof
// 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, generate a proof
let dpopProof = null;
if (metadata.proof_types_supported?.dpop) {
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();
}Minimal Metadata for Simple APIs
If you're not using DPoP or token exchange yet, a minimal metadata document is acceptable:
{
"resource_url": "https://api.example.com",
"authorization_servers": [
"https://auth.example.com/"
],
"scopes_supported": [
"read",
"write"
],
"jwks_uri": "https://auth.example.com/.well-known/jwks.json",
"response_types_supported": [
"token"
],
"grant_types_supported": [
"client_credentials"
]
}Publishing Metadata in OpenAPI
You can reference protected resource metadata in your OpenAPI 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-resourceThis 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_urlmatches your API's base URI -
authorization_serversincludes all trusted identity providers -
scopes_supportedis exhaustive and matches your API's permission model -
jwks_uripoints to a valid JWKS endpoint with current public keys -
proof_types_supportedis included if DPoP is required -
grant_types_supportedincludes the flows your API accepts - Response includes
Cache-Controlheader with appropriate TTL - Metadata is served with
Content-Type: application/json
See Also
- DPoP — sender-constrained tokens
- Token Exchange — RFC 8693 delegation
- MCP Authorization — MCP's use of metadata
- (RFC 8414: OAuth 2.0 Authorization Server Metadata) — base specification
- (RFC 9728: OAuth 2.0 Protected Resource Metadata) — resource server extension