Claude Code MCP Server Setup: A Practical Guide (2026)
MCP (Model Context Protocol) lets Claude Code talk to external tools and data sources through a standardized interface. Instead of Claude having to search the web or read files manually, an MCP server can expose tools that Claude calls directly — fetching Slack threads, querying databases, running browser automation, or reading from any API.
This guide covers the practical setup: where the config lives, how to add servers, the differences between local and remote servers, and how to debug when tools don't appear.
Where the MCP config lives
Claude Code reads MCP server configuration from two places:
User-level (applies to all projects):
~/.claude/claude_desktop_config.json
Project-level (checked into your repo):
.claude/mcp.json # or claude_desktop_config.json
Project-level takes precedence for the servers defined there. Both files are merged — you can have global servers (GitHub, Slack) and project-specific servers (a database connection, a local test runner) active simultaneously. Both MCP config files and hook definitions live inside the same .claude/ directory — see the Claude Code settings.json reference for the full schema.
Config format
Both files use the same JSON structure:
{
"mcpServers": {
"server-name": {
"type": "stdio",
"command": "node",
"args": ["/path/to/server.js"],
"env": {
"API_KEY": "your-key-here"
}
}
}
}
The key fields:
"type":"stdio"for local processes,"sse"for remote HTTP servers"command": the executable to run"args": arguments passed to the command"env": environment variables injected into the server process
Adding a local stdio server
Most community MCP servers are npm packages that run as local processes. The pattern:
{
"mcpServers": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/projects"]
},
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..."
}
},
"postgres": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"]
}
}
}
After editing the config, restart Claude Code. New servers take effect on next launch.
Adding a remote SSE server
For servers that run as HTTP endpoints (e.g., a deployed MCP server your team runs):
{
"mcpServers": {
"company-data": {
"type": "sse",
"url": "https://mcp.yourcompany.com/sse",
"headers": {
"Authorization": "Bearer your-token"
}
}
}
}
SSE servers don't require a local process. Claude Code connects to the URL and maintains a server-sent events connection for the duration of the session.
Verifying servers loaded correctly
In a Claude Code session, run:
/mcp
This shows all configured MCP servers, their connection status, and the tools they expose. A healthy output looks like:
MCP Servers:
✓ filesystem — connected (3 tools: read_file, write_file, list_directory)
✓ github — connected (8 tools: get_file_contents, create_issue, ...)
✗ postgres — failed to connect (exit code 1)
If a server shows failed to connect, that's the signal to debug.
Debugging a server that won't connect
Step 1: Run the server command manually.
Take the exact command from your config and run it in your terminal:
npx -y @modelcontextprotocol/server-postgres "postgresql://localhost/mydb"
If it exits immediately with an error, you'll see the real error message. Common causes:
- The npm package name is wrong or misspelled
- The connection string is malformed
- Required environment variables aren't set in your shell
Step 2: Check environment variable injection.
Env vars in the MCP config are injected into the server process, but they aren't available in your shell. If the server needs GITHUB_PERSONAL_ACCESS_TOKEN, it must be in the config's "env" block, not just in your .zshrc:
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_actual_token"
}
Step 3: Check MCP logs.
Claude Code writes server stderr to logs. On macOS:
# For Claude Desktop app
cat ~/Library/Logs/Claude/mcp-server-{server-name}.log
# For Claude Code CLI
cat ~/.claude/logs/mcp-{server-name}.log
The logs show the exact error the server process emitted before exiting.
Step 4: Verify the server version is compatible.
MCP is a living protocol. Some community servers target an older SDK version and haven't been updated. Check the package's GitHub issues for "Claude Code compatibility" before spending time debugging an incompatibility.
Writing your own MCP server
If you need to expose internal data or tools that don't have a community server, the SDK makes this straightforward:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{ name: "my-internal-tools", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "get_customer",
description: "Fetch customer record by ID from internal CRM",
inputSchema: {
type: "object",
properties: {
customer_id: { type: "string", description: "Customer ID" },
},
required: ["customer_id"],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "get_customer") {
const { customer_id } = request.params.arguments as { customer_id: string };
const customer = await fetchFromCRM(customer_id);
return {
content: [{ type: "text", text: JSON.stringify(customer, null, 2) }],
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
const transport = new StdioServerTransport();
await server.connect(transport);
Then add it to your config:
{
"mcpServers": {
"internal-crm": {
"type": "stdio",
"command": "node",
"args": ["/path/to/your/server.js"],
"env": {
"CRM_API_KEY": "your-key"
}
}
}
}
Security: what to keep in mind
MCP servers run with your permissions. A stdio server runs as your local user and can read any file you can. Only add servers you trust.
Don't commit secrets to the project config. If .claude/mcp.json is checked in, use environment variable references rather than hardcoding tokens. Some teams use a .claude/mcp.local.json (gitignored) pattern for local overrides.
Remote SSE servers see your requests. Claude sends tool call arguments to the server, which means the server can log anything Claude sends. For sensitive internal data, a locally-running server is safer than a remote one. You can also use Claude Code hooks to log or block specific MCP tool calls at the Claude Code level as an extra layer of control.
Practical server recommendations for Claude Code workflows
| Use case | Server | Install |
|---|---|---|
| Read/write local files | @modelcontextprotocol/server-filesystem | npx -y @modelcontextprotocol/server-filesystem /path |
| GitHub repos, issues, PRs | @modelcontextprotocol/server-github | Needs GITHUB_PERSONAL_ACCESS_TOKEN |
| Postgres queries | @modelcontextprotocol/server-postgres | Pass connection string as arg |
| Fetch web pages | @modelcontextprotocol/server-fetch | No config needed |
| Browser automation | @playwright/mcp | Requires Playwright install |
| Slack threads | Community Slack MCP | Needs workspace token |
The MCP registry at modelcontextprotocol.io maintains the canonical list of official and community servers. For curated picks across the most common developer workflows, see best MCP servers for Claude Code in 2026.
For more Claude Code configuration patterns — including 40 slash command templates and 300 production-tested prompts — see the Claude Code Power Prompts collection.
Frequently Asked Questions
Where should I put my MCP server configuration — user-level or project-level?
Use ~/.claude/claude_desktop_config.json for servers you want available in every project (GitHub, Slack, filesystem access to your home directory). Use .claude/mcp.json for project-specific servers like a database connection or a custom internal tool. Both files are merged — servers from both locations are active simultaneously.
Why does my MCP server show "failed to connect" in /mcp?
The most common causes are: a misspelled npm package name, a missing environment variable not included in the config's "env" block, a malformed connection string, or a version incompatibility between the server and the MCP protocol. Run the exact command from your config manually in your terminal to see the actual error message before debugging further.
Do I need to restart Claude Code every time I change the MCP config?
Yes. MCP server configuration is read at startup. After editing claude_desktop_config.json or .claude/mcp.json, restart your Claude Code session. Changes will not take effect in an already-running session.
Is it safe to check .claude/mcp.json into git?
It is safe to commit the structure and server definitions, but never commit the actual secrets. Use the "env" block for API tokens and reference them as environment variables that team members set locally, or use .claude/mcp.local.json (gitignored) for personal credentials on top of the shared project config.
Can I build an MCP server in Python instead of TypeScript?
Yes. The MCP SDK has official Python support (pip install mcp). The TypeScript SDK is more commonly used in examples because most community servers are Node.js packages, but Python MCP servers work identically — implement the ListTools and CallTool request handlers, connect via stdio transport, and add the server to your config the same way.
Take It Further
Claude Code Power Prompts 300 — 300 battle-tested prompts for Claude Code, organized by use case. Copy, paste, ship.
40 slash command templates. Token-optimized variants. JSONL file for direct import. Tested in production sessions.
→ Get Claude Code Power Prompts 300 — $29
30-day money-back guarantee. Instant download.