remote-mcp
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.826826Notice 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.pyYou'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_TOKENas 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.tomlconfig 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
StaticTokenVerifieris explicitly for development/testing only — tokens are stored in plain text in server memory. A real production deployment would useJWTVerifieragainst 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.localhostonly accepts connections from the same machine — binding to it would defeat the purpose of this whole exercise.0.0.0.0means "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
StaticTokenVerifierforJWTVerifieragainst 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.