How to Fix "Could Not Connect to MCP Server mcp-registry"
The error "Could Not Connect to MCP Server mcp-registry" appears in Claude Desktop, Cursor, and other MCP-compatible clients when the host application cannot establish a working connection to the server entry named mcp-registry in your configuration. Despite the specific-sounding name, this is a generic connection failure — mcp-registry is simply whatever key you used in your config file. The root cause could be anything from a typo in a file path to a Docker networking misconfiguration to a TLS certificate that the client refuses to trust.
This guide covers every meaningful root cause in order of how frequently they appear in practice, with concrete commands, configuration examples, and a systematic workflow you can follow to isolate and fix the problem in under 15 minutes.
What the Error Actually Means
When an MCP client shows this error, it has failed at one of three layers:
- Transport layer — the process didn't start, the port isn't listening, or the URL is wrong.
- Protocol layer — the process started but didn't respond with a valid JSON-RPC
initializeresponse. - Authentication layer — the connection was established but the server rejected the client's credentials.
Want to analyze your API security?
Import your OpenAPI spec and generate a Security Report automatically.
The error message itself rarely tells you which layer failed. That's why a structured diagnostic approach — starting at the transport and working up — is far faster than guessing.
Layer 1: Transport Problems
Is This a stdio or HTTP/SSE Server?
MCP supports three transports:
| Transport | How it works | When to use it |
|---|---|---|
| stdio | Client spawns a child process; communicates over stdin/stdout | Local servers, CLI tools |
| Streamable HTTP | Client POSTs JSON-RPC to an HTTP endpoint | Remote servers (MCP spec ≥ 2025-03-26) |
| SSE | Client connects to /sse, server pushes events | Remote servers (older spec, still supported) |
A transport mismatch is one of the most common causes of this error. If your server is an HTTP server but your config specifies command (stdio), the client will try to spawn a process that either doesn't exist or exits immediately.
Stdio config example (Claude Desktop):
{
"mcpServers": {
"mcp-registry": {
"command": "/usr/local/bin/node",
"args": ["/home/user/my-mcp-server/index.js"],
"env": {
"API_KEY": "your-key-here"
}
}
}
}
HTTP/SSE config example (Claude Desktop):
{
"mcpServers": {
"mcp-registry": {
"url": "https://my-mcp-server.example.com/mcp",
"headers": {
"Authorization": "Bearer your-token-here"
}
}
}
}
Key rule: If the server is a long-running HTTP process, use
url. If it's a script or binary the client should launch, usecommand+args. Never mix these.
For stdio: Verify the Binary Path
The most common stdio failure is an incorrect or non-executable path in command. Claude Desktop does not inherit your shell's PATH, so node, python, uvx, or npx will silently fail unless you use the absolute path.
# Find the absolute path to your runtime
which node # e.g. /usr/local/bin/node
which python3 # e.g. /usr/bin/python3
which uvx # e.g. /home/user/.local/bin/uvx
# Verify the server script is executable
ls -la /path/to/your/server.js
node /path/to/your/server.js --version # Should start without crashing
For Python servers using uv or uvx:
{
"mcpServers": {
"mcp-registry": {
"command": "/home/user/.local/bin/uvx",
"args": ["my-mcp-package"]
}
}
}
For HTTP/SSE: Verify the Server Is Listening
Before touching any config, confirm the server is actually accepting connections:
# Check if anything is listening on the expected port
ss -tlnp | grep 3000 # Linux
lsof -iTCP:3000 -sTCP:LISTEN # macOS
# Test the connection directly
curl -v http://localhost:3000/health
curl -v https://my-mcp-server.example.com/health
# Test the MCP initialize handshake manually
curl -X POST https://my-mcp-server.example.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
A healthy MCP server should return a JSON object with result.protocolVersion and result.serverInfo. If you get a connection refused, 502, or no response, the server is the problem — not the client config.
Layer 2: Configuration Errors
Claude Desktop Configuration
The Claude Desktop config file lives at:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Common mistakes in this file:
// WRONG — relative path, inherits no shell PATH
{
"mcpServers": {
"mcp-registry": {
"command": "node",
"args": ["./server.js"]
}
}
}
// WRONG — mixing url and command
{
"mcpServers": {
"mcp-registry": {
"command": "node",
"url": "http://localhost:3000/mcp"
}
}
}
// CORRECT — absolute path, explicit env vars
{
"mcpServers": {
"mcp-registry": {
"command": "/usr/local/bin/node",
"args": ["/home/user/projects/mcp-registry/server.js"],
"env": {
"NODE_ENV": "production",
"REGISTRY_API_KEY": "sk-...",
"DATABASE_URL": "postgresql://..."
}
}
}
}
After editing the config, you must fully restart Claude Desktop — not just close the chat window. On macOS, Cmd+Q and reopen. The MCP server list is only re-read on application startup.
Validate your JSON before saving:
cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | python3 -m json.tool
A JSON parse error will silently prevent all MCP servers from loading.
Cursor Configuration
Cursor stores MCP server config in .cursor/mcp.json in your project directory or globally at ~/.cursor/mcp.json. The schema is nearly identical to Claude Desktop:
{
"mcpServers": {
"mcp-registry": {
"command": "/usr/local/bin/node",
"args": ["/absolute/path/to/server.js"],
"env": {
"API_KEY": "your-key"
}
}
}
}
Cursor-specific gotchas:
- Cursor respects both project-level and global configs; project-level takes precedence.
- After changing
~/.cursor/mcp.json, go to Cursor Settings → MCP and click Refresh — or restart Cursor entirely. - If the MCP panel shows a server as "disconnected" with no error, check Cursor's developer console: Help → Toggle Developer Tools → Console.
Layer 3: Authentication Failures
Bearer Token Problems
If the server starts and responds but immediately closes the connection, authentication is likely the cause. MCP servers using OAuth 2.0 or static bearer tokens will reject requests with a 401 or close the SSE stream.
# Test auth explicitly
curl -v https://my-mcp-server.example.com/mcp \
-H "Authorization: Bearer WRONG_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
# Look for: HTTP/2 401 or "Unauthorized"
Checklist for token issues:
- Token is copied correctly with no trailing spaces or newlines.
- Token is not expired (check your provider's dashboard).
- The
Authorizationheader is spelled correctly —Authorization, notAuthorisation. - The scheme matches —
Bearer(capital B) for JWT/OAuth tokens. - The token has the correct scopes for MCP tool access.
Environment Variable Expansion
A subtle issue: Claude Desktop does not expand shell variables in config values. This silently fails:
// WRONG — $HOME and ${API_KEY} are NOT expanded
{
"command": "$HOME/.local/bin/uvx",
"env": {
"API_KEY": "${MY_SECRET}"
}
}
You must hardcode absolute values:
// CORRECT
{
"command": "/home/youruser/.local/bin/uvx",
"env": {
"API_KEY": "sk-actual-key-value-here"
}
}
If you need secrets management, consider using a wrapper script that sources your env file and then execs the MCP server:
#!/bin/bash
# /home/user/bin/start-mcp-registry.sh
source /home/user/.secrets/mcp-registry.env
exec /usr/local/bin/node /home/user/mcp-registry/server.js
{
"mcpServers": {
"mcp-registry": {
"command": "/bin/bash",
"args": ["/home/user/bin/start-mcp-registry.sh"]
}
}
}
Layer 4: Network and Firewall Issues
Local Server Firewall (macOS)
macOS's Application Firewall can block a Node.js or Python process from accepting connections even on localhost. Check:
- System Preferences → Security & Privacy → Firewall → Firewall Options
- Look for your server's binary (node, python3, etc.).
- If it shows "Block incoming connections", change it to "Allow".
Alternatively, test from the terminal:
# Start your server manually, then in another terminal:
curl http://127.0.0.1:3000/health
# If this works but Claude Desktop can't connect, the issue is the config, not the server.
Corporate Proxy and VPN
If your MCP server is remote and you're behind a corporate proxy or VPN:
# Test connectivity to the remote server
curl -v --proxy http://proxy.company.com:8080 https://my-mcp-server.example.com/health
# Check if the domain resolves
nslookup my-mcp-server.example.com
dig my-mcp-server.example.com
For Node.js MCP servers that need to reach external APIs through a proxy, set these in the env block:
{
"env": {
"HTTP_PROXY": "http://proxy.company.com:8080",
"HTTPS_PROXY": "http://proxy.company.com:8080",
"NO_PROXY": "localhost,127.0.0.1"
}
}
Docker Networking
Docker introduces a separate networking namespace. This is the most common source of connection errors when running MCP servers in containers.
Problem: Your MCP client is on the host, your server is in a Docker container.
# WRONG — 127.0.0.1 inside Docker refers to the container, not the host
url: "http://127.0.0.1:3000/mcp"
# CORRECT — bind the container port to the host
# docker run -p 3000:3000 my-mcp-server
# Then use:
url: "http://localhost:3000/mcp"
Problem: Two containers need to talk to each other (e.g., MCP server + database).
Use Docker Compose with a named network:
# docker-compose.yml
version: '3.8'
services:
mcp-registry:
build: .
ports:
- "3000:3000"
networks:
- mcp-net
environment:
- DATABASE_URL=postgresql://db:5432/registry
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 15s
db:
image: postgres:16
networks:
- mcp-net
environment:
- POSTGRES_DB=registry
- POSTGRES_PASSWORD=secret
networks:
mcp-net:
driver: bridge
Critical: in Docker Compose, services refer to each other by service name, not localhost:
DATABASE_URL=postgresql://db:5432/registry ✓
DATABASE_URL=postgresql://localhost:5432/registry ✗ (inside container)
Problem: MCP client is inside one container, server is in another.
# Get the container's IP on the shared network
docker network inspect mcp-net
# Or use the service name if both are on the same Compose network
url: "http://mcp-registry:3000/mcp"
Layer 5: TLS/HTTPS Certificate Problems
Diagnosing Certificate Errors
# Check certificate validity
curl -v https://my-mcp-server.example.com/health 2>&1 | grep -E "(SSL|TLS|certificate|expire)"
# Check certificate details
openssl s_client -connect my-mcp-server.example.com:443 -showcerts 2>/dev/null | \
openssl x509 -noout -dates -subject -issuer
# Check if cert is expired
echo | openssl s_client -servername my-mcp-server.example.com \
-connect my-mcp-server.example.com:443 2>/dev/null | \
openssl x509 -noout -checkend 0
# Output: "Certificate will not expire" = valid
Self-Signed Certificate Fix (Development)
Never disable TLS validation in production. For development with self-signed certs:
# macOS — add to system trust store
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain ./my-ca.crt
# Ubuntu/Debian
sudo cp my-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# For Node.js specifically (without system store)
export NODE_EXTRA_CA_CERTS=/path/to/my-ca.crt
Production TLS with Let's Encrypt
The easiest production fix for certificate problems is using Caddy as a reverse proxy — it handles certificate issuance and renewal automatically:
# Caddyfile
my-mcp-server.example.com {
reverse_proxy localhost:3000
header {
# Security headers for MCP endpoints
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
}
}
caddy run --config Caddyfile
Caddy automatically obtains and renews Let's Encrypt certificates — no manual certificate management required.
Layer 6: Server Startup and Timeout Issues
Initialization Timeout
MCP clients wait for the server to complete the JSON-RPC initialize handshake. If your server takes too long to start (database connection pool warming, model loading, external API calls during init), the client gives up.
Default timeouts vary by client:
- Claude Desktop: ~30 seconds
- Cursor: ~10 seconds
- Custom clients: depends on implementation
Fix: Lazy initialization
// WRONG — heavy work blocks initialize response
const server = new McpServer({ name: 'mcp-registry', version: '1.0.0' });
await loadAllDataIntoMemory(); // Takes 20 seconds
await server.connect(transport);
// CORRECT — respond to initialize immediately, load data lazily
const server = new McpServer({ name: 'mcp-registry', version: '1.0.0' });
server.tool('search', async (params) => {
// Initialize connection lazily on first use
if (!dbPool) dbPool = await createPool(process.env.DATABASE_URL);
return await search(dbPool, params);
});
await server.connect(transport); // Returns immediately
Server Crashes on Startup
For stdio servers, if the process crashes before sending the initialize response, you'll see the connection error immediately. Capture stderr to diagnose:
# Run the server manually and watch stderr
/usr/local/bin/node /path/to/server.js 2>server-error.log
cat server-error.log
# Common causes:
# - Missing environment variable: "Error: DATABASE_URL is required"
# - Port already in use: "EADDRINUSE :::3000"
# - Module not found: "Cannot find module 'some-package'"
# - Syntax error: "SyntaxError: Unexpected token"
Enabling Debug Logging
Claude Desktop Debug Logs
- Open Claude Desktop.
- Go to Help → Enable Debug Logging.
- Reproduce the connection error.
- Check logs:
# macOS
tail -f ~/Library/Logs/Claude/mcp-server-mcp-registry.log
tail -f ~/Library/Logs/Claude/mcp.log
# Windows
Get-Content "$env:APPDATA\Claude\logs\mcp-server-mcp-registry.log" -Wait
What to look for:
[ERROR] Failed to spawn process: ENOENT /usr/bin/node → Wrong binary path
[ERROR] Process exited with code 1 → Server crashed; check stderr
[ERROR] Connection timeout after 30000ms → Server too slow to initialize
[ERROR] HTTP 401 Unauthorized → Bad auth token
[ERROR] DEPTH_ZERO_SELF_SIGNED_CERT → TLS certificate not trusted
Adding Logging to Your MCP Server
MCP servers must never write to stdout (for stdio transport) — only the JSON-RPC protocol goes there. Always write logs to stderr or a file:
// Correct logging for stdio MCP servers
const log = (level, message, data) => {
// stderr is safe for stdio transport
process.stderr.write(JSON.stringify({ level, message, data, ts: Date.now() }) + '\n');
};
// Or use the MCP SDK's logging notification
server.server.notification({
method: 'notifications/message',
params: { level: 'debug', logger: 'mcp-registry', data: 'Server initialized' }
});
For HTTP/SSE servers, use a structured logger like pino or winston that writes to stdout (file) rather than to the HTTP response stream.
Health Checks
Every production MCP server should expose a health endpoint. This lets you diagnose connection problems in seconds:
// Express example — health check endpoint
app.get('/health', async (req, res) => {
const checks = {
status: 'ok',
timestamp: new Date().toISOString(),
version: process.env.npm_package_version,
checks: {
database: 'unknown',
external_api: 'unknown'
}
};
try {
await db.query('SELECT 1');
checks.checks.database = 'ok';
} catch (e) {
checks.checks.database = 'error';
checks.status = 'degraded';
}
const statusCode = checks.status === 'ok' ? 200 : 503;
res.status(statusCode).json(checks);
});
# Quick health check from terminal
curl -s http://localhost:3000/health | python3 -m json.tool
# Watch health in real time during startup
watch -n 1 'curl -s http://localhost:3000/health | python3 -m json.tool'
Local vs. Remote MCP Servers
Understanding where your server runs eliminates an entire class of connection errors:
| Scenario | Transport | Config key | Common failure |
|---|---|---|---|
| Script on same machine | stdio | command + args | Wrong binary path, missing env vars |
| HTTP server on same machine | HTTP | url: http://localhost:PORT/mcp | Server not running, wrong port |
| HTTP server on remote host | HTTP | url: https://host/mcp | Firewall, TLS cert, auth token |
| Server in Docker (host client) | HTTP | url: http://localhost:PORT/mcp | Port not published with -p |
| Server in Docker Compose | HTTP | url: http://service-name:PORT/mcp | Wrong network, service name typo |
Switching from Local to Remote
When you move a server from localhost to a remote host, remember to:
- Update the
urlin your config fromhttp://localhost:3000tohttps://your-domain.com. - Add a valid TLS certificate on the remote server.
- Add an
Authorizationheader with a bearer token — never expose MCP servers to the internet without auth. - Check firewall rules: the remote server's port (443 for HTTPS) must be reachable from the client's network.
- Update any CORS configuration if the server enforces origin restrictions.
For production security considerations when running remote MCP servers, see our running MCP in production guide.
Registry Availability and Version Compatibility
If your mcp-registry server connects to the official MCP registry or a private registry for tool/server discovery, the registry itself may be unreachable:
# Test registry connectivity
curl -v https://registry.example.com/v1/servers
# Check DNS
nslookup registry.example.com
# Check if the issue is specific to your network
curl --resolve registry.example.com:443:1.2.3.4 https://registry.example.com/v1/servers
Protocol Version Mismatch
The MCP specification has evolved. Clients and servers must agree on a protocolVersion during the initialize handshake:
// Client sends:
{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26",...}}
// Server must respond with a version it supports:
{"result":{"protocolVersion":"2025-03-26","serverInfo":{...},...}}
If the server responds with an older version (2024-11-05) and the client only accepts 2025-03-26, some clients will reject the connection. Check your server SDK version:
# Node.js MCP SDK version
npm list @modelcontextprotocol/sdk
# Python MCP SDK version
pip show mcp
Update to the latest version if there's a mismatch:
npm update @modelcontextprotocol/sdk
pip install --upgrade mcp
Common Mistakes Quick Reference
| Mistake | Symptom | Fix |
|---|---|---|
Relative binary path in command | "ENOENT" or process not found | Use absolute path from which node |
| Shell env vars not inherited | Missing API key errors | Declare all vars in env block |
$VAR syntax in config | Wrong value or connection failure | Hardcode values; no shell expansion |
| JSON syntax error in config | No MCP servers load at all | Validate with python3 -m json.tool |
Using localhost in Docker | Connection refused | Use Docker service name or published port |
| Self-signed cert not trusted | DEPTH_ZERO_SELF_SIGNED_CERT | Add CA to system trust store |
| HTTP server, but stdio config | Process spawn error | Switch to url config key |
| stdio server, but HTTP config | Timeout waiting for response | Switch to command + args |
| Forgot to restart client | Old config still active | Fully quit and reopen Claude Desktop/Cursor |
| Server writes to stdout (stdio) | Protocol parse error | Move all logging to stderr |
| Heavy init work before response | Initialization timeout | Use lazy loading |
| No auth on public endpoint | Works locally, breaks in prod | Add bearer token authentication |
Systematic Troubleshooting Workflow
Follow this in order. Each step either confirms the layer is working or identifies the problem.
Step 1 — Validate Config File Syntax
# Claude Desktop
python3 -m json.tool ~/Library/Application\ Support/Claude/claude_desktop_config.json
# Cursor
python3 -m json.tool ~/.cursor/mcp.json
Pass: No output, exits 0.
Fail: Fix the JSON error. This blocks all MCP servers.
Step 2 — Identify Transport Type
Look at your config entry for mcp-registry:
- Has
command? → stdio. Go to Step 3a. - Has
url? → HTTP/SSE. Go to Step 3b.
Step 3a — stdio: Test the Binary Manually
ENV_VAR_1=value /absolute/path/to/command /absolute/path/to/server.js
Does it start without errors? If not, fix the binary path or missing dependencies first.
Step 3b — HTTP: Test Server Connectivity
curl -v http://localhost:PORT/health
# or
curl -v https://your-domain.com/health
Pass: 200 response.
Fail: Server is down, wrong port, or network issue. Fix the server or firewall first.
Step 4 — Test Authentication
curl -X POST YOUR_MCP_URL \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
Pass: JSON response with result.protocolVersion.
Fail with 401: Token is wrong or expired.
Fail with TLS error: Certificate problem. Go to TLS section.
Step 5 — Enable Debug Logging
# Enable in Claude Desktop: Help → Enable Debug Logging
tail -f ~/Library/Logs/Claude/mcp-server-mcp-registry.log
Look for the specific error message. Match it against the common errors table above.
Step 6 — Check Environment Variables
For stdio servers, add a temporary debug command to print env:
{
"command": "/bin/sh",
"args": ["-c", "env > /tmp/mcp-env-debug.txt && exec /usr/local/bin/node /path/to/server.js"]
}
Check /tmp/mcp-env-debug.txt to confirm variables are being passed correctly.
Step 7 — Validate MCP Protocol Compliance
Before deploying to production — or when you suspect a protocol-level mismatch — run your server through MCPForge Verify. It performs the full MCP initialization handshake, validates tool schemas, checks capability declarations, and flags issues that manual curl testing won't catch. It's particularly useful for catching transport mismatches and protocol version incompatibilities that only surface under real client conditions.
Also review your server's security posture using MCPForge Security Reports — authentication configuration, exposed sensitive tools, and missing input validation are the three most common production issues that start as "connection errors" but are actually security misconfigurations.
Step 8 — Test in Isolation
# Create a minimal test config
cat > /tmp/test-mcp-config.json << 'EOF'
{
"mcpServers": {
"mcp-registry": {
"command": "/usr/local/bin/node",
"args": ["/path/to/server.js"]
}
}
}
EOF
# Then point Claude Desktop at this config (rename/swap configs temporarily)
# This eliminates interactions between multiple server configs
Step 9 — Check for Port Conflicts
# Linux
ss -tlnp | grep 3000
# macOS
lsof -i :3000
# Kill conflicting process if needed
kill -9 $(lsof -t -i:3000)
Step 10 — Restart Everything in Order
- Stop the MCP server (if HTTP).
- Restart Docker (if using containers).
- Start the MCP server.
- Confirm
/healthreturns 200. - Fully quit Claude Desktop / Cursor.
- Reopen Claude Desktop / Cursor.
- Check connection status.
Production Pre-Deployment Checklist
Before shipping an MCP server to production, verify each item:
- Server responds to
/healthwith 200 in under 500ms. -
initializehandshake completes in under 5 seconds (test withcurl). - All environment variables are documented and set in the deployment environment.
- Bearer token authentication is enabled and tested with an invalid token (should return 401).
- TLS certificate is valid, from a trusted CA, and auto-renews (check expiry with
openssl). - Docker containers use named networks; no raw IP addresses in service configs.
- Firewall rules allow traffic only on the required port (443 for HTTPS).
- Server logs go to stderr (stdio) or a structured log aggregator (HTTP).
- No sensitive data (tokens, passwords) appears in log output.
- Tool input schemas include type validation — no raw
anytypes that accept arbitrary input. - Server has been tested with MCPForge Verify and passes all protocol compliance checks.
- Startup time is under 10 seconds under load (test with multiple concurrent starts).
- Client config uses absolute paths and hardcoded env values (no shell expansion).
- JSON config files pass syntax validation with
python3 -m json.tool.
Key Takeaways
- "Could Not Connect to MCP Server mcp-registry" is a generic failure that happens at the transport, protocol, or auth layer — not always at the server itself.
- The config file is the #1 source of errors: relative paths, missing env vars, JSON syntax errors, and transport mismatches account for the majority of cases.
- stdio servers do not inherit your shell environment — always use absolute paths and explicit
envdeclarations. - Docker networking is a separate namespace — use service names and published ports, never raw
localhostinside containers. - TLS problems are fixable without disabling security — add your CA to the system trust store or use Caddy for automatic Let's Encrypt certificates.
- Heavy server startup work causes timeout errors — use lazy initialization to respond to
initializeimmediately. - Debug logs are your fastest path to the root cause — enable them before spending time guessing.
- Validate with real tools: run
curlagainst your endpoints, usepython3 -m json.toolon config files, and use MCPForge Verify before production deployments.
For comprehensive guidance on running MCP infrastructure reliably at scale, read Running MCP in Production.