MCP JetBrains integration on Windows: malformed `file://` root URI kills the session, then SSE reconnect fails with 409 Conflict

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

Observed sequence (timestamps from MCP output log)

1. Initial connection refused (benign, self-resolves):

[warning] Error connecting to streamableHttp server, falling back to SSE: fetch failed
[error] Error connecting to SSE server after fallback: SSE error: TypeError: fetch failed: connect ECONNREFUSED ::1:64342, connect ECONNREFUSED 127.0.0.1:64342
[warning] Connection failed: SSE error: TypeError: fetch failed: connect ECONNREFUSED ::1:64342, connect ECONNREFUSED 127.0.0.1:64342

This happens because Rider’s embedded MCP HTTP server isn’t ready yet at the moment Cursor first tries to connect. It resolves on its own ~1 minute later.

2. Malformed root URI kills a successful connection:

[info] Successfully connected to streamableHttp server
[warning] MCP HTTP exchange completed
[error] Client error: Streamable HTTP error: Error POSTing to endpoint: {"jsonrpc":"2.0","error":{"code":-32700,"message":"Invalid JSON-RPC payload: Root URI must start with 'file://', got: c:\\Users\\manu\\RiderProjects\\wpf"},"id":null}
[warning] [V2 FSM] connection:transport_error: conn=connected,auth=valid -> conn=failed,auth=valid

Right after a successful handshake, Cursor sends the workspace root as a raw Windows path (c:\Users\manu\RiderProjects\wpf) instead of a proper file:// URI (e.g. file:///c:/Users/manu/RiderProjects/wpf). The JetBrains MCP server validates the URI format strictly and rejects it with a JSON-RPC -32700 error, which kills the transport that had just connected successfully.

3. Reconnect attempts fail with 409 Conflict, exhausting retries:

[error] Client error: Streamable HTTP error: Failed to open SSE stream: Conflict
[warning] [V2 FSM] connection:transport_error: conn=connected,auth=valid -> conn=failed,auth=valid
[error] Client error: Failed to reconnect SSE stream: Streamable HTTP error: Failed to open SSE stream: Conflict
[error] Client error: Maximum reconnection attempts (2) exceeded.

Because the previous session wasn’t cleanly closed server-side after the transport error in step 2, the next SSE reconnect attempt collides with the stale session and the server returns 409 Conflict. After 2 failed attempts, Cursor gives up and the connection stays in a failed state indefinitely.

Steps to Reproduce

Workaround: Manually toggling the JetBrains MCP server off/on in Tools & MCP immediately restores the connection (forces a clean client-side session teardown/recreate). Simply waiting does not reliably fix it — sometimes it takes tens of minutes to self-recover, sometimes it doesn’t recover until the manual toggle.

Expected Behavior

  • Cursor should encode the workspace root as a valid file:// URI when sending it to MCP servers on Windows (this looks like the same class of Windows-path-encoding bug reported elsewhere for MCP, e.g. backslash handling in stdio server paths).
  • After a transport error, Cursor should perform a full client-side teardown (equivalent to the manual toggle) before attempting to reconnect, instead of retrying a new SSE stream against a session the server still considers active.

Operating System

Windows 10/11

Version Information

Version: 3.12.10 (user setup)
VS Code Extension API: 1.128.0
Commit: 24a12dbd9cabf48956ce5bb3dbd234e41385b3d0
Date: 2026-07-16T03:15:48.538Z
Layout: IDE
Build Type: Stable
Release Track: Default
Electron: 40.10.3
Chromium: 144.0.7559.236
Node.js: 24.15.0
V8: 14.4.258.32-electron.0
xterm.js: 6.1.0-beta.256
OS: Windows_NT x64 10.0.26200

For AI issues: which model did you use?

Sonnet 5

Does this stop you from using Cursor

No - Cursor works, but with this issue

Hi @Emmanuel_Belair,

Thanks for the information. To help us reproduce the issue, could you describe what the agent is using the JetBrains MCP server for when the call fails? Details about a specific tool, workflow, or prompt would be very helpful!

Thanks for following up. I went back and checked my local agent session logs for the exact time window covered in the pasted output (13:39–14:11 on that day), and **no chat/agent tool call was in progress at any of the failure timestamps**. There was no active prompt, no tool invocation, nothing running in the background from my side.

This is actually the key detail: the failures are not tied to a specific tool, workflow, or prompt at all. They happen during the MCP client’s own connection lifecycle, independent of any agent activity:

  • The “Root URI must start with ‘file://’” error occurs **immediately after** the transport reports “Successfully connected” — i.e. during the initial session handshake/capability exchange right after connecting, before any tool call could even happen.
  • The “Failed to open SSE stream: Conflict” error occurs ~5 minutes later, which lines up with what looks like an idle SSE keep-alive/reconnect check, again with no tool call involved.

So to reproduce this, no specific chat prompt or tool seems to be needed. It looks reproducible just by:

  1. Configuring a JetBrains MCP server as a remote `url` entry in `mcp.json` (`http://localhost:/stream`, pointing at Rider’s built-in HTTP server) on Windows.
  2. Opening the workspace / letting Cursor establish (or re-establish) the MCP connection — e.g. on IDE startup, on “Reload Window”, or after toggling the server off/on.
  3. Watching the MCP output log around the connection handshake, without ever invoking a tool from chat.

If it’s useful for your reproduction, I’m happy to trigger an explicit JetBrains tool call from chat (e.g. a file-diagnostics request) right after a fresh connection and share the exact timestamped logs, to confirm whether the same malformed-URI error can also occur mid-tool-call, or only during the initial handshake. Let me know if that would help.

## Update: live reproduction just now, with an extra finding

I reproduced the “Failed to open SSE stream: Conflict” failure again live, spontaneously, ~5 minutes after a successful connection, with no tool call involved (confirmed via `mcp-server-user-jetbrains.log` and `mcpprocess.log`, both ending on `Maximum reconnection attempts (2) exceeded.`):

2026-07-20 17:02:02.512 [warning] MCP HTTP exchange completed

2026-07-20 17:02:02.512 [error] Client error: Streamable HTTP error: Failed to open SSE stream: Conflict

2026-07-20 17:02:02.512 [warning] [V2 FSM] connection:transport_error: conn=connected,auth=unknown -> conn=failed,auth=unknown

2026-07-20 17:02:04.024 [error] Client error: Maximum reconnection attempts (2) exceeded.

But then, about 35 seconds after that failure, I made an explicit tool call (`get_file_problems` on a file) from chat — **and it succeeded**, returning a valid result. Neither log file recorded any new line for that successful call; both still end at the `17:02:04.024` failure.

This suggests the “failed” / “Maximum reconnection attempts exceeded” state applies to one specific SSE stream (likely the server-initiated notification channel), not to the functional request/response path actually used for tool calls, which appears to keep working through some other, unlogged connection. This might also explain why the connection sometimes appears to “self-heal” over time in the original report: the underlying tool-call path may never have actually been down, only the auxiliary SSE stream was — though the client still reports the whole connection as failed, which is confusing from the user’s perspective (the Tools & MCP panel shows the server as broken even though calls go through).

This is a different, apparently less severe failure mode than the “Root URI must start with ‘file://’” one from the original report, which did seem to break the connection for real (that one happened right at session initialization, before any tool call was possible at all).

## Root cause found: the manual toggle does a clean teardown, the automatic retry doesn’t

I then manually toggled the JetBrains MCP server off/on from Tools & MCP while the connection was still in the `failed` state above, and captured the exact log diff:

2026-07-20 17:06:51.868 [info] stopped connection: user-jetbrains

2026-07-20 17:06:51.881 [info] connecting streamableHttp for "jetbrains" (user-jetbrains)

2026-07-20 17:06:51.881 [info] [V2 FSM] connection:connect_start: conn=idle,auth=unknown -> conn=connecting,auth=unknown

2026-07-20 17:06:51.939 [info] Successfully connected to streamableHttp server

2026-07-20 17:06:51.939 [info] [V2 FSM] connection:connect_success: conn=connecting,auth=unknown -> conn=connected,auth=unknown

Compare this to the automatic reconnect attempts right after the failure, a few minutes earlier:

2026-07-20 17:02:02.512 [error] Client error: Failed to reconnect SSE stream: Streamable HTTP error: Failed to open SSE stream: Conflict

2026-07-20 17:02:04.024 [error] Client error: Maximum reconnection attempts (2) exceeded.

The key difference: the **manual toggle explicitly logs `stopped connection: user-jetbrains` before reconnecting** — i.e. it performs a full client-side session teardown before establishing a new one, which succeeds immediately (71ms). The **automatic reconnect path skips this teardown step entirely** and just tries to reopen an SSE stream directly, which collides with the session the server still considers active, hence the `409 Conflict`, and it gives up after 2 attempts without ever trying a clean teardown+recreate.

This looks like the actual, fixable root cause of why the connection gets stuck until manual intervention: **the automatic reconnect logic should perform the same “stopped connection” teardown that the manual toggle does before attempting to re-open the stream**, instead of retrying against a session the server hasn’t released yet.

Thanks @Emmanuel_Belair!

I was able to reproduce this myself, and I can confirm that we’re doing something out of spec here:

  • uri: Unique identifier for the root. This MUST be a file:// URI in the current specification.

Ignoring an invalid roots response instead of killing the session with -32700 would be friendlier, but putting that aside, I’ve gone ahead and filed a bug with the team!

Your follow-up observation also looks correct: only the server-to-client notification stream is in conflict. The request/response path on the same session stays healthy, which is why tool calls kept working while the panel showed the server as broken.

Will look into improving this, but the off/on toggle in Tools & MCP remains the reliable workaround.