By Sagar Shankaran, Founder of CallSphere
Explore the Model Context Protocol specification from the ground up — JSON-RPC 2.0 message format, request/response lifecycle, transport layers, and how every MCP message is structured under the hood.
Key takeaways
When you connect an AI agent to an MCP server and call a tool, several layers of messaging happen beneath the surface. Understanding these layers is the difference between debugging MCP integrations in minutes versus hours. The Model Context Protocol is built on JSON-RPC 2.0 — a stateless, lightweight remote procedure call protocol that uses JSON as its data format.
Every interaction between an MCP client (the agent runtime) and an MCP server (the tool provider) follows a strict message contract. If you have ever worked with the Language Server Protocol (LSP) in code editors, the architecture will feel familiar. MCP borrows the same JSON-RPC foundation and adapts it for AI tool calling.
JSON-RPC 2.0 defines three message types: requests, responses, and notifications. MCP uses all three.
flowchart LR
HOST(["MCP host<br/>Claude Desktop or IDE"])
CLIENT["MCP client"]
subgraph SERVERS["MCP Servers"]
S1["Filesystem server"]
S2["GitHub server"]
S3["Postgres server"]
SX["Custom tool server"]
end
LLM["LLM session"]
OUT(["Grounded action"])
HOST <--> CLIENT
CLIENT <-->|stdio or HTTP+SSE| S1
CLIENT <--> S2
CLIENT <--> S3
CLIENT <--> SX
CLIENT --> LLM --> OUT
style HOST fill:#f1f5f9,stroke:#64748b,color:#0f172a
style CLIENT fill:#4f46e5,stroke:#4338ca,color:#fff
style OUT fill:#059669,stroke:#047857,color:#fff
A request is a message from client to server (or server to client) that expects a response:
# MCP request message structure
request = {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "query_database",
"arguments": {
"sql": "SELECT * FROM users LIMIT 10"
}
}
}
The id field is critical — it correlates the request with its response. The method field identifies the MCP operation. The params field carries the operation-specific payload.
A response mirrors the request by its id:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
# Successful response
response = {
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Found 10 users in the database."
}
]
}
}
# Error response
error_response = {
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid params: unknown tool 'query_database'"
}
}
A notification is a message that does not expect a response. It has no id field:
# Notification — no id, no response expected
notification = {
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {}
}
MCP uses notifications for events like progress updates, log messages, and lifecycle signals.
Every MCP connection begins with a handshake. The client sends an initialize request, the server responds with its capabilities, and the client confirms with an initialized notification:
import json
# Step 1: Client sends initialize request
initialize_request = {
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"roots": {"listChanged": True}
},
"clientInfo": {
"name": "my-agent",
"version": "1.0.0"
}
}
}
# Step 2: Server responds with its capabilities
initialize_response = {
"jsonrpc": "2.0",
"id": 0,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {"listChanged": True},
"resources": {"subscribe": True},
"prompts": {"listChanged": True}
},
"serverInfo": {
"name": "database-server",
"version": "2.1.0"
}
}
}
# Step 3: Client sends initialized notification
initialized_notification = {
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {}
}
After this handshake, the client can call tools/list to discover available tools, resources/list to enumerate data sources, and tools/call to execute a specific tool. Each call follows the same request-response pattern.
MCP supports multiple transport mechanisms, each suited to different deployment patterns.
stdio transport communicates over standard input and output. The client spawns the server as a subprocess and writes JSON-RPC messages to its stdin, reading responses from stdout:
import subprocess
import json
# Spawn MCP server as a subprocess
process = subprocess.Popen(
["python", "my_mcp_server.py"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
def send_message(msg: dict) -> dict:
"""Send a JSON-RPC message and read the response."""
line = json.dumps(msg) + "\n"
process.stdin.write(line)
process.stdin.flush()
response_line = process.stdout.readline()
return json.loads(response_line)
result = send_message(initialize_request)
print(result)
Streamable HTTP transport uses HTTP POST for client-to-server messages and Server-Sent Events (SSE) for server-to-client streaming. This is the transport you use for remote MCP servers accessible over the network.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
Custom transports are also possible. Any bidirectional byte stream that can carry newline-delimited JSON works as an MCP transport.
MCP inherits JSON-RPC 2.0 error codes and adds its own:
# Standard JSON-RPC error codes
PARSE_ERROR = -32700 # Invalid JSON
INVALID_REQUEST = -32600 # Not a valid request object
METHOD_NOT_FOUND = -32601 # Method does not exist
INVALID_PARAMS = -32602 # Invalid method parameters
INTERNAL_ERROR = -32603 # Internal server error
When building MCP clients, always handle these error codes gracefully. A robust client retries transient errors and surfaces permanent errors to the agent with enough context for the LLM to adjust its approach.
MCP is a bidirectional protocol built on JSON-RPC 2.0, meaning both client and server can send messages. Unlike REST, MCP includes a capability negotiation handshake, standardized tool schemas, and supports streaming via notifications. REST is request-response only and lacks the built-in discovery and schema mechanisms that MCP provides.
Yes. An agent can connect to one MCP server over stdio (a local tool) and another over Streamable HTTP (a remote service). The MCP client handles transport abstraction internally — your agent code sees the same tool interface regardless of how the underlying messages are carried.
The client will receive an I/O error (broken pipe for stdio, connection reset for HTTP). Well-designed MCP clients implement timeouts and reconnection logic. The JSON-RPC id field ensures that even after reconnection, stale responses from a previous session are ignored because the ids will not match pending requests.
#MCP #JSONRPC #ProtocolDesign #AIAgents #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A founder's guide to the personal AI assistant market: best AI assistant apps, business-grade options, and how CallSphere's voice agent fits in.
A founder's guide to free AI agents, low-code AI agent builders, and how to know when you should pay for a real platform like CallSphere.
Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.
Chatbot app vs ChatGPT in 2026: a founder's clear take on the difference, when to use which, and how a real AI chatbot app development works.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
How to design a multi-agent system using MCP for tools and A2A for cross-vendor coordination, with a CallSphere voice agent as a participating node.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco