Home / AI & GenAI

AI & GenAITroubleshootingIntermediate

MCP server not connecting or tools missing: fixing stdio, Streamable HTTP and auth failures

An MCP server shows as failed, times out or lists no tools. How to test it outside the client, and fix stdio, Streamable HTTP, session and OAuth problems.

Deepak Bhardwaj9 min read

Applies toModel Context Protocol servers and clients (spec 2025-03-26 and later, Streamable HTTP and stdio transports); MCP Inspector; MCP Python SDK 1.x; Claude Code, Claude Desktop and VS Code as example clients

The symptom is usually one of these: the client shows the MCP server as "failed" or "disconnected", the connection hangs until a timeout, the server connects but the tool list is empty, or tools worked yesterday and now every call returns an error. The assistant then answers without the tool, or tells the user it has no access.

The fastest route is to take the AI client out of the picture. Test the server directly, find which layer fails (process start, transport, session, auth or tool listing), then fix that layer.

Quick checks

  1. Which transport is it? stdio (the client starts a local process and talks over stdin/stdout) or Streamable HTTP (the client calls a URL, usually ending in /mcp). The fixes are different.
  2. What does the client log say? Most clients keep a per-server log:
    • Claude Code: run claude mcp list and claude mcp get <name>, use /mcp inside a session, or start with claude --debug.
    • Claude Desktop: mcp*.log files under %APPDATA%\Claude\logs (Windows) or ~/Library/Logs/Claude (macOS).
    • VS Code: the MCP server's entry in the Output panel.
  3. Did anything change? A server upgrade, a new protocol version, an expired token, a proxy or a moved URL explains most "worked yesterday" cases.

Symptom → cause → fix

Symptom Likely cause Fix
stdio server "failed" immediately Command not found: the client does not use your shell's PATH Use the absolute path to node, npx, uvx or python; on native Windows wrap npx in cmd /c
stdio server connects, then drops or shows parse errors Server writes logs or banners to stdout Send all logging to stderr; stdout is for JSON-RPC only
stdio server works in a terminal, not in the client Missing environment variables (API keys, proxy settings) Set them in the client's server config env block
Connection hangs, then times out Slow first start (package download, model load) or server waiting on a network call Pre-install packages; raise the client start timeout (Claude Code reads MCP_TIMEOUT)
HTTP 406 on connect Request Accept header lacks application/json and text/event-stream Fix the client or proxy that rewrites headers
HTTP 400 after initialize Missing or wrong Mcp-Session-Id header Make sure proxies pass the header through in both directions
HTTP 404 on calls that used to work Session expired or server restarted; or wrong URL path Client must re-initialize; check the path (/mcp vs /mcp/)
HTTP 401 Token missing, expired or for the wrong audience Check the WWW-Authenticate header and protected resource metadata
HTTP 403 Origin check, IP allow list or missing scope Check server-side Origin validation and token scopes
Connected, but zero tools Server failed to register tools, or tools are filtered by role Call tools/list directly; read server startup logs
Responses arrive late or all at once Proxy buffering SSE responses Disable response buffering for the MCP route

Test the server without the AI client

MCP Inspector

The MCP Inspector is the reference test client. It can start a stdio server or connect to a URL.

# Interactive UI for a stdio server
npx @modelcontextprotocol/inspector uvx my-ops-mcp-server

# CLI mode: list tools and exit (useful over SSH)
npx @modelcontextprotocol/inspector --cli uvx my-ops-mcp-server --method tools/list

If the Inspector lists tools and the client does not, the problem is client configuration (path, env, headers). If the Inspector fails too, the problem is the server or the network.

Raw HTTP with curl

For a Streamable HTTP server, walk the handshake by hand. Each step tells you which layer is broken.

URL=https://mcp.internal.example.com/mcp
TOKEN=...   # if the server needs one

# 1. Initialize. Note the Mcp-Session-Id response header.
curl -sS -i -X POST "$URL" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.1"}}}'

A healthy server returns 200 with either JSON or an SSE stream (Content-Type: text/event-stream) containing the result, including protocolVersion and serverInfo. Use a protocol version your server supports; the server answers with the version it will use.

SID=<value of Mcp-Session-Id from step 1>

# 2. Tell the server initialization is done
curl -sS -i -X POST "$URL" \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" -H "Mcp-Session-Id: $SID" -H "MCP-Protocol-Version: 2025-06-18" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

# 3. List tools
curl -sS -X POST "$URL" \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" -H "Mcp-Session-Id: $SID" -H "MCP-Protocol-Version: 2025-06-18" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

Step 2 should return 202 Accepted. Not every server issues a session ID; if none comes back, leave the header out.

A small Python check for scripts and monitoring

import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

URL = "https://mcp.internal.example.com/mcp"
HEADERS = {"Authorization": "Bearer <token>"}

async def main():
    async with streamablehttp_client(URL, headers=HEADERS) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print(sorted(t.name for t in tools.tools))

asyncio.run(main())

Run this from the same network as the client. If it works from your laptop but not from the build agent or the user's machine, look at DNS, proxies and firewalls rather than the server.

Fix stdio servers

Path and environment

GUI clients and IDEs often start with a minimal environment. Give them absolute paths and explicit variables:

{
  "mcpServers": {
    "ops-tools": {
      "command": "C:\\Program Files\\nodejs\\npx.cmd",
      "args": ["-y", "@example/ops-mcp-server"],
      "env": { "OPS_API_URL": "https://ops.internal.example.com", "HTTPS_PROXY": "http://proxy:8080" }
    }
  }
}

The top-level key differs by client (mcpServers in Claude Desktop and project .mcp.json files, servers in VS Code's mcp.json). With Claude Code you can add the same server from the command line:

claude mcp add ops-tools --env OPS_API_URL=https://ops.internal.example.com -- npx -y @example/ops-mcp-server

Nothing on stdout except protocol messages

One print() or startup banner on stdout corrupts the JSON-RPC stream. In a Python server, log to stderr:

import logging, sys
logging.basicConfig(stream=sys.stderr, level=logging.INFO)

In Node, use console.error rather than console.log. Run the server by hand and check that it prints nothing to stdout until it receives input.

Fix Streamable HTTP servers

Proxies, gateways and load balancers

Most HTTP failures come from the layer in front of the server:

  • Headers stripped: the proxy must forward Mcp-Session-Id, MCP-Protocol-Version, Authorization and Accept, and return Mcp-Session-Id to the client.
  • Buffering: turn off response buffering for SSE. In nginx, proxy_buffering off; on the MCP location, and a proxy_read_timeout longer than your slowest tool call.
  • Sticky sessions: if the server keeps session state in memory and runs several replicas, requests for one session must reach the same replica. Use session affinity or a shared session store.
  • Idle timeouts: load balancer idle timeouts shorter than a long tool call cut the stream. Raise them or make the tool return faster.

Warning

Do not "fix" a 403 by turning off the server's Origin check or binding a local server to 0.0.0.0. The Origin check protects local servers from DNS rebinding attacks. Add the client's origin to the allow list instead, and keep local servers on 127.0.0.1.

Authentication

A 401 from an MCP server that uses OAuth should carry a WWW-Authenticate header pointing to its protected resource metadata. Check what the client is supposed to discover:

curl -sS -i -X POST "$URL" -H "Content-Type: application/json" -d '{}' | grep -i www-authenticate
curl -sS https://mcp.internal.example.com/.well-known/oauth-protected-resource

The metadata lists the authorization server. Typical causes of repeated 401s: the token was issued for a different audience (resource), the user's refresh token expired, or the client cached an old token. Clear the client's stored credentials for that server (for example, re-authenticate through /mcp in Claude Code) and try again. For servers using static bearer tokens or API keys, check expiry and whether the header name matches what the server expects.

Caution

Never paste long-lived tokens into shared project config files that are checked into Git. Use the client's environment variable or secret support, and rotate any token that has been committed.

Tools listed but calls fail

If tools/list works but calls fail, call the tool directly from the Inspector with the same arguments the model used (copy them from the client log). Errors here are ordinary application errors: downstream API down, permissions on the service account, input validation. A tool result with isError: true means the server ran and reported a failure; a JSON-RPC error means the request itself was rejected (unknown tool, bad parameters).

Prevent it next time

  • Run the Python check above as a synthetic probe every few minutes against each shared MCP server and alert when initialize or tools/list fails, or when the tool list changes unexpectedly.
  • Pin server package versions in client config (@example/ops-mcp-server@1.4.2) instead of always pulling the latest.
  • Log to stderr in every stdio server you build, and add a startup test that fails if anything is written to stdout.
  • Document the proxy settings for MCP routes (header pass-through, no buffering, timeouts, affinity) next to the server's deployment code.
  • Track token expiry for service credentials used by MCP servers and rotate them before they expire.