anthropics / anthropics/claude-code

Desktop-hosted session never restarts a stdio MCP server after it exits, and keeps reporting it as connected

Open
#95,502 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:desktop area:mcp bug platform:macos
Dominant language
TypeScript
Stars
146k
Forks
23.8k
PR merge metrics
PR metrics pending

Description

What happens

In a Claude Code session hosted by the Claude desktop app (Code tab), a local stdio MCP server that exits is never started again. Every later call to its tools returns:

MCP server "<name>" is not connected

The session keeps listing the server as connected with its old tool count, so the desktop app's own reconnect action refuses with "<name> is connected; only a failed server can be reconnected." The tools stay in the tool list and keep failing. The only way back is for the user to open /mcp and reconnect by hand, or restart the session.

The same Claude Code version in command-line mode does the right thing: the next tool call starts the server again.

Environment
  • macOS (Darwin 25.6.0), Apple silicon
  • Claude desktop app, Code tab, bundled Claude Code 2.1.275 (entrypoint: claude-desktop)
  • Server configured at user scope in ~/.claude.json, type: stdio
Steps
  1. Configure the minimal server below as a user-scope stdio MCP server and open a session in the desktop app's Code tab.
  2. Ask Claude to call ping. It answers pong from pid N.
  3. Make the server process exit (call die, or send the process SIGTERM from a terminal).
  4. Ask Claude to call ping again, in the same turn or a later one.
Expected

The server is started again on the next call (this is what command-line mode does), or at least the session reports it as failed so it can be reconnected.

Actual (desktop-hosted session)
  • The tool call returns MCP server "<name>" is not connected, on every later call and in every later turn. I left it for about half an hour across several turns. No new server process is ever spawned.
  • The session's server list still shows it as connected with the same tool count.
  • The reconnect action refuses because the server is "connected".
Command-line mode, same machine, for comparison

claude -p with --strict-mcp-config --mcp-config pointing at the same server, asked to call ping, die, ping, ping:

Version Results
2.1.268 pong from pid 51896, pid 51896 exiting, pong from pid 55601, pong from pid 55601
2.1.275 (the binary bundled with the desktop app, run from a terminal) pong from pid 71580, pid 71580 exiting, pong from pid 73112, pong from pid 73112

So it is not the version. The respawn that works from a terminal does not happen when the desktop app hosts the session.

Two things I have not separated. The desktop observation was with a different local stdio server of mine, stopped by SIGTERM from outside; the command-line runs used the minimal server below, which exits by itself. I have not yet run the minimal server inside a desktop-hosted session, so if the exit path matters, that is the other variable.

Why it matters

Local MCP servers get restarted for ordinary reasons: a crash, an upgrade of the server, a wrapper that re-execs. In a long-running desktop session that silently removes the tool for the rest of the session, and because the status still says connected, neither the user nor the agent is told why.

Minimal server (Node, no dependencies)
// dies.mjs: `ping` answers with the pid, `die` exits the process.
import { createInterface } from 'node:readline';
const send = (m) => process.stdout.write(JSON.stringify(m) + '\n');
const tools = [
  { name: 'ping', description: 'Returns pong and this server process id.', inputSchema: { type: 'object', properties: {} } },
  { name: 'die', description: 'Exits the server process.', inputSchema: { type: 'object', properties: {} } },
];
createInterface({ input: process.stdin }).on('line', (line) => {
  let m; try { m = JSON.parse(line); } catch { return; }
  if (m.method === 'initialize') send({ jsonrpc: '2.0', id: m.id, result: { protocolVersion: m.params?.protocolVersion ?? '2025-06-18', capabilities: { tools: {} }, serverInfo: { name: 'dies', version: '1.0.0' } } });
  else if (m.method === 'tools/list') send({ jsonrpc: '2.0', id: m.id, result: { tools } });
  else if (m.method === 'tools/call' && m.params?.name === 'ping') send({ jsonrpc: '2.0', id: m.id, result: { content: [{ type: 'text', text: `pong from pid ${process.pid}` }] } });
  else if (m.method === 'tools/call' && m.params?.name === 'die') { send({ jsonrpc: '2.0', id: m.id, result: { content: [{ type: 'text', text: `pid ${process.pid} exiting` }] } }); setTimeout(() => process.exit(1), 50); }
  else if (m.id !== undefined) send({ jsonrpc: '2.0', id: m.id, error: { code: -32601, message: 'Method not found' } });
});
{ "mcpServers": { "dies": { "type": "stdio", "command": "node", "args": ["/absolute/path/dies.mjs"] } } }

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the minimal dies.mjs server in a Claude desktop Code-tab session using the shown stdio configuration, then reproduce the die and ping sequence. Compare the desktop-hosted behavior with command-line mode. Done means the server respawns on the next call, or its session status changes to failed so the reconnect action works.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.