Guide · Claude Desktop
Configure Claude Desktop to connect to a custom MCP server
Claude Desktop supports the Model Context Protocol natively, letting you connect it to any MCP server you build or host. Connecting takes two steps: editing a JSON config file and restarting Claude Desktop. The trickier parts are choosing the right transport (stdio for local servers, SSE for remote ones), passing secrets through environment variables rather than hardcoding them, and verifying that Claude Desktop actually loaded your server after the restart. This guide covers all of that plus what to monitor once the server is live.
TL;DR
Find your Claude Desktop config file — ~/Library/Application Support/Claude/claude_desktop_config.json on macOS or %APPDATA%\Claude\claude_desktop_config.json on Windows. Add your server to the mcpServers object: use command + args for local stdio servers, or type: "sse" + url for remote SSE servers. Restart Claude Desktop. Open the Claude menu → "MCP Servers" to confirm your server appears with a green dot. If the dot is red or the server is missing, check the MCP log at ~/Library/Logs/Claude/mcp*.log. Once confirmed, add your server's public URL to AliveMCP for ongoing external protocol monitoring.
Config file location
Claude Desktop stores MCP server configuration in a single JSON file. The path differs by OS:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json(usuallyC:\Users\<username>\AppData\Roaming\Claude\claude_desktop_config.json)
If the file doesn't exist, create it. The directory should already exist from the Claude Desktop installation. The file must be valid JSON — a trailing comma or missing brace will silently prevent your server from loading.
// claude_desktop_config.json skeleton
{
"mcpServers": {
"my-mcp-server": {
"command": "node",
"args": ["/path/to/your/dist/index.js"]
}
}
}
The key inside mcpServers ("my-mcp-server" above) is the display name shown in Claude Desktop's server list. It can be any string without spaces — choose something descriptive.
stdio transport: connecting a local server
Stdio transport is the simplest option for servers running on the same machine as Claude Desktop. Claude Desktop launches your server as a subprocess and communicates via stdin/stdout. You don't need to run the server in advance — Claude Desktop starts and stops it automatically.
{
"mcpServers": {
"my-local-server": {
"command": "node",
"args": ["/Users/you/projects/my-mcp-server/dist/index.js"],
"env": {
"NODE_ENV": "production",
"DATABASE_URL": "sqlite:///Users/you/.local/share/my-mcp-server/db.sqlite"
}
}
}
}
The command field is the executable. For TypeScript projects compiled to JavaScript, use node with the compiled output path. For projects using ts-node or tsx, you can use those directly:
{
"mcpServers": {
"my-ts-server": {
"command": "npx",
"args": ["tsx", "/Users/you/projects/my-mcp-server/src/index.ts"]
}
}
}
Use absolute paths — Claude Desktop may not inherit your shell's PATH or ~ expansion. If you see "command not found" errors, specify the full path to the executable (e.g., /usr/local/bin/node rather than node). Find the full path with which node in a terminal.
SSE transport: connecting a remote server
If your MCP server is hosted remotely (deployed to Railway, Render, Fly.io, or any HTTPS endpoint), use SSE transport. Claude Desktop connects to the server's SSE endpoint over HTTPS and maintains a persistent connection.
{
"mcpServers": {
"my-remote-server": {
"type": "sse",
"url": "https://my-mcp-server.railway.app/sse"
}
}
}
The url must point to the SSE endpoint — typically /sse, though this depends on your server's routing. The server must return Content-Type: text/event-stream on this path and handle MCP messages sent to the associated POST endpoint.
For servers that require authentication, add an Authorization header via the headers field (supported in recent Claude Desktop versions):
{
"mcpServers": {
"my-auth-server": {
"type": "sse",
"url": "https://my-mcp-server.example.com/sse",
"headers": {
"Authorization": "Bearer your-api-key-here"
}
}
}
}
Don't commit API keys into a shared config file. On macOS, use launchctl setenv or a tool like direnv to inject secrets as environment variables, then reference them in your server's startup command rather than embedding them in the Claude Desktop config.
Environment variables
The env field in each server entry lets you pass environment variables to the spawned subprocess (stdio transport only — for SSE servers, env vars go into the server process itself, not the client config).
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/path/to/dist/index.js"],
"env": {
"NODE_ENV": "production",
"LOG_LEVEL": "info",
"API_KEY": "your-secret-here",
"DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb"
}
}
}
}
Claude Desktop merges these env vars with the system environment before spawning the subprocess, so your process also inherits standard env vars like HOME and USER. The server-specific env vars take precedence if there's a conflict.
For secret management on macOS, the Keychain is the right store. Retrieve secrets in a wrapper script rather than embedding them in the config file:
#!/bin/bash
# /usr/local/bin/my-mcp-server-wrapper
export API_KEY=$(security find-generic-password -a "$USER" -s "my-mcp-server-api-key" -w)
exec node /path/to/dist/index.js
Then use the wrapper as the command in your Claude Desktop config.
Verifying the connection
After saving the config file, fully quit Claude Desktop (Cmd+Q on Mac, not just closing the window) and relaunch it. Claude Desktop reads the config on startup — changes while it's running are not picked up automatically.
To confirm your server loaded:
- Open the Claude Desktop menu bar icon or app menu and look for "MCP Servers"
- Your server should appear with a green indicator when successfully connected
- A red indicator or missing server means the connection failed
You can also verify the server is available by starting a new conversation in Claude Desktop and using a tool from your server. If the tool appears in the tools panel (the hammer icon in the compose area), the MCP layer is working.
Reading MCP logs for troubleshooting
Claude Desktop writes MCP-specific logs that reveal exactly why a server failed to connect. These logs are essential for diagnosing startup errors, transport mismatches, and initialization failures.
macOS log location:
~/Library/Logs/Claude/mcp.log
~/Library/Logs/Claude/mcp-server-my-server-name.log
Each server gets its own log file named with the server key from your config. Common errors and what they mean:
- "spawn ENOENT" — the
commandexecutable was not found. Use the absolute path. - "Error: Cannot find module" — the compiled JS file path in
argsis wrong. Rebuild your project first. - "Connection refused" (SSE) — the remote server isn't running or isn't listening on the configured URL.
- "Invalid JSON" — your server sent malformed output to stdout before the MCP handshake started (e.g., a console.log in the startup path). Stdio transport uses stdout exclusively for MCP messages — log everything to stderr instead.
- "Timeout waiting for initialize" — the server started but never completed the
initializehandshake. Check for async initialization blocking without a timeout.
Tail the log while restarting Claude Desktop to see errors in real time:
tail -f ~/Library/Logs/Claude/mcp-server-my-server-name.log
Multiple servers and ordering
The mcpServers object can contain multiple servers simultaneously. Claude Desktop connects to all of them at startup:
{
"mcpServers": {
"file-system": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/projects"]
},
"my-custom-server": {
"command": "node",
"args": ["/Users/you/projects/my-server/dist/index.js"]
},
"remote-api-server": {
"type": "sse",
"url": "https://my-api-server.fly.dev/sse"
}
}
}
Tool names from all servers are merged into a single tool list visible to Claude. If two servers expose a tool with the same name, Claude Desktop uses the first one alphabetically by server key. To disambiguate, prefix your tool names with a namespace in your server implementation (e.g., myserver_search instead of just search).
Monitoring your remote MCP server
For remote servers connected via SSE, Claude Desktop has no visibility into whether the server is reliably reachable between conversations. If the server goes down, SSL certificate expires, or DNS resolution breaks, Claude Desktop will show a connection error only when a user tries to use it — not proactively.
Add your remote MCP server's public URL to AliveMCP to get continuous external monitoring. AliveMCP probes the SSE endpoint from outside your network on a regular interval, running the full initialize → tools/list sequence and alerting you if the MCP protocol layer fails — not just if the HTTP endpoint returns 200. This catches the gap between "the server is responding" and "the MCP layer is actually working" that Claude Desktop can't see when no conversation is active.
See MCP server health checks for how to implement a thorough health endpoint that validates the full MCP handshake.
Related questions
Does Claude Desktop cache the server config between restarts?
No — Claude Desktop reads the config file fresh on each startup. If you change the config while Claude Desktop is running, a full quit-and-relaunch is required. On macOS, Cmd+Q quits the app; closing the window with Cmd+W only hides it. On Windows, right-click the system tray icon and choose Exit to fully quit.
Can I use a locally running HTTP server instead of stdio?
Yes. If you run your MCP server locally on a port (e.g., localhost:3000), you can connect to it via SSE transport using http://localhost:3000/sse as the URL. This is useful for development — you run the server in one terminal with hot-reload and connect Claude Desktop to it. Changes to your server code take effect without restarting Claude Desktop; just restart the server process.
Why do my console.log statements prevent the server from loading?
Stdio transport uses stdout as a dedicated MCP message channel. Any output to stdout that isn't a valid JSON-RPC message — including console.log statements, startup banners, or debug output — corrupts the protocol stream and causes Claude Desktop to fail during the initialize handshake. Always redirect application logging to stderr: console.error, a file logger, or a structured logging library configured to write to stderr. The MCP SDK's stdio transport silently reads stdout and will choke on unexpected content.
How do I update the server without disrupting active Claude conversations?
For stdio servers, Claude Desktop restarts the subprocess on the next conversation start — there's no hot-reload. For SSE servers, you can do a zero-downtime deploy on the server side (see MCP server zero-downtime deployment) and Claude Desktop will reconnect automatically on the next request after a brief connection error. For graceful handling of in-progress conversations, implement reconnect logic in your SSE server that respects in-flight session IDs.
Further reading
- MCP server with Cursor — configuring the IDE's MCP client
- MCP server with Cline — VS Code extension MCP setup
- MCP server with Continue.dev — mcpServers config and tool integration
- MCP server health checks — the full initialize probe sequence
- MCP server authentication — API key validation and OAuth patterns
- MCP server SSE transport — connection lifecycle and reconnect handling
- MCP server deployment — transport selection and rolling-restart safety
- AliveMCP — external monitoring for your Claude Desktop MCP server