A Model Context Protocol tool server over Streamable HTTP (JSON-RPC 2.0) with OAuth —
the handshake an LLM/agent uses to discover and call your tools: initialize → tools/list →
tools/call, every call gated by a bearer token. Runs offline, $0.
Stack: Python · MCP (JSON-RPC 2.0) · Streamable HTTP · OAuth bearer · FastMCP (prod path)
Demonstrates: the MCP tool-server handshake (initialize → tools/list → tools/call) · per-call OAuth · RFC-9728 protected-resource metadata · JSON-Schema tool contracts — how an agent safely discovers and calls your tools.
python3 -m venv .venv && source .venv/bin/activate
make install
make demo # the handshake: 401 unauth -> initialize -> tools/list -> tools/call
make test # 7 tests
make serve # real server on http://localhost:8000 (POST /mcp)# call it with curl (needs the bearer token):
curl -s localhost:8000/mcp -H 'Authorization: Bearer mcp-demo-token' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"add","arguments":{"a":21,"b":21}}}'
# -> {"result":{"content":[{"type":"text","text":"{\"sum\": 42}"}]}}client --POST /mcp (JSON-RPC)--> server [OAuth bearer checked on every call]
initialize -> protocol + serverInfo
tools/list -> [add, get_weather, search_docs] each with an inputSchema (the model's contract)
tools/call -> run the handler, return content
- server.py — Streamable HTTP transport (one POST
/mcp), JSON-RPC, OAuth, + RFC-9728 protected-resource metadata - tools.py — the tools (name + JSON-Schema + handler)
- demo.py — the handshake demo
/.well-known/oauth-protected-resource— tells a client where to get a token (401 →WWW-Authenticate)
Swap the hand-rolled transport for the official FastMCP SDK (@mcp.tool()), validate real
OAuth2 access tokens against an authorization server (Streamable HTTP replaced SSE), put it behind
an AI gateway, and add per-tool authz + rate limits.
A proof-of-concept built as interview evidence — framed honestly as a POC, not a production system.