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.

5 min readSume
All posts

Sume's hosted MCP server supports one PKCE method: S256. If a client sends code_challenge_method=plain to https://mcp.sume.com/oauth/authorize, the request fails with invalid_request and the message "OAuth code_challenge_method must be S256." The authorization-server metadata says the same thing in advance: code_challenge_methods_supported lists S256 and nothing else.

This matters for anyone who writes their own connector, a bridge script or a test harness. Established MCP clients already use S256, so a failure here is usually a hand-rolled client that copied an old OAuth example.

If you are not sure which side is wrong, the quickest test is to read the metadata, then decode your own authorize URL. Two query parameters decide the result: code_challenge and code_challenge_method. Both have to be present, and the method has to be the string S256 in capital letters.

Read the metadata first

The metadata is public, so a client can check before it sends a user anywhere. Sume documents two endpoints on the MCP host: the protected-resource metadata and the authorization-server metadata. Fetch the second one and read the field.

The grant_types_supported list is authorization_code only, and token_endpoint_auth_methods_supported is none. In other words, Sume treats an MCP client as a public client: no client secret, a code grant, and PKCE as the proof of possession.

A client library that supports many servers usually picks its method from this same list. If the library offers plain as a fallback when a server does not mention PKCE, make sure it sees the metadata field, because a server that lists S256 is telling the client that the stronger method is available and required.

curl -s https://mcp.sume.com/.well-known/oauth-authorization-server \
  | python3 -c "import sys, json; m = json.load(sys.stdin); print(m['code_challenge_methods_supported'], m['grant_types_supported'], m['token_endpoint_auth_methods_supported'])"

What S256 means in practice

The challenge is the base64url form of the SHA-256 digest of the verifier, without padding. The verifier is a random string that only your client knows until the token step. The authorize request carries the challenge; the token request carries the verifier.

The verifier has its own shape rules in the OAuth standard: it should be a high-entropy random string of unreserved characters. A library function that returns a random URL-safe token is the right source. Do not derive it from a timestamp or from the state value, because the challenge is public in the browser address bar and a guessable verifier removes the protection that PKCE exists to give.

PKCE fields at the two OAuth steps for hosted Sume MCP (read 2026-10-05)
StepEndpointPKCE fieldValue
Authorize/oauth/authorizecode_challengebase64url(SHA-256(verifier))
Authorize/oauth/authorizecode_challenge_methodS256
Token/oauth/tokencode_verifierThe original random verifier

Typical mistakes

Do not try to fall back to plain when S256 fails. A failure at the token step with Invalid PKCE verifier. is not a method problem; it is a pair that does not match, for example a verifier that was regenerated between the two steps. The message list is in the invalid_grant post.

Common causes in custom clients are a verifier that was truncated when it was stored in a cookie, a challenge computed from the base64 string of the digest instead of the raw bytes, and padding characters (=) left on the challenge.

A second class of failure is storage. The authorize step ends in a browser redirect, and the token step happens later in a different request. If your client runs in a stateless function, put the verifier in something that survives between the two requests, keyed by the state value, and delete it after one use. Never log it: a leaked verifier together with a captured code is enough to complete the exchange for the attacker, as long as the code is still unused and inside its 10 minute life.

When you do not need to care

If you use Claude Code, Cursor or Codex you do not need to do any of this by hand: add the URL and run the client's OAuth login, as in the MCP quickstart. The details of scopes and API-key sessions are in MCP OAuth and API keys.

One more place to look is the user agent that opens the browser. Some wrappers rewrite the authorize URL, for example to add tracking parameters or to re-encode the query string. A challenge that contains + or / from standard base64 instead of - and _ from base64url will compute the wrong value on the server side, and the failure shows up only at the token step.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume