Skip to main content
A personal bookmark manager where users save and organize URLs with tags. Demonstrates building a new multi-user service with per-user data isolation. GitHub: poke-mcp-examples/bookmark-manager Authentication: Remote OAuth with Dynamic Client Registration Who should follow this: Developers building new multi-user services from scratch, or implementing production-ready MCP-compliant OAuth.
This example uses WorkOS AuthKit for OAuth authentication. AuthKit provides a complete identity solution with JWT-based auth, JWKS validation, automatic token refresh, and Dynamic Client Registration - all MCP-spec compliant. You’ll need a free WorkOS account to run this example. Sign up here.

What to Notice

1. Remote OAuth with Dynamic Client Registration (DCR)

The problem: Traditional OAuth requires manually creating an OAuth app for every client. You’d need to create an app in your dashboard, get credentials, configure redirect URIs - doesn’t scale. DCR solution: Clients register themselves automatically via API:
from fastmcp.server.auth.providers.workos import AuthKitProvider

auth = AuthKitProvider(
    authkit_domain="https://your-project.authkit.app",  # WorkOS handles OAuth
    base_url="https://your-server.com"
)

mcp = FastMCP("Bookmark Manager", auth=auth)
What happens when Poke connects:
1. Poke → Your server: GET /.well-known/oauth-protected-resource
   Response: {"authorization_servers": ["https://your-project.authkit.app"]}

2. Poke → WorkOS AuthKit: POST /oauth2/register
   Payload: {"redirect_uris": ["poke://oauth/callback"], ...}
   Response: {"client_id": "client_auto_generated_xyz"}

3. OAuth flow begins with auto-registered client_id

4. User authorizes → WorkOS issues JWT → Poke calls your tools with token
Why this matters: Zero manual setup. MCP spec-compliant. Scales to thousands of clients. AuthKitProvider handles JWT validation (JWKS), token refresh, OAuth discovery - you write 4 lines.

2. Per-User Data Isolation with JWT Claims

Every tool extracts user identity from the validated JWT:
from fastmcp.server.dependencies import get_access_token

def get_user_id() -> str:
    token = get_access_token()  # Already validated by AuthKitProvider
    return token.claims["sub"]  # JWT standard: unique user ID

@mcp.tool
async def add_bookmark(url: str, title: str) -> dict:
    user_id = get_user_id()

    # Scope to this user
    await db.execute(
        "INSERT INTO bookmarks (user_sub, url, title) VALUES (?, ?, ?)",
        (user_id, url, title)
    )
    return {"saved": True}

@mcp.tool
async def list_bookmarks(limit: int = 20) -> dict:
    user_id = get_user_id()

    # Only this user's bookmarks
    rows = await db.query(
        "SELECT * FROM bookmarks WHERE user_sub = ? LIMIT ?",
        (user_id, limit)
    )
    return {"items": rows}
Why: No sessions, no cookies, no permission tables. User identity lives in the JWT. Filter every query by user_sub - users never see each other’s data.

Quick Start

# Clone
git clone https://github.com/InteractionCo/poke-mcp-examples.git
cd poke-mcp-examples/bookmark-manager

# Environment
conda create -n bookmark-manager python=3.12
conda activate bookmark-manager

# Install
pip install -r requirements.txt

# Configure (requires WorkOS account - see repo README)
cp .env.example .env
# Edit .env with your AUTHKIT_DOMAIN

# Run
python src/server.py
Deployment: See repo README for Render deployment with HTTPS.

Key Takeaway

Building a new service? Use Remote OAuth + DCR. WorkOS AuthKit handles identity, OAuth, and client registration. You handle tools and data. Two lines of auth config, infinite clients.