Skip to content

feat(server): add protected resource metadata discovery scenario - #572

Open
JosephDoUrden wants to merge 1 commit into
modelcontextprotocol:mainfrom
JosephDoUrden:feat/server-auth-prm-discovery
Open

JosephDoUrden wants to merge 1 commit into
modelcontextprotocol:mainfrom
JosephDoUrden:feat/server-auth-prm-discovery

Conversation

@JosephDoUrden

Copy link
Copy Markdown
Contributor

closes #565

adds auth/protected-resource-metadata, a server scenario. the harness plays a client with no token. one unauthenticated post, then metadata discovery in client order: resource_metadata from the challenge, the path well-known url, the root one.

six checks come out of that. server-auth-401: 401 plus a WWW-Authenticate header. server-prm-discovery: the metadata can be found; at 2025-06-18 the challenge url is required, from 2025-11-25 a well-known document on its own is fine. server-prm-response: 200, application/json, json object body (rfc 9728 §3.2).

server-prm-resource: resource identifies the server (§3.3); at the root well-known url the origin is accepted too, same as the ts sdk. server-prm-authorization-servers: non-empty array of urls. server-www-authenticate-scope: scope in the challenge, warning only; skipped at 2025-06-18, the should first appears in 2025-11-25.

a 2xx to the probe means the server has no auth. every check then reports "not testable" per #248, never skipped.

every request is bounded to 10 s, headers and body. a server that holds the sse stream open, or never ends the json body, fails the checks it blocks with a reason naming the timeout. the suite does not hang. two tests cover it with a mock server.

the fetch and parse logic sits in src/scenarios/server/auth/prm.ts. the challenge parser handles multiple challenges, quoted pairs, a token68 challenge before the bearer one, and an unquoted resource_metadata value, which shows up in the wild. a url named by two sources is fetched once.

fixture: examples/servers/typescript/auth-prm-server.ts, port 3013. PRM_BREAK picks one defect per check. unknown values exit with the allowed list. readme has the table. 25 vitest cases.

against the real ts sdk (typescript-sdk/examples/oauth/server.ts): 5/5 plus the scope warning at 2025-11-25 and 2026-07-28, scope skipped at 2025-06-18. note --url must match the server's own resource exactly. localhost against a server that names 127.0.0.1 fails server-prm-resource, which is what rfc 9728 §3.3 asks for.

starts in the pending suite. the everything-servers do not run with auth on. targeted run: npm start -- server --scenario auth/protected-resource-metadata --url http://localhost:3013/mcp

on the dpop stack (#395, #527, #528, #529): those branches also add src/scenarios/server/auth/spec-references.ts, so there is an add/add conflict on that one file. the three rfc 9728 entries are identical. happy to rebase whichever lands second. the helper here was written in parallel, not extracted from #529.

spec refs point at the dated 2025-11-25 pages, each anchor checked.

Add auth/protected-resource-metadata, a server scenario that probes an MCP
endpoint without a token and grades the Protected Resource Metadata it
publishes (RFC 9728): the 401 challenge, discovery via resource_metadata or
the well-known URIs, the 200 application/json response, the resource field
and a non-empty authorization_servers list, plus a SHOULD-level scope check
that is SKIPPED at 2025-06-18, where the spec says nothing about scope in
the challenge. A 2xx to the unauthenticated probe reports every check
untestable.

The fetch and parse logic lives in a standalone helper under
src/scenarios/server/auth/prm.ts so other server auth scenarios can reuse
it. Every request is bounded to 10 s, headers and body, so a server that
holds a stream open fails with a reason naming the timeout instead of
hanging the suite. Discovery candidates are fetched once per URL, and the
challenge parser survives a token68 challenge and an unquoted
resource_metadata value.

The scenario starts in the pending suite because the everything-server
does not run with authorization enabled; an auth-enabled fixture with one
break mode per check backs the vitest cases.

Closes modelcontextprotocol#565
@pkg-pr-new

pkg-pr-new Bot commented Nov 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

npx https://pkg.pr.new/@modelcontextprotocol/conformance@572

commit: f068cdb

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.

Server Auth: Protected Resource Metadata discovery (RFC 9728)

1 participant