✨ Ship a production-ready MCP server in an afternoon

MCP starter kit

FastAPI + Supabase + OAuth2 (PKCE) + SSE/Streamable HTTP. Works with Claude Desktop, Claude.ai custom connectors, and Cursor—out of the box.

✨ TL;DR for the capabilities

Swipe through what makes this starter kit special

Auth you can trust

OAuth2 + PKCE, refresh tokens, OIDC discovery

Data stays safe

Postgres with RLS; tenant scoped by JWT

Streaming that holds up

Streamable HTTP + SSE, keepalives, back-pressure

Client compatibility

Claude Desktop, Cursor, Inspector—works out-of-the-box

Real tools, now

CRUD / Search / Job examples, typed models, streaming

Production ready

Health/readiness, timeouts, structured logs

Deploy anywhere

Docker, 12-factor config, CI templates

Demo in minutes

Seed data, demo auth, sample tools

Scroll or click to see all capabilities

Quick Start

Pick your path

One-line bring-up

cd backend && uv sync && uv run uvicorn app.main:app --reload

When stuck, run: ./scripts/preflight.sh && npx @modelcontextprotocol/inspector

FAST Start Guide

The 6 things we fix

Real problems, concrete solutions. Each fix addresses a specific pain point that stops teams from shipping MCP servers.

1) Auth that sticks

PAIN

Claude.ai/Desktop flows (PKCE, refresh) often half-work; user context disappears.

FIX

Full OAuth2 + PKCE, short-lived access tokens with auto-refresh, get_current_user injected into every tool.

Example
@router.post("/api/v1/tools/items.create", response_model=Item)
def create(input: CreateItemInput, user=Depends(get_current_user)):
    return repo.create_item(owner_id=user.id, **input.model_dump())

2) Streaming that doesn't flake

PAIN

SSE timeouts, proxies, dropped connections → partial results.

FIX

SSE with heartbeats + graceful cleanup; Streamable HTTP fallback for hostile networks.

Example
return EventSourceResponse(gen(), ping=15)  # heartbeat keeps streams alive

3) Data isolation by default

PAIN

Hand-rolled WHERE owner_id = ? leaks in multi-tenant demos.

FIX

Postgres Row-Level Security (RLS) via Supabase; policies enforce per-user/org access.

Example
ALTER TABLE items ENABLE ROW LEVEL SECURITY;
CREATE POLICY items_owner ON items
USING (owner_id = auth.uid()) WITH CHECK (owner_id = auth.uid());

4) First success in minutes (not days)

PAIN

Blank servers don't demo; teams stall before value.

FIX

Working CRUD tools + a 3-step Quickstart (clone → .env → run → connect → items.create).

Example
Endpoint:  /api/v1/mcp/sse
Claude.ai (HTTPS): https://{PUBLIC_BASE_URL}/api/v1/mcp/sse
Desktop (Local):   http://127.0.0.1:8000/api/v1/mcp/sse

5) Operable under bursty loads

PAIN

Tail-latency spikes, silent timeouts, no breadcrumbs.

FIX

Async FastAPI, pooled DB, bounded concurrency & back-pressure, sensible timeouts; /health + structured logs with request IDs.

No code example

6) Runs anywhere, quickly

PAIN

"Works on my laptop" + platform-specific wiring hell.

FIX

Containerized app with copy-paste deploys (Docker, Cloud Run, K8s, Render, Railway, ECS, ACI) and HTTPS/local endpoints for Claude.

Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen
COPY app ./app
CMD ["uv","run","uvicorn","app.main:app","--host","0.0.0.0","--port","8000"]

One-liner:
MCP Starter Kit makes Claude MCP boring in all the right ways—auth that sticks, streams that don't flake, data that stays in its lane—so you can ship real tools fast, anywhere.

What this project does

MCP Starter Kit is a production-ready Model Context Protocol server built on FastAPI (Python 3.11+) with Postgres 15+ (via Supabase RLS) and OAuth2/OIDC + PKCE. It gives you a protocol-correct transport, real auth/tenancy, and a clean tool surface that streams results.

Transport (protocol-correct)

  • Streamable HTTP endpoint at POST /mcp (JSON-RPC 2.0 requests; supports server-to-client streaming for long jobs).
  • SSE compatibility at GET /mcp/sse with unified event shapes.
  • Back-pressure, idle timeouts, and resumable streams for long-running tools.

Auth & Discovery (no bespoke glue)

  • OIDC discovery exposed under /.well-known/oauth-authorization-server.
  • Authorization Code + PKCE required for desktop/native clients.
  • Access tokens → request context; structured JWT claims (sub, tenant) propagate to handlers.

Tenancy & Data Safety (by construction)

  • Row-Level Security on all user/tenant tables; policies use JWT claims for scoping.
  • Separate service role for admin/automation (kept out of request paths).
  • End-to-end request IDs and structured logs on every tool call.

Tool Surface (small, composable)

  • Registry-driven tools: CRUD, Search, Job patterns.
  • Handlers are stream-capable (progress + partials) and JSON-schema described.
  • Clear seams: transport → auth → handler → domain/service/DB.

Ops Rails (ship without yak-shaving)

  • Health/readiness: GET /healthz, GET /readyz.
  • Sensible defaults: timeouts, connection pooling, retry/backoff, and consistent error envelopes.
  • Dockerfile + DB migrations; env-first config (MCP_*, OAUTH_*, DB_*).

Client Compatibility (drop-in)

  • Works with common MCP hosts (e.g., Claude Desktop, Inspector).
  • Standard flow: tools/list tools/call (streamed) → final result.

At a glance (filesystem map)

/app
  /mcp          # transport wiring, JSON-RPC, streaming
  /auth         # OIDC discovery, PKCE, token exchange, middleware
  /tools        # registry + tool handlers (CRUD/Search/Job examples)
  /domain       # business logic services
  /db           # migrations, RLS policies, queries
  /ops          # healthz/readyz, logging, error envelopes

Minimal tool handler (one file)

# /tools/search.py
@tool("search.index", in_schema=SearchIn, out_schema=SearchOut, stream=True)
async def search_index(q: str, limit: int = 10, ctx: Ctx = Depends()):
    yield Progress(message="querying index")
    rows = await services.search(q=q, limit=limit, tenant=ctx.jwt.sub)
    for r in rows:
        yield Partial(r)
    return SearchOut(items=rows)

Why this exists

Three architectural decisions that remove the friction from building production MCP servers.

Auth that mirrors Claude reality

Full OAuth2 + PKCE with refresh that works for Claude.ai (HTTPS) and Claude Desktop (localhost). This removes 80% of real-world integration pain.

Dual-transport, same semantics

SSE with heartbeats plus an HTTP fallback that emits the same event sequence. Networks can be flaky; behavior shouldn't be.

RLS-first data model

Postgres Row-Level Security is the default, so "act-as-user" is enforced in the database, not by developer willpower.

How it works

Client (Claude/Cursor)MCP Server (FastAPI)Supabase Postgres (RLS)

Endpoints:

https://your-domain.com/api/v1/mcp/sse
https://your-domain.com/health

Auth:

OAuth2 (PKCE) → short-lived JWT with auto-refresh → user context arrives in your tool handler

Transports:

SSE with heartbeats for streaming; HTTP fallback for resilient requests

Tools:

Ready-made CRUD + logout; add your own handlers with typed inputs/outputs

Security:

Row-Level Security ensures each user only sees their own rows

First-call trace:

connect → OAuth success
tools.list["items.create", "items.list", "logout"]
items.create201 Created

Trusted by developers

Everything you need to build MCP servers

Production-ready components and integrations to get your MCP server running quickly.

FastAPI Backend

Modern Python web framework with automatic API documentation and validation.

Supabase Integration

Real-time database with built-in authentication and row-level security.

OAuth2 PKCE

Secure authentication flow compatible with Claude Desktop and web clients.

Spin up your MCP server today.

Apache-2.0 • Self-hosted • Remove in minutes if it's not a fit.