Lesson 8 - Step 2 of 6

Server-Sent Events Basics

Understand the SSE format, event structure, and event IDs.

What are Server-Sent Events?

Server-Sent Events (SSE) is a standard for pushing updates from a server to a client over HTTP. Unlike WebSockets, SSE is unidirectional — data flows only from server to client — which makes it simpler and works through most proxies and firewalls.

SSE vs WebSockets

SSE is perfect for MCP because the client sends requests via POST (one direction) and receives responses via SSE (other direction). This clean separation is simpler than bidirectional WebSockets.

SSE Event Format

SSE uses a simple text-based format. Each event consists of one or more fields, with each field on its own line:

text
event: message
id: 1
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[]}}

event: message
id: 2
data: {"jsonrpc":"2.0","id":2,"result":{"resources":[]}}

Event Fields

Field Purpose Required
event: Event type name (e.g., "message") Optional (defaults to "message")
data: The event payload (can span multiple lines) Required
id: Unique event identifier for reconnection Optional
retry: Reconnection time in milliseconds Optional

Critical Formatting Rules

SSE has strict formatting requirements:

  • Each event ends with a blank line (two newlines: \n\n)
  • Fields are separated by a single newline (\n)
  • The data: field contains your JSON payload
  • Multi-line data requires multiple data: lines
python
def format_sse_event(data, event_type="message", event_id=None):
    """Format data as a Server-Sent Event."""
    lines = []

    # Add event type
    if event_type:
        lines.append(f"event: {event_type}")

    # Add event ID (useful for reconnection)
    if event_id is not None:
        lines.append(f"id: {event_id}")

    # Add data (must be a single line for JSON)
    lines.append(f"data: {data}")

    # Join with newlines and add blank line to end event
    return "\n".join(lines) + "\n\n"


# Example usage:
import json

response = {
    "jsonrpc": "2.0",
    "id": 1,
    "result": {"tools": []}
}

sse_event = format_sse_event(
    data=json.dumps(response),
    event_type="message",
    event_id=1
)

print(sse_event)
# Output:
# event: message
# id: 1
# data: {"jsonrpc":"2.0","id":1,"result":{"tools":[]}}
#
# (blank line)

Event IDs and Reconnection

Event IDs enable clients to resume after a disconnection. When a client reconnects, it sends the last received ID in the Last-Event-ID header:

diagram
┌─────────────────────────────────────────────────────────────┐
│                    SSE RECONNECTION                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. Client receives events with IDs                         │
│     ◄── id: 1, data: {...}                                  │
│     ◄── id: 2, data: {...}                                  │
│     ◄── id: 3, data: {...}                                  │
│                                                             │
│  2. Connection drops after id: 3                            │
│     ✕ DISCONNECTED                                          │
│                                                             │
│  3. Client reconnects with Last-Event-ID                    │
│     ──► GET /events                                         │
│         Last-Event-ID: 3                                    │
│                                                             │
│  4. Server resumes from id: 4                               │
│     ◄── id: 4, data: {...}                                  │
│     ◄── id: 5, data: {...}                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

MCP-Specific SSE Usage

In MCP, SSE is used for streaming JSON-RPC responses. Each event contains a complete JSON-RPC message:

text
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{}}}

event: message
data: {"jsonrpc":"2.0","method":"notifications/initialized"}

Important: One JSON Per Data Line

Each SSE data: line should contain a complete, single-line JSON object. Don't pretty-print your JSON when sending SSE events!

HTTP Headers for SSE

When sending SSE responses, include these headers:

python
def send_sse_headers(self):
    """Send the required headers for SSE responses."""
    self.send_response(200)

    # Required: Identifies this as an SSE stream
    self.send_header("Content-Type", "text/event-stream")

    # Prevent caching of the stream
    self.send_header("Cache-Control", "no-cache")

    # Keep the connection open
    self.send_header("Connection", "keep-alive")

    # Allow cross-origin requests (if needed)
    self.send_header("Access-Control-Allow-Origin", "*")

    self.end_headers()
Next Step

Now that you understand SSE format, let's implement the POST request handler that receives JSON-RPC requests from clients.