Claude Code is an MCP client. It reads your config, launches server processes, and gives the model access to their tools alongside built-in ones like Read and Bash. The model doesn't distinguish between them — a tool is a tool. This post covers the tool call flow, config setup, debugging, and building your own server.
I wrote about what MCP is last week. Today I want to get specific about the tool I actually use it with every day: Claude Code, Anthropic's CLI agent.
Claude Code leans on MCP more than most people realize. Understanding that connection — what gets sent where, what can go wrong, how to fix it — has changed how I work with it. Less "why isn't this working," more "ah, the GitHub server is timing out again, let me check the token."
Claude Code as an MCP client
When you run claude in your terminal, you're starting an MCP client. It reads your config, launches server processes, runs the JSON-RPC handshake, and then feeds all discovered tools to the model alongside its built-in toolkit.
So Claude Code's tool list looks something like:
Built-in: Read, Edit, Write, Bash, Glob, Grep, Agent, ... From MCP: fetch, list_issues, create_pr, search_code, ...
The model doesn't know which are built-in and which come from MCP. It sees a flat list of tools, each with a name, description, and parameter schema. It picks the right one based on what you're asking it to do. A fetch from an MCP server and a Read from the built-in toolkit are, from the model's perspective, the same kind of thing.
This is actually clever. It means you can extend Claude Code's capabilities without forking it, without writing plugins, without any special API. Just add a server to a JSON config file and restart. The model figures out the rest.
Where the config lives
Claude Code checks these locations:
~/.claude/claude_desktop_config.json
~/Library/Application Support/Claude/claude_desktop_config.json # macOS
Same file Claude Desktop uses. If you've set up MCP servers for the desktop app, Claude Code picks them up automatically. No duplication.
Here's my daily driver config:
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
},
"github": {
"command": "uvx",
"args": ["mcp-server-github"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here"
}
}
}
}
Two servers. When I run claude, both launch as subprocesses, handshake completes, and the model gets fetch plus all ~35 GitHub tools.
A tool call, start to finish
Say I ask Claude Code: "What are the open issues on the mcptools repo?"
The model sees list_issues in its tool list (from the GitHub MCP server) and decides to call it. Here's the JSON-RPC that flows between Claude Code and the MCP server:
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "list_issues",
"arguments": {
"owner": "jannik-cas",
"repo": "mcptools",
"state": "open"
}
}
}
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [
{
"type": "text",
"text": "[{\"number\": 1, \"title\": \"Add SSE transport support\", ...}]"
}
]
}
}
The MCP server receives the call, hits the GitHub API with my token, wraps the response in JSON-RPC format, and sends it back. Claude Code feeds this to the model, which formats a nice answer. ~500ms end to end. I just see the answer in my terminal. All the plumbing is invisible.
Adding a server
This is genuinely a two-step process. Edit the config, restart Claude Code.
Want Claude Code to fetch web pages? Add this to your config:
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
}
}
}
Restart claude. Done. The model now has a fetch tool. Ask it to "grab the content of https://example.com" and it'll use it.
Verify before you rely on it:
$ mcptools doctor --config ~/.claude/claude_desktop_config.json Config: ~/.claude/claude_desktop_config.json Checking fetch... ✓ 1 tool, 1 prompt
Pro tip: Run mcptools doctor after every config change. Takes two seconds and saves you from the "why is this tool not showing up" spiral.
When things break (and they will)
MCP failures in Claude Code are silent. A server that doesn't start? The tool just doesn't appear. Wrong token? The server starts but fails at call time — and the model quietly moves on without telling you it tried and failed.
I've hit all of these, some more than once:
- Tool not showing up — Server command is wrong, or the binary isn't in PATH. I once spent 20 minutes before realizing I'd typed
uvx mcp-server-gitub. No error. Just... no tools. - Tool exists but fails — Usually a bad or expired token. Server starts fine, handshake works, but it chokes when it actually tries to call an API.
- Tool is slow — The GitHub server takes 3+ seconds to initialize. 35 tools is a lot of schema to negotiate. If Claude Code feels sluggish at startup, check how many MCP tools you're loading.
- Wrong arguments from the model — Sometimes the model generates parameters that don't match the schema. Rare, but happens with complex tools. Check the schema with
mcptools inspect.
My debugging workflow, every time:
# 1. Does the server even start? $ mcptools doctor --config ~/.claude/claude_desktop_config.json # 2. What tools does it expose? What are the parameter types? $ mcptools inspect uvx mcp-server-github # 3. Watch the actual traffic flow $ mcptools proxy --config ~/.claude/claude_desktop_config.json --server github --no-tui
Step 1 catches dead servers. Step 2 shows you what's actually exposed (vs what you think is exposed). Step 3 shows you every message in real time — if the server returns an error, you'll see it. Between these three, I've diagnosed everything I've hit so far.
Common trap: You add a server to your config but forget to restart Claude Code. The config is only read at startup. No hot-reloading.
MCP servers I actually use
After a few months, here's what stuck in my daily rotation:
mcp-server-fetch is the most useful MCP server, full stop. Claude Code can grab documentation, check URLs, pull in context from the web mid-conversation. I use it multiple times a day. If you only install one server, make it this one.
mcp-server-github is powerful but heavy. 35 tools, slow init. I use it for "check the open issues" and "look at this PR" type tasks. Worth having if you're doing a lot of GitHub work in Claude Code.
Custom project servers are where it gets interesting. I've written a couple of small ones — one that queries our internal docs, another that pokes a staging API. Each one was maybe an afternoon of work. Which brings me to...
Writing your own server
An MCP server is ~50 lines of Python. Here's a real, working one:
from mcp.server import Server from mcp.server.stdio import stdio_server app = Server("my-server") @app.list_tools() async def list_tools(): return [ { "name": "greet", "description": "Say hello to someone", "inputSchema": { "type": "object", "properties": { "name": {"type": "string"} }, "required": ["name"] } } ] @app.call_tool() async def call_tool(name, arguments): if name == "greet": return [{"type": "text", "text": f"Hello, {arguments['name']}!"}] async def main(): async with stdio_server() as (read, write): await app.run(read, write) import asyncio asyncio.run(main())
Yes, the example is silly. But swap greet with "query our staging database" or "search our internal wiki" and you've got something genuinely useful. The MCP SDK handles all the JSON-RPC plumbing. You just define tools and implement them.
Add it to your config:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["my_server.py"]
}
}
}
Test it before opening Claude Code:
$ mcptools inspect python my_server.py ┌───────────────────────── MCP Server ─────────────────────────┐ │ my-server │ └──────────────────────────────────────────────────────────────┘ Tools (1) Name Description Parameters greet Say hello to someone name: string*
Green. Works. Restart Claude Code and you can say "greet Jannik" and it'll call your server.
What I'd do next
If you're using Claude Code and haven't touched MCP yet, start with mcp-server-fetch. Two minutes to set up. Noticeable difference immediately — suddenly Claude can look things up instead of guessing from training data.
If you want to go further, think about what repetitive tasks you do that involve an API or a data source. Querying a database. Checking deployment status. Searching internal docs. Each of those is a potential MCP server, and they're small enough to build in an afternoon.