Skip to content

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

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

JosephDoUrden wants to merge 2 commits into
modelcontextprotocol:mainfrom
JosephDoUrden:feat/server-auth-prm-discovery

Conversation

@JosephDoUrden

@JosephDoUrden JosephDoUrden commented Nov 5, 2026 •

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.

seven 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-prm-no-empty-parameters: array params §2 defines are omitted, not [] (§3.2); bearer_methods_supported is exempt since §2 allows [] there, and params §2 doesn't define are skipped. 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 at a time. empty-authorization-servers fails two checks by design, server-prm-authorization-servers and server-prm-no-empty-parameters, and empty-bearer-methods-supported has to pass. unknown values exit with the allowed list. readme has the table. 44 vitest cases.

against the real ts sdk (typescript-sdk/examples/oauth/server.ts): 6/6 plus the scope warning at 2025-11-25 and 2026-07-28, scope skipped at 2025-06-18. a throwaway server with the same wiring on the sdk's mcpAuthMetadataRouter gets the same result without scopesSupported. with scopesSupported: [] it serves "scopes_supported":[] and fails server-prm-no-empty-parameters only, 5/6 at all three versions. 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: a9aede2

@PederHP

PederHP commented Nov 6, 2026

Copy link
Copy Markdown
Member

One RFC 9728 requirement the scenario doesn't cover yet: §3.2 says "Parameters with zero values MUST be omitted from the response." The checks already enforce other parts of §3.2 (200, application/json, JSON object body). empty-authorization-servers fails, but because of the MCP "at least one authorization server" rule, not because of §3.2. An empty scopes_supported: [] passes every check today.

This came up in the C# SDK: modelcontextprotocol/csharp-sdk#1999 fixes the SDK's protected resource metadata emitting "scopes_supported": [] when no scopes are configured. That suggests it's an easy mistake when the metadata model defaults list properties to empty collections, which may be common across SDKs.

One exception to keep in mind: bearer_methods_supported is exempt. §2 says "The empty array [] can be used to indicate that no bearer methods are supported."

A few questions, since I'm not sure where this fits best:

  1. Check placement: should it go into server-prm-response, alongside the other §3.2 requirements, or into a separate check (e.g. server-prm-no-empty-parameters) so the failure reason stays specific?
  2. Severity: it's a MUST in the RFC the spec incorporates, which points to FAILURE. But I'd expect most clients to treat [] the same as a missing parameter in practice. Is WARNING more appropriate, at least while the scenario sits in pending?
  3. Scope: does this belong in this PR, or would you rather keep feat(server): add protected resource metadata discovery scenario #572 as scoped in Server Auth: Protected Resource Metadata discovery (RFC 9728) #565 and handle it in a follow-up? Happy to open an issue if a follow-up is preferred.

If it does go in, an empty-scopes-supported break mode in auth-prm-server.ts would cover it. A test that a document with bearer_methods_supported: [] still passes would guard the exemption.

🤖 Generated with Claude Code

RFC 9728 §3.2: "Parameters with zero values MUST be omitted from the
response." scopes_supported: [] passed every check. Add
server-prm-no-empty-parameters as its own check, FAILURE per the MUST.

It covers the array parameters §2 defines. bearer_methods_supported is
left out because §2 allows [] there. Parameters §2 does not define are
left out too: the MUST has no carve-out for them, but the harness cannot
know what [] means in someone else's parameter, and §3.2 tells readers
to ignore what they do not understand. authorization_servers: [] now
fails this check as well as the MCP at-least-one rule, since it breaks
both.

Add empty-scopes-supported and empty-bearer-methods-supported fixture
modes, plus in-process tests for the remaining parameters, for falsy
non-array values including null, and for non-object bodies.
@JosephDoUrden

Copy link
Copy Markdown
Contributor Author

added in a9aede2, so scopes_supported: [] fails now.

  1. separate check, server-prm-no-empty-parameters. a server that fails this has nothing wrong with its status, content type or body shape, and a baseline can name it on its own.
  2. FAILURE. AGENTS.md ties severity to the spec keyword, and pending doesn't change that: http-header-validation is pending and still has 400 (MUST) as FAILURE and -32020 (SHOULD) as WARNING. while it's pending no sdk is gated on this, the default suite skips it. you're right about clients, the ts sdk falls through [] the same as a missing param, but that doesn't change the keyword.
  3. this PR, same section as the checks Server Auth: Protected Resource Metadata discovery (RFC 9728) #565 asked for. one check and two fixture modes, empty-scopes-supported and empty-bearer-methods-supported, the second guards the exemption.

what it covers:

  • the array params §2 defines, minus bearer_methods_supported. that adds resource_signing_alg_values_supported, authorization_details_types_supported and dpop_signing_alg_values_supported, same as your note on csharp-sdk#1999
  • authorization_servers: [] now fails this check and server-prm-authorization-servers, since it breaks both rules
  • empty strings and false pass. the sentence before says multiple values are JSON arrays, and rfc 8414 §3.2 has the same pair as "zero elements", so I read it as arrays only
  • params §2 doesn't define are skipped. the MUST has no carve-out for them, but the harness can't know what [] means in someone else's param, and readers are told to ignore what they don't understand. that includes keywords and contacts, which OpenID Federation registered with IANA
  • scopes_supported: null still passes, nothing here checks types on these params. I read it as a type problem, not zero values, but it's the output you flagged on csharp-sdk#1999 and the ts sdk client rejects the whole document on it. say if you want it failed here

ran it on real ts sdk servers. mcpAuthMetadataRouter with scopesSupported: [] serves "scopes_supported":[] and fails this check only, 5/6. without it 6/6, same for examples/oauth/server.ts.

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)

2 participants