Project-scoped stdio MCP server with a path-based `command` shows "Connected" via `claude mcp list`/`get` but its tools never appear in the interactive session
Title
Project-scoped stdio MCP server with a path-based command shows "Connected" via claude mcp list/get but its tools never appear in the interactive session
Body
Description
A project-scoped stdio MCP server defined in .mcp.json is reported as connected by claude mcp list / claude mcp get <name>, and responds correctly to raw JSON-RPC (initialize + tools/list) when the server binary is invoked directly (bypassing the claude CLI entirely). However, its tools never appear in the interactive session — not found via the tool-search mechanism, and ListMcpResourcesTool reports the server as entirely absent from the list of available servers.
A second stdio server in the same .mcp.json, configured with a bare PATH-resolved command name (no path separators), works correctly and its tools are visible/callable as expected.
.mcp.json (reproduction)
{
"mcpServers": {
"broken-server": {
"type": "stdio",
"command": "apps/my_app/.venv/bin/python",
"args": ["apps/my_app/src/mcp_server.py"]
},
"working-server": {
"type": "stdio",
"command": "some-globally-installed-mcp-binary",
"args": []
}
}
}
broken-server is a Python server built with the official mcp SDK's FastMCP (from mcp.server.fastmcp import FastMCP), version 1.28.1, exposing 4 simple tools (string in / string out).
What I tried (all reproduced across multiple full claude process restarts, not just new conversations)
- Relative path in
command/args(as above) — tools never visible. - Absolute path (hardcoded, e.g.
/Users/me/repo/apps/my_app/.venv/bin/python) — tools never visible. ${CLAUDE_PROJECT_DIR:-.}expansion (per the documented pattern for portable absolute paths in project.mcp.json, https://code.claude.com/docs/en/mcp.md) — tools never visible.- Removed FastMCP's auto-generated
outputSchema(structured tool output) from all 4 tools via@mcp.tool(structured_output=False), on the theory that a JSON Schema shape difference vs. the working server's tools (which have nooutputSchema) might be silently rejected — no change. - Confirmed server-side correctness independently: spawned the server directly via Python
subprocess, spoke raw JSON-RPC (initialize→notifications/initialized→tools/list) over stdio, and got a clean, correctly-shaped response listing all 4 tools with valid schemas. stderr only contained normal INFO-level logging, no errors. - Confirmed
~/.claude.json's project entry hasenableAllProjectMcpServers: trueand.claude/settings.local.jsonhasenabledMcpjsonServers: ["broken-server"]— not in a pending-approval state (claude mcp getnever showed⏸ Pending approval). - As a final isolation test, wrapped the venv Python invocation in a small shell script and placed it on
PATHunder a bare name (matching exactly howworking-serveris configured) — this is the one variable I have not yet confirmed the outcome of at time of filing (in progress).
Key diagnostic finding
claude mcp list and claude mcp get <name> (invoked as short-lived subprocesses, e.g. via the Bash tool from within a session) consistently report the broken server as ✔ Connected with the correct command/args — this appears to be an independent, fresh connection check that does not reflect whether the actual long-running interactive host process has incorporated the server's tools into its own live toolset. This makes claude mcp list/get an unreliable signal for diagnosing this specific class of bug.
Environment
claude --version: v2.1.220 (also reproduced on v2.1.218)- OS: macOS
- Both servers are
"type": "stdio" - Interface used: VS Code native extension (Claude Code) and standalone terminal CLI — reproduced identically on both
Expected behavior
A stdio MCP server configured with a path-based command (relative, absolute, or ${CLAUDE_PROJECT_DIR}-expanded) should expose its tools in the interactive session exactly as a PATH-resolved bare-command server does, given that claude mcp list/get reports it as connected.
Additional notes
Happy to provide the exact mcp_server.py source, full raw JSON-RPC transcripts (initialize/tools/list responses for both the broken and working server, captured via direct subprocess testing), and the exact .claude.json/settings.local.json state if useful for reproduction. Redacted the actual project/business details above since this is a work repository, but can provide a minimal fully-reproducible example (a toy FastMCP server + toy .mcp.json) on request.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗