Skip to content

50% off your first year. Join before 2027. Claim →

Publish protected resource metadata

This check verifies that MCP Tower can fetch your server’s protected resource metadata, the JSON document that tells agents which authorization server signs users in.

Check id resource_metadata (resource metadata). Results:

pass fail

The protected resource metadata is the second step of every sign-in, right after the 401 challenge. It names the resource identifier and the authorization server. Without it, an agent knows that your server needs sign-in but not where to send the user, so the connection fails before any sign-in screen appears.

  • The MCP authorization specification requires every protected MCP server to publish this document (RFC 9728) and requires clients to use it to find the authorization server. Clients use the resource_metadata URL from the challenge when there is one, and otherwise try the path-suffixed well-known URL, then the root one (MCP: Authorization server discovery).
  • Claude locates your authorization server only through this document (Claude: Authentication for connectors).
  • ChatGPT fetches it from the well-known URL, or from the URL in your 401 challenge, to know where to sign in (OpenAI: Authentication).

Other agents follow the MCP specification; MCP Tower has not verified them.

MCP Tower builds a list of URLs and requests them in order, stopping at the first that works:

  1. the resource_metadata URL from your 401 challenge, if there was one;
  2. the path-suffixed well-known URL: for https://mcp.example.com/mcp that is https://mcp.example.com/.well-known/oauth-protected-resource/mcp;
  3. the root well-known URL, https://mcp.example.com/.well-known/oauth-protected-resource. For an endpoint without a path, this is the only well-known URL.

Each request is a GET with Accept: application/json. It follows up to three redirects, gives up after 10 seconds, and reads at most 64 KB. A URL works when it answers 200 with a JSON body of at most 64 KB. The result is pass for the first URL that works and fail when none does. On a failure, every check that needs the document is skipped with “Needs the protected resource metadata.”

The message lists each URL tried with its outcome in brackets. The outcome is HTTP <status> whenever the server answered, or the error class when no answer came: dns_error, connect_error, tls_error, timeout or address_blocked (the address is private or otherwise not allowed). So HTTP 200 in the list means the server answered 200 but the body was not valid JSON or was larger than 64 KB, and HTTP 301 or HTTP 302 means a fourth redirect was needed.

StatusMessageMeaningFix
passProtected resource metadata found.One of the URLs served a valid document.
failNo protected resource metadata was found. Tried: <url> (<outcome>), <url> (<outcome>).No URL answered 200 with valid JSON of at most 64 KB.Serve the document or Return JSON, not a page

The next step for the failure is: “Serve the document at <path-suffixed well-known URL> with resource and authorization_servers.”

  1. Serve a GET route at the path-suffixed URL, such as /.well-known/oauth-protected-resource/mcp. Serve the same document at the root /.well-known/oauth-protected-resource too, for clients that try only that one.
  2. Set resource to the exact URL agents connect to, and list your authorization server’s issuer in authorization_servers. Add scopes_supported if you have scopes.
  3. Answer 200 with Content-Type: application/json, and make sure the route is reachable without a token.

The document agents expect:

/.well-known/oauth-protected-resource/mcp
{
"resource": "https://mcp.example.com/mcp",
"authorization_servers": ["https://auth.example.com"],
"scopes_supported": ["mcp:tools"]
}
nginx.conf
location ~ ^/\.well-known/oauth-protected-resource(/mcp)?$ {
default_type application/json;
return 200 '{"resource":"https://mcp.example.com/mcp","authorization_servers":["https://auth.example.com"],"scopes_supported":["mcp:tools"]}';
}

When the list shows (HTTP 200) for a URL, something answered but not with the document: often a single-page app’s fallback route that returns HTML for every path, or a document over 64 KB.

  1. Open the URL and look at the body and the Content-Type.
  2. Make the route return only the metadata as JSON, ahead of any catch-all route.
  3. If the URL redirects more than three times, serve the document at the URL itself.

Agents fetch the metadata from your public hostname, mcp.example.com. MCP Tower forwards every /.well-known/ request to your origin, origin.example.com, with the same path and query, and passes your status code through, including 404. It never serves or caches the document itself, so your origin must serve it. If the resource field names the origin host, MCP Tower rewrites it to the public hostname; see resource identifier.

Request the path-suffixed URL as MCP Tower does:

Terminal window
curl -si https://mcp.example.com/.well-known/oauth-protected-resource/mcp \
-H 'Accept: application/json'

The problem is fixed when the response is a 200 with a JSON body:

HTTP/1.1 200 OK
Content-Type: application/json
{"resource":"https://mcp.example.com/mcp","authorization_servers":["https://auth.example.com"],"scopes_supported":["mcp:tools"]}

A 404 here, and at https://mcp.example.com/.well-known/oauth-protected-resource, is the failure MCP Tower reports. Then select Scan again on the server’s page.