Does this issue occur when all extensions are disabled?: Yes
- VS Code Version: 1.118.1
- OS Version: macOS 26.4.1 (Apple Silicon)
Steps to Reproduce:
- Stand up any MCP HTTP server with OAuth + RFC 7591 dynamic client registration. Self-contained reproducer (zero deps, plain Node.js, no extension or workspace required) at . It registers a DCR client, opens a callback listener, and asks you to complete OAuth login in a browser.
- Run the reproducer multiple times — or once with a forced state value containing a literal
+ character. The script sends state=test%2Babc%2Bdef%3D%3D (correctly URL-encoded), so the server receives the value cleanly with literal +s. The OAuth provider echoes the state back. The reproducer's callback listener parses the result and reports byte-by-byte whether the value survived.
- Observe the callback URL:
state=test+abc+def%3D%3D — the + characters are emitted as literal +, not %2B. VS Code's MCP OAuth client parses this URL form-urlencoded (+ → space), produces "test abc def==", compares to the originally-stored "test+abc+def==", sees a mismatch, and surfaces "An error occurred while signing in: State does not match."
Triggering this in normal use: configure VS Code Copilot against an OAuth-fronted MCP server that is not patched with a callback proxy, click "sign in", and expect failure on roughly half of attempts (whenever the random base64 state happens to contain a +).
Summary
VS Code's MCP OAuth client uses asymmetric URL encoding for the state parameter:
- On send (
/authorize URL): emits literal + characters in the query string.
- On receive (OAuth callback URL): parses
+ as a space character (application/x-www-form-urlencoded semantics).
These two behaviors are inconsistent. Whenever the random base64 state happens to contain a + character (~50% of attempts; standard base64 alphabet includes + and /), the round-trip fails with An error occurred while signing in: State does not match.
This is the same symptom as the closed-but-unresolved #299238 — that issue was closed for missing trace logs, but the underlying defect is reproducible and likely the same root cause.
Empirical evidence
Spectrum test against an XSUAA-backed MCP server, sending each state URL-encoded properly (%2B, %2F) so the server-side ingress is unambiguous and we isolate the round-trip behavior:
| Scenario |
State sent |
State VS Code-style parser sees |
Result |
+ middle |
aaa+bbb== |
aaa bbb== |
✗ Mismatch |
+ leading |
+aaaaaaa== |
aaaaaaa== |
✗ Mismatch |
+ trailing |
aaaaaaa+== |
aaaaaaa == |
✗ Mismatch |
Multiple + |
a+b+c+d== |
a b c d== |
✗ Mismatch |
Realistic state w/ two + |
6QadZ5GFXGvZ649+OuQi+Q== |
6QadZ5GFXGvZ649 OuQi Q== |
✗ Mismatch |
/ middle |
aaa/bbb== |
aaa/bbb== |
✓ |
| Plain alphanum |
aaaabbbb |
aaaabbbb |
✓ |
The bug is +-only, not a general encoding issue — /, =, alphanumerics all survive.
Why this is a client-side bug, not the OAuth provider's
Server-side workarounds (callback proxies that re-encode + as %2B before redirecting back to VS Code) are possible, and at least one is being implemented downstream (marianfoo/arc-1#214). But that's every OAuth-fronting server having to compensate independently for VS Code's asymmetry. The real bug lives in VS Code's outgoing-URL constructor versus its callback parser — a single client-side fix would close this bug class for every OAuth provider VS Code talks to.
Suggested fix
Generate OAuth state using URL-safe base64 (RFC 4648 §5: - instead of +, _ instead of /) rather than standard base64.
- Same alphabet as PKCE
code_challenge, which VS Code already uses correctly.
- Same entropy.
- No
+ or / to be ambiguous in URLs.
- Closes the bug at the source for any OAuth provider, regardless of how they handle the callback.
Alternative if base64url isn't desired: URL-encode literal + as %2B in the outgoing /authorize URL.
Prior art (same bug class, different actors)
Related issues / what this isn't
Does this issue occur when all extensions are disabled?: Yes
Steps to Reproduce:
+character. The script sendsstate=test%2Babc%2Bdef%3D%3D(correctly URL-encoded), so the server receives the value cleanly with literal+s. The OAuth provider echoes the state back. The reproducer's callback listener parses the result and reports byte-by-byte whether the value survived.state=test+abc+def%3D%3D— the+characters are emitted as literal+, not%2B. VS Code's MCP OAuth client parses this URL form-urlencoded (+→ space), produces"test abc def==", compares to the originally-stored"test+abc+def==", sees a mismatch, and surfaces "An error occurred while signing in: State does not match."Triggering this in normal use: configure VS Code Copilot against an OAuth-fronted MCP server that is not patched with a callback proxy, click "sign in", and expect failure on roughly half of attempts (whenever the random base64 state happens to contain a
+).Summary
VS Code's MCP OAuth client uses asymmetric URL encoding for the
stateparameter:/authorizeURL): emits literal+characters in the query string.+as a space character (application/x-www-form-urlencodedsemantics).These two behaviors are inconsistent. Whenever the random base64 state happens to contain a
+character (~50% of attempts; standard base64 alphabet includes+and/), the round-trip fails withAn error occurred while signing in: State does not match.This is the same symptom as the closed-but-unresolved #299238 — that issue was closed for missing trace logs, but the underlying defect is reproducible and likely the same root cause.
Empirical evidence
Spectrum test against an XSUAA-backed MCP server, sending each state URL-encoded properly (
%2B,%2F) so the server-side ingress is unambiguous and we isolate the round-trip behavior:+middleaaa+bbb==aaa bbb==+leading+aaaaaaa==aaaaaaa==+trailingaaaaaaa+==aaaaaaa ==+a+b+c+d==a b c d==+6QadZ5GFXGvZ649+OuQi+Q==6QadZ5GFXGvZ649 OuQi Q==/middleaaa/bbb==aaa/bbb==aaaabbbbaaaabbbbThe bug is
+-only, not a general encoding issue —/,=, alphanumerics all survive.Why this is a client-side bug, not the OAuth provider's
Server-side workarounds (callback proxies that re-encode
+as%2Bbefore redirecting back to VS Code) are possible, and at least one is being implemented downstream (marianfoo/arc-1#214). But that's every OAuth-fronting server having to compensate independently for VS Code's asymmetry. The real bug lives in VS Code's outgoing-URL constructor versus its callback parser — a single client-side fix would close this bug class for every OAuth provider VS Code talks to.Suggested fix
Generate OAuth state using URL-safe base64 (RFC 4648 §5:
-instead of+,_instead of/) rather than standard base64.code_challenge, which VS Code already uses correctly.+or/to be ambiguous in URLs.Alternative if base64url isn't desired: URL-encode literal
+as%2Bin the outgoing/authorizeURL.Prior art (same bug class, different actors)
+in OAuth state). Discussion thread at https://groups.google.com/g/smart-on-fhir/c/U67Fl6G3uto. Justin Richer / Josh Mandel diagnosed it as the OAuth provider not using "proper URL builders". Workaround that resolved it: client-side switch to URL-safe base64.Related issues / what this isn't