MCP PKCE oAuth flow is not using refresh token

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

we use PKCE flow to authenticate MCP server and it seems like refresh token is not used to keep session live

Steps to Reproduce

have MCP server that support PKCE

    "example-dev": {
      "url": "https://mcp.example.com/v1",
      "auth": {
        "CLIENT_ID": "0fd0488b-66a8-432b-a89d-b2d0efe7bd4e"
      }
    },

go through initial auth and once token is expired you will see that MCP fails and asks for reauthentication

this is extract from MCP log.
nit

  • there used to be more info in older Cursor version (e.g. if it received refresh token)
  • logs don’t mention if refresh token was even tried
  • logs don’t mention what is the redirect URL
2026-03-18 11:38:39.951 [info] Handling LogoutServer action
2026-03-18 11:38:39.952 [info] Clearing stored OAuth data
2026-03-18 11:38:39.966 [info] Successfully cleared OAuth tokens
2026-03-18 11:38:39.966 [info] Cleaning up, reason: logout_server
2026-03-18 11:38:39.967 [warning] [V1] needsAuth -> disconnected
2026-03-18 11:38:39.974 [info] Handling ReloadClient action
2026-03-18 11:38:39.977 [info] Creating streamableHttp transport
2026-03-18 11:38:39.977 [info] No scopes in config, fetching from well-known endpoint
2026-03-18 11:38:40.192 [info] Using redirect URL
2026-03-18 11:38:40.192 [info] Persisting static OAuth client information for callback flow
2026-03-18 11:38:40.194 [info] Persisted static OAuth client information for callback flow
2026-03-18 11:38:40.194 [info] Connecting to streamableHttp server
2026-03-18 11:38:40.223 [info] CreateClient already in progress, waiting for existing creation
2026-03-18 11:38:42.837 [info] Using enriched static OAuth client information from storage
2026-03-18 11:38:42.838 [info] Using redirect URL
2026-03-18 11:38:42.840 [info] Using redirect URL
2026-03-18 11:38:42.840 [info] Using redirect URL
2026-03-18 11:38:42.840 [info] Saving PKCE code verifier
2026-03-18 11:38:42.843 [info] Redirect to authorization requested
2026-03-18 11:38:42.845 [info] Stored server URL for OAuth flow
2026-03-18 11:38:42.845 [info] OAuth provider needs auth callback during connection
2026-03-18 11:38:42.845 [warning] UnauthorizedError in onerror (current status: 'needsAuth'): Unauthorized
2026-03-18 11:38:42.845 [warning] Auth-related error connecting to streamableHttp server, returning transport
2026-03-18 11:38:42.846 [info] Successfully connected to streamableHttp server
2026-03-18 11:38:42.846 [info] Storing streamableHttp client
2026-03-18 11:38:42.846 [info] [MCP Allowlist] Creating adapter with serverName="example-mcp", identifier="example-mcp"
2026-03-18 11:38:42.846 [info] Successfully reloaded client
2026-03-18 11:38:42.846 [info] CreateClient completed (from existing), server stored: true
2026-03-18 11:38:51.966 [info] Received OAuth callback with code
2026-03-18 11:38:54.569 [info] Using redirect URL
2026-03-18 11:38:54.569 [info] Using redirect URL
2026-03-18 11:38:54.569 [info] Using redirect URL
2026-03-18 11:38:54.570 [info] Using redirect URL
2026-03-18 11:38:55.218 [info] Saving tokens
2026-03-18 11:38:55.220 [info] OAuth authorization completed
2026-03-18 11:38:55.224 [info] Handling ReloadClient action
2026-03-18 11:38:55.226 [info] Cleaning up, reason: reload_client
2026-03-18 11:38:55.227 [warning] [V1] needsAuth -> disconnected
2026-03-18 11:38:55.227 [info] Creating streamableHttp transport
2026-03-18 11:38:55.227 [info] No scopes in config, fetching from well-known endpoint
2026-03-18 11:38:55.437 [info] Using redirect URL
2026-03-18 11:38:55.438 [info] Persisting static OAuth client information for callback flow
2026-03-18 11:38:55.438 [info] Persisted static OAuth client information for callback flow
2026-03-18 11:38:55.439 [info] Connecting to streamableHttp server
2026-03-18 11:38:55.755 [info] Successfully connected to streamableHttp server
2026-03-18 11:38:55.755 [info] Storing streamableHttp client
2026-03-18 11:38:55.755 [info] [MCP Allowlist] Creating adapter with serverName="example-mcp", identifier="example-mcp"
2026-03-18 11:38:55.755 [info] Successfully reloaded client
2026-03-18 12:13:05.203 [info] Using enriched static OAuth client information from storage
2026-03-18 12:13:05.225 [info] Using redirect URL
2026-03-18 12:13:05.379 [info] Invalidating credentials: tokens
2026-03-18 12:13:07.972 [info] Using enriched static OAuth client information from storage
2026-03-18 12:13:07.972 [info] Using redirect URL
2026-03-18 12:13:07.973 [info] Using redirect URL
2026-03-18 12:13:07.973 [info] Using redirect URL
2026-03-18 12:13:07.973 [info] Saving PKCE code verifier
2026-03-18 12:13:07.975 [info] Redirect to authorization requested
2026-03-18 12:13:07.976 [info] Stored server URL for OAuth flow
2026-03-18 12:13:07.976 [info] OAuth provider needs auth callback during connection
2026-03-18 12:13:07.977 [warning] UnauthorizedError in onerror (current status: 'needsAuth'): Unauthorized
2026-03-18 12:13:07.977 [warning] listOfferingsForUI sub-call failed (count=1): tools(authRelated=true): Unauthorized

Expected Behavior

refresh token is used to get new token and MCP still works

Operating System

Linux

Version Information

Version: 2.6.19 (Universal)
VSCode Version: 1.105.1
Commit: 224838f96445be37e3db643a163a817c15b36060
Date: 2026-03-12T04:07:27.435Z
Build Type: Stable
Release Track: Default
Electron: 39.4.0
Chromium: 142.0.7444.265
Node.js: 22.22.0
V8: 14.2.231.22-electron.0
OS: Darwin arm64 25.3.0

Does this stop you from using Cursor

Yes - Cursor is unusable

Hello, we are noticing the exact same thing.

I am using Cursor 2.6 and my MCP server using Oauth disconnects very quickly. It seems the Refresh token is not being used to keep the session live.

Hi @Karlis_Melderis & @Bharat_Batra,

This is a known issue that our team is actively tracking. While an earlier fix (shipped in 2.5) addressed some related problems, such as MCP servers incorrectly showing as connected after token expiry — the underlying refresh token behavior still has gaps.

When the access token expires, Cursor doesn’t reliably use the refresh token to silently obtain a new one, and instead falls back to requiring full re-authentication. You’re also right that the logs in recent versions provide less visibility into the refresh flow than older versions did (which used to show refreshPresent:true) — that reduced logging makes it harder to debug what’s happening.

Our team is aware and working on a more complete fix. Unfortunately, there’s no workaround at the moment other than re-authenticating when the connection drops.

2 Likes

Hey Mohit! Many thanks for confirming the issue. Do you have a timeline in mind for the fix?

Unfortunately, I don’t have a timeline for the fix, but I’ll keep this thread post updated when it’s out.

1 Like

Hey Mohit. I’m noticing now that Cursor is not timing out like it was previously. Before we go ahead and roll it out on our side / inform our customers, can you confirm this was fixed at your end?

Bharat, thanks for the update — glad to hear things are improving on your end.

I’ve checked and can’t confirm that a specific fix for this has shipped in the stable channel yet. There have been several improvements to MCP connection handling and OAuth resilience in recent builds, but they’re currently only available on the Nightly release track.

Before rolling this out to your customers, I’d recommend switching to Nightly ( Nightly Downloads | Cursor - The best way to code with AI ) and testing there for a few days to make sure the improvement holds. That way, you can validate against the latest fixes before committing to a broader rollout.

It feels that latest Cursor broke our oAuth flow as such and logs are still not very helpful

2026-04-07 11:13:29.731 [info] Received OAuth callback with code
2026-04-07 11:13:32.398 [info] Using attempt-scoped OAuth client information for callback flow
2026-04-07 11:13:32.403 [info] Using redirect URL
2026-04-07 11:13:32.554 [error] Failed to complete OAuth exchange HTTP 404: Invalid OAuth error response: SyntaxError: Unexpected token '<', "<html>
<h"... is not valid JSON. Raw body: <html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

what redirect URL was used?

This is a separate issue from the original refresh token problem — a regression in a recent update broke the initial OAuth token exchange for MCP servers that use a separate authorization server.

A fix has been shipped. Could you update Cursor to the latest version (3.0.12 or later) and try the OAuth flow again? You can check your version in Help > About and trigger an update via the Command Palette > Attempt Update.

To answer your question about the redirect URL: the issue wasn’t the redirect URL itself, but that the token exchange request was being sent to the wrong server endpoint, which is why it returned a 404 from nginx. The fix corrects the endpoint resolution.

Let me know if the issue persists after updating.

With the 3.0.12 version our OAuth flow stopped to working:

2026-04-07 17:26:41.187 [info] Persisting static OAuth client information for callback flow

2026-04-07 17:26:42.628 [info] Using enriched static OAuth client information from storage
2026-04-07 17:26:42.629 [info] Using redirect URL

2026-04-07 17:26:42.630 [info] Using redirect URL

2026-04-07 17:26:42.631 [info] Saving PKCE code verifier

2026-04-07 17:26:42.636 [info] MCP OAuth redirect to authorization

2026-04-07 17:26:42.641 [info] Stored server URL for OAuth flow

2026-04-07 17:26:42.641 [info] OAuth provider needs auth callback during connection

2026-04-07 17:26:42.641 [info] Connect failed after auth_required; returning needsAuth (streamableHttp)

2026-04-07 17:26:42.642 [info] MCP OAuth needsAuth (v2)

2026-04-07 17:26:42.643 [warning] [V2 FSM] connection:connect_failure: conn=connecting,auth=unknown -> conn=transient_failure,auth=unknown

2026-04-07 17:26:42.643 [info] ReloadClient completed, connected: false, statusType: needsAuth

2026-04-07 17:26:47.376 [info] Received OAuth callback with code

2026-04-07 17:26:48.854 [info] Using attempt-scoped OAuth client information for callback flow

2026-04-07 17:26:48.856 [info] Using redirect URL

2026-04-07 17:26:49.063 [error] Failed to complete OAuth exchange HTTP 404: Invalid OAuth error response: SyntaxError: Unexpected end of JSON input. Raw body: 

The request is going to the root ‘/’ (at least we have this request in our logs), maybe it calls another url, we don’t know since the logs are ‘useful’ now.

In general, it would be helpful to have more useful information in the logs. For example, the log entry [info] Using redirect URL - is there a reason to not include the actual URL being used?

Similarly, in the last line - [error] Failed to complete OAuth exchange HTTP 404 - including the requested URL would significantly improve traceability.
Thank you.

1 Like

This looks like the 3.0.12 fix didn’t fully resolve the token exchange issue for your OAuth server configuration. The fix addressed a specific regression, but the error pattern you’re showing (token exchange POST hitting / and returning a 404 with empty body) suggests endpoint resolution is still wrong for your setup.

I’ve flagged this with our extensibility team with your logs and details.

While the team works on a fix, you can try wrapping your MCP server with mcp-remote as a workaround:

{
"your-server": {
"command": "npx",
"args": ["-y", "mcp-remote", "
"]
}
}

This handles the OAuth flow externally and bypasses Cursor’s token exchange logic.

One question that would help the team: is your OAuth authorization server on a different domain from your MCP server URL? That setup is where the endpoint resolution has been failing most consistently.

You do realise that tiny mcp-remote open source library (written by two people) has better logs and is more usable than built in Cursor MCP features

Just dropping my thoughts here
:wink:

Yes, the OAuth authorization server is on a different domain from the MCP server.

Our setup:

  • MCP server URL: https://our.domain.dev/ai/mcp (Azure API Management → backend)

  • OAuth AS: https://login.microsoftonline.com/common/v2.0 (Microsoft Entra ID)

APIM serves the well-known metadata at our domain (our.domain.dev), but the authorization_endpoint and token_endpoint in the metadata point directly to Microsoft:

{
  "authorization_endpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
  "token_endpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/token"
}

We also serve /.well-known/oauth-protected-resource with a reference to the authorization server.

Also, my assumption that the request to the root is executed during the exchange might be wrong. I retested without noisy logs, and there is only one 404 request, which I believe is unrelated to the issue:

GET /.well-known/oauth-protected-resource/ai/mcp

Also, I’ve just rechecked Cursor version 2.6.21 - the OAuth flow works correctly there.

Here is the logs from Cursor 2.6.21 (if it can help):

2026-04-08 10:17:06.988 [info] Received OAuth callback with code

2026-04-08 10:17:08.046 [info] Using redirect URL

2026-04-08 10:17:08.046 [info] Using redirect URL

2026-04-08 10:17:08.046 [info] Using redirect URL

2026-04-08 10:17:08.047 [info] Using redirect URL

2026-04-08 10:17:08.370 [info] Saving tokens

2026-04-08 10:17:08.372 [info] OAuth authorization completed

2026-04-08 10:17:08.378 [info] [V2] Handling ReloadClient action

2026-04-08 10:17:08.378 [info] [V2 FSM] connection:connect_start: conn=idle,auth=unknown -> conn=connecting,auth=unknown

2026-04-08 10:17:08.380 [info] No scopes in config, fetching from well-known endpoint

2026-04-08 10:17:08.720 [info] Using OAuth scopes: openid, profile, offline_access, https://our.domain.dev/ai/mcp/Mcp.Full

2026-04-08 10:17:08.721 [info] Using redirect URL

2026-04-08 10:17:08.721 [info] Persisting static OAuth client information for callback flow

2026-04-08 10:17:10.155 [info] Successfully connected to streamableHttp server

2026-04-08 10:17:10.155 [info] [V2 FSM] connection:connect_success: conn=connecting,auth=unknown -> conn=connected,auth=unknown

2026-04-08 10:17:10.156 [info] ReloadClient completed, connected: true, statusType: connected

@Karlis_Melderis — Fair point on the logging. I’ve passed that feedback to the team. More actionable log output (including the actual URLs being used and the endpoints being called during token exchange) would make these issues much easier to debug from your side.

@Oleksandr_Reznichenk — Thank you for the detailed breakdown and especially the 2.6.21 comparison logs. This confirms the regression clearly.

Your setup (MCP server and OAuth authorization server on separate domains, with Microsoft Entra ID using a tenant-specific path like /common/v2.0) is the exact configuration that the recent fix didn’t fully cover. The fix corrected the issue for simpler setups, but in cross-domain configurations where the authorization server URL includes a path component, the endpoint resolution during the token exchange step remains incorrect.

I’ve flagged this specific variant — including your APIM + Entra ID details and the working 2.6.21 logs — with our extensibility team.

In the meantime, wrapping your MCP server with mcp-remote (as mentioned above) or staying on 2.6.21 are both viable workarounds while the fix is being developed.

2 Likes

@mohitjain
We’ve found a misconfiguration in our OAuth flow. After correcting it, both Cursor 2 and Cursor 3 now work well.
Sorry for the misleading information.