Scanner

What a server's metadata says, measured every night

The scanner reads the public authorization metadata an MCP server publishes for every client and judges the 14 requirements of MCP Authorization 2026-07-28 that metadata can answer. Nothing else is sent: no token, no authorization flow, no protocol message; the one request to the endpoint itself carries no credentials and only its status and headers are read. Results land in the atlas as observations, by pull request.

Servers listed
39
Scanned
36
Checks per server
14
Newest scan

How it works

What it reads

  1. The protected resource metadata (RFC 9728) at the path-aware well-known location derived from the server URL, then at the root.
  2. The challenge the endpoint returns to an unauthenticated request: the status and the WWW-Authenticate header, which RFC 9728 and the MCP specification define as the other way to announce that metadata, and whose scopeparameter names the scopes to ask for. First one GET; when that is routed elsewhere (a streamable HTTP server may answer GET with 405, a landing page with a redirect), one POST with an empty body, which carries no JSON-RPC message. The response body is never read. When the well-known locations had nothing and the challenge names a resource_metadata URL, that document is fetched.
  3. For the first authorization server the metadata names: its metadata at the three well-known locations the MCP specification lists (RFC 8414 and OpenID Connect Discovery, path-inserted and path-appended).

At most 8 requests over HTTPS, no credentials, no body sent, redirects followed only on the same host. It identifies itself as letsauth-scanner/0.4.0 (+https://letsauth.eu/mcp/scanner/). The challenge is the only thing read from the endpoint, and it is what every anonymous client receives; that is why it still counts as a passive scan (why, and why the empty POST is too).

What a status means

Supported and unsupported are written only when a document was fetched and the deciding field is present or absent.Unknown means the scanner could not tell: the document could not be fetched, no authorization server was named, or nothing was advertised to judge. A fetch failure is never written as a failure of the server.

A requirement a server can announce in more than one place is read from each in turn, the server's own metadata first, then its challenge, then its authorization server, and the evidence names the one that decided.

Every observation starts with the URL that decided it and names the field and value, so a reviewer can open it.

How the grade is derived

  1. F when a MUST-level check is unsupported or partial.
  2. Withheld (no letter) when a MUST-level check is unknown: the scanner could not establish it.
  3. C when every MUST-level check is supported and a SHOULD-level check is unsupported or partial.
  4. B when every MUST-level check is supported, no SHOULD-level check fails, and a SHOULD-level check is unknown.
  5. A when every MUST-level and SHOULD-level check is supported.
  6. A MUST the server may meet in either of two ways is met when either way is supported, and counts as unknown, not unsupported, while the other way is unknown: protected resource metadata may be served at a well-known location or announced in the WWW-Authenticate challenge the endpoint returns to an unauthenticated request; the scanner reads both.
  7. MAY-level and deprecated requirements are listed and never change the grade.
  8. Servers whose auth class is API key or no authentication are not graded: there is no OAuth authorization to check. The class is an evidenced statement on the server, not the directory's claim.

The grade is a property of one report at one time, derived from the statuses shown. The atlas never sorts or ranks servers by it.

Where the results show

The scanner has no list of its own. Its results live in theMCP auth atlas: the letter next to each server name in the matrix is that server's latest grade, and every server page carries the same grade with a link to the full report at /mcp/servers/<server>/scan/, which lists each of the 14 checks with the metadata it read. A server without a scanner observation reads "not scanned": the nightly run has not yet been able to read its metadata, or has not run since it was listed. Servers whose auth class is API key or no authentication read "no OAuth" and are not scanned; a server whose class is not yet stated is scanned, and the run summary says what its endpoint's answer suggests.

Run it yourself

The same command runs the nightly job and your terminal:

pnpm scan https://mcp.example.com/mcp   # any server, nothing written
pnpm scan notion                        # a listed server by id
pnpm scan notion --write                # append what the nightly rule would

--write appends observations to data/observations/<id>.yamlfollowing the same append rule as the nightly run; open a pull request with the result. See docs/scanner.md.

What it does not check

9 server-side requirements need a token, a scope or a reading of the implementation and stay out of a passive scan: for exampleDocumented pre-registration path, 403 insufficient_scope for step-up, Refresh token rotation for public clients. Their cells come from documentation and manual checks until active probes exist for verified owners.

Opting out

If you run a listed server and do not want it scanned, withdraw it from the atlas; a withdrawn server is never contacted again. The scanner only reads what a server publishes for every client: its metadata documents and the challenge it sends to anyone without a token.