Skip to content

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

Fix errors returned to the sign-in callback

This check verifies that, after the user signs in, your authorization server redirects back with an authorization code, the right state, no error, and an iss that names the issuer.

Check id authorization (authorization). Results:

pass fail skipped

The redirect back from the authorization server is the only way an agent learns how the sign-in went. An error there, or a response the agent must reject, ends the sign-in: the user signed in, or tried to, and the agent still has no token.

  • ChatGPT records the issuer before redirecting and checks the returned iss before exchanging the code. If your metadata advertises issuer identification and the response omits iss or carries another value, ChatGPT rejects it. It compares strings exactly, without normalizing trailing slashes, paths, ports or case (OpenAI: Authentication).
  • Claude sends a PKCE code_challenge with S256 on every authorization request and requests the scopes from your 401 challenge or scopes_supported (Claude: Authentication for connectors). A scope your server refuses there fails the same way it fails for MCP Tower.
  • The MCP authorization specification requires clients to validate iss (RFC 9207) before they send the code anywhere, and recommends that authorization servers always include it.

This check runs only while someone signs in with Sign in to scan. In a scan it shows “Checked when someone signs in.”

MCP Tower sends the user to your authorization_endpoint with response_type=code, its client id, its redirect URI, the scopes it requests, a random state, a PKCE code_challenge with code_challenge_method=S256, and the resource parameter set to your resource identifier. When the browser comes back to the redirect URI, MCP Tower checks the callback in this order:

  1. state must equal the value of the sign-in it started. Otherwise: fail.
  2. An error parameter is a fail. The message quotes error and error_description, and the next step depends on the code. An invalid_request whose description mentions “redirect” is treated as a redirect URI mismatch.
  3. An iss parameter, when present, must equal the issuer from your metadata. A trailing / is ignored. Otherwise: fail.
  4. A code parameter must be present. Otherwise: fail.

When all four hold, the result is pass, and MCP Tower moves on to the token exchange.

StatusMessageMeaningFix
passThe provider returned an authorization code.The sign-in worked.
failThe callback’s state does not match the sign-in that was started.The callback belongs to another or an expired sign-in. Next step: “Start the sign-in again from MCP Tower, in the same browser session.”Start again
failThe provider returned error access_denied: <description>.Next step: “The sign-in was cancelled or refused at the provider. Sign in again and approve the request, or check that this account may use the client.”Allow the account
failThe provider returned error invalid_scope: <description>.Next step: “The provider rejected the scope <scopes>. Allow the client to request it, or remove it from the tool’s declared scopes.” <scopes> are all the scopes MCP Tower requested.Allow the scopes
failThe provider returned error invalid_scope: <description>.When no scopes were requested, the next step is: “The provider rejected a requested scope. Check the scopes the server declares against those the provider allows.”Allow the scopes
failThe provider returned error unauthorized_client: <description>.Next step: “The provider does not allow this client to use the authorization code flow. Enable it for the client.”Allow the account
failThe provider returned error <error>: <description>.For invalid_redirect_uri, redirect_uri_mismatch, or invalid_request mentioning “redirect”. Next step: “Register MCP Tower’s redirect URI with the provider exactly as shown.”Register the redirect URI
failThe provider returned error <error>: <description>.Any other code. Next step: “Check the provider’s logs for this error and the client’s configuration.”Your authorization server’s logs
failThe callback’s iss “<iss>” does not match the issuer “<issuer>” (RFC 9207). This can be a mix-up attack.Next step: “Make the authorization response’s iss parameter equal the issuer in the authorization server metadata.”Return the issuer
failThe callback has no authorization code.Next step: “Return the code parameter to the redirect URI.”Return the code
skippedChecked when someone signs in.A scan, not a sign-in.Select Sign in to scan

Without an error_description, the message ends after the code. The description is cut after 200 characters.

A pending sign-in expires after 10 minutes, and starting a new one replaces it. In the app, a callback whose state is missing or belongs to no pending sign-in does not reach this check: you are sent back to the server list with a notice that the sign-in expired. Either way, select Sign in to scan once, finish signing in in the same browser within 10 minutes, and do not reuse an old login tab. If it repeats, check that your authorization server returns state unchanged.

access_denied means the user cancelled, or your server refused the account. Check that the account may use this client and that consent is not blocked by policy. unauthorized_client means the client may not use the authorization code flow: enable the authorization_code grant for it.

  1. Compare the scopes in the message with those your authorization server allows for this client.
  2. Allow the client to request each one, or remove a scope you do not need from your 401 challenge, your scopes_supported and the tools’ declared scopes.
  3. Sign in again. See tool scopes for how MCP Tower chooses them.

Register MCP Tower’s redirect URI, https://app.mcptower.com/api/mcp-servers/oauth/callback, exactly, for the client MCP Tower uses. If you entered client credentials by hand, add it to that client. Some servers show an error page instead of redirecting when the URI is unknown; then the sign-in never returns to MCP Tower and no result appears.

Send iss in every authorization response, successful or not, with the exact issuer string from your metadata, and advertise it:

Authorization server metadata (excerpt)
{
"issuer": "https://auth.example.com",
"authorization_response_iss_parameter_supported": true
}
Redirect back to the client
HTTP/1.1 302 Found
Location: <redirect-uri>?code=<code>&state=<state>&iss=https%3A%2F%2Fauth.example.com

A successful response carries code and state as query parameters of the redirect URI (RFC 6749 §4.1.2). A fragment (#code=…) or a form post does not reach MCP Tower’s callback.

First confirm that your metadata advertises issuer identification and names the issuer:

Terminal window
curl -s https://auth.example.com/.well-known/oauth-authorization-server \
| grep -o '"\(issuer\|authorization_response_iss_parameter_supported\)":[^,}]*'

Then build the authorization URL MCP Tower opens. Use the client id from the last registration, its redirect URI, and any PKCE pair:

Terminal window
VERIFIER=test-verifier-0123456789-0123456789-0123456789
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -binary -sha256 | openssl base64 | tr '+/' '-_' | tr -d '=')
curl -si -G https://auth.example.com/authorize \
--data-urlencode response_type=code \
--data-urlencode client_id='<client-id>' \
--data-urlencode redirect_uri='<redirect-uri>' \
--data-urlencode scope='mcp:tools' \
--data-urlencode state=test-state \
--data-urlencode code_challenge="$CHALLENGE" \
--data-urlencode code_challenge_method=S256 \
--data-urlencode resource=https://mcp.example.com/mcp

Most servers answer with a login page. Open the same URL in a browser, sign in, and look at the address the browser is sent back to. The problem is fixed when it holds code, the same state, and iss equal to the issuer, and no error:

<redirect-uri>?code=<code>&iss=https%3A%2F%2Fauth.example.com&state=test-state

Then select Sign in to scan again on the server’s page.