{"id":50312582,"url":"https://github.com/5queezer/mcp-oauth-template","last_synced_at":"2026-05-28T22:01:58.478Z","repository":{"id":350836937,"uuid":"1208447111","full_name":"5queezer/mcp-oauth-template","owner":"5queezer","description":"Generic MCP server with OAuth 2.1 PKCE for claude.ai — deploy to Cloud Run in one command","archived":false,"fork":false,"pushed_at":"2026-04-20T22:18:41.000Z","size":64,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T23:27:52.930Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/5queezer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-12T09:44:28.000Z","updated_at":"2026-04-20T22:18:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/5queezer/mcp-oauth-template","commit_stats":null,"previous_names":["5queezer/mcp-oauth-template"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/5queezer/mcp-oauth-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fmcp-oauth-template","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fmcp-oauth-template/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fmcp-oauth-template/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fmcp-oauth-template/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/5queezer","download_url":"https://codeload.github.com/5queezer/mcp-oauth-template/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fmcp-oauth-template/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33627948,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-28T22:01:57.512Z","updated_at":"2026-05-28T22:01:58.469Z","avatar_url":"https://github.com/5queezer.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mcp-oauth-template\n\nGeneric MCP server with OAuth 2.1 PKCE for claude.ai.  \nBuild any MCP service in ~30 lines. Deploy to Cloud Run in one command.\n\n---\n\n## Structure\n\n```\nmcp_server/\n  __init__.py        -- Public API\n  auth.py            -- PKCE + TokenStore + AuthProvider (+ challenge hook)\n  oauth_routes.py    -- OAuth 2.1 AS endpoints\n  app.py             -- FastAPI factory (wires everything)\n  context.py         -- Per-request `current_sub` for tool code\n\nexamples/\n  polymarket_server.py    -- No-auth example (Polymarket markets)\n  github_oauth_server.py  -- Per-user upstream OAuth (GitHub)\n\ntests/\n  test_oauth.py         -- Full PKCE flow + edge cases\n  test_github_oauth.py  -- GitHub provider + callback logic\n```\n\n---\n\n## Quick Start\n\n### 1. Install\n\n```bash\nuv pip install .\n```\n\n### 2. Build your MCP server\n\n```python\n# my_service.py\nimport fastmcp\nfrom mcp_server import create_app\n\nmcp = fastmcp.FastMCP(\n    \"my-service\",\n    instructions=\"Describe what your server does — shown as the connector card in claude.ai.\",\n)\n\n@mcp.tool()\ndef my_tool(query: str) -\u003e str:\n    return f\"Result for: {query}\"\n\napp = create_app(mcp=mcp)\n```\n\n### 3. Run locally\n\n```bash\nuvicorn my_service:app --reload --port 8080\n```\n\n### 4. Test OAuth discovery\n\n```bash\ncurl http://localhost:8080/.well-known/oauth-authorization-server | jq\n```\n\n### 5. Deploy to Cloud Run\n\n```bash\nchmod +x deploy.sh\n./deploy.sh my-service europe-west1\n```\n\n### 6. Add to claude.ai\n\nSettings → Connectors → Add MCP Server  \nURL: `https://my-service-xxxx.run.app/mcp`\n\nClaude.ai will handle the OAuth PKCE flow automatically.\n\n---\n\n## Auth Modes\n\n### Single-user (default, no login)\n\n```python\napp = create_app(mcp=mcp)\n# /authorize issues code immediately -- protect at network level\n```\n\n### Single-user with password\n\n```python\nfrom mcp_server import create_app, StaticPasswordProvider\nimport os\n\napp = create_app(\n    mcp=mcp,\n    provider=StaticPasswordProvider(os.environ[\"ADMIN_PASSWORD\"])\n)\n```\n\nSet `ADMIN_PASSWORD` env var on Cloud Run. claude.ai redirects the user's browser to `/authorize`; the server renders a password form; after submit, the OAuth code is issued and the PKCE flow completes.\n\n### Multi-user (upstream OAuth)\n\nSubclass `AuthProvider`:\n\n```python\nfrom mcp_server.auth import AuthProvider\nfrom starlette.requests import Request\n\nclass GoogleAuthProvider(AuthProvider):\n    def authenticate(self, request: Request, credentials: dict[str, str]) -\u003e str | None:\n        # `request` gives access to headers/cookies for session-based auth.\n        # `credentials` merges /authorize query params + POST form fields.\n        # Return user sub or None.\n        ...\n```\n\n### Upstream OAuth (per-user identity)\n\nEach claude.ai user logs in through an external identity provider (GitHub,\nGoogle, etc.) and subsequent tool calls run with **their own** upstream\ntoken — not a shared one. The server maintains an allowlist of permitted\nusers. Override `AuthProvider.challenge()` to redirect unauthenticated\nusers to the IdP; your callback route exchanges the code for a token,\nsets a session cookie, and bounces the user back into `/authorize`. Tools\nread the caller's identity via `mcp_server.get_current_sub()` and look up\nthe per-user token from a session store.\n\nSee [`examples/github_oauth_server.py`](examples/github_oauth_server.py)\nfor a complete, working GitHub implementation (`whoami`, `list_my_repos`,\n`get_starred` — each executes as the caller).\n\n---\n\n## OAuth 2.1 Flow (what claude.ai does)\n\n```mermaid\nsequenceDiagram\n    autonumber\n    participant C as claude.ai / browser\n    participant S as your MCP server\n    participant I as upstream IdP — e.g. GitHub\n\n    Note over C,S: Discovery\n    C-\u003e\u003eS: GET /.well-known/oauth-authorization-server\n    S--\u003e\u003eC: AS metadata — issuer, endpoints, PKCE=S256\n\n    Note over C,S: PKCE challenge\n    C-\u003e\u003eS: GET /authorize?code_challenge=S256\u0026redirect_uri=…\n    alt password required — StaticPasswordProvider\n        S--\u003e\u003eC: 200 HTML login form\n        C-\u003e\u003eS: POST /authorize — password + hidden PKCE fields\n        S--\u003e\u003eC: 302 redirect_uri?code=X\n    else upstream OAuth — challenge hook\n        S--\u003e\u003eC: 302 to IdP authorize URL\n        C-\u003e\u003eI: GET /login/oauth/authorize\n        Note over C,I: user logs in + approves\n        I--\u003e\u003eC: 302 /auth/github/callback?code=Y\n        C-\u003e\u003eS: GET /auth/github/callback?code=Y\n        S-\u003e\u003eI: POST /login/oauth/access_token\n        I--\u003e\u003eS: access_token\n        S--\u003e\u003eC: 302 /authorize — Set-Cookie mcp_session\n        C-\u003e\u003eS: GET /authorize + Cookie\n        S--\u003e\u003eC: 302 redirect_uri?code=X\n    else no password — SingleUserProvider\n        S--\u003e\u003eC: 302 redirect_uri?code=X\n    end\n\n    Note over C,S: PKCE verify\n    C-\u003e\u003eS: POST /token — code=X + code_verifier\n    S--\u003e\u003eC: access_token\n\n    Note over C,S: Tool calls\n    loop each tool invocation\n        C-\u003e\u003eS: POST /mcp — Authorization Bearer …\n        S--\u003e\u003eC: tool result\n    end\n```\n\n---\n\n## Security Notes\n\n- `SingleUserProvider` with no password: protect `/authorize` via Cloud Run IAM or VPN if not behind a login\n- `StaticPasswordProvider`: use a strong random password, rotate via env var\n- Token store is in-memory -- tokens lost on restart; users re-auth automatically (PKCE flow is fast)\n- For production multi-user: replace `TokenStore` with a Redis or SQLite-backed implementation\n- `state` parameter is passed through but not validated in `SingleUserProvider` mode -- add validation for multi-user\n\n---\n\n## Tests\n\n```bash\nuv pip install '.[dev]'\npytest tests/ -v\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5queezer%2Fmcp-oauth-template","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F5queezer%2Fmcp-oauth-template","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5queezer%2Fmcp-oauth-template/lists"}