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:
Why it matters
Section titled “Why it matters”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
issbefore exchanging the code. If your metadata advertises issuer identification and the response omitsissor 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_challengewithS256on every authorization request and requests the scopes from your401challenge orscopes_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.
What MCP Tower checks
Section titled “What MCP Tower checks”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:
statemust equal the value of the sign-in it started. Otherwise:fail.- An
errorparameter is afail. The message quoteserroranderror_description, and the next step depends on the code. Aninvalid_requestwhose description mentions “redirect” is treated as a redirect URI mismatch. - An
issparameter, when present, must equal the issuer from your metadata. A trailing/is ignored. Otherwise:fail. - A
codeparameter must be present. Otherwise:fail.
When all four hold, the result is pass, and MCP Tower moves on to the
token exchange.
Results
Section titled “Results”| Status | Message | Meaning | Fix |
|---|---|---|---|
| pass | The provider returned an authorization code. | The sign-in worked. | |
| fail | The 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 |
| fail | The 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 |
| fail | The 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 |
| fail | The 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 |
| fail | The 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 |
| fail | The 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 |
| fail | The 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 |
| fail | The 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 |
| fail | The callback has no authorization code. | Next step: “Return the code parameter to the redirect URI.” | Return the code |
| skipped | Checked 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.
How to fix
Section titled “How to fix”Start the sign-in again
Section titled “Start the sign-in again”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.
Allow the account and the client
Section titled “Allow the account and the client”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.
Allow the requested scopes
Section titled “Allow the requested scopes”- Compare the scopes in the message with those your authorization server allows for this client.
- Allow the client to request each one, or remove a scope you do not need from your
401challenge, yourscopes_supportedand the tools’ declared scopes. - Sign in again. See tool scopes for how MCP Tower chooses them.
Register the redirect URI
Section titled “Register the redirect URI”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.
Return the issuer as iss
Section titled “Return the issuer as iss”Send iss in every authorization response, successful or not, with the exact issuer string from
your metadata, and advertise it:
{ "issuer": "https://auth.example.com", "authorization_response_iss_parameter_supported": true}HTTP/1.1 302 FoundLocation: <redirect-uri>?code=<code>&state=<state>&iss=https%3A%2F%2Fauth.example.comReturn the code
Section titled “Return the code”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.
Check it yourself
Section titled “Check it yourself”First confirm that your metadata advertises issuer identification and names the issuer:
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:
VERIFIER=test-verifier-0123456789-0123456789-0123456789CHALLENGE=$(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/mcpMost 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-stateThen select Sign in to scan again on the server’s page.
References
Section titled “References”- MCP authorization
2026-07-28: authorization response validation and resource parameter implementation - RFC 6749 §4.1.2.1: authorization error responses
- RFC 9207 §2: the
issparameter - RFC 7636 §4.3: the PKCE code challenge
- OpenAI: Authentication, “Protect callbacks with issuer identification”
- Claude: Authentication for connectors, “PKCE and requested scopes”