mcp-server-template
# mcp-server-template
A starting point for MCP servers that are going in front of real users, not just a demo. Clone it, replace the three example tools with your product's operations, and you already have:
- **Bearer-token auth** on the HTTP transport (`MCP_TOKENS="alice:token1,bob:token2"`), constant-time compare, `401` + `WWW-Authenticate` on failure
- **Per-client rate limits** (sliding window per token, `429` + `Retry-After`) and a process-wide tool budget as defense in depth
- **Host allow-list** (the SDK's DNS-rebinding protection, configured instead of disabled)
- **Structured call logging**: one JSON line per request with method, tool, duration, outcome
- **stdio and Streamable HTTP** from the same code (`mcp-template stdio` / `mcp-template http`)
- **Tests** that run without a network (Starlette TestClient against the real transport), **CI** on Linux and macOS, a **Dockerfile** that runs as non-root
- Tool docstrings written for the model, and a swappable `Backend` class so tests never touch your real API
Built on the official Python SDK (`mcp` 2.x, `MCPServer`).
## Quickstart
```bash
git clone https://github.com/JustFady/mcp-server-template my-server && cd my-server
uv venv && uv pip install -e ".[dev]"
.venv/bin/python -m pytest -q
# local, for Claude Desktop / Claude Code / Cursor
.venv/bin/mcp-template stdio
# network
MCP_TOKENS="alice:$(openssl rand -hex 24)" .venv/bin/mcp-template http --host 0.0.0.0 --port 8000 --rate 60
curl -s localhost:8000/healthz
```
Claude Code: `claude mcp add my-server -- /path/to/.venv/bin/mcp-template stdio`
Claude Desktop (`claude_desktop_config.json`):
```json
{ "mcpServers": { "my-server": { "command": "/path/to/.venv/bin/mcp-template", "args": ["stdio"] } } }
```
Remote clients connect to `https://your-host/mcp` with `Authorization: Bearer <token>`. Set the public hostname so the SDK's DNS-rebinding protection accepts it: `--allowed-host mcp.example.com` (repeatable) or `MCP_ALLOWED_HOSTS=mcp.example.com,mcp.example.com:443`. Unknown `Host` headers are rejected (`421` from the SDK middleware; some servers surface it as `400`).
## Adapting it to your product
1. `src/mcp_template/server.py`: replace `Backend` with your API client and the example tools with 5–10 operations your users actually ask for. Keep return values small and structured; the model reads them every call.
2. Write each docstring as instructions to the model: what it does, when to use it, what it returns, what it must not be used for.
3. `tests/`: fake the backend; assert tool names, descriptions and error behavior.
4. Decide limits: `--rate` per client per minute; `GlobalToolBudget` per process.
5. Put it behind TLS (a reverse proxy or your platform's ingress). Rotate tokens by editing `MCP_TOKENS` and restarting.
## Layout
```
src/mcp_template/
server.py tools + build_server()
http.py BearerAuth ASGI middleware, make_app()
middleware.py call_logger, GlobalToolBudget (MCP-level middleware)
ratelimit.py SlidingWindow
__main__.py CLI
tests/ ratelimit, server, http (real transport via TestClient)
Dockerfile, .github/workflows/ci.yml
```
## Docker
```bash
docker build -t my-mcp .
docker run --rm -p 8000:8000 -e MCP_TOKENS="alice:changeme" my-mcp
```
## Why not OAuth?
For a server you operate for a known set of clients, static bearer tokens over TLS are simpler to run and to audit. The SDK supports full OAuth (`AuthSettings` + a `TokenVerifier`) when you need third-party clients to self-register; this template leaves that door open rather than making you walk through it on day one.
## License
MIT © Fady Youssef
TDQS
Scored across 3 tools
Each tool has a clearly distinct role: add performs arithmetic, lookup reads a key, and store writes a key. There is no overlap between read and write operations, and the descriptions make the boundaries obvious.
All three names are simple lowercase verbs, which is readable and consistent in style. However, they do not follow a predictable verb_noun pattern such as get_value/set_value, so the convention is only mostly consistent.
Three tools is a small, well-scoped surface for a template server. Each tool has a clear purpose, and none feels redundant or excessive.
The key-value portion covers read and write/update, but there is no delete or list-keys operation, leaving notable lifecycle gaps. The arithmetic tool is complete for its narrow purpose but is unrelated to the storage domain.