MCP transports: stdio vs streamable HTTP (and why to drop SSE)
An MCP server speaks the same JSON-RPC protocol no matter how it ships, but the transport you pick decides where the server runs, who can reach it, and how much of your attack surface is exposed to the network. There are two transports worth using in 2026: stdio and streamable HTTP. A third, the old HTTP+SSE transport, is deprecated and should not be in new code. Here is how to choose, and what each choice means for security.
stdio: the local subprocess
With stdio, the client (Claude Desktop, Cursor, the Claude CLI) launches your server as a child process and talks to it over standard input and standard output. Each JSON-RPC message is one line on the pipe. There is no port, no socket, and no network listener. The server lives and dies with the client session.
A stdio entry in a client config looks like this:
{
"mcpServers": {
"files": {
"command": "node",
"args": ["/abs/path/to/server.js"],
"env": { "ROOT_DIR": "/Users/me/projects" }
}
}
}
Use stdio when:
- The server only serves the local user (filesystem access, a local database, a CLI wrapper, git).
- You want zero network exposure. Nothing is listening, so nothing remote can connect.
- You need the server to run with the user's own credentials and environment rather than a shared service account.
The security model is the process boundary, not a login. There is no auth handshake because the client already trusts the binary it spawned. That puts the weight on two things: the secrets you hand the process through env, and what the tools are actually allowed to do. A stdio server that shells out to command built from tool input is still a command-injection bug, transport notwithstanding. Local does not mean safe.
Streamable HTTP: the remote server
Streamable HTTP is the current spec transport for servers that run somewhere else and are reached over the network. The client sends JSON-RPC requests by POST to a single endpoint (commonly /mcp). The server replies with a plain JSON response for simple calls, or upgrades to a Server-Sent Events stream on the same connection when it needs to push progress, logs, or multiple messages back. One endpoint, two response shapes, decided per request by the Accept header.
A minimal remote config points at a URL instead of a command:
{
"mcpServers": {
"search": {
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer ${SEARCH_TOKEN}" }
}
}
}
Use streamable HTTP when:
- The server is shared by many users or runs as a hosted service.
- It needs to live near a backend (a database, an internal API) rather than on each laptop.
- You want to deploy and update it centrally without shipping a binary to every client.
The moment you bind a port, the threat model changes completely. The server is now reachable by anything that can route to it, so every request is potentially hostile. That means you own a real authentication story (the spec points at OAuth 2.1 for user-facing servers; a vetted bearer token is the floor), TLS so tokens are not sent in cleartext, and an Origin check plus binding to 127.0.0.1 if it is only meant to be local-but-HTTP. Skipping the Origin check is how DNS-rebinding attacks turn a "local" HTTP server into one a malicious web page can drive.
Because a remote MCP server is a normal web service, it inherits normal web bugs. A tool that fetches a URL needs SSRF protection, and any HTTP-exposed server should have rate limiting and input validation in front of it. If you are running one already, mcp-audit (github.com/alih552/mcp-audit) scans your client config and flags remote servers with no auth, cleartext http:// URLs, and similar issues, locally and with zero dependencies.
Why not SSE
The original remote transport used two endpoints: a long-lived GET /sse stream for server-to-client messages and a separate POST for client-to-server. It was deprecated in the 2025-03-26 spec revision and replaced by streamable HTTP. Avoid it in anything new for concrete reasons:
- It is deprecated. New client and SDK features target streamable HTTP. The two-endpoint design is legacy compatibility only.
- The split endpoints are awkward to secure. Two routes with shared session state means two places to get auth, CORS, and session handling right, and the persistent
GETstream is easy to leave open without an Origin check. - Long-lived connections do not survive infrastructure well. The always-open stream fights with load balancers, proxies, and serverless timeouts in ways the request-scoped streamable HTTP model does not.
If you have an old /sse server, the migration is mechanical: collapse to a single POST /mcp endpoint and let the server choose between a JSON response and an SSE upgrade per request. Most SDKs ship a streamable-HTTP server helper that does this for you.
How to choose, quickly
- Runs on the user's machine and touches their files or local tools, default to stdio.
- Runs as a shared or hosted service over the network, use streamable HTTP with auth and TLS.
- Reaching for SSE, stop and use streamable HTTP instead.
The transport is not where most MCP bugs live, but it sets the rules. stdio keeps you off the network and puts the burden on tool behavior and secret handling. HTTP opens the door to the whole internet and demands that you defend it like any other public service.
Ship a hardened HTTP MCP server, not a starter snippet
MCP Forge Kit gives you a streamable-HTTP server with auth, TLS-ready config, Origin checks, rate limiting, SSRF-safe fetch, validation, tests, and CI already wired up.
Get MCP Forge Kit, €39Related: Authentication · Deploy securely · Security checklist