If you've wired an MCP server into an agent client with "command": "npx", you've probably met this one. The server runs fine when you launch it by hand. The agent insists the tools don't exist. No error, nothing useful in the log, and maybe it works again tomorrow.
I've been digging through a pile of these reports lately, and I'm fairly sure about the cause: it's usually not your server...... it's the npx layer you launched it with.
What the failure looks like
Three flavors of the same thing, from the threads I read:
- The agent announces that the server's tools aren't available, as if you never configured them.
- One session works, the next is dead, with nothing changed.
- The same server connects fine in the MCP Inspector and fails in the desktop app.
If any of that sounds familiar, the server is probably fine. The spawn is not.
Why npx makes it worse
MCP's stdio transport works by the client launching your server as a subprocess and talking JSON-RPC over stdin/stdout. The spec is blunt about it: "the client launches the MCP server as a subprocess." So startup time is the client's problem, and clients wait with a deadline.
npx -y <package> is not a shortcut to a binary. It boots npm (itself a Node program), resolves the package, consults the npx cache, and only then spawns your server as yet another child process. On a cold cache there's a download in there too. Every step of that happens inside the client's handshake window.
Then the deadline expires. In Copilot CLI's case the handshake budget is reported as a fixed 60,000 ms with no retry. The reporter measured 22 of 76 handshakes blowing past it in a single day (29%). Once a server misses the budget it's marked failed for the entire session, and the agent just says the tools don't exist. That's the silent-no-tools symptom.
The numbers
From the reporter's measurements, same machine, three ways to launch the same server:
| How it was launched | Average |
|---|---|
npx -y @azure-devops/mcp |
15.39s (runs: 27.91 / 8.31 / 9.96) |
global .cmd shim |
3.32s |
direct node .../dist/index.js
|
3.21s |
That's one person, one machine, one day, so read it as a range rather than a law of nature. But the direction matches what I see locally.
My own check, Windows 11, Node 22.23.2, with the package already warm in the npx cache and --offline so nothing was downloaded:
npx --offline -y @playwright/mcp@0.0.81 --version 1.54s / 1.59s / 1.64s
node <npx-cache>/node_modules/@playwright/mcp/cli.js --version 0.44s / 0.44s / 0.48s
With --help, which loads the whole CLI, it was 7.56s versus 1.74s. Booting npm on its own costs about 0.6s on this box; booting node about 0.1s. So even with a warm cache you're paying seconds for nothing...... and a cold cache is a different animal.
Check your own setup first
- Time the command your client actually runs. Compare
time npx -y <your-server> --helpagainsttime node <path-to-entrypoint> --help. - Run the server outside the client.
npx @modelcontextprotocol/inspector, then paste the same command in. Works in the Inspector, fails in the app? You have a spawn problem, not a server problem. - Read the client's MCP logs. Claude Desktop writes to
%APPDATA%\Claude\logs\mcp*.logon Windows and~/Library/Logs/Claude/on macOS. Other clients mostly just say the server failed and stop there. - Ask whether the client retries at all. Most don't. One miss means dead tools for that session.
The fix, in the order I'd trust it
1st, install the server globally and point command at the resolved binary instead of npx. This is the fix the reporter landed on:
npm install -g @playwright/mcp
npm prefix -g # where global packages land
where.exe playwright-mcp # Windows: full path to the shim
command -v playwright-mcp # macOS / Linux
{
"mcpServers": {
"playwright": {
"command": "C:\\Users\\you\\AppData\\Roaming\\npm\\playwright-mcp.cmd"
}
}
}
2nd, skip the shim and hand the entrypoint to node. This was the fastest of the three measured paths:
{
"command": "node",
"args": ["C:/Users/you/AppData/Roaming/npm/node_modules/@playwright/mcp/cli.js"]
}
3rd, if you have to keep npx, at least warm the cache. Run the exact command once by hand (npx -y <server> --help) so the package is fetched, then restart the client. It helps, but you still pay the npm boot on every session.
4th, raise the timeout if your client has one. The Copilot CLI issue body says the budget is hard-coded, while a commenter on that same issue says there's a per-entry timeout in the config. I can't settle that from here, so check your own client's docs and version before trusting either claim.
5th, use a remote transport when the server supports it. An HTTP/SSE server is already running, so there's no per-session spawn to be slow. It's not a coincidence that one vendor's resolution was rewriting the desktop app's MCP client instead of fixing anyone's config.
Windows gotchas that masquerade as the same bug
A raw absolute path in command can kill every cold start in Claude Desktop: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'c:'. Use node plus a file path, or an .exe/.cmd, and remember the config is read only at startup, so quit from the tray rather than closing the window.
Also, "command": "C:\\Program Files\\nodejs\\npx.cmd" gets split at the space by the cmd.exe handoff and tries to run C:\Program. The 8.3 short path (C:\\PROGRA~1\\nodejs\\npx.cmd) sidesteps it.
One more config-shape trap, from a different toolkit: if the client defaults to SSE, a stdio-shaped config (command/args) throws URL is required for SSE transport. Declare the transport explicitly and the error goes away.
When it isn't npx
If the server connects and then dies, or the process exits immediately, spawn timing is irrelevant...... go read the server's stderr. A subprocess that hangs past the budget gets treated exactly like a slow one, so a wedged server and a slow launcher look identical from the outside.
None of this is exotic. It's a handshake deadline most clients keep private plus a launcher that's slower than most of us assume. Ten minutes moving your config off npx and you can stop thinking about it.
I built Smart-MCP-Proxy for the adjacent itch, running a lot of servers without restarting the client every time: https://github.com/MilkyWay008/Smart-MCP-Proxy. Worth a look if you've got a wall of MCP entries. Hope this helps somebody.
Top comments (0)