claude-code - 💡(How to fix) Fix [BUG] Custom MCP connector OAuth tokens fail to persist across restart; menu "Connect" silently fails while in-chat auth works [1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#52565Fetched 2026-04-24 06:03:46
View on GitHub
Comments
1
Participants
1
Timeline
6
Reactions
0
Participants
Timeline (top)
labeled ×5commented ×1

Error Message

Error Messages / Logs

  1. Initial OAuth handshake error (Zod v4 DCR validation — server side since fixed) Shown in terminal via /mcp → quarri → Authenticate, during the ~April 22–23, 2026 window when Claude Code 2.1.117/2.1.118 shipped:

Quarri MCP Server Status: ∆ needs authentication Auth: × not authenticated URL: https://app.quarri.ai/mcp Config location: C:\Users\tl.claude.json Error: SDK auth failed: [ { "expected": "string", "code": "invalid_type", "path": ["client_uri"], "message": "Invalid input: expected string, received null" }, { "code": "invalid_union", "errors": [ [{ "expected": "string", "code": "invalid_type", "path": [], "message": "Invalid input: expected string, received null" }], [{ "code": "invalid_value", "values": [""], "path": [], "message": "Invalid input: expected """ }] ], "path": ["logo_uri"], "message": "Invalid input" }, { "expected": "string", "code": "invalid_type", "path": ["software_id"], "message": "Invalid input: expected string, received null" }, { "expected": "string", "code": "invalid_type", "path": ["software_version"], "message": "Invalid input: expected string, received null" } ]

  1. Authenticate
  1. Disable This indicates the MCP SDK (likely Zod v4 migration in @modelcontextprotocol/sdk) began strictly validating RFC 7591 optional fields as required strings, rejecting null. These fields are per-spec optional and should be treated as such (accept null or undefined, or omit from schema). Fixed on the Quarri server side by omitting the fields — but the strict client schema remains a latent issue for any OAuth server returning null on optional DCR metadata.

  2. Post-auth reconnection failure (current, reproducible) After completing OAuth in the browser and returning to Claude Code:

/mcp ⎿ Authentication successful, but server reconnection failed. You may need to manually restart Claude Code for the changes to take effect. Restarting Claude Code does not resolve it. Running claude mcp list afterward reports:

Checking MCP server health… quarri: https://app.quarri.ai/mcp (HTTP) - ✗ Failed to connect Note: status is ✗ Failed to connect, not ! Needs authentication, despite the server returning a 401 with a valid WWW-Authenticate header indicating OAuth is expected.

  1. Desktop / Cowork in-app error Shown in Claude Desktop chat after calling a quarri tool post-restart:

Couldn't reach the MCP server. You can check the server URL and verify the server is running. If this persists, share this reference with support: "ofid_5f63d6676526dbdd" Server is reachable; issue is client-side token binding.

  1. Direct server probe (confirms server is compliant, client is at fault) $ curl -i -X POST https://app.quarri.ai/mcp
    -H "Content-Type: application/json"
    -H "Accept: application/json, text/event-stream"
    --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}' HTTP/1.1 401 UNAUTHORIZED server: Fly/0e7dad54f (2026-04-23) content-type: application/json www-authenticate: Bearer resource_metadata="https://app.quarri.ai/.well-known/oauth-protected-resource" ... {"error":{"code":-32000,"message":"Missing or invalid Authorization header"},"id":null,"jsonrpc":"2.0"} $ curl -i https://app.quarri.ai/.well-known/oauth-protected-resource HTTP/1.1 200 OK content-type: application/json { "authorization_servers": ["https://app.quarri.ai"], "bearer_methods_supported": ["header"], "resource": "https://app.quarri.ai/mcp", "resource_documentation": "https://app.quarri.ai/docs", "scopes_supported": [] } $ curl https://app.quarri.ai/.well-known/oauth-authorization-server { "authorization_endpoint": "https://app.quarri.ai/oauth/authorize", "code_challenge_methods_supported": ["S256"], "grant_types_supported": ["authorization_code"], "issuer": "https://app.quarri.ai", "registration_endpoint": "https://app.quarri.ai/oauth/register", "response_types_supported": ["code"], "service_documentation": "https://app.quarri.ai/docs", "token_endpoint": "https://app.quarri.ai/oauth/token", "token_endpoint_auth_methods_supported": ["none"] } Server is fully MCP 2025-06-18 / RFC 9728 / RFC 8414 compliant. Despite this, the Claude Code client classifies the 401 as a connect failure rather than an auth challenge.

  2. Stale DCR client registration in credential store Evidence of state pollution from failed OAuth attempts:

$ cat ~/.claude/.credentials.json | jq 'keys, .mcpOAuth | keys' [ "claudeAiOauth", "mcpOAuth" ] [ "quarri|0c54185165cc81b2" ] The quarri|0c54185165cc81b2 entry remains even after:

claude mcp remove quarri -s user claude mcp add ... re-adding the server Repeated OAuth completions No automatic cleanup of failed or superseded client registrations. Required manual node-script wipe to recover a clean auth state.

  1. Auth-cache classification gap $ cat ~/.claude/mcp-needs-auth-cache.json {"claude.ai Google Calendar":{"timestamp":1776973847941}} Only managed connectors are ever written here. quarri never appears despite:

Returning a spec-correct 401 + WWW-Authenticate header The user having completed OAuth for it multiple times The stored OAuth token existing under mcpOAuth in .credentials.json This is almost certainly the root-cause data structure: since quarri isn't in this cache, the CLI does not classify its 401s as auth challenges and does not trigger re-auth, leading to the ✗ Failed to connect status and the silent "connected but no tools" desktop state.

  1. Timeline summary When Claude Code Behavior Pre April 22, 2026 2.1.116 or earlier Custom connector worked, persisted across restarts April 22–23, 2026 2.1.117 → 2.1.118 Zod v4 DCR validation error; total auth failure After server DCR fix 2.1.118+ OAuth completes but token not bound on reconnect; silent "connected but no tools" via Desktop menu; in-chat auth works only in-session; no persistence across restart

Root Cause

Desktop Settings → Connectors → Connect: Launches browser OAuth → completes successfully → returns to app → UI shows "connected" → but no tools are ever loaded. Silent failure. No token persistence across app restart: Even when an in-chat auth flow succeeds and tools work in-session, closing and reopening the app reverts the connector to an unauthenticated state. Menu still claims "connected" but no tools are available. Only reliable workaround: In-chat MCP auth prompt (Claude itself detects the need and triggers OAuth). Must be repeated every session. Claude Code CLI behavior: claude mcp list reports ✗ Failed to connect rather than ! Needs authentication, because: ~/.claude/mcp-needs-auth-cache.json only contains managed connectors (e.g. claude.ai Google Calendar) — custom MCPs are never added Stale DCR client registrations from earlier failed attempts persist in ~/.claude/.credentials.json under mcpOAuth (e.g. quarri|0c54185165cc81b2) and must be manually wiped to produce a clean auth state Initial root cause (now fixed on server side): Claude Code SDK Zod v4 validation rejected null on optional RFC 7591 DCR fields (client_uri, logo_uri, software_id, software_version) where previous versions tolerated them. Relevant error: SDK auth failed: [ {"expected":"string","code":"invalid_type","path":["client_uri"],"message":"Invalid input: expected string, received null"}, {"code":"invalid_union","path":["logo_uri"], ...}, {"expected":"string","code":"invalid_type","path":["software_id"], ...}, {"expected":"string","code":"invalid_type","path":["software_version"], ...} ] Server was updated to omit these fields — but client-side persistence issues remain. Likely root cause (Anthropic side)

Fix Action

Fix / Workaround

Desktop Settings → Connectors → Connect: Launches browser OAuth → completes successfully → returns to app → UI shows "connected" → but no tools are ever loaded. Silent failure. No token persistence across app restart: Even when an in-chat auth flow succeeds and tools work in-session, closing and reopening the app reverts the connector to an unauthenticated state. Menu still claims "connected" but no tools are available. Only reliable workaround: In-chat MCP auth prompt (Claude itself detects the need and triggers OAuth). Must be repeated every session. Claude Code CLI behavior: claude mcp list reports ✗ Failed to connect rather than ! Needs authentication, because: ~/.claude/mcp-needs-auth-cache.json only contains managed connectors (e.g. claude.ai Google Calendar) — custom MCPs are never added Stale DCR client registrations from earlier failed attempts persist in ~/.claude/.credentials.json under mcpOAuth (e.g. quarri|0c54185165cc81b2) and must be manually wiped to produce a clean auth state Initial root cause (now fixed on server side): Claude Code SDK Zod v4 validation rejected null on optional RFC 7591 DCR fields (client_uri, logo_uri, software_id, software_version) where previous versions tolerated them. Relevant error: SDK auth failed: [ {"expected":"string","code":"invalid_type","path":["client_uri"],"message":"Invalid input: expected string, received null"}, {"code":"invalid_union","path":["logo_uri"], ...}, {"expected":"string","code":"invalid_type","path":["software_id"], ...}, {"expected":"string","code":"invalid_type","path":["software_version"], ...} ] Server was updated to omit these fields — but client-side persistence issues remain. Likely root cause (Anthropic side)

Code Example

Error Messages / Logs

1. Initial OAuth handshake error (Zod v4 DCR validation — server side since fixed)
Shown in terminal via /mcp → quarri → Authenticate, during the ~April 2223, 2026 window when Claude Code 2.1.117/2.1.118 shipped:

Quarri MCP Server
Status:           ∆ needs authentication
Auth:             × not authenticated
URL:              https://app.quarri.ai/mcp
Config location:  C:\Users\tl\.claude.json
Error: SDK auth failed: [
  {
    "expected": "string",
    "code": "invalid_type",
    "path": ["client_uri"],
    "message": "Invalid input: expected string, received null"
  },
  {
    "code": "invalid_union",
    "errors": [
      [{ "expected": "string", "code": "invalid_type", "path": [], "message": "Invalid input: expected string, received null" }],
      [{ "code": "invalid_value", "values": [""], "path": [], "message": "Invalid input: expected \"\"" }]
    ],
    "path": ["logo_uri"],
    "message": "Invalid input"
  },
  {
    "expected": "string",
    "code": "invalid_type",
    "path": ["software_id"],
    "message": "Invalid input: expected string, received null"
  },
  {
    "expected": "string",
    "code": "invalid_type",
    "path": ["software_version"],
    "message": "Invalid input: expected string, received null"
  }
]
> 1. Authenticate
  2. Disable
This indicates the MCP SDK (likely Zod v4 migration in @modelcontextprotocol/sdk) began strictly validating RFC 7591 optional fields as required strings, rejecting null. These fields are per-spec optional and should be treated as such (accept null or undefined, or omit from schema). Fixed on the Quarri server side by omitting the fields — but the strict client schema remains a latent issue for any OAuth server returning null on optional DCR metadata.

2. Post-auth reconnection failure (current, reproducible)
After completing OAuth in the browser and returning to Claude Code:

> /mcp
Authentication successful, but server reconnection failed. You may need to manually restart Claude Code for the
     changes to take effect.
Restarting Claude Code does not resolve it. Running claude mcp list afterward reports:

Checking MCP server health…
quarri: https://app.quarri.ai/mcp (HTTP) -Failed to connect
Note: status is ✗ Failed to connect, not ! Needs authentication, despite the server returning a 401 with a valid WWW-Authenticate header indicating OAuth is expected.

3. Desktop / Cowork in-app error
Shown in Claude Desktop chat after calling a quarri tool post-restart:

Couldn't reach the MCP server. You can check the server URL and verify the server is running.
If this persists, share this reference with support: "ofid_5f63d6676526dbdd"
Server is reachable; issue is client-side token binding.

4. Direct server probe (confirms server is compliant, client is at fault)
$ curl -i -X POST https://app.quarri.ai/mcp \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'
HTTP/1.1 401 UNAUTHORIZED
server: Fly/0e7dad54f (2026-04-23)
content-type: application/json
www-authenticate: Bearer resource_metadata="https://app.quarri.ai/.well-known/oauth-protected-resource"
...
{"error":{"code":-32000,"message":"Missing or invalid Authorization header"},"id":null,"jsonrpc":"2.0"}
$ curl -i https://app.quarri.ai/.well-known/oauth-protected-resource
HTTP/1.1 200 OK
content-type: application/json
{
  "authorization_servers": ["https://app.quarri.ai"],
  "bearer_methods_supported": ["header"],
  "resource": "https://app.quarri.ai/mcp",
  "resource_documentation": "https://app.quarri.ai/docs",
  "scopes_supported": []
}
$ curl https://app.quarri.ai/.well-known/oauth-authorization-server
{
  "authorization_endpoint": "https://app.quarri.ai/oauth/authorize",
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code"],
  "issuer": "https://app.quarri.ai",
  "registration_endpoint": "https://app.quarri.ai/oauth/register",
  "response_types_supported": ["code"],
  "service_documentation": "https://app.quarri.ai/docs",
  "token_endpoint": "https://app.quarri.ai/oauth/token",
  "token_endpoint_auth_methods_supported": ["none"]
}
Server is fully MCP 2025-06-18 / RFC 9728 / RFC 8414 compliant. Despite this, the Claude Code client classifies the 401 as a connect failure rather than an auth challenge.

5. Stale DCR client registration in credential store
Evidence of state pollution from failed OAuth attempts:

$ cat ~/.claude/.credentials.json | jq 'keys, .mcpOAuth | keys'
[
  "claudeAiOauth",
  "mcpOAuth"
]
[
  "quarri|0c54185165cc81b2"
]
The quarri|0c54185165cc81b2 entry remains even after:

claude mcp remove quarri -s user
claude mcp add ... re-adding the server
Repeated OAuth completions
No automatic cleanup of failed or superseded client registrations. Required manual node-script wipe to recover a clean auth state.

6. Auth-cache classification gap
$ cat ~/.claude/mcp-needs-auth-cache.json
{"claude.ai Google Calendar":{"timestamp":1776973847941}}
Only managed connectors are ever written here. quarri never appears despite:

Returning a spec-correct 401 + WWW-Authenticate header
The user having completed OAuth for it multiple times
The stored OAuth token existing under mcpOAuth in .credentials.json
This is almost certainly the root-cause data structure: since quarri isn't in this cache, the CLI does not classify its 401s as auth challenges and does not trigger re-auth, leading to the ✗ Failed to connect status and the silent "connected but no tools" desktop state.

7. Timeline summary
When	Claude Code	Behavior
Pre April 22, 2026	2.1.116 or earlier	Custom connector worked, persisted across restarts
April 2223, 2026	2.1.1172.1.118	Zod v4 DCR validation error; total auth failure
After server DCR fix	2.1.118+	OAuth completes but token not bound on reconnect; silent "connected but no tools" via Desktop menu; in-chat auth works only in-session; no persistence across restart
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

Summary After completing the OAuth flow for a custom MCP connector (Quarri: https://app.quarri.ai/mcp), the connection only works within a single in-session in-chat auth prompt. It does not persist across app restarts, and the Settings → Connectors → Connect button fails silently ("connected" shown but no tools load). Managed connectors (Gmail, Drive, Granola, Apollo) work correctly on the same device and account.

Environment

Windows 11, user tl Claude Code CLI (version from claude --version) Claude Desktop, Cowork mode Timeline: Started ~April 22–23, 2026 (lines up with Claude Code 2.1.117/2.1.118 releases) Custom MCP server: https://app.quarri.ai/mcp, HTTP transport, OAuth 2.1 + PKCE + Dynamic Client Registration Server compliance verified (all passing)

✅ 401 response includes WWW-Authenticate: Bearer resource_metadata="https://app.quarri.ai/.well-known/oauth-protected-resource" ✅ /.well-known/oauth-protected-resource returns RFC 9728-compliant JSON ✅ /.well-known/oauth-authorization-server serves valid AS metadata ✅ DCR endpoint accepts registrations and omits optional fields rather than returning null (fixed after previous Zod v4 validation error) Server is fully MCP 2025-06-18 spec compliant Observed bugs

Desktop Settings → Connectors → Connect: Launches browser OAuth → completes successfully → returns to app → UI shows "connected" → but no tools are ever loaded. Silent failure. No token persistence across app restart: Even when an in-chat auth flow succeeds and tools work in-session, closing and reopening the app reverts the connector to an unauthenticated state. Menu still claims "connected" but no tools are available. Only reliable workaround: In-chat MCP auth prompt (Claude itself detects the need and triggers OAuth). Must be repeated every session. Claude Code CLI behavior: claude mcp list reports ✗ Failed to connect rather than ! Needs authentication, because: ~/.claude/mcp-needs-auth-cache.json only contains managed connectors (e.g. claude.ai Google Calendar) — custom MCPs are never added Stale DCR client registrations from earlier failed attempts persist in ~/.claude/.credentials.json under mcpOAuth (e.g. quarri|0c54185165cc81b2) and must be manually wiped to produce a clean auth state Initial root cause (now fixed on server side): Claude Code SDK Zod v4 validation rejected null on optional RFC 7591 DCR fields (client_uri, logo_uri, software_id, software_version) where previous versions tolerated them. Relevant error: SDK auth failed: [ {"expected":"string","code":"invalid_type","path":["client_uri"],"message":"Invalid input: expected string, received null"}, {"code":"invalid_union","path":["logo_uri"], ...}, {"expected":"string","code":"invalid_type","path":["software_id"], ...}, {"expected":"string","code":"invalid_type","path":["software_version"], ...} ] Server was updated to omit these fields — but client-side persistence issues remain. Likely root cause (Anthropic side)

Custom connectors use a different token storage / retrieval code path than managed connectors, and the former is not wired through to the restart/reconnect layer 401 response classification inconsistency: custom MCPs classified as "generic connect failure" instead of "needs auth," so re-auth is never prompted Stale DCR client registrations from failed attempts are never cleaned up, causing compounding breakage Reproduction

Add custom MCP via Settings → Connectors → Add custom connector → https://app.quarri.ai/mcp Click Connect → complete OAuth → observe "connected" Attempt to use a tool from the connector → tools don't appear Trigger in-chat MCP prompt → auth succeeds → tools appear and work Quit and reopen app → tools gone, menu still claims connected Reference IDs

Intercom conversation: 215474031698397 Error session reference: ofid_5f63d6676526dbdd

What Should Happen?

Custom MCP connectors should behave the same as managed connectors (Gmail, Drive, Granola, Apollo) with respect to authentication and persistence. Specifically:

Settings → Connectors → Connect should, after successful OAuth, produce a working connection where tools are immediately available — not a silent "connected but no tools" state. OAuth tokens for custom connectors should persist across app restarts. After authenticating once, closing and reopening Claude Desktop / Cowork / Claude Code should restore the authenticated session without requiring re-authentication, exactly as happens for managed connectors. When a stored token expires or is invalid, the client should classify the resulting 401 as "Needs authentication" (not "Failed to connect") and prompt for re-auth automatically, using the RFC 9728 Protected Resource Metadata the server advertises via the WWW-Authenticate header. Custom MCPs should be added to mcp-needs-auth-cache.json just like managed connectors are. Stale client registrations from prior failed OAuth attempts should be cleaned up automatically on subsequent retry, rather than persisting indefinitely in .credentials.json and requiring manual wipe of mcpOAuth entries to recover. The in-chat auth flow and the Settings → Connectors auth flow should produce identical, equally durable results — there should not be a second-class path that only works in-session.

Error Messages/Logs

Error Messages / Logs

1. Initial OAuth handshake error (Zod v4 DCR validation — server side since fixed)
Shown in terminal via /mcp → quarri → Authenticate, during the ~April 22–23, 2026 window when Claude Code 2.1.117/2.1.118 shipped:

Quarri MCP Server
Status:           ∆ needs authentication
Auth:             × not authenticated
URL:              https://app.quarri.ai/mcp
Config location:  C:\Users\tl\.claude.json
Error: SDK auth failed: [
  {
    "expected": "string",
    "code": "invalid_type",
    "path": ["client_uri"],
    "message": "Invalid input: expected string, received null"
  },
  {
    "code": "invalid_union",
    "errors": [
      [{ "expected": "string", "code": "invalid_type", "path": [], "message": "Invalid input: expected string, received null" }],
      [{ "code": "invalid_value", "values": [""], "path": [], "message": "Invalid input: expected \"\"" }]
    ],
    "path": ["logo_uri"],
    "message": "Invalid input"
  },
  {
    "expected": "string",
    "code": "invalid_type",
    "path": ["software_id"],
    "message": "Invalid input: expected string, received null"
  },
  {
    "expected": "string",
    "code": "invalid_type",
    "path": ["software_version"],
    "message": "Invalid input: expected string, received null"
  }
]
> 1. Authenticate
  2. Disable
This indicates the MCP SDK (likely Zod v4 migration in @modelcontextprotocol/sdk) began strictly validating RFC 7591 optional fields as required strings, rejecting null. These fields are per-spec optional and should be treated as such (accept null or undefined, or omit from schema). Fixed on the Quarri server side by omitting the fields — but the strict client schema remains a latent issue for any OAuth server returning null on optional DCR metadata.

2. Post-auth reconnection failure (current, reproducible)
After completing OAuth in the browser and returning to Claude Code:

> /mcp
  ⎿  Authentication successful, but server reconnection failed. You may need to manually restart Claude Code for the
     changes to take effect.
Restarting Claude Code does not resolve it. Running claude mcp list afterward reports:

Checking MCP server health…
quarri: https://app.quarri.ai/mcp (HTTP) - ✗ Failed to connect
Note: status is ✗ Failed to connect, not ! Needs authentication, despite the server returning a 401 with a valid WWW-Authenticate header indicating OAuth is expected.

3. Desktop / Cowork in-app error
Shown in Claude Desktop chat after calling a quarri tool post-restart:

Couldn't reach the MCP server. You can check the server URL and verify the server is running.
If this persists, share this reference with support: "ofid_5f63d6676526dbdd"
Server is reachable; issue is client-side token binding.

4. Direct server probe (confirms server is compliant, client is at fault)
$ curl -i -X POST https://app.quarri.ai/mcp \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'
HTTP/1.1 401 UNAUTHORIZED
server: Fly/0e7dad54f (2026-04-23)
content-type: application/json
www-authenticate: Bearer resource_metadata="https://app.quarri.ai/.well-known/oauth-protected-resource"
...
{"error":{"code":-32000,"message":"Missing or invalid Authorization header"},"id":null,"jsonrpc":"2.0"}
$ curl -i https://app.quarri.ai/.well-known/oauth-protected-resource
HTTP/1.1 200 OK
content-type: application/json
{
  "authorization_servers": ["https://app.quarri.ai"],
  "bearer_methods_supported": ["header"],
  "resource": "https://app.quarri.ai/mcp",
  "resource_documentation": "https://app.quarri.ai/docs",
  "scopes_supported": []
}
$ curl https://app.quarri.ai/.well-known/oauth-authorization-server
{
  "authorization_endpoint": "https://app.quarri.ai/oauth/authorize",
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code"],
  "issuer": "https://app.quarri.ai",
  "registration_endpoint": "https://app.quarri.ai/oauth/register",
  "response_types_supported": ["code"],
  "service_documentation": "https://app.quarri.ai/docs",
  "token_endpoint": "https://app.quarri.ai/oauth/token",
  "token_endpoint_auth_methods_supported": ["none"]
}
Server is fully MCP 2025-06-18 / RFC 9728 / RFC 8414 compliant. Despite this, the Claude Code client classifies the 401 as a connect failure rather than an auth challenge.

5. Stale DCR client registration in credential store
Evidence of state pollution from failed OAuth attempts:

$ cat ~/.claude/.credentials.json | jq 'keys, .mcpOAuth | keys'
[
  "claudeAiOauth",
  "mcpOAuth"
]
[
  "quarri|0c54185165cc81b2"
]
The quarri|0c54185165cc81b2 entry remains even after:

claude mcp remove quarri -s user
claude mcp add ... re-adding the server
Repeated OAuth completions
No automatic cleanup of failed or superseded client registrations. Required manual node-script wipe to recover a clean auth state.

6. Auth-cache classification gap
$ cat ~/.claude/mcp-needs-auth-cache.json
{"claude.ai Google Calendar":{"timestamp":1776973847941}}
Only managed connectors are ever written here. quarri never appears despite:

Returning a spec-correct 401 + WWW-Authenticate header
The user having completed OAuth for it multiple times
The stored OAuth token existing under mcpOAuth in .credentials.json
This is almost certainly the root-cause data structure: since quarri isn't in this cache, the CLI does not classify its 401s as auth challenges and does not trigger re-auth, leading to the ✗ Failed to connect status and the silent "connected but no tools" desktop state.

7. Timeline summary
When	Claude Code	Behavior
Pre April 22, 2026	2.1.116 or earlier	Custom connector worked, persisted across restarts
April 22–23, 2026	2.1.117 → 2.1.118	Zod v4 DCR validation error; total auth failure
After server DCR fix	2.1.118+	OAuth completes but token not bound on reconnect; silent "connected but no tools" via Desktop menu; in-chat auth works only in-session; no persistence across restart

Steps to Reproduce

Prerequisites Claude Code 2.1.117 or later (desktop app and/or CLI) A custom MCP server with OAuth 2.1 + Dynamic Client Registration that is fully MCP 2025-06-18 spec compliant. The Quarri MCP server at https://app.quarri.ai/mcp can be used directly; it currently passes all spec checks:

Verifies server compliance (all should return expected values)

curl -i -X POST https://app.quarri.ai/mcp
-H "Content-Type: application/json"
-H "Accept: application/json, text/event-stream"
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}'

→ HTTP 401 with header: www-authenticate: Bearer resource_metadata="https://app.quarri.ai/.well-known/oauth-protected-resource"

curl -i https://app.quarri.ai/.well-known/oauth-protected-resource

→ HTTP 200, RFC 9728 JSON body

curl https://app.quarri.ai/.well-known/oauth-authorization-server

→ Valid OAuth 2.0 AS metadata JSON

Reproduction A — Settings → Connectors menu silently fails Open Claude Desktop. Go to Settings → Connectors → Add custom connector. Enter URL: https://app.quarri.ai/mcp, name: quarri. Save. Click Connect next to the new quarri entry. Browser opens, OAuth flow completes successfully, browser shows "You're connected, return to Claude". Return to Claude Desktop. Menu shows quarri as Connected. Open a new chat and ask Claude to use a quarri tool (e.g. "list my databases"). Observed: No quarri tools are available. Claude reports the connector is unavailable, despite the menu showing "Connected". Expected: Quarri tools appear and are callable. Reproduction B — Token does not persist across restart Complete Reproduction A, then trigger the in-chat MCP auth flow instead (prompt Claude to use a quarri tool; when it errors, follow the in-chat "Authenticate" prompt). Complete OAuth in browser. Return to Claude. Tools now load and work. Call list_databases or similar — succeeds. Fully quit Claude Desktop (task tray → Quit, or Cmd/Ctrl+Q). Verify process is gone. Reopen Claude Desktop. Open a new chat. Ask Claude to call any quarri tool. Observed: Tools are no longer available. Settings → Connectors still shows quarri as "Connected", but calling any tool fails. No re-auth prompt appears. Expected: Tools available without re-auth, matching the behavior of managed connectors (Gmail, Drive) on the same device. Reproduction C — CLI misclassifies 401 and accumulates stale state In PowerShell / terminal: claude mcp add --transport http --scope user quarri https://app.quarri.ai/mcp claude mcp list Observe: quarri: https://app.quarri.ai/mcp (HTTP) - ! Needs authentication ✓ (correct) Run claude, then inside the session run /mcp → select quarri → Authenticate. Complete OAuth in browser. Observe terminal output: Authentication successful, but server reconnection failed. You may need to manually restart Claude Code for the changes to take effect. /exit, then in PowerShell: claude mcp list Observed: quarri: https://app.quarri.ai/mcp (HTTP) - ✗ Failed to connect — NOT ! Needs authentication. Inspect state: type $env:USERPROFILE.claude\mcp-needs-auth-cache.json

→ only contains managed connectors (e.g. "claude.ai Google Calendar"); quarri is absent

node -e "const c=require('fs').readFileSync(process.env.USERPROFILE+'/.claude/.credentials.json','utf8'); const j=JSON.parse(c); console.log(Object.keys(j.mcpOAuth||{}))"

→ shows stale entries like "quarri|0c54185165cc81b2" from failed DCR attempts that were never cleaned up

Expected: After a successful auth, claude mcp list should show ✓ connected. On token expiry, it should show ! Needs authentication and add quarri to mcp-needs-auth-cache.json, mirroring the managed-connector code path. Reproduction D — Only the in-chat path produces a working connection (demonstrates divergent code paths) Fresh state: claude mcp remove quarri -s user, wipe mcpOAuth entries in .credentials.json matching ^quarri|, delete any Quarri entry from the Desktop Connectors UI. Variant A: Re-add via Settings → Connectors → Add custom connector, click Connect. Complete OAuth. Variant B: Re-add via claude mcp add --transport http --scope user quarri https://app.quarri.ai/mcp, launch claude, let in-chat flow prompt for auth, complete OAuth. Observed: Variant A produces "connected but no tools" state. Variant B produces a working in-session connection but does not survive restart (see Reproduction B). Expected: Both variants produce identical, persistent, working connections. Comparison control On the same device, same user account, managed connectors (claude.ai Gmail, claude.ai Google Drive, claude.ai Granola, Apollo) work correctly through Reproduction A/B/C equivalents. They persist across restarts, correctly classify expired tokens as "Needs authentication," and are populated in mcp-needs-auth-cache.json. The bug is specific to custom connectors.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

No response

Claude Code Version

2.1.116 or earlier

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Other

Additional Information

No response

extent analysis

TL;DR

The issue can be resolved by modifying the custom connector's token storage and retrieval code path to match the managed connectors, ensuring proper token persistence across app restarts and correct classification of 401 errors as "Needs authentication".

Guidance

  • Investigate the difference in token storage and retrieval code paths between custom and managed connectors to identify the root cause of the issue.
  • Verify that the mcp-needs-auth-cache.json file is being updated correctly for custom connectors, and that the claude mcp list command is classifying 401 errors as "Needs authentication" instead of "Failed to connect".
  • Clean up stale client registrations from failed OAuth attempts in the .credentials.json file to prevent compounding breakage.
  • Test the in-chat auth flow and the Settings → Connectors auth flow to ensure they produce identical, equally durable results.

Example

No code snippet is provided as the issue requires investigation and modification of the existing codebase.

Notes

The issue is specific to custom connectors and does not affect managed connectors. The problem is likely due to a difference in the token storage and retrieval code path between custom and managed connectors.

Recommendation

Apply a workaround by modifying the custom connector's token storage and retrieval code path to match the managed connectors, ensuring proper token persistence across app restarts and correct classification of 401 errors as "Needs authentication". This will require investigation and modification of the existing codebase.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Custom MCP connector OAuth tokens fail to persist across restart; menu "Connect" silently fails while in-chat auth works [1 comments, 1 participants]