AIScan fix guide cover: Protected-resource metadata, wrong path. A failing page beside a passing page.
AIScan fix guide cover: Protected-resource metadata, wrong path. A failing page beside a passing page.
AI Readiness

Fix: your OAuth protected-resource metadata is at the wrong path for MCP clients

MCP clients look for RFC 9728 metadata at a path-inserted URL first. Serve it there, match the resource value, and add resource_metadata to the 401.

3 Nov 2026 6 min read
#oauth#rfc-9728#mcp

This guide covers P4 · Capabilities.

Table of contents

An MCP client that gets a 401 from https://api.example.com/mcp has to find your OAuth metadata somewhere, and RFC 9728 gives it two places to look: /.well-known/oauth-protected-resource/mcp (the path-inserted form) and /.well-known/oauth-protected-resource (the root form). If your server answers only one of them, or answers with the wrong resource value, the client stops before it ever sees your authorization server. In a seven-case lab run on 3 November 2026, two of the seven setups failed discovery, and neither printed an error on the server side.

TL;DR

Your situationDo thisTime
MCP endpoint is at /mcp, no metadata servedServe the file at /.well-known/oauth-protected-resource/mcp and set resource to the full endpoint URL10 min
You already serve the root fileChange its resource to the endpoint URL, or add the path-inserted file as well5 min
Clients still miss itAdd resource_metadata="..." to the WWW-Authenticate header on every 4015 min
Not sure what you serveRun npx aiscan-cli yoursite.com (check P4), then the three curl lines below2 min

Short answer: serve the file at the path-inserted URL, put the endpoint's full URL in resource, and point to it from the 401 header. Do all three and client order stops mattering.

Why one file is not always enough

RFC 9728 section 3.1 says that when the resource identifier has a path, the client inserts /.well-known/oauth-protected-resource between the host and the path. For https://api.example.com/mcp that gives /.well-known/oauth-protected-resource/mcp. The root form is the special case for a resource with no path.

The MCP authorization spec tells clients to use the resource_metadata URL from the WWW-Authenticate header when it is present. Without the header they must try the path-inserted URL first and the root URL second. So a server that has only the root file works for clients that fall back, and a server that has only the path-inserted file works for clients that ask first. A client with a different order, or one that skips the fallback, sees a 404.

The second trap is validation. RFC 9728 section 3.3 says the resource value in the file must be identical to the resource identifier the URL was built from, and a mismatch means the client MUST NOT use the data. Copying the root example, where resource is the bare origin, onto an /mcp endpoint fails that check.

The lab

I ran a small Python server with seven configurations and a client that follows the MCP order (header first, then path-inserted, then root) and enforces section 3.3. It is a simulator of the spec, not of any particular product, so a real client may differ. Each server protected /mcp and returned 401.

CaseWhat the server servesHeader on 401Result
ARoot file, resource = originnoneFailed: 404 on path form, then root file rejected (resource ≠ endpoint)
BPath-inserted file onlynoneOK on the first request
CRoot file, resource = endpointpoints to rootOK via header
DBoth filesnoneOK on the first request
EPath-inserted file, resource = originpoints to itFailed: file found, then rejected
FRoot file, catch-all page returns 200 HTML for unknown pathsnoneOK, but only because the client rejects non-JSON
GPath-inserted file onlypoints to itOK via header

Case F is the quiet one. A single-page app that answers every unknown path with 200 text/html makes the path-inserted probe look like it succeeded. My client checks the content type and moves on. A client that does not will try to parse HTML as JSON, which I did not test.

Fix it

1. Serve the path-inserted file

Return JSON with Content-Type: application/json at the path that matches your endpoint. For an endpoint at /mcp:

{
  "resource": "https://api.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"]
}

Serve it at /.well-known/oauth-protected-resource/mcp. If your endpoint is /v1/mcp, the file goes at /.well-known/oauth-protected-resource/v1/mcp. How you register that route depends on your framework, and a dot-leading folder is often ignored by static hosts, which the .well-known 404 guide covers.

2. Make resource match exactly

Scheme, host, port and path must equal the endpoint URL the client calls, with no trailing-slash difference. A root file may stay, but only if its resource is the same endpoint URL.

3. Point to the file from the 401

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

RFC 9728 section 5.1 defines this parameter. With it, the client does not guess a location at all. The OAuth discovery how-to explains the rest of the chain (authorization server metadata, scopes).

Verify it

AIScan is the quickest route: run npx aiscan-cli yoursite.com and read check P4 (capabilities). Per our OAuth discovery guide, P4 grades the root file only, so a server with just the path-inserted file can pass your MCP clients and still show a P4 gap. Serve both to satisfy both.

If you would rather check by hand, run these three:

curl -sS -o /dev/null -w "%{http_code} %{content_type}\n" https://api.example.com/.well-known/oauth-protected-resource/mcp
curl -sS -o /dev/null -w "%{http_code} %{content_type}\n" https://api.example.com/.well-known/oauth-protected-resource
curl -sSI https://api.example.com/mcp | grep -i www-authenticate

Expected: 200 application/json on the path-inserted URL, 200 application/json or 404 on the root, and a WWW-Authenticate line containing resource_metadata=. Then confirm the file's resource equals the endpoint:

curl -sS https://api.example.com/.well-known/oauth-protected-resource/mcp | jq -e '.resource == "https://api.example.com/mcp"'

true means it matches. A 200 text/html on either well-known URL means your app's catch-all route is answering, so fix the route.

Exclusions and upkeep

  • If your MCP server sits at the site root (https://example.com/), there is no path to insert, and the root file is the right one.
  • Servers that need no authentication need none of this.
  • Recheck after any proxy or router change. Path-based routing rules are the usual way the .well-known path stops reaching your app.
  • Log the requests that hit these paths, as in logging MCP server requests, to see which client asks where.

For background on how often servers are called at all, see what 60 days of a public MCP server showed, and for the discovery card that sits beside this file, publish an MCP server card.

Next step

Run the scan for check P4, fix what it reports, then browse the rest of the AIScan fix guides and the capabilities check page.

Frequently asked questions

What is the difference between /.well-known/oauth-protected-resource and /.well-known/oauth-protected-resource/mcp?

The first is the root form, for a resource with no path. The second is the path-inserted form RFC 9728 section 3.1 defines for a resource such as https://api.example.com/mcp. The MCP spec tells clients to try the path-inserted URL first and the root URL second when the 401 carries no resource_metadata.

Do I need both files?

No. Serve the path-inserted file, point to it from the WWW-Authenticate header, and every client that follows the spec finds it. Keep a root file only if its resource value equals the endpoint URL, and note that AIScan check P4 reads the root file.

My client says it cannot find protected resource metadata. What do I check first?

Request /.well-known/oauth-protected-resource/mcp with curl. You want 200 and application/json. A 404 means the route is missing, and 200 text/html means a catch-all page is answering instead of your file.

The metadata file returns 200 but the client rejects it. Why?

Most likely the resource value does not equal the endpoint URL. RFC 9728 section 3.3 says a client must not use metadata when the two differ. Compare scheme, host, port, path and any trailing slash.

Why does a copy of the RFC example fail on my /mcp endpoint?

The example for a path-less resource sets resource to the bare origin. On an endpoint with a path, the identifier includes the path, so the copied value mismatches and the file is discarded.

Does the WWW-Authenticate header replace the well-known file?

No. The header only tells the client where the file is. The file still has to exist at that URL and carry the right resource value.

My MCP server is at the site root. Does path insertion apply?

No. With no path in the resource identifier there is nothing to insert, so /.well-known/oauth-protected-resource is the correct and only location.

The path-inserted URL returns 404 after I moved behind a proxy. What happened?

The proxy or router probably forwards only the MCP route to your app. Add a rule that sends /.well-known/oauth-protected-resource/* to the service that builds the file, then recheck with the three curl commands.

M Asif Rahman

About the author

Entrepreneur, curious about AI

I have been building companies for a long time, and lately most of my curiosity goes to AI. AIScan is a small personal project I built on a flight from Dubai to Narita. I use it to understand how ready the web is for AI agents, and I share what the scans teach me.

Related guides

Browse every guide by check and platform