A simple MCP server using streamable HTTP transport, secured with Auth0 or Clerk (OAuth/OIDC upstream via FastMCP).
uv sync
cp .env.example .env- Create an Auth0 account and tenant
- Create a Regular Web Application — copy the Client ID and Client Secret
- Create an API — copy the Audience (identifier)
- Enable Dynamic Client Registration on your tenant (Settings > Advanced > OAuth)
- Fill in your
.env:
AUTH0_DOMAIN=your-tenant.us.auth0.com
AUTH0_CLIENT_ID=your-client-id
AUTH0_CLIENT_SECRET=your-client-secret
AUTH0_AUDIENCE=your-api-audience
BASE_URL=http://localhost:8000
See .env.example for the full list.
Set AUTH_PROVIDER=auth0 (default) or AUTH_PROVIDER=clerk.
- In Clerk Dashboard → OAuth applications, create an app and copy Discovery URL, Client ID, and Client Secret.
- Under Scopes for that OAuth app, enable only what you need. This server defaults to requesting
profileandemail(noopenid), which matches many Clerk apps. If you enableopenidin the Dashboard (for OIDC /id_token), setCLERK_SCOPES=openid profile emailin.envso the requested scopes match. See How Clerk implements OAuth. - In
.env:AUTH_PROVIDER=clerk,CLERK_CONFIG_URL,CLERK_CLIENT_ID,CLERK_CLIENT_SECRET, andBASE_URL. Optional:CLERK_SCOPES(space- or comma-separated) to override requested scopes exactly. - Add the FastMCP callback to the Clerk app’s redirect URIs (same host/port as
BASE_URL), e.g.http://localhost:8000/auth/callback, plus Cursor’scursor://anysphere.cursor-mcp/oauth/callbackif required by your setup. - If Clerk’s access token is opaque, set
CLERK_VERIFY_ID_TOKEN=trueso validation uses the OIDC id_token (JWT). That requires theopenidscope on the Clerk OAuth app. See Clerk as OIDC IdP.
invalid_scope / “not allowed to request scope openid”: Your OAuth app was not created with the openid scope. Either edit the app in the Dashboard and add openid, then set CLERK_SCOPES=openid profile email, or leave openid off the app and do not request it (defaults profile email only).
Do you need a Machine-to-Machine (M2M) app? No — Cursor and MCP Inspector use interactive login (authorization code + PKCE). Use one Regular Web Application for your Client ID/Secret. M2M is only optional if you want client-credentials tokens for scripts or curl tests, not for Cursor.
That URL must return JSON in a browser. If you see 404:
- In Auth0: Settings → General → Domain — copy the value exactly into
.envasAUTH0_DOMAIN(nohttps://). Openhttps://<AUTH0_DOMAIN>/.well-known/openid-configurationin a browser; it must return JSON, not 404. - If you use a custom domain, put that hostname in
AUTH0_DOMAINinstead.
Cursor uses Streamable HTTP + OAuth for remote MCP. Your TinyMCP server must be running (uv run python server.py).
FastMCP’s OAuth proxy first acts as its own authorization server for MCP clients. Cursor must dynamically register with your server and get an MCP client ID (stored by FastMCP). That ID is not your Auth0 application’s Client ID.
If you add Cursor’s auth block with AUTH0_CLIENT_ID / AUTH0_CLIENT_SECRET, Cursor sends Auth0’s app id to /authorize, and FastMCP responds with “Client Not Registered” — that id was never registered on the MCP server.
Use a URL-only entry (see .cursor/mcp.json.example):
{
"mcpServers": {
"tinymcp": {
"url": "http://localhost:8000/mcp"
}
}
}Then Cursor discovers OAuth metadata, calls /register on FastMCP, and completes the browser flow through FastMCP → Auth0.
If you previously used static auth, remove it, restart Cursor, and clear stale MCP auth (e.g. Command Palette → sign out / remove MCP or cached OAuth for this server) so Cursor registers again.
Use the Regular Web Application whose Client ID/Secret are already in .env (server-side only).
-
Applications → your app → Settings
-
Under Allowed Callback URLs, include both:
http://localhost:8000/auth/callback— FastMCP OAuth callback (change host/port ifBASE_URLdiffers)cursor://anysphere.cursor-mcp/oauth/callback— Cursor’s fixed redirect
-
Save changes.
(Optional) Under Advanced Settings → Grant Types, ensure Authorization Code is enabled.
- Project:
.cursor/mcp.json - Global:
~/.cursor/mcp.json
cp .cursor/mcp.json.example .cursor/mcp.json- Cursor Settings → Features → MCP (or Cmd+Shift+J → MCP)
- Ensure tinymcp is enabled
- When prompted, complete login in the browser (via FastMCP → Auth0)
If connection fails, open Output → MCP for errors.
Adjust scopes in mcp.json if your Auth0 API defines custom scopes (must match what the API allows).
uv run python server.pyThe server starts at http://localhost:8000/mcp.
- hello — Say hello to someone
- add — Add two numbers together
Cursor skills are Cursor-specific. For a generic setup that also works with other agents, keep shared tool instructions in:
docs/agent-tool-guidelines.md
Then:
- Cursor: keep lightweight adapters in
.cursor/skills/*/SKILL.mdthat reference the shared doc. - Other agents (for example Claude): copy/reference
docs/agent-tool-guidelines.mdin their own project instruction system.
# Claude Code
claude mcp add --transport http tinymcp http://localhost:8000/mcp
# MCP Inspector (OAuth flow depends on client support)
npx -y @modelcontextprotocol/inspector