Skip to content

test(oauthserver): add an MCP authorization conformance suite - #2914

Draft
shanurwan wants to merge 2 commits into
supabase:masterfrom
shanurwan:test/mcp-authorization-conformance
Draft

shanurwan wants to merge 2 commits into
supabase:masterfrom
shanurwan:test/mcp-authorization-conformance

Conversation

@shanurwan

Copy link
Copy Markdown

What

A test suite that checks the OAuth 2.1 server against what the MCP authorization specification requires of an authorization server. It drives the full router the way MCP clients (Claude, ChatGPT, Claude Code) do: discovery, registration, authorize, consent, token and refresh. Each of the 18 requirements cites the specification.

It also fixes one gap it found: token responses did not send Cache-Control: no-store (OAuth 2.1 §3.2.3).

Why

I run an MCP server (Counsel) behind this OAuth server, and Claude signs people in to it. It works, but every access token carries aud: "authenticated" whatever resource the client asked for, so the server can't tell a token issued for it from one issued for another MCP server on the same project. I had to turn the audience check off. The MCP Python SDK warns that validate_token_resource becomes the default in 3.0; servers built on it would then refuse every token from Supabase Auth. I wanted to know exactly which requirements hold today, and to know when that one is fixed.

Several open PRs close MCP sign-in gaps (#2871, #2894, #2895). This gives them a shared definition of done. A known gap is a pending test that names its issue and PR. It is skipped while the gap exists, and fails with "now conforms; remove the pending marker" once it is closed, so the list stays accurate.

Results on master (with the fix)

The open PRs, each merged onto this branch

On master, AS-14 also shows that a form-encoded token request for a different resource than the authorized one is accepted, because the check only reads JSON bodies. #2894 fixes this.

Not covered

Fetching Client ID Metadata Documents, DPoP, and the consent page itself, which each project builds.

Testing

go test ./internal/api/ -run TestMCPAuthorizationConformance -v
go test ./internal/api/... -p 1

Both pass locally against Postgres 15.

OAuth 2.1 section 3.2.3 requires token responses to carry
Cache-Control: no-store, because they contain credentials. The header
is set before any response is written, so error responses carry it
as well.
MCP clients such as Claude, ChatGPT and Claude Code sign users in to an
MCP server through its authorization server. This suite drives the full
router the way they do (discovery, registration, authorize, consent,
token and refresh) and checks each requirement the MCP authorization
specification places on the authorization server, with a reference to
the specification on every requirement.

Known gaps carry a pending reference to their issue and proposed fix.
Their tests are skipped while the gap exists, and fail once it is
closed, so the marker is removed by the change that closes it.
@shanurwan

Copy link
Copy Markdown
Author

Split the Cache-Control: no-store fix into #2919 so it can land on its own. Once that merges I'll rebase this, and it will be only the conformance suite (AS-11 checks the header, so it relies on #2919).

@shanurwan
shanurwan marked this pull request as draft November 10, 2026 13:31

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant