Quick Answer
Use Streamable HTTP for a new remote Model Context Protocol (MCP) server. Use stdio when an MCP client launches a local server process. Keep legacy HTTP+SSE only when older clients still require it.
SSE itself is not deprecated. MCP deprecated a specific two-endpoint transport called HTTP+SSE. Current Streamable HTTP still uses Server-Sent Events when a response needs progress updates or incremental messages. A simple request can return one JSON object instead.
The current MCP revision, 2026-07-28, sends every client request as a POST to one MCP endpoint. It removed the standalone GET stream and the Mcp-Session-Id protocol session. Both features existed in earlier Streamable HTTP revisions, so advice written for 2025 can describe a different wire protocol.
Key Takeaways
- SSE is a UTF-8 event-stream format. Streamable HTTP is an MCP transport that can use that format.
- MCP deprecated its legacy HTTP+SSE transport, not the underlying Server-Sent Events web standard.
- Legacy HTTP+SSE uses a long-lived GET stream and a separate POST endpoint.
- Current Streamable HTTP uses one POST endpoint and returns either JSON or request-scoped SSE.
- MCP revision 2026-07-28 removed protocol-level sessions, the standalone GET stream, SSE event IDs, and Last-Event-ID resumption.
- stdio and Streamable HTTP are the two standard transport bindings in the current MCP specification.
- New local MCP servers normally use stdio. New independently hosted remote servers normally use Streamable HTTP.
- Production deployments must test buffering, idle timeouts, cancellation, authentication, and version fallback.
- The current Proxidize MCP server uses stdio. Proxidize's forward proxies serve a separate outbound web-access role.
Last updated: September 3, 2026. Verified against MCP specification revision 2026-07-28.
MCP transports carry JSON-RPC messages between an AI application's MCP client and an MCP server. They do not define the tools, resources, or prompts exposed by that server.
This distinction matters because “SSE” has described several different things in MCP discussions. It can mean the general Server-Sent Events format, the original HTTP+SSE transport, or an SSE response inside Streamable HTTP.
For a broader introduction, read what MCP is and how Model Context Protocol works. This guide focuses on transport behavior, version differences, migration, and production deployment.
How Do SSE and Streamable HTTP Compare in MCP?
SSE and Streamable HTTP differ because legacy MCP split traffic across two endpoints, while current MCP uses one POST endpoint.
The first correction is terminological. The accurate comparison is legacy HTTP+SSE versus Streamable HTTP. SSE remains available inside Streamable HTTP as the response format text/event-stream.
The current MCP Streamable HTTP specification defines one endpoint that accepts POST. Each JSON-RPC request receives either one application/json object or an SSE stream scoped to that request.
| Question | Legacy HTTP+SSE | Current Streamable HTTP |
|---|---|---|
| What is it? | Deprecated MCP remote transport | Current MCP remote transport |
| Which revision introduced it? | `2024-11-05` | `2025-03-26`; redesigned in `2026-07-28` |
| How many endpoint roles? | Two: SSE GET and message POST | One MCP endpoint accepting POST |
| How does the client send a message? | POST to the URI advertised by the SSE endpoint | One POST for each JSON-RPC request |
| How does the server return a result? | Through the shared SSE connection | JSON or SSE on that request's response |
| Is SSE required for every result? | Yes, for server-to-client messages | No |
| Is a standalone GET stream used? | Yes | No in revision `2026-07-28` |
| Are protocol sessions used? | Connection-oriented design | No in revision `2026-07-28` |
| How does streaming work? | Server messages travel over the shared SSE channel | A request can return request-scoped `text/event-stream` |
| Should a new server adopt it? | No | Yes, for remote deployment |
Three decisions cover most projects:
- Use stdio when the AI client should launch and own a local server process.
- Use Streamable HTTP when the MCP server runs as an independent network service.
- Support legacy HTTP+SSE only while measured compatibility requirements justify it.
Streamable HTTP does not guarantee that every request is short. A tool can keep its POST response open as an SSE stream. An explicit subscriptions/listen request can also create a long-lived stream.
The improvement is control. Streaming is attached to the request that needs it instead of being the mandatory return path for the entire connection.
In short: MCP replaced a two-channel remote transport with a single-endpoint request model. Current Streamable HTTP can still use SSE, but it does so on a request response. Use Streamable HTTP for new remote servers, stdio for local child processes, and HTTP+SSE only for old-client compatibility.
Why Is “SSE vs Streamable HTTP” Not a Perfect Comparison?
SSE is an event-stream format, while Streamable HTTP is an MCP transport that can select SSE when incremental delivery helps.
Server-Sent Events defines how a server formats incremental events over an HTTP response. Streamable HTTP defines a broader MCP binding. That binding covers endpoints, HTTP methods, JSON-RPC framing, request metadata, response choices, cancellation, and compatibility.
This produces a category mismatch similar to comparing JSON with a REST API. JSON can appear inside the API, but it is not the complete API architecture.
| Term | What it names | Relationship to MCP |
|---|---|---|
| Server-Sent Events | An event-stream format over HTTP | Can carry JSON-RPC messages from server to client |
| `text/event-stream` | The SSE media type | One permitted current Streamable HTTP response type |
| HTTP+SSE | The original MCP remote transport | Deprecated; used separate GET and POST endpoint roles |
| Streamable HTTP | The current MCP remote binding | Uses POST requests and either JSON or SSE responses |
| HTTP streaming | A general technique for sending a response incrementally | Broader than MCP and broader than SSE |
| stdio | Newline-delimited messages over standard streams | Current standard binding for client-launched processes |
The WHATWG HTML Living Standard defines event streams and the browser EventSource interface. The MCP specification defines how MCP messages use a transport.
MCP clients are not necessarily browser EventSource objects. Official SDKs can parse SSE inside Node.js, Python, Go, C#, or other runtimes. The relevant commonality is the event-stream wire format, not a requirement to use the browser API.
Wording also matters when discussing deprecation. “MCP deprecated SSE” is too broad. The accurate statement is that MCP deprecated the HTTP+SSE transport from revision 2024-11-05.
Current Streamable HTTP explicitly retains SSE. Therefore, removing every SSE parser from a client would make that client noncompliant with current remote transport behavior.
In short: SSE and Streamable HTTP sit at different levels. SSE formats a streaming HTTP response. Streamable HTTP defines how an MCP client and server exchange requests and responses. Compare legacy HTTP+SSE with Streamable HTTP, while recognizing that the latter still includes SSE.
What Is Server-Sent Events (SSE)?
Server-Sent Events is a UTF-8 HTTP event-stream format for sending ordered, incremental messages from a server to a client.
An SSE response uses the media type text/event-stream. The server sends line-oriented fields, and a blank line terminates one event. The WHATWG standard states that event streams are always decoded as UTF-8.
A current-MCP-compatible SSE response can look like this:
| SSE field | General meaning | Current MCP consideration |
|---|---|---|
| `data` | Adds data to the event payload | Carries a JSON-RPC message |
| `event` | Names an event type | Generic SSE field; not required for current MCP message framing |
| `id` | Updates the client's last event ID | Removed from current MCP Streamable HTTP together with message redelivery |
| `retry` | Suggests a reconnection delay | Generic SSE field; current MCP defines no stream resumption protocol |
| `:` comment | Carries no event data | Useful as a keep-alive on long-lived quiet streams |
SSE is one-way within a response. The server streams data toward the client after the client opens an HTTP request. A separate request is still needed for the client to send new application messages.
SSE also does not guarantee immediate delivery through every intermediary. A reverse proxy can buffer upstream response bytes. An idle timeout can close a quiet connection. A client can receive several events together if a gateway waits before flushing them.
The WHATWG standard describes automatic reconnection for browser EventSource, including Last-Event-ID. That general behavior must not be copied blindly into MCP. The 2026-07-28 changelog records the removal of both SSE event IDs and Last-Event-ID resumption from Streamable HTTP.
This distinction separates a wire format from the protocol using it. SSE supplies framing tools. MCP decides which fields and lifecycle rules are valid for a particular protocol revision.
In short: SSE sends UTF-8 events over an HTTP response using text/event-stream. Its fields include data, event, id, retry, and comments. MCP uses the format selectively and defines its own cancellation and recovery rules, which can differ from generic browser SSE behavior.
How Did MCP's Legacy HTTP+SSE Transport Work?
Legacy HTTP+SSE used one long-lived GET stream for server messages and a separate POST endpoint for client messages during a session.
MCP revision 2024-11-05 defined HTTP with SSE as one of two standard transports. Its transport specification required two endpoint roles.
The 2024 transport required the POST but did not prescribe its HTTP response status. A displayed 202 Accepted therefore reflects implementation behavior rather than a requirement of the 2024-11-05 transport.
Current Streamable HTTP uses a narrower rule. Its sending-messages requirements require 202 Accepted with no body when a server accepts a JSON-RPC notification POST.
The connection followed four main steps:
- The client sent a GET request to the server's SSE endpoint.
- The server opened an event stream and sent an endpoint event.
- That event told the client which URI should receive future POST messages.
- The server sent JSON-RPC responses and notifications as SSE message events.
An illustrative opening event looked like this:
The client then posted JSON-RPC to the advertised URI:
The HTTP response to that POST did not need to carry the JSON-RPC result. The actual result traveled through the already-open SSE channel.
This design created a required association between the inbound POST and one live SSE connection. A single-process server can hold that mapping in memory. A distributed deployment needs a reliable way to send the result to the instance holding the stream.
That requirement can lead to connection affinity, shared routing state, or a message broker. Those components are deployment consequences, not extra requirements named by the 2024 specification.
The design also made SSE central even when a tool returned one small result. Every server message still needed the shared event stream.
In short: Legacy HTTP+SSE split one logical MCP conversation across a GET stream and an advertised POST endpoint. The POST accepted client messages, while the GET stream carried server messages. That coupling made connection routing part of the server's operating model for each connected client.
What Is MCP Streamable HTTP?
MCP Streamable HTTP is the remote binding where each JSON-RPC request uses one POST and receives JSON or request-scoped SSE.
Streamable HTTP first appeared in MCP revision 2025-03-26. The current 2026-07-28 shape exposes one MCP endpoint, such as https://mcp.example.com/mcp, that accepts POST.
The binding has five defining properties:
| Property | Current behavior |
|---|---|
| Endpoint | One MCP URL accepting POST |
| Request | One JSON-RPC request per POST |
| Response | One JSON object or one SSE stream |
| Metadata | Protocol version and capabilities are required per request; client information is recommended |
| Session | No protocol-level session in revision `2026-07-28` |
The word “Streamable” means a response can stream. It does not mean every response must stream. A server can return a normal JSON object when one response is enough.
The current binding also does not create a full-duplex WebSocket. The client sends requests through HTTP POST. The server answers within each request's response. Multi-step server-to-client input uses Multi Round-Trip Requests, or MRTR.
Long-lived notifications use the same request-oriented principle. A client sends subscriptions/listen, and the response remains open as an SSE stream. There is no unrelated, always-on GET channel.
This architecture separates transport choice from feature design. Tools, resources, and prompts retain MCP semantics. The transport decides how their JSON-RPC messages cross the process or network boundary.
In short: Streamable HTTP gives remote MCP a normal HTTP request boundary without losing incremental responses. Each POST stands alone, while the server chooses JSON or SSE. Current MCP also uses per-request metadata instead of a hidden transport session across ordinary HTTP infrastructure.
How Did MCP Transport Change From 2024 to 2026?
MCP transport moved from two-endpoint HTTP+SSE to session-based Streamable HTTP, then to stateless, POST-only Streamable HTTP.
The most common MCP transport mistake is treating every page labeled “Streamable HTTP” as equivalent. Three released eras have materially different wire behavior.
| Protocol era | Remote transport shape | State and stream behavior | Current status |
|---|---|---|---|
| `2024-11-05` | HTTP+SSE with separate GET and POST endpoint roles | Shared long-lived SSE channel | Deprecated |
| `2025-03-26` to `2025-11-25` | One MCP endpoint supporting POST and GET | Optional `Mcp-Session-Id`, standalone GET stream, server requests on SSE, resumable streams | Supported only when an implementation serves those revisions |
| `2026-07-28` | One MCP endpoint accepting POST | Per-request metadata, no protocol session, request-scoped SSE without event-ID resumption, explicit subscriptions | Current |
What changed in 2025?
The March 2025 specification replaced HTTP+SSE with Streamable HTTP. By revision 2025-11-25, every client message used a new POST to one MCP endpoint.
That endpoint also supported GET. A client could open a standalone SSE stream for server-initiated messages. A server could assign Mcp-Session-Id, and a client could terminate that session with DELETE.
The 2025 design also allowed SSE event IDs. A disconnected stream could resume through a GET carrying Last-Event-ID. The archived 2025-11-25 transport specification remains the right source when maintaining that era.
What changed in 2026?
The July 2026 revision removed the initialization handshake, standalone GET stream, protocol session, and SSE event-ID resumption. It added per-request metadata, header-based routing, MRTR, and explicit notification subscriptions.
The official release announcement describes the goal as a stateless protocol core. Any request can reach any compatible instance without shared protocol-session storage.
The current deprecated-features registry also marks Roots, Sampling, and Logging as deprecated. They remain functional during the deprecation window, but new implementations should not adopt them. Their earliest eligible removal is the first revision released on or after July 28, 2027; actual removal may happen later.
The HTTP+SSE transport has been deprecated since 2025-03-26. The registry says new implementations should not adopt it. It also separates earliest eligibility from actual removal, so teams should not invent a fixed removal date.
In short: MCP has used three remote HTTP shapes. The 2024 design used two channels. The 2025 design unified the endpoint but retained sessions and GET streams. The current 2026 design uses self-contained POST requests, optional SSE responses, MRTR, and explicit subscriptions; it also deprecates Roots, Sampling, and Logging.
How Does a Current Streamable HTTP Request Work?
Current Streamable HTTP requests send one JSON-RPC message by POST and receive either one JSON object or a scoped SSE stream.
The client must send an Accept header listing both supported response types. Body _meta must include the protocol version and client capabilities. Clients should also include their identity, while selected body fields are mirrored into HTTP headers.
An illustrative tool call looks like this:
A server with one immediate result can return JSON:
A server that needs incremental delivery can return an SSE response:
The response examples include io.modelcontextprotocol/serverInfo in result _meta. The current result schema says servers should attach this self-reported identity to every result unless specifically configured not to do so. Clients may use it for display or debugging, but not for security or behavioral decisions.
The final JSON-RPC response should terminate that request stream. Independent server requests must not appear on it. Current server-to-client input uses MRTR instead.
The specification uses normative words such as MUST and SHOULD. SDKs normally implement this bookkeeping, but wire-level tests should still verify headers, content types, and close behavior.
In short: One modern POST normally contains the JSON-RPC request, protocol version, client identity, and capabilities. The response is either one JSON object or an SSE sequence ending in the matching result. The choice belongs to the server for that request at runtime, and servers should identify themselves in result metadata.
When Does Streamable HTTP Return JSON Instead of SSE?
Streamable HTTP can return JSON when one response is sufficient; clients must also accept SSE when the server needs incremental delivery.
The current specification does not impose a time threshold for choosing JSON. It defines two valid response types and requires clients to handle both.
| Request behavior | Suitable response | Reason |
|---|---|---|
| One result is ready without useful intermediate messages | `application/json` | Simplest lifecycle |
| Work produces meaningful progress notifications | `text/event-stream` | Delivers progress before completion |
| A request opts into MCP log notifications | `text/event-stream` | Carries `notifications/message`; Logging is deprecated |
| The client requests a long-lived notification subscription | `text/event-stream` | Keeps the subscription response open |
| The result is large but not incrementally encoded as MCP messages | Implementation dependent | Payload size alone does not mandate SSE |
JSON avoids stream-specific concerns when they add no value. Intermediaries can process one finite response, and clients can parse one object. The server still performs ordinary authentication, authorization, and JSON-RPC error handling.
SSE is useful when the server has multiple protocol messages to deliver before the final response. Those messages can include progress when the client supplies a progressToken.
Request-related notifications/message logs are allowed only when request _meta includes io.modelcontextprotocol/logLevel. MCP deprecated Logging in revision 2026-07-28, so new implementations should use OpenTelemetry for observability or stderr with stdio.
The client advertises both media types:
The client should not advertise only JSON and then assume every compliant server must comply with that preference. Current Streamable HTTP requires support for both cases.
Nor should a server open an SSE response merely because “Streamable” appears in the transport name. Long-lived responses consume connection and runtime resources. JSON is the cleaner choice when no intermediate message is needed.
The right decision is semantic. Stream because multiple messages improve the request experience. Return JSON when one response fully represents the result.
In short: JSON is a first-class Streamable HTTP response, not a fallback mode. Use it for one-result requests. Use SSE when the server needs to deliver several request-related messages or maintain an explicit subscription. Clients must be ready for either response on every call; MCP Logging is now deprecated.
When Does Streamable HTTP Use SSE?
Streamable HTTP uses SSE for request-related progress and for explicit long-lived subscriptions to supported change notifications.
Current MCP defines two distinct reasons to keep an HTTP response open. They share SSE framing but have different scopes.
| SSE stream type | Opened by | What it can carry | How it normally ends |
|---|---|---|---|
| Request-scoped response | A tool, resource, prompt, or other request POST | Related progress or log notifications, then the result | Final JSON-RPC response |
| Subscription response | `subscriptions/listen` POST | Acknowledgment, requested change notifications, and completion | Client cancellation or graceful server completion |
A request-scoped stream belongs to one JSON-RPC request. The server may send progress before the result. It must not use that stream for unrelated server requests.
A subscription stream is deliberately long-lived. The client names the change events it wants, and the server acknowledges the supported subset. This replaces the old generic GET listening channel.
Current Streamable HTTP adds four important boundaries:
- A final response should end a request-scoped stream.
- Closing that response stream signals cancellation of the associated request.
- SSE streams cannot resume through Last-Event-ID under the current revision.
- Quiet long-lived streams should use comment lines as keep-alives where needed.
The current receiving-messages rules recommend the response header X-Accel-Buffering: no. They also encourage periodic comment lines for long-lived streams.
Those measures address delivery, not replay. A comment can keep a connection active, but it does not create durable history. If a subscription drops, the client must re-establish it and recover application state appropriately.
In short: Current MCP uses SSE for two bounded jobs: incremental messages related to one request, and explicit change subscriptions. It no longer uses one general GET stream for the entire remote session. Stream purpose determines lifecycle, cancellation, and recovery in the protocol.
How Do Stateless MCP Requests Handle State and Server-to-Client Input?
Stateless MCP requests carry metadata each time and use explicit handles or MRTR results instead of hidden transport sessions.
Protocol statelessness does not forbid application state. A tool can create a job, transaction, cursor, or workflow and return an explicit handle. Later calls can pass that handle as a normal argument.
The important change is visibility. State is no longer attached implicitly through Mcp-Session-Id. It becomes part of the application's data contract.
| State need | Current pattern | Example |
|---|---|---|
| Client identity and capabilities | Per-request `_meta` | Client name, version, supported features |
| Long-running application work | Explicit server-minted handle | `job_id` returned by a tool |
| More user or model input | Multi Round-Trip Requests | Confirmation before a write |
| Change notifications | `subscriptions/listen` | Tool-list or resource update |
| Durable recovery | Application design | Database record or task extension |
The current Multi Round-Trip Requests specification replaces standalone server-initiated JSON-RPC requests. Its basic flow has four steps:
- The client sends the original request.
- The server returns InputRequiredResult with requested input.
- The client gathers that input and retries the original operation.
- The server returns a final result when enough input is available.
MRTR still defines sampling/createMessage and roots/list input requests during their deprecation window. Roots and Sampling remain functional, but new implementations should follow the current migration guidance: use explicit tool parameters, resource URIs, or server configuration for files, and call model-provider APIs directly for sampling.
An abbreviated intermediate result can look like this:
The client must treat requestState as opaque. The server must treat the returned value as attacker-controlled. If it influences authorization, resource access, or business logic, the server must protect its integrity with a control such as HMAC or AEAD.
To limit replay, the server should bind protected state to the authenticated principal and originating request, then give it a short expiry. Integrity protection may be omitted only when tampering can do nothing worse than make the request fail.
MRTR makes each retry self-contained enough for another instance to process. It also means application developers must design duplicate protection and state validation deliberately.
In short: Current MCP moves state from an implicit transport session into request metadata, explicit tool arguments, and protected MRTR state. The protocol remains stateless while applications can retain durable workflows. That separation improves routing but increases the importance of explicit state design.
How Do Long-Lived MCP Notifications Work Without a GET Stream?
Long-lived MCP notifications use a subscriptions/listen POST whose SSE response carries only the event types a client requested.
Revision 2026-07-28 replaced the old GET listening stream and resources/subscribe RPC with one explicit message pattern. The subscriptions specification defines its filter, acknowledgment, message correlation, and closure.
The request can ask for four current categories:
| Filter field | Notification requested |
|---|---|
| `toolsListChanged` | `notifications/tools/list_changed` |
| `promptsListChanged` | `notifications/prompts/list_changed` |
| `resourcesListChanged` | `notifications/resources/list_changed` |
| `resourceSubscriptions` | `notifications/resources/updated` for named URIs |
The server's first stream message must be notifications/subscriptions/acknowledged. That message reports the subset the server agreed to honor.
Every later notification carries the subscription ID in _meta. The ID matches the JSON-RPC request ID that opened the subscription. A client can maintain several subscriptions and use those IDs to keep events separate.
Three closure cases matter:
- The client closes the HTTP response stream to cancel the subscription.
- The server returns a successful result before closing for a graceful shutdown.
- The underlying transport drops without a final result.
Current Streamable HTTP does not replay subscription events through Last-Event-ID. A robust client should reconnect, reopen the subscription, and refetch any state whose freshness matters.
That recovery pattern treats notifications as change signals rather than a durable event log. Applications needing guaranteed replay should add their own cursor or queue semantics outside the base transport.
In short: subscriptions/listen restores long-lived server notifications without restoring a generic GET channel. The client explicitly selects event types, the server acknowledges support, and each message carries a subscription ID. Recovery remains an application concern because current streams do not replay missed events.
How Do stdio, Legacy HTTP+SSE, and Streamable HTTP Compare?
stdio fits local child processes, legacy HTTP+SSE serves old remote clients, and Streamable HTTP fits new remote services.
The current MCP specification names two standard bindings: stdio and Streamable HTTP. Legacy HTTP+SSE remains a compatibility concern, not a third recommended choice.
| Decision factor | stdio | Legacy HTTP+SSE | Current Streamable HTTP |
|---|---|---|---|
| Deployment | Local child process | Remote service | Remote service |
| Who starts the server? | MCP client | Service operator | Service operator |
| Message channel | Standard input and output | Separate POST and SSE GET roles | HTTP POST response |
| Network authentication | Usually environment or local controls | Required for exposed services | Required for protected exposed services |
| Streaming | Bidirectional byte stream | Mandatory SSE return channel | Optional request SSE or explicit subscription |
| Protocol session | Process relationship | Connection mapping | None in `2026-07-28` |
| Horizontal routing | Not applicable to one local process | Requires connection-aware design | Any instance can handle a self-contained request |
| Main risk boundary | Local code execution and environment access | Remote auth plus connection state | Remote auth, request policy, and streaming operations |
| Specification status | Current | Deprecated | Current |
stdio uses newline-delimited JSON-RPC over a process's standard streams. The client launches the executable and owns its lifecycle. Diagnostic output belongs on standard error because standard output must contain only valid MCP messages.
Legacy HTTP+SSE runs independently over the network. It remains relevant when a client configuration still names an SSE endpoint. The current TypeScript SDK still exposes SSEClientTransport as a fallback for those servers.
Current Streamable HTTP also runs independently, but each request owns its response. A deployment can still maintain application state, yet the protocol does not hide that state in a session header.
Custom transports remain possible. They should preserve MCP message semantics and document framing, cancellation, and connection behavior. A custom transport also creates an interoperability obligation, so it should solve a real constraint.
In short: Choose between the two current bindings by process boundary. stdio is local and client-launched. Streamable HTTP is remote and service-operated. Treat HTTP+SSE as a migration path only, even though some clients and SDKs retain compatibility classes during a controlled transition.
Which MCP Transport Should Developers Choose?
Developers should choose stdio for client-launched local servers and current Streamable HTTP for independently hosted remote servers.
Transport selection begins with ownership and placement, not with whether a tool might stream.
| Scenario | Recommended transport | Reason |
|---|---|---|
| Desktop assistant launches a filesystem tool | stdio | Simple local process lifecycle |
| IDE launches an npm MCP package | stdio | No independently hosted endpoint needed |
| Company hosts one server for many authorized clients | Streamable HTTP | Shared remote service boundary |
| SaaS product exposes MCP to customers | Streamable HTTP | Network auth, routing, and observability |
| Existing client only understands an SSE URL | Legacy compatibility | Temporary interoperability need |
| Embedded runtime already has a reliable byte channel | Custom or stdio framing | Deployment-specific constraint |
Streaming requirements do not change the first decision. stdio already carries multiple messages. Streamable HTTP can return SSE when required.
Security can change the architecture. A local stdio server runs with access available to its process. A remote server creates a network authorization boundary. Neither is automatically safer for every workload.
Client support also matters. Verify the exact SDK and product versions in use. A client that says “Streamable HTTP” may support only a 2025 protocol revision unless it advertises current behavior.
The official TypeScript SDK connection guide follows the same deployment distinction. It documents StdioClientTransport for local process-spawned servers and Streamable HTTP for remote endpoints.
In short: Pick transport from deployment topology. Local, client-owned processes use stdio. Independent network services use current Streamable HTTP. Add legacy HTTP+SSE only when real clients still need it, and record when that compatibility path can be retired through a measured migration plan.
Why Is Current Streamable HTTP Easier to Scale?
Current Streamable HTTP scales cleanly because requests carry their own metadata and do not depend on a protocol session or sticky route.
Revision 2026-07-28 requires the protocol version and client capabilities in every request. Clients should also include their identity. The server can validate and handle that request without retrieving an MCP session first.
| Scaling concern | Session-based approach | Current per-request approach |
|---|---|---|
| Instance selection | Route to session owner or share state | Route to any compatible instance |
| Client capabilities | Stored from initialization | Sent in request `_meta` |
| Protocol version | Negotiated for session | Declared per request |
| Gateway routing | Often parses path or body | Can inspect `Mcp-Method` and `Mcp-Name` |
| Catalog freshness | Refetch after reconnect or notification | Use `ttlMs`, `cacheScope`, and invalidation notifications |
| Mid-call client input | Server request over live channel | MRTR result and independent retry |
| Application state | Can be hidden in session | Explicit handle or durable store |
The official 2026-07-28 release states that a request can land on any instance behind a round-robin load balancer. No shared protocol-session store is required.
Header-based routing strengthens that model. Mcp-Method identifies the JSON-RPC method. Mcp-Name identifies a tool, resource, or prompt for selected operations.
A gateway can use those headers for routing, authorization, rate limiting, or metrics without parsing the JSON body. The origin server must still verify that mirrored headers match the body.
Caching is another 2026 scaling change. The current MCP caching specification requires ttlMs and cacheScope on complete results from server/discover, tools/list, prompts/list, resources/list, resources/templates/list, and resources/read.
ttlMs is a freshness hint, not a guarantee. cacheScope controls whether a cached result may cross authorization contexts. A server should use public only for non-user-specific data that is safe to share; a relevant change notification invalidates a still-fresh entry.
The result _meta also illustrates server identity. Servers should include io.modelcontextprotocol/serverInfo on every result unless specifically configured not to do so. That identity is self-reported and must not drive security decisions.
Stateless transport does not remove all affinity. An individual SSE response remains connected to the instance serving that HTTP request. A long-lived subscription also occupies one live response.
Application state can still require a database, queue, object store, or explicit handle. A browser automation tool may own a live browser process. A database transaction may belong to one worker.
The improvement is that MCP no longer creates hidden affinity for every operation. Teams add state only where the application actually needs it.
In short: Current Streamable HTTP removes protocol-session routing from ordinary requests. Per-request metadata and mirrored headers support conventional load balancing and policy. Cache hints can reduce unnecessary refetches, while result metadata identifies the server. Live streams and stateful tools still need deliberate runtime design.
When Is Streamable HTTP Still Operationally Difficult?
Streamable HTTP still needs streaming support, timeout control, retry safety, authentication, and compatibility testing in production.
Stateless does not mean effortless. It means the base protocol does not rely on a session. The network and application still have failure modes.
| Operational challenge | Why it remains | Practical control |
|---|---|---|
| Long response streams | Connections consume runtime and gateway resources | Bound duration and test platform limits |
| Quiet subscriptions | Intermediaries may enforce idle timeouts | Send SSE comments and align timeouts |
| Response buffering | Events may arrive in batches | Disable buffering and verify flush behavior |
| Ambiguous retry | A disconnected write may have completed | Use idempotency or operation lookup |
| Application state | Some tools own jobs, browsers, or transactions | Return explicit handles and store state deliberately |
| Mixed client versions | 2024, 2025, and 2026 traffic differs | Log negotiated era and test compatibility |
| Authorization | Remote tools can affect real systems | Apply least privilege per operation |
| Stream loss | Current MCP provides no `Last-Event-ID` replay | Reconnect, refetch, or add durable application events |
Serverless is a qualified fit. A stateless JSON tool call maps cleanly to a function invocation. A long-lived SSE response requires a platform that supports streaming and sufficient request duration.
Backpressure is also bounded by implementation. Holding a POST response open can tie work to the request lifecycle. An internal producer can still outrun the socket unless the server respects stream pressure.
Retries deserve special attention. A lost response does not prove the server failed before performing a side effect. Blindly repeating create_order, rotate_proxy, or delete_record can duplicate an action.
Version compatibility creates another layer. A 2025 Streamable HTTP client can send GET, Mcp-Session-Id, or Last-Event-ID. A current-only server handles those inputs differently.
The balanced conclusion is not that Streamable HTTP removes infrastructure. It gives infrastructure clearer request boundaries. Teams must still engineer the behavior at those boundaries.
In short: Streamable HTTP reduces hidden session complexity, but streams, gateways, side effects, and mixed versions remain operational work. Treat “stateless” as a routing property. It is not, by itself, a guarantee of short requests, replay, idempotency, low cost, or automatic security.
How Do You Migrate From Legacy HTTP+SSE to Streamable HTTP?
Migration requires a modern POST endpoint, updated clients, version-aware fallback, production stream tests, and measured legacy retirement.
Start by identifying the source era. A 2024 HTTP+SSE server needs a transport change. A 2025 Streamable HTTP server already has request POSTs, but it can still rely on GET streams and Mcp-Session-Id.
| Starting point | Main migration work |
|---|---|
| 2024 HTTP+SSE | Replace the shared SSE return channel with request responses |
| 2025 Streamable HTTP | Remove handshake, GET stream, session header, and resumption assumptions |
| Mixed deployment | Serve explicit compatibility paths and measure their use |
| New remote server | Implement current Streamable HTTP directly |
A controlled migration follows twelve steps:
- Inventory client and server versions. Record SDK version, protocol revision, endpoint URL, and configured transport.
- Find legacy endpoints. Search configuration, documentation, and logs for /sse, advertised message URLs, and SSE transport classes.
- Add one current MCP POST endpoint. Use a distinct path such as /mcp when that avoids ambiguity.
- Return responses on their request. Send one JSON object or a request-scoped SSE stream.
- Add per-request metadata. Include the required protocol version and client capabilities in _meta, plus the recommended client identity.
- Add required HTTP headers. Mirror the protocol version, method, and applicable operation name.
- Replace server-initiated requests. Use MRTR for Elicitation. MRTR also carries Sampling and Roots during their deprecation window, but new implementations should use direct model-provider APIs and explicit tool, resource, or configuration inputs instead.
- Replace generic notifications. Use subscriptions/listen for supported long-lived change notifications.
- Remove transport-session dependence. Replace Mcp-Session-Id with explicit application handles when state remains necessary.
- Test through production intermediaries. Verify JSON, SSE flushing, quiet periods, disconnects, and authentication.
- Run dual support only when needed. Keep old routes isolated and observable during the compatibility window.
- Retire legacy traffic from evidence. Remove HTTP+SSE after supported clients stop using it.
An illustrative client configuration change might look like this:
Actual keys vary by client. Do not copy one product's configuration syntax into another without checking its current documentation.
The official MCP guidance allows servers to keep old SSE and POST endpoints beside the modern endpoint. That is a transition strategy, not a reason to make new clients choose the old path.
In short: Migrate both the endpoint and the lifecycle. Moving /sse to /mcp is incomplete if code still assumes initialization, session affinity, server-pushed requests, or replay. Measure old-client use, test the real network path, and remove compatibility only after evidence supports it.
How Should Clients Detect Modern and Legacy MCP Servers?
Clients should try a modern request, inspect recognized JSON-RPC errors, and fall back only when the response identifies a legacy server.
Blind fallback is unsafe and difficult to debug. An HTTP 400 can mean the server is modern but the request has a bad version, missing capability, or header mismatch.
The current versioning specification uses three terms:
| Era | Meaning |
|---|---|
| Modern | Revision `2026-07-28` or later, with per-request metadata |
| Legacy | Revision `2025-11-25` or earlier, with an initialization handshake |
| Dual-era | Client or server that supports both models |
A modern server must implement server/discover. A client may call that method before other operations to learn the server's supported versions, although current MCP does not require a separate discovery round trip.
A dual-era HTTP client can follow this flow:
| Response | Likely interpretation | Client action |
|---|---|---|
| Successful modern JSON or SSE | Modern endpoint | Continue |
| `400` with `-32022` | Modern server rejects requested version | Choose from advertised versions |
| `400` with `-32020` | Modern header validation failed | Correct request |
| `404` with `-32601` JSON-RPC body | Modern server lacks that method | Do not assume legacy |
| `400`, `404`, or `405` without recognized modern body | Possible legacy server or wrong path | Continue controlled fallback |
| SSE `endpoint` event after GET | Legacy HTTP+SSE server | Use advertised POST URI |
The current transport specification says clients should inspect a 400 body before falling back. It also describes the additional GET probe for the old HTTP+SSE transport.
Cache an era decision for the relevant server origin when appropriate. Re-probe when configuration changes or the cached assumption fails.
Do not convert authentication failures into transport fallback. A 401 or 403 should enter the authorization path, not trigger an attempt to use an older and potentially weaker transport.
In short: Transport detection is a protocol decision, not “try random URLs until one works.” Send a valid modern request first. Treat recognized modern errors as repair instructions. Fall back only when the status and response body indicate that the server belongs to an older era.
Which HTTP Headers Does Current Streamable HTTP Require?
Current Streamable HTTP requires version and method headers, plus a name header for tool, resource, and prompt operations.
The body remains the source of truth. The transport mirrors selected fields so gateways can route or enforce policy without parsing JSON.
| Header | Direction | Requirement | Purpose |
|---|---|---|---|
| `Content-Type: application/json` | Request | Used for JSON-RPC POST body | Declares request representation |
| `Accept: application/json, text/event-stream` | Request | Required by Streamable HTTP | Allows either valid response type |
| `MCP-Protocol-Version` | Request | Required on every current POST | Mirrors body protocol version |
| `Mcp-Method` | Request | Required on every current request | Mirrors JSON-RPC `method` |
| `Mcp-Name` | Request | Required for `tools/call`, `resources/read`, and `prompts/get` | Mirrors `params.name` or `params.uri` |
| `Authorization: Bearer …` | Request | Required when the endpoint is protected | Carries an access token |
| `Origin` | Request | Validate on every incoming connection; reject an invalid present value with `403` | Helps prevent DNS rebinding |
| `X-Accel-Buffering: no` | SSE response | Recommended by current MCP | Requests immediate proxy forwarding |
The MCP-Protocol-Version header must match io.modelcontextprotocol/protocolVersion in request _meta. Mcp-Method and Mcp-Name must match their JSON-RPC fields.
A mismatch requires HTTP 400 Bad Request and JSON-RPC error -32020, named HeaderMismatch. This validation prevents one component from authorizing a header value while the MCP server executes a different body value.
Current Streamable HTTP also supports Mcp-Param-* headers. A tool schema can mark certain primitive parameters with x-mcp-header, allowing gateways to inspect those values.
That feature needs strict handling:
- Clients must mirror designated values.
- Servers must compare recognized parameter headers with the body.
- Header names are case-insensitive.
- Method and parameter values remain case-sensitive.
- Unsafe or non-ASCII values use the specification's Base64 sentinel encoding.
- Intermediaries must forward unrecognized headers rather than inventing their meaning.
Do not place arbitrary secrets in mirrored tool-parameter headers. Headers are commonly logged by infrastructure. Schema authors should expose only values that genuinely support routing or policy.
In short: Current MCP headers make requests visible to HTTP infrastructure, but duplication creates a validation duty. Mirror version, method, and applicable name correctly. Validate header-body agreement on every request, protect authorization data, and use custom parameter headers only for deliberate policy needs.
How Do You Run Streamable HTTP Behind NGINX, Load Balancers, and API Gateways?
Streamable HTTP needs unbuffered SSE, compatible timeouts, forwarded MCP headers, secure routing, and disconnect testing at every hop.
A JSON response can work while streaming remains broken. Production verification must therefore test both response modes through the exact CDN, web application firewall, API gateway, load balancer, ingress, and reverse proxy path.
| Hop behavior | Failure symptom | Required review |
|---|---|---|
| Buffers upstream response | Progress events arrive together | Disable buffering for SSE |
| Uses a short idle timeout | Quiet subscription closes | Align timeout and keep-alive interval |
| Rejects long POST responses | Stream ends before result | Confirm maximum request duration |
| Drops custom MCP headers | Server returns header errors | Preserve and validate headers |
| Rewrites the MCP path | `404` or legacy fallback | Define one canonical endpoint |
| Caches responses | Stale or cross-client data risk | Do not cache request-specific responses |
| Retries POST automatically | Duplicate side effects | Disable blind retries for mutations |
| Delays disconnect propagation | Cancelled work continues | Test client abort through every hop |
NGINX's proxy module documentation says proxy_buffering is on by default. NGINX can disable it through configuration or an X-Accel-Buffering: no response header.
A minimal location might include:
The 300s value is illustrative. Set it from measured request and quiet-subscription behavior. A keep-alive interval must be shorter than every relevant idle timeout.
AWS defines an Application Load Balancer's connection idle timeout as a period with no data sent or received. Other gateways use different names and limits.
Current MCP encourages an SSE comment line during quiet long-lived streams:
The client ignores that line, but the bytes can keep an intermediary from treating the connection as idle.
Headers enable useful policy. A gateway can meter Mcp-Method: tools/call differently from tools/list. It can apply a rule to a named tool through Mcp-Name.
The origin still needs authorization and header-body validation. A gateway decision alone cannot make an unvalidated body safe.
In short: Test Streamable HTTP as a full request path, not just an application handler. Disable SSE buffering, align timeouts, preserve metadata, prevent unsafe POST retries, and verify cancellation through production infrastructure. One successful JSON call does not prove that long-lived streaming works.
How Should MCP Clients Handle Disconnects, Retries, and Cancellation?
MCP clients should treat a current SSE stream closure as cancellation and retry mutations only with explicit duplicate protection.
Disconnect semantics changed between MCP eras. Under 2025 Streamable HTTP, a disconnection did not necessarily cancel work, and streams could resume through Last-Event-ID. Under current Streamable HTTP, closing a request's SSE response is the cancellation signal.
The current cancellation specification requires servers to treat that disconnect as cancellation. Servers should stop work promptly and release associated resources.
| Event | What the client knows | Safe default |
|---|---|---|
| JSON response received | Request completed with that response | Process once |
| SSE final result received | Streamed request completed | Close normally |
| Client intentionally closes SSE | Cancellation was requested | Do not retry automatically |
| SSE drops before final result | Completion is uncertain | Inspect operation state before retry |
| Subscription drops | Notifications may have been missed | Re-listen and refetch relevant state |
| Request times out after side effect | Server outcome may be unknown | Use idempotency or status lookup |
| `429` or transient `5xx` | Retry may be allowed | Respect policy and operation semantics |
Timeouts should exist for every request. That cancellation specification allows an implementation to reset a timeout after progress, but it recommends a maximum timeout even when progress continues.
Retries need operation awareness. Listing tools is naturally safer to repeat than creating a resource. A rotation or deletion can complete even when its response is lost.
MCP does not supply a universal HTTP idempotency key for every tool call. A tool can accept an operation identifier, return a durable job handle, or expose a lookup method. The right pattern belongs in the tool's contract.
Current streams do not support Last-Event-ID. Sending that header will not restore a request or subscription. A recovery workflow should query authoritative state instead of guessing from the last visible event.
In short: A disconnect is not a generic retry instruction. Current SSE closure cancels its request, while a lost response can leave the side effect uncertain. Use bounded timeouts, explicit idempotency, durable handles, and state lookup before safely repeating a mutating tool.
How Do You Secure a Remote Streamable HTTP MCP Server?
Remote Streamable HTTP MCP servers need Origin validation, authentication, token audience checks, authorization, and input validation.
Transport security protects the connection. Tool authorization protects the operation. A valid HTTP request must still be denied when its identity lacks permission for the named tool or resource.
| Risk | Current control |
|---|---|
| DNS rebinding against a local server | Validate `Origin`; bind local services to `127.0.0.1` |
| Unauthenticated remote calls | Require suitable authentication |
| Token sent to wrong server | Validate resource and audience binding |
| Token leakage | Use the Authorization header, TLS, redacted logs, and secure storage |
| Header-policy confusion | Validate mirrored header values against JSON body |
| Overpowered tool access | Apply least privilege and per-operation authorization |
| Modified MRTR state | Integrity-protect `requestState` and bind it to principal and request |
| Cross-user application handle | Authorize every handle lookup against the caller |
| Resource exhaustion | Bound request time, stream count, body size, and concurrency |
| Unsafe tool output or metadata | Treat external content as untrusted and validate it |
The current MCP authorization specification makes authorization optional for MCP as a whole. When HTTP authorization is implemented, it defines an OAuth-based flow and protected-resource discovery.
Access tokens belong in the Authorization header on every protected HTTP request. They must not appear in the URI query string. The MCP server must validate that a token was issued for that resource.
Do not accept a token merely because its signature is valid. Validate issuer, audience, expiry, scopes, and the operation being requested. Never pass an MCP access token through to an unrelated downstream API.
Current transport metadata is not authentication. Mcp-Method, Mcp-Name, client information, and the removed Mcp-Session-Id do not prove user identity.
Local servers also need care. Binding a development MCP server to 0.0.0.0 can expose it beyond the developer's machine. A local stdio package can read privileges from its environment, so install and run only trusted code.
In short: Secure remote MCP at several layers. Protect TLS and tokens, validate Origin, bind tokens to the MCP resource, authorize every operation, compare headers with the body, and validate explicit state. Transport compliance alone does not make an exposed tool safe.
Which SSE and Streamable HTTP Mistakes Cause MCP Failures?
SSE and Streamable HTTP failures usually trace to wrong-era endpoints, missing headers, buffering, timeouts, or unsafe retries.
The Five-Signal MCP Transport Test separates those failures by endpoint, method, response, state, and version.
| Symptom | Likely cause | First check |
|---|---|---|
| GET `/mcp` returns `405` | Server implements current POST-only transport | Confirm negotiated revision |
| POST `/sse` returns `404` | Client uses a legacy path against a modern server | Change to documented MCP URL |
| POST returns `400` | Missing header, mismatch, capability, or version problem | Parse JSON-RPC error body |
| Client waits forever after `202` | Legacy client expects shared SSE return path | Verify both old endpoint roles |
| Progress arrives at completion | Reverse proxy buffered SSE | Disable buffering and inspect headers |
| Stream closes during quiet period | Idle timeout | Add comments and align timeout values |
| GET or DELETE session workflow returns `405` | Client assumes 2025 behavior | Use current per-request metadata |
| Resume loses messages | Current server ignores `Last-Event-ID` | Re-listen and refetch state |
| Calls fail only after scaling out | Hidden session or local application state | Trace instance routing and handles |
| One mutation happens twice | Client or gateway retried POST | Add idempotency and stop blind retries |
| Gateway allows one tool but another executes | Header and body were not compared | Enforce `HeaderMismatch` validation |
| Local server is reachable from another machine | Bound to all interfaces | Bind to loopback and add auth |
Five wire signals provide a faster diagnosis:
- Endpoint: Does the client have one MCP URL or separate SSE and message URLs?
- Method: Is it sending current POST traffic, an older GET, or DELETE for a session?
- Response: Is the server returning JSON, request-scoped SSE, or a shared event stream?
- State: Does traffic carry an endpoint event, session ID, or per-request _meta?
- Version: Does the flow initialize a connection or declare the revision on each request?
Capture headers and status codes without logging tokens or sensitive tool arguments. Include the selected protocol revision, response content type, request ID, method, tool name, duration, and disconnect reason.
In short: Most transport failures become understandable once the wire era is known. First identify endpoint shape, HTTP method, response channel, state mechanism, and version behavior. Then fix the specific mismatch instead of changing unrelated tool code or repeatedly downgrading the connection.
Which MCP Transport Does the Proxidize MCP Server Use?
Proxidize's MCP server uses stdio: an MCP client launches a local Node.js process that manages proxies through the Proxidize API.
As of September 3, 2026, the open-source package runs through npx -y @proxidize/mcp. Claude Code, Claude Desktop, Cursor, Windsurf, or VS Code starts that process through its MCP configuration.
The current Proxidize MCP server source imports StdioServerTransport, connects the server to it, and logs that the server is running on stdio.
This design means users do not choose between SSE and Streamable HTTP when installing the current package. The MCP client owns the local process and communicates through standard input and output.
The official Proxidize MCP server page explains that the server discovers active subscriptions and exposes relevant proxy-management tools. Those tools can rotate IPs, inspect usage, manage access points, and query analytics according to the account's available plans.
The API token belongs in the server environment. It should not be committed into a shared MCP configuration file. Local process transport removes the remote MCP authorization flow, but it does not make the API token or tool permissions harmless.
A future independently hosted version could use Streamable HTTP. That would be a separate deployment decision requiring remote authentication, authorization, tenancy, and operations. It is not how the current open-source package runs.
In short: The current Proxidize MCP server is a practical stdio example. The AI client launches it locally, and the process calls the Proxidize API with an environment token. SSE and Streamable HTTP apply when designing an independently hosted remote MCP service, not this installation path.
How Do MCP Transports and Web Proxies Fit Together?
MCP transports carry agent tool messages; web forward proxies route an application's outbound requests to public destinations.
The two systems can appear in one workflow without performing the same job. MCP is the control and capability interface. A forward proxy is part of the outbound network path.
| Layer | Main purpose | Common protocol | Identity or control |
|---|---|---|---|
| MCP transport | Carry tool, resource, prompt, and notification messages | stdio or Streamable HTTP | MCP client, server, and tool authorization |
| Proxidize API | Manage proxy infrastructure | HTTPS API | Proxidize API token |
| Forward proxy | Route selected outbound web requests | HTTP, HTTPS proxying, or SOCKS5 | Proxy credentials, location, and session settings |
| Public destination | Serve the requested website or API | HTTP or HTTPS | Destination authorization and site policy |
An MCP tool can ask Proxidize to rotate a proxy or inspect usage. That tool call does not automatically route the AI host's other HTTP traffic through the proxy.
The crawler, browser, or data tool must configure the returned proxy endpoint separately. Its public request then travels through the selected exit network.
Proxidize Residential Proxies fit global public-web collection that needs broad country, city, or ISP selection. Proxidize Mobile Proxies fit workflows that specifically need real mobile carrier exits.
Use either product only for legitimate, authorized work. Proxies can support web scraping, AI data collection, SEO monitoring, price monitoring, market research, and brand protection. They do not grant permission to access a target.
The distinction also avoids a naming trap. A reverse proxy in front of an MCP endpoint manages inbound service traffic. A Proxidize forward proxy manages selected outbound traffic. The forward proxy, reverse proxy, API gateway, and egress gateway comparison explains those roles in detail.
In short: MCP transport and proxy transport belong to different connections. MCP carries agent instructions to a tool server. A forward proxy carries the tool's or application's outbound web request. Configure, authenticate, observe, and secure each boundary independently throughout the full stack.
What Should You Remember About SSE and Streamable HTTP?
SSE remains a streaming format inside current MCP, while Streamable HTTP is the standard remote transport for new implementations.
- SSE remains part of current MCP. MCP deprecated its specific 2024-11-05 HTTP+SSE transport, not the underlying format.
- One current endpoint accepts POST. Each JSON-RPC request receives JSON or request-scoped SSE; 2025-era sessions and GET streams are gone.
- stdio remains current. Use it when an MCP client launches and owns a local server process.
- Remote deployment adds an operations boundary. Test buffering, timeouts, cancellation, retries, authentication, and authorization.
- Stateless transport is not stateless business logic. Use explicit, authorized handles for jobs or workflows that span requests.
- Migration should be observable. Keep legacy endpoints only while supported clients generate real traffic.
- MCP and forward proxies solve different problems. MCP carries tool messages; Proxidize routes selected outbound web requests.
The durable decision rule is simple. Pick stdio for a child process. Pick current Streamable HTTP for a remote service. Treat the word “SSE” as a clue that needs a second question: does it mean a response format or the deprecated two-endpoint MCP transport?
In short: A correct transport choice starts with process boundary and protocol revision. Current remote MCP uses stateless POST requests with optional SSE responses when useful. Local packages use stdio. Legacy HTTP+SSE belongs in a measured compatibility plan, not a new architecture.