vercel/ai · error · MCPClientOAuthError
OAuth protected resource metadata URL ${resourceMetadataUrl.
Error message
OAuth protected resource metadata URL ${resourceMetadataUrl.href} must have the same origin as the MCP server URL ${expectedOrigin} What it means
assertResourceMetadataUrlSameOrigin enforces RFC 9728 hardening: the `resource_metadata` URL returned in a WWW-Authenticate challenge must be same-origin with the MCP server URL, otherwise a malicious server could point the client at attacker-controlled metadata. When the origins differ, MCPClientOAuthError is thrown listing both the metadata URL and the expected origin. It runs in authInternal when the MCP server responds 401 with a resource-metadata URL.
Source
Thrown at packages/mcp/src/tool/oauth.ts:311
),
);
return true;
}
return false;
}
function assertResourceMetadataUrlSameOrigin(
serverUrl: string | URL,
resourceMetadataUrl?: URL,
): void {
if (!resourceMetadataUrl) {
return;
}
const expectedOrigin = new URL(serverUrl).origin;
if (resourceMetadataUrl.origin !== expectedOrigin) {
throw new MCPClientOAuthError({
message: `OAuth protected resource metadata URL ${resourceMetadataUrl.href} must have the same origin as the MCP server URL ${expectedOrigin}`,
});
}
}
function assertAuthorizationServerInformationMatches({
storedAuthorizationServerInformation,
currentAuthorizationServerInformation,
}: {
storedAuthorizationServerInformation: OAuthAuthorizationServerInformation;
currentAuthorizationServerInformation: OAuthAuthorizationServerInformation;
}): void {
if (
(storedAuthorizationServerInformation.issuer != null &&
currentAuthorizationServerInformation.issuer != null &&
storedAuthorizationServerInformation.issuer !==
currentAuthorizationServerInformation.issuer) ||
storedAuthorizationServerInformation.authorizationServerUrl !==View on GitHub (pinned to 69428b1f8b)
Solutions
- Fix the MCP server (or its reverse proxy) so the resource_metadata URL in the 401 challenge is served from the same origin as the MCP server URL itself.
- If metadata legitimately lives elsewhere, host a same-origin metadata document (or route /.well-known/oauth-protected-resource on the server origin) per RFC 9728.
- Check scheme and port match: https server must advertise https metadata on the same port; localhost dev should use the same port for server and metadata.
- Inspect the two URLs in the error message — align the metadata URL host/scheme/port with the serverUrl origin you passed to the client.
- If a proxy injects the wrong absolute URL, configure it to emit relative or same-origin absolute resource_metadata URLs.
Example fix
// before: challenge from https://api.example.com points metadata elsewhere WWW-Authenticate: Bearer resource_metadata="https://auth.example.com/.well-known/oauth-protected-resource" // after: same-origin metadata URL WWW-Authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"
Defensive patterns
Strategy: validation
Validate before calling
export function isSameOriginResourceMetadata(serverUrl: string, resourceMetadataUrl?: string): boolean {
if (!resourceMetadataUrl) return true;
try {
return new URL(resourceMetadataUrl).origin === new URL(serverUrl).origin;
} catch {
return false;
}
}
// run after parsing the 401 WWW-Authenticate challenge, before continuing the auth flow Try / catch
import { MCPClientOAuthError } from './oauth';
try {
await client.auth();
} catch (error) {
if (MCPClientOAuthError.isInstance(error) && error.message.includes('must have the same origin')) {
// fix the server's resource_metadata URL (or host metadata same-origin) and retry
} else {
throw error;
}
} Prevention
- Serve .well-known/oauth-protected-resource from the MCP server's own origin.
- Match scheme and port exactly between server URL and metadata URL (http/https and ports create different origins).
- Configure reverse proxies to emit relative or same-origin absolute metadata URLs.
- Test the 401 challenge with curl to inspect the resource_metadata value before wiring up the client.
When it happens
Trigger: An MCP server returns a 401 challenge whose `resource_metadata` header/field points at a different origin than the server URL (CDN host, different subdomain, another port); a reverse proxy rewrites the metadata URL to an external host; the server advertises `http://` metadata while the client uses `https://` (different origin due to scheme); server configured with the wrong absolute URL for its own metadata document.
Common situations: MCP gateways where auth metadata lives on a separate domain (e.g. server at api.example.com, metadata at auth.example.com); localhost dev with mismatched ports (localhost:3000 server advertising localhost:8080 metadata — different origin); misconfigured proxy setting absolute URLs to a public domain; self-hosted servers with hand-written WWW-Authenticate headers.
Related errors
- Protected resource ${resourceMetadata.resource} does not mat
- OAuth endpoint URL is not allowed: ${endpointUrl.href}
- OAuth authorization response issuer ${callbackIssuer} does n
- Incompatible OIDC provider at ${endpointUrl}: does not suppo
- Incompatible auth server: does not support response type ${r
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/34a4aa027e444c2e.
Report an issue: GitHub.