Skip to content

50% off your first year. Join before 2027. Claim →

Fix token endpoint errors

This check verifies that your token_endpoint exchanges the authorization code for a Bearer access token.

Check id token_exchange (token exchange). Results:

pass fail skipped

The token exchange is the last step of sign-in. The user has signed in and approved access, and the agent now trades the code for the token it will send to your MCP server. If the exchange fails, the user sees a sign-in error right after a successful login, which is the hardest failure for them to understand.

  • The MCP authorization specification requires clients to send the resource parameter (RFC 8707) in both the authorization request and the token request, naming the MCP server, whether or not the authorization server supports it.
  • ChatGPT appends resource to both requests and expects it to tie the token to your protected resource metadata (OpenAI: Authentication).
  • Claude sends the exchange as application/x-www-form-urlencoded and waits up to 10 seconds for your token endpoint to answer (Claude: Authentication for connectors).

So a token endpoint that rejects resource, or parses only JSON, fails for every agent that follows the specification.

This check runs only while someone signs in with Sign in to scan. In a scan it shows “Checked when someone signs in.” It also runs only after the authorization check passed; otherwise it is skipped.

  1. MCP Tower sends a POST to token_endpoint with a form body: grant_type=authorization_code, the code, the redirect_uri it used, the PKCE code_verifier, and resource set to your resource identifier.
  2. The client authenticates the way it registered: HTTP Basic for client_secret_basic, client_id and client_secret in the body for client_secret_post, and only client_id for none, which public clients such as a Client ID Metadata Document client use.
  3. An error response is a fail. The message quotes the HTTP status and the OAuth error and error_description, and the next step depends on the code. A connection error is a fail too.
  4. A successful response must carry an access token whose token_type is Bearer. A response without token_type counts as Bearer. Any other type is a fail.
StatusMessageMeaningFix
passThe token endpoint returned a Bearer access token.Sign-in is complete. The next check is token accepted.
failThe token exchange failed with HTTP <status>: invalid_grant (<description>).Next step: “The authorization code or verifier was refused. Sign in again; if it repeats, check the provider’s PKCE support and code lifetime.”Accept the code
failThe token exchange failed with HTTP <status>: invalid_client (<description>).Next step: “The provider refused the client credentials. Check the client id and secret, or reset the connection to register again.”Accept the client
failThe token exchange failed with HTTP <status>: invalid_target (<description>).Next step: “Support RFC 8707 resource indicators: accept the resource parameter naming this MCP server.”Accept resource
failThe token exchange failed with HTTP <status>: unauthorized_client (<description>).Next step: “The provider does not allow this client to use the authorization code flow. Enable it for the client.”Accept the client
failThe token exchange failed with HTTP <status>: invalid_scope (<description>).Next step: “The provider rejected a requested scope. Check the scopes the server declares against those the provider allows.”Tool scopes
failThe token exchange failed with HTTP <status>: <error> (<description>).Any other code, such as invalid_request or unsupported_grant_type. Next step: “Check the provider’s logs for this error and the client’s configuration.”Accept the request
failThe token exchange failed with HTTP <status>.An error status without an OAuth error. Same next step as above.Accept the request
failThe token exchange failed: the token endpoint could not be reached (<error class>).dns_error, connect_error, tls_error, timeout or address_blocked. Next step: “Check that token_endpoint is reachable over https.”Accept the request
failThe token endpoint returned a <type> token, not a Bearer token.For example MAC. Next step: “Issue Bearer access tokens for this client.”Issue Bearer tokens
skippedChecked when someone signs in.A scan, not a sign-in.Select Sign in to scan
skippedNeeds a valid callback.The callback’s state did not match.Authorization
skippedNeeds an authorization code.The callback carried an error, or no code.Authorization
skippedThe callback was rejected.The callback’s iss did not match the issuer.Authorization

The description is cut after 200 characters. The message never contains the code, the verifier, the token or the response body.

invalid_grant means the code is unknown, expired, already used, issued to another client or redirect URI, or the code_verifier does not hash to the code_challenge with S256.

  1. Check that codes live long enough. One minute is common; the exchange follows the redirect within seconds.
  2. Check that the code is bound to the same client_id and redirect_uri the token request sends.
  3. Check that the endpoint verifies S256: base64url of the SHA-256 of the verifier, without padding (RFC 7636 §4.6).

invalid_client means the credentials did not match the client’s registered authentication method. If the client was created by Dynamic Client Registration, its secret is stored per user; select Reset my connection on the server’s page and sign in again to register a new one. For credentials an Admin entered, check the client id and secret. unauthorized_client means the client may not use the authorization_code grant: enable it for the client.

Your token endpoint must accept resource, and should issue a token whose audience is that URL (RFC 8707 §2). Rejecting it with invalid_target blocks every agent, because MCP clients send it in every token request. Allow the URL in your protected resource metadata’s resource as a valid target. When MCP Tower runs in front of your server, that is the public hostname URL; see public audience.

The token request is application/x-www-form-urlencoded (RFC 6749 §4.1.3), not JSON. Errors come back as JSON with error and error_description (RFC 6749 §5.2), so agents can show what went wrong. The endpoint must be an https URL that answers from the public internet.

A successful response names the token type Bearer (RFC 6750 §4), because MCP clients send the token in an Authorization: Bearer header:

Token response
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": "<access-token>",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<refresh-token>",
"scope": "mcp:tools"
}

Send a token request with a made-up code. Use the client id from the last registration and its redirect URI:

Terminal window
curl -si https://auth.example.com/token \
-d grant_type=authorization_code \
-d code=made-up-code \
-d client_id='<client-id>' \
--data-urlencode redirect_uri='<redirect-uri>' \
-d code_verifier=test-verifier-0123456789-0123456789-0123456789 \
--data-urlencode resource=https://mcp.example.com/mcp

The endpoint is set up correctly when it refuses the made-up code, and only the code:

HTTP/1.1 400 Bad Request
Content-Type: application/json
{"error":"invalid_grant"}

If the problem is the resource parameter, you see this instead:

HTTP/1.1 400 Bad Request
Content-Type: application/json
{"error":"invalid_target","error_description":"resource indicators are not supported"}

To test a real exchange, run the browser sign-in from authorization, then send the code from the redirect with the same verifier within the code’s lifetime. A 200 with "token_type": "Bearer" means it works. Then select Sign in to scan again on the server’s page.