Unpacking the MCP Base Protocol
Hi there! I'm Shrijith Venkatrama, founder of Hexmos. Right now, I’m building LiveAPI, a tool that makes generating API docs from your code ridiculously easy. Let’s dig into the Model Context Protocol (MCP)—specifically its base protocol layer. If you’re crafting client-server apps and want a reliable, flexible communication system, this is your ticket. The MCP base protocol, rooted in JSON-RPC 2.0, is the core of it all. It’s straightforward but packed with power, and I'll walk you through it with plenty of details and examples. Buckle up—this is going to be an interesting exploration! What’s the MCP Base Protocol All About? The MCP base protocol is the foundation of the Model Context Protocol, last updated on March 26, 2025. *It’s the essential layer that *every MCP implementation must support, defining how clients and servers exchange messages using JSON-RPC 2.0. ** As of April 07, 2025, this is the latest spec, and it’s designed to be modular—pair it with lifecycle management (we’ll get there), and add only the extras your app needs, like server tools or client sampling. Key Point: It’s all about separation of concerns. The base protocol handles core messaging, leaving room for optional features to slot in as needed. Think of it as the minimal, must-have glue for client-server chatter. Core Message Types: The Building Blocks MCP leans on three message types, all following JSON-RPC 2.0 rules. Let’s break them down with examples you can copy-paste and tweak. Requests: Asking for Action A request kicks off an operation. Here’s a client asking a server to process some data: { "jsonrpc": "2.0", "id": "101", "method": "processData", "params": { "input": "Hello, MCP!", "priority": 1 } } id: Unique string or number (e.g., "101"). Null isn’t allowed, and you can’t reuse IDs in a session. method: The action, like "processData". params: Optional key-value pairs for the method. Example Use: A client might send this to a server to analyze text. The server knows it’s request "101" and can reply accordingly. Responses: Delivering Results or Errors The server replies with a response—success or failure. Success looks like this: { "jsonrpc": "2.0", "id": "101", "result": { "output": "Hello, MCP! Processed.", "status": "complete" } } Failure might be: { "jsonrpc": "2.0", "id": "101", "error": { "code": -32000, "message": "Invalid input format", "data": { "details": "Input must be UTF-8" } } } id: Matches the request’s ID. result or error: One’s present, never both. Errors need a numeric code and a message. Example Use: The server confirms it processed "101" or flags an issue. The client can act based on the outcome. Notifications: One-Way Updates Notifications are fire-and-forget messages—no response needed: { "jsonrpc": "2.0", "method": "logStatus", "params": { "event": "Server started", "timestamp": "2025-04-07T10:00:00Z" } } No id—it’s not tracked. Used for updates like logs or status pings. Example Use: A server might send this to log its startup. The client sees it but doesn’t reply. Batching: Grouping Messages for Efficiency MCP lets you batch multiple messages into one array. Here’s a client sending a request and a notification together: [ { "jsonrpc": "2.0", "id": "102", "method": "fetchData", "params": { "query": "recent_logs" } }, { "jsonrpc": "2.0", "method": "logEvent", "params": { "event": "Batch sent" } } ] Server Requirement: Must handle batches if received. Client Option: Sending batches is optional but supported. Example Use: Cuts down network trips. The server processes "fetchData" and logs the event in one go. Transports: How Messages Travel MCP defines two standard transports—stdio and Streamable HTTP—plus room for custom ones. Let’s explore with examples. Stdio Transport: Local Simplicity The client launches the server as a subprocess, using stdin/stdout for messaging: Client → Server: Writes to stdin. Server → Client: Writes to stdout. Logs: Server can write to stderr. Direction Channel Example Content Client → Server stdin {"jsonrpc":"2.0","id":"1","method":"ping"}\n Server → Client stdout {"jsonrpc":"2.0","id":"1","result":{}}\n Server → Client stderr Log: Ping received Messages end with newlines (\n). No embedded newlines allowed. Example Use: A CLI tool spawns an MCP server, sends a ping via stdin, and reads the response from stdout. Streamable HTTP Transport: Network Power For networked setups, Streamable HTTP uses HTTP POST and optional Server-Sent Events (SSE): Client POST: Sends a message to /mcp. Server Response: JSON or an SSE stream. Example POST: POST /mcp HTTP/1.1 Host: example.com Accept: application/json, text/event-stream Content-Type: application/json

Hi there! I'm Shrijith Venkatrama, founder of Hexmos. Right now, I’m building LiveAPI, a tool that makes generating API docs from your code ridiculously easy.
Let’s dig into the Model Context Protocol (MCP)—specifically its base protocol layer.
If you’re crafting client-server apps and want a reliable, flexible communication system, this is your ticket.
The MCP base protocol, rooted in JSON-RPC 2.0, is the core of it all.
It’s straightforward but packed with power, and I'll walk you through it with plenty of details and examples.
Buckle up—this is going to be an interesting exploration!
What’s the MCP Base Protocol All About?
The MCP base protocol is the foundation of the Model Context Protocol, last updated on March 26, 2025.
*It’s the essential layer that *every MCP implementation must support, defining how clients and servers exchange messages using JSON-RPC 2.0. **
As of April 07, 2025, this is the latest spec, and it’s designed to be modular—pair it with lifecycle management (we’ll get there), and add only the extras your app needs, like server tools or client sampling.
Key Point: It’s all about separation of concerns. The base protocol handles core messaging, leaving room for optional features to slot in as needed.
Think of it as the minimal, must-have glue for client-server chatter.
Core Message Types: The Building Blocks
MCP leans on three message types, all following JSON-RPC 2.0 rules.
Let’s break them down with examples you can copy-paste and tweak.
Requests: Asking for Action
A request kicks off an operation. Here’s a client asking a server to process some data:
{
"jsonrpc": "2.0",
"id": "101",
"method": "processData",
"params": {
"input": "Hello, MCP!",
"priority": 1
}
}
-
id: Unique string or number (e.g.,
"101"
). Null isn’t allowed, and you can’t reuse IDs in a session. -
method: The action, like
"processData"
. - params: Optional key-value pairs for the method.
Example Use: A client might send this to a server to analyze text. The server knows it’s request "101"
and can reply accordingly.
Responses: Delivering Results or Errors
The server replies with a response—success or failure. Success looks like this:
{
"jsonrpc": "2.0",
"id": "101",
"result": {
"output": "Hello, MCP! Processed.",
"status": "complete"
}
}
Failure might be:
{
"jsonrpc": "2.0",
"id": "101",
"error": {
"code": -32000,
"message": "Invalid input format",
"data": {
"details": "Input must be UTF-8"
}
}
}
- id: Matches the request’s ID.
- result or error: One’s present, never both. Errors need a numeric code and a message.
Example Use: The server confirms it processed "101"
or flags an issue. The client can act based on the outcome.
Notifications: One-Way Updates
Notifications are fire-and-forget messages—no response needed:
{
"jsonrpc": "2.0",
"method": "logStatus",
"params": {
"event": "Server started",
"timestamp": "2025-04-07T10:00:00Z"
}
}
- No id—it’s not tracked.
- Used for updates like logs or status pings.
Example Use: A server might send this to log its startup. The client sees it but doesn’t reply.
Batching: Grouping Messages for Efficiency
MCP lets you batch multiple messages into one array. Here’s a client sending a request and a notification together:
[
{
"jsonrpc": "2.0",
"id": "102",
"method": "fetchData",
"params": { "query": "recent_logs" }
},
{
"jsonrpc": "2.0",
"method": "logEvent",
"params": { "event": "Batch sent" }
}
]
- Server Requirement: Must handle batches if received.
- Client Option: Sending batches is optional but supported.
Example Use: Cuts down network trips. The server processes "fetchData"
and logs the event in one go.
Transports: How Messages Travel
MCP defines two standard transports—stdio and Streamable HTTP—plus room for custom ones. Let’s explore with examples.
Stdio Transport: Local Simplicity
The client launches the server as a subprocess, using stdin/stdout for messaging:
- Client → Server: Writes to stdin.
- Server → Client: Writes to stdout.
- Logs: Server can write to stderr.
Direction | Channel | Example Content |
---|---|---|
Client → Server | stdin | {"jsonrpc":"2.0","id":"1","method":"ping"}\n |
Server → Client | stdout | {"jsonrpc":"2.0","id":"1","result":{}}\n |
Server → Client | stderr | Log: Ping received |
- Messages end with newlines (
\n
). - No embedded newlines allowed.
Example Use: A CLI tool spawns an MCP server, sends a ping via stdin, and reads the response from stdout.
Streamable HTTP Transport: Network Power
For networked setups, Streamable HTTP uses HTTP POST and optional Server-Sent Events (SSE):
-
Client POST: Sends a message to
/mcp
. - Server Response: JSON or an SSE stream.
Example POST:
POST /mcp HTTP/1.1
Host: example.com
Accept: application/json, text/event-stream
Content-Type: application/json
{"jsonrpc":"2.0","id":"103","method":"streamData"}
Server might reply with SSE:
HTTP/1.1 200 OK
Content-Type: text/event-stream
data: {"jsonrpc":"2.0","id":"103","result":{"chunk":1}}
data: {"jsonrpc":"2.0","id":"103","result":{"chunk":2}}
- Accept Header: Signals client supports JSON or SSE.
Example Use: A web app streams real-time data from the server over SSE, keeping the UI updated.
Spec Link: MCP Transports
Lifecycle Management: Start, Run, Stop
MCP connections follow a clear lifecycle, tied to the base protocol:
-
Initialization:
- Client sends
initialize
:
{ "jsonrpc": "2.0", "id": "1", "method": "initialize", "params": { "protocolVersion": "2025-03-26", "capabilities": { "sampling": {} } } }
- Client sends
-
Server responds:
{ "jsonrpc": "2.0", "id": "1", "result": { "protocolVersion": "2025-03-26", "capabilities": { "tools": {} } } }
-
Client confirms with
initialized
:
{ "jsonrpc": "2.0", "method": "notifications/initialized" }
- Operation: Normal messaging flows.
- Shutdown: Client closes stdin (stdio) or HTTP connection ends.
Example Use: Ensures both sides sync on version and features before proceeding.
Spec Link: MCP Lifecycle Management
Authorization: Locking It Down
MCP’s optional auth framework shines with HTTP:
- OAuth 2.1: Supports flows like Authorization Code.
-
Header:
Authorization: Bearer
.
Example request with auth:
POST /mcp HTTP/1.1
Host: example.com
Authorization: Bearer abc123
Content-Type: application/json
{"jsonrpc":"2.0","id":"104","method":"secureTask"}
- Stdio skips this, using environment creds.
- Custom auth? Go for it! Join the GitHub Discussions to brainstorm.
Example Use: A client accesses a restricted server endpoint with a token.
Spec Link: MCP Authorization
Schema: The Spec’s Source
The protocol’s defined in a TypeScript schema—the ultimate reference. A JSON Schema is auto-generated for tools. It’s precise, ensuring your implementation matches the spec.
Spec Link: MCP Schema
Why You’ll Love This
The MCP base protocol is developer-friendly:
- Lightweight: JSON-RPC 2.0 keeps it simple.
- Flexible: Stdio or HTTP, your choice.
- Modular: Only use what you need.
Try it out! Spin up a server with the official examples, send some requests, and see it in action.
What’s your next project that could use this? Share it in the comments!