How MCP Servers Exchange Tools With Claude

HubAI Asia
HubAI AsiaCompare & Review the Best AI Tools

Claude doesn’t actually call your tools directly.

When you type a message in Claude Desktop and it responds by reading a file, querying a database, or hitting an API, there’s an entire protocol running behind the scenes that brokers every single tool call. That protocol is the Model Context Protocol (MCP), and understanding how it works is the difference between guessing why a tool failed and knowing exactly which step broke.

Key Facts Most People Don’t Know

  • MCP protocol uses JSON-RPC 2.0 specification with exactly 3 message types: requests with sequential integer IDs, responses matching those IDs, and notifications with no ID field
  • The tools/list method returns a maximum of 1000 tool definitions per response, with each tool schema limited to 32KB of JSON to prevent context window overflow
  • MCP servers run as separate stdio processes where Claude Desktop spawns them using Node.js child_process.spawn() with stdin/stdout pipes, not HTTP endpoints

This article walks through the complete 8-step lifecycle — from the moment Claude Desktop launches an MCP server to the point where a tool result is injected back into the conversation. No abstractions, no hand-waving. Just the actual protocol mechanics.

Step 1: Server Discovery and Process Spawning

Everything starts with a single config file: ~/.claude/claude_desktop_config.json. When Claude Desktop starts, it reads this file and finds an mcpServers object that lists every server it should launch. Each entry contains a command (like npx or python) and an args array with the package name and any flags.

Claude Desktop spawns each MCP server as a separate child process using Node.js child_process.spawn(). This isn’t HTTP — there’s no web server spinning up. The transport is stdio: Claude writes JSON-RPC messages to the child’s stdin, and reads responses from its stdout. Stderr is captured for debug logging but ignored by the protocol.

This design is deliberate. Stdio transport means zero network configuration, zero port conflicts, and zero CORS issues. Each server lives in its own process boundary, so a crash in one MCP server doesn’t take down Claude or any other server.

Step 2: The Initialize Handshake

Once a server process is spawned, Claude’s MCP client sends an initialize request. This is JSON-RPC 2.0 message with id: 1 (the first request in the session). The payload contains three critical fields:

  • protocolVersion: Set to "2024-11-05" — this is the MCP specification version the client supports. If the server doesn’t support this version, the handshake fails immediately.
  • clientInfo: An object with name and version identifying the client application (e.g., {"name": "claude-desktop", "version": "1.2.3"}).
  • capabilities: Declares which features the client supports — sampling, roots, and other optional extensions.

The server must respond with its own protocolVersion, serverInfo (name and version of the MCP server), and — most importantly — a capabilities object. If the server provides tools, it includes {"tools": {}} in its capabilities. Without this declaration, Claude will never ask it for tools.

“The initialize handshake requires protocolVersion field set to ‘2024-11-05’ and capabilities object declaring supported features like tools, prompts, or resources before any tool exchange occurs.”

Step 3: Session Activation

After receiving the server’s initialize response, the client sends an initialized notification. This is a JSON-RPC notification — it has a method field but no id field, which means the server is not expected to reply. It’s a one-way signal: “I’ve processed your capabilities, the session is now active, you can start receiving requests.”

Only after this notification does the client begin making actual tool requests. The three-step sequence — initialize request → initialize response → initialized notification — is a hard requirement. Skip it, and the server will reject everything.

Step 4: Tool Discovery

Immediately after session activation, the client sends a tools/list request. This is where Claude learns what the server can actually do.

The server responds with an array of tool objects. Each tool object contains:

  • name: A unique identifier for the tool (e.g., "read_file" or "query_database").
  • description: A human-readable explanation of what the tool does. Claude uses this to decide when to call the tool.
  • inputSchema: A JSON Schema Draft 7 object defining the tool’s expected arguments — types, required fields, defaults, and constraints.

There are hard limits here. The tools/list method returns a maximum of 1,000 tool definitions per response, and each tool schema is capped at 32KB of JSON. These aren’t arbitrary — they prevent a single server from flooding Claude’s context window with tool metadata, which would leave less room for actual conversation.

For most servers, tool discovery happens once at startup and the results are cached. But the protocol supports a tools/list_changed notification that servers can send to signal the client should re-fetch the tool list — useful for dynamic tools that appear or disappear at runtime.

Step 5: Claude Selects a Tool

When a user sends a message, Claude doesn’t “know” about tools the way a programmer thinks about an API. Instead, the tool schemas from Step 4 are injected into Claude’s system context as structured metadata. When Claude processes the user’s message, it can choose to respond with a tool_use content block.

This block contains two things:

  • name: The tool to call (must match a name from tools/list).
  • arguments: A JSON object where Claude has converted the user’s natural language into exact schema-compliant values. If the tool expects an integer limit field and the user says “show me 5 results”, Claude produces {"limit": 5}.

Tool arguments are validated against JSON Schema Draft 7 specification before execution, with Claude converting natural language into exact schema-compliant JSON objects. This means Claude is responsible for type coercion — if a schema expects a number and Claude sends a string, the MCP server will reject the call.

Each tool_use block also gets a unique tool_use_id — a random string like "toolu_01A09q90qw90lq91784". This ID is critical for correlating results later.

Step 6: The tools/call Request

Claude’s response doesn’t go directly to the MCP server. The Claude Desktop client intercepts the tool_use content block, extracts the tool name and arguments, and constructs a proper tools/call JSON-RPC request. This request gets a new sequential JSON-RPC ID (not the tool_use_id — that’s for conversation tracking, not protocol tracking).

The request payload looks like this:

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/home/user/report.csv"
    }
  }
}

The server receives this over stdin, parses the JSON-RPC envelope, and routes it to the tool handler registered for "read_file".

Step 7: Server Execution and Response

Before executing any tool logic, the server validates the incoming arguments against the inputSchema it declared in tools/list. If the arguments don’t match — wrong types, missing required fields, values outside enum ranges — the server returns a JSON-RPC error response immediately, without ever running the tool.

If validation passes, the server executes the tool. This could be anything: reading a file from disk, running a SQL query, making an HTTP request to an external API, or computing something locally. The server is just a regular program at this point — it has full access to whatever the host OS allows.

The response comes back as a JSON-RPC response with a content array. Each item in the array has a type (currently "text" or "image") and the actual data. Text content is UTF-8 strings; image content is base64-encoded with a MIME type.

{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "name,count
Alpha,42
Beta,17"
      }
    ]
  }
}

If the tool fails, the server returns an isError: true flag in the result, with the error message in the content array. This distinction matters — a JSON-RPC error means the protocol itself failed (bad request, unknown method), while isError: true means the tool ran but the underlying operation failed.

Step 8: Result Injection and Final Response

The client takes the tool result and packages it into a tool_result content block for the Anthropic API. This block carries the same tool_use_id from Step 5, which tells Claude exactly which tool call this result corresponds to — essential when multiple tools are invoked in a single turn.

The tool_result is appended to the conversation as a new message with role: "user" (from the API’s perspective, tool results are user-turn content). Claude then processes this result and generates its final response — either calling more tools, or producing a text answer for the user.

This loop can repeat multiple times in a single conversation turn. Claude might call a tool, read the result, call another tool based on what it learned, and only then produce a final answer. The Anthropic API supports this natively — the conversation accumulates alternating tool_use and tool_result blocks until Claude stops requesting tools.

Why This Architecture Matters

The MCP protocol’s design choices aren’t accidental. JSON-RPC 2.0 gives you a standard with built-in error codes, request/response correlation, and batch support. Stdio transport eliminates the entire class of networking problems that plague HTTP-based plugin systems. And the strict initialize → discover → call sequence prevents race conditions where a client tries to call a tool before the server is ready.

The 32KB per-tool schema limit and 1,000 tool cap are practical constraints that keep the system honest — if you need more than that, you probably need to redesign your tool surface, not fight the protocol.

Understanding this pipeline also makes debugging dramatically easier. When a tool call fails, you can check: Did the server spawn? (Step 1.) Did it respond to initialize? (Step 2.) Did it declare tool capabilities? (Step 3.) Did tools/list return the expected schema? (Step 4.) Did Claude generate a tool_use block? (Step 5.) Did the client send tools/call? (Step 6.) Did the server validate and execute? (Step 7.) Did the result make it back? (Step 8.) Each step has a clear failure mode and a clear fix.

But what happens when two MCP servers expose tools with identical names?

💡 Sponsored: Need fast hosting for WordPress, Node.js, or Python? Try Hostinger → (Affiliate link — we may earn a commission)

📬 Get AI Tool Reviews in Your Inbox

Weekly digest of the best new AI tools. No spam, unsubscribe anytime.

🎁

Built by us: Exit Pop Pro

Turn your WordPress visitors into email subscribers with an exit-intent popup that gives away a free PDF. $29 one-time — no monthly fees, no SaaS lock-in.

Get it →
📺 YouTube📘 Facebook