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

> Your MCP server runs fine from the terminal, then fails or shows "Server disconnected" in Claude Desktop or Cursor. The client starts it with a different PATH, folder, environment and runtime. Here are the six differences, the error each one leaves in the log, and the fix.

**Source:** https://mcpplaygroundonline.com/blog/mcp-server-works-in-terminal-not-claude-desktop  
**Author:** Nikhil Tiwari  
**Published:** 2026-10-30  
**Category:** Troubleshooting  
**Reading time:** 10 min read  
**Topics:** mcp server works in terminal but not claude desktop, mcp server not working in claude desktop, claude desktop server disconnected, mcp server works locally not in cursor, claude desktop mcp env variables not working, EROFS read-only file system mcp, mcp server is not valid JSON, claude desktop mcp ModuleNotFoundError, mcp server working directory claude desktop, claude desktop mcp logs

---

📖 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 the terminal is a bad test](#why)
-   [Step 0: read the client's log](#logs)
-   [Match your error to a cause](#table)
-   [1\. The client has a shorter PATH](#path)
-   [2\. The server starts in a different folder](#cwd)
-   [3\. Your environment variables aren't there](#env)
-   [4\. Something prints to stdout](#stdout)
-   [5\. The config isn't what you think](#config)
-   [6\. A different Node or Python runs it](#runtime)
-   [Find which one it is](#narrow)
-   [FAQ](#faq)

## 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 directory`

[1\. PATH](#path)

`EROFS: read-only file system`, `ENOENT: no such file or directory, open './data.db'`

[2\. Working folder](#cwd)

"API key is required", `401 Unauthorized`, `KeyError: 'OPENAI_API_KEY'`

[3\. Environment variables](#env)

`Unexpected token` … `is not valid JSON`

[4\. Output on stdout](#stdout)

Nothing at all, or the server isn't listed

[5\. Config](#config)

`ModuleNotFoundError: No module named 'mcp'`, `SyntaxError` in a dependency

[6\. Runtime version](#runtime)

## 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"](/blog/fix-spawn-npx-enoent-mcp) 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](/blog/complete-guide-mcp-config-files-claude-desktop-cursor-lovable) 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](/local-mcp-server-testing) 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](/blog/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](/blog/test-local-mcp-server-stdio-inspector-ai-agents) 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](/blog/mcp-server-troubleshooting-common-errors-fix).

## 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 →](/local-mcp-server-testing)

## 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.


---

_Canonical page: https://mcpplaygroundonline.com/blog/mcp-server-works-in-terminal-not-claude-desktop — MCP Playground (mcpplaygroundonline.com), the free browser-based tool for testing MCP servers and building AI agents._
