
Understanding the True Nature of MCP: From the Agent Loop of Tool Use to Two-Layer Error Handling in JSON-RPC 2.0
This page has been translated by machine translation. View original
Introduction
While implementing an agent using Tool Use with the Claude API, a question suddenly came to mind.
"What exactly is MCP (Model Context Protocol)? What's the difference from Tool Use?"
As I researched, I arrived at the fact that LLMs don't even know MCP exists, and from there, a chain of questions resolved themselves — the architectural philosophy of MCP, and further, the mechanism of JSON-RPC 2.0 which serves as its communication foundation.
This article shares that journey of exploration.
The Correct Message Structure for the Tool Use Agent Loop
Starting with the basics. When implementing an agent with tool use enabled via the Claude API, you need to understand the correct design of the agent loop.
The Loop Flow
When Claude returns stop_reason: "tool_use", the developer needs to execute the tool locally and return the result to the API. The rules to follow are clear.
Send the entire conversation history including the immediately preceding assistant message, along with a new user message containing a tool_result content block with the corresponding tool_use_id.

{
"messages": [
{
"role": "user",
"content": "Check the current weather in Tokyo."
},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_xyz123",
"name": "get_weather",
"input": { "city": "Tokyo" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_xyz123",
"content": "Sunny, temperature 22 degrees"
}
]
}
]
}
Why the user Role?
You might wonder, "The assistant called the tool, so why is the result returned as user?"
In the Claude API, role represents the direction data flows.
| Role | Meaning |
|---|---|
assistant |
Data coming from the model (including tool call instructions) |
user |
Data being sent to the model (including tool execution results) |
The application code acts as the user's "proxy (agent)", playing the role of conveying facts obtained from the external world to the model.

Handling Errors
Even when tool execution fails due to a timeout or similar issue, sending the tool_result cannot be omitted. Omitting it results in an API error (400 Bad Request). On error, set is_error: true and describe the error details in content.
{
"type": "tool_result",
"tool_use_id": "toolu_01A2B3C4D5",
"content": "Error: The external API request timed out after 10 seconds.",
"is_error": true
}
This allows Claude to recognize the fact of the error and autonomously decide how to explain it to the user or whether to retry.
MCP Does Not Exist for the LLM
Here is the main topic. The Tool Use rules described above are exactly the same even when using MCP.
That's because Claude has absolutely no knowledge of MCP's existence.
The Client App as "Illusionist"
In MCP's architecture, a client app (Claude Desktop, Claude Code, a custom agent, etc.) stands in the middle and performs the following processing.
Phase 1: Consolidating the Tool List
The client app queries the connected MCP servers asking "what tools do you have?", then collects the retrieved tool definitions into a standard tools array and sends it to the Claude API.
"tools": [
{ "name": "github_create_issue", "description": "..." },
{ "name": "postgres_query", "description": "..." },
{ "name": "slack_post_message", "description": "..." }
]
As far as Claude is concerned, there is no way to distinguish whether these came from MCP servers or were locally written functions.
Phase 2: Claude Simply Calls Standard Tool Use
{
"type": "tool_use",
"name": "postgres_query",
"input": { "query": "SELECT * FROM users WHERE active = true" }
}
Claude doesn't know "how it will be executed". It simply outputs structured JSON intent and stops.
Phase 3: The Client App Determines Routing
The client app looks at the name in tool_use and decides.
- If it's a local function → execute directly
- If it's an MCP server tool → forward to the relevant server via JSON-RPC
Phase 4: Return the Result with tool_result
The result received from the MCP server is wrapped in a standard tool_result message block and returned to Claude.

MCP = HTTP for the AI Era, Microservices for LLMs
So if the LLM doesn't know about MCP, why did Anthropic create MCP?
The answer is decoupling. Just as HTTP separates web browsers from web servers, MCP separates AI clients from tools and data sources.
Structural Similarity to HTTP
| Perspective | HTTP | MCP |
|---|---|---|
| What doesn't the client need to know? | Server implementation language, DB type | Tool implementation language, authentication flow |
| What doesn't the server need to know? | Client's OS, device | Which LLM is calling |
| Common contract | HTTP methods, status codes | JSON-RPC 2.0, three primitives |
The Microservices Analogy
In traditional system design, a large application is split into microservices (authentication service, payment service, shipping service, etc.), and the main app uses the necessary features via network calls.
MCP is doing exactly the same thing in the world of AI. The only difference is that the "boss" calling the services is not a hardcoded application but an LLM.
MCP is similar to microservices. If microservices are for applications, MCP is for LLMs.

The Problem Before MCP
In the days before MCP, if five different AI coding assistants (Cursor, Claude Desktop, Continue.dev, Aider, an in-house agent, etc.) each wanted to implement GitHub integration, each one had to reinvent the same wheel five times. And each implementation was incompatible with the others.
With MCP, if one developer creates an excellent GitHub MCP server, all AI client apps can immediately use it.
The Reality of the Ecosystem
However, MCP cannot prevent "100 developers creating 100 MCP servers for the same app". It's the same as having 50 packages on npm for string padding.
What matters is that where effort is directed has changed. Instead of "each app developer rewriting the same basic tools privately", it has become "the community competing to create the best public MCP servers".
JSON-RPC 2.0: Schema Enforcement for Communication Protocols
What underpins MCP's communication foundation is JSON-RPC 2.0.
The Difference Between JSON and JSON-RPC 2.0
JSON is a data format; JSON-RPC 2.0 is a strict contract. If JSON is an "alphabet", JSON-RPC 2.0 is like a "legal contract" written in that alphabet.
JSON only defines syntax; there are no rules about meaning.
{ "coffee": "iced", "temperature": 2, "is_good": true }
JSON-RPC 2.0, on the other hand, enforces a strict structure for remote procedure calls (RPC).
The Request Contract
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": { "name": "fetch_file", "path": "./src/main.ts" },
"id": "request-abc-123"
}
Three required keys: jsonrpc (version), method (function name to execute), id (request identifier). params (arguments) is optional, but is typically included in MCP.
The Response Contract
{
"jsonrpc": "2.0",
"result": { "content": "console.log('hello world');" },
"id": "request-abc-123"
}
The Error Contract
{
"jsonrpc": "2.0",
"error": { "code": -32602, "message": "File not found at specified path" },
"id": "request-abc-123"
}
Automatic Rejection of Schema Violations
JSON-RPC 2.0 automatically detects and rejects schema violations.
| Type of Violation | Error Code | Condition |
|---|---|---|
| Parse error | -32700 |
JSON syntax error (missing closing bracket, etc.) |
| Invalid Request | -32600 |
Missing required key (jsonrpc or method absent) |
| Method not found | -32601 |
Calling a non-existent method |
| Invalid params | -32602 |
Parameter type or structure is invalid |
Why It Matters for LLMs
Because LLMs dynamically generate tool parameters, tool name hallucinations or missing parameters can occur. With JSON-RPC 2.0's schema enforcement, MCP servers don't crash and return standardized error codes. The client app feeds those errors back to Claude, and Claude self-corrects and re-requests — creating a robust loop.
The Two-Layer Structure of HTTP Status Codes and JSON-RPC Error Codes
JSON-RPC 2.0 coexists with HTTP status codes (400, 500, etc.) rather than replacing them.
The Two-Layer Security Model
An armored truck analogy makes this easy to understand.
- HTTP status codes → Manage the truck itself (did delivery physically succeed?)
- JSON-RPC error codes → Manage the safe inside the truck (did the contents pass inspection?)

Pattern 1: Network Success + Tool Execution Success
HTTP/1.1 200 OK
{
"jsonrpc": "2.0",
"result": { "status": "Slack message sent successfully!" },
"id": "mcp-msg-99"
}
Pattern 2: Network Success + Tool Execution Failure
HTTP returns 200 OK, yet internally it's an error. This is the case where network delivery succeeded, but the contents failed inspection.
HTTP/1.1 200 OK
{
"jsonrpc": "2.0",
"error": { "code": -32602, "message": "Invalid params: 'path' must be an absolute string." },
"id": "claude-bad-request-1"
}
Pattern 3: Network Itself Fails
When the MCP server crashes, it can't even return a JSON-RPC response.
HTTP/1.1 504 Gateway Timeout

| Layer | What It Means | Example |
|---|---|---|
| HTTP (4xx, 5xx) | The pipe is broken | Server unreachable, timeout |
| JSON-RPC (-32xxx) | The pipe is fine, but the data sent failed inspection | Method absent, invalid parameters |
Summary
Here is a summary of the understanding gained through this exploration.
| Concept | Essence |
|---|---|
| Tool Use agent loop | A strict alternating pattern of assistant: tool_use → user: tool_result. Complete preservation of conversation history is mandatory |
| The relationship between MCP and LLMs | The LLM doesn't know about MCP. The client app decides the routing |
| The value of MCP | HTTP for the AI era. A decoupling standard regardless of platform or language |
| JSON-RPC 2.0 | A schema enforcement layer for communication protocols. Coexists on a separate layer from HTTP status codes |
As a practical judgment criterion, thinking of MCP as "microservices for LLMs" is the most useful framing. If traditional microservices are for applications, MCP is for LLMs. JSON-RPC 2.0 as a contract is layered on top of HTTP, composed of three primitives (Prompts, Resources, Tools). With this understanding, you should rarely find yourself lost when making MCP design decisions.