Transport
The Model Context Protocol (MCP) specification defines three standard transport mechanisms ↗ for communication between clients and servers:
- stdio, communication over standard in and standard out — designed for local MCP connections.
- Server-Sent Events (SSE) — Currently supported by most remote MCP clients, but is expected to be replaced by Streamable HTTP over time. It requires two endpoints: one for sending requests, another for receiving streamed responses.
- Streamable HTTP — New transport method introduced ↗ in March 2025. It simplifies the communication by using a single HTTP endpoint for bidirectional messaging. It is currently gaining adoption among remote MCP clients, but it is expected to become the standard transport in the future.
MCP servers built with the Agents SDK can support both remote transport methods (SSE and Streamable HTTP), with the McpAgent class ↗ automatically handling the transport configuration.
If you're building a new MCP server or upgrading an existing one on Cloudflare, we recommend supporting both remote transport methods (SSE and Streamable HTTP) concurrently to ensure compatibility with all MCP clients.
You can use the "Deploy to Cloudflare" button to create a remote MCP server that automatically supports both SSE and Streamable HTTP transport methods.
If you're manually configuring your MCP server, here's how to use the McpAgent class to handle both transport methods:
export default { fetch(request: Request, env: Env, ctx: ExecutionContext) { const { pathname } = new URL(request.url);
if (pathname.startsWith('/sse')) { return MyMcpAgent.serveSSE('/sse').fetch(request, env, ctx); }
if (pathname.startsWith('/mcp')) { return MyMcpAgent.serve('/mcp').fetch(request, env, ctx); } },};export default { fetch(request: Request, env: Env, ctx: ExecutionContext): Response | Promise<Response> { const { pathname } = new URL(request.url);
if (pathname.startsWith('/sse')) { return MyMcpAgent.serveSSE('/sse').fetch(request, env, ctx); }
if (pathname.startsWith('/mcp')) { return MyMcpAgent.serve('/mcp').fetch(request, env, ctx); }
// Handle case where no path matches return new Response('Not found', { status: 404 }); },};const app = new Hono()
app.mount('/sse', MyMCP.serveSSE('/sse').fetch, { replaceRequest: false })app.mount('/mcp', MyMCP.serve('/mcp').fetch, { replaceRequest: false )
export default appIf your MCP server implements authentication & authorization using the Workers OAuth Provider ↗ Library, then you can configure it to support both transport methods using the apiHandlers property.
export default new OAuthProvider({ apiHandlers: { '/sse': MyMCP.serveSSE('/sse'), '/mcp': MyMCP.serve('/mcp'), }, // ... other OAuth configuration})If you've already built a remote MCP server using the Cloudflare Agents SDK, make the following changes to support the new Streamable HTTP transport while maintaining compatibility with remote MCP clients using SSE:
- Use
MyMcpAgent.serveSSE('/sse')for the existing SSE transport. Previously, this would have beenMyMcpAgent.mount('/sse'), which has been kept as an alias. - Add a new path with
MyMcpAgent.serve('/mcp')to support the new Streamable HTTP transport.
If you have an MCP server with authentication/authorization using the Workers OAuth Provider, update the configuration to use the apiHandlers property, which replaces apiRoute and apiHandler.
With these few changes, your MCP server will support both transport methods, making it compatible with both existing and new clients.
The Agents SDK supports bidirectional streaming for MCP servers, allowing server-to-client requests (such as elicitation requests or notifications) to be routed through the same stream as the originating client request. This is particularly important for the Streamable HTTP transport, which uses a single endpoint for bidirectional messaging.
When a client sends a request to your MCP server, the transport layer tracks which stream the request came from. When your server needs to send a message back to the client, the routing behavior depends on the message type:
- Responses and errors: Automatically routed through the same stream as the request they are responding to, using the
message.idfield - Server-to-client requests: Can be routed through a specific stream by providing a
relatedRequestIdoption that references the original client request ID - Standalone notifications: Sent through the standalone GET stream (if available) when no
relatedRequestIdis provided
The relatedRequestId option is automatically handled by the MCP SDK when your server uses elicitation or other server-to-client request features. The SDK will include the appropriate relatedRequestId to ensure bidirectional communication works correctly.
For example, when using the elicitation API:
import { McpAgent } from "agents/mcp";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
export class MyMCP extends McpAgent { server = new McpServer( { name: "Demo", version: "1.0.0" }, { capabilities: { elicitation: { form: {} } } } );
async init() { this.server.tool( "getUserName", "Ask the user for their name", {}, async () => { // The SDK automatically handles relatedRequestId routing const result = await this.server.server.elicitInput({ message: "What is your name?", requestedSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } });
if (result.action === "accept" && result.content?.name) { return { content: [ { type: "text", text: `Hello, ${result.content.name}!` } ] }; }
return { content: [{ type: "text", text: "Name not provided" }] }; } ); }}In this example, when the tool calls elicitInput(), the elicitation request is automatically sent through the same POST stream as the original tool call request, ensuring proper bidirectional communication. The client response to the elicitation is then routed back through the same stream.
If you are implementing a custom MCP transport (not using McpAgent), you can use the TransportSendOptions parameter when sending messages:
import type { TransportSendOptions } from "@modelcontextprotocol/sdk/shared/transport.js";
// Send a server-to-client request through the same stream as request "req-1"await transport.send( { jsonrpc: "2.0", id: "elicit-1", method: "elicitation/create", params: { /* ... */ } }, { relatedRequestId: "req-1" });The transport layer will prioritize routing based on:
- Response routing: If the message has an
idfield matching a previous request (response or error), it uses that request's stream - Related request routing: If
relatedRequestIdis provided, the message is sent through the stream associated with that request - Fallback: If neither applies, the message is sent through the standalone GET stream
While most MCP clients have not yet adopted the new Streamable HTTP transport, you can start testing it today using mcp-remote ↗, an adapter that lets MCP clients that otherwise only support local connections work with remote MCP servers.
Follow this guide for instructions on how to connect to your remote MCP server from Claude Desktop, Cursor, Windsurf, and other local MCP clients, using the mcp-remote local proxy ↗.
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark