Build a todo manager (Python)
Same tutorial as the Node.js version, built on the official Python MCP SDK's FastMCP instead. Every code sample here is real — tested with the actual MCP Inspector CLI, using two separate tokens, before publishing.
1. Create a project
Sign in at getmcpauth.dev/dashboard and create a project. Copy the registration secret it shows you — it's shown once.
2. Install dependencies
pip install "mcp[cli]" getmcpauthRequires Python 3.10+ (the same floor the official mcp package itself requires).
3. Build the server
Each tool calls get_access_token() — a context-local lookup the official SDK populates automatically once McpAuthTokenVerifier verifies the request — to scope the in-memory store to the current user:
import os
from mcp.server.fastmcp import FastMCP
from mcp.server.auth.middleware.auth_context import get_access_token
from getmcpauth import McpAuthTokenVerifier, build_auth_settings
# Keyed by the authenticated user's subject — this is the whole point:
# each user only ever sees their own todos.
todos_by_subject: dict[str, list[dict]] = {}
def get_todos(subject: str) -> list[dict]:
return todos_by_subject.setdefault(subject, [])
mcp = FastMCP(
"todo-manager",
token_verifier=McpAuthTokenVerifier(
"https://getmcpauth.dev/api/oauth/introspect",
registration_secret=os.environ["MCPAUTH_SECRET"],
),
auth=build_auth_settings(
"https://getmcpauth.dev",
resource_server_url="https://my-server.example.com",
),
)
@mcp.tool()
def add_todo(text: str) -> str:
"""Add a new todo item."""
subject = get_access_token().subject
todos = get_todos(subject)
todo = {"id": len(todos) + 1, "text": text, "done": False}
todos.append(todo)
return f"Added #{todo['id']}: {text}"
@mcp.tool()
def list_todos() -> str:
"""List this user's todos."""
subject = get_access_token().subject
todos = get_todos(subject)
if not todos:
return "No todos yet."
return "\n".join(f"#{t['id']} [{'x' if t['done'] else ' '}] {t['text']}" for t in todos)
if __name__ == "__main__":
mcp.run(transport="streamable-http")4. Test it live
Run python server.py, then use MCP Inspector to add a todo:
npx @modelcontextprotocol/inspector --cli http://localhost:8000/mcp \
--transport http --header "Authorization: Bearer <your token>" \
--method tools/call --tool-name add_todo --tool-arg text="Buy milk"List it back — you'll only ever see todos added under the same token's identity. Try it with a second token minted for a different subject and the list comes back empty — real per-user isolation, enforced by the token:
npx @modelcontextprotocol/inspector --cli http://localhost:8000/mcp \
--transport http --header "Authorization: Bearer <your token>" \
--method tools/call --tool-name list_todosClosing notes
If your product already has its own login system, see server-to-server token minting — your backend can mint a token for an already-authenticated user directly, skipping mcpauth's own consent screen entirely.