Engineering note by Jasper Tham · Updated 14 October 2026 · 6 min read
Hosting remote MCP servers on Kubernetes
We build and host our own MCP servers, the tool servers that let assistants like Claude and ChatGPT fetch data and take actions. Running them remotely on Kubernetes works well, once the proxy is configured for streaming.
Why run MCP servers remotely
A local MCP server only helps the person running it. A remote one, deployed once, can be used by a whole team and by hosted assistants, and it keeps credentials for internal systems on the server instead of on every laptop. Our own servers include a web scraper and a market-data server, deployed as normal containers from our private registry.
Make the ingress stream properly
MCP's Streamable HTTP transport answers each request with either a plain JSON response or, optionally, a stream of server-sent events. The proxy has to pass that stream through promptly and keep the connection open for long tool calls. Plain NGINX and several ingress controllers buffer responses by default; the community ingress-nginx controller, which these annotations belong to, already defaults response buffering to off but buffers request bodies. Note that community ingress-nginx was retired in March 2026, so new clusters should use a maintained controller or a Gateway API implementation, where the same settings exist under other names. On ingress-nginx we set:
- nginx.ingress.kubernetes.io/proxy-buffering: "off" explicitly, so a ConfigMap change elsewhere cannot start buffering streams
- nginx.ingress.kubernetes.io/proxy-request-buffering: "off", a separate setting that is on by default, so large requests stream through
- nginx.ingress.kubernetes.io/proxy-read-timeout and proxy-send-timeout raised well above 60 seconds
- nginx.ingress.kubernetes.io/force-ssl-redirect: "true" so clients always use HTTPS
Sessions and replicas
A Streamable HTTP server may assign a session ID and expect it back in the Mcp-Session-Id header. With two or more replicas behind a Service, consecutive requests can land on different pods that do not share that session. Cookie-based affinity is not a fix: many MCP clients are not browsers and will not keep an ingress cookie. Either build the server to be stateless, keep session state in a shared store your SDK supports, or route by the header itself, for example ingress-nginx's upstream-hash-by annotation set to $http_mcp_session_id. Then test reconnects, repeated calls and a pod restart with the clients you actually use.
CORS, authentication and safety
The MCP specification requires servers to validate the Origin header, which blocks DNS-rebinding attacks. Browser-based clients also need CORS, and they need to read the Mcp-Session-Id response header, so expose it explicitly. Allow only the origins you use rather than a wildcard. Never leave a tool server open to the internet without authentication: the MCP specification describes OAuth 2.1 for remote servers, and at minimum an API key checked by the server or a gateway in front of it. Tools that fetch URLs, like a scraper, should also block internal addresses so they cannot be used to reach other services inside your network.
Resources and probes
Tool servers that drive a headless browser are heavier than they look. Our scraper runs with requests of 250m CPU and 512Mi memory and limits of 1 CPU and 2Gi. Add readiness and liveness probes, keep configuration in a ConfigMap and secrets in a Secret, and build images in CI and push them to a private registry rather than building on the cluster.
Frequently asked questions
What transport should a remote MCP server use?
Streamable HTTP, which replaced the older HTTP with server-sent events transport in the MCP specification. It works through normal HTTPS load balancers as long as proxy buffering is off.
Can you build a custom MCP server for our internal systems?
Yes. Cassnetic Tech builds and hosts MCP servers that give AI assistants controlled access to internal data and tools, on your infrastructure or ours.