vercel/ai · error
Protected resource ${resourceMetadata.resource} does not mat
Error message
Protected resource ${resourceMetadata.resource} does not match expected ${defaultResource} (or origin) What it means
selectResourceURL validates the resource value from OAuth protected resource metadata (RFC 9728) against the MCP server URL the client is connecting to. checkResourceAllowed requires the configured resource to match the expected resource derived from the server URL (or at least share its origin); otherwise this security check throws to prevent sending tokens minted for a different resource (audience confusion / token substitution).
Source
Thrown at packages/mcp/src/tool/oauth.ts:1218
if (provider.validateResourceURL) {
return await provider.validateResourceURL(
defaultResource,
resourceMetadata?.resource,
);
}
if (!resourceMetadata) {
return undefined;
}
if (
!checkResourceAllowed({
requestedResource: defaultResource,
configuredResource: resourceMetadata.resource,
})
) {
throw new Error(
`Protected resource ${resourceMetadata.resource} does not match expected ${defaultResource} (or origin)`,
);
}
return new URL(resourceMetadata.resource);
}
async function authInternal(
provider: OAuthClientProvider,
{
serverUrl,
authorizationCode,
callbackState,
callbackIssuer,
scope,
resourceMetadataUrl,
fetchFn,
}: {
serverUrl: string | URL;View on GitHub (pinned to 69428b1f8b)
Solutions
- Fix the MCP server's protected resource metadata so the resource field matches the URL clients use to reach the server (scheme, host, port, path).
- Check reverse-proxy/gateway config so the advertised resource matches the externally visible origin.
- If intentional, implement provider.validateResourceURL on your OAuthClientProvider to approve the mismatched resource explicitly.
- Compare the PRM resource value with your serverUrl (curl the PRM endpoint) to spot the exact mismatch (http vs https, port, path).
Example fix
// before: PRM served by server at https://mcp.example.com advertises
// { "resource": "https://internal-mcp.corp:8443" }
// after: correct the PRM resource to the public server URL
// { "resource": "https://mcp.example.com/" } Defensive patterns
Strategy: validation
Validate before calling
const prm = await fetch(resourceMetadataUrl).then(r => r.json());
const expected = new URL(serverUrl).origin;
if (!prm.resource || new URL(prm.resource).origin !== expected) {
throw new Error(`PRM resource ${prm.resource} does not match server origin ${expected}; fix server metadata`);
} Type guard
function resourceMatchesServer(serverUrl: URL, resource: string): boolean {
try { return new URL(resource).origin === serverUrl.origin; } catch { return false; }
} Try / catch
try {
await auth(provider, { serverUrl });
} catch (error) {
if (String(error.message).includes('does not match expected')) {
console.error('Protected resource metadata does not match the MCP server URL; fix the server RFC 9728 document.');
}
} Prevention
- Keep the PRM resource field identical to the public server URL clients use.
- Audit reverse-proxy deployments so advertised resource matches the external origin.
- Optionally implement provider.validateResourceURL to codify any intentional origin-level tolerance.
- Diff PRM resource against serverUrl in deployment smoke tests.
When it happens
Trigger: Running auth()/selectResourceURL where the discovered protected resource metadata's resource field does not match the serverUrl-derived default resource or its origin.
Common situations: MCP server deployments where the PRM is served with a resource URL on a different host/port/scheme than the actual server URL (reverse proxy misconfiguration, trailing-slash or scheme mismatch, staging URL serving production metadata).
Related errors
- OAuth protected resource metadata URL ${resourceMetadataUrl.
- 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/6d0656f7289543b9.
Report an issue: GitHub.