MCP servers started local — a stdio process forked by Claude Desktop or Cursor, talking to the agent over pipes on your machine. The move to remote MCPs was quick, and running one in production now means running a real web service. This post covers what changes when the server goes remote, what you need from a host, and how the pieces fit on Suga.

Why MCP is starting to look like a real production workload

Scale is what pushes the server off the laptop. Whether it's a team wanting one shared MCP for everyone's Cursor, or a SaaS wanting agents to reach their platform through a single endpoint, you end up in the same place: the server needs a URL. Streamable HTTP is the transport. It's a single HTTP endpoint that streams responses over Server-Sent Events, with a session header for stateful flows.

An HTTP MCP server is just a web service. Shipping it to production means answering the same questions as any other web service.

What actually changes when the server is remote

Four things change at once.

The transport carries auth now. A local stdio server trusts the parent agent by default, because the parent forked it and they run as the same OS user. A remote server has to authenticate every request. MCP's answer is OAuth 2.1 with Protected Resource Metadata: your server publishes a .well-known/oauth-protected-resource document, the client discovers an authorization server from it, walks the user through consent, and comes back with a bearer token it attaches to every tool call. Your server is now an OAuth resource server, not just a tool endpoint.

Sessions replace process memory. Streamable HTTP carries an Mcp-Session-Id header for stateful sessions. If your tools carry context across calls (an open connection, a partial result, a rolling conversation with a downstream API), that session id is the only thing tying the calls together. You either pin sessions to one instance with sticky routing, or push the session state into a shared store. Pick one up front.

Tool calls stream, and streaming has timeouts. Long-running tools return an SSE stream so the client sees progress before the final result. That stream stays open across the edge, load balancer, and reverse proxy between the client and your process, and each layer has its own idle timeout to design around.

The server holds credentials now. Local MCP servers usually delegate to the user's environment (they read ~/.aws/credentials or shell out to gh auth token). A remote server holds the credentials itself: either a service account it uses on every request, or per-user tokens it received from the OAuth exchange. Either way, you need real secret management.

What you need from a host

Once you're running the server as a real web workload, here's what it needs.

A container that stays warm. MCP clients keep connections open across a session, and an always-on container holds the session state through every tool call. Same reasoning as long-running AI agents — a resident process holds state across turns.

HTTPS termination handled for you. The OAuth surface (discovery documents, authorization redirects, token endpoints) needs TLS end-to-end, and MCP clients expect a proper HTTPS endpoint to complete the flow.

A streaming-friendly edge. The proxy in front of your app needs an idle timeout that's generous enough for real tool calls. A few minutes of headroom, with periodic keepalives to reset the idle counter, keeps tool calls flowing through the edge and any load balancer in front.

Per-service secrets injected at runtime. The credentials your MCP holds (downstream API keys, OAuth client secrets, database passwords) go in at runtime, marked sensitive, so you can rotate them without a rebuild.

Private networking to the services the MCP talks to. Most useful MCP servers are thin wrappers over a downstream API or database. Keep that downstream on a private network, addressable by hostname from the MCP container.

Persistent storage for session and audit state. Even mostly-stateless MCPs benefit from persisting session metadata, request logs, and rate-limit counters somewhere that survives a redeploy. A mounted volume is usually enough for a single-instance MCP; a shared database is the right answer once you're horizontally scaled.

An edge in front of the MCP. Public MCP endpoints see specific adversarial patterns (OAuth surface probing, token stuffing, tool-spec enumeration for destructive tool names). Handling that at the edge is much cheaper than handling it inside your application.

How this sits together on Suga

Suga runs always-on containers behind Cloudflare's global edge, which covers most of the list above.

Each MCP server runs as a container with a public HTTPS endpoint. Suga allocates the hostname and provisions TLS when you expose the port, and the hostname is available before the first deploy — useful for MCP, since the Protected Resource Metadata document has to publish its own URL at boot.

The edge is Cloudflare, so L3/L4/L7 DDoS protection and TLS termination apply to every container by default. Streams have a five-minute idle timeout, which covers a typical MCP tool call plus its downstream API round-trips. If a tool sits silent for longer than that, emit an SSE keepalive frame, or move the long-idle step onto a background worker container that reports back over the private network.

Secrets are per-container and marked sensitive. Once set from the dashboard or the Suga MCP, the value is write-only and injected at runtime. For OAuth client credentials and downstream API keys this is the correct posture, and rotating a secret is a config change on the container.

Session state and audit logs go on a persistent volume mounted at whatever path the container writes to. For anything you'd rather run as a proper database, Suga has templates for Postgres, Redis, and MariaDB with volumes attached. Those data services sit on the environment's private network, addressable by hostname, and stay private by default; you expose one only when you explicitly ask.

Cross-container references let you pull a downstream service's connection string into the MCP's environment without duplicating the value. Rotate the Postgres password on the database container, and the MCP picks up the new value on its next deploy through the reference.

Every deploy captures a commit message and the full environment definition, so you can roll back to any previous state with a single click. Forking an environment clones the services, images, resource allocations, volumes, and networking wiring into a new one — useful for staging an MCP change against an exact copy of production.

A recipe for deploying one

Push your MCP server as a container to a Suga environment. If your repo has an obvious runtime (Node with package.json, Python with pyproject.toml, Go module) Suga detects it and builds the image for you, so a Dockerfile is optional. Expose the MCP's port as public HTTPS to get a hostname allocated up-front, then feed that hostname back into your MCP's PUBLIC_URL env var in the same pass, so the OAuth Protected Resource Metadata document publishes the correct value on first boot.

Set your downstream API keys and OAuth client secret as sensitive environment variables on the MCP container. If your MCP holds per-user tokens from the OAuth exchange, stand up a Postgres or Redis as a sibling container on the same environment and pull its connection string into the MCP's env vars through a cross-container reference. If the tokens are short-lived and re-derivable from user consent, a mounted volume with an encrypted SQLite file is often enough.

Register the deployed URL with whichever client you're targeting. For Claude Code that's:

claude mcp add --transport http <name> https://<your-mcp-url>

Other MCP-capable agents have their own version of that command. Once a user walks through OAuth consent the first time, every later tool call goes straight through with the bearer token attached.

Where to start

To try this on your own MCP server, sign up for Suga and start with the quickstart. To drive the deployment from an agent, connect the Suga MCP to your coding agent and hand it a one-sentence prompt describing what to deploy — create_environment, add_container, add_volume, and set_secret are enough to stand up an MCP workload from a conversation.