WRITING July 18, 2026 8 min read

Setting Up MCP Servers with Claude Desktop and Claude Code

A practical walkthrough for configuring MCP servers in Claude Desktop and Claude Code: where the config lives, what the JSON looks like, and how to debug when things don't connect.

Setting up MCP servers in Claude Desktop and Claude Code is straightforward once you know where the config lives and how the client discovers servers. But the docs scatter this information across multiple pages and don’t cover the debugging workflow when things refuse to connect. I’ve configured four servers (scry, tome, lore, and flume) and hit every common failure mode at least once. This is the setup guide I wish existed when I started.

If you’re not familiar with what MCP servers are or why you’d want them, start with my complete guide to the Model Context Protocol. This post assumes you already have a server binary or know which server you want to run, and you just need to wire it up to your client.

Claude Desktop setup

Claude Desktop reads its MCP configuration from a JSON file on disk. The location depends on your OS:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

If the file doesn’t exist, create it. The structure is a top-level object with an mcpServers key. Each entry under that key is a server name mapped to its configuration.

Here’s what a real config looks like with multiple servers:

{
  "mcpServers": {
    "scry": {
      "command": "/usr/local/bin/scry",
      "args": ["serve"],
      "env": {
        "SCRY_INDEX_PATH": "/Users/jeff/.scry/index"
      }
    },
    "tome": {
      "command": "/usr/local/bin/tome",
      "args": ["serve", "--dsn", "postgres://localhost:5432/myapp"]
    },
    "lore": {
      "command": "/usr/local/bin/lore",
      "args": ["serve", "--repo", "/Users/jeff/workspace/myproject"]
    }
  }
}

Each of those is a stdio server. Claude Desktop spawns the process and communicates over stdin/stdout. This is the default and what you’ll use for any locally-installed server binary.

For HTTP servers (streamable HTTP transport), the config looks different:

{
  "mcpServers": {
    "remote-server": {
      "url": "http://localhost:8080/mcp",
      "headers": {
        "Authorization": "Bearer your-token-here"
      }
    }
  }
}

The url field tells Claude Desktop this is an HTTP server rather than a stdio process. The client connects to that endpoint and uses the streamable HTTP transport for all communication.

After saving the config, restart Claude Desktop completely. It only reads the config file at startup.

Claude Code setup

Claude Code takes a different approach. Instead of editing JSON by hand, you use the CLI:

claude mcp add scry -- /usr/local/bin/scry serve
claude mcp add tome -- /usr/local/bin/tome serve --dsn postgres://localhost:5432/myapp
claude mcp add lore -- /usr/local/bin/lore serve --repo /Users/jeff/workspace/myproject

The format is claude mcp add <name> -- <command> [args...]. Everything after -- is the command and arguments that will be spawned as a stdio server.

For HTTP servers:

claude mcp add remote-server --transport http --url http://localhost:8080/mcp

You can list configured servers with claude mcp list and remove them with claude mcp remove <name>.

Under the hood, claude mcp add writes to your global Claude Code settings. But for project-specific servers, you can also create a .claude/settings.json file in your project root:

{
  "mcpServers": {
    "scry": {
      "command": "/usr/local/bin/scry",
      "args": ["serve"],
      "env": {
        "SCRY_INDEX_PATH": "/Users/jeff/.scry/index"
      }
    }
  },
  "permissions": {
    "allow": [
      "mcp__scry__*"
    ]
  }
}

Project-level config is useful when different projects need different servers or different server arguments. Claude Code merges project settings with your global settings at runtime. The permissions.allow array auto-approves tool calls matching that pattern so you don’t get prompted every time.

Verifying the connection

Once configured, you need to confirm your servers actually connected.

In Claude Desktop: Look at the bottom of the chat input area. Connected MCP servers show as icons or a tool count indicator. Click it to see the list of discovered tools. If your server isn’t listed, the connection failed silently. Claude Desktop doesn’t show error messages in the UI for MCP failures.

In Claude Code: Run claude mcp list. Each server shows its connection status. A healthy server shows as “connected” with its tool count. You can also just start a conversation and ask Claude to list its available tools. It will enumerate everything it discovered from all connected servers.

If tools aren’t appearing, the issue is almost always in the initialization handshake. The server starts, the client sends initialize, and if the server doesn’t respond correctly, the client gives up. Check that your server binary actually runs when invoked with the configured arguments. Try running the exact command from your terminal first.

Debugging common issues

These are the failures I’ve hit repeatedly, in order of how often they occur.

Server binary not found

The most common issue. Claude Desktop and Claude Code spawn the server process using the command field, and that command needs to resolve. If you installed a server to ~/go/bin/scry but your config says "command": "scry", it won’t work unless ~/go/bin is in the PATH that Claude sees.

The fix: always use absolute paths in your config. Not "command": "scry" but "command": "/Users/jeff/go/bin/scry". Don’t rely on shell PATH expansion because the client may not source your shell profile.

Server crashes on startup

The server binary exists but immediately exits. This usually means it’s missing a required flag, can’t find a config file, or can’t connect to a dependency (like a database for tome).

Debug it by running the exact command from your terminal:

/usr/local/bin/tome serve --dsn postgres://localhost:5432/myapp

If it prints an error and exits, you’ve found your problem. MCP servers in stdio mode should start and then block waiting for input on stdin. If they exit, the client sees a broken pipe and gives up.

Tools not appearing after connection

The server connects but reports zero tools. This usually means the server’s tools/list handler is returning an empty list. Some servers require initialization steps (scry needs an index built first, tome needs database access) and expose no tools until that prerequisite is met.

Check your server’s documentation for required setup steps. For my servers, scry index must run before scry serve has anything to offer.

Transport mismatch

You configured a stdio server with a url field, or an HTTP server with command and args. The client tries the wrong transport, gets garbage back, and disconnects. Double-check that stdio servers use command/args and HTTP servers use url.

Environment variables

Some servers need environment variables that exist in your shell but not in the process Claude spawns. Use the env field in your config to explicitly pass them:

{
  "command": "/usr/local/bin/scry",
  "args": ["serve"],
  "env": {
    "SCRY_INDEX_PATH": "/Users/jeff/.scry/index",
    "HOME": "/Users/jeff"
  }
}

My setup

Here’s what my Claude Code configuration actually looks like. I run four MCP servers that form a context layer around my development environment:

  • scry: code intelligence. Symbol definitions, references, call graphs. Pre-indexed with SCIP into BadgerDB.
  • tome: database schema. Table descriptions, column types, foreign key relationships. Connects to my local Postgres.
  • lore: git intelligence. Blame, commit history, co-change patterns, contributor analysis.
  • flume: HTTP traffic inspection. Captures requests between browser and dev server for debugging.

My .claude/settings.json in a typical project:

{
  "mcpServers": {
    "scry": {
      "command": "/Users/jeff/go/bin/scry",
      "args": ["serve"],
      "env": {
        "SCRY_INDEX_PATH": "/Users/jeff/.scry/index"
      }
    },
    "tome": {
      "command": "/Users/jeff/go/bin/tome",
      "args": ["serve", "--dsn", "postgres://localhost:5432/myapp"]
    },
    "lore": {
      "command": "/Users/jeff/go/bin/lore",
      "args": ["serve"]
    },
    "flume": {
      "command": "/Users/jeff/go/bin/flume",
      "args": ["serve", "--port", "9090"]
    }
  },
  "permissions": {
    "allow": [
      "mcp__scry__*",
      "mcp__tome__*",
      "mcp__lore__*",
      "mcp__flume__*"
    ]
  }
}

The four servers work together. When I’m debugging a bug, Claude uses scry to find the relevant code, lore to check who changed it last and why, tome to verify the database schema matches what the code expects, and flume to inspect the actual HTTP traffic hitting the endpoint. Each server does one thing well, and the agent composes them as needed. I wrote about this architecture in depth in The Context Layer.

Tips

Restart behavior. Claude Desktop only reads MCP config at launch. You must fully restart it after config changes. Claude Code re-reads config each time you start a new session, so changes take effect on the next conversation.

Environment variables in Claude Code. The claude mcp add command doesn’t have a flag for env vars. If you need them, edit the settings JSON directly. The CLI is convenient for simple stdio servers but falls short for complex configs.

Managing many servers. I’ve run up to six servers simultaneously without issues. Each stdio server is a separate process that Claude spawns and manages. The overhead is minimal for well-built servers. Mine use around 20MB of RAM each. But poorly-built servers that do expensive work at startup can slow down session initialization noticeably.

Performance with many tools. Every connected server’s tools are included in the system prompt. If you have servers with dozens of tools each, that’s token overhead on every request. Keep your tool counts lean. My four servers expose maybe 15 tools total. Each one is specific and well-described.

Absolute paths everywhere. I cannot stress this enough. Relative paths, tilde expansion, and PATH lookups are all sources of subtle failures. Use absolute paths for the command, for any file arguments, and for environment variable values that reference the filesystem.

Test the command manually first. Before adding any server to your config, run the exact command in your terminal and verify it starts without errors and waits for input. If it works standalone, it’ll work under Claude. If it doesn’t work standalone, no amount of config fiddling will fix it.


This post is part of my series on the Model Context Protocol. For the conceptual overview, read What Are MCP Servers?. For building your own, see Building MCP Servers in Go. For the architectural pattern behind my four servers, see The Context Layer.

Frequently Asked Questions

How do I add an MCP server to Claude Desktop?

Edit the claude_desktop_config.json file (on macOS: ~/Library/Application Support/Claude/claude_desktop_config.json) and add your server under the mcpServers key. Each server needs a transport type (stdio or http), a command or URL, and optionally args and env. Restart Claude Desktop after saving.

How do I add an MCP server to Claude Code?

Run `claude mcp add <name> -- <command> <args>` from your terminal. For example: `claude mcp add scry -- scry serve`. This writes the config to your global Claude Code settings. You can also add servers at the project level by editing .claude/settings.json.

Where is the Claude Desktop MCP config file?

On macOS: ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows: %APPDATA%/Claude/claude_desktop_config.json. On Linux: ~/.config/Claude/claude_desktop_config.json. Create the file if it doesn't exist.

Why is my MCP server not connecting in Claude?

The most common causes are: the server binary isn't in PATH (use absolute paths in config), the server crashes on startup (check stderr output), or there's a transport mismatch (stdio server configured as HTTP or vice versa). In Claude Desktop, check the MCP status indicator. In Claude Code, run `claude mcp list` to see connection status.

Can I use project-specific MCP servers in Claude Code?

Yes. Create a .claude/settings.json file in your project root and add an mcpServers object there. Claude Code merges project settings with global settings, so project-level servers are available alongside your global ones when working in that directory.

Back to all writing