Skip to content

09. MCP Communication

MCP Communication follows a structured protocol where clients and servers exchange JSON-RPC messages for initialization, capability discovery, tool invocation, resource access, and real-time notifications.

Every interaction in MCP — from connecting to a server to calling a tool to receiving updates — follows a defined message flow. Understanding this communication pattern is essential for building robust MCP applications.

flowchart LR
CLIENT["📡 MCP Client"] -->|"JSON-RPC Request"| SERVER["🗄️ MCP Server"]
SERVER -->|"JSON-RPC Response"| CLIENT
SERVER -->|"JSON-RPC Notification"| CLIENT
subgraph REQUESTS["Request Types"]
INIT["initialize"]
TLIST["tools/list"]
TCALL["tools/call"]
RLIST["resources/list"]
RREAD["resources/read"]
PLIST["prompts/list"]
PGET["prompts/get"]
end
subgraph NOTIFICATIONS["Notification Types"]
NRES["resources/updated"]
NTOOL["tools/changed"]
NCAP["capabilities/updated"]
end
style CLIENT fill:#3b82f6,color:#fff
style SERVER fill:#22c55e,color:#fff

The Problem: Ad-Hoc Integrations Are Fragile

Section titled “The Problem: Ad-Hoc Integrations Are Fragile”

Without a standard communication protocol:

  • Every integration invents its own message format
  • Error handling is inconsistent
  • There’s no standard way to discover capabilities
  • Real-time updates require custom polling

MCP uses JSON-RPC 2.0 as its message protocol — a lightweight, standardized format for remote procedure calls. This means every MCP interaction follows the same structure, whether it’s listing tools, calling a function, or receiving a notification.


Imagine a restaurant with a standardized ordering system:

  • You place an order (Request) — “I’d like the steak, medium rare”
  • The kitchen confirms (Response) — “Your order is received, estimated 15 minutes”
  • The waiter checks on your table (Notification) — “Your steak is almost ready”

Every interaction follows the same format: request → processing → response. Even when something goes wrong (out of steak), the response format is the same — just with an error code.


MCP uses three message types:

A request expects a response:

{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}

A response to a request:

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "search",
"description": "Search the knowledge base",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string"}
},
"required": ["query"]
}
}
]
}
}

A notification does not expect a response:

{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"uri": "config://app/settings"
}
}
sequenceDiagram
participant Client as MCP Client
participant Server as MCP Server
Note over Client,Server: Request-Response Pattern
Client->>Server: Request (id: 1, method: "tools/list")
Server->>Server: Process request
Server->>Client: Response (id: 1, result: {...})
Note over Client,Server: Notification Pattern
Server->>Client: Notification (method: "notifications/resources/updated")
Note over Client: No response expected
Note over Client,Server: Error Pattern
Client->>Server: Request (id: 2, method: "tools/call", params: {name: "unknown"})
Server->>Client: Response (id: 2, error: {code: -32601, message: "Method not found"})

flowchart TD
CLIENT["Client sends request"] --> VALID{"Server validates\nJSON-RPC format?"}
VALID -->|"Invalid"| EPARSE["Error: Parse error\n(code: -32700)"]
VALID -->|"Valid"| METHOD{"Method exists\nand valid?"}
METHOD -->|"Unknown"| EMETHOD["Error: Method not found\n(code: -32601)"]
METHOD -->|"Known"| PARAMS{"Parameters\nvalid?"}
PARAMS -->|"Invalid"| EPARAMS["Error: Invalid params\n(code: -32602)"]
PARAMS -->|"Valid"| EXEC["Execute method"]
EXEC --> SUCCESS{"Successful?"}
SUCCESS -->|"Yes"| RESULT["Return result\n(id, result)"]
SUCCESS -->|"No"| EINTERNAL["Error: Internal error\n(code: -32603)"]
style CLIENT fill:#3b82f6,color:#fff
style RESULT fill:#22c55e,color:#fff
style EPARSE fill:#ef4444,color:#fff
style EMETHOD fill:#ef4444,color:#fff
style EPARAMS fill:#ef4444,color:#fff
style EINTERNAL fill:#ef4444,color:#fff

sequenceDiagram
participant Client as MCP Client
participant Server as MCP Server
Note over Client,Server: Phase 1: Initialization
Client->>Server: initialize (protocol_version, capabilities)
Server->>Client: initialized (protocol_version, capabilities)
Client->>Client: Check protocol compatibility
Note over Client,Server: Phase 2: Capability Discovery
Client->>Server: tools/list
Server->>Client: [Tool definitions]
Client->>Server: resources/list
Server->>Client: [Resource definitions]
Client->>Server: prompts/list
Server->>Client: [Prompt definitions]
Note over Client,Server: Phase 3: Operation
Client->>Server: tools/call (search, {query: "MCP"})
Server->>Client: [Search results]
Client->>Server: resources/read (uri: "docs://overview")
Server->>Client: [Document content]
Server->>Client: notifications/resources/updated (uri: "config://app")
Client->>Server: resources/read (uri: "config://app")
Server->>Client: [Updated config]
Note over Client,Server: Phase 4: Termination
Client->>Server: shutdown
Server->>Client: Shutdown acknowledgment
Client->>Client: Clean up resources

Communication over standard input/output:

flowchart LR
subgraph HOST["Host Process"]
CLIENT["MCP Client"]
end
subgraph SERVER_PROC["Server Process"]
SERVER["MCP Server"]
end
CLIENT -->|"stdin\n(JSON-RPC Request)"| SERVER
SERVER -->|"stdout\n(JSON-RPC Response/Notification)"| CLIENT
SERVER -->|"stderr\n(Logs, diagnostics)"| LOG["Log Capture"]
style CLIENT fill:#3b82f6,color:#fff
style SERVER fill:#22c55e,color:#fff

Messages are delimited by newlines:

--> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}
<-- {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}}}}
--> {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
<-- {"jsonrpc":"2.0","id":2,"result":{"tools":[]}}

Communication over HTTP/HTTPS:

sequenceDiagram
participant Client as MCP Client
participant Gateway as HTTP Gateway
participant Server as MCP Server
Client->>Gateway: POST /mcp (JSON-RPC Request)
Gateway->>Gateway: Authenticate, rate limit
Gateway->>Server: Forward request
Server->>Gateway: JSON-RPC Response
Gateway->>Client: HTTP 200 (JSON-RPC Response)
Client->>Gateway: POST /mcp/subscribe
Gateway->>Server: Subscribe to notifications
Server->>Gateway: notifications/resources/updated
Gateway->>Client: HTTP 200 (notification)

Standard JSON-RPC error codes:

CodeErrorMeaning
-32700Parse errorInvalid JSON
-32600Invalid requestNot a valid JSON-RPC message
-32601Method not foundUnknown method
-32602Invalid paramsArguments don’t match schema
-32603Internal errorServer-side failure
-32000 to -32099Server errorImplementation-specific errors
0+Tool errorTool-specific error codes
# Example: Error response from server
{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"tool": "search_documents",
"field": "query",
"issue": "query cannot be empty"
}
}
}

flowchart TD
CLIENT["Client sends request"] --> CHECK{"Rate limit\nexceeded?"}
CHECK -->|"No"| PROCESS["Process normally"]
CHECK -->|"Yes"| RESPOND["Respond with error\n'Too Many Requests'\nRetry-After: 30s"]
RESPOND --> WAIT["Client waits\n30 seconds"]
WAIT --> RETRY["Client retries"]
PROCESS --> DONE["Return result"]
style CLIENT fill:#3b82f6,color:#fff
style PROCESS fill:#22c55e,color:#fff
style RESPOND fill:#f59e0b,color:#fff

  1. Always use request IDs — Responses must match their requests
  2. Handle protocol version mismatch — Initialize before any other operation
  3. Implement request timeouts — Don’t hang indefinitely
  4. Log all messages — Every request, response, and notification for debugging
  5. Use notifications for non-critical updates — Don’t expect a response
  6. Implement reconnection — Handle transport failures gracefully
  7. Validate message format — Reject malformed JSON-RPC
MistakeWhy It’s Wrong
Sending requests before initializationServer rejects with “not initialized”
Ignoring protocol version compatibilityClient and server may have incompatible features
Blocking on responsesAll operations should be async
No timeout handlingRequest can hang indefinitely
Treating notifications the same as responsesNotifications have no response — don’t wait for one

Q: What protocol does MCP use for communication?

MCP uses JSON-RPC 2.0 — a lightweight, standardized remote procedure call protocol. Messages are serialized as JSON and sent over the transport layer (STDIO, HTTP, or WebSocket).

Q: What are the three types of messages in MCP?

(1) Requests — Expect a response (e.g., tools/list, tools/call), (2) Responses — Reply to a request (success result or error), (3) Notifications — No response expected (e.g., resources/updated).

Q: Explain the initialization phase and why it’s important.

Initialization is the first communication between client and server. The client sends an initialize request with its protocol version and capabilities. The server responds with its protocol version and capabilities. Both sides check version compatibility. This phase is critical because it establishes the contract for all subsequent communication — the client knows what the server can do, and both know which protocol features are supported.

Q: How does the server handle errors during tool execution?

The server returns a JSON-RPC error response with an error code and message. For standard errors (invalid params, method not found), it uses predefined codes. For tool-specific errors, it uses custom error codes and includes detailed error information in the data field. The client can then decide whether to retry, inform the agent, or escalate.

Q: Design a retry strategy for transient communication failures in MCP.

Strategy: (1) Classify errors as retryable (timeout, rate limit, connection lost) or non-retryable (invalid params, method not found), (2) Use exponential backoff: 1s, 2s, 4s, 8s, max 30s, (3) Add jitter (±500ms) to prevent thundering herd, (4) Set a max retry count (5 attempts), (5) After max retries, escalate to the agent with an error message, (6) For rate limits, use the Retry-After header if provided, (7) Log all retry attempts for monitoring.

Q: How would you implement server-side request prioritization for MCP?

Prioritization strategy: (1) Classify requests by priority (high: tool calls from user requests, medium: resource reads, low: capability listing), (2) Use a priority queue on the server with configurable concurrency per priority level, (3) High-priority requests can preempt low-priority ones, (4) Set queue timeouts — if a request waits too long, return a timeout error, (5) Monitor queue depth and latency per priority level, (6) Provide priority hints in request params as an extension.

Q: Compare MCP’s JSON-RPC communication model with gRPC for AI agent communication. What are the trade-offs?

MCP/JSON-RPC: Human-readable, easy to debug, no schema compilation needed, flexible (dynamic arguments), widely supported. gRPC: Binary format (Protobuf), strongly typed, code generation, built-in streaming, better performance. Trade-offs: MCP’s JSON-RPC is more accessible for rapid development and debugging AI agent interactions. gRPC is better for high-throughput, low-latency internal service communication. For AI agents, MCP’s flexibility (dynamic tool definitions, self-describing schemas) is more valuable than raw performance.

Q: Design a communication architecture for an MCP system that handles 10,000+ simultaneous agent connections.

Architecture: (1) Load balancer distributing connections across MCP server instances, (2) Each server instance manages a connection pool with configurable max connections, (3) Use HTTP/2 for multiplexing multiple requests over a single connection, (4) Implement connection pooling with keepalive to reduce handshake overhead, (5) Use Redis pub/sub for cross-server notification broadcasting, (6) Separate initialization (handshake) from operation (tool calls) using different server pools, (7) Implement circuit breakers to isolate failing server instances, (8) Use async I/O throughout to handle concurrent connections efficiently.


ConceptKey Point
ProtocolJSON-RPC 2.0
RequestMethod call expecting a response
ResponseResult or error matching a request
NotificationOne-way message, no response
InitializationVersion check and capability discovery
TransportSTDIO, HTTP, WebSocket
Error handlingStandard JSON-RPC error codes
Rate limitingServer can throttle requests

Previous: 08 — Prompts

Next: 10 — Transports

Related Topics: