Courses/Introduction to Model Context Protocol (MCP)/The Data Layer: JSON-RPC and Capabilities
MCP FoundationsLesson 3 of 21

The Data Layer: JSON-RPC and Capabilities

Two Layers, One Protocol

MCP is built from two layers. The data layer is the inner layer — it defines the actual messages, what they mean, and the lifecycle of a connection. The transport layer is the outer layer — it defines how those messages physically travel between client and server. Separating them is what makes MCP transport-agnostic: the exact same data-layer messages work whether you're talking over stdio on your laptop or HTTP across the internet.

LayerResponsibilityIncludes
Data layer (inner)WHAT is exchanged and its meaningJSON-RPC 2.0 messages, lifecycle, capabilities, the primitives (tools/resources/prompts), notifications
Transport layer (outer)HOW messages travelConnection setup, message framing, authentication; stdio or Streamable HTTP

The data layer is the same across every transport — that is the source of MCP's flexibility.

Built on JSON-RPC 2.0

The data layer uses JSON-RPC 2.0 as its message format — a small, well-established standard. There are just two shapes you need to know. A request has an id, a method, and optional params, and it expects a response. A notification has a method but no id, and it expects no response (it's fire-and-forget).

jsonThe three JSON-RPC message shapes used throughout MCP.
// Request — expects a response (note the "id")
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}

// Response — correlated back by the same "id"
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": { "tools": [ /* ... */ ] }
}

// Notification — no "id", no response expected
{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed"
}
ℹ️

Why JSON-RPC?

Using an existing, language-neutral standard means SDKs in any language (Python, TypeScript, Go, Rust, …) all speak the same wire format. You rarely write these messages by hand — the SDK does — but recognizing them is invaluable when debugging.

The Initialization Handshake

MCP is a stateful protocol, so every connection begins with a lifecycle handshake. The client sends an initialize request that includes the protocol version it speaks and the capabilities it supports. The server replies with its own protocol version and capabilities. Then the client sends a notifications/initialized notification to signal it's ready. Only after this handshake does normal operation begin.

MCP ClientMCP Server1. initialize (version + capabilities)2. result (version + capabilities)3. notifications/initializedconnection ready — normal operation begins

The three-step initialization handshake that opens every MCP connection.

Capability negotiation is the heart of this exchange. Each side declares only what it supports — for example, a server might declare it offers tools and can send list-changed notifications, while a client declares it supports elicitation. Neither side then attempts operations the other can't handle. This is why MCP can evolve: older and newer implementations negotiate a common subset and still work together.

Notifications Keep Things Live

Because connections are stateful, servers can push notifications when something changes — without being asked. The classic example is notifications/tools/list_changed: when a server's available tools change, it tells connected clients, which then re-fetch the list. This keeps the model's view of available capabilities current in real time, rather than relying on polling.

⚠️

Notifications are capability-gated

A server only sends list_changed notifications if it declared that capability during initialization (e.g. "tools": {"listChanged": true}). This is capability negotiation in action — don't expect a notification a peer never advertised.

Next up

We've covered the data layer (the messages). Next: the transport layer — exactly how those JSON-RPC messages travel, via stdio for local servers and Streamable HTTP for remote ones.

Key Takeaways

  • MCP has two layers: the data layer (what is exchanged + meaning) and the transport layer (how messages travel).
  • Separating them makes MCP transport-agnostic — identical data-layer messages work over stdio or HTTP.
  • The data layer uses JSON-RPC 2.0, with three message shapes: request (has id, expects response), response (same id), and notification (no id, no response).
  • Every connection opens with an initialize handshake: client and server exchange protocol versions and capabilities, then the client sends notifications/initialized.
  • Capability negotiation lets each side declare only what it supports, so old and new implementations interoperate on a common subset.
  • Servers can push notifications (e.g. tools/list_changed) to keep clients current — but only for capabilities they advertised.

Check Your Understanding

Test what you learned in this lesson.

Q1.What is the difference between the data layer and the transport layer in MCP?

Q2.In JSON-RPC 2.0 as used by MCP, what distinguishes a notification from a request?

Q3.What happens during the MCP initialization handshake?

Q4.Why can a server send a tools/list_changed notification to a client?

Practice This Lesson