Dev Encyclopedia
ArticlesToolsContactAbout

Get notified when new content drops

No spam. Just new articles, tools, and updates straight to your inbox.

Dev Encyclopedia

A reference for builders

Content

  • Articles
  • Tools
  • About
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

Legal

  • Privacy Policy
  • Terms of Service
  • Disclaimer

ยฉ 2026 Dev Encyclopedia

Back to top โ†‘
  1. Home
  2. /Blog
  3. /MCP Server Disconnected? Here's the Actual Fix (2026)
ai tools12 min read

MCP Server Disconnected? Here's the Actual Fix (2026)

Your MCP server shows disconnected in Claude Desktop, Claude Code, or Cursor. Here's exactly why, with fixes for stdout pollution, paths, and env vars.

Zeeshan Tofiq
Zeeshan Tofiq
July 12, 2026
On this page

On this page

  • What "Disconnected" Actually Means
  • Check This First: Run It Manually
  • Cause 1: The Process Never Started
  • Cause 2: Stdout Pollution
  • Cause 3: Path and Environment Problems
  • Cause 4: Transport Mismatch
  • How to Actually Debug This Instead of Guessing
  • Client-Specific Quirks: Claude Desktop, Claude Code, and Cursor
  • Quick Reference: All Four Causes at a Glance
  • Frequently Asked Questions

Your MCP server was working five minutes ago. Now Claude Desktop, Claude Code, or Cursor just shows "disconnected" and gives you nothing else to go on. No stack trace, no line number, just a status dot that turned red. Here's what is actually happening and how to fix it.

MCP servers run as a subprocess of your AI client, not as some remote service you can curl. That single fact explains almost every disconnect you'll ever see. When you see "disconnected," one of four things happened: the process never started, it started and then crashed, its stdout got polluted with something that isn't valid JSON-RPC, or there's a mismatch between the transport your client expects and the one your server actually speaks. That's the whole list. Once you know which of the four you're dealing with, the fix is usually five minutes, not an afternoon of guessing.

๐Ÿ’ก Quick fix summary

Run your server's exact start command directly in a terminal first. If it crashes there, the bug is in your server, not MCP. If it runs fine standalone but disconnects inside your AI client, the problem is almost always an absolute path, a missing environment variable, or a stray print()/console.log() writing to stdout. Jump to the section matching your symptom below, or run your config through MCPConfigCheck first to rule out known config mistakes automatically.

What "Disconnected" Actually Means

The Model Context Protocol defines two transports that matter for local development: stdio and Streamable HTTP (which replaced the older HTTP+SSE transport). Almost every MCP server you run locally, whether it's one you wrote or one you installed from npm or PyPI, uses stdio. That means your AI client spawns your server as a child process and talks to it by writing JSON-RPC messages to the child's standard input and reading JSON-RPC messages back from its standard output.

That's the entire mechanism. There's no socket, no port, no HTTP handshake to inspect in your browser's network tab. Which is exactly why "disconnected" feels like a black box: the failure is happening at the process and stream level, somewhere between your operating system spawning a subprocess and two programs agreeing on a message format. Every real cause of an MCP disconnect traces back to one of those two mechanics breaking down.

  • <strong>Spawn failure.</strong> The client tried to launch your server and the operating system couldn't find the command, couldn't find the script, or the process exited immediately with a non-zero code.
  • <strong>Runtime crash.</strong> The process started fine, ran for a few seconds or longer, then threw an unhandled exception and exited.
  • <strong>Corrupted stream.</strong> The process is still alive, but something wrote non JSON-RPC bytes to stdout, so the client can no longer parse the messages coming back and gives up.
  • <strong>Protocol mismatch.</strong> The process is alive and behaving correctly, but it's speaking a transport (stdio vs. Streamable HTTP) the client isn't configured to expect.

Check This First: Run It Manually

Before you touch any client config, try running your server's exact start command directly in a terminal, completely outside of Claude Desktop, Claude Code, or Cursor. This single step eliminates half of all MCP disconnect reports, because it immediately tells you whether the bug is in your server or in how the client is invoking it.

bash โ€” Run the same command your client config uses
# Whatever command is in your mcpServers config, run it as-is
node /Users/you/projects/my-server/index.js

# or for a Python server
python /Users/you/projects/my-server/server.py

If it throws an error or a traceback right there in your terminal, you have your answer, and it has nothing to do with MCP as a protocol. Fix that bug first, then move on. If it starts cleanly and just sits there waiting (that's normal for a stdio server with no client attached), the bug is more likely in how your client is spawning or talking to the process, which is what the rest of this guide covers.

โ„น Info

A stdio MCP server that starts correctly will look like it's hanging when run manually. That's expected: it's blocked reading from stdin, waiting for a client to send it a JSON-RPC message. No output at all is a good sign here, not a bad one.

Cause 1: The Process Never Started

This shows up as an immediate disconnect, often within a second of the client trying to connect. Usually the command in your config is wrong in a way your shell would have silently forgiven but a GUI application launching a subprocess will not.

A relative path like ./server.py or server.js works fine when you run it from your project folder in a terminal, because your shell's current working directory is that folder. Your AI client is not launching from that folder. It's launching from wherever its own process happens to run from, usually your home directory or the application bundle's own location, so a relative path resolves to a completely different (and nonexistent) file.

The same problem hits interpreters and package runners. npx, python, and uv being available in your terminal doesn't mean the GUI app sees them. On macOS in particular, GUI applications launched from Finder or Spotlight do not inherit your shell's PATH the way a Terminal.app session does, because they never load your .zshrc or .bash_profile. A command that resolves perfectly in your terminal can produce a plain "command not found" style spawn failure when the same string is handed to posix_spawn by a desktop app.

json โ€” claude_desktop_config.json (or mcp.json)
{
  "mcpServers": {
    "my-server": {
      "command": "/usr/local/bin/python3",
      "args": ["/Users/you/projects/my-server/server.py"]
    }
  }
}

Use full, absolute paths for both the command and the script argument. If you're not sure where an interpreter actually lives, ask your shell directly rather than assuming.

bash
which python3
which node
which npx

โš  Warning

Version managers make this worse. If you installed Node with nvm or Python with pyenv, which node in your terminal often points at a version-manager shim, not a stable absolute path. Prefer the actual binary path (check nvm which <version> or your pyenv version's bin/ directory) so the config doesn't break the next time you switch versions.

Cause 2: Stdout Pollution

This is the cause that gets almost everyone at least once, and it's the least intuitive if you haven't hit it before. With stdio transport, stdout is reserved entirely for JSON-RPC messages between your server and the client. Nothing else is allowed to touch it. Any print() statement, any library that logs to stdout by default, any startup banner your framework prints for you, corrupts the stream. The client either disconnects immediately or behaves in ways that look completely unrelated to the actual cause, because from its perspective it just received garbage where it expected a JSON object.

The frustrating part is that this bug is invisible when you test your server in isolation with a simple script, and only shows up once a client is actually parsing the stream character by character.

python โ€” Python: broken vs. fixed
# This breaks your server
print("Server starting up...")

# This is fine
import sys
print("Server starting up...", file=sys.stderr)
javascript โ€” Node.js: broken vs. fixed
// Breaks the server
console.log("Connected to database");

// Fine
console.error("Connected to database");

In Node, console.log writes to stdout and console.error writes to stderr. In Python, print() defaults to stdout unless you redirect it. If you're using a logging library instead of raw print statements, check its default output stream before assuming it's safe. Python's logging module defaults to stderr when you call basicConfig() with no handler, which is safe, but plenty of third-party loggers and ORMs default to stdout for their query logs.

This also catches people indirectly, when a dependency you didn't write logs to stdout on its own initialization. If your server works fine when you test it manually but disconnects only when the AI client runs it, and you've already ruled out path and environment issues, a dependency writing to stdout is one of the first things worth grepping your node_modules or site-packages for.

โš  Warning

Watch for implicit stdout writes too, not just explicit ones. Uncaught exceptions in some runtimes print a traceback to stdout instead of stderr depending on how the process was launched. A crash you can't see anywhere in your client's logs is often exactly this: the traceback corrupted the stream before it could even reach a place you'd think to look.

Cause 3: Path and Environment Problems

Related to cause 1, but specifically about environment variables rather than the command path itself. If your server reads an API key, a database URL, or any other secret from an environment variable, remember that the process spawning your server does not automatically inherit your shell's full environment the way a terminal session does. GUI applications on macOS and Windows launch with a minimal environment set by the OS, not the one built up by your .zshrc, .bashrc, or direnv configuration.

You almost always need to declare those variables explicitly inside the server's config block, even if the exact same variable is already exported in your shell profile.

json โ€” Declaring env vars explicitly
{
  "mcpServers": {
    "my-server": {
      "command": "/usr/local/bin/python3",
      "args": ["/Users/you/projects/my-server/server.py"],
      "env": {
        "DATABASE_URL": "postgres://localhost/mydb",
        "API_KEY": "your-key-here"
      }
    }
  }
}

The failure mode here is subtly different from a spawn failure. The process actually starts (you'll often see the client briefly show "connecting" before it flips to "disconnected"), then your server code hits a missing environment variable, throws, and exits. If your server silently fails a second or two after startup, with no obvious spawn error, a missing environment variable it expected to just be there is the first thing to check.

Working directory assumptions cause a similar failure. If your server reads a config file, a .env file, or a SQLite database using a relative path like ./data.db, that path resolves relative to whatever working directory the client happened to launch the process from, which is often not your project folder at all. Use absolute paths for any file your server reads at startup, not just for the command itself.

Cause 4: Transport Mismatch

Less common than the first three, but worth ruling out, especially if you're connecting to a remote or self-hosted MCP server rather than a local one. Your config might specify a url field, which tells the client to expect a Streamable HTTP (or the older SSE) server, when your actual server is only implemented for stdio, or the reverse: a command and args pair when the server you're pointing at only listens on a port.

Double check which transport your server actually implements against what your config is asking for. Mixing these up produces a disconnect that looks identical to every other cause on the surface, no useful error message, just a red status dot, so it's easy to burn time debugging path issues or stdout pollution on a server that was never going to connect over stdio in the first place.

How to Actually Debug This Instead of Guessing

Once you've ruled out the obvious (the manual run test from earlier in this guide), the next step isn't to keep tweaking your client config and reconnecting. It's to isolate the server from the client entirely using a tool built for exactly this.

  1. 1

    Confirm the Server Runs Standalone

    Run the exact command from your config directly in a terminal, as covered above. If this fails, you're debugging your server, not MCP. Only move to the next step once this succeeds.

  2. 2

    Test It in the Official MCP Inspector

    The MCP Inspector is a browser-based tool maintained by the Model Context Protocol team, built specifically to connect to a server the same way a client would, without any of Claude Desktop, Claude Code, or Cursor's own config layered on top.

    bash โ€” Launch the Inspector against your server
    npx @modelcontextprotocol/inspector node my-server.js
    
    # or for a Python server
    npx @modelcontextprotocol/inspector python my_server.py

    This opens a local web UI showing the exact JSON-RPC messages going back and forth, every tool your server exposes, and lets you call each one individually with test input. If your server misbehaves here too, you've confirmed the bug is in your server, independent of any AI client.

  3. 3

    If the Inspector Works, the Problem Is Your Client Config

    If your server behaves correctly inside the Inspector but still disconnects inside Claude Desktop or Cursor, you've narrowed the problem down to your client configuration specifically: the path, the environment variables it's passing (or not passing), or the working directory it launches from. That's a much smaller problem than "my server is broken."

    This is also where a config-specific checker earns its keep. MCPConfigCheck parses your mcp.json or claude_desktop_config.json entirely in your browser and flags common config-level mistakes and known risky patterns before you spend more time on manual log diving.

Client-Specific Quirks: Claude Desktop, Claude Code, and Cursor

The four root causes above are the same regardless of which AI client you're using, but where you go to look for evidence differs quite a bit between them.

ClientWhere config livesHow to see what went wrong
Claude Desktop<code>claude_desktop_config.json</code>, opened via Settings &gt; Developer &gt; Edit ConfigCheck the app's MCP log files (Settings &gt; Developer shows the log directory) rather than trusting the in-app status alone; startup errors are written there even when the UI just says disconnected
Claude CodeProject-level <code>.mcp.json</code> or user-level config, editable via <code>claude mcp add</code>Run <code>/mcp</code> inside a session to see connection status per server, and <code>/doctor</code> to catch config validation issues (it won't diagnose deeper stdio problems on its own)
Cursor<code>.cursor/mcp.json</code> per project, or a global config in Cursor SettingsThe MCP settings panel shows connection status per server and is usually the fastest place to spot which specific server is failing when you have several configured

One Windows-specific wrinkle worth knowing about if you support users on it: Claude Desktop's config file location on Windows moved after a recent MSIX packaging update. If the Edit Config button in the app doesn't seem to match what you're actually editing, check the real file path directly rather than trusting the shortcut, and if you're specifically debugging under WSL2, treat that as a separate troubleshooting pass since it introduces its own networking and process-spawning failure modes on top of everything covered here.

Quick Reference: All Four Causes at a Glance

The four root causes of an MCP disconnect, side by side
CauseTypical symptomFix
Process never startedDisconnects almost instantly, no delayUse absolute paths for both <code>command</code> and <code>args</code>; verify the interpreter path with <code>which</code>
Stdout pollutionWorks when run manually, disconnects only through the clientMove all logging to stderr (<code>console.error</code>, <code>print(..., file=sys.stderr)</code>); check dependencies too
Path/environment problemsBriefly connects, then disconnects a second or two laterDeclare needed variables explicitly in the config's <code>env</code> block; use absolute paths for any file the server reads
Transport mismatchDisconnects with no distinguishing symptom at allConfirm the server's actual transport (stdio vs. Streamable HTTP) matches what the config specifies

Run MCPConfigCheck against your config before working through this list manually. It catches a number of the most common config-level mistakes automatically, in your browser, before you go digging through client logs.

Frequently Asked Questions

Why does my MCP server work when I run it manually but not through Claude Desktop?

Almost always an environment or path difference. GUI apps on macOS and Windows do not inherit your shell's PATH or environment variables the way a terminal does, so a command that works fine in your terminal can fail silently when launched by the AI client.

  • <strong>Use absolute paths</strong> for both the command and any script arguments in your config
  • <strong>Declare env vars explicitly</strong> in the config's env block, even if they're already set in your shell profile
  • <strong>Use absolute paths for data files</strong> your server reads, since the working directory it launches from may not be your project folder
What does "MCP server disconnected" actually mean at a technical level?

The client launched your server as a subprocess over stdio, and either the subprocess exited unexpectedly, or something wrote non JSON-RPC content to stdout and corrupted the message stream between client and server. There's no socket or handshake to inspect the way there would be with an HTTP-based integration; it's purely a process and stream-level failure.

Is the MCP Inspector official, and is it safe to use?

Yes. It's maintained by the Model Context Protocol team and is the standard first tool to reach for when a server isn't behaving as expected, instead of guessing through client logs alone. It runs locally via npx and connects directly to your server the same way a real client would, without any AI client's own config layered on top.

Can a dependency cause this even if my own code is fine?

Yes. Any library your server imports that logs to stdout by default, an ORM printing query logs, a startup banner from a web framework, will corrupt the JSON-RPC stream the same way a stray print() statement would. This is one of the harder versions of the bug to spot, because your own code looks completely correct.

bash
# Search for likely stdout writers in your dependencies
grep -rn "print(" node_modules/some-dependency/
grep -rn "console.log" node_modules/some-dependency/
Does this apply the same way on Windows?

The same four causes apply, but Windows adds extra failure modes on top, mainly around WSL2 networking and the way spawn() crosses the Windows and Linux boundary. Claude Desktop's config file location on Windows also moved after a recent MSIX packaging update, so verify the actual file path rather than trusting the in-app shortcut.

โ„น Info

If you're specifically running your server inside WSL2 while your AI client runs natively on Windows, treat that as its own separate troubleshooting pass beyond what's covered in this guide.

Why does restarting Claude Desktop or Cursor temporarily fix a disconnected server?

A restart clears the client's in-memory process handle and forces it to respawn your server fresh. If the underlying cause was a one-off crash (a transient network error inside your server, a race condition on startup), a fresh process can genuinely succeed and the disconnect won't recur. But if the cause is structural, an absolute path issue, a missing environment variable, or stdout pollution baked into your server's normal startup sequence, the restart will just reproduce the exact same disconnect on the next launch.

Treat a restart as a way to confirm whether the failure is intermittent or deterministic, not as an actual fix. If it happens again after a restart, go through the four causes above rather than restarting a second time.

Zeeshan Tofiq

Zeeshan Tofiq

Full Stack Developer

Full stack developer with over 6 years of experience building production applications. Writes practical guides on JavaScript, TypeScript, React, Node.js, and cloud infrastructure. Focused on helping developers solve real problems with clean, maintainable code.

Enjoyed this article?

Get practical dev guides, tool updates, and new articles delivered straight to your inbox. No spam, unsubscribe anytime.

Related Articles

ai tools

Claude Code Cheatsheet: Commands, Hooks & Subagents

The complete Claude Code reference: every slash command, keyboard shortcut, hook, subagent, and CLAUDE.md tip, with real examples for developers.

Jun 6, 2026ยท10 min read
ai tools

Multi-Agent AI Coding Workflow: Step-by-Step (2026)

Build a 3-agent AI coding workflow with CrewAI and Python. One agent writes, one reviews, one writes tests. Full code included.

Jun 7, 2026ยท10 min read

On this page

  • What "Disconnected" Actually Means
  • Check This First: Run It Manually
  • Cause 1: The Process Never Started
  • Cause 2: Stdout Pollution
  • Cause 3: Path and Environment Problems
  • Cause 4: Transport Mismatch
  • How to Actually Debug This Instead of Guessing
  • Client-Specific Quirks: Claude Desktop, Claude Code, and Cursor
  • Quick Reference: All Four Causes at a Glance
  • Frequently Asked Questions
Advertisement