---
title: "Fix: your OAuth protected-resource metadata is at the wrong path for MCP clients"
slug: mcp-protected-resource-metadata-path-fix
published: 2026-11-03T13:09:33.128521+00:00
updated: 2026-11-03T13:09:33.128521+00:00
author: "M Asif Rahman"
author_url: https://ar.bd
category: "AI Readiness"
tags: check:P4, platform:mcp, oauth, rfc-9728, mcp
description: "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."
url: https://aiscan.site/blog/mcp-protected-resource-metadata-path-fix
---

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 situation | Do this | Time |
|---|---|---|
| MCP endpoint is at `/mcp`, no metadata served | Serve the file at `/.well-known/oauth-protected-resource/mcp` and set `resource` to the full endpoint URL | 10 min |
| You already serve the root file | Change its `resource` to the endpoint URL, or add the path-inserted file as well | 5 min |
| Clients still miss it | Add `resource_metadata="..."` to the `WWW-Authenticate` header on every 401 | 5 min |
| Not sure what you serve | Run `npx aiscan-cli yoursite.com` (check P4), then the three `curl` lines below | 2 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](https://www.rfc-editor.org/rfc/rfc9728#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](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) 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](https://www.rfc-editor.org/rfc/rfc9728#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`.

| Case | What the server serves | Header on 401 | Result |
|---|---|---|---|
| A | Root file, `resource` = origin | none | **Failed**: 404 on path form, then root file rejected (`resource` ≠ endpoint) |
| B | Path-inserted file only | none | OK on the first request |
| C | Root file, `resource` = endpoint | points to root | OK via header |
| D | Both files | none | OK on the first request |
| E | Path-inserted file, `resource` = origin | points to it | **Failed**: file found, then rejected |
| F | Root file, catch-all page returns 200 HTML for unknown paths | none | OK, but only because the client rejects non-JSON |
| G | Path-inserted file only | points to it | OK 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`:

```json
{
  "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](/blog/well-known-folder-404-fix) 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](https://www.rfc-editor.org/rfc/rfc9728#section-5.1) defines this parameter. With it, the client does not guess a location at all. The [OAuth discovery how-to](/blog/oauth-discovery-for-agents) 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](/blog/oauth-discovery-for-agents), 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:

```bash
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:

```bash
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](/blog/log-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](/blog/public-mcp-server-usage-60-days), and for the discovery card that sits beside this file, [publish an MCP server card](/blog/publish-mcp-server-card).

## Next step

Run the scan for check P4, fix what it reports, then browse the rest of the [AIScan fix guides](https://aiscan.site/guides) and the [capabilities check page](/docs/checks/capabilities).

