[BUG] Claude Desktop 1.3109.0 — Silent tools/call hang on connector-key collision with overlapping tool names

Resolved 💬 3 comments Opened Apr 18, 2026 by ainatechnology Closed May 26, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Note to triager: This is a Claude Desktop bug, filed here because there is no dedicated public issue tracker for Claude Desktop. Prior related reports have been closed as invalid on that basis ([#43642](https://github.com/anthropics/claude-code/issues/43642)); please consider this report on its merits given the specific, narrow, reproducible trigger (connector-key identity across stdio and Remote Connector with overlapping tool names).
Suggested labels: area:desktop, area:mcp, platform:macos, bug, has repro.

<!-- obsidian --><h2 data-heading="Environment">Environment</h2>

Item | Value
-- | --
Claude Desktop version | 1.3109.0 (35cbf6), Build 2026-04-16T20:32:01Z
macOS | Apple Silicon, Sequoia 15.7.4 (24G517)
Hardware | Mac Mini M4 Pro
MCP transports involved | stdio (FastMCP) + Remote Streamable HTTP (Custom Connector added via claude.ai)
Remote endpoint | https://snap.aina-mcp.org/mcp (Cloudflare Tunnel)

<h2 data-heading="Summary">Summary</h2>
<p>When a local stdio MCP server registered in <code>claude_desktop_config.json</code> shares its <strong>config key</strong> with a Remote Connector registered on claude.ai, <em>and</em> the two expose tool names that collide, Claude Desktop silently hangs on every <code>tools/call</code> to the collided tool. The local server never receives the call. The Remote server receives and answers it normally (HTTP 200), but the response is dropped inside Claude Desktop and never reaches the chat UI. There is no error, warning, or log entry indicating failure.</p>
<p>With <strong>disjoint</strong> tool sets under the same connector key, merge works correctly (verified separately — see "Prior work on this class of bug"). With <strong>identical</strong> tool names, it hangs. This is a partial regression of the stdio <code>tools/call</code>-dispatch bug that was observed in earlier Claude Desktop builds: the disjoint case is now fixed, the collision case is not.</p>

What Should Happen?

Expected behavior

Any deterministic outcome. Reasonable options:

  1. local wins dedup semantics, matching Claude Code's behavior.
  2. Keep the pre-merge namespace separation (i.e., do not merge; dispatch via FQN).
  3. Visible startup warning or runtime error surfaced to the user.

Silent hang is the only option that is clearly wrong.

Actual behavior

tools/call never returns. The UI shows the tool-call spinner indefinitely. No error message, no timeout, no log entry in main.log or mcp.log. User must manually abort the turn.

Error Messages/Logs

Steps to Reproduce

<!-- obsidian --><h2 data-heading="Reproduction">Reproduction</h2>
<p>Three-phase test isolates the namespace key and the collision condition.</p>
<h3 data-heading="Setup">Setup</h3>
<p>A minimal local stdio MCP server exposing a single tool <code>browser_status</code>, whose FastMCP server name is controlled via CLI flag:</p>
<pre><code class="language-python"># test_server.py
import sys, datetime
from fastmcp import FastMCP

fastmcp_name = "aina-snap-bridge" if "--mode=merge" in sys.argv else "aina-snap-bridge-stub"
mcp = FastMCP(fastmcp_name)

@mcp.tool
def browser_status() -> str:
return f"LOCAL_STUB[{fastmcp_name}] ts={datetime.datetime.now().isoformat()}"

if __name__ == "__main__":
mcp.run()
</code></pre>
<p>Plus: a Custom Connector named <strong><code>aina-snap-bridge</code></strong> registered on claude.ai pointing at an unrelated Remote HTTP MCP server that also exposes a <code>browser_status</code> tool with a different description.</p>
<h3 data-heading="Three phases">Three phases</h3>
<p><code>claude_desktop_config.json</code>:</p>
<pre><code class="language-json">{
"mcpServers": {
"&#x3C;CONFIG_KEY>": {
"command": "/path/to/python",
"args": ["-u", "/path/to/test_server.py", "&#x3C;MODE_FLAG>"]
}
}
}
</code></pre>

Phase | CONFIG_KEY | MODE_FLAG | FastMCP server name | Result
-- | -- | -- | -- | --
V1 | aina-snap-bridge-stub | --mode=split | aina-snap-bridge-stub | Two separate namespaces. Baseline.
V2 | aina-snap-bridge-stub | --mode=merge | aina-snap-bridge | Still two separate namespaces.
V3 | aina-snap-bridge | --mode=merge | aina-snap-bridge | Namespaces merge. tools/call hangs.

<p>Restart Claude Desktop between phases. In each phase, inspect tool discovery (via <code>tool_search</code> or the MCP debugger) and attempt to invoke <code>browser_status</code>.</p>
<h3 data-heading="Observed outcomes">Observed outcomes</h3>
<p><strong>V1 (baseline):</strong><br>
Tool discovery lists both <code>aina-snap-bridge-stub:browser_status</code> (stub description) and <code>aina-snap-bridge:browser_status</code> (Remote description). Dispatch via FQN works. Local call returns <code>LOCAL_STUB[aina-snap-bridge-stub] ts=...</code> in ~2 ms; Remote call returns the connector's own response.</p>
<p><strong>V2 (FastMCP name matches Remote, but config key differs):</strong><br>
Tool discovery still shows two separate namespaces. The local namespace prefix is <code>aina-snap-bridge-stub:</code> — i.e., the <strong>config key</strong>, not the FastMCP server name. This confirms that Claude Desktop ignores the <code>serverInfo.name</code> field from the MCP <code>initialize</code> response when computing the namespace.</p>
<p><strong>V3 (config key matches Remote Connector name):</strong><br>
Tool discovery shows <strong>only one</strong> <code>browser_status</code> tool, under <code>aina-snap-bridge:</code>. Total tool count drops by 1. The visible description is the <strong>Remote</strong> variant's description, even though the local server has sent a distinct description in its <code>tools/list</code> response (confirmed via the local stdio log).</p>
<p>Calling that tool: <strong>indefinite hang.</strong> Aborted manually after ~30 s.</p>
<h2 data-heading="Evidence (V3)">Evidence (V3)</h2>
<p><strong>Local stdio server log</strong> (<code>~/Library/Logs/Claude/mcp-server-aina-snap-bridge.log</code>):</p>
<ul>
<li><code>initialize</code> received and responded.</li>
<li><code>tools/list</code> received and responded with the stub tool description.</li>
<li><strong>No <code>tools/call</code> entry ever appears.</strong> The call never reaches the local process.</li>
</ul>
<p><strong>Remote server log:</strong></p>
<ul>
<li><code>Processing request of type CallToolRequest</code> received from Claude.ai's IP.</li>
<li>HTTP 200 returned with a well-formed JSON-RPC response.</li>
</ul>
<p><strong>Claude Desktop <code>main.log</code> / <code>mcp.log</code>:</strong></p>
<ul>
<li>No errors, warnings, or transport closures during the hanging call.</li>
<li>No diagnostic of any kind.</li>
</ul>
<p>So: the Remote did answer. The local was never asked. The response went missing inside Claude Desktop.</p>

Claude Model

Opus

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.88 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Warp

Additional Information

Key observations

  1. Namespace key for stdio servers is the config key in claude_desktop_config.json. For Remote Connectors it is the connector name configured on claude.ai. The MCP serverInfo.name field is ignored for namespacing.
  2. Namespace merge happens iff these two keys are identical. Different keys → separate namespaces (safe, works fine).
  3. On merge, tool metadata is taken from the Remote variant in discovery, even though both servers send their own. This is separately surprising.
  4. On merge with colliding tool names, tools/call silently hangs. The response path inside Claude Desktop appears to fail to route the Remote's response back to the chat, and the local server is never invoked.
  5. Claude Code, by contrast, applies local wins dedup when an MCP server is registered in both locations. Desktop does not.

Prior work on this class of bug

  • [#36319](https://github.com/anthropics/claude-code/issues/36319) — silent drop of stdio tools/call when serialized payload exceeds ~1 KB. Different trigger but the same silent-failure pattern.
  • [#43642](https://github.com/anthropics/claude-code/issues/43642) — "Claude's response could not be fully generated" on multi-tool-call turns via stdio. Closed as invalid with the reason that it is a Claude Desktop issue rather than a Claude Code issue. Please route this one appropriately.
  • An earlier variant of this exact bug (disjoint tool sets under identical connector keys) was reproducible in pre-1.3109 builds of Claude Desktop. That variant is fixed in 1.3109.0 — verified on 2026-04-18 with a separate two-server test that merges disjoint tool sets cleanly. Only the collision variant remains.

Suggested fix direction (non-normative)

  • Simplest: adopt Claude Code's local wins dedup when config key equals a Remote Connector name.
  • Alternative: emit a user-visible startup warning when merge with name collision is detected, and fall back to FQN-based dispatch for the colliding entries.
  • If merge itself cannot be made fully safe, revert to pre-merge namespace separation under all conditions.

Impact

Blocks a valuable dual-registration pattern for latency-sensitive setups (local stdio as primary, Remote Connector as fallback for cross-device continuity) when both sides expose the same service and tool catalog — which is precisely the realistic case. The workaround is viable but non-obvious: use different config keys on Desktop vs. claude.ai (e.g., myservice-local and myservice) and rely on prompt-level routing to select between them.

Additional info available on request

  • Full test harness (test_stdio_toolname_collision.py) with --mode=split|merge support.
  • Sanitized Claude Desktop log bundle covering all three phases.
  • FastMCP server version: ≥ 1.26.0 with TransportSecuritySettings(enable_dns_rebinding_protection=False) set to avoid 421 responses over the Cloudflare Tunnel (unrelated to this bug).

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗