Back to Blog
TroubleshootingOct 30, 202610 min read

MCP Server Works in Terminal but Not in Claude Desktop: 6 Causes and Fixes

NT

Nikhil Tiwari

MCP Playground

πŸ“– TL;DR

  • Your terminal and your MCP client start the server in different worlds. The PATH, the working folder, the environment variables and even the Node or Python version can all differ.
  • Read the client's log first. Claude Desktop only says "Server disconnected"; the real error is in ~/Library/Logs/Claude/ (Mac) or %APPDATA%\Claude\logs\ (Windows).
  • The six causes: PATH, working folder, environment variables, output on stdout, config mistakes, and a different runtime version.
  • Almost every fix is in the config: absolute paths, an env block, and one program in command.
  • To narrow it down, start the server outside both your terminal and the client, with exactly the config's command, args and env.

Table of Contents

Why "it works in my terminal" proves less than you think

When you run node build/index.js or uv run server.py in a terminal, the server inherits everything your shell set up. That includes your full PATH, the folder you're in, every variable you exported, and whichever Node or Python your version manager picked.

A stdio MCP client starts the same command from scratch. On a Mac, Claude Desktop and Cursor opened from the Dock are started by launchd, not by your shell, so none of your .zshrc or .zprofile runs. On any system, the client passes only the environment it chooses.

So the server code is the same, but the process is not. The six causes below are the ways those two processes differ.

Step 0: read the client's log

Don't guess from "Server disconnected" or a red dot. The client writes the actual error, plus whatever your server printed to stderr, to a log file.

  • Claude Desktop on macOS: ~/Library/Logs/Claude/, with mcp.log for all servers and mcp-server-NAME.log for each one.
  • Claude Desktop on Windows: %APPDATA%\Claude\logs\
  • Cursor: the Output panel, with the MCP logs picked in the dropdown.

On a Mac, keep this running while you restart the client:

tail -n 50 -f ~/Library/Logs/Claude/mcp*.log

The last error before the disconnect is usually the answer. If the server crashed, its stack trace is in the same file.

Match your error to a cause

What the log says Likely cause
spawn npx ENOENT, env: node: No such file or directory1. PATH
EROFS: read-only file system, ENOENT: no such file or directory, open './data.db'2. Working folder
"API key is required", 401 Unauthorized, KeyError: 'OPENAI_API_KEY'3. Environment variables
Unexpected token … is not valid JSON4. Output on stdout
Nothing at all, or the server isn't listed5. Config
ModuleNotFoundError: No module named 'mcp', SyntaxError in a dependency6. Runtime version

1. The client has a shorter PATH

This is the most common one. An app opened from the Dock gets /usr/bin:/bin:/usr/sbin:/sbin as its PATH. Homebrew, nvm, Volta, fnm, asdf and uv all live outside those folders, so npx, node, uvx or docker can't be found.

The log shows spawn npx ENOENT (or spawn uvx ENOENT). If you already put the full path to npx in the config, it changes to env: node: No such file or directory, because npx itself looks up node on PATH.

Fix: find the program with which npx, use that absolute path in command, and give the server a PATH that includes the same folder:

{
  "mcpServers": {
    "my-server": {
      "command": "/opt/homebrew/bin/npx",
      "args": ["-y", "my-mcp-server"],
      "env": {
        "PATH": "/opt/homebrew/bin:/usr/bin:/bin"
      }
    }
  }
}

Windows has its own version of this: npx is npx.cmd, which needs "command": "cmd" with "/c", "npx" at the start of args. Fix "spawn npx ENOENT" covers both systems step by step.

2. The server starts in a different folder

In a terminal you usually cd into the project first. Relative paths in your code, like ./data.db, .env or logs/, then resolve inside the project.

The client doesn't cd anywhere. On a Mac, an app opened from the Dock runs with / as its working folder, and the server inherits it. A server that writes a log file or creates a database next to itself then fails with:

Error: EROFS: read-only file system, mkdir '/logs'

Reading a file gives ENOENT: no such file or directory instead. A Python server that calls load_dotenv() with no arguments quietly loads nothing, which then looks like cause 3.

Fix: don't depend on the working folder.

  • In your code, build paths from the script's own location: path.join(import.meta.dirname, 'data.db') in Node 20.11+, or Path(__file__).parent / "data.db" in Python.
  • In the config, use absolute paths for the script and for any folder you pass as an argument.
  • For uv projects, tell uv where the project is: "args": ["run", "--directory", "/Users/you/my-server", "server.py"].

3. Your environment variables aren't there

Exporting GITHUB_TOKEN or OPENAI_API_KEY in .zshrc only affects programs started from a shell. The client never reads that file.

Clients also don't pass their whole environment to the server. The official TypeScript SDK's stdio client, for example, passes only HOME, LOGNAME, PATH, SHELL, TERM and USER on Mac and Linux (and a similar short list on Windows), plus whatever is in the config's env. A .env file in your project only helps if the server finds it, which runs into cause 2.

The symptom is the server starting, then failing on the first tool call with "API key is required", a 401, or a KeyError.

Fix: put every variable the server needs in the config:

"env": {
  "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_token_here",
  "LOG_LEVEL": "debug"
}

On Windows, a variable set with setx or in System Properties only reaches apps started after the change. Quit the client fully from the system tray and open it again.

4. Something prints to stdout

A stdio server uses stdout for JSON-RPC messages and nothing else. Every line the client reads there must be a JSON-RPC message.

In a terminal, a stray console.log("Server started") or print("Loading…") looks harmless; it just appears on screen. Inside the client, that line is read as a protocol message, fails to parse, and the log shows something like:

Unexpected token 'S', "Server started" is not valid JSON

Depending on the client, the connection then drops or the bad line is skipped. Banners from libraries and progress bars from package downloads cause the same thing.

Fix: send all logging to stderr. In Node use console.error. In Python use print(..., file=sys.stderr) or the logging module, which writes to stderr by default. For npx, keep -y in args so it never stops to ask whether to install a package.

5. The config isn't what you think

If the log shows nothing for your server at all, the client probably never read your entry. Check these in order:

  • Right file? Claude Desktop reads ~/Library/Application Support/Claude/claude_desktop_config.json on a Mac and %APPDATA%\Claude\claude_desktop_config.json on Windows. Settings β†’ Developer β†’ Edit Config opens the one it actually uses. Cursor reads ~/.cursor/mcp.json plus .cursor/mcp.json in the open project. The config file guide lists every client.
  • Valid JSON? A trailing comma or a single backslash in a Windows path makes the whole file invalid, and every server disappears, not just the new one. Paste the file into a JSON validator.
  • One program in command? "command": "node build/index.js" fails, because the client looks for a program with that whole name. Put node in command and the path in args.
  • Fully restarted? Closing the window isn't enough. Quit with Cmd+Q on a Mac or from the system tray on Windows, then open the client again.
  • Server built? For a TypeScript server, the config points at build/index.js. If you changed the source but didn't run the build, the client is still running the old code.

6. A different Node or Python runs it

Your terminal picks a runtime through nvm, pyenv, a virtualenv or uv. The client finds whatever comes first on its own, shorter PATH. That can be a much older version, or a Python without your packages.

  • Python: "command": "python3" in Claude Desktop on a Mac often finds /usr/bin/python3, Apple's copy, which doesn't have your project's packages installed. The log shows ModuleNotFoundError: No module named 'mcp'.
  • Node: an old Node in /usr/local/bin runs a server written for Node 20. Errors range from SyntaxError in a dependency to fetch is not defined.

Fix: point command at the exact runtime you tested with.

  • For a virtualenv: "command": "/Users/you/my-server/.venv/bin/python".
  • For uv: uv run --directory /Users/you/my-server server.py, so uv uses the project's own Python and packages.
  • For Node: the full path from which node, run in the terminal where the server works.

Check what the terminal is using with node --version and which python3, then compare it with what the config points at.

Find which one it is

The terminal test hides causes 1, 2, 3 and 6, because the terminal supplies everything the client doesn't. The client test shows you the failure but not much else. What helps is a third place to run the same config, where you can change one thing at a time.

MCP Playground Desktop works well for this:

  • Import your Claude Desktop, Cursor, VS Code, Windsurf or Claude Code config. Each server comes in with the same command, arguments, working folder and env vars the client uses.
  • It passes environment variables like an SDK client does: only the basic system ones plus the server's own env list. If a key is missing from your config, the server fails here too, and you'll see its complaint in the Server stderr tab.
  • On a Mac it loads your login shell's PATH. If the server connects here but not in Claude Desktop, PATH is your problem (cause 1). If it fails in both, look at causes 2 to 6.
  • Spawn errors come with a next step. A missing program shows "Couldn't find uvx on this computer" with the install command, and a working folder that doesn't exist is reported as a missing folder, not a missing command.
  • The JSON-RPC tab shows each message once it connects, so you can see whether tools/list came back and what it contained.

New to the app? The MCP Playground Desktop guide walks through every tab. Once the server connects, call a tool that reads and one that writes before you trust it. How to test local MCP servers has a checklist.

If the server connects everywhere but drops after a while, the problem is in the protocol, not the launch. See MCP Server Not Working? Fix Error -32000, Timeouts, and Connection Failures.

Frequently Asked Questions

Why does my MCP server work in the terminal but not in Claude Desktop?+
Claude Desktop starts the server with its own PATH, working folder and environment, not your terminal's. The usual causes are a program not on the client's PATH, relative file paths, environment variables that only exist in your shell, logging to stdout, a config mistake, or a different Node or Python version.
Where do I see why Claude Desktop says "Server disconnected"?+
In the log files: ~/Library/Logs/Claude/ on macOS (mcp.log and one mcp-server-NAME.log per server) and %APPDATA%\Claude\logs\ on Windows. Your server's stderr output is written there too.
Why aren't my exported environment variables reaching the MCP server?+
Variables exported in .zshrc or .bashrc only exist in programs started from a shell, and clients pass only a few system variables to the server. Put every variable the server needs in the "env" block of its config entry.
What does "EROFS: read-only file system" mean for an MCP server?+
The server tried to write a file using a relative path while its working folder was /, which is read-only on macOS. Build file paths from the script's own location, or use absolute paths in the config.
Why does my MCP server log "is not valid JSON"?+
Something printed plain text to stdout, which a stdio server must use only for JSON-RPC messages. Send logs to stderr with console.error in Node or print(..., file=sys.stderr) in Python.
Does the same apply to Cursor, VS Code and Windsurf?+
Yes. Any client that starts a stdio server itself can differ from your terminal in PATH, working folder, environment and runtime version. The fixes in the config are the same; only the config file location changes.

Conclusion

"Works in my terminal" means the code is fine. It says nothing about the process the client starts. Read the client's log, match the error to one of the six causes, and fix the config with absolute paths and an env block.

Then test the server outside both the terminal and the client, with the same config, so you know which side the problem is on. Get MCP Playground Desktop for Mac and Windows β†’

NT

Written by Nikhil Tiwari

15+ years in product development. AI enthusiast building developer tools that make complex technologies accessible to everyone.

Build, compare & ship MCP agents

Connect any MCP server, run evals on it, compare 80+ models side-by-side, deploy hosted servers, and save reusable agents you can export as an API β€” all in your browser.

Try for Free β†’
MCP Server Works in Terminal but Not in Claude Desktop: 6 Causes and Fixes