Migrate Your Claude Connector from SSE to Streamable HTTP (May 2026)

Migrate your Claude Connector from SSE to Streamable HTTP transport.
TL;DR: If your Claude Connector still uses HTTP+SSE, migrate it to Streamable HTTP. The latest MCP spec uses Streamable HTTP as the standard remote transport, Claude documents legacy HTTP+SSE as deprecated, and the current transport rules add details that many older migration guides miss: MCP-Protocol-Version, MCP-Session-Id, Origin validation, resumable SSE polling, and a single MCP endpoint that supports POST and GET.
The March 2025 MCP spec made Streamable HTTP the standard remote transport. Since then, the ecosystem has moved further in that direction: Claude’s connector docs list Streamable HTTP as the transport protocol, the latest MCP transport spec is 2025-11-25, and MCP Apps became the first official MCP extension in January 2026. If you want your connector to keep working across Claude, ChatGPT, VS Code, Goose, and other MCP hosts, Streamable HTTP is the baseline you should target.
This guide keeps the migration practical. It shows the endpoint change, TypeScript and Python examples, session choices, backwards compatibility, auth and proxy gotchas, and a test plan you can run before you point Claude at your production URL.
What Changed Since the Old SSE Transport
The old HTTP+SSE transport used two routes:
- The client opened a long-lived SSE connection with
GET /sse - The server sent an
endpointevent with a URL for client messages - The client sent JSON-RPC messages to that URL with
POST - The server streamed responses over the SSE connection
That model worked for early local clients, but it caused production problems. Long-lived connections do poorly on many serverless platforms, load balancers need sticky sessions, proxies need special handling, and the old transport did not give the protocol a clean session model.
Streamable HTTP changes the shape:
- One MCP endpoint, commonly
/mcp, handles the transport - Every client JSON-RPC message is sent as a new
POST - The server returns
application/jsonfor a single response ortext/event-streamwhen it needs to stream - The client may send
GETto open an SSE stream for server-to-client messages - Stateful servers use
MCP-Session-Id - HTTP clients include
MCP-Protocol-Versionafter initialization - Servers validate
Originto block DNS rebinding attacks
SSE did not disappear. It moved from being the transport to being one response mode inside Streamable HTTP. That distinction matters because your connector can now run behind normal HTTP infrastructure while still streaming when the request needs it.
Migration Checklist
Before changing code, write down the current shape of your connector:
- Current SSE URL, usually
/sse - Current message URL, usually
/message - Current auth behavior, including whether auth happens before initialization
- Whether the server stores session state in memory
- Whether the connector streams responses or always returns complete JSON
- Reverse proxy rules for CORS, allowed methods, request body size, and timeouts
- Production platform limits, especially connection duration and cold starts
The migration usually means:
- Add a Streamable HTTP endpoint, usually
/mcp - Keep the old SSE endpoints during rollout if existing clients still use them
- Move client message handling to
POST /mcp - Add
GET /mcpif your server supports server-to-client streams - Add
DELETE /mcpif your server supports explicit session termination - Validate
Originon incoming HTTP requests - Decide between stateless and stateful sessions
- Update auth to return a real
401challenge when authorization is needed - Test initialize, tool discovery, tool calls, streaming, session expiry, and auth refresh
If you only do the transport class swap, you will probably miss proxy and auth behavior. Most migration bugs happen outside the tool handlers.
Migration: TypeScript SDK
Update the MCP SDK first:
pnpm add @modelcontextprotocol/sdk@latest
Before: HTTP+SSE
Older Express servers often used SSEServerTransport with two routes:
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
const app = express();
const server = new McpServer({ name: "my-connector", version: "1.0.0" });
server.tool("get_tickets", { status: z.string() }, async ({ status }) => {
return {
content: [{ type: "text", text: `Tickets with status ${status}` }],
};
});
let transport: SSEServerTransport;
app.get("/sse", async (req, res) => {
transport = new SSEServerTransport("/message", res);
await server.connect(transport);
});
app.post("/message", async (req, res) => {
await transport.handlePostMessage(req, res);
});
app.listen(8000);
That shape assumes one live SSE connection owns the session.
After: Streamable HTTP
The new endpoint receives POST and GET on the same path:
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
const app = express();
app.use(express.json());
const server = new McpServer({ name: "my-connector", version: "1.0.0" });
server.tool("get_tickets", { status: z.string() }, async ({ status }) => {
return {
content: [{ type: "text", text: `Tickets with status ${status}` }],
};
});
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
await server.connect(transport);
app.post("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
app.get("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
app.delete("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
app.listen(8000);
sessionIdGenerator: undefined puts this example in stateless mode. That is a good default for simple tool servers because any instance can handle any request. If your connector needs server-side state across requests, use stateful mode and store sessions somewhere shared, such as Redis or durable storage, rather than in one Node process.
Migration: Python SDK
Update the Python SDK first:
uv add mcp
or:
pip install --upgrade mcp
Before: HTTP+SSE
from mcp.server import Server
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Route
server = Server("my-connector")
sse = SseServerTransport("/message")
# Register tools with the server.
app = Starlette(
routes=[
Route("/sse", endpoint=sse.handle_sse_connection),
Route("/message", endpoint=sse.handle_post_message, methods=["POST"]),
]
)
After: Streamable HTTP
from mcp.server import Server
from mcp.server.streamable_http import StreamableHTTPServerTransport
from starlette.applications import Starlette
from starlette.routing import Route
server = Server("my-connector")
transport = StreamableHTTPServerTransport(
stateless_http=True,
json_response=True,
)
# Register tools with the server.
async def handle_mcp(request):
await transport.handle_request(request.scope, request.receive, request.send)
app = Starlette(
routes=[
Route("/mcp", endpoint=handle_mcp, methods=["POST", "GET", "DELETE"]),
]
)
For most data connectors, json_response=True is easier to operate because the server returns a normal JSON response instead of opening an SSE stream. Use streaming only when you need incremental output, server-to-client notifications, or long-running tool calls where the host should receive progress.
Stateful vs Stateless Sessions
Streamable HTTP gives you a choice that HTTP+SSE did not make clean.
Use stateless mode when:
- Tool calls are independent
- State lives in your database or upstream API
- You deploy to Cloudflare Workers, Vercel, Fly, Lambda, or another horizontally scaled platform
- You do not need resumable streams
Use stateful mode when:
- The server holds a multi-step workflow state
- You need resumable SSE streams
- You coordinate server-to-client notifications
- You can store session data outside the process
In stateful mode, the server returns MCP-Session-Id during initialization. The client sends that same header on later requests. If the session expires, the server must return 404 Not Found, and the client starts again with a fresh initialize request.
Do not assume sticky sessions are enough. They can hide bugs in staging and then fail during deploys, restarts, or regional failover. If the session matters, store it in shared storage. If it does not matter, run stateless.
The Protocol Version Header
The latest transport spec requires HTTP clients to include MCP-Protocol-Version on requests after initialization. The header value should be the negotiated protocol version, such as:
MCP-Protocol-Version: 2025-11-25
Servers should be tolerant during migration. If the header is missing and you cannot infer the negotiated version another way, the spec says servers should assume 2025-03-26 for backwards compatibility. If the header is present but invalid or unsupported, return 400 Bad Request.
This header is easy to forget because older Streamable HTTP examples did not include it. Add it to your test cases now, especially if you run custom middleware in front of the SDK transport.
Auth Changes to Check While You Migrate
Transport and auth tend to break together because the old endpoint layout often had auth bolted onto only one route.
Claude’s current connector docs say Claude supports OAuth with Dynamic Client Registration, OAuth with Client ID Metadata Document, Anthropic-held client credentials for approved cases, custom connection flows for approved cases, and authless servers. They also say user-pasted bearer tokens are not yet supported and tokens in connector URL query parameters are not supported.
For production connectors, check these details:
- Return
401 Unauthorizedwith the rightWWW-Authenticatechallenge when auth is needed - Do not return a normal MCP tool error for missing auth
- Do not pass access tokens in URL query strings
- Support PKCE
S256 - Make refresh token handling work after server restarts
- Apply auth consistently to
POST /mcp,GET /mcp, andDELETE /mcp - Keep protected resource metadata reachable from the unauthenticated challenge
If you are submitting to the Claude Connector Directory, read the current Claude auth docs before you freeze your implementation. Auth behavior changes more often than tool handler code.
Supporting Both Transports During Rollout
If you have existing clients that still use HTTP+SSE, keep both transports for a release window:
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
const streamableTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
await server.connect(streamableTransport);
app.post("/mcp", async (req, res) => {
await streamableTransport.handleRequest(req, res);
});
app.get("/mcp", async (req, res) => {
await streamableTransport.handleRequest(req, res);
});
app.delete("/mcp", async (req, res) => {
await streamableTransport.handleRequest(req, res);
});
app.get("/sse", async (req, res) => {
const legacyTransport = new SSEServerTransport("/message", res);
await server.connect(legacyTransport);
});
app.post("/message", async (req, res) => {
// Route the old client message endpoint to the legacy transport.
});
The MCP spec explicitly allows this backwards compatibility setup. It is cleaner than trying to make /message also be your new MCP endpoint.
During rollout, log the transport path and user agent for each connection. Once /sse traffic drops to zero for a full release cycle, remove the legacy routes and simplify your proxy rules.
Proxy, CORS, and Hosting Gotchas
Most Streamable HTTP migrations fail in deployment, not locally.
Allowed Methods
Your proxy probably allows GET /sse and POST /message. Update it for:
POST /mcpGET /mcpDELETE /mcp, if you support explicit session terminationOPTIONS /mcp, if browsers or hosted clients need CORS preflight
Accept Headers
Streamable HTTP clients send Accept: application/json, text/event-stream on POST because either response type is valid. Middleware that strips or rewrites Accept can break negotiation.
Origin Validation
The MCP spec requires Streamable HTTP servers to validate Origin on incoming requests. If the Origin header is present and invalid, return 403 Forbidden. Local development servers should bind to 127.0.0.1 instead of 0.0.0.0 unless you know why you need broader access.
Timeouts
If you stream with SSE, check every timeout in the path: CDN, load balancer, app server, platform, and upstream API. Streamable HTTP supports polling behavior for long streams, but a proxy that buffers or kills streams early can still break results.
Cold Starts
Streamable HTTP works much better on serverless platforms than the old SSE transport, but cold starts still count against tool call latency. Lazy-load heavy SDKs, keep global initialization small, and warm expensive upstream clients only when a tool needs them.
Testing the Migration
Run transport tests before UI tests. A good minimum suite covers:
initializeoverPOST /mcptools/listover the same endpoint- One happy-path
tools/call - One tool error with a valid MCP error shape
- Auth-required request returning
401, not a tool result - Invalid
Originreturning403 - Expired
MCP-Session-Idreturning404in stateful mode - Invalid
MCP-Protocol-Versionreturning400 - A streaming response, if your connector uses SSE
- A legacy
/sseclient, if you keep backwards compatibility
After the transport is solid, test the host behavior. sunpeak’s Inspector replicates Claude and ChatGPT runtimes locally, so you can call tools, render resources, switch host shells, test dark mode and light mode, and catch layout bugs without burning host credits.
For a new sunpeak app:
npx sunpeak new
pnpm dev
For an existing MCP server:
npx sunpeak inspect --server http://127.0.0.1:8000/mcp
Then deploy the connector and add the production MCP endpoint in Claude Settings > Connectors. Test at least one conversation for each high-risk tool, including any tool that reads private data, mutates state, uploads files, streams output, or renders an MCP App UI.
Where MCP Apps Fit
This migration is not only about Claude. MCP Apps are now an official MCP extension, and the same connector can serve interactive UI to hosts that support the extension. The current MCP Apps pattern is:
- A tool declares UI metadata pointing at a
ui://resource - The host fetches the resource from the MCP server
- The host renders the HTML in a sandboxed iframe
- The app and host communicate over JSON-RPC through
postMessage
That means your transport layer needs to be boring and reliable. If the host cannot initialize, list tools, fetch resources, or call tools consistently, the UI never gets a chance to work. Streamable HTTP gives MCP Apps a more production-friendly base because the same endpoint can handle tool calls, resource access, auth challenges, and streaming without relying on one long-lived SSE connection.
When to Migrate
If you are building a new Claude Connector, start with Streamable HTTP. There is no reason to build new HTTP+SSE infrastructure in 2026.
If you already have an SSE connector in production, add Streamable HTTP now and run both transports briefly. The code change is usually small, but the surrounding work deserves care: auth, proxy rules, session state, headers, and tests. Once your logs show that clients have moved to /mcp, remove /sse and /message.
If you are also building UI, treat this as a good time to add automated tests. sunpeak lets you test Claude Connectors, ChatGPT Apps, and MCP Apps from one local workflow, with a host inspector, Playwright tests, visual regression tests, and CI-friendly simulations. Transport changes are exactly the kind of work that should be checked by automation because a one-line proxy rule can break every tool call.
Get Started
npx sunpeak newFurther Reading
- Claude Connectors tutorial - build a connector from scratch
- Deploying Claude Connectors - hosting options and production setup
- Testing Claude Connectors - unit tests, local inspector, and CI/CD
- Debugging Claude Connectors - fix connection failures and tool errors
- Claude Connector OAuth authentication - current auth patterns
- Claude Connector Directory submission - requirements for getting listed
- Claude Connector Framework - sunpeak overview
- MCP Transports specification - latest Streamable HTTP rules
- Claude custom connector documentation
- sunpeak documentation
Frequently Asked Questions
Is SSE deprecated for Claude Connectors?
Yes. The MCP specification replaced the legacy HTTP+SSE transport with Streamable HTTP. Claude still supports both Streamable HTTP and legacy HTTP+SSE, but Claude documentation says the legacy transport is being deprecated in favor of Streamable HTTP. New connector work should use Streamable HTTP.
What is the difference between HTTP+SSE and Streamable HTTP in MCP?
HTTP+SSE used a long-lived GET /sse connection plus a separate POST endpoint for client messages. Streamable HTTP uses one MCP endpoint, commonly /mcp, for POST and GET. A POST can return application/json or text/event-stream, so SSE remains a response format, but it is no longer the whole transport.
How do I migrate an MCP server from SSE to Streamable HTTP?
Replace SSEServerTransport with StreamableHTTPServerTransport, move from two endpoints to one MCP endpoint, add POST and GET handlers, return the right Accept and Content-Type behavior, validate Origin headers, and update session logic to use MCP-Session-Id. If you target recent protocol versions, also handle MCP-Protocol-Version after initialization.
Can I support both SSE and Streamable HTTP during migration?
Yes. The MCP transport spec describes a backwards compatibility path where servers keep the old SSE and POST endpoints alongside the new Streamable HTTP endpoint. That lets older clients keep working while Claude, newer SDKs, and directory submissions move to Streamable HTTP.
Does Claude require Streamable HTTP for custom connectors?
Claude supports Streamable HTTP and legacy HTTP+SSE today, but Claude documentation describes Streamable HTTP as the transport protocol and the legacy HTTP+SSE transport as deprecated. Treat Streamable HTTP as the production default for any new or updated Claude Connector.
What changed in the latest MCP transport spec?
The latest MCP transport spec keeps Streamable HTTP as a standard transport, requires a single endpoint that supports POST and GET, requires Origin validation for DNS rebinding protection, defines MCP-Session-Id for stateful sessions, and requires MCP-Protocol-Version on HTTP requests after initialization.
How do I test a Claude Connector after migrating to Streamable HTTP?
First test the transport directly with MCP Inspector or curl-level checks for initialize, tools/list, and tools/call. Then use sunpeak to run a local inspector that replicates Claude and ChatGPT runtimes so you can test tool output, UI resources, display modes, themes, and error states before adding the deployed connector to Claude Settings > Connectors.
What breaks most often when migrating from SSE to Streamable HTTP?
The common breaks are stale CORS rules, reverse proxies that only allow GET /sse and POST /message, missing Origin validation, incorrect Accept headers, session IDs stored in process memory without sticky sessions, missing MCP-Protocol-Version handling, and auth flows that return tool errors instead of proper 401 challenges.