Skip to main content
Glama
suhasdesai02

remote-mcp

by suhasdesai02

Remote MCP Server — HTTP transport + authentication

A minimal MCP server exposed over the network (HTTP) instead of stdio, with bearer token authentication — the two things that change the moment an MCP server leaves your own laptop.

This project is intentionally simple (two trivial tools: echo and get_server_time) so the lesson is entirely about transport and auth mechanics, not new business logic — you already know RAG, tool use, and MCP fundamentals from Projects 2-5.

The core concept

Every previous project used stdio transport: Claude Code launches your script as its own child process and talks to it over stdin/stdout. Only whoever can start a process on your machine can ever reach it — there's no meaningful "who's allowed to call this" question, because nothing outside your machine can even see it.

HTTP transport is fundamentally different: the server runs as an independent, long-lived network service, reachable by URL. Once that's true, anyone who can reach the port can send it requests — so authentication stops being optional.

# stdio (every previous project) — implicitly private, no auth needed
mcp.run()

# HTTP (this project) — network-reachable, auth now required
mcp.run(transport="http", host="0.0.0.0", port=8000)

Verified live: the actual authentication boundary

This was tested against a real running server process, not simulated — three real HTTP requests, three real outcomes:

--- Attempt with NO auth token ---
Correctly rejected: HTTPStatusError: Client error '401 Unauthorized'

--- Attempt with WRONG auth token ---
Correctly rejected: HTTPStatusError: Client error '401 Unauthorized'

--- Attempt with CORRECT auth token ---
Tools discovered: ['get_server_time', 'echo']
Echo result: Server received: hello from a real HTTP client
Server time: 2026-08-13T22:07:47.826826

Notice the wrong-token case: it fails identically to no-token-at-all — proof the server is genuinely validating the token's value, not just checking that some header is present.

Setup

pip install -r requirements.txt
$env:REMOTE_MCP_TOKEN = "pick-any-secret-string-here"

Run it locally

python remote_server.py

You'll see it start a real Uvicorn web server, listening at http://0.0.0.0:8000/mcp — this is a genuinely different kind of process than every previous project's script, which just ran, did something, and exited.

Connect a client (from a second terminal, while the server is running)

import asyncio
from fastmcp import Client

async def main():
    async with Client("http://localhost:8000/mcp", auth="pick-any-secret-string-here") as client:
        result = await client.call_tool("echo", {"message": "hello"})
        print(result.data)

asyncio.run(main())

Connecting Claude Code to a remote HTTP server

Unlike previous projects (claude mcp add name -- python script.py, which tells Claude Code to launch the process), an HTTP server is already running independently — you point Claude Code at its URL instead:

claude mcp add remote-demo --transport http https://your-deployed-url.com/mcp

(Exact syntax for passing the bearer token alongside this may vary by Claude Code version — check claude mcp add --help for the current auth-header flag.)

Actually deploying this off your laptop

Running python remote_server.py locally proves the mechanism works, but it's still only reachable from your own machine (localhost). To make it genuinely reachable by others, you need to run it on a machine that's always on and has a public address — a small cloud host, not your laptop.

Simplest free options for a small Python service like this:

  • Render (render.com) — free tier, connects directly to a GitHub repo, auto-deploys on push. Set REMOTE_MCP_TOKEN as an environment variable in their dashboard (never commit it to the repo).

  • Railway (railway.app) — similar free-tier flow, GitHub-connected.

  • Fly.io — free tier, more control but a bit more setup (a fly.toml config file, in addition to your requirements.txt).

All three follow roughly the same shape: connect your GitHub repo → they detect it's Python → they run something equivalent to python remote_server.py on a real server with a real public URL → you set REMOTE_MCP_TOKEN as a secret in their dashboard, not in code.

This step is deliberately left as your next hands-on exercise, since the actual mechanics (transport + auth) are already verified above — picking a host, connecting a GitHub repo, and setting an environment variable in a web dashboard is a different, more click-through-y skill than anything code-related, and it's worth doing yourself rather than following a script blindly.

Design notes

  • StaticTokenVerifier is explicitly for development/testing only — tokens are stored in plain text in server memory. A real production deployment would use JWTVerifier against a real identity provider (Auth0, Okta, your company's SSO), not a single shared secret string. This project uses the static version deliberately, to isolate the concept (HTTP needs auth) from the complexity (real OAuth/JWT flows) — worth knowing the limitation, not just the pattern.

  • host="0.0.0.0" vs "localhost" matters. localhost only accepts connections from the same machine — binding to it would defeat the purpose of this whole exercise. 0.0.0.0 means "accept connections on any network interface," which is what actually makes remote access possible once deployed.

  • The wrong-token test is the important one, not the no-token test. A server that merely checks "is a token present" without validating its value would still pass a naive test but be completely insecure — this project's verification specifically confirms invalid credentials are rejected, not just missing ones.

What's next

  • Deploy to Render/Railway/Fly.io and re-run the same three-case auth test against the real public URL instead of localhost.

  • Swap StaticTokenVerifier for JWTVerifier against a real identity provider — the production-grade version of this same concept.

  • Point Claude Desktop (not just Claude Code) at the deployed URL, to confirm the server truly doesn't care which client connects — the same client-independence property from Project 4, now proven over a real network instead of a local subprocess.