MCP OAuth: valid stored token for mcp.facebook.com/ads reported as "OAuth session expired and could not be refreshed" (issuer mismatch, no refresh token)

Status Fixed / completed
Reported on v2.1.228
Maintainer reply ✓ Yes — localden
Activity 3 comments · opened Aug 25, 2026 · closed Oct 12, 2026
💡 Likely answer: A maintainer (localden, collaborator) responded on this thread — see the highlighted reply below.

Follow-up to #86430, which was closed for lack of a reproduction. Related to #86023, still open, reported independently by other users.

The previous issue was closed because the maintainer could not run the scenario end to end ("it needs a Meta app ID and a Facebook login"). This report is written so that the core defect can be reproduced with no Meta app and no Facebook login: two curl calls are enough. The authenticated half is at the end, and we can run it on request.

Environment

  • Claude Code 2.1.243 (also seen on 2.1.228 and 2.1.233)
  • macOS 15 (Darwin 25.3.0), Apple Silicon
  • Server: https://mcp.facebook.com/ads, registered per Meta's docs with --transport http --client-id <YOUR_APP_ID>

Symptom

After a successful OAuth authorization, subsequent sessions fail. The visible error is a timeout:

connection timed out after 30000ms

With --debug, the real cause appears:

Failed to authenticate: OAuth session expired and could not be refreshed

The stored token is neither expired nor invalid. Using the same stored token directly over HTTPS, initialize, notifications/initialized and tools/list each complete in under a second and return all 66 tools. What fails is the client's OAuth layer, not the credential.

Reproduction without any credential

The discovery documents contradict each other. Anyone can see this:

curl -s https://mcp.facebook.com/.well-known/oauth-protected-resource/ads
{
  "resource": "https://mcp.facebook.com/ads",
  "authorization_servers": ["https://mcp.facebook.com/ads"]
}

The protected-resource document names https://mcp.facebook.com/ads as the authorization server. Now fetch that authorization server's metadata:

curl -s https://mcp.facebook.com/.well-known/oauth-authorization-server/ads
{
  "issuer": "https://www.facebook.com",
  "authorization_endpoint": "https://www.facebook.com/v26.0/dialog/oauth",
  "token_endpoint": "https://graph.facebook.com/v26.0/oauth/access_token",
  "grant_types_supported": ["authorization_code", "refresh_token"]
}

Two problems, both visible above:

  1. Issuer mismatch. The document is served from https://mcp.facebook.com but declares issuer: https://www.facebook.com. RFC 8414 section 3.3 requires the issuer value to match the location the metadata was retrieved from, and that check is what SEP-2352 added to the SDK. Meta's metadata cannot pass it.
  1. refresh_token is advertised but never issued. Meta's OAuth returns a long-lived access token with no refresh token. So once the client decides the session must be refreshed, there is nothing to refresh with, and the only possible outcome is the message above.

Our reading is that the client discards a perfectly valid stored credential and falls into a refresh path that cannot succeed. Whether the trigger is the failed issuer validation or a separate session-expiry heuristic is exactly the part we cannot see from outside.

What we would expect

A stored, unexpired access token should be used as is. If issuer validation must fail for a non-conformant provider, the error should say so instead of reporting an expired session and a 30 second timeout, which sends everyone looking at the network and at their credentials.

Current workaround

A local stdio bridge registered in place of the HTTP server. Claude Code speaks stdio to it, it speaks HTTPS to Meta with the token already stored in the Keychain, skipping the client's OAuth layer entirely. It has been running since 13/08 with no failures, which is further evidence that the credential and the endpoint are both fine.

Offer

We have the Meta app, the Business Manager and the ad account, which is what was missing last time. Tell us what you want captured and we will run the authenticated path with --debug and post the output. We monitor this issue and will reply within a day.

View original on GitHub ↗

3 Comments

farmaconapratica · 1 month ago

One more datapoint that narrows this to a regression, from #86023 (still open, different reporter):

this is since 224 version, if i downgrade to 220 it works again.

That matches our timeline: the connection worked here for weeks and broke on 13/08 without anything changing on the Meta side or in the stored credential. If 2.1.220 works and 2.1.224 does not, a diff of the MCP OAuth handling between those two releases should show the change that starts discarding a valid stored token, and the issuer mismatch described above is the most likely thing it started tripping on.

farmaconapratica · 1 month ago

Update: this is fixed for us on 2.1.270.

Re-tested today by registering a fresh HTTP server against https://mcp.facebook.com/ads with --client-id <our Meta App ID>, authenticating via /mcp, and then making a real read call. The OAuth handshake completed, the stored token was accepted, and ads_get_ad_accounts returned actual account data. No "OAuth session expired and could not be refreshed" at any point, on the first call or on repeat calls in a new process.

For context on the timeline: this broke for us on 2.1.224 and we had been working around it with a local stdio bridge ever since. That workaround is no longer needed here.

I don't know which release carried the fix, so I can't point at a specific change. Happy to run anything else that would help confirm it on your side.

localden collaborator · 2 days ago

Thanks for taking the time to report this, and for the credential-free repro and the retest. As you confirmed, the stored token for https://mcp.facebook.com/ads is accepted again as of 2.1.270 and the "OAuth session expired and could not be refreshed" error no longer appears, so the stdio bridge workaround is no longer needed. Anyone else seeing this should update to 2.1.270 or later. If it reproduces on that version or later, please reopen with the version and the repro.