Skip to content
verify mcp Beta VerifyMCP is currently in beta. If you notice any issues, email [email protected] and we’ll put it right.

Add OAuth (RFC 9728) auth to your MCP server

What we checked

We probed your declared endpoint to see whether (and how) it enforces authorization, following the MCP authorization spec. The outcome decides the signal:

The endpoint enforces authorisation, advertised via RFC 9728 protected-resource metadata.
Authorisation is enforced on tool calls, advertised via RFC 9728 protected-resource metadata. Discovery is public, which costs nothing: no tool can be invoked without a token.
The endpoint enforces authorisation, but returns a challenge with no valid RFC 9728 metadata, so a client cannot discover where to get a token.
Authorisation is enforced on tool calls, but the challenge carries no valid RFC 9728 metadata, so a client cannot discover where to get a token.
No authorisation is required to call this server, and it exposes a tool marked destructive
No authorisation is required to call this server. Every tool declares its destructiveHint and none is destructive, so open access doesn't expose one.
No authorisation is required to call this server, and some tools never declared a destructiveHint, so we cannot call the surface safe
Authorisation not fully verified: no authorisation is required to connect, but we couldn't read the tool list to see what that exposes.
Authorisation not yet verified: we couldn't confirm whether this endpoint requires it.
Endpoint requires authentication, so we couldn't verify the transport: it counts against the score; claim this server to verify
Schema blocked by authentication: the endpoint requires auth we don't have to read it.
Capabilities blocked by authentication: the endpoint requires auth we don't have to read them.

Why it matters

A remote MCP server is a third party you hand context and tool-calls to. If it exposes sensitive data or actions with no authorization, anyone who learns the URL can drive it. OAuth 2.0 gives each client a scoped, revocable token instead of a shared secret, and RFC 9728 lets a client discover how to get one without any out-of-band configuration.

The metadata document is the missing half of a bare challenge: a 401 alone tells a client it needs a token, but not where to obtain it. RFC 9728 points the client at your authorization server so the flow can complete automatically.

How to fix it

Two pieces are needed: a challenge that references your metadata, and the metadata document itself.

First, serve the Protected Resource Metadata at /.well-known/oauth-protected-resource. It names the resource and the authorization server(s) a client should use:

.well-known/oauth-protected-resource
{
"resource": "https://mcp.example.com",
"authorization_servers": ["https://auth.example.com"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["mcp:read", "mcp:write"]
}

Then reject unauthenticated requests with a 401 whose WWW-Authenticate header points back at that metadata. The snippets below are deliberately minimal and conceptual; check your SDK version’s docs, since recent releases of the official SDKs ship built-in authorization helpers (token verifiers, a metadata route) that wire most of this for you.

// Conceptual Express middleware. Verify the bearer token your
// authorization server issued; on failure, point the client at the
// RFC 9728 metadata so it can start the OAuth flow.
const METADATA_URL =
"https://mcp.example.com/.well-known/oauth-protected-resource";

app.use((req, res, next) => {
const token = (req.headers.authorization ?? "").replace(/^Bearer /, "");
if (!isValidToken(token)) {
  res
    .status(401)
    .set("WWW-Authenticate", `Bearer resource_metadata="${METADATA_URL}"`)
    .end();
  return;
}
next();
});

// Validate tokens against your authorization server (JWKS / introspection).
// The official SDK exposes auth helpers - prefer those over hand-rolling.

How we re-check

We re-probe your endpoint on the next crawl. Once we see a 401 challenge that references a reachable, valid RFC 9728 metadata document naming your authorization server, the check records full OAuth discovery on the following score refresh.

Give this to your AI

Paste this into Claude Code (or any coding agent) from inside your server's repository. It states the failing signal, the outcome we re-check for, and the format the fix has to take.

Prompt
Context: this repository (or its infrastructure) runs a remote MCP server
at an HTTPS endpoint. VerifyMCP's authentication check failed, which means
the endpoint either accepts tool calls with no authorization check, or
challenges with a bare `401` that doesn't tell a client where to obtain a
token.

Goal: unauthenticated requests get a `401` whose `WWW-Authenticate` header
points at a reachable RFC 9728 Protected Resource Metadata document, so the
next crawl records the check as passing.

Do this:
1. First decide whether this server needs auth at all: if everything it
   exposes is public, read-only data, an open endpoint is an accepted state
   per the guide, stop here and tell me instead of adding auth.
   You do NOT need to gate discovery. VerifyMCP probes `tools/call` as well
   as `initialize`, so a server that serves `tools/list` anonymously and
   requires a token to CALL a tool earns full marks.
   Keeping the tool list public is fine and costs nothing.
2. If it exposes private data, writes, or side effects, serve Protected
   Resource Metadata at `/.well-known/oauth-protected-resource`, naming
   the `resource` and the `authorization_servers` a client should use.
3. Reject requests without a valid bearer token with a `401` whose
   `WWW-Authenticate` header includes `resource_metadata` pointing at
   that document, and verify tokens against the authorization server
   (JWKS or introspection); prefer the SDK's built-in auth helpers.
4. If any tool declares `annotations.destructiveHint: true`, treat it as
   the priority: it must require a valid token to be CALLED, since leaving
   it open is the one authorization state we fail outright.

Rules:
- Adding auth changes who can call this server. Confirm the auth
  requirement and the chosen authorization server with the human before
  shipping; a botched rollout can lock out legitimate clients.
- Never try to pass the check by publishing metadata that points at an
  authorization server that doesn't actually verify tokens.
- Don't add auth to a server that's genuinely public and read-only, that's
  a legitimate pass, not a gap to close.

Report back: the files changed, the metadata URL, and the curl command
showing a token-less request now gets a `401` with `WWW-Authenticate`
referencing that metadata.

Reference: https://verifymcp.io/docs/remote/authentication

Written by Stuart Blackler · Last reviewed 30 June 2026.