Effective communication between the distinct components of the Model Context Protocol requires a strict, agreed-upon language. While the transport layer handles the movement of bits across a socket or standard input stream, the application layer relies on a structured format to interpret intent. MCP utilizes JSON-RPC 2.0, a stateless and lightweight remote procedure call (RPC) protocol encoded in JSON.This format defines how a Client requests data, how a Server responds, and how errors are reported. Because JSON-RPC is transport-agnostic, the message structure remains identical whether you are communicating over HTTP with Server-Sent Events or piping data through local standard I/O. Understanding this structure is necessary for implementing any MCP server, as standard debugging tools and inspectors visualize traffic in this raw format.The JSON-RPC 2.0 StandardJSON-RPC functions by exchanging data objects. Every message sent in an MCP connection is a JSON object containing specific fields that determine its purpose. There are three primary types of messages: Requests, Responses, and Notifications.All messages must adhere to the version 2.0 specification. This is enforced by the inclusion of a specific member in every object:"jsonrpc": "2.0"If this field is missing or contains a different version number, the receiver will typically reject the message. This version tag ensures future compatibility if the protocol evolves.Anatomy of a RequestA Request is a call initiated by a Client (or occasionally a Server in bi-directional sampling) to invoke a specific method on the remote party. A valid request object must contain four properties:jsonrpc: Must be exactly "2.0".method: A string containing the name of the method to be invoked. MCP defines standard methods such as resources/list or tools/call.params: A structured value (object or array) that holds the parameter values to be used during the invocation of the method. This may be omitted if the method takes no arguments.id: An identifier established by the sender. This can be a string, a number, or null.The id member is functionally significant. Since MCP connections are often asynchronous, multiple requests may be in flight simultaneously. The id allows the sender to match an incoming response back to the original request that triggered it.Consider a scenario where an MCP Client asks a Server to execute a tool named "get-weather". The payload would look like this:{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "get-weather", "arguments": { "city": "San Francisco", "unit": "celsius" } }, "id": 1 }In this structure, the method directs the server to the tool execution logic, and params provides the necessary arguments defined by that tool's schema.digraph G { bgcolor="transparent"; rankdir=TB; node [fontname="Helvetica", fontsize=12, style=filled, shape=box]; edge [fontname="Helvetica", fontsize=10, color="#868e96"]; subgraph cluster_0 { label=""; style=invis; Client [label="MCP Client", fillcolor="#a5d8ff", color="#1c7ed6", penwidth=2]; Server [label="MCP Server", fillcolor="#b2f2bb", color="#37b24d", penwidth=2]; } Client -> Server [label="Request (id: 1)", color="#1c7ed6", penwidth=1.5]; Server -> Client [label="Response (id: 1)", color="#37b24d", penwidth=1.5]; Server -> Client [label="Notification (no id)", style=dashed, color="#fd7e14"]; {rank=same; Client; Server} }The exchange pattern relies on the identifier to maintain state across the stateless JSON transport. Notifications allow for unidirectional data flow without blocking.Anatomy of a ResponseWhen a Server receives a Request, it must reply with a Response object. The Response indicates whether the action was successful or if it failed. A Response object must contain the jsonrpc version and the same id as the Request it is answering.Crucially, a Response object must contain exactly one of two possible result members: result or error. They cannot both act as members of the same Response object.Successful ResponseIf the method execution completes successfully, the response object includes a result member. The value of this member is determined by the method invoked.Continuing the weather tool example, a successful response would appear as follows:{ "jsonrpc": "2.0", "result": { "content": [ { "type": "text", "text": "Current temperature in San Francisco is 18°C" } ] }, "id": 1 }The Client receives this object, notes that the id is 1, matches it to its internal tracking of the original request, and processes the result.Error ResponseIf the method fails, perhaps the tool name does not exist or the arguments were invalid, the server returns an error object instead of a result.The error object has specific required fields:code: An integer indicating the error type.message: A concise string description of the error.data (optional): Additional primitive or structured data containing detailed error information.{ "jsonrpc": "2.0", "error": { "code": -32602, "message": "Invalid params", "data": "Missing required argument: 'city'" }, "id": 1 }JSON-RPC defines a range of reserved error codes from -32768 to -32000. For example, -32700 denotes a Parse Error (invalid JSON was received), and -32601 denotes Method not found. When building your MCP server, you should map internal application exceptions to these standardized codes where appropriate, or use generic codes for implementation-specific failures.Notifications and One-Way CommunicationNot all interactions require a response. In many MCP workflows, one party needs to send information without waiting for acknowledgment. This is handled via Notifications.A Notification is structurally identical to a Request, with one distinct difference: it does not include an id member. When a receiver processes an object lacking an identifier, it treats it as a notification. The receiver executes the method but does not reply.Notifications are frequently used in MCP for logging and state updates. For instance, if a server wants to log a debug message to the client console, it sends a notifications/message:{ "jsonrpc": "2.0", "method": "notifications/message", "params": { "level": "info", "data": "Database connection established" } }Because there is no id, the server does not block waiting for the client to acknowledge receipt. This is efficient for high-volume telemetry or progress updates where dropped messages are acceptable. However, developers must be careful not to use Notifications for actions where confirmation is necessary for data integrity.Message BatchingJSON-RPC 2.0 supports batching, allowing a client to send an array of Request objects simultaneously. The server then responds with an array of Response objects. While the MCP specification relies primarily on single message interactions for simplicity in typical LLM chat loops, server implementations should be prepared to parse an array of objects if the transport layer aggregates them.When processing a batch, the server handles each request independently. The order of elements in the response array is not guaranteed to match the request array. Instead, the client must rely entirely on the id field to map responses back to their requests. If a batch contains Notifications, those items will not generate a corresponding entry in the response array.Understanding these structures provides the syntax required to write the logic layers discussed in subsequent chapters. With the topology defined and the message format clear, the next consideration is the physical transport mechanism that carries these JSON objects between processes.