Skip to main content
Glama
suhasdesai02

remote-mcp

by suhasdesai02
README.md
# 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.

```python
# 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

```bash
pip install -r requirements.txt
```

```powershell
$env:REMOTE_MCP_TOKEN = "pick-any-secret-string-here"
```

## Run it locally

```bash
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)

```python
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:

```bash
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.