resource indicator is missing, or unknown
The client reached your authorization server and asked for a token bound to your MCP server, and the authorization server refused the binding: the `resource` value it was handed names something it does not recognise, or the client sent no `resource` at all and this server requires one. No token is minted, so nothing ever arrives at your MCP server to be accepted or rejected.
- 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 are not proof of anything and it is worth knowing which: 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, which is the answer you wanted. A provider that lists noresourcein its documented parameters and simply ignores yours also lands here: RFC 6749 requires an authorization server to ignore unrecognised request parameters, so silence is a legitimate outcome and the audience check still has to happen at your MCP server. - Then this is not 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 by teaching it that identifier or by switching to the parameter it actually reads. Microsoft Entra ID's v2.0 endpoint documents no resource parameter at all and wants scope={resource}/.default; Keycloak's docs say plainly it "cannot recognize resource parameter" and route you to an Audience mapper on a client scope; Auth0 uses audience unless you turn on its Resource Parameter Compatibility Profile. If instead the parameter is missing, the client dropped it: both reference SDKs omit resource when protected resource metadata was not discovered, which is a narrower rule than the spec's, and the spec requires clients to send it "regardless of whether authorization servers support it".
Have your agent fix this
Copies a prompt that sends your coding agent to this page, then has it 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, because it 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 The two error codes are not the same problem.
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 dialled. - 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 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 By default Auth0 reads
audience, notresource. 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 spec 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 spec 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 Know 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 spec required. - 5 One boundary worth planning around: 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 This is 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 spec requires the parameter regardless of support, and Keycloak ignoring it is the correct OAuth behaviour for an unrecognised 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 Because Keycloak never rejects the indicator, this configuration fails silently rather than with
invalid_target— the flow completes and the token'saudis wrong. Your MCP server is the only component that can catch it, and the spec makes that its job: servers **MUST** validate that access tokens were issued specifically for them as the intended audience. - 5 A realm issuer always carries a path (
/realms/<realm>), which is a separate source of failure one step earlier in the flow, and worth ruling out before you touch mappers.
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 The SDK decides for you whether to send the parameter at all, and its rule is narrower than the spec'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 serialised 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, the SDK prefers the metadata's
resourceover the URL you dialled, after checking the dialled URL is same-origin with it and under its path —checkResourceAllowed. 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 behaviours: 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 should be serving.
MCP Python SDK (client)
If the client is built on the MCP Python SDK and the authorization server says the indicator is missing
- 1 The gate is
OAuthContext.should_include_resource_param, and 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 Which makes this error a symptom more often than a cause. If your MCP server's protected resource metadata is not 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 The value comes from
get_resource_url, which starts at the canonical server URL and switches to the metadata'sresourcewhen the dialled 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
validate_resource_urlonOAuthClientProviderreplaces the default RFC 8707 mismatch check — it is called with(server_url, prm_resource)and raises to reject. It does not change whether the parameter is sent, and the SDK's client OAuth guide barely mentions it, so read the gate inmcp/client/auth/oauth2.pyrather than the docs before you rely on either.
Claude Code
If the client is Claude Code and Entra ID answers invalid_target on the callback
- 1 An open report describes the mechanism 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 cannot patch the client, so change what it is normalising. Publish a
resourcethat already has a path —https://mcp.example.com/mcprather thanhttps://mcp.example.com— and URL serialisation 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 does not implement resource indicators and you would rather not have every MCP client negotiate that
- 1 For its MCP routes the gateway is both the OAuth 2.1 resource server and the authorization server, so it is the component that honours the indicator. 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 Which 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,audienceor neither is a question between the gateway and the provider, not between the provider and every MCP client. - 3 Per-route binding 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 The caveat to know before you rely on it: the canonical resource URI is derived from the incoming request origin. Front the gateway with a proxy that rewrites the host and it will publish and validate against an identifier that is not the one clients dialled — the same rejection, one layer further along.
Something else
Whatever the components are, there are only two strings 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. Decide which with the curl above, and compare the two strings character by character rather than by eye: a trailing slash, an api:// prefix where an https:// one was published, or a path dropped from an endpoint URL are each enough. Do not 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 spec 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 is what stops a token minted for one MCP server from working at another, and the 2026-07-28 spec is unusually firm about 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 itself is looser — an authorization server that cannot parse a value, or does not consider the resource acceptable, rejects the request with invalid_target ("The requested resource is invalid, missing, unknown, or malformed"), and it **MAY** also require the parameter and fail requests that omit it with the same code. So one error code covers three different mistakes. The value is wrong: usually by a single character, because URL serialisation adds a trailing slash to an origin-only resource and RFC 8707 comparison is exact — the MCP TypeScript SDK sends resource.href, and new URL('https://mcp.example.com').href is https://mcp.example.com/, which is the root cause identified in an open Claude Code report against Entra ID. The value is right but the provider reads a different parameter: Entra ID's v2.0 protocol reference lists no resource parameter, and reports against it 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 warns you which case you are in, because — as the open spec issue asking for the requirement to be relaxed points out — neither RFC 8414 nor OpenID Connect Discovery defines a metadata field that advertises RFC 8707 support, so a client cannot learn from .well-known whether to send the parameter.
The exchange
Each step in the exchange, in order.
- 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 the metadata document above omits the parameter entirely.
Inside MCP client
Normalise it into a resource indicator resource=https://mcp.example.com/ -
MCP client to Authorization server
POST /token code=…&resource=https%3A%2F%2Fmcp.example.com%2F -
Authorization server to MCP client
400 Bad Request error="invalid_target"Flow stops here
What lands on you next
A resource indicator settles which server a token is for. It says nothing about who is holding it, or what they are allowed to do once they arrive.
-
The token is bound to this route. Which of our tools may it call?
-
mcp-capability-filter-inboundA static per-route allow-list of capabilities. Anything not on it is refused with
MethodNotFoundbefore the request reaches your server. -
Same route, two kinds of user. How do we split what each may do?
-
require-user-claims-inboundPer-user decisions come from claims on the validated token. The capability filter has no per-user axis, so this is a different policy rather than a setting on that one.
-
The user consented. What credential reaches the API behind us?
-
mcp-token-exchange-inboundNot the client's token — the spec 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.
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.