What actually changes when an MCP server leaves your laptop A developer built a local lab demonstrating the operational differences between running an MCP server on a laptop and hosting it behind a gateway for multiple clients and tenants. The project implements bearer-token authentication, edge-enforced tenant scoping, request-bound expiring approvals for write tools, and request-ID-correlated logging, validated by a 13-test suite with deterministic failure injection. The code is available on GitHub at glatinone/mcp-local-to-hosted-deployment-fix. An MCP server that works locally is not the same as an MCP server that runs behind a gateway other people can reach. Every tutorial shows the local case because the local case is easy. The interesting problems only appear the moment another client, another tenant, or another team needs to talk to it. I built a small local lab to make those problems visible. This post is what I learned building it. On a laptop, an MCP server talks to one client. There is one user, one workspace, and one trust boundary: your own machine. Behind a gateway, that changes fast: None of this is exotic. It is just the ordinary shape of anything that gets handed to a team. A gateway cannot trust a long-lived session. Every request carries a bearer token, and the gateway validates it before the tool registry is even consulted. Wrong token means 401. Wrong path means 404. That sounds obvious. A surprising number of demos skip both. Tenant scoping belongs at the edge, not inside each tool. If every tool has to remember to check the tenant, one tool will forget. When the gateway enforces it before the tool is called, a tool physically cannot return another tenant's data. The pattern is simple: the token carries the tenant, the gateway attaches the tenant to the request, the tool only reads from that tenant's fixtures. The tool never sees the raw tenant ID from the caller. Read tools can run without ceremony. Write tools cannot. The approval is bound to a specific request ID and tool name, and it expires. A stale approval does not work. An approval for documents.archive does not authorize documents.delete . This is what turns a demo into something a team can actually ship. Every call gets a request ID. Every log line carries it. Secrets are redacted before the line is written. When something goes wrong, the log shows the sequence of decisions that led to the failure, not just that the failure happened. A streamable HTTP gateway sits in front of a two-tool registry: documents.list read and documents.archive write, requires approval . A target smoke client talks to the gateway the same way a real MCP client would. Deterministic failure injection lets a reviewer trigger each failure class on demand: The test suite is small: 13 runtime tests. They cover authentication, tenant boundary, approval binding, and every failure status. No production dependencies, no external services, no deployment. If you are moving MCP from a proof-of-concept to something a team can use, this is the checklist: None of this is a framework. It is four decisions and a runbook. The code is at github.com/glatinone/mcp-local-to-hosted-deployment-fix https://github.com/glatinone/mcp-local-to-hosted-deployment-fix . If your team is stuck on this exact transition, I take short implementation sprints on it. kielltampubolon.id https://www.kielltampubolon.id/