move bearer token to local clients
Notion MCP Token Proxy
FastAPI service that multiplexes requests to ephemeral @notionhq/notion-mcp-server processes.
Each inbound request is authenticated with a Notion token, a matching backend is started (or re-used), and the proxy forwards the request over HTTP while stripping hop-by-hop headers.
Features
- Spins up to
MAX_PARALLEL_BACKENDSisolated MCP servers, each scoped to a single Notion token. - Performs active health checks before exposing a backend.
- Cleans up idle or unhealthy processes automatically.
- Ships with Docker and Compose definitions for reproducible deployment.
Requirements
- Python 3.11+
- Node.js 18+ (only required to run the Notion MCP server locally)
- Docker (optional, for containerized workflows)
Configuration
| Variable | Default | Purpose |
| --- | --- | --- |
| BACKEND_IDLE_TIMEOUT | 900 | Seconds of inactivity before a backend is torn down. |
| BACKEND_HEALTH_TIMEOUT | 15 | Seconds to wait for a backend to pass /health. |
| BACKEND_HEALTH_INTERVAL | 0.5 | Delay between health retries. |
| MAX_PARALLEL_BACKENDS | 8 | Maximum simultaneously running MCP servers. |
| MCP_SERVER_COMMAND | npx -y @notionhq/notion-mcp-server | Command used to launch the MCP backend. |
| PORT | 8000 | Exposed FastAPI port. |
Authentication model
Clients call the proxy with Authorization: Bearer <NOTION_TOKEN>.
The proxy memoizes a backend per token, injects NOTION_TOKEN into the backend environment, and swaps the header for an internal auth token before forwarding.
Running locally
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
export MCP_SERVER_COMMAND="npx -y @notionhq/notion-mcp-server"
uvicorn main:app --host 0.0.0.0 --port 8000
Send requests to http://localhost:8000/mcp and pass your Notion token in the Authorization header.
Docker workflow
Build manually:
docker build -t notion-mcp-proxy .
docker run -p 4554:8000 --env BACKEND_IDLE_TIMEOUT=600 notion-mcp-proxy
or rely on Compose:
docker compose up --build
The Compose file exposes port 4554 and sets sensible defaults for idle timeout and maximum parallel backends.
API surface
GET /healthz– lightweight status including the number of active backends.ANY /mcp– forwards requests to the MCP backend, streaming responses and filtering hop-by-hop headers.
Development notes
main.pycontains all of the orchestration logic including process lifecycle and streaming proxying viahttpx.- Idle cleanup runs in the background every 30 seconds; adjust
BACKEND_IDLE_TIMEOUTto fit your workload. - When changing dependencies, keep the Dockerfile and
requirements.txtin sync to avoid drift between local and containerized environments.