Hosting Your Own Remote MCP Server — What Actually Changes
If you've built an MCP server and want other people — or your own agent, from anywhere — to reach it, the first thing to settle is which transport it speaks. The protocol defines two: stdio and Streamable HTTP. A stdio server is launched as a subprocess by the client and communicates over standard input and output, so hosting it makes no sense; it runs on the user's machine by definition. Only a Streamable HTTP server can be hosted remotely, and once it is, three things change that don't come up locally: you must bind to all interfaces rather than localhost, Origin validation stops being optional, and your sessions die whenever the container restarts.
An MCP server is a near-ideal small workload
Good news before the caveats. A Streamable HTTP MCP server is a small process that spends nearly all its time waiting for requests, and requests are usually cheap — look something up, call an API, return JSON. That's the small, long-running, mostly idle shape that runs comfortably on a modest container.
The exception is the same as ever: if your tools do heavy local work — running a model, driving a headless browser — the tool is the workload, not the protocol, and you should size for that.
Bind to 0.0.0.0, despite what the spec says
This one trips people up because they're following the specification correctly.
The MCP security guidance says: "When running locally, servers SHOULD bind only to localhost (127.0.0.1) rather than all network interfaces (0.0.0.0)" (MCP spec).
Note when running locally. That advice exists because a local server bound to 0.0.0.0 is exposed to your whole network. In a container the situation is inverted: your container has its own network namespace, and the platform routes traffic to a port inside it. A server bound to 127.0.0.1 is reachable only from inside its own container — which means nothing can reach it, including the router in front of it.
So bind 0.0.0.0 in a container, and rely on the container boundary plus authentication rather than on the interface. If your MCP server deploys and then nothing can connect, this is the first thing to check — there's no health probe to catch it for you.
Origin validation is not optional now
The same section of the spec is unambiguous: "Servers MUST validate the Origin header on all incoming connections to prevent DNS rebinding attacks" and "Servers SHOULD implement proper authentication for all connections."
Locally you might get away with skipping both. Hosted, you can't — every port binding here is public, and there's no IP filtering or WAF in front of your app. The moment your app starts, your MCP endpoint is reachable from anywhere.
Minimum viable posture:
- Validate
Originagainst an allowlist, rejecting anything unexpected. - Require a token on every request. A bearer token checked against an environment variable is a few lines and covers the common case; the spec's full authorization framework is there when you need real multi-user auth.
- Assume it will be scanned. Public endpoints get found. Same reasoning as protecting any public endpoint.
Think about what your tools can do, too. An MCP server is a remote code execution surface by design — that's the point of it. A tool that reads a file is a very different exposure from one that writes to your database.
One port binding is all you need
The transport is simple to host: "The server MUST provide a single HTTP endpoint path (hereafter referred to as the MCP endpoint) that supports both POST and GET methods."
One path, two methods. So one port binding as HTTPS gives you everything — a public URL with a valid certificate, which clients require, and no certificate work on your side.
You don't need a second port for the SSE stream. In Streamable HTTP, the stream is a response to a POST or GET on that same endpoint, not a separate service. If you're carrying an older HTTP+SSE implementation with two endpoints, that transport is deprecated — the spec's backwards-compatibility section explains keeping both alive during a migration.
Sessions die on redeploy — plan for it
Here's the platform-specific bit that catches people, and it follows from how sessions work.
A server may assign a session ID at initialization and return it in the Mcp-Session-Id header, after which clients must send it on every subsequent request. Most implementations keep that session state in memory.
Now recall what a redeploy does: your container is stopped — with about one second between the signal and the kill — removed, and recreated. In-memory sessions go with it.
The spec defines the recovery precisely: "The server MAY terminate the session at any time, after which it MUST respond to requests containing that session ID with HTTP 404 Not Found", and a client receiving that 404 "MUST start a new session by sending a new InitializeRequest."
So the correct behaviour after a restart is to return 404, not 500, for a session ID you no longer recognise. A compliant client recovers silently; a 500 just looks broken. Two practical consequences:
- Return 404 for unknown sessions. It's one branch, and it turns every redeploy from an outage into a reconnect.
- Prefer stateless tools where you can. If a tool call doesn't depend on accumulated session state, a lost session costs nothing. If it does, persist that state on a volume rather than in a dictionary.
Long-lived SSE streams are cut by a redeploy too. No per-app timeout or buffering middleware is configured on your route, so nothing is truncating your streams in normal operation — but a deploy still ends them, and clients should treat disconnection as recoverable. The spec is explicit that "disconnection SHOULD NOT be interpreted as the client cancelling its request."
Deploying it
Nothing special. Write a Dockerfile, bind your MCP endpoint's port as HTTPS, set your auth token as an environment variable, and push. If you built the server with an AI coding tool, run it past the Dockerfile checklist first — the localhost-binding mistake is the one that overlaps hardest with the advice above.
Add the MCP-Protocol-Version header handling while you're there: clients must send it, and a server that receives an unsupported value "MUST respond with 400 Bad Request." If the header is absent entirely, assume 2025-03-26.
FAQ
Can I host any MCP server remotely?
Only one using the Streamable HTTP transport. A stdio server is launched as a subprocess by the client, so it runs on the user's machine.
Should my hosted MCP server bind to localhost?
No. That guidance applies to servers running on a user's own machine. In a container, bind 0.0.0.0 or nothing can reach you.
Do I need to validate the Origin header?
Yes — the spec says servers MUST, to prevent DNS rebinding. Hosted, it matters more, because your endpoint is publicly reachable.
Why do my clients break after I redeploy?
In-memory sessions are lost when the container is recreated. Respond 404 to unknown session IDs and compliant clients will re-initialize automatically.
How many ports do I need?
One. Streamable HTTP uses a single endpoint path handling both POST and GET.
Is an MCP server heavy to run?
Usually not — it's a small request/response service. Weight comes from what your tools do, not from the protocol.
Wrapping up
Hosting an MCP server is mostly ordinary web-service work with three specifics worth getting right. Bind to all interfaces, because the localhost advice in the spec is about laptops and not containers. Validate Origin and require a token, because a hosted endpoint is public from the second it starts. And answer 404 for sessions you've forgotten, so that a redeploy is a reconnect rather than an incident. Everything else is a container with one HTTPS port.