[BUG] MCP server that connects but fails tools/list: the error reaches /mcp and claude mcp list, never the model
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
When an MCP server completes initialize but answers tools/list with a JSON-RPC error, Claude Code knows exactly what went wrong, and the model is never told.
claude mcp listand/mcpshow the server as! Connected · tools fetch failed — <the server's error message>(thanks to the fixes in 2.1.132 and 2.1.181).- The model gets zero tools from that server and no status, no error, no message. Asked what tools it has from the server and why there are none, it answers that nothing in the session explains it, and guesses between "not configured", "failed to start", "connected with no tools" and "filtered by settings".
The docs section "How Claude learns that a server failed" (https://code.claude.com/docs/en/mcp) covers servers that fail to connect. A server that connects and then fails tools/list falls through that gap: the session init event even reports it as "status": "connected" with no error field.
This matters because a server can refuse its tool list on purpose. For example, an administrator has switched off tool access for this user or account, and the server says so with a JSON-RPC error such as code -32000, message access_disabled: … and a machine-readable data.code. The user asks their assistant why the tools are missing, and the assistant cannot say, although Claude Code is holding the answer.
What Should Happen?
The model should be told that the server connected but its tool list failed, with the server's error message (and ideally data), the same way it is already told about servers that fail to connect. Then it can tell the user why the server has no tools, e.g. "refusing-server is connected but refused to list its tools: access_disabled: an administrator has switched off tool access for this account".
Error Messages/Logs
$ claude mcp list
refusing-server: uv run --quiet --script /path/to/refusing_server.py - ! Connected · tools fetch failed — access_disabled: an administrator has switched off tool access for this account
# The server's raw reply to tools/list:
{"jsonrpc":"2.0","id":2,"error":{"code":-32000,"message":"access_disabled: an administrator has switched off tool access for this account","data":{"code":"access_disabled"}}}
# Session init event (claude -p --output-format stream-json --verbose):
"mcp_servers": [{"name": "refusing-server", "status": "connected", "source": "dynamic"}]
Steps to Reproduce
- Save this as
refusing_server.py: a stdio MCP server whosetools/listalways fails. It is a self-contained uv script, so uv installs themcpSDK on first run.
```python
# /// script
# requires-python = ">=3.10"
# dependencies = ["mcp>=1.9,<2"]
# ///
"""Minimal stdio MCP server whose tools/list always fails with a JSON-RPC error."""
import anyio
from mcp.server.lowlevel import Server
from mcp.server.stdio import stdio_server
from mcp.shared.exceptions import McpError
from mcp.types import ErrorData, Tool
server = Server("refusing-server")
@server.list_tools()
async def list_tools() -> list[Tool]:
raise McpError(
ErrorData(
code=-32000,
message="access_disabled: an administrator has switched off tool access for this account",
data={"code": "access_disabled"},
)
)
async def main() -> None:
async with stdio_server() as (read, write):
await server.run(read, write, server.create_initialization_options())
anyio.run(main)
```
- Register it:
claude mcp add refusing-server -- uv run --quiet --script /absolute/path/to/refusing_server.py claude mcp list(or/mcpin a session) shows! Connected · tools fetch failed — access_disabled: …, as expected.- Ask the model. Interactively, or headless with built-in tools removed, so that it cannot run
claude mcp listitself and must answer from what the harness told it:
``shell``
MCP_CONNECTION_NONBLOCKING=false claude -p --tools "" \
"What tools do you have from the refusing-server MCP server? If none, why not? Answer only from what you have been told in this session."
- The answer (Opus, 2.1.289; one sentence about unrelated user configuration elided):
> I have no tools from a refusing-server MCP server.
>
> Why not: nothing in this session explains it. Nothing I've been told mentions a server by that name: no tool definitions, no connection status, no error, and no system message saying it failed to start or was disabled. So I can't tell you whether it:
>
> - isn't configured,
> - failed to connect or start,
> - connected but exposed no tools, or
> - had its tools filtered out by settings or permissions.
>
> […]
>
> To find the cause, run /mcp in Claude Code to see each server's status and any connection error, or claude mcp list from a shell.
The model is sending the user to /mcp for an error message that Claude Code already has.
Notes: MCP_CONNECTION_NONBLOCKING=false makes -p wait for the server, so the session starts with it connected rather than pending. In a multi-turn session asked after the server has finished connecting, the model is just as uninformed: it says the server was listed as connecting at session start and that it has heard nothing since.
Claude Model
Opus
Is this a regression?
No, this never worked
Last Working Version
(blank)
Claude Code Version
2.1.289 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Non-interactive/CI environment (claude -p); /mcp shows the same status interactively.
Additional Information
- Install method: native installer.
- Related, but different:
- #71352 (MCP tool errors don't include the
datafield when surfaced to the model; closed as not planned) is about atools/callerror result. This report is abouttools/list: no tool exists to call, so nothing reaches the model at all. - #23808 (MCP server instructions from the initialize response are not passed to the model; closed as not planned) was reported against Claude Desktop and is about
instructions, not about thetools/listerror. - Changelog 2.1.132 ("MCP servers that connect but fail
tools/list… now retry once and show 'connected · tools fetch failed' in/mcp") and 2.1.181 (claude mcp get/list"now show! Connected · tools fetch failedwith the error detail") put this failure in front of the user. This report asks for the same detail to reach the model. - Suggested shape: whatever system note already tells the model a server failed to connect could also cover "connected, tools/list failed: <message>", including the JSON-RPC
messageand, if possible,data.