MCP server URL
https://api.atlas.griddo.io/mcp/
Where does the issue occur?
During a conversation (after initial auth)
Transport used
streamable-http
Client registration type
Dynamic Client Registration (DCR)
SSE only: Does your server return the expected status code for POST requests?
Not applicable (my server does not use SSE)
When did you last reproduce this?
2026-05-01 between 02:02:11Z and 02:05:11Z (UTC). Additional reproductions through ~03:00Z UTC during the same debugging session.
Browser and OS
Claude Cowork (in claude desktop)
Describe the issue
A Custom Connector pointing to an MCP server that exposes OAuth 2.1 protected resource metadata (/.well-known/oauth-protected-resource) becomes permanently unusable after its OAuth access token expires. Claude does not refresh the token using the refresh_token grant the resource server advertises, and reinstalling the connector through the Enterprise admin console does not clear the stale state — the same UUID, the same tool permissions, and the same expired credentials are restored without prompting the user for re-authentication.
The end-user symptom is a hard, repeating 401 Invalid or expired token for every tool call against the connector.
We ran a multi-hour debugging session that ruled out client-side caches, local environment variables, MCP proxy state and mcp-remote configuration as the root cause. Server-side logging on our MCP server confirmed that the failing requests originate from Anthropic's cloud bridge (User-Agent Claude-User, IPs in the 160.79.106.0/24 range) and carry an OAuth access token whose exp claim is in the past. The same connector configuration works in the very narrow window during which the token is still valid, then fails permanently.
The four issues we surfaced, in order of impact:
- No automatic OAuth token refresh. The bridge keeps using an expired token instead of using the
refresh_token grant that the resource server advertises in its authorization-server metadata.
- "Delete" of a Custom Connector does not purge server-side state. Reinstalling reuses the same connector UUID, restores tool permissions, and silently reuses cached credentials — the UI never re-prompts for auth.
- Marketplace plugin freezes
${ENV_VAR} substitution at install time. Updating the env var (or the .mcp.json) and re-installing does not re-resolve the variable; the resolved value lives in a cache that survives uninstall/reinstall cycles.
- Cowork agent runtime appears to share the connector's broken state with the interactive UI, so a stale OAuth state in one surface poisons all surfaces. There is no surfaced way to refresh it from inside Cowork.
We strongly suspect (1) is the root cause and the rest are aggravating factors. The fix on Anthropic's side would unblock everyone who ships an OAuth-protected MCP server through Custom Connectors.
Issue details
## The Bug, Reproduced End-to-End
1. User adds a Custom Connector via the Enterprise console pointing at an MCP server that exposes `/.well-known/oauth-protected-resource`.
2. Claude detects OAuth metadata and forces an OAuth flow. It does **not** offer the user a Bearer / API-key option in the UI.
3. User completes OAuth login on the personal account. Tools become available.
4. After the OAuth access token expires (in our case `exp=1777602883`, ~24 h after issuance), every tool call returns:
{"success":false,"error":{"message":"Invalid or expired token","status_code":401}}
5. Disconnecting and reconnecting the connector from the user's account does not refresh the token.
6. Deleting the connector at the org-admin level and reinstalling it preserves: (a) the connector UUID, (b) tool permissions, (c) the cached credentials. Anthropic's UI does not re-prompt for auth on reinstall.
7. Restarting Claude does not help. Clearing local caches (`~/.mcp-auth`, `claude_desktop_config.json`, `launchctl` env, killing `mcp-remote` zombies) does not help.
8. Server-side logging confirms requests are arriving with an OAuth access token whose `exp` claim is in the past. The same server, queried directly with `curl` and a valid token, returns `200 OK`. So the server is healthy; the bridge is sending stale credentials.
Expected behavior
The mcp actually working.
Logs from your server
[
"INFO - Session 4a605aa00c154a3281f782a691ff81c4 idle timeout ",
"INFO - Terminating session: 4a605aa00c154a3281f782a691ff81c4 ",
"INFO - Session f753e0ca8db3409493aa04f683f2cebb idle timeout ",
"INFO - Terminating session: f753e0ca8db3409493aa04f683f2cebb ",
"INFO: 169.254.172.2:37734 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:37308 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:45748 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:35096 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:51058 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:36354 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:56306 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:36468 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:42124 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:52926 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:40160 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:52262 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:46624 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:58858 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:32824 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:34790 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO - Session 9238a1c422f34634b8f1500acc6af2dd idle timeout ",
"INFO - Terminating session: 9238a1c422f34634b8f1500acc6af2dd ",
"INFO - Session 7203b0adf7764a549552762c1bd4bbf5 idle timeout ",
"INFO - Terminating session: 7203b0adf7764a549552762c1bd4bbf5 ",
"INFO - Session 0feafada98db4a4cadf7a493cdde5474 idle timeout ",
"INFO - Terminating session: 0feafada98db4a4cadf7a493cdde5474 ",
"INFO: 169.254.172.2:51924 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:33046 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:46254 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:54904 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:50810 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:49178 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:52876 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:40810 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:50604 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:56402 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:45148 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:38772 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:38526 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:46796 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:42390 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:52886 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:38530 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:45086 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:54254 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:40712 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:51086 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:57808 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:50120 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:36950 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO - [MCP-AUTH-DEBUG] POST /mcp/ | ip=81.40.140.120 | auth=Bearer ",
"eyJhbGciOiJIUzI1NiIs...IhIza93A (len=260) ",
"claims={sub='277b2804-e7f8-4899-97d4-f4cb7a0dbead',type='mcp_api_key',exp=?} | ",
"ua=node ",
"INFO: 169.254.172.3:50136 - \"POST /mcp/ HTTP/1.1\" 404 Not Found",
"INFO - [MCP-AUTH-DEBUG] POST /mcp/ | ip=81.40.140.120 | auth=Bearer ",
"eyJhbGciOiJIUzI1NiIs...IhIza93A (len=260) ",
"claims={sub='277b2804-e7f8-4899-97d4-f4cb7a0dbead',type='mcp_api_key',exp=?} | ",
"ua=node ",
"INFO: 169.254.172.3:50140 - \"POST /mcp/ HTTP/1.1\" 404 Not Found",
"INFO: 169.254.172.2:48516 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO: 169.254.172.3:57990 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO - [MCP-AUTH-DEBUG] GET /mcp | ip=160.79.106.37 | auth=Bearer ",
"eyJhbGciOiJIUzI1NiIs...C3avHeH0 (len=475) ",
"claims={sub='277b2804-e7f8-4899-97d4-f4cb7a0dbead',type='?',exp=1777602883} | ",
"ua=Claude-User ",
"INFO: 169.254.172.3:57996 - \"GET /mcp HTTP/1.1\" 404 Not Found",
"INFO - [MCP-AUTH-DEBUG] GET /mcp | ip=160.79.106.38 | auth=Bearer ",
"eyJhbGciOiJIUzI1NiIs...C3avHeH0 (len=475) ",
"claims={sub='277b2804-e7f8-4899-97d4-f4cb7a0dbead',type='?',exp=1777602883} | ",
"ua=Claude-User ",
"INFO: 169.254.172.3:57996 - \"GET /mcp HTTP/1.1\" 404 Not Found",
"INFO - [MCP-AUTH-DEBUG] POST /mcp | ip=160.79.106.35 | auth=Bearer ",
"eyJhbGciOiJIUzI1NiIs...C3avHeH0 (len=475) ",
"claims={sub='277b2804-e7f8-4899-97d4-f4cb7a0dbead',type='?',exp=1777602883} | ",
"ua=Claude-User ",
"INFO: 169.254.172.3:57996 - \"POST /mcp HTTP/1.1\" 404 Not Found",
"INFO - [MCP-AUTH-DEBUG] POST /mcp | ip=160.79.106.35 | auth=Bearer ",
"eyJhbGciOiJIUzI1NiIs...C3avHeH0 (len=475) ",
"claims={sub='277b2804-e7f8-4899-97d4-f4cb7a0dbead',type='?',exp=1777602883} | ",
"ua=Claude-User ",
"INFO - Created new transport with session ID: 8da40d59b68c43ed8c79873cbbb93c6a ",
"INFO: 169.254.172.3:57996 - \"POST /mcp HTTP/1.1\" 200 OK",
"INFO - [MCP-AUTH-DEBUG] POST /mcp | ip=160.79.106.35 | auth=Bearer ",
"eyJhbGciOiJIUzI1NiIs...C3avHeH0 (len=475) ",
"claims={sub='277b2804-e7f8-4899-97d4-f4cb7a0dbead',type='?',exp=1777602883} | ",
"ua=Claude-User ",
"INFO: 169.254.172.3:57996 - \"POST /mcp HTTP/1.1\" 202 Accepted",
"INFO - [MCP-AUTH-DEBUG] GET /mcp | ip=160.79.106.37 | auth=Bearer ",
"eyJhbGciOiJIUzI1NiIs...C3avHeH0 (len=475) ",
"claims={sub='277b2804-e7f8-4899-97d4-f4cb7a0dbead',type='?',exp=1777602883} | ",
"ua=Claude-User ",
"INFO: 169.254.172.3:57996 - \"GET /mcp HTTP/1.1\" 200 OK",
"INFO: 169.254.172.2:36622 - \"GET /api/health HTTP/1.1\" 200 OK",
"INFO - [MCP-AUTH-DEBUG] POST /mcp | ip=160.79.106.37 | auth=Bearer ",
"eyJhbGciOiJIUzI1NiIs...C3avHeH0 (len=475) ",
"claims={sub='277b2804-e7f8-4899-97d4-f4cb7a0dbead',type='?',exp=1777602883} | ",
"ua=Claude-User ",
"INFO: 169.254.172.3:58010 - \"POST /mcp HTTP/1.1\" 200 OK",
"INFO - Processing request of type CallToolRequest "
]
Additional context
- macOS 14.x (Apple Silicon)
- Claude desktop app version 1.5354.0
- Plan: Claude Enterprise (org admin)
- Two surfaces tested: interactive Claude.app chat and Cowork agent mode
- MCP transport: HTTP via
npx -y mcp-remote (locally) and via Anthropic's hosted bridge (Custom Connector)
- MCP server:
https://api.atlas.griddo.io/mcp/, FastAPI behind AWS App Runner / Envoy. Server-side OAuth metadata published at /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server. Supports both static mcp_api_key Bearer tokens and full OAuth 2.1 with PKCE and dynamic client registration (RFC 7591) including refresh_token grant.
anthropic-bug-report-custom-connector-oauth.md
MCP server URL
https://api.atlas.griddo.io/mcp/
Where does the issue occur?
During a conversation (after initial auth)
Transport used
streamable-http
Client registration type
Dynamic Client Registration (DCR)
SSE only: Does your server return the expected status code for POST requests?
Not applicable (my server does not use SSE)
When did you last reproduce this?
2026-05-01 between 02:02:11Z and 02:05:11Z (UTC). Additional reproductions through ~03:00Z UTC during the same debugging session.
Browser and OS
Claude Cowork (in claude desktop)
Describe the issue
A Custom Connector pointing to an MCP server that exposes OAuth 2.1 protected resource metadata (
/.well-known/oauth-protected-resource) becomes permanently unusable after its OAuth access token expires. Claude does not refresh the token using therefresh_tokengrant the resource server advertises, and reinstalling the connector through the Enterprise admin console does not clear the stale state — the same UUID, the same tool permissions, and the same expired credentials are restored without prompting the user for re-authentication.The end-user symptom is a hard, repeating
401 Invalid or expired tokenfor every tool call against the connector.We ran a multi-hour debugging session that ruled out client-side caches, local environment variables, MCP proxy state and
mcp-remoteconfiguration as the root cause. Server-side logging on our MCP server confirmed that the failing requests originate from Anthropic's cloud bridge (User-AgentClaude-User, IPs in the160.79.106.0/24range) and carry an OAuth access token whoseexpclaim is in the past. The same connector configuration works in the very narrow window during which the token is still valid, then fails permanently.The four issues we surfaced, in order of impact:
refresh_tokengrant that the resource server advertises in its authorization-server metadata.${ENV_VAR}substitution at install time. Updating the env var (or the.mcp.json) and re-installing does not re-resolve the variable; the resolved value lives in a cache that survives uninstall/reinstall cycles.We strongly suspect (1) is the root cause and the rest are aggravating factors. The fix on Anthropic's side would unblock everyone who ships an OAuth-protected MCP server through Custom Connectors.
Issue details
Expected behavior
The mcp actually working.
Logs from your server
Additional context
npx -y mcp-remote(locally) and via Anthropic's hosted bridge (Custom Connector)https://api.atlas.griddo.io/mcp/, FastAPI behind AWS App Runner / Envoy. Server-side OAuth metadata published at/.well-known/oauth-protected-resource/mcpand/.well-known/oauth-authorization-server. Supports both staticmcp_api_keyBearer tokens and full OAuth 2.1 with PKCE and dynamic client registration (RFC 7591) includingrefresh_tokengrant.anthropic-bug-report-custom-connector-oauth.md