What happened?
discoverAuthorizationServerMetadata() picks its validation schema from which well-known filename resolved, not from the document that came back. Every openid-configuration candidate is typed "oidc", so anything served there is validated against OpenIdProviderDiscoveryMetadataSchema:
// client/dist/index.mjs (2.0.0)
const parsed = type === "oauth"
? OAuthMetadataSchema.parse(await response.json())
: OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
That schema requires jwks_uri, subject_types_supported and id_token_signing_alg_values_supported — three fields OpenID Connect Discovery 1.0 requires and RFC 8414 does not. So a plain OAuth 2.0 authorization server (no ID tokens, no JWKS, no sub) that publishes RFC 8414 metadata at /.well-known/openid-configuration is rejected:
[
{"expected":"string","code":"invalid_type","path":["jwks_uri"],"message":"Invalid input: expected string, received undefined"},
{"expected":"array","code":"invalid_type","path":["subject_types_supported"],"message":"Invalid input: expected array, received undefined"},
{"expected":"array","code":"invalid_type","path":["id_token_signing_alg_values_supported"],"message":"Invalid input: expected array, received undefined"}
]
Two things make this fatal rather than cosmetic:
- RFC 8414 §5 explicitly permits that filename for general OAuth metadata. The server is conforming; the client is not.
- The parse throws instead of continuing the candidate loop. Every other failure in that loop (
continue on 4xx/502, continue on a CORS failure) falls through to the next candidate. A schema failure aborts discoverAuthorizationServerMetadata() entirely, so no later candidate is tried and the whole connection fails.
Reported downstream at modelcontextprotocol/inspector#2172 against a real server (https://misc.poodll.com/mod/minilesson/mcp.php, a plain OAuth 2.0 AS whose resource lives under a path). Reproduced independently against @modelcontextprotocol/client@2.0.0 with an injected fetchFn, and again against a local test server that serves RFC 8414 metadata only at /.well-known/openid-configuration.
Two smaller things noticed in the same code path, mentioned here rather than as separate issues since a fix will likely touch both:
OpenIdProviderDiscoveryMetadataSchema is a z.object(...), not a z.looseObject(...) like OAuthMetadataSchema. So a successful OIDC parse strips RFC 8414 fields that are not in the OIDC shape — revocation_endpoint and introspection_endpoint among them — from the returned metadata.
- The doc comment on
discoverAuthorizationServerMetadata says it "attempts RFC 8414 OAuth metadata discovery first / if OAuth discovery fails, falls back to OpenID Connect Discovery", which is what the URL ordering does but not what the schema selection does.
What did you expect?
A document should be validated by what it is, not by the filename it was found under. Concretely, either:
- try
OAuthMetadataSchema and fall back to OpenIdProviderDiscoveryMetadataSchema (or vice versa) regardless of the candidate's type — OAuthMetadataSchema is a loose object, so it accepts a full OIDC document without dropping any of its fields; or
- treat a schema failure like the other per-candidate failures and
continue to the next candidate rather than throwing out of the whole function.
Either way, issuer validation (RFC 8414 §3.3 / OIDC Discovery §4.3) should stay exactly as it is.
Code to reproduce
import { discoverAuthorizationServerMetadata } from '@modelcontextprotocol/client';
// A plain OAuth 2.0 AS: RFC 8414 metadata, served only at the OIDC well-known path.
const METADATA = {
issuer: 'https://as.example.com',
authorization_endpoint: 'https://as.example.com/oauth/authorize',
token_endpoint: 'https://as.example.com/oauth/token',
response_types_supported: ['code'],
grant_types_supported: ['authorization_code', 'refresh_token'],
code_challenge_methods_supported: ['S256'],
};
const fetchFn: typeof fetch = async input => {
const url = String(input);
if (url === 'https://as.example.com/.well-known/openid-configuration') {
return new Response(JSON.stringify(METADATA), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
return new Response('not found', { status: 404 });
};
// Throws a ZodError for jwks_uri / subject_types_supported /
// id_token_signing_alg_values_supported. Expected: the RFC 8414 document.
await discoverAuthorizationServerMetadata('https://as.example.com', { fetchFn });
SDK version
@modelcontextprotocol/client@2.0.0 (with @modelcontextprotocol/core@2.0.0)
What happened?
discoverAuthorizationServerMetadata()picks its validation schema from which well-known filename resolved, not from the document that came back. Everyopenid-configurationcandidate is typed"oidc", so anything served there is validated againstOpenIdProviderDiscoveryMetadataSchema:That schema requires
jwks_uri,subject_types_supportedandid_token_signing_alg_values_supported— three fields OpenID Connect Discovery 1.0 requires and RFC 8414 does not. So a plain OAuth 2.0 authorization server (no ID tokens, no JWKS, nosub) that publishes RFC 8414 metadata at/.well-known/openid-configurationis rejected:Two things make this fatal rather than cosmetic:
continueon 4xx/502,continueon a CORS failure) falls through to the next candidate. A schema failure abortsdiscoverAuthorizationServerMetadata()entirely, so no later candidate is tried and the whole connection fails.Reported downstream at modelcontextprotocol/inspector#2172 against a real server (
https://misc.poodll.com/mod/minilesson/mcp.php, a plain OAuth 2.0 AS whose resource lives under a path). Reproduced independently against@modelcontextprotocol/client@2.0.0with an injectedfetchFn, and again against a local test server that serves RFC 8414 metadata only at/.well-known/openid-configuration.Two smaller things noticed in the same code path, mentioned here rather than as separate issues since a fix will likely touch both:
OpenIdProviderDiscoveryMetadataSchemais az.object(...), not az.looseObject(...)likeOAuthMetadataSchema. So a successful OIDC parse strips RFC 8414 fields that are not in the OIDC shape —revocation_endpointandintrospection_endpointamong them — from the returned metadata.discoverAuthorizationServerMetadatasays it "attempts RFC 8414 OAuth metadata discovery first / if OAuth discovery fails, falls back to OpenID Connect Discovery", which is what the URL ordering does but not what the schema selection does.What did you expect?
A document should be validated by what it is, not by the filename it was found under. Concretely, either:
OAuthMetadataSchemaand fall back toOpenIdProviderDiscoveryMetadataSchema(or vice versa) regardless of the candidate'stype—OAuthMetadataSchemais a loose object, so it accepts a full OIDC document without dropping any of its fields; orcontinueto the next candidate rather than throwing out of the whole function.Either way, issuer validation (RFC 8414 §3.3 / OIDC Discovery §4.3) should stay exactly as it is.
Code to reproduce
SDK version
@modelcontextprotocol/client@2.0.0 (with @modelcontextprotocol/core@2.0.0)