mcp-use-hang-repro
by cswkim
README.md
# Repro: widget tool result is dropped when the handler loses a race with the iframe load
In `mcp-use dev`, a `tools/call` that renders a widget can hang in the inspector: the server
finishes the call, but the **result never reaches the client**, which spins until its request
timeout (`-32001`). Whether it hangs depends on how long the tool handler takes — it's a
**probabilistic race**, not a fixed timeout.
Measured on the stock scaffold by sweeping the handler delay (`DELAY_MS`):
| `DELAY_MS` | worked |
|-----------:|--------|
| 0 | always |
| 100 | always |
| 150 | ~6 / 8 |
| 200 | ~50% |
| 250 | ~2 / 6 |
| 300 | never |
| 1000 | never |
| 2000 | never |
The probability of hanging climbs smoothly from ~150ms to ~300ms — a transition band, not a
cliff. Reading: the tool result is racing the **widget iframe finishing its load/connect**
(itself a variable ~150–300ms). If the handler returns before the iframe is done, the result
is delivered; if it returns after, the result is dropped.
This is why it looks **intermittent** in a real server: a handler that hits an API/DB has
jittery latency that lands in the transition band, so it hangs only some fraction of the time.
- Only in **`mcp-use dev`**. A production build / deployed connector does **not** hang.
- This project is the **unmodified `create-mcp-use-app --template mcp-apps` scaffold**; the
only change is making the stock `search-tools` 2s delay configurable via `DELAY_MS`.
## Environment
- mcp-use: **1.33.0** (also seen on 1.30.0). macOS, Chrome.
## Getting Started
```bash
npm install
npm run dev
```
## Steps
```bash
DELAY_MS=2000 npm run dev # http://localhost:3000, opens the inspector
```
1. Open the inspector, call **`search-tools`** (args can be empty `{}`) → spins forever,
eventually `-32001`. (At 2000ms it hangs every time.)
2. Restart with `DELAY_MS=0 npm run dev`, call again → delivers instantly, every time.
3. To see the race, sweep `DELAY_MS=150 / 200 / 250` (restart each time) and call the tool
several times — it hangs a growing fraction of the time as the delay rises.
On hanging runs the widget iframe renders its skeleton (it received the tool *input*) but
never the *output* (props).
## Negative controls — Node clients do NOT reproduce it (so it's browser-specific)
These are MCP *clients*, so the dev server must already be running in another terminal.
`DELAY_MS` is a **server** setting (it controls the handler) — set it on `npm run dev`, not
on the script. Two terminals:
```bash
# terminal 1 — start the server at the hang-inducing delay, leave it running
DELAY_MS=2000 npm run dev
# terminal 2 — run a client against it
node repro.mjs # SDK client: pre-read widget, then tools/call + resources/read CONCURRENTLY
node repro-flood.mjs # SDK client: tools/call + a burst of widget asset fetches, concurrently
```
(If you see `ECONNREFUSED ::1:3000`, the dev server isn't running — start terminal 1 first.)
Both deliver the tool result fine even at `DELAY_MS=2000` (the browser hangs every time at
that delay). A headless `mcp-use client screenshot --tool search-tools` also works. These
never load the widget iframe in a browser, so there's nothing for the result to race —
consistent with the race reading above.
## Why (unconfirmed)
Browser-only + dev-only, probabilistic in a ~150–300ms band → looks like the result racing
something that completes when the widget iframe loads/connects, rather than a fixed timer or
plain connection contention. In `mcp-use dev` the iframe's assets are served from the **same
origin** (`localhost:3000`) by Vite; a deployed build serves prebuilt assets from a **separate
CDN origin** and doesn't hang. Exact mechanism unconfirmed.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues