On this page
No token issued for an unknown resource
resource indicator is missing, or unknown Your authorization server refused to bind a token to your MCP server — the resource value it was handed names something it does not recognize, or the client sent none and this server requires one — so no token is minted and nothing ever reaches your MCP server.
- MCP specification
- 2026-07-28
Is this you?
Take the resource identifier from your own metadata rather than from your notes, then find out whether the authorization server has an opinion about it. The second half works by sending a token request that is certain to fail on the grant, and reading which error the server reaches for first.
- This error
- Step 2 answers
invalid_targetwhile step 3 answers something about the code: the server parsed your resource, rejected it, and never looked at the grant. Read the description for which product you are arguing with —resource indicator is missing, or unknownis the default text node-oidc-provider ships for itsInvalidTargeterror, so a Logto or node-oidc-provider deployment is the far end;AADSTS901002orAADSTS9010010is Entra ID. Two results prove nothing on their own: a server that validates the grant before the resource returns the grant error in both steps even when your resource is wrong, and a server that rejectsresourceonly at/authorizecannot be caught from the token endpoint at all. In both cases only a real authorization request settles it. If step 1 prints a resource with no path component, treat that as unhealthy on its own: it is the shape that acquires a trailing slash in transit. - Then the fix is the next section.
- Working
- Step 1 prints a resource identifier that is exactly the URL a client dials — same scheme, host, port, and path, and the same trailing slash or absence of one — and steps 2 and 3 come back with the same complaint about the authorization code, typically
invalid_grant. Identical errors mean the server got past resource validation and is now objecting to the fake code. A provider that lists noresourcein its documented parameters and ignores yours also lands here: RFC 6749 requires an authorization server to ignore unrecognized request parameters, so silence is a legitimate outcome and the audience check still has to happen at your MCP server. - Then this isn't your problem — try the error reference.
6 other strings clients print for this
- Authorization error: InvalidTargetError: resource indicator is missing, or unknown
- AADSTS9010010: The resource parameter provided in the request doesn't match with the requested scopes.
- AADSTS901002: The 'resource' request parameter is not supported.
- {"error":"invalid_target","error_description":"apps scopes require a valid MCP resource"}
- Error getting token from server metadata: ServerError: invalid_target
- Missing or invalid grant_type, code, redirect_uri, client_id, PKCE (code_verifier), or resource required
Fix it
Read the resource string your own protected resource metadata publishes, then make the authorization server agree with it: either teach it that identifier, or switch to the parameter it actually reads, which for Microsoft Entra ID, Keycloak, and Auth0 is scope or audience rather than resource. If the parameter is missing instead, the client dropped it — both reference SDKs omit resource when protected resource metadata was not discovered, which is a narrower rule than the specification's.
Have your agent fix this
Copy a prompt that sends your coding agent to this page to find and apply the right fix for your project.
The cause depends on your platform. Pick yours.
Which component decides the resource value?
Microsoft Entra ID
If the authorization server is Microsoft Entra ID and the request fails with AADSTS901002 or AADSTS9010010
- 1 Start from what Entra documents, which is less than people expect. The v2.0 authorization code reference lists the parameters
/authorizeand/tokenaccept, andresourceis not among them; the documented replacement is the.defaultsuffix, and Microsoft states the mapping outright — "Usingscope={resource-identifier}/.defaultis functionally the same asresource={resource-identifier}on the v1.0 endpoint". - 2 Tell the two error codes apart, because they are different problems.
AADSTS901002— "The 'resource' request parameter isn't supported", which is in Microsoft's own error-code reference — is the parameter being refused outright.AADSTS9010010— "The resource parameter provided in the request doesn't match with the requested scopes" — is Entra having parsed it and found it inconsistent withscope, and that code appears nowhere in the reference. Reports of it cluster on Microsoft's own remote MCP servers: Azure DevOps, Power BI, Fabric. - 3 Where you can influence both sides, make them share one URI namespace. Microsoft's own App Service walkthrough has you publish
api://<app-registration-app-id>/user_impersonationas the scope via theWEBSITE_AUTH_PRM_DEFAULT_WITH_SCOPESapp setting — and the protected resource metadataresourceis then your app's HTTPS URL, which shares no prefix withapi://…. A filed VS Code case reports that replacing theapi://Application ID URI with the server's own HTTPS URL, so the scope becomeshttps://<host>/mcp/user_impersonationagainst aresourceofhttps://<host>/mcp, madeAADSTS9010010stop. - 4 Add the MCP server URL as an Application ID URI on the registration that represents the API (
identifierUrisin the manifest), path included, no trailing slash. Entra compares strings; the defaultapi://<client-id>can never match a URL a client dialed. - 5 Watch the trailing slash on both sides. Microsoft documents the trap for
.defaultexplicitly: a resource URI that ends in a slash needs the slash kept, sohttps://management.azure.com/is requested ashttps://management.azure.com//.default— "notice the double slash!". The same character is what breaks theresourcecomparison from the client side. - 6 Check the client build before you keep digging: not every failure here is yours to fix. VS Code closed its own
AADSTS901002issue with "We don't includeresourcefor Entra flows", and a VS Code maintainer has suggested"microsoft-authentication.implementation": "msal-no-broker"on an openAADSTS9010010report — so which client build a user is on changes what goes on the wire.
Auth0
If the authorization server is Auth0 and the token comes back for the wrong audience, or resource appears to be ignored
- 1 Expect Auth0 to read
audience, notresource, by default. Its own guidance for the compatibility profile says so: when the profile is disabled Auth0 "will keep the experience as is and only use theaudienceparameter". An MCP client that obeys the specification sendsresourceand noaudience, so the resulting token is not bound to your MCP server at all — which surfaces later as a rejected token rather than asinvalid_target. - 2 Turn on the Resource Parameter Compatibility Profile: Dashboard → Settings → Advanced → Settings, where two toggles are listed as required — "Resource Parameter Compatibility Profile" and "Include Issuer in Authorization Responses", the second being the RFC 9207
issparameter the 2026-07-28 specification asks authorization servers to emit. - 3 Then fix the identifier. Auth0's note is that "RFC 8707 requires the
resourceparameter to be an absolute URI; we recommend defining your resource server identifiers in URI format" — so the API's identifier has to be the MCP server's canonical URI, not a bare name. - 4 Learn the precedence before you debug a mixed setup: "If both the
resourceandaudienceare available, theaudiencewill still be used." A client library that helpfully addsaudiencewill quietly win over theresourcethe specification required. - 5 Plan around one boundary: with the profile on, Auth0 "will not forward the
resourceto upstream Identity Providers (IdPs)". If Auth0 federates to another provider that does its own audience binding, that provider will not see the indicator.
Keycloak
If the authorization server is a Keycloak realm
- 1 Read this as documented, not broken. Keycloak's own MCP authorization server guide states it: "Keycloak cannot recognize
resourceparameter." A conformant MCP client will still send it, because the specification requires the parameter regardless of support, and Keycloak ignoring it is the correct OAuth behavior for an unrecognized parameter. - 2 Bind the audience through the channel Keycloak does read. The guide directs you to "use OAuth 2.0's
scopeparameter instead of theresourceparameter", with a client scope carrying an Audience mapper whose "Included Custom Audience" is the MCP server's URL. - 3 The two strings have to be identical, and the guide says which two: "the client scope's
Included Custom Audiencefield needs to be the same as the authorization request'sresourceparameter value and the MCP server's URL." Publish that same string asresourcein your protected resource metadata and you have one identifier in three places instead of three near-misses. - 4 Expect this to fail silently rather than with
invalid_target, because Keycloak never rejects the indicator: the flow completes and the token'saudis wrong. Your MCP server is the only component that can catch it, and the specification makes that its job: servers MUST validate that access tokens were issued specifically for them as the intended audience. - 5 Rule out the realm path before you touch mappers. A realm issuer always carries a path (
/realms/{realm-name}), which is a separate source of failure one step earlier in the flow.
MCP TypeScript SDK (client)
If the client is built on the MCP TypeScript SDK and resource is absent, or arrives with a trailing slash you did not put there
- 1 Check whether the SDK is sending the parameter at all, because its rule is narrower than the specification's.
selectResourceURLreturnsundefinedwhen no protected resource metadata was discovered — the comment in the source is exactly that, "Only include resource parameter when Protected Resource Metadata is present" — andstartAuthorizationand the token exchange each setresourceonly when it is defined. So a discovery failure upstream reappears here as a missing indicator, and an authorization server that requires one answersinvalid_target. - 2 The value is serialized as
resource.hrefon both requests, and that is where the trailing slash comes from:new URL('https://mcp.example.com').hrefishttps://mcp.example.com/. Publish aresourcewith a path (https://mcp.example.com/mcp) andhrefis a no-op; publish an origin, and every client on this code path sends a string that differs from your metadata by one character. An open Claude Code report against Entra ID names this as the root cause of itsAADSTS9010010. - 3 When metadata is present, expect the metadata's
resourceto win over the URL you dialed, aftercheckResourceAllowedconfirms the dialed URL is same-origin with it and under its path. A publishedresourcethat is not a prefix of the endpoint throwsProtected resource … does not match expected …rather than reaching the authorization server, which is a different error with a different fix. - 4
validateResourceURLon the provider is the one hook that overrides both behaviors: implement it and whatever URL you return is used, skipping the "omit when there is no metadata" default and the prefix check. Use it to pin an exact string for a provider that is fussy about one, not to paper over metadata you are supposed to serve.
MCP Python SDK (client)
If the client is built on the MCP Python SDK and the authorization server says the indicator is missing
- 1 Read the gate,
OAuthContext.should_include_resource_param. It returnsTrueonly when protected resource metadata was discovered, or when anMCP-Protocol-Versionof2025-06-18or later is available to compare against. With neither,resourceis dropped from the authorization request, the token request, and the refresh — three places, one condition. - 2 Treat this error as a symptom more often than a cause. If your MCP server's protected resource metadata isn't being found, fix that first: with the document in hand the SDK includes the parameter unconditionally and this failure goes away without you touching the client.
- 3 Change the identifier in your metadata, not in the client. The value comes from
get_resource_url, which starts at the canonical server URL and switches to the metadata'sresourcewhen the dialed URL passescheck_resource_allowedagainst it. So the identifier on the wire is the one you published, and getting it right in the metadata is what changes it. - 4 Read the gate in
mcp/client/auth/oauth2.pyrather than the docs before you rely on the override.validate_resource_urlonOAuthClientProviderreplaces the default RFC 8707 mismatch check — it is called with(server_url, prm_resource)and raises to reject — but it doesn't change whether the parameter is sent, and the SDK's client OAuth guide barely mentions it.
Claude Code
If the client is Claude Code and Entra ID answers invalid_target on the callback
- 1 Start from the mechanism an open report describes precisely: Claude Code's MCP OAuth client appends a trailing
/to theresourceparameter on both the/authorizeredirect and the/tokenexchange, so a protected resource metadataresourcepublished without one no longer matches the implicit resource of the requestedscope, and Entra returnsAADSTS9010010. It was filed against Microsoft's own Business Central MCP server, whose metadata publishes"resource": "https://mcp.businesscentral.dynamics.com". - 2 You can't patch the client, so change what it normalizes. Publish a
resourcethat already has a path —https://mcp.example.com/mcprather thanhttps://mcp.example.com— and URL serialization has nothing left to add. - 3 Then move the scope with it, or you have traded one mismatch for another. Entra compares
resourceagainst the resource implied byscope, soscopes_supportedhas to sit under the same identifier:https://mcp.example.com/mcp/user_impersonationagainst aresourceofhttps://mcp.example.com/mcp. - 4 To confirm the client is the layer at fault rather than your metadata, capture the authorize URL it opens. The report's method works: point
BROWSERat a script that logs its arguments instead of opening anything, run the connection again, and URL-decode theresourcevalue.
Zuplo MCP Gateway
If your identity provider doesn't implement resource indicators and you'd rather not have every MCP client negotiate that
- 1 Let the gateway honor the indicator. For its MCP routes it is both the OAuth 2.1 resource server and the authorization server: It builds the canonical resource URI from the request origin and the route path, publishes it as
resourcein the RFC 9728 document at/.well-known/oauth-protected-resource/{routePath}, mints tokens against it, and then validates that incoming bearer tokens were minted for that route's canonical resource URI. - 2 That is what removes the negotiation.
mcp-oauth-inbounddelegates browser login to an OIDC provider you configure and then issues the gateway's own bearer token, so whether your provider readsresource,audience, or neither is a question between the gateway and the provider, not between the provider and every MCP client. - 3 Expect per-route binding, which is the point, and the docs state the consequence plainly: a token issued for
/mcp/linear-v1cannot be reused on/mcp/stripe-v1. Two routes, two audiences, without two app registrations. - 4 Check one caveat before you rely on it: the gateway derives the canonical resource URI from the incoming request origin. Front it with a proxy that rewrites the host and it publishes and validates against an identifier that is not the one clients dialed — the same rejection, one layer further along.
Something else
There are only two strings here, and they have to be the same one. Change what the client sends, so the indicator matches an identifier the authorization server already knows. Or change what the authorization server knows, by registering that identifier — or by binding the audience through the parameter it does read, which for most providers today is scope or audience rather than resource. Compare the two strings character by character: a trailing slash, an api:// prefix where an https:// one was published, or a path dropped from an endpoint URL are each enough. Don't treat a provider that ignores the parameter as broken — RFC 8707 makes resource optional for clients to send and optional for servers to require, and the 2026-07-28 specification puts the obligation on the client. What is never optional is the check at the other end: MCP servers MUST validate that access tokens were issued specifically for them as the intended audience, and no request-side arrangement substitutes for that.
Why it happens
The resource parameter stops a token minted for one MCP server from working at another. The 2026-07-28 specification requires it: MCP clients MUST implement RFC 8707, the parameter MUST be included in both the authorization request and the token request, and clients MUST send it "regardless of whether authorization servers support it".
RFC 8707 is looser. An authorization server that cannot parse the value, or does not accept the resource, rejects the request with invalid_target ("The requested resource is invalid, missing, unknown, or malformed"), and it MAY require the parameter and fail requests that omit it with the same code. So one error code covers three mistakes.
The value is wrong, usually by one character. RFC 8707 comparison is exact and URL serialization adds a trailing slash to an origin-only resource: the MCP TypeScript SDK sends resource.href, and new URL('https://mcp.example.com').href is https://mcp.example.com/. An open Claude Code report against Entra ID names that as its root cause.
The value is right but the provider reads a different parameter. Entra ID's v2.0 protocol reference lists no resource parameter, and reports split between AADSTS901002 ("The 'resource' request parameter isn't supported", which is in Microsoft's own error-code reference) and AADSTS9010010 ("doesn't match with the requested scopes", which is not).
Or the parameter never left the client: both reference SDKs gate it on having found protected resource metadata first, so a discovery hiccup upstream turns into invalid_target here.
Nothing in the handshake tells you which case you are in. As the open specification issue asking to relax the requirement points out, neither RFC 8414 nor OpenID Connect Discovery defines a metadata field advertising RFC 8707 support, so a client cannot learn from .well-known whether to send the parameter.
The exchange
- MCP client
- MCP server resource server
- Authorization server
-
The client does not invent this identifier. It comes out of your own metadata document, which is why a value nobody at the authorization server has heard of is a configuration mismatch rather than a client bug.
MCP client to MCP server
GET …/oauth-protected-resource/mcp the URL the 401 challenge named -
MCP server to MCP client
200 OK "resource": "https://mcp.example.com" -
Two things go wrong here and produce the same error code. The string can arrive altered — a trailing slash is enough, because the comparison is exact. Or it can not arrive at all, because a client that never found that metadata document omits the parameter entirely.
Inside MCP client
Normalize it into a resource indicator resource=https://mcp.example.com/ -
MCP client to Authorization server
POST /token (carries the credential) code=…&resource=https%3A%2F%2Fmcp.example.com%2F -
Authorization server to MCP client
400 Bad Request (this step fails) error="invalid_target"Flow stops here
A resource indicator settles which server a token is for, and nothing about who is holding it.
-
The token is bound to this route. Which of our tools can it call?
-
mcp-capability-filter-inboundJust the ones on the route's allowlist — anything else is refused with
MethodNotFoundbefore the request reaches your server. -
Same route, two kinds of user. How do we split what each can do?
-
require-user-claims-inboundTwo jobs, both driven by claims on the validated token. This policy decides whether the caller reaches the route at all, answering
403; the capability filter, inrolesAndGroupsmode, decides which tools they see once they are through it. -
The user consented. What credential reaches the API behind us?
-
mcp-token-exchange-inboundNot the client's token — the specification forbids passthrough with a MUST NOT. The inbound credential is dropped and an upstream one is minted per user or per gateway.
-
Something is being turned away. What was it, and why?
-
mcp_request_rejectedEach refused request emits its own event with a named reason, so a rejection resolves to an outcome you can query rather than a
401in an access log.
All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out. Your MCP server keeps
the code and the authentication it has today.
Stop debugging auth on every MCP server
A gateway in front of your MCP servers handles discovery, audience binding, and token validation once — for every server and every client.