MCP OAuth invalid_grant: four causes at Sume's token endpoint
The Sume MCP token endpoint answers invalid_grant with one of five messages. They group into four causes: reuse, expiry, mismatch and a bad PKCE verifier.

invalid_grant from Sume's hosted MCP token endpoint means the authorization code or the PKCE verifier was rejected, and the message text tells you which. The endpoint answers HTTP 400 with one of these messages: "Authorization code is invalid.", "Authorization code has expired.", "Authorization code does not match this token request.", "Invalid PKCE verifier.", or "Authorization code is already consumed." Most clients hide the text, so turn on the client's OAuth debug log first, then match the message against the list below.
The flow is the one in the MCP OAuth and API keys page: the client connects to https://mcp.sume.com/mcp, sends you to https://mcp.sume.com/oauth/authorize, you approve on the consent page, and the client exchanges the code (with PKCE) for an access token. The token step is the only place invalid_grant appears.
The messages, in the order the server checks them
Each message maps to one check that runs in a fixed order. The code is looked up by a hash; the server never keeps the raw code.
| Message | What failed | What to change |
|---|---|---|
| Authorization code is invalid. | No such code, or the code was denied or already used | Start a new authorize request |
| Authorization code has expired. | The code is older than its lifetime of 10 minutes | Exchange the code right after consent |
| Authorization code does not match this token request. | client_id, redirect_uri or resource differs from the authorize step | Send the same three values in both calls |
| Invalid PKCE verifier. | SHA-256 of the verifier does not equal the stored challenge | Reuse the verifier that made the challenge |
| Authorization code is already consumed. | A concurrent exchange won the single-use race | Do not retry the same code; authorize again |
Timing bugs and mismatch bugs
Two of these are timing bugs. The code lives 10 minutes and works once. A client that retries a failed token request with the same code can see the "invalid" or "already consumed" message on the second try, even though the first try failed for a different reason, such as a network drop after the server already consumed the code. The fix is never to loop on the token call. Treat any invalid_grant as the end of that authorization attempt and start a new one.
Two more are mismatch bugs. The code is bound to the client_id, the redirect_uri and the resource it was issued for. If your client sends https://mcp.sume.com/mcp as the resource at authorize time and omits it, or changes it, at token time, the code is rejected as not matching. The redirect URI must also be byte-for-byte the one from the authorize request.
Check your verifier and challenge pair
The PKCE check is the one that surprises people who build their own client. The server computes the S256 challenge from the code_verifier you send and compares it with the challenge saved when the code was issued. A verifier regenerated between the two steps, for example by a stateless serverless function, fails every time.
Keep the verifier next to the state value for the whole round trip. A short Python check of your own pair before you debug anything else:
import base64, hashlib, secrets
verifier = secrets.token_urlsafe(64)
digest = hashlib.sha256(verifier.encode()).digest()
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
print("code_verifier :", verifier)
print("code_challenge:", challenge)
print("method : S256")A short triage list
Work through this list when a connector fails at the last step of sign-in:
- Read the exact message, not just the error code.
- Confirm the client sends the same
client_id,redirect_uriandresourceat both steps. - Confirm the verifier is the one that produced the challenge.
- Make sure only one process exchanges the code, and that it does so soon after consent.
- After any
invalid_grant, restart the authorize step instead of retrying the token call.
When the token works but the tool call does not
If the sign-in succeeds but the next call is rejected, the problem has moved: the access token is valid for one hour, and a read-only grant cannot call tools that change data. Those cases are covered in MCP tools and gates. For the connect steps for Claude Code, Cursor and Codex, use the MCP quickstart.
Sources
Related posts
More in Integrations
- PKCE plain is rejected: Sume's MCP OAuth accepts S256 only
A remote MCP client that sends code_challenge_method=plain gets invalid_request from Sume. The server advertises S256 only, so set it and keep the verifier.
- Cursor sends refresh_token at MCP registration: Sume ignores it
Sume's /oauth/register accepts refresh_token in grant_types but drops it and keeps authorization_code. The access token lasts one hour; then sign in again.
- MCP dynamic client registration at Sume: 10 redirect URIs, no secret
Sume's /oauth/register takes at most 10 redirect_uris, requires auth method none and returns invalid_redirect_uri for a bad entry. See what each error means.
- POST /oauth/revoke on Sume MCP: 200 even for a token it never saw
Revoke a Sume MCP OAuth access token with POST /oauth/revoke. It answers 200 with an empty body, also when the token is unknown, so check by calling the API.
Written by Sume