Skip to main content

Scaffold a production-ready remote MCP (Model Context Protocol) server with OAuth, RFC 8414/9728 discovery, retry, telemetry, and tests — in one command. Zero runtime dependency on this package.

Project description

remote-mcp

One command to a production-ready remote MCP server.

PyPI Python CI License: MIT


Building a remote MCP (Model Context Protocol) server means solving the same hard problems every time: OAuth pass-through, RFC 8414/9728 discovery endpoints, middleware stack, telemetry, retry logic, Docker packaging, and a test suite that passes on day one.

remote-mcp scaffolds all of it. Generated projects have zero runtime dependency on this package — every file is yours to read, audit, and modify.

Install

pipx install remote-mcp   # recommended
# or
pip install remote-mcp

Usage

Interactive:

remote-mcp new my-project

Non-interactive (CI / scripts):

remote-mcp new my-project --yes --service-name "My Project" --into ./apps/my-project
  FastMCP Remote Server Generator

  Scaffolding my-project into my-project...
  ✓ Scaffold complete

  Done! Next steps:
    cd my-project
    python -m venv venv && source venv/bin/activate
    pip install -e ".[dev]"
    cp env.example .env
    uvicorn asgi:application --reload --port 8001 --lifespan on

Server is live at http://localhost:8001. MCP endpoint: http://localhost:8001/mcp.

Also works as a module:

python -m remote_mcp new my-project

Commands

Command What it does
remote-mcp new <name> Scaffold a new project
remote-mcp add tool <name> Add a tool stub to an existing scaffolded project
remote-mcp --version Print version

new flags:

Flag Purpose
--yes / -y Skip prompts; use defaults / provided flags
--service-name / -s "Foo Bar" Human-readable service name
--into PATH Target directory (default ./<name>)
--auth-mode none|passthrough|jwt Default auth mode for the generated project (default: passthrough)
--github-owner OWNER GitHub user/org for the MCP registry namespace in server.json
--legacy-sse Also serve the deprecated SSE transport at /sse

add tool flags:

Flag Purpose
--project-dir / -p PATH Target project (default .)

What you get

my-project/
├── src/
│   ├── server.py              # FastMCP("My Project") instance + tool mounts
│   ├── app.py                 # Starlette factory — routes, middleware, ASGI wiring
│   ├── config/settings.py     # Pydantic BaseSettings (lazy get_settings())
│   ├── core/
│   │   ├── auth.py            # extract_bearer_token() — OAuth pass-through
│   │   ├── errors.py          # MyProjectError hierarchy
│   │   ├── http_client.py     # api_get/post/patch/put/delete/upload + tenacity retry
│   │   ├── telemetry.py       # JSONL event log, HMAC-salted user-id hashing
│   │   ├── handlers.py        # @tool_handler decorator, get_client_and_token()
│   │   └── app_state.py       # lifespan: shared httpx.AsyncClient pool
│   ├── middleware/
│   │   ├── auth.py            # RequireAuthMiddleware — Bearer enforcement + probe
│   │   └── telemetry.py       # TelemetryMiddleware — auth failures, connections
│   ├── views/
│   │   ├── health.py          # GET /health
│   │   ├── oauth.py           # RFC 8414 + RFC 9728 discovery endpoints
│   │   └── root.py            # landing page at /
│   └── tools/example.py       # echo tool — mounted and live at first boot
├── tests/
│   ├── test_auth.py           # extract_bearer_token edge cases
│   ├── test_middleware.py     # auth bypass, 401 on missing token
│   └── test_telemetry.py      # hash_token stability, record_event never raises
├── templates/index.html       # landing page served at /
├── Dockerfile                 # multi-stage, non-root, healthcheck
├── docker-compose.yml         # local dev parity
├── render.yaml                # Render Blueprint — one-click deploy
├── fly.toml                   # Fly.io app config
├── mcp.json                   # MCP Inspector preset
├── server.json                # MCP Registry metadata
├── .github/
│   └── workflows/
│       └── publish-mcp.yml    # registry publish on version tags
├── .dockerignore
├── .gitignore
├── asgi.py                    # production ASGI entrypoint
├── env.example                # all env vars with safe defaults
├── pyproject.toml             # hatchling build, ruff lint
├── README.md
└── DEPLOYMENT.md              # Render / Railway / Fly.io / Docker deploy guide

Included infrastructure

Module What it provides
core/auth.py extract_bearer_token(ctx) — forward Bearer token verbatim to your backend
core/http_client.py api_get, api_post, api_patch, api_put, api_delete, api_upload — pooled httpx client with tenacity retry
core/errors.py MyProjectError + Auth, Forbidden, Validation, Backend, RateLimit subclasses
core/telemetry.py Rotating JSONL log, user IDs hashed via HMAC-SHA256 (TELEMETRY_HASH_SALT)
core/handlers.py @tool_handler — catches errors, formats responses, records telemetry
middleware/auth.py CORS → Auth → optional backend token probe on MCP connect (reuses pooled client)
middleware/telemetry.py Connection-level events (auth failures, rate limits, MCP connects)
views/ Health, OAuth discovery (RFC 8414 + 9728), landing page — each in its own file
Dockerfile Multi-stage build, non-root app user, healthcheck, WORKERS env

Nothing is forced on you. Delete what you don't need.

Adding a tool

Quickest path:

remote-mcp add tool search

Then mount it in src/server.py:

from src.tools.search import search_router
mcp.mount(search_router)

Or write one by hand:

# src/tools/my_tool.py
from fastmcp import Context, FastMCP
from src.core.handlers import get_client_and_token, tool_handler

my_router = FastMCP("my-tool")

@my_router.tool()
@tool_handler
async def my_tool(param: str, ctx: Context) -> str:
    client, auth_header = await get_client_and_token(ctx)
    response = await client.get("/some/endpoint", headers={"Authorization": auth_header})
    return response.json()

Connecting to Claude

Claude.ai (web): Settings → Connectors → Add → Custom → Web

  • URL: https://your-server.example.com/mcp

Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "my-project": {
      "command": "npx",
      "args": ["-y", "mcp-remote@latest", "https://your-server.example.com/mcp"]
    }
  }
}

Claude Code:

claude mcp add my-project --transport http https://your-server.example.com/mcp

Local debugging with MCP Inspector

The scaffold ships an mcp.json preset:

npx @modelcontextprotocol/inspector --config ./mcp.json

Set a Bearer token in the Inspector UI to exercise authenticated tools without standing up a full OAuth flow. For zero-config local dev (no token needed), run with AUTH_MODE=none.

Deployment

Docker / Compose

docker build -t my-project .
docker run --rm -p 8001:8001 --env-file .env my-project
# or
docker compose up --build

One-click platforms

The generated project ships pre-filled config:

  • Renderrender.yaml Blueprint; click the Deploy to Render button in your repo README.
  • Fly.iofly.toml; run fly launch --copy-config && fly deploy.
  • Railway — Dockerfile-based; point Railway at the repo.

DEPLOYMENT.md in the generated project covers env vars, workers, healthcheck, and hardening notes.

Publish to the MCP Registry

Every scaffolded project ships server.json (MCP Registry metadata) and .github/workflows/publish-mcp.yml (OIDC-authenticated publish workflow). To list your server on the official registry:

  1. Edit server.json: replace YOUR-DOMAIN with your deployed domain and set github_owner to your GitHub user/org.
  2. Ensure the server is publicly reachable at the URL in remotes[].url.
  3. Push a version tag: git tag v1.0.0 && git push --tags.

The workflow authenticates via GitHub OIDC (no stored secrets) and publishes to registry.modelcontextprotocol.io, which feeds the discovery catalogues that MCP clients use.

Configuration

Copy env.example to .env and edit. Key variables:

Variable Default Description
API_BASE_URL https://api.example.com Your upstream API
AUTH_MODE passthrough none | passthrough | jwt — auth enforcement mode
OAUTH_JWKS_URL `` JWKS URL, required when AUTH_MODE=jwt
JWT_AUDIENCE `` JWT audience; defaults to MCP_PUBLIC_URL
MCP_PUBLIC_URL http://localhost:8001/mcp Public MCP URL (shown in landing page)
OAUTH_ISSUER_URL http://localhost:8001 OAuth issuer for RFC 8414 discovery
OAUTH_AUTHORIZE_PATH /o/authorize/ OAuth authorize endpoint (relative or full URL)
OAUTH_TOKEN_PATH /o/token/ OAuth token endpoint
OAUTH_REGISTRATION_PATH /o/register/ Dynamic client registration endpoint
OAUTH_SCOPES_SUPPORTED read Comma-separated scope list
AUTH_PROBE_ENABLED false Validate token against backend on MCP connect
AUTH_PROBE_PATH /health/ Endpoint used for token probe
LOGO_URI `` Logo shown in OAuth discovery (optional)
ALLOWED_ORIGINS https://claude.ai,... CORS origins; unset = all origins accepted (startup warning emitted)
TELEMETRY_ENABLED true Enable JSONL telemetry
TELEMETRY_HASH_SALT `` HMAC salt for user-id hashing (set per-deployment)
SUPPORT_EMAIL `` Shown as Get Help mailto link on the landing page; hidden when unset

Full variable list and deploy instructions in DEPLOYMENT.md.

How it works

remote-mcp renders Jinja2 templates into your project directory at scaffold time. After that, it's gone — no version pinning, no update command, no hidden runtime. You own every line.

Requirements

  • Python ≥ 3.10 (this CLI). Generated project supports the same range.
  • FastMCP ≥ 3.0 (installed in the generated project, not this package).

Contributing

See CONTRIBUTING.md. Security reports: SECURITY.md.

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

remote_mcp-0.3.0.tar.gz (40.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

remote_mcp-0.3.0-py3-none-any.whl (49.5 kB view details)

Uploaded Python 3

File details

Details for the file remote_mcp-0.3.0.tar.gz.

File metadata

  • Download URL: remote_mcp-0.3.0.tar.gz
  • Upload date:
  • Size: 40.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for remote_mcp-0.3.0.tar.gz
Algorithm Hash digest
SHA256 35baf540ebd417f778be9848764ff7fe31f699012d9be115d1c5ff7feec12009
MD5 9245c5ca3df539a6f2029d34af5576e0
BLAKE2b-256 eb445f47c21882d72b73c4021be119339addf2857303140ed6df56ed7da87f0c

See more details on using hashes here.

Provenance

The following attestation bundles were made for remote_mcp-0.3.0.tar.gz:

Publisher: publish.yml on pushpendra-tripathi/remote-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file remote_mcp-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: remote_mcp-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 49.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for remote_mcp-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ad0f5c447c719f8820e03a3d0c96fb86a49e763f46c98d8c49bc95ff56e6d07b
MD5 d414c1a6547fd2303d91884c01f9be5e
BLAKE2b-256 f3a506d287d52537e88d22c4c6589ad0623b59790358bcdf894c76e24ba92d4c

See more details on using hashes here.

Provenance

The following attestation bundles were made for remote_mcp-0.3.0-py3-none-any.whl:

Publisher: publish.yml on pushpendra-tripathi/remote-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page