The examples run on 127.0.0.1 with no authorization on the MCP endpoint, and that is correct for a tutorial and wrong for anything a customer connects to. This page is the list of what changes, in the order it usually bites.

1. HTTPS and a stable URL

Clients store the server URL. Pick a subdomain you control, such as mcp.yourcompany.com, terminate TLS in front of the server, and never move it. Serve the MCP endpoint at /mcp and keep /health for your load balancer.

2. Authorization on the endpoint

The examples authenticate to the upstream API with a key from the environment. The MCP endpoint itself must also identify who is calling:

  • Bearer tokens are the simplest: the client presents a token, the server verifies it. The TypeScript SDK's requireBearerAuth and mcpAuthMetadataRouter helpers cover this and the protected-resource metadata that clients use to discover your authorization server.
  • OAuth is what lets each user of ChatGPT or Claude act on their own account. Separate authorizing the connection from accessing records, and map each tool call to the authenticated user. Our guide to OAuth for remote MCP covers the design.

Until this is in place, do not expose the endpoint publicly, even "just to test".

3. Permission checks on every call

A read tool is still a query against your data. Every call must be scoped to the caller's account, inside the tool, not only at connection time. The sample API has no accounts, which is why the examples cannot show this; your upstream does, and the check belongs in the server that holds the credential. See read-only tools still need permissions.

4. Secrets

The upstream credential belongs in a secret store with a restricted runtime identity, not in a .env file on the server. Rotate it without redeploying. Never log it, and never return it in an error message; the examples' explain functions only ever echo the upstream's own message text.

5. Stateless or sessions

The TypeScript, Python and C# examples are stateless: every request builds a fresh server and nothing is held between calls, so you can run as many instances as you like behind a load balancer. PHP's example uses a file session store; in production use a shared store (Redis via PSR-16, for instance) so any instance can serve any session, and pass a cache to setDiscovery so attribute scanning happens once.

6. Observability

Log every tool call with its name, the authenticated subject, duration and outcome (ok, invalid input, not found, conflict, unavailable). That one log line answers most support questions ("the assistant said it could not cancel my order") and shows which tools are actually used. Alert on the unavailable rate; it is your upstream's health seen from the assistant's side.

7. Keeping up with clients

MCP clients have changed transport details, authorization flows and tool-listing behaviour within the protocol's short life. Someone has to notice a change, test the server against the new client, and ship a fix, usually on the client's schedule rather than yours. Keep the conformance test and run it against each SDK upgrade; it is cheap insurance.

8. Versioning tools

Changing a tool's name, parameters or meaning changes what every connected assistant believes. Add tools rather than redefining them; keep old names working while clients catch up; and change descriptions deliberately, since they are the behaviour. Our note on versioning MCP tools covers the trade-offs.

Or hand it off

Everything above is standing work, not a one-time setup, and it is the part most teams underestimate. If you would rather not own it, API to Agents designs the tools from your documentation, hosts the server under your subdomain on EU infrastructure, and carries the monitoring and client changes, for a fixed setup fee quoted by the free Agent Readiness Audit and a monthly plan listed on the pricing page. The examples you have just read are the same patterns we use; the difference is who runs them.