Guide

Claude Desktop not working with your MCP server? Five checks that fix it

By Aman Singh · Published · Updated · 10 min read

Claude Desktop is not working with your MCP server: you added it to the config, and nothing happens. No tools, no error, just an assistant that acts as if the server does not exist. The cause is almost always one of five things, and they are quick to rule out if you check them in order.

This guide gives you that order, and a short script that starts every server in your config the way a client does and prints the real error. The script and each failure below were run in October 2026 with Node.js 20 and version 1.32.1 of the official @modelcontextprotocol/sdk. It covers local servers that the app starts itself (stdio). The same ideas apply to Cursor and Claude Code.

Still not connecting after the five checks? Have me debug your MCP server →

Check in this order. A failure at an early stage hides every later one, so fixing step 4 first is wasted effort if step 2 is broken.

Is it the app or your MCP server?

If Claude Desktop stopped working right after you added or edited an MCP server, start by taking the servers out of the picture. Quit the app fully, rename claude_desktop_config.json (for example to claude_desktop_config.backup.json) and open the app again.

  • It works without the config: the problem is your config or one of its servers. Rename the file back and work through the checks below.
  • It still does not open or work: the problem is the app itself, not MCP. Update or reinstall Claude Desktop, then add your config back.

Quit the app fully and restart

Do this first, and again after every config change. Closing the window often leaves the app running in the menu bar or system tray, and the config is only read at startup. Quit from the tray or menu icon, then open it again.

Run a checker that shows the real error

When a server fails inside Claude Desktop you usually get a vague message. The script below reads the same config file, starts each local server with its command, arguments and environment, asks it to list its tools, and prints what the server said on stderr if it fails.

Terminal
mkdir mcp-check && cd mcp-check
npm init -y
npm install @modelcontextprotocol/sdk
# save the script below as check-mcp.mjs, then:
node check-mcp.mjs "/path/to/claude_desktop_config.json"
check-mcp.mjs
// Starts every server in a Claude Desktop style config the way a client does,
// and reports which ones answer. Usage: node check-mcp.mjs path/to/config.json
import { readFileSync } from "node:fs";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const configPath = process.argv[2];
if (!configPath) {
  console.error("Usage: node check-mcp.mjs path/to/claude_desktop_config.json");
  process.exit(2);
}

let text;
try {
  text = readFileSync(configPath, "utf8");
} catch (err) {
  console.error(`Cannot read ${configPath}: ${err.message}`);
  process.exit(2);
}

let config;
try {
  config = JSON.parse(text);
} catch (err) {
  console.error(`Config is not valid JSON: ${err.message}`);
  console.error("On Windows, paths need doubled backslashes or forward slashes.");
  process.exit(1);
}

const TIMEOUT_MS = 10_000;
let failed = 0;

for (const [name, server] of Object.entries(config.mcpServers ?? {})) {
  if (!server.command) {
    console.log(`SKIP  ${name}: no "command" (remote servers are added in the app, not here)`);
    continue;
  }

  const transport = new StdioClientTransport({
    command: server.command,
    args: server.args ?? [],
    env: server.env,
    stderr: "pipe",
  });
  const stderrLines = [];
  transport.stderr?.on("data", (chunk) => stderrLines.push(...String(chunk).split("\n")));

  const client = new Client({ name: "check-mcp", version: "1.0.0" });
  let timer;
  try {
    const timeout = new Promise((_, reject) => {
      timer = setTimeout(() => reject(new Error(`no answer within ${TIMEOUT_MS / 1000}s`)), TIMEOUT_MS);
    });
    await Promise.race([client.connect(transport), timeout]);
    const { tools } = await Promise.race([client.listTools(), timeout]);
    console.log(`OK    ${name}: ${tools.length} tool(s): ${tools.map((t) => t.name).join(", ")}`);
  } catch (err) {
    failed++;
    console.log(`FAIL  ${name}: ${err.message}`);
    const tail = stderrLines.filter(Boolean).slice(-5);
    // macOS/Linux report ENOENT; on Windows the shell prints "not recognized".
    const notFound =
      err.code === "ENOENT" || tail.some((line) => /not recognized|not found|ENOENT/i.test(line));
    if (notFound) {
      console.log(`      "${server.command}" was not found. Use the full path to the program.`);
    }
    if (tail.length) console.log(`      server stderr:\n        ${tail.join("\n        ")}`);
  } finally {
    clearTimeout(timer);
    await client.close().catch(() => {});
  }
}

process.exit(failed ? 1 : 0);

The config file is claude_desktop_config.json: on macOS under ~/Library/Application Support/Claude/, on Windows under %APPDATA%\Claude\. Cursor reads .cursor/mcp.json in the same format. Here is the output against a test config with one working server and three broken ones (run on Windows, trimmed):

Output
OK    orders: 3 tool(s): get_order, list_orders, cancel_order
FAIL  missing-command: MCP error -32000: Connection closed
      "npx-does-not-exist" was not found. Use the full path to the program.
      server stderr:
        'npx-does-not-exist' is not recognized as an internal or external command,
        operable program or batch file.
OK    noisy: 1 tool(s): ping
FAIL  crashes: MCP error -32000: Connection closed
      server stderr:
        Error: ORDERS_API_KEY is not set
SKIP  remote-one: no "command" (remote servers are added in the app, not here)

Each failure points at a different check below. The apps also keep their own logs: Claude Desktop writes one per server, typically ~/Library/Logs/Claude/mcp-server-NAME.log on macOS and under %APPDATA%\Claude\logs\ on Windows. They contain the same stderr text.

MCP error -32000: Connection closed

This is the error most people search for, and on its own it says very little. In the official SDK, -32000 is the code for ConnectionClosed: the server's process ended, or closed its output, before it answered. It is a symptom. The cause is whatever the server wrote to stderr just before it exited.

I reproduced it with the SDK's own client (version 1.32.1, Node.js 26.12.1, Windows 11, 24 October 2026). Each row is a separate server setup:

Server setupError the client gotWhat stderr saidFix
Exits on start: an env var is missing-32000 Connection closedMissing API_KEYCheck 3: set it in the env block
Command does not exist-32000 Connection closed'...' is not recognized as an internal or external commandCheck 2: use an absolute path
Starts, prints a banner, never answers-32001 Request timed out, after 60 sNothingCheck 4: make sure it speaks MCP on stdio
npx -y @modelcontextprotocol/server-everything, with or without cmd /cNone: connected, 16 toolsNothingNone needed
  • -32000 means the process died. Run the command by hand or use the checker above to read its last words: usually a missing environment variable, a wrong path or a command that was not found.
  • -32001 means the process is alive but not talking MCP. Typical causes are a script path that starts something else, a server that runs in HTTP mode unless told to use stdio, or a first-run npx download that outlasts the timeout.

Check 1: the config file

If the file is not valid JSON, the app may ignore every server in it without a word. The most common cause on Windows is a path with single backslashes, because \U and \o are not valid escapes:

Looks fine, is not valid JSON
{ "mcpServers": { "orders": { "command": "node",
    "args": ["C:\Users\me\orders\dist\server.js"] } } }
What the checker prints
Config is not valid JSON: Bad escaped character in JSON at position 62
On Windows, paths need doubled backslashes or forward slashes.

Either of these forms is valid:

Valid paths
"args": ["C:/Users/me/orders/dist/server.js"]
"args": ["C:\\Users\\me\\orders\\dist\\server.js"]

Trailing commas are the other usual culprit. Paste the file into a JSON validator if the checker reports a position you cannot find.

Check 2: the command

The app starts your server by running the command you gave it. It does this without your shell profile, so a command that works in your terminal can be missing for the app. This is the classic spawn ... ENOENT error. It is especially common when Node comes from a version manager such as nvm or asdf, because the version your terminal picks is not on the app's PATH (a write-up of the asdf case shows it).

The fix is to give the full path to Node and to your script. Find the path with:

Terminal
which node        # macOS / Linux
where node        # Windows
claude_desktop_config.json
{
  "mcpServers": {
    "orders": {
      "command": "/usr/local/bin/node",
      "args": ["/Users/me/orders/dist/server.js"],
      "env": { "ORDERS_API_KEY": "your-key-here" }
    }
  }
}

On Windows, many guides tell you to wrap npx as "command": "cmd" with "args": ["/c", "npx", ...]. In my test the SDK client connected either way, because it starts servers through cross-spawn, which finds npx.cmd. If your client still reports spawn npx ENOENT, the cmd /c wrapper is the fix. If you built the server yourself, pointing node at the compiled file avoids npx altogether.

Check 3: the process stays up

If the command starts but the process exits straight away, the client reports "Connection closed". In the test above, a server that needs ORDERS_API_KEY and does not find it exits immediately, and the only clue is the line it wrote to stderr. Do not count on your shell's environment being there: set required variables in the config's env block.

You can see the same thing by hand. Run the exact command from a terminal. A healthy stdio server prints nothing and waits for input, which is normal; press Ctrl+C to stop it. A crashing one prints its error and returns to the prompt.

Terminal
ORDERS_API_KEY=your-key node /Users/me/orders/dist/server.js

Check 4: the handshake

After starting, the client and server exchange messages over the process's standard input and output. Two things go wrong here.

  • Output on stdout. In stdio mode, stdout carries protocol messages only. My test server printed a one-line banner with console.log, and the SDK client tolerated it (the noisy line above says OK). That does not make it safe: other clients may reject it, and a message that looks like JSON could corrupt the exchange. Log with console.error.
  • A slow start. A server launched through npx -y some-package may download the package on first run, which can outlast the client's patience. Install the package once, or run a built file with node.

Check 5: the tools

If the checker lists your tools but the assistant never uses them, the server is fine and the problem is selection. Make each tool description say what it does and when to use it, keep the tool list short, and ask the assistant a question that clearly needs the tool. The build guide covers how to write them, and the PostgreSQL MCP server guide shows a schema-discovery tool pattern that helps the assistant pick the right tool first time.

Remote servers are different

Everything above is for local servers started by the app. A server that lives at a URL is not started by the app and is not set up in the JSON file. In Claude Desktop, remote servers are added through the app's settings (Settings, then Connectors); an issue on the Webflow MCP server shows people hitting exactly this. The checker above skips entries with no command for that reason.

Symptom cheat sheet

What you seeLikely checkTry this
No servers at all, no error1 ConfigValidate the JSON; fix backslashes and trailing commas
spawn ... ENOENT, or command not recognized2 CommandUse absolute paths to node and your script
MCP error -32000: Connection closed3 ProcessRun the command by hand; set env vars in the config
MCP error -32001: Request timed out4 HandshakeCheck it speaks MCP on stdio; avoid downloading at startup
Connected but never used5 ToolsRewrite descriptions; trim the tool list
Remote URL does nothingRemoteAdd it in the app's connector settings

Common questions

Why is Claude Desktop not working after I added an MCP server?+
Usually the config file or one server in it is broken. Rename claude_desktop_config.json and restart: if the app works without it, fix the config with the five checks in this guide. If it still fails, the problem is the app, so update or reinstall it.
What does MCP error -32000: Connection closed mean?+
The server's process exited, or closed its output, before it answered the client. The error itself is generic: run the server's command by hand or with the checker in this guide to see what it printed to stderr, which is usually a missing environment variable, a wrong path or a command that was not found.
Why does my MCP server work in the terminal but not in Claude Desktop?+
The app starts it without your shell profile, so PATH and environment variables differ. Use absolute paths and set required variables in the config's env block.
Where does Claude Desktop keep MCP logs?+
It writes a log per server, typically under ~/Library/Logs/Claude/ on macOS and %APPDATA%\Claude\logs\ on Windows. They contain the server's stderr output.
Can I print debug output from my MCP server?+
Yes, to stderr (console.error). Keep stdout for protocol messages.
Does the same fix apply to Cursor and Claude Code?+
The causes are the same: bad config, command not found, a crashing process, or a slow start. Only the config location differs.

Want it working and hardened?

If you would rather hand this over, I build and debug MCP servers for existing APIs: see custom MCP server development, or bug fixing for Node.js and Python apps if the problem is elsewhere in your stack.

Next steps

Keep reading

Need a hand with this?

Tell me what you are working on. Free scoping call, fixed quote within 48 hours.

Book a call

← All guides