OAuth 2.1 removed four things from OAuth 2.0 and marked them not safe to keep. An authorization server that still ships them is out of spec. MCP inherits OAuth 2.1 verbatim, so an MCP server that sits behind such an authorization server is out of spec too.
You cannot see any of this in the metadata document. The metadata says what the server supports, not what it refuses. The only way to tell is to send a request and see what comes back. That is what the four new checks do.
What OAuth 2.1 took out
OAuth 2.1 is a consolidation of OAuth 2.0 best current practice into one spec. Four items from 2.0 did not survive.
- PKCE is now required on every authorization code flow, not just on public clients. OAuth 2.0 allowed confidential clients to skip it. OAuth 2.1 does not.
- The
plaincode challenge method is gone. OnlyS256is allowed. - The implicit grant (
response_type=token) is removed. Access tokens delivered in a URL fragment leak into browser history and referer headers. - Redirect URIs are compared by exact string match. Prefix match or host-only match turns the authorization server into an open redirector for code exfiltration.
Why metadata does not tell you
You can look at the authorization server metadata all day and never know whether these rules are enforced. code_challenge_methods_supported says the server knows about PKCE. It does not say the server rejects a request that comes without a challenge. There is no metadata field that says I removed the implicit grant. There is no field that says I enforce exact-match redirect_uri comparison.
The MCP authorization spec makes PKCE mandatory, but only the authorization server can answer whether it actually applies the rule. So you have to ask it.
How the probe works
The new checks are opt-in because they write to the authorization server. Run them with one flag on an MCP server you operate:
npx -y @patchbay-nebius/agent audit https://mcp.example.com/mcp --probe-headless-oauthThe flag does three things, in this order:
- Register a throw-away client via Dynamic Client Registration with
redirect_uri=http://127.0.0.1:33419/callback. The client hasapplication_type: nativeandtoken_endpoint_auth_method: none. - Send four crafted
/authorizerequests with that client_id, one per OAuth 2.1 rule to test. The requests follow redirects in manual mode, so the scanner can read theLocationheader. - Decide, for each request, whether the server rejected it (good), accepted it (bad) or produced a response the scanner cannot judge from the outside.
No tool is ever called. No access token is ever redeemed. The probe stops at the authorization response and never touches the token endpoint.
What each check looks for
PKCE-001 sends an /authorize request with no code_challenge. If the server responds with a redirect that carries an authorization code, PKCE is not enforced. Fires critical.
PKCE-002 sends a request with code_challenge_method=plain. If a code comes back, the server still accepts the removed method. Fires high.
IMPLICIT-001 sends a request with response_type=token. If the response delivers an access token in the URL fragment, the implicit grant is still on. Fires critical.
REDIRECT-002 registers the client with http://127.0.0.1:33419/callback and then asks for a redirect to https://attacker.example.com/callback. If the server redirects there or issues a code, it does not compare the URI by exact match. Fires critical.
Why you should run this on your own server
Most of the checks patchbay has had so far look at what a server publishes. These four look at what it actually does when a client hands it a bad request. That is a different class of bug. A server can publish perfect metadata and still issue codes without PKCE on an old code path that was never updated, because the OAuth library upstream had a permissive default. The metadata will not catch that. These checks will.
If you run an MCP server that uses a hosted identity provider, run the probe against your own setup once. The flag only writes one client registration and does not leak anything to anyone. The alternative is to find out the hard way on the next penetration test, or after the one that finds a stolen code.
The full list of what the scanner now runs is on the scan page. 26 checks live, one retired. The four new ones are opt-in; the other 22 run by default.