Skip to main content

Claude Desktop + MCP: Complete Setup Guide

· 7 min read
MCPBundles

I spent two hours staring at Claude Desktop wondering why my MCP server wouldn't connect. The config looked perfect. The server ran fine standalone. But Claude showed no tools. The problem? A single trailing comma in my JSON config that Claude silently ignored.

This is everything I wish the docs had told me upfront.

What This Guide Covers

You've built an MCP server (or grabbed one from GitHub). Now you need Claude Desktop to actually use it. This involves:

  1. Finding the right config file (it's in different places on Mac/Windows/Linux)
  2. Writing valid JSON config (one syntax error breaks everything silently)
  3. Restarting Claude Desktop the right way (closing the window isn't enough)
  4. Debugging when tools don't appear (they won't, the first time)
  5. Testing tool calls and reading logs

This assumes you have a working MCP server. If you don't, start with Building Your First MCP Server.

Find Your Config File

Claude Desktop stores MCP server configuration in a JSON file. 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 with this starter:

{
"mcpServers": {}
}

Pro tip: Open this file in VS Code or another editor with JSON validation. Claude Desktop doesn't show config errors, so you'll waste time chasing phantom issues if your JSON is malformed.

Add Your Server Configuration

Here's what a working config looks like:

{
"mcpServers": {
"my-notes": {
"command": "python",
"args": ["/Users/tony/projects/notes-mcp/server.py"],
"env": {
"LOG_LEVEL": "info"
}
}
}
}

Breaking this down:

  • "my-notes": A label for your server. Use lowercase with hyphens. This shows up in Claude's UI.
  • "command": The executable—python, node, dotnet, etc.
  • "args": Array of arguments. Use absolute paths, not relative ones.
  • "env": Optional environment variables your server needs.

Common mistakes I made:

Trailing commas:

{
"mcpServers": {
"my-notes": {
"command": "python",
"args": ["/path/to/server.py"],
} // ← This comma breaks everything
}
}

Relative paths:

"args": ["./server.py"]  // ✗ Breaks - Claude doesn't know where "./" is
"args": ["/Users/tony/projects/server.py"] // ✓ Works

Wrong quotes:

"command": "python"  // ✗ Smart quotes from copying docs
"command": "python" // ✓ Regular double quotes

Multiple servers:

{
"mcpServers": {
"notes": {
"command": "python",
"args": ["/path/to/notes-server.py"]
},
"tasks": {
"command": "node",
"args": ["/path/to/tasks-server/dist/index.js"]
}
}
}

Notice the comma between servers but not after the last one.

Restart Claude Desktop (Really Restart It)

Save your config, then restart Claude Desktop. Not close-the-window restart—actually quit the application.

macOS: Cmd+Q or Claude menu → Quit Claude

Windows: Right-click the taskbar icon → Quit (don't just X the window)

Linux: Use your window manager's quit command

Claude caches MCP connections on startup. If you just close the window, your changes won't load.

Test Discovery

Open Claude Desktop and start a new conversation. Ask:

"What MCP tools do you have available?"

Claude should list your tools with descriptions. If you see your tools, success! Skip to the next section.

If you don't see tools:

  1. Check Claude Desktop logs
    Settings → Advanced → View Logs
    Look for MCP connection errors or your server name

  2. Test your server independently
    Run the command from your config manually:

    python /path/to/server.py

    You should see startup logs. If it crashes, fix that first.

  3. Verify the config file saved
    Open it again and make sure your changes are there (easy to edit the wrong file)

  4. Check for JSON syntax errors
    Use jq or an online JSON validator:

    cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | jq .
  5. Look for process launch errors
    On macOS, check Console.app for errors from Claude Desktop

Make Your First Tool Call

Once tools appear, test them. Start simple:

"Search my notes for MCP"

Watch what happens:

  1. Claude decides to use your search_notes tool
  2. Claude shows you the tool call with parameters
  3. Your server executes and returns results
  4. Claude incorporates the results into its response

If the tool call fails:

  • Check your server logs (they should be going to stderr)
  • Look at the parameters Claude sent—does your schema match?
  • Test the exact same parameters by running your server directly

Common Tool Call Problems

Claude invents fields that don't exist:

Your JSON Schema isn't strict enough. Add "required" arrays and enums:

@mcp.tool(description="Search notes")
async def search_notes(
query: Annotated[str, Field(description="Search term")],
limit: Annotated[int, Field(description="Max results", ge=1, le=50)] = 10,
sort: Annotated[
Literal["relevance", "date"],
Field(description="Sort order")
] = "relevance"
) -> dict:
...

Now Claude can't invent a sort="best" value.

Tool returns too much data:

Claude gets overwhelmed by large JSON responses. Return IDs and summaries, not full objects:

# ✗ Bad - too much data
return {"results": [full_note_object for note in results]}

# ✓ Good - just IDs
return {
"count": len(results),
"note_ids": [note.id for note in results]
}

Let Claude call another tool like get_note_by_id if it needs details.

Claude never uses your tool:

Your description isn't clear enough. Compare these:

# ✗ Vague
description="Search notes"

# ✓ Specific
description="Search through your personal notes by keyword or phrase. Use this when the user asks about their notes, ideas, or past entries."

The second one tells Claude exactly when to use it.

Environment Variables and Secrets

Don't hardcode API keys in your server. Use environment variables:

{
"mcpServers": {
"github": {
"command": "python",
"args": ["/path/to/github-mcp/server.py"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here",
"LOG_LEVEL": "debug"
}
}
}
}

Your server reads them:

import os
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
if not GITHUB_TOKEN:
raise ValueError("GITHUB_TOKEN environment variable required")

Better: Store tokens in a .env file and reference them. But for local development, putting them directly in the config works.

Debugging Tips That Actually Help

Add verbose logging:

import logging
logging.basicConfig(level=logging.DEBUG, handlers=[logging.StreamHandler()])
logger = logging.getLogger(__name__)

@mcp.tool(description="Search notes")
async def search_notes(query: str) -> dict:
logger.info(f"Received search query: {query}")
results = do_search(query)
logger.info(f"Returning {len(results)} results")
return {"results": results}

Watch your server logs while testing:

Run your server in a terminal window while using Claude Desktop. You'll see every tool call in real time.

Test tools manually first:

Before blaming Claude, call your tools directly:

import asyncio
result = asyncio.run(search_notes("MCP"))
print(result)

If this doesn't work, your tool has bugs independent of Claude.

Use correlation IDs:

Generate a unique ID for each tool call:

import uuid

@mcp.tool(description="Search notes")
async def search_notes(query: str) -> dict:
request_id = str(uuid.uuid4())
logger.info(f"[{request_id}] Search query: {query}")
# ... your code ...
logger.info(f"[{request_id}] Completed in {elapsed}s")
return results

Now you can trace specific calls through your logs.

Why This Setup Works

Claude Desktop launches your server as a subprocess and communicates over stdin/stdout using JSON-RPC. This means:

  • Your server must be non-interactive (no input() prompts)
  • Logs must go to stderr, not stdout
  • The process must stay running and responsive
  • Config changes require a full restart

Once you understand this model, debugging gets much easier. Most issues are:

  1. Invalid JSON config
  2. Wrong paths
  3. Server crashes on startup
  4. Print statements breaking stdout

Fix these, and everything works.

Key Takeaways

  • Use absolute paths everywhere in your config—relative paths don't work
  • Watch for trailing commas in JSON—they break everything silently
  • Actually quit Claude Desktop (Cmd+Q), don't just close the window
  • Test your server standalone first before blaming the integration
  • Add verbose logging to stderr so you can see what's happening
  • Start with one server and get it working before adding more
  • JSON Schema descriptions matter—the better they are, the more accurately Claude uses your tools

Resources

Still stuck? Drop a question—we're all figuring this out together.