Skip to content

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

Return a 401 that points agents to your metadata

This check verifies that a request without a token gets a 401 whose WWW-Authenticate header tells agents where your protected resource metadata lives.

Check id challenge. Results:

pass warn fail skipped

The 401 is the start of every sign-in. An agent that gets one reads the WWW-Authenticate header to learn that the server uses OAuth and where its metadata is. Without a Bearer challenge there, the agent has nothing to follow, and the user sees a connection error instead of a sign-in prompt.

  • Claude starts sign-in only when a request fails with 401 and a WWW-Authenticate header. It ignores the header on any other status, including 200 (Claude: Authentication for connectors). Without resource_metadata, Claude can still probe the well-known locations, but its documentation calls that a fallback that works only when your platform serves them.
  • ChatGPT finds your metadata either at the well-known URL or from the resource_metadata in a 401 challenge (OpenAI: Authentication).
  • The MCP authorization specification requires clients to use resource_metadata when it is present and to fall back to the well-known URLs otherwise. Other agents, such as Gemini CLI, follow it; MCP Tower has not verified them.

A server whose tools all work without a token does not need a challenge on initialize or tools/list. It still needs one when a protected tool is called; see call-time responses.

  1. MCP Tower connects to your MCP endpoint without a token, as an agent does: server/discover, or initialize for servers on protocol 2025-11-25, then tools/list.
  2. If the server answers every request without asking for a token, there is no challenge to check, and the result is skipped.
  3. If the server answers 401 or 403, MCP Tower parses its WWW-Authenticate header:
    • no header, or a scheme other than Bearer (such as Basic): fail;
    • a Bearer challenge without the resource_metadata parameter: warn;
    • a Bearer challenge with resource_metadata: pass. An optional scope parameter is read as the scopes the server asks for, and error is read on a 403.

MCP Tower then fetches the metadata from the resource_metadata URL first, before the well-known locations; see protected resource metadata.

StatusMessageMeaningFix
passThe 401 response carries a Bearer challenge with resource_metadata.Agents can start sign-in.
failThe <status> response has no Bearer challenge in WWW-Authenticate.The 401 or 403 has no WWW-Authenticate header, or one with another scheme.Send a Bearer challenge
warnThe 401 response’s Bearer challenge has no resource_metadata parameter. Agents have to guess the metadata location.Agents fall back to the well-known URLs, which works only if you serve them.Add resource_metadata
skippedThe server listed tools without a token, so there was no 401 challenge to check.Your server is open without a token. Protected tools must still answer 401 when called.Call-time responses

The next step for the failure is: “Answer unauthenticated requests with 401 and WWW-Authenticate: Bearer resource_metadata=”…/.well-known/oauth-protected-resource” so agents can find your authorization server.” For the warning it is: “Add resource_metadata=“<path-suffixed well-known URL>” to the 401 response so agents can find your authorization server.” For an endpoint at https://mcp.example.com/mcp, that URL is https://mcp.example.com/.well-known/oauth-protected-resource/mcp.

  1. Find where your server rejects requests without a token. It is usually an authentication middleware in front of your MCP route.
  2. Make it answer 401 Unauthorized, not 403, 302 to a login page, or 200 with an error in the body.
  3. Add a WWW-Authenticate header with the Bearer scheme and the resource_metadata parameter. Add scope with the scopes a first sign-in needs, if you have any.

The response agents expect:

401 response
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp", scope="mcp:tools"
Content-Type: application/json
{"error":"invalid_token","error_description":"Sign in to use this server"}
nginx.conf
# With auth_request in front of /mcp, a 401 from the auth service
# gets the challenge before it reaches the agent.
location /mcp {
auth_request /_auth;
error_page 401 = @challenge;
proxy_pass http://origin.example.com;
}
location @challenge {
add_header WWW-Authenticate 'Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp", scope="mcp:tools"' always;
return 401 '{"error":"invalid_token"}';
}

If you already send Bearer without parameters, add resource_metadata with the absolute URL of your protected resource metadata. Use the path-suffixed URL, which names your MCP path after /.well-known/oauth-protected-resource, and make sure the document is served there. Quote the value, and separate parameters with a comma.

Agents reach your server through your public hostname, mcp.example.com, while your server runs at origin.example.com. If your server builds resource_metadata from its own host, MCP Tower rewrites the scheme, host and port in that parameter to the public hostname and keeps the path. A URL that already names the public hostname, or any other host, is left as it is.

Send the request an agent sends first, without a token:

Terminal window
curl -si https://mcp.example.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

The problem is fixed when the response starts like this:

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

A 200 means the server is open without a token, and the check is skipped. Then select Scan again on the server’s page.