What MCP is
Your agent already has hands — view, create, str_replace, bash. But those hands only reach the local filesystem. What about a Postgres database, a GitHub API, a company's internal docs? You could hard-code a new tool for each one, recompiling your agent every time. The Model Context Protocol is the alternative: a single wire format that lets an agent discover and call any external tool at runtime, with zero new code.
MCP is how Claude Code reaches the outside world. This chapter you'll speak it by hand — connect to a server, ask what it offers, and call one of its tools — all over plain JSON-RPC.
Host and server
MCP has two roles. A server wraps some external capability — a database, an API, a filesystem — and exposes it. A host (more precisely, a client inside a host) connects to that server, discovers what it offers, and calls it on the model's behalf.
Claude Code is an MCP host. It launches the servers you configure, opens a connection to each, asks each one what it can do, and routes the model's tools/call requests to the right server — then feeds the result back into the agent loop as a tool result. You — the host — are the side you're building this chapter.
{
"mcpServers": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
}
}
Each entry under mcpServers is one server. For a stdio server, command + args is literally the process the host spawns; the host then talks to it over that process's stdin and stdout. No port, no socket — the child's standard streams are the channel. Claude Code reads this file from the project root (and merges it with user- and enterprise-level config); you'll parse the same shape next step.
JSON-RPC 2.0 over stdio
The wire format is JSON-RPC 2.0 — a tiny, decades-old standard. Every message is one JSON object. A request carries a method, a params object, and an id; the server replies with a response that echoes the same id and carries either a result or an error. The id is how you match a reply to the request that asked for it, even when several are in flight.
Because stdout is just a byte stream with no message boundaries, MCP frames each message as one line of JSON terminated by a newline — read until \n, parse the line, repeat. That's the entire framing rule, and it's the first thing you'll implement.
host → server {"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}\n
server → host {"jsonrpc":"2.0","id":1,"result":{"tools":[ ... ]}}\n
└── same id ──┘ result (or "error": {code, message})
Tools, resources, prompts
A server can expose three kinds of capability, and it declares which it supports during the handshake:
- Tools — actions the model can invoke (
tools/listto discover,tools/callto run). Each tool has aname, adescription, and a JSON-SchemainputSchema. This is the one you'll drive end-to-end. - Resources — readable data the host can pull in as context (files, records, API responses).
- Prompts — reusable prompt templates the server offers to the user.
This chapter focuses on tools, because that's the primitive that lets the agent act on the outside world — the same tool_use / tool_result shape from Chapter 1, now sourced from a separate process instead of your own code.
The conversation, end to end
Across the next three steps you'll build exactly this exchange, in order:
initialize— the host announces its protocol version and capabilities; the server replies with its capabilities. Nothing else happens until this handshake completes.tools/list— the host asks what tools exist; the server returns their definitions.tools/call— the host invokes one tool bynamewitharguments; the server runs it and returnscontent(plus anisErrorflag), which the host surfaces back to the model as atool_result.
Discover, then call. By the end of the chapter your agent can use a tool it never had source code for.