The architecture has three pieces. Understanding them takes five minutes and prevents a lot of confusion later.
The three pieces
Host — the application the user is actually running. Claude Desktop, Cursor, Windsurf. The host manages the overall session and talks to the LLM.
Client — lives inside the host. For each MCP server you connect, the host creates one client to manage that connection. One server = one client. They have a 1:1 relationship.
Server — the thing you're building. It runs as a separate process on your machine (or remotely). It exposes tools, resources, and prompts. It has no idea what the host is — it just speaks the MCP protocol.
When Claude decides it needs to use a tool, the flow looks like this:
User asks Claude something
→ Claude decides it needs a tool
→ Host tells the Client to call the tool
→ Client sends the request to your MCP Server
→ Your server runs the function and returns the result
→ Result goes back to Claude as context
→ Claude responds to the user with that context
The key thing: your server doesn't call Claude. Claude calls your server. Your server is passive — it waits, responds, and returns data. Claude decides when and whether to use it.
How they communicate
MCP uses JSON-RPC 2.0 — a simple request/response format over either:
- stdio (standard input/output) — for local servers running on your machine. This is what you'll use. Claude Desktop starts your server as a subprocess and talks to it over stdin/stdout.
- Streamable HTTP — for remote servers. More complex, out of scope for this guide.
One gotcha worth knowing now: never use console.log() in a stdio MCP server. stdout is the communication channel. If you write to it, you'll corrupt the JSON-RPC messages and break the connection. Use console.error() instead — that goes to stderr, which is separate.
// ❌ Breaks your server silently
console.log("Server started");
// ✅ Safe
console.error("Server started");
Write this on a sticky note. You'll thank yourself later.
Tools in detail
When your server registers a tool, it tells Claude three things:
- Name — what the tool is called (
read_file,fetch_url,query_db) - Description — a natural language description Claude uses to decide when to call it. This matters more than you'd expect — write it well.
- Input schema — what parameters the tool accepts, defined as JSON Schema (or Zod in TypeScript)
Claude reads these descriptions and decides on its own when a tool is relevant to the user's request. The better your description, the better Claude's judgment about when to use it.