Figures on this page were verified 31 August 2026 against the providers' own documentation. Pricing, context windows and rate limits change without notice, so confirm any number against the provider before you rely on it. Tell us if something here is out of date.
When an MCP server will not connect, the cause is nearly always one of four things: the command is not on the client’s PATH, the server writes to stdout, credentials are missing from its environment, or the client is not restarting after a config change. Work through those in order and you will resolve most failures without reading a line of protocol documentation.
What a connection actually involves
The Model Context Protocol lets a client launch a server that exposes tools and resources. For stdio transport the client spawns your command as a subprocess and speaks JSON-RPC over standard input and output. For HTTP transport it makes requests to a URL you supply.
Two consequences follow from the stdio design, and both cause most beginner failures. The client’s environment is not your shell’s environment. And anything your server prints to stdout corrupts the protocol stream, because stdout is the transport.
The four failure modes
| Symptom | Cause | Fix |
|---|---|---|
| Server never appears; no error | Command not found in the client’s PATH | Use an absolute path to the binary |
| Connects, then drops immediately | Server printed to stdout | Send all logging to stderr |
| Connects but every tool call fails | Missing environment variables | Declare them in the server config block |
| Config edited, nothing changed | Client caches config at startup | Fully restart the client |
| Zero tools listed | Handshake incomplete | Initialize, then send the initialized notification |
Never print to stdout
This is the one that wastes the most time, because the server looks healthy when you run it by hand. A single stray print() injects text into the JSON-RPC stream and the client drops the connection with an unhelpful parse error.
import sys, logging
# WRONG: this is the protocol channel
print("server starting")
# RIGHT: stderr is free for humans
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
logging.info("server starting")Watch for indirect writes too: a library that prints a deprecation warning, a progress bar, or a banner on import will break the transport just as effectively as your own code.
Give the server an explicit environment
The client does not inherit your shell profile, so a server that works in your terminal can fail when launched by the client. Declare the command with an absolute path and pass credentials explicitly.
{
"mcpServers": {
"my-server": {
"command": "/usr/local/bin/node",
"args": ["/absolute/path/to/server.js"],
"env": { "API_TOKEN": "..." }
}
}
}Keep real credentials out of a file you might commit. Reference an environment variable your launcher populates, and add the config file to .gitignore. The broader habit is covered in keeping secrets out of prompts and logs.
Test the server before blaming the client
Drive it directly and confirm it answers. For HTTP transport the handshake has three steps, and skipping the third is why a server sometimes reports zero tools despite being configured correctly.
# 1. initialize, and keep the session id from the response headers
# 2. send notifications/initialized
# 3. only then will tools/list return anything
curl -s -X POST "$URL" -H 'Content-Type: application/json'
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"test","version":"1"}}}'An empty tool list after a bare tools/list is not evidence that the server is broken or that its tools are disabled. It usually means the handshake was never completed, and that misreading sends people rewriting a server that was working the whole time.
Frequently asked questions
Why does my MCP server connect and then immediately drop?
Almost always because the server printed to stdout. For stdio transport, stdout is the protocol channel, so a single stray print, a library banner or a progress bar injects text into the JSON-RPC stream and the client drops the connection.
Why does it work in my terminal but not in the client?
The client does not inherit your shell profile. Use an absolute path to the binary rather than relying on PATH, and declare any credentials explicitly in the server's env block.
Why does tools/list return nothing?
The handshake was probably not completed. You must send initialize, keep the session id from the response headers, then send the initialized notification before listing. An empty list is not evidence that the server is broken.



