Back to MCP Store
Partner guide

Build & publish an MCP app

Scaffold with FastAPI Cloud, add fastapi-mcp and store auth, deploy your MCP server, and submit for catalog review. Users sign in once through the MCP Store — your app verifies installs before serving tools.

Recommended stack

FastAPI Cloud

Scaffold, deploy, and host your MCP server — one CLI workflow

fastapi-mcp

Expose MCP tools as FastAPI routes at /mcp

Store auth

Verify Supabase JWTs + install status on every request

MCP Store

Catalog, OAuth, and shared user identity

What you own vs. what the store owns

You deploy & maintain

  • • MCP tools and business logic
  • • Your FastAPI Cloud app at mcp_url
  • • Scaling and uptime of your server

MCP Store provides

  • • User accounts & OAuth for MCP clients
  • • Sign-in UI at /mcp-store/oauth
  • • Catalog listing after review
  • • Install records — who may use your app

1. Create your FastAPI Cloud project

Start with the FastAPI Cloud quick start. This scaffolds a project with fastapi[standard] (includes the deploy CLI) and a working app/main.py.

Terminal
uvx fastapi-new myapp
cd myapp
source .venv/bin/activate   # Windows: .venv\Scripts\activate

You'll need

  • uv installed
  • • A FastAPI Cloud account (created on first fastapi deploy)

2. Add fastapi-mcp

Install MCP dependencies, then replace app/main.py with the wired version in the next section. Every tool route uses Depends(require_installed_user) so only users who installed your app from the catalog can call tools.

Terminal
uv add fastapi-mcp httpx pyjwt
app/main.py
from fastapi import Depends, FastAPI
from fastapi_mcp import AuthConfig, FastApiMCP
from app.mcp_store_auth import StoreUser, require_installed_user
import os

APP_SLUG = os.environ["APP_SLUG"]
STORE_URL = os.environ["STORE_URL"].rstrip("/")

app = FastAPI(title="My App", version="0.1.0")

@app.get("/health")
def health() -> dict[str, str]:
    return {"status": "ok", "app": APP_SLUG}

@app.get("/my_tool", operation_id="my_tool", summary="My first tool")
async def my_tool(user: StoreUser = Depends(require_installed_user)):
    return {"message": f"Hello {user.email or user.id}"}

mcp = FastApiMCP(
    app,
    name="My App",
    auth_config=AuthConfig(
        issuer=STORE_URL,
        authorize_url=f"{STORE_URL}/oauth/authorize?app={APP_SLUG}",
        oauth_metadata_url=f"{STORE_URL}/.well-known/oauth-authorization-server",
        client_id="mcp-store",
        client_secret="local-dev-secret",
        dependencies=[Depends(require_installed_user)],
        setup_proxies=True,
        setup_fake_dynamic_registration=True,
    ),
)
mcp.mount_http()  # → /mcp

3. Store auth module

Add app/mcp_store_auth.py. It verifies JWTs from the MCP Store's Supabase project and calls the store install-check endpoint before your tools run. Required on every MCP app.

app/mcp_store_auth.py
"""Save as app/mcp_store_auth.py"""

from __future__ import annotations

import os
from dataclasses import dataclass
from functools import lru_cache
from uuid import UUID

import httpx
import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jwt import PyJWKClient

_bearer = HTTPBearer(auto_error=False)

APP_SLUG = os.environ["APP_SLUG"]
STORE_URL = os.environ["STORE_URL"].rstrip("/")
SUPABASE_URL = os.environ["SUPABASE_URL"].rstrip("/")


@dataclass
class StoreUser:
    id: UUID
    email: str | None


@lru_cache
def _jwks_client() -> PyJWKClient:
    return PyJWKClient(f"{SUPABASE_URL}/auth/v1/.well-known/jwks.json")


def verify_supabase_jwt(token: str) -> StoreUser:
    try:
        signing_key = _jwks_client().get_signing_key_from_jwt(token)
        payload = jwt.decode(
            token,
            signing_key.key,
            algorithms=["ES256", "HS256"],
            audience="authenticated",
        )
    except jwt.PyJWTError as exc:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token.") from exc

    sub = payload.get("sub")
    if not sub:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token subject.")
    return StoreUser(id=UUID(sub), email=payload.get("email"))


async def verify_install(token: str) -> StoreUser:
    user = verify_supabase_jwt(token)
    async with httpx.AsyncClient(timeout=10.0) as client:
        response = await client.get(
            f"{STORE_URL}/api/v1/auth/verify",
            params={"app": APP_SLUG},
            headers={"Authorization": f"Bearer {token}"},
        )
    if response.status_code == status.HTTP_401_UNAUTHORIZED:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token.")
    if response.status_code != status.HTTP_200_OK:
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Store verify unavailable.")

    data = response.json()
    if not data.get("authorized"):
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=data.get("reason", "not_installed"))
    return user


async def require_installed_user(
    credentials: HTTPAuthorizationCredentials | None = Depends(_bearer),
) -> StoreUser:
    if credentials is None or credentials.scheme.lower() != "bearer":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Missing bearer token.",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return await verify_install(credentials.credentials)

Store URLs your app uses

  • Store API https://mcp-store.fastapicloud.dev
  • OAuth authorize https://mcp-store.fastapicloud.dev/oauth/authorize?app={APP_SLUG}Users sign in at https://thinkrecursion.ai/mcp-store/oauth during MCP client setup.
  • Install verify https://mcp-store.fastapicloud.dev/api/v1/auth/verify?app={APP_SLUG}
  • OAuth metadata https://mcp-store.fastapicloud.dev/.well-known/oauth-authorization-server

4. Environment variables & Supabase

MCP Store users authenticate through a shared Supabase project. Set SUPABASE_URL to that project URL so your app can verify JWTs via public JWKS — no Supabase secret keys needed in your app. If your tools need their own database, you can additionally connect a Supabase project through the FastAPI Cloud Supabase integration (injects DATABASE_URL). That is separate from store auth.

.env.local (development)
APP_SLUG=my-app
STORE_URL=http://localhost:8002
SUPABASE_URL=http://127.0.0.1:54321
Production values (set on FastAPI Cloud)
APP_SLUG=my-app
STORE_URL=https://mcp-store.fastapicloud.dev
SUPABASE_URL=https://YOUR_STORE_SUPABASE_PROJECT.supabase.co
APP_ENV=production

APP_SLUG must match your catalog slug. Contact jackson@thinkrecursion.ai for the production SUPABASE_URLif you don't have it yet.

Run locally
# Create .env.local in project root (see env vars section)
fastapi dev

# Health:  http://127.0.0.1:8000/health
# MCP:     http://127.0.0.1:8000/mcp

5. Deploy to FastAPI Cloud

Deploy creates your FastAPI Cloud app and gives you a URL like https://myapp.fastapicloud.dev. Set env vars, redeploy, then use https://myapp.fastapicloud.dev/mcp as your catalog mcp_url.

Terminal
# Creates your FastAPI Cloud app on first run
fastapi deploy
# → https://myapp.fastapicloud.dev

# Set production env vars, then redeploy
fastapi cloud env set APP_SLUG my-app
fastapi cloud env set STORE_URL https://mcp-store.fastapicloud.dev
fastapi cloud env set SUPABASE_URL https://YOUR_STORE_SUPABASE_PROJECT.supabase.co
fastapi cloud env set APP_ENV production

fastapi deploy

# Catalog mcp_url:
# https://myapp.fastapicloud.dev/mcp

Confirm: curl https://myapp.fastapicloud.dev/health · Optional: connect GitHub in the FastAPI Cloud dashboard for auto-deploy on push.

6. Write the agent skill file

The MCP URL tells clients where to connect. A SKILL.md tells agents when and how to use your tools. Users get both after installing from the catalog.

SKILL.md (Cursor agent skill format)
---
name: my-app
description: When and how agents should use this MCP app. Be specific.
---

# My App

## When to use

Describe the scenarios where an agent should reach for this app.

## Tools

### my_tool

What it does, required inputs, and example prompts.

## Setup

1. Install from the MCP Store catalog
2. Connect your MCP client to the app's MCP URL
3. Add this SKILL.md to your agent (Cursor skills folder)

7. Submit for review

Submit your live mcp_url and SKILL.md content. RecursionAI reviews both before approving. Approved apps appear in the public catalog.

Ready to publish?

Sign in with your MCP Store account and submit slug, name, live mcp_url, and your SKILL.md.

Submit app