anthropics / anthropics/claude-code

[BUG] Severe fixed-cost latency in Claude Desktop's tool-calling layer

Open
#81,258 0 comments 0 reactions 0 assignees View on GitHub
area:desktop bug
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) 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?

I have tried lots of things. I give up.
Claude code has produced this bug report:

# Severe fixed-cost latency in Claude Desktop's tool-calling layer

## Summary

Every tool call observed in a Claude Desktop "Code" tab session carried a **~5-10 second fixed latency floor**, regardless of what the tool actually did — confirmed for MCP tool calls (both bridged via `mcp-remote` and via direct Streamable-HTTP transport) and for a built-in tool (`Glob`) doing zero network I/O. On top of that floor, write operations requiring the model to author content inline show an **additional ~5 ms/byte cost** that scales with content size.

Net effect measured on a real product surface (Cowork): writing a ~14KB text file took **85.40 seconds** end-to-end. The equivalent raw operation against the same backend, bypassing Claude's tool-calling layer, took **under 300 milliseconds**. That is roughly **250-300x overhead**, and it appears unrelated to the specific MCP server, the network, or the user's environment.

## Environment

- Claude Desktop, "Code" tab (Claude Agent SDK-based session)
- A custom internal MCP server ("fs-mcp"): Python + FastMCP, exposing filesystem read/write over Streamable HTTP on a home LAN, no auth, no TLS (trusted local network)
- Default config at the time of testing: bridged via `npx -y mcp-remote --allow-http` (a stdio-to-HTTP proxy process) per `claude_desktop_config.json`
- Notably, fs-mcp itself was purpose-built to *fix* a prior performance problem: per its own module docstring, it replaced an earlier SMB-based file access setup that had proven unreliable ("dropped sessions mid-write, slow whole-file transfers"). The latency documented in this report is unrelated to SMB — it shows up specifically in the MCP tool-calling layer that was introduced to get *away* from that earlier problem, i.e. this is a second, independent performance issue on top of one that had already been solved once.

## Methodology

Every operation was bracketed with `date +%s%3N` (millisecond-resolution epoch time) immediately before and after, executed via the session's own Bash tool, to measure end-to-end wall-clock time as experienced within the session.

## How to reproduce

The MCP-specific numbers below depend on our own NAS/fs-mcp setup, which won't be available to whoever picks this up. The core finding does not — it reproduces with a plain built-in tool and no custom server at all:

1. Open a Claude Desktop "Code" tab (or the standalone `claude` CLI) in any project directory.
2. Ask Claude to time a trivial, zero-I/O built-in tool call: run `date +%s%3N` via Bash, call `Glob` with a simple pattern (e.g. `*.json`), then run `date +%s%3N` again, and report the millisecond difference.
3. Expected-if-healthy: well under a second — it's a local directory pattern match, no network, no disk work of consequence.
4. Observed instead: 8.58-11.87 s, repeatably, in both a long-running conversation and a freshly-opened empty one (tests 7 and 8 below).

To reproduce the MCP-specific part, repeat the same bracketing around a call to any Streamable-HTTP-transport MCP server's tool (self-hosted or otherwise) instead of `Glob`. Expect a similar multi-second floor through Claude, versus well under a second for a raw HTTP request to that same server made directly (bypassing Claude's tool-calling layer) — see tests 3, 4, and 6.

## Data

### 1. Reference data from a separate product surface ("Cowork"), provided by the user before this investigation started

| Nominal size | Actual bytes | Time |
|---|---|---|
| "1KB" | 1,020 | 13.97 s |
| "10KB" | 9,316 | 51.53 s |
| "20KB" | 14,178 | 85.40 s |

Fitting `t = overhead + K × bytes` to these three points: overhead ≈ 8-9 s, K ≈ 5.4 ms/byte.

### 2. `write_file` via `mcp-remote`-bridged fs-mcp, this session

| Actual bytes written | Time |
|---|---|
| 10 | 10.58 s |
| 920 | 44.36 s |
| 1,032 | 17.70 s |
| 1,654 | 18.94 s |
| 4,900 | 21.29 s |
| 6,950 | 20.69 s |

### 3. Control — `list_dir` via the same bridged fs-mcp, pre-whitelisted (zero approval clicks needed)

**6.11 s** — rules out human click-latency as the explanation for this call.

### 4. Control — same MCP server, raw `curl` directly to it (full JSON-RPC handshake by hand, bypassing Claude's tool-calling layer entirely)

| Operation | Time |
|---|---|
| `initialize` handshake | 576 ms |
| `notifications/initialized` | 322 ms |
| `tools/call` → `list_dir` | 503 ms |
| `tools/call` → `write_file` (1,020 bytes, byte-exact) | 260 ms |

### 5. Control — local file generation, no network at all (`head -c` via Bash)

| Size | Time |
|---|---|
| 1,020 B | 138 ms |
| 9,316 B | 140 ms |
| 14,178 B | 134 ms |
| 3 files, total | 879 ms |

### 6. Control — same fs-mcp server, reconfigured with **direct Streamable-HTTP transport** via a project-level `.mcp.json`, completely bypassing `mcp-remote`

| Call | Time |
|---|---|
| `list_dir`, first call (fresh approval needed) | 8.98 s |
| `list_dir`, second call (pre-whitelisted, zero clicks) | 5.16 s |

### 7. Control — built-in tool, zero MCP, zero network (`Glob`), same session, run immediately after test 6

| Call | Time |
|---|---|
| First | 11.87 s |
| Second | 8.58 s |

### 8. Control — brand-new conversation (empty context, freshly opened tab), same `list_dir` call via direct-HTTP fs-mcp

Both the approval-needed first call and the clean second call: **5-6 s each**, self-reported by the user running the same test independently.

## What we ruled out

- **fs-mcp server code itself** — reviewed the full source (Python/FastMCP, ~200 lines). `write_file` is a plain synchronous `Path.write_text`/`open(...).write()` call. No artificial delay, retry logic, locking, or anything else that could account for multi-second cost.
- **Network/NAS latency** — direct `curl` round-trips to the same server, same LAN: 260-576 ms.
- **Transport mechanism** — `mcp-remote` (stdio bridge) vs. direct Streamable HTTP: both showed the same ~5-9 s floor for equivalent, fully-whitelisted calls (tests 3 and 6).
- **Bridge process respawning** — confirmed via OS process inspection (`Get-CimInstance Win32_Process`) that the `mcp-remote` bridge processes are long-lived and were not respawned between calls.
- **Conversation/context length** — a brand-new, empty conversation showed the same ~5-6 s floor (test 8), ruling out "large accumulated context slows down each turn" as the primary explanation.
- **Human approval-click latency** — real and additive on a tool's *first* use in a session, but a clean ~5-10 s floor persists even for fully pre-whitelisted, zero-click calls (tests 3, 6-second-call, 7).

## What remains unexplained

After eliminating all of the above, a **~5-10 second fixed latency floor remains on every tool call — MCP or built-in — inside this Claude Desktop Code-tab session**, with no cause identifiable from the client side. Since a built-in tool with zero I/O (`Glob`) shows the same magnitude of delay as an MCP round-trip to a remote server, the floor appears to live somewhere in Claude Desktop's own tool-call dispatch/round-trip mechanism — something we have no visibility into from outside the client.

Separately, for any write operation where content must be authored inline by the model (required by the MCP tool schema — there is no local-path or binary-copy alternative available), there is an additional ~5 ms/byte cost that scales linearly with content size and dominates total time for anything beyond a few KB. As a related side effect, we also observed that model-authored long, homogeneous content cannot be guaranteed byte-exact — repeated attempts to write an exact 1,020-byte payload by hand produced 1,032-1,654 bytes; only content generated by a separate program (Node.js) and sent via raw HTTP was byte-exact on the first try.

## Impact

A single ~14KB documentation update took 85 seconds end-to-end for an operation that is, at the infrastructure level, sub-300-milliseconds — roughly 250-300x overhead. This makes routine file-editing workflows (updating notes, docs, config — a common Claude use case) disproportionately slow and disruptive, independent of any specific MCP server's design, the user's network, or file size in isolation.

## Suggested follow-up (needs Anthropic-side visibility we don't have)

- Instrument the tool-call dispatch path inside Claude Desktop end-to-end (model decision → client dispatch → local/remote execution → result return) to find where the ~5-10 s is actually spent.
- Check whether this floor is specific to Claude Desktop's "Code" tab, or also present in the standalone `claude` CLI, claude.ai, and Cowork.
- Consider whether large inline-content tool calls (e.g., an MCP `write_file`-style tool) could support a lower-overhead path that doesn't require the full content to pass through model-generated output tokens — the ~5 ms/byte cost is otherwise unavoidable by design.

### What Should Happen?

Thar Claude's tools work with low(er) latency and not spending 85 seconds internally to process/send 14kb of data to a mcp server that takes 0.5 secs when called with curl (exact same service/environ/time

### Error Messages/Logs

```shell

```

### Steps to Reproduce

see report above

### Claude Model

Not sure / Multiple models

### Is this a regression?

No, this never worked

### Last Working Version

_No response_

### Claude Code Version

1.24012.9 (03c61d)

### Platform

Anthropic API

### Operating System

Windows

### Terminal/Shell

PowerShell

### Additional Information

_No response_

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the timed built-in Glob reproduction using date +%s%3N, then compare it with the direct Streamable-HTTP calls and the .mcp.json and claude_desktop_config.json setups described here. Done means locating the Claude tool-dispatch stage responsible for the fixed delay and confirming the result across the listed transports or client entry points.

Written by the indexing model from the issue text.

Assessment

Tech stack
powershell, python
Domain
devtools, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.