Skip to content

Commit 4161f02

Browse files
authored
Merge pull request modelcontextprotocol#665 from modelcontextprotocol/update-transports-docs-from-spec
Update transports documentation from specification
2 parents e0a7d3b + 0eaee87 commit 4161f02

File tree

2 files changed

+256
-16
lines changed

2 files changed

+256
-16
lines changed

docs/docs/concepts/architecture.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ The transport layer handles the actual communication between clients and servers
108108
- Uses standard input/output for communication
109109
- Ideal for local processes
110110

111-
2. **HTTP with SSE transport**
112-
- Uses Server-Sent Events for server-to-client messages
111+
2. **Streamable HTTP transport**
112+
- Uses HTTP with optional Server-Sent Events for streaming
113113
- HTTP POST for client-to-server messages
114114

115115
All transports use [JSON-RPC](https://www.jsonrpc.org/) 2.0 to exchange messages. See the [specification](/specification/) for detailed information about the Model Context Protocol message format.
@@ -295,7 +295,7 @@ Here's a basic example of implementing an MCP server:
295295
- Simple process management
296296

297297
2. **Remote communication**
298-
- Use SSE for scenarios requiring HTTP compatibility
298+
- Use Streamable HTTP for scenarios requiring HTTP compatibility
299299
- Consider security implications including authentication and authorization
300300

301301
### Message handling

docs/docs/concepts/transports.mdx

Lines changed: 253 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ There are three types of JSON-RPC messages used:
4949

5050
## Built-in Transport Types
5151

52-
MCP includes two standard transport implementations:
52+
MCP currently defines two standard transport mechanisms:
5353

5454
### Standard Input/Output (stdio)
5555

@@ -127,7 +127,7 @@ Use stdio when:
127127

128128
</Tabs>
129129

130-
### Server-Sent Events (SSE)
130+
### Streamable HTTP
131131

132132
<Warning>
133133

@@ -139,22 +139,200 @@ and latest SDKs for the most recent information.
139139

140140
SSE transport enables server-to-client streaming with HTTP POST requests for client-to-server communication.
141141

142-
Use SSE when:
142+
Use Streamable HTTP when:
143143

144-
- Only server-to-client streaming is needed
145-
- Working with restricted networks
146-
- Implementing simple updates
144+
- Building web-based integrations
145+
- Needing client-server communication over HTTP
146+
- Requiring stateful sessions
147+
- Supporting multiple concurrent clients
148+
- Implementing resumable connections
149+
150+
#### How it Works
151+
152+
1. **Client-to-Server Communication**: Every JSON-RPC message from client to server is sent as a new HTTP POST request to the MCP endpoint
153+
2. **Server Responses**: The server can respond either with:
154+
- A single JSON response (`Content-Type: application/json`)
155+
- An SSE stream (`Content-Type: text/event-stream`) for multiple messages
156+
3. **Server-to-Client Communication**: Servers can send requests/notifications to clients via:
157+
- SSE streams initiated by client requests
158+
- SSE streams from HTTP GET requests to the MCP endpoint
159+
160+
<Tabs>
161+
<Tab title="TypeScript (Server)">
162+
163+
```typescript
164+
import express from "express";
165+
166+
const app = express();
167+
168+
const server = new Server({
169+
name: "example-server",
170+
version: "1.0.0"
171+
}, {
172+
capabilities: {}
173+
});
174+
175+
// MCP endpoint handles both POST and GET
176+
app.post("/mcp", async (req, res) => {
177+
// Handle JSON-RPC request
178+
const response = await server.handleRequest(req.body);
179+
180+
// Return single response or SSE stream
181+
if (needsStreaming) {
182+
res.setHeader("Content-Type", "text/event-stream");
183+
// Send SSE events...
184+
} else {
185+
res.json(response);
186+
}
187+
});
188+
189+
app.get("/mcp", (req, res) => {
190+
// Optional: Support server-initiated SSE streams
191+
res.setHeader("Content-Type", "text/event-stream");
192+
// Send server notifications/requests...
193+
});
194+
195+
app.listen(3000);
196+
```
197+
198+
</Tab>
199+
<Tab title="TypeScript (Client)">
147200

148-
#### Security Warning: DNS Rebinding Attacks
201+
```typescript
202+
const client = new Client({
203+
name: "example-client",
204+
version: "1.0.0"
205+
}, {
206+
capabilities: {}
207+
});
149208

150-
SSE transports can be vulnerable to DNS rebinding attacks if not properly secured. To prevent this:
209+
const transport = new HttpClientTransport(
210+
new URL("http://localhost:3000/mcp")
211+
);
212+
await client.connect(transport);
213+
```
214+
215+
</Tab>
216+
<Tab title="Python (Server)">
151217

152-
1. **Always validate Origin headers** on incoming SSE connections to ensure they come from expected sources
153-
2. **Avoid binding servers to all network interfaces** (0.0.0.0) when running locally - bind only to localhost (127.0.0.1) instead
154-
3. **Implement proper authentication** for all SSE connections
218+
```python
219+
from mcp.server.http import HttpServerTransport
220+
from starlette.applications import Starlette
221+
from starlette.routing import Route
222+
223+
app = Server("example-server")
224+
225+
async def handle_mcp(scope, receive, send):
226+
if scope["method"] == "POST":
227+
# Handle JSON-RPC request
228+
response = await app.handle_request(request_body)
229+
230+
if needs_streaming:
231+
# Return SSE stream
232+
await send_sse_response(send, response)
233+
else:
234+
# Return JSON response
235+
await send_json_response(send, response)
236+
237+
elif scope["method"] == "GET":
238+
# Optional: Support server-initiated SSE streams
239+
await send_sse_stream(send)
240+
241+
starlette_app = Starlette(
242+
routes=[
243+
Route("/mcp", endpoint=handle_mcp, methods=["POST", "GET"]),
244+
]
245+
)
246+
```
247+
248+
</Tab>
249+
<Tab title="Python (Client)">
250+
251+
```python
252+
async with http_client("http://localhost:8000/mcp") as transport:
253+
async with ClientSession(transport[0], transport[1]) as session:
254+
await session.initialize()
255+
```
256+
257+
</Tab>
258+
259+
</Tabs>
260+
261+
#### Session Management
262+
263+
Streamable HTTP supports stateful sessions to maintain context across multiple requests:
264+
265+
1. **Session Initialization**: Servers may assign a session ID during initialization by including it in an `Mcp-Session-Id` header
266+
2. **Session Persistence**: Clients must include the session ID in all subsequent requests using the `Mcp-Session-Id` header
267+
3. **Session Termination**: Sessions can be explicitly terminated by sending an HTTP DELETE request with the session ID
268+
269+
Example session flow:
270+
271+
```typescript
272+
// Server assigns session ID during initialization
273+
app.post("/mcp", (req, res) => {
274+
if (req.body.method === "initialize") {
275+
const sessionId = generateSecureId();
276+
res.setHeader("Mcp-Session-Id", sessionId);
277+
// Store session state...
278+
}
279+
// Handle request...
280+
});
281+
282+
// Client includes session ID in subsequent requests
283+
fetch("/mcp", {
284+
method: "POST",
285+
headers: {
286+
"Content-Type": "application/json",
287+
"Mcp-Session-Id": sessionId,
288+
},
289+
body: JSON.stringify(request),
290+
});
291+
```
292+
293+
#### Resumability and Redelivery
294+
295+
To support resuming broken connections, Streamable HTTP provides:
296+
297+
1. **Event IDs**: Servers can attach unique IDs to SSE events for tracking
298+
2. **Resume from Last Event**: Clients can resume by sending the `Last-Event-ID` header
299+
3. **Message Replay**: Servers can replay missed messages from the disconnection point
300+
301+
This ensures reliable message delivery even with unstable network connections.
302+
303+
#### Security Considerations
304+
305+
When implementing Streamable HTTP transport, follow these security best practices:
306+
307+
1. **Validate Origin Headers**: Always validate the `Origin` header on all incoming connections to prevent DNS rebinding attacks
308+
2. **Bind to Localhost**: When running locally, bind only to localhost (127.0.0.1) rather than all network interfaces (0.0.0.0)
309+
3. **Implement Authentication**: Use proper authentication for all connections
310+
4. **Use HTTPS**: Always use TLS/HTTPS for production deployments
311+
5. **Validate Session IDs**: Ensure session IDs are cryptographically secure and properly validated
155312

156313
Without these protections, attackers could use DNS rebinding to interact with local MCP servers from remote websites.
157314

315+
### Server-Sent Events (SSE) - Deprecated
316+
317+
<Note>
318+
SSE as a standalone transport is deprecated as of protocol version 2024-11-05.
319+
It has been replaced by Streamable HTTP, which incorporates SSE as an optional
320+
streaming mechanism. For backwards compatibility information, see the
321+
[backwards compatibility](#backwards-compatibility) section below.
322+
</Note>
323+
324+
The legacy SSE transport enabled server-to-client streaming with HTTP POST requests for client-to-server communication.
325+
326+
Previously used when:
327+
328+
- Only server-to-client streaming is needed
329+
- Working with restricted networks
330+
- Implementing simple updates
331+
332+
#### Legacy Security Considerations
333+
334+
The deprecated SSE transport had similar security considerations to Streamable HTTP, particularly regarding DNS rebinding attacks. These same protections should be applied when using SSE streams within the Streamable HTTP transport.
335+
158336
<Tabs>
159337
<Tab title="TypeScript (Server)">
160338

@@ -437,8 +615,8 @@ When implementing transport:
437615
- Handle denial of service scenarios
438616
- Monitor for unusual patterns
439617
- Implement proper firewall rules
440-
- For SSE transports, validate Origin headers to prevent DNS rebinding attacks
441-
- For local SSE servers, bind only to localhost (127.0.0.1) instead of all interfaces (0.0.0.0)
618+
- For HTTP-based transports (including Streamable HTTP), validate Origin headers to prevent DNS rebinding attacks
619+
- For local servers, bind only to localhost (127.0.0.1) instead of all interfaces (0.0.0.0)
442620

443621
## Debugging Transport
444622

@@ -454,3 +632,65 @@ Tips for debugging transport issues:
454632
8. Monitor resource usage
455633
9. Test edge cases
456634
10. Use proper error tracking
635+
636+
## Backwards Compatibility
637+
638+
To maintain compatibility between different protocol versions:
639+
640+
### For Servers Supporting Older Clients
641+
642+
Servers wanting to support clients using the deprecated HTTP+SSE transport should:
643+
644+
1. Host both the old SSE and POST endpoints alongside the new MCP endpoint
645+
2. Handle initialization requests on both endpoints
646+
3. Maintain separate handling logic for each transport type
647+
648+
### For Clients Supporting Older Servers
649+
650+
Clients wanting to support servers using the deprecated transport should:
651+
652+
1. Accept server URLs that may use either transport
653+
2. Attempt to POST an `InitializeRequest` with proper `Accept` headers:
654+
- If successful, use Streamable HTTP transport
655+
- If it fails with 4xx status, fall back to legacy SSE transport
656+
3. Issue a GET request expecting an SSE stream with `endpoint` event for legacy servers
657+
658+
Example compatibility detection:
659+
660+
```typescript
661+
async function detectTransport(serverUrl: string): Promise<TransportType> {
662+
try {
663+
// Try Streamable HTTP first
664+
const response = await fetch(serverUrl, {
665+
method: "POST",
666+
headers: {
667+
"Content-Type": "application/json",
668+
Accept: "application/json, text/event-stream",
669+
},
670+
body: JSON.stringify({
671+
jsonrpc: "2.0",
672+
method: "initialize",
673+
params: {
674+
/* ... */
675+
},
676+
}),
677+
});
678+
679+
if (response.ok) {
680+
return "streamable-http";
681+
}
682+
} catch (error) {
683+
// Fall back to legacy SSE
684+
const sseResponse = await fetch(serverUrl, {
685+
method: "GET",
686+
headers: { Accept: "text/event-stream" },
687+
});
688+
689+
if (sseResponse.ok) {
690+
return "legacy-sse";
691+
}
692+
}
693+
694+
throw new Error("Unsupported transport");
695+
}
696+
```

0 commit comments

Comments
 (0)