Connecting MCP Servers to Claude Desktop: A Setup Guide
Why Claude Desktop Suddenly Needs "Servers"
The first time you read that Claude Desktop can connect to "MCP servers," it sounds like overkill. You just want Claude to read a file, query a database, or check your calendar — why does that require standing up a server?
Here's the honest answer: because chat models are stateless and sandboxed by default. Claude Desktop, on its own, can only see what you paste into the conversation. It has no filesystem access, no way to hit an internal API, no memory of your Notion workspace. The Model Context Protocol (MCP) is Anthropic's open standard for closing that gap — it defines a common way for an AI application (the "host," in this case Claude Desktop) to talk to external tools and data sources (the "servers") over a well-defined JSON-RPC interface.
Once you've wired up even one MCP server, the mental model clicks. You're not writing a plugin API from scratch for every tool. You install a small server process — often a single npm package or a Python script — that exposes a handful of "tools" (functions) and "resources" (readable content). Claude Desktop discovers what's available, and you can then ask Claude to "list the open PRs in my repo" or "pull the last five rows from my Postgres table" and it just works, because the server is doing the actual I/O and Claude is doing the reasoning.
This guide walks through connecting MCP servers to Claude Desktop end to end: the config file, the two transport types you'll actually encounter, a full worked example, and the debugging steps that save you an hour of guessing. If you've been putting off "just add MCP" because the docs assume too much prior context, this is the guide to actually get it running today.
Before touching a config file, it helps to separate three terms that get used loosely:
- Host — the application you interact with. Here, that's Claude Desktop itself.
- Client — the connector inside the host that speaks MCP. Claude Desktop has one MCP client built in; you don't write this part.
- Server — the external process that exposes tools, resources, and prompts. This is the part you install and configure.
An MCP server is not a web server in the traditional sense you're thinking of, not usually. Most of the servers you'll connect to Claude Desktop run locally, launched as a subprocess by Claude Desktop itself, and communicate over stdio (standard input/output). A smaller number run as long-lived HTTP services with Server-Sent Events (SSE) or the newer streamable HTTP transport, which matters when the server needs to be shared across machines or kept running independently of the desktop app.
For a beginner, the practical distinction is this:
- stdio servers: Claude Desktop starts the process for you when the app launches. You give it a command (like
npxorpython) and arguments. No network port to manage. - remote/HTTP servers: the server is already running somewhere (your machine, a container, a cloud host) and you point Claude Desktop at a URL.
Almost every "getting started" MCP server — filesystem access, GitHub, Slack, Google Drive, a local SQLite database — uses stdio. Start there.
There's also a fourth term worth knowing even though it's less visible day to day: prompts. In addition to tools (things Claude can call) and resources (content Claude can read), the MCP spec lets a server define reusable prompt templates — a canned "summarize this ticket" or "draft a release note" workflow that shows up as a slash-command-style shortcut in the client. Most servers you'll install skip this and only expose tools, but it's worth recognizing the term when you see it in a server's documentation so you don't assume it's a typo for "prompt caching" or something unrelated.
Choosing Which Servers to Install First
It's tempting to open the MCP server registry, see forty interesting-looking packages, and install all of them in one sitting. Resist that. Every server you add is a subprocess Claude Desktop launches on startup, a credential you now have to manage, and a source of failures to debug if something goes wrong. A more disciplined approach is to add one server, confirm it works end to end, and only then move to the next.
A reasonable order for a first setup:
- Filesystem — zero credentials, immediate feedback, teaches you the config mechanics safely.
- A version control server (GitHub or GitLab) — introduces the
envblock and token scoping, and is immediately useful if you write code. - A database server (Postgres, SQLite) — introduces connection strings and read/write permission boundaries; a good place to first ask "should this tool even have write access?"
- A team communication or docs server (Slack, Notion, Google Drive) — usually involves OAuth rather than a static token, which is a different setup flow worth learning once you're comfortable with the basics.
- A custom server you write yourself — once you understand the shape of a working config, writing your own tool takes an afternoon, not a week.
This order isn't arbitrary — it goes from "no failure modes" to "progressively more failure modes," so each new server teaches you one new thing about MCP rather than five things at once.
Now that you know what a server is and where to start, the mechanics of actually wiring one in come down to a single file.
Locating and Understanding the Config File
Claude Desktop reads its MCP server list from a single JSON file called claude_desktop_config.json. Its location differs by OS:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux (unofficial builds):
~/.config/Claude/claude_desktop_config.json
If the file doesn't exist yet, create it. Claude Desktop won't complain about a missing file on first launch, but it also won't have any servers configured, so you may as well create the directory structure now.
The file has one meaningful top-level key: mcpServers. Each entry under it is a named server with its own launch configuration:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/yourname/Documents"
]
}
}
}A few things to notice, because they trip people up constantly:
- The key name (`"filesystem"` here) is arbitrary — it's just a label Claude Desktop shows you in the UI. It does not need to match the package name.
- `command` and `args` are separate. The command is the executable (
npx,node,python,uvx, a compiled binary path);argsis the array of arguments passed to it, in order, exactly as you'd type them on a command line. - This file is strict JSON. No trailing commas, no comments. A single misplaced comma will make Claude Desktop silently fail to load any servers — not just the broken one.
- You must fully quit and restart Claude Desktop after editing this file. Closing the window is not enough on macOS; use Cmd+Q or quit from the menu bar, then relaunch.
Step-by-Step: Your First Server
Let's connect the official filesystem server, since it requires no API keys and gives immediate, visible feedback.
Step 1 — Confirm Node.js is installed. Most MCP servers are distributed as npm packages and launched via npx, so you need Node installed regardless of whether you write JavaScript.
node --version
npx --versionIf these fail, install Node (via nvm or the official installer) before continuing.
Step 2 — Open or create the config file. On macOS:
mkdir -p ~/"Library/Application Support/Claude"
open -a TextEdit ~/"Library/Application Support/Claude/claude_desktop_config.json"Step 3 — Add the server entry. Paste this, adjusting the path to a real directory on your machine:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/yourname/Documents/teachyou-notes"
]
}
}
}The path argument is important: this server only grants access to the directories you explicitly list. It is not a "give Claude your whole disk" switch, and that's a deliberate design choice — MCP servers should be scoped as narrowly as the task allows.
Step 4 — Restart Claude Desktop fully. Quit it, relaunch it.
Step 5 — Verify the connection. Open a new chat and look for a small tools/plug icon near the message input, or check Settings → Developer (naming varies slightly by version). You should see "filesystem" listed as connected, with the tools it exposes (read_file, list_directory, search_files, and so on).
Step 6 — Test it. Ask Claude something like: "List the files in my teachyou-notes directory" or "Read the contents of outline.md in that folder." If it responds with real file contents rather than a refusal or hallucination, the server is wired correctly.
Step 7 — Watch how Claude uses the tool, not just whether it works. Open the tool-call detail in the chat (Claude Desktop shows a small expandable block whenever it invokes an MCP tool) and look at the actual arguments it passed and the raw result it got back. This matters more than it sounds like it should: the first time you see the literal JSON going back and forth, the abstraction stops being magic and starts being a debuggable system. When something later goes wrong with a more complex server, this is the view you'll go back to.
Connecting a Real-World Server With Credentials
The filesystem example is clean because it needs no auth. Most useful servers — GitHub, Postgres, Slack, Google Drive — need an API key or token, and that's where env comes in. Every server entry can carry an env block that gets passed to the subprocess as environment variables:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_yourTokenHere"
}
},
"postgres": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"postgresql://user:password@localhost:5432/mydb"
]
}
}
}A couple of practical notes from setting these up:
- Generate a scoped token, not your admin credentials. For GitHub, create a fine-grained personal access token limited to the repos you actually want Claude to touch. Treat this exactly like you'd treat an API key checked into a
.envfile — because functionally, that's what it is. - Multiple servers run simultaneously. Claude Desktop launches every entry in
mcpServersas its own subprocess on startup. There's no conflict between having filesystem, GitHub, and Postgres all active at once — each is isolated. - Order in the JSON doesn't matter. Claude Desktop doesn't care what sequence the keys appear in.
If you're connecting a database server, double-check the connection string format matches what that specific server package expects — some want a full postgresql:// URI, others want separate host/port/user fields passed as args. Read the server's own README rather than assuming they're all identical; this is one of the more common sources of "it looks right but won't connect" bugs.
Remote (HTTP/SSE) Servers
Not every server should be a subprocess spawned per desktop launch. If you're running a server that's shared across a team, needs to persist state, or lives behind a company firewall, you'll expose it over HTTP instead. The config shape changes slightly — instead of command/args, you provide a url:
{
"mcpServers": {
"internal-docs": {
"url": "https://mcp.internal.example.com/sse",
"headers": {
"Authorization": "Bearer your-shared-secret"
}
}
}
}This is the pattern you'd reach for if, say, your team already runs an internal knowledge-base MCP server on a shared box and doesn't want every engineer independently launching their own copy. It also matters if the server needs long-lived state — a stdio server dies and restarts with Claude Desktop, which is fine for read-mostly tools but wrong for something like a long-running job queue.
For most individual developers getting started, though, stick with stdio. Remote servers introduce a second thing that can break (network reachability, TLS certs, auth headers) on top of everything a local server can break, and you don't need that complexity for a personal setup.
One more distinction worth flagging: some remote servers use the older SSE transport, others use the newer "streamable HTTP" transport that the MCP spec introduced as SSE's successor. Functionally they solve the same problem — a persistent channel for a server to push results and notifications back to the client — but they're not wire-compatible with each other. If a remote server's docs say it uses streamable HTTP and your Claude Desktop version only understands SSE (or vice versa), you'll get a connection failure that looks identical to a bad URL. If a remote server won't connect and the URL and headers all check out, checking transport compatibility is worth doing before you assume the config is wrong.
Writing a Minimal Server From Scratch
At some point you'll want a server that does something no pre-built package covers — maybe it queries your own internal API, or wraps a proprietary tool. The official MCP SDKs make this fast. Here's a minimal Python server using the mcp package that exposes one tool: fetching a course's metadata by slug.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("course-lookup")
COURSES = {
"ai-agents-101": {"title": "AI Agents 101", "hours": 6},
"prompt-engineering": {"title": "Prompt Engineering Deep Dive", "hours": 4},
}
@mcp.tool()
def get_course(slug: str) -> dict:
"""Return metadata for a course given its slug."""
course = COURSES.get(slug)
if not course:
return {"error": f"no course found for slug '{slug}'"}
return course
if __name__ == "__main__":
mcp.run(transport="stdio")Save that as course_lookup.py, install the SDK with pip install mcp, and point Claude Desktop at it like any other stdio server:
{
"mcpServers": {
"course-lookup": {
"command": "python3",
"args": ["/Users/yourname/mcp-servers/course_lookup.py"]
}
}
}Restart Claude Desktop, and you can now ask "What's the course metadata for ai-agents-101?" and get a structured answer sourced from your own function rather than a guess. This is the actual unlock MCP provides: turning any internal system into something Claude can reason over, without building a custom plugin architecture per tool.
Two details matter once you go past this toy example. First, your tool's docstring is not decoration — it's the interface Claude reads to decide when to call the tool at all. If get_course had no docstring, or a vague one like "does course stuff," Claude would have a much harder time deciding this was the right tool for a given question, especially once you have five or six tools registered on the same server. Write docstrings the way you'd write a function signature for a teammate who's never seen the codebase.
Second, return structured data, not prose. Returning a Python dict (which FastMCP serializes to JSON) lets Claude parse the result reliably and cite specific fields back to you. If you instead returned a hand-formatted string like "The course is called AI Agents 101 and runs 6 hours", Claude can still use it, but you've pushed parsing work onto the model that the server could have done deterministically. This is a small thing on a one-tool toy server and a very consequential thing once a server has real business logic behind it.
Debugging When a Server Won't Connect
Almost everyone hits a snag on their first or second server. The failure modes are predictable enough that you can work through them in order rather than guessing randomly.
- Check the logs first. Claude Desktop writes MCP-related logs to
~/Library/Logs/Claude/mcp*.logon macOS. Tail them while you restart the app:
tail -f ~/Library/Logs/Claude/mcp-server-filesystem.logWhatever the subprocess prints to stderr shows up here, including stack traces from a crashing server.
- Validate your JSON. A single missing comma anywhere in
claude_desktop_config.jsoncan silently break every server, not just one. Run it through a linter before restarting:
python3 -m json.tool ~/"Library/Application Support/Claude/claude_desktop_config.json"If that command errors, fix the syntax before doing anything else.
- Run the command manually. If
npx -y @modelcontextprotocol/server-filesystem /some/pathis your config, run exactly that in a terminal. If it fails there, it will fail identically inside Claude Desktop — this isolates whether the problem is your config or the server itself.
- Watch for `npx` first-run delay. The very first time
npxruns a package, it downloads it, which can take longer than Claude Desktop's connection timeout. Run the command manually once to warm the cache, then restart Claude Desktop.
- Confirm you fully restarted the app. This sounds obvious and still accounts for a large share of "I made the change and nothing happened" reports. Quit from the menu, don't just close the window.
- Check for absolute vs. relative paths. Subprocess launches don't inherit your shell's working directory the way you'd expect. Always use absolute paths in
args, both for the target directories and for any script files.
- Environment variables not showing up inside the server? Remember that
envin the config is the *only* environment the subprocess gets in some setups — it may not inherit your shell'sPATHor other variables automatically depending on how Claude Desktop launches it. If your server depends on something likePATHto find another binary, it's safer to reference binaries by absolute path inside the server itself.
Security Practices Worth Adopting Early
Because MCP servers can read files, hit APIs, and execute code on your behalf, it's worth being deliberate rather than permissive from day one.
- Scope filesystem servers tightly. Pass the narrowest directory that satisfies the task, not your home folder.
- Use scoped tokens, and rotate them. A GitHub token limited to three repos does far less damage if leaked than an org-admin token.
- Prefer local servers over remote ones when the data is sensitive. A stdio server never sends your data anywhere except the process you started; a remote server implies a network hop and a party operating that endpoint.
- Review third-party servers before installing. MCP's openness is the point, but that also means anyone can publish a server package. Before pointing Claude Desktop at an unfamiliar server, skim its source — most are small enough to read in a few minutes — and check what it actually does with the credentials you hand it.
- Don't commit `claude_desktop_config.json` to a public repo. It routinely contains live API keys and tokens sitting in plaintext.
None of this is exotic advice — it's the same hygiene you'd apply to any credential-holding config file — but it's easy to skip when you're excited to get a new tool connected.
Wrapping Up
Connecting an MCP server to Claude Desktop comes down to four repeatable steps: find the config file, add a named entry with the right command/args (or url for remote servers), restart the app completely, and verify the tools show up in a fresh chat. The filesystem server is the fastest way to prove the mechanism works with zero credentials involved; from there, adding GitHub, a database, or your own custom Python server is the same pattern with different arguments.
The bigger shift is conceptual: once you've connected two or three servers, you stop thinking of Claude as a chat window and start thinking of it as a reasoning layer sitting on top of whatever tools you've wired in — your filesystem, your repos, your internal APIs. That's the actual value of MCP, and it's also exactly why we built a full module on Building & Integrating MCP Servers as part of the AI engineering curriculum here at teachyou.ai — going from "I connected someone else's server" to "I designed the tool schema, the auth model, and the error handling for my own." If this guide got your first server running, that module is the natural next step.
AI CodingShip full-stack AI apps at conversation speed — specs, agents, deploys, all from the terminal.
Claude CodeGo from zero to confident with Claude Code, the terminal agent that reads, edits, runs, and verifies real code.