ModelRefs / Deploy a Remote MCP Server

Deploy a Remote MCP Server

Take your MCP server from local to production: switch to Streamable HTTP, add OAuth 2.1 with PKCE, bind tokens to your server, and deploy safely.

This tutorial assumes you have a working server. If not, start with building a custom MCP server, then come back to make it remote.

When to go remote

Keep a server local when only you use it. Go remote when other users, other machines, or a hosted agent need to reach the same tools.

The transport is the dividing line. Local servers run over stdio as a subprocess, with no network surface. Remote servers run over Streamable HTTP, which replaced the deprecated HTTP+SSE transport and is now the standard for anything multi-user or cloud-hosted.

That network exposure changes the threat model. A remote endpoint accepts requests from outside your machine, so it needs identity, authorization, and transport security that a local server never did.

Prerequisites

Pin your versions. Stating what you tested on is good practice and an honest signal to readers.

  • A working MCP server. The support-desk server from the custom server tutorial is a fine starting point.
  • The official MCP SDK, version 1.23 or later, which ships built-in OAuth 2.1 Resource Server support.
  • An OAuth 2.1 authorization server, such as your identity provider (Entra ID, Auth0, Okta, or similar).
  • HTTPS in front of the server, via a reverse proxy or cloud gateway.

Step 1. Switch to Streamable HTTP

Changing transport is a one-line edit. Streamable HTTP exposes a single endpoint that carries JSON-RPC over HTTP, with optional streaming.

if __name__ == "__main__":
    # Local was: mcp.run()
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)

At this point the server is reachable over the network, and completely unauthenticated. Do not stop here. An open Streamable HTTP server is an open door to every tool it exposes.

Step 2. Make the server an OAuth 2.1 Resource Server

In OAuth terms, your MCP server is a Resource Server. It accepts bearer tokens, validates them, and only then serves tools and resources. The tokens themselves are issued by a separate authorization server.

The MCP Python SDK provides the plumbing through AuthSettings and a TokenVerifier:

from mcp.server.fastmcp import FastMCP
from mcp.server.auth.settings import AuthSettings
from mcp.server.auth.provider import TokenVerifier, AccessToken
class SupportDeskVerifier(TokenVerifier):
    async def verify_token(self, token: str) -> AccessToken | None:
        # Validate the bearer token against your authorization server.
        # Return an AccessToken (with scopes) if valid, otherwise None.
        ...
mcp = FastMCP(
    "support-desk",
    token_verifier=SupportDeskVerifier(),
    auth=AuthSettings(
        issuer_url="https://auth.example.com",
        resource_server_url="https://mcp.example.com",
        required_scopes=["tickets:read", "tickets:write"],
    ),
)

Now every request must present a valid token carrying the required scopes. Use PKCE with the S256 method for the client flow, and deny by default so a missing or wrong scope fails closed.

Step 3. Advertise the authorization server

Clients need to discover where to get a token. The protocol handles this with a metadata handshake, so you do not hardcode auth URLs into every client.

The flow is short. A client calls your server without a token and receives a 401. That response points to your protected resource metadata (RFC 9728), which names the authorization server and the scopes it requires. The client then gets a token there and retries.

AuthSettings above wires this discovery for you. Your job is to make sure issuer_url and resource_server_url are correct and reachable.

Step 4. Bind tokens and forbid passthrough

A token minted for one server should never work against another. Bind each token to your server with resource indicators (RFC 8707), so a stolen token cannot be replayed elsewhere.

The single most important rule here comes from the official security guidance: never pass a client's token through to a downstream API. Forwarding it breaks audience binding, destroys the audit trail, and creates a confused-deputy vulnerability.

When your server needs to call a downstream service, exchange the incoming token for a new, downstream-scoped one (token exchange, RFC 8693) rather than reusing it. The downstream API should see a credential minted for that call, not the user's original token.

Step 5. Deploy behind HTTPS

Never expose a remote MCP server over plain HTTP. Put it behind a reverse proxy or cloud gateway that terminates TLS, and bind the app to the gateway rather than the open internet.

A typical shape is a container running the server on an internal port, with nginx, Caddy, or a managed gateway handling HTTPS and forwarding to it. Add rate limiting and request logging at that layer while you are there.

The 2026-07-28 specification makes the protocol core stateless, so a well-built Streamable HTTP server scales horizontally behind a load balancer without sticky sessions. Design for that from the start.

Client registration

Clients must identify themselves to your authorization server before they can get a token. The modern default is Client ID Metadata Documents (CIMD), the preferred method since late 2025.

With CIMD, a client identifies itself using a URL it controls that hosts a small JSON document describing it. Your authorization server fetches and validates that document, which avoids maintaining a registration database and the open-registration abuse that came with legacy dynamic client registration.

If you must support older clients, do so carefully, and require per-client consent so an attacker cannot register a malicious client silently.

Security must-dos

Remote deployment inherits every tool risk plus a set of identity risks. Treat this as the short, non-negotiable list, and read the full MCP security and tool poisoning guide for the rest.

  • OAuth 2.1 with PKCE on every internet-facing server.
  • Audience-bound tokens (RFC 8707), scoped per tool, short-lived.
  • No token passthrough, ever. Use token exchange for downstream calls.
  • Per-client consent and exact redirect-URI matching to stop the confused deputy.
  • HTTPS only, with rate limiting and centralized logging.

Common mistakes

Most remote-server incidents come from skipping auth or trusting the wrong thing.

The frequent errors:

  • Shipping an unauthenticated Streamable HTTP endpoint.
  • Forwarding a client's token straight to a downstream API.
  • Pulling in an unvetted OAuth helper.

That last one is not hypothetical: a critical flaw in a popular remote-connection package once let malicious servers run operating-system commands through the OAuth flow, across hundreds of thousands of installs.

Keep dependencies current, connect only to servers you trust, and validate every token your server accepts.

Keep exploring

Continue with related references and the next steps in this cluster:

Sources

  1. Model Context Protocol, Python SDK documentation (official). FastMCP AuthSettings and TokenVerifier, Streamable HTTP transport, and OAuth 2.1 Resource Server support.
  2. Model Context Protocol, Security Best Practices (official). The token passthrough prohibition, confused deputy prevention, and per-client consent.
  3. Model Context Protocol, 2026-07-28 Specification (official blog). Stateless core and current authorization direction.

Methodology: transport, auth, and deployment details verified against the official MCP Python SDK, the official MCP security guidance, and the 2026-07-28 specification on 17 Jul 2026. Implementation is asserted in ModelRefs' own voice; spec- and SDK-sensitive statements are marked for re-check before publication.

Frequently asked questions

Do local MCP servers need OAuth?

No. stdio servers run as a local subprocess and should use environment credentials instead.

What is token passthrough?

Forwarding a client's token to a downstream API without checking audience — it breaks binding and auditing and is prohibited by the spec.