Lesson 02 The Protocol: Clients, Servers, Transports
Theme:
Lesson 02 · Slide 1

Lesson 02 — The Protocol

JSON-RPC, transports, and the connection lifecycle.

MCP Servers Module 1

Lesson 02 · Slide 2

JSON-RPC in 30 seconds

MCP is JSON-RPC 2.0 underneath. Every request is a JSON object with three load-bearing fields:

{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}
Lesson 02 · Slide 3

MCP request shapes

The handful of methods you'll see on the wire every day:

MethodParams shapeWho calls it
initialize{protocolVersion, capabilities, clientInfo}client → server (first message)
tools/list{}client → server
tools/call{name, arguments}client → server
resources/list{}client → server
resources/read{uri}client → server
prompts/list{}client → server
prompts/get{name, arguments}client → server
Lesson 02 · Slide 4

MCP response shapes

Every response carries the same id the request used. It is either a result or an error — never both.

{"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}
{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"invalid params"}}
Lesson 02 · Slide 5

Notifications: fire-and-forget

A notification is a JSON-RPC message without an id. The peer must not reply.

{"jsonrpc":"2.0","method":"notifications/initialized"}
Notes: If you reply to a notification, that is a protocol error — not a soft warning.
Lesson 02 · Slide 6

Connection lifecycle

Every MCP session walks through four phases, in order:

Connect spawn / dial Initialize capability swap Operate list / call / sample Close graceful or abort client → server: list / call server → client: sampling / elicitation
Lesson 02 · Slide 7

Capability negotiation

The first round-trip of every session is initialize. Client and server each declare which optional features they support; everything that follows is constrained by that intersection.

// client → server
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{
  "protocolVersion":"2026-05-01",
  "clientInfo":{"name":"claude-desktop","version":"1.4.0"},
  "capabilities":{"roots":{"listChanged":true},"sampling":{}}
}}
// server → client
{"jsonrpc":"2.0","id":0,"result":{
  "protocolVersion":"2026-05-01",
  "serverInfo":{"name":"pkb","version":"0.1.0"},
  "capabilities":{"tools":{"listChanged":true},"resources":{},"prompts":{}}
}}
Lesson 02 · Slide 8

Transport 1: stdio

The server is a subprocess of the client. The client spawns it, writes JSON-RPC requests to the server's stdin, and reads responses from its stdout. stderr is for logs.

The client owns the lifecycle: when the client exits, the OS reaps the server. There is no networking, no auth, no port — the security boundary is the process boundary.

Default choice for local dev and per-user installations like Claude Desktop.

Newline-delimited JSON-RPC on stdio

→ {"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}\n
← {"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}\n
→ {"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"search","arguments":{"q":"mcp"}}}\n
← {"jsonrpc":"2.0","id":2,"result":{...}}\n
Lesson 02 · Slide 9

Transport 2: HTTP + SSE (legacy)

The original remote transport. The client opens a long-lived Server-Sent Events stream against one endpoint to receive messages, and POSTs requests to a separate endpoint. Two URLs, two connections, kept in sync by a session id.

Lesson 02 · Slide 10

Transport 3: Streamable HTTP

The 2025+ standard for remote MCP servers. One HTTP endpoint handles everything: regular request/response over POST, and an optional SSE upgrade on the same URL when the server needs to stream. Simpler to deploy, simpler to authenticate, friendlier to serverless and load balancers.

If you are writing a new remote server in 2026, this is the transport you ship.

Lesson 02 · Slide 11

When to pick which

Use caseTransportWhy
Local dev / Claude Desktopstdioclient owns lifecycle, no networking
One server, many users, intranetStreamable HTTPclean URL, easy auth header
Legacy clients only speak SSEHTTP + SSEback-compat
Lesson 02 · Slide 12

Errors and retries

MCP errors are JSON-RPC errors with structured codes — not free-form text. That structure is the contract: the client can decide its retry policy by looking at the code alone, without parsing English.

The server's job is determinism: given the same call, return the same error code. Don't retry on the server side — surface the failure and let the client choose. If a tool is idempotent, say so in its description; the client may then retry safely on transport-level failures.

Lesson 02 · Slide 13

Inspecting traffic with the MCP Inspector

The @modelcontextprotocol/inspector is the protocol-level debugger. Point it at any MCP server (stdio or HTTP), and it gives you a UI to call tools, read resources, and watch the wire:

npx @modelcontextprotocol/inspector \
  npx -y @modelcontextprotocol/server-filesystem /tmp/mcp-demo
Lesson 02 · Slide 14

Recap

Coming up: Lesson 03 — picking a Python SDK and scaffolding the PKB project.
1 / ?