Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a new remote MCP server, start with Streamable HTTP. The older HTTP+SSE transport is still useful when a client only implements the 2024-11-05 protocol transport or when you must preserve an existing integration. In that legacy design, GET /sse opens a long-lived event stream and POST /messages receives JSON-RPC requests for the stream’s session.

This guide shows the official TypeScript compatibility pattern, explains host and request-size settings, and then outlines how to migrate to Streamable HTTP. “SSE” here means MCP’s older HTTP+SSE transport; it does not mean every modern MCP server must maintain a server-sent-events connection.

Choose the transport before writing code

Concern Legacy HTTP+SSE Streamable HTTP
Protocol status 2024-11-05 transport retained for backward compatibility Recommended for new remote servers
Endpoints Separate long-lived GET /sse and POST /messages One Streamable HTTP endpoint handling POST request/response; SSE can be enabled for notifications
Session model Each SSE connection gets a session ID; POST messages must be routed to that session Built-in session management and resumability options
Client compatibility Required for clients that only speak the older transport Preferred by current clients and new implementations
SDK status Deprecated bridge; the v2 SDK does not serve this transport directly Current implementation path

The MCP TypeScript SDK v1 server guide states that “The older HTTP+SSE transport (protocol version 2024‑11‑05) is supported only for backwards compatibility.” Read the current guidance at the MCP TypeScript SDK server guide and the transport details in the MCP transport specification.

When legacy SSE is the right choice

  • A production client explicitly documents support for HTTP+SSE but not Streamable HTTP.
  • You are replacing an existing server without being able to upgrade all clients at once.
  • You need a short-lived compatibility bridge while clients migrate.

For a greenfield v2 server, do not build new application logic around the legacy class. The v2 migration guide says SSEServerTransport was removed from v2; the frozen copy is a temporary bridge and is planned for removal in v3. Verify package exports and version instructions when you install the SDK.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build the compatibility server in TypeScript

1. Install the SDK and web framework

The example below follows the v2 legacy-client guide. It uses the frozen transport package, Express, and the MCP server class. Pin versions that are compatible with your application rather than copying a floating dependency range.

npm install @modelcontextprotocol/server @modelcontextprotocol/server-legacy express
npm install -D typescript tsx @types/express

2. Create a server factory

Create a fresh MCP server for every SSE connection. A transport represents one client session, so sharing one server instance across unrelated connections can leak state.

import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";

export function createServer() {
  const server = new McpServer({
    name: "example-sse-server",
    version: "1.0.0",
  });

  server.tool(
    "add",
    "Add two numbers",
    { a: z.number(), b: z.number() },
    async ({ a, b }) => ({
      content: [{ type: "text", text: String(a + b) }],
    }),
  );

  return server;
}

Register your real tools, resources, and prompts in this factory. Keep tool input schemas explicit so malformed JSON-RPC arguments fail with a useful validation error.

3. Add the SSE and message routes

This is the important part: keep a map from session IDs to SSEServerTransport objects, create the transport on GET /sse, and dispatch each POST /messages request to the matching transport.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import express from "express";
import { SSEServerTransport } from "@modelcontextprotocol/server-legacy/sse";
import { createServer } from "./server.js";

const app = express();
const port = Number(process.env.PORT ?? 3000);

// The legacy SSE transport accepts messages up to 4 MB in the documented example.
app.use(express.json({ limit: "4mb" }));

const transports = new Map<string, SSEServerTransport>();

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  transports.set(transport.sessionId, transport);

  res.on("close", () => {
    transports.delete(transport.sessionId);
  });

  const server = createServer();
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  const sessionId = req.query.sessionId;

  if (typeof sessionId !== "string") {
    res.status(400).json({ error: "sessionId must be a string" });
    return;
  }

  const transport = transports.get(sessionId);
  if (!transport) {
    res.status(404).json({ error: "Unknown sessionId" });
    return;
  }

  await transport.handlePostMessage(req, res);
});

app.listen(port, "127.0.0.1", () => {
  console.log(`MCP SSE server listening on http://127.0.0.1:${port}`);
});

The transport sends an initial SSE endpoint event containing a URL such as /messages?sessionId=…. The client posts JSON-RPC traffic to that URL while reading responses from the open event stream.

4. Run and connect

npx tsx src/index.ts

Point an SSE-capable MCP client at http://127.0.0.1:3000/sse. Do not treat a browser tab opened on /sse as a complete test: the endpoint is an event stream, and a valid MCP client must parse the endpoint event and then post protocol messages using the supplied session ID.

Host validation and safe remote deployment

When the process listens only on localhost, the SDK’s local assumptions are usually sufficient. Once you bind to a non-loopback address, explicitly configure which host names the server accepts. The v2 example binds to 0.0.0.0 while allowing sse.example.com. Binding beyond localhost can drop default Host/Origin validation and expose DNS-rebinding risk if you do not list the hosts you actually serve.

app.listen(port, "0.0.0.0", () => {
  console.log("Listening on all interfaces");
});

Use the host-allowlisting mechanism documented for your exact SDK version, terminate TLS at a trusted proxy or the application, and restrict access with authentication before exposing the endpoint to the public internet. Do not accept arbitrary Host or Origin values simply because a reverse proxy is in front of the process.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Request size, sessions, and lifecycle details

Raise the JSON limit deliberately

Express defaults to a 100 KB JSON body. The documented legacy transport example raises the limit to 4 MB because the transport accepts messages up to that size. Treat 4 MB as the example’s configuration, not a universal requirement: choose a smaller ceiling if your tools do not need large payloads, and enforce matching limits at your proxy.

Clean up disconnected clients

The close handler removes the session from the map. Without it, every abandoned browser, proxy timeout, or network interruption leaves a transport reference in memory. If your application allocates additional per-session resources, release them in the same handler.

Do not assume sessions survive restarts

The in-memory map is process-local. A restart invalidates every session, and multiple instances require sticky routing or a shared session design. Streamable HTTP is the better foundation when you need resumability and a modern session model.

Use Streamable HTTP for a new server

The v1 guide points new implementations to simpleStreamableHttp.ts. Start there, remove features you do not need, and register your own tools, resources, and prompts. Streamable HTTP uses POST request/response exchanges and can optionally return SSE for server-to-client notifications; it can also use JSON-only responses when streaming is unnecessary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This means “I need SSE” is not always a reason to select the legacy transport. If your requirement is notifications from server to client, enable the Streamable HTTP notification mode and retain the current protocol, session, and resumability behavior. Choose the frozen SSE bridge only when an actual client compatibility constraint requires it.

Supporting old and new clients during migration

  1. Implement the primary server with Streamable HTTP and test it with current clients.
  2. Expose the legacy /sse and /messages routes using the frozen bridge package for clients that cannot migrate yet.
  3. Keep tool behavior identical behind both transports; transport-specific code should be limited to connection and request handling.
  4. Log which transport each client uses, then contact owners of remaining legacy clients.
  5. Remove the bridge after those clients support Streamable HTTP and your compatibility window ends.

The bridge is deliberately temporary: the migration guide confirms that SSEServerTransport was removed from v2 and points developers toward Streamable HTTP.

Troubleshooting common failures

“Cannot find module @modelcontextprotocol/server-legacy/sse”

The frozen bridge package is not installed, or your SDK version does not export the path. Install the package named in the v2 legacy-client guide and verify its version and export map. Do not substitute a similarly named transport from an unrelated package.

The client receives an SSE stream but never sends requests

Check that the client parses the initial endpoint event and posts to /messages?sessionId=…. Posting to /messages without the query parameter cannot be routed to a session.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

“Unknown sessionId” after reconnecting

The old connection was closed, the process restarted, or a load balancer sent the POST to another instance. Reconnect to /sse to obtain a new session and configure sticky routing or a shared design for multi-instance deployments.

413 Payload Too Large

Raise the Express JSON limit only as far as required and align the reverse proxy’s limit. The documented example uses 4 MB; larger values increase memory and denial-of-service risk.

Requests fail only through a proxy

Confirm that the proxy preserves long-lived streaming responses, disables buffering for the SSE route, forwards the query string, and allows idle connections to remain open. Also verify that the public host appears in your allowlist.

Nothing works when binding to 0.0.0.0

Binding changes the attack surface and can disable default host checks. Configure explicit allowed hosts and origins, use TLS, and test with the exact DNS name clients will send.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your MCP tools need website screenshots rather than an MCP transport tutorial, ScreenshotNeo provides a screenshot API and MCP server. A single request returns PNG, JPEG, WebP, or PDF; it accepts cookie banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.

Use the API directly (the full option list is in the ScreenshotNeo documentation):

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

It also includes an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Frequently Asked Questions

Does an MCP server have to use SSE?

No. Streamable HTTP is the recommended transport for new remote servers and can optionally use SSE for server-to-client notifications.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which protocol version does legacy MCP HTTP+SSE implement?

It corresponds to the 2024-11-05 transport and is retained for backward compatibility.

Can I share one SSE transport between clients?

No. Each SSE connection is a session; keep a separate transport and session ID for each client.

What should I migrate to from SSEServerTransport?

Migrate to Streamable HTTP. The v2 migration guide describes the legacy package as a temporary frozen bridge.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.