Flowershow MCP OAuth 2.1 Implementation Plan
Flowershow MCP OAuth 2.1 Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Add OAuth 2.1 authorization server support to apps/flowershow-mcp for Claude Desktop connector compatibility while preserving existing PAT authentication.
Architecture: From the client's perspective, the MCP server is both Authorization Server and Resource Server — clients only interact with the MCP server for OAuth endpoints (/authorize, /token, /register, metadata). Internally, the MCP server delegates all state persistence (clients, codes, tokens) to authenticated REST endpoints in apps/flowershow via MCP_OAUTH_API_SECRET. The SDK's mcpAuthRouter handles the OAuth protocol surface (including Protected Resource Metadata per RFC 9728 and Authorization Server Metadata per RFC 8414), while requireBearerAuth protects /mcp and sets WWW-Authenticate headers with resource_metadata on 401. Browser login/consent uses existing NextAuth session on a new /mcp/authorize page. Token verification supports JWT access tokens first (with iss/aud/exp validation per RFC 8707) with PAT fallback for backward compatibility. Targets MCP spec revision 2025-06-18.
Tech Stack: TypeScript, Next.js App Router, Prisma, PostgreSQL, @modelcontextprotocol/sdk OAuth router/provider APIs, jose, Vitest, Turbo, Zod (@flowershow/api-contract).
Task 1: Create and claim the related beads issue before coding
Files:
- Modify: none
Step 1: Find ready work
Run: bd ready
Expected: list includes an OAuth/MCP-related item; if none exists, identify nearest parent task.
Step 2: Inspect the chosen issue
Run: bd show <id>
Expected: scope matches MCP OAuth implementation.
Step 3: Claim issue
Run: bd update <id> --status in_progress
Expected: issue status updates to in_progress.
Step 4: Commit (if issue metadata files changed)
git add .beads/
git commit -m "chore: claim mcp oauth implementation issue"
Task 2: Add failing MCP-server OAuth app tests (route/middleware wiring)
Files:
- Modify:
apps/flowershow-mcp/src/http.test.ts - Modify:
apps/flowershow-mcp/src/app.test.ts
Step 1: Add failing tests for OAuth metadata endpoints
Add tests asserting all of these return 200 and JSON bodies:
GET /.well-known/oauth-authorization-server— must includeissuer,authorization_endpoint,token_endpoint,registration_endpointGET /.well-known/oauth-protected-resource/mcp— must includeresource,authorization_servers,scopes_supported
Note: The PRM path suffix
/mcpis derived from theresourceServerUrlpassed tomcpAuthRouter. IfresourceServerUrlishttps://host.com/mcp, the SDK mounts PRM at/.well-known/oauth-protected-resource/mcp. If root, it's at/.well-known/oauth-protected-resource. UsegetOAuthProtectedResourceMetadataUrl(serverUrl)from the SDK to compute the expected path in tests.
Optionally add compatibility tests for path variants some clients request:
GET /.well-known/oauth-authorization-server/mcp— if not handled, verify it returns 404 (and consider adding a redirect in a follow-up)
Step 2: Add failing tests for auth behavior changes
Add tests asserting:
POST /mcpwithout bearer returns401withWWW-Authenticateheader containingresource_metadata=".../.well-known/oauth-protected-resource/mcp"POST /mcpwith invalid bearer returns401withWWW-AuthenticateheaderPOST /mcpwith a valid mocked OAuth token reaches MCP initialize and returns success.
Step 3: Run focused tests to verify failures
Run: pnpm --filter @flowershow/mcp test -- src/http.test.ts src/app.test.ts
Expected: new tests fail with missing OAuth wiring.
Step 4: Commit failing tests
git add apps/flowershow-mcp/src/http.test.ts apps/flowershow-mcp/src/app.test.ts
git commit -m "test: cover mcp oauth metadata and bearer auth wiring"
Task 3: Add MCP OAuth provider/client modules and make tests pass
Files:
- Create:
apps/flowershow-mcp/src/lib/oauth-provider.ts - Create:
apps/flowershow-mcp/src/lib/oauth-api.ts - Create:
apps/flowershow-mcp/src/lib/oauth-jwt.ts - Modify:
apps/flowershow-mcp/src/app.ts - Modify:
apps/flowershow-mcp/src/index.ts - Modify:
apps/flowershow-mcp/src/local-dev.ts - Modify:
apps/flowershow-mcp/package.json
Step 1: Implement oauth-api.ts HTTP client (no app wiring yet)
Implement methods for MCP server -> Flowershow API calls:
getClient(clientId)registerClient(clientMetadata)createAuthorizationChallenge(...)exchangeAuthorizationCode(...)exchangeRefreshToken(...)revokeToken(...)verifyPatToken(...)(PAT fallback endpoint)
All requests must send Authorization: Bearer ${MCP_OAUTH_API_SECRET} and parse strict JSON errors.
Step 2: Implement oauth-jwt.ts helpers
Implement:
issueAccessToken(payload)usingjosewithMCP_OAUTH_JWT_SECRET- Set
isstoMCP_OAUTH_ISSUER_URL - Set
audto the MCP server's resource URL (MCP_OAUTH_RESOURCE_URL, e.g.https://mcp.flowershow.app/mcp) - Set
exp(short-lived, e.g. 15 minutes),iat,nbf - Include
scope(space-delimited string),client_id, andjti - If
resourceparameter was provided in the token request, include it in claims
- Set
verifyAccessToken(token)returning MCPAuthInfo- Validate
issmatchesMCP_OAUTH_ISSUER_URL(reject tokens from other issuers) - Validate
audmatchesMCP_OAUTH_RESOURCE_URL(RFC 8707 audience binding — reject tokens not intended for this server) - Enforce
exp(reject expired),nbf(reject not-yet-valid), validateiatis present - Parse
scopeclaim intostring[]forAuthInfo.scopes - Populate
AuthInfo.resourcefrom claims if present - Return
AuthInfowithtoken,clientId,scopes,expiresAt,resource
- Validate
Step 3: Implement oauth-provider.ts
Implement OAuthServerProvider with:
clientsStore.getClient/clientsStore.registerClientregisterClientmust validatetoken_endpoint_auth_method(accept"none"for public clients per MCP spec)- Validate
redirect_uris: allowhttp://localhost:*andhttp://127.0.0.1:*, require HTTPS for all other URIs - Validate
grant_types,response_types,client_name
authorize(...)redirecting browser to Flowershow consent URL (include serialized challenge/context)challengeForAuthorizationCode(...)exchangeAuthorizationCode(client, code, codeVerifier?, redirectUri?, resource?)— passresourceparameter through to token issuance (RFC 8707)exchangeRefreshToken(client, refreshToken, scopes?, resource?)— passresourceparameter through to token reissuanceverifyAccessToken(...)with JWT-first, PAT fallback second- optional
revokeToken(...) skipLocalPkceValidation: set tofalse— PKCE S256 is required for all clients per MCP spec
Step 4: Wire app router in app.ts
Use SDK router/middleware:
- mount
mcpAuthRouter(...)at app root with options:provider: theOAuthServerProviderinstanceissuerUrl:new URL(MCP_OAUTH_ISSUER_URL)resourceServerUrl:new URL(MCP_OAUTH_RESOURCE_URL)(e.g.https://mcp.flowershow.app/mcp) — this determines the PRM path suffixscopesSupported:['mcp:tools'](or similar)resourceName:'Flowershow MCP Server'
- protect
/mcpwithrequireBearerAuth({ verifier: provider, resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resourceServerUrl) })- This ensures 401 responses include
WWW-Authenticate: Bearer resource_metadata="..."per MCP spec 2025-06-18 / RFC 9728
- This ensures 401 responses include
- create per-request
FlowershowApiusingreq.authtoken - remove direct
extractPathard requirement (retain helper only if used for PAT fallback tests)
Step 5: Add dependency updates
In apps/flowershow-mcp/package.json, add:
josecorsexpress-rate-limitpkce-challenge
Step 6: Run MCP tests and make green
Run: pnpm --filter @flowershow/mcp test
Expected: all MCP tests pass.
Step 7: Commit
git add apps/flowershow-mcp/src apps/flowershow-mcp/package.json
git commit -m "feat: add oauth 2.1 auth server support to mcp service"
Task 4: Add failing contract tests/schemas for new main-app MCP OAuth API
Files:
- Modify:
packages/api-contract/src/schemas.ts - Create:
packages/api-contract/src/routes/mcp-oauth.ts - Modify:
packages/api-contract/src/openapi.ts - Create:
packages/api-contract/src/routes/mcp-oauth.test.ts(if route tests pattern exists)
Step 1: Add schema types in contract first
Create Zod schemas and exported inferred types for:
- registered client read/write payloads
- Write payload must include:
redirect_uris,grant_types,response_types,token_endpoint_auth_method(enum:"none"for public clients,"client_secret_post"for confidential),client_name - Read payload includes all write fields plus
client_id,client_id_issued_at,client_secret(optional, only for confidential clients),client_secret_expires_at(optional)
- Write payload must include:
- authorization challenge creation payload (include
resourceparameter per RFC 8707) - authorization code exchange payload (include
resourceparameter per RFC 8707) - refresh token exchange payload (include
resourceparameter per RFC 8707) - token revocation payload
- PAT validation request/response
Step 2: Register route definitions
In routes/mcp-oauth.ts, register all endpoints with method/path/tags/security/operationId.
Suggested tag: CLI Auth until dedicated OAuth tag is added.
Step 3: Wire route file into openapi.ts
Import and include route registration.
Step 4: Build contract to verify failing/passing shape
Run: pnpm turbo build --filter=@flowershow/api-contract
Expected: build passes and generated OpenAPI includes /api/mcp-oauth/* endpoints.
Step 5: Commit
git add packages/api-contract/src
git commit -m "feat: add api contract routes for mcp oauth persistence"
Task 5: Implement main-app MCP OAuth persistence endpoints
Files:
- Create:
apps/flowershow/app/api/mcp-oauth/clients/route.ts - Create:
apps/flowershow/app/api/mcp-oauth/clients/[clientId]/route.ts - Create:
apps/flowershow/app/api/mcp-oauth/challenges/route.ts - Create:
apps/flowershow/app/api/mcp-oauth/token/exchange-code/route.ts - Create:
apps/flowershow/app/api/mcp-oauth/token/exchange-refresh/route.ts - Create:
apps/flowershow/app/api/mcp-oauth/token/revoke/route.ts - Create:
apps/flowershow/app/api/mcp-oauth/token/verify-pat/route.ts - Modify: shared auth helpers as needed (prefer
apps/flowershow/lib/cli-auth.tsfor PAT validation reuse)
Step 1: Add auth guard for internal MCP OAuth API secret
Implement shared guard:
- require
Authorization: Bearer ${env.MCP_OAUTH_API_SECRET} - return
401JSON error on mismatch
Step 2: Implement each route with contract schemas
For each endpoint:
- use
.safeParse()with imported contract schemas - return
400with{ error: 'bad_request', error_description }on validation failure - return typed responses (
satisfies SomeType)
DCR policy for client registration endpoint:
- Allow unauthenticated Dynamic Client Registration (no auth required to POST
/register) - Validate
redirect_uris: accepthttp://localhost:<port>andhttp://127.0.0.1:<port>for desktop/CLI clients; requirehttps://for all other URIs - Require PKCE (
code_challenge_method: "S256") — reject"plain"if provided - Accept
token_endpoint_auth_method: "none"(public clients — Claude Desktop, MCP Inspector) - Rate-limit DCR endpoint to prevent abuse (e.g. 10 registrations/minute per IP)
Step 3: Reuse/centralize PAT validation for fallback endpoint
Expose PAT validation by token string (or factor reusable logic from request-based validator).
Step 4: Run route-level tests (add if absent)
Run: pnpm --filter flowershow test
Expected: new endpoint tests pass.
Step 5: Commit
git add apps/flowershow/app/api/mcp-oauth apps/flowershow/lib/cli-auth.ts
git commit -m "feat: add internal mcp oauth persistence api endpoints"
Task 6: Add Prisma models + migration for MCP OAuth persistence
Files:
- Modify:
apps/flowershow/prisma/schema.prisma - Create:
apps/flowershow/prisma/migrations/<timestamp>_add_mcp_oauth_tables/migration.sql
Step 1: Add new models
Add:
McpOAuthClientMcpOAuthCodeMcpOAuthToken
Include indexes/uniques for:
clientIdunique- authorization code lookup and expiry cleanup
- refresh token hash lookup
- access token jti/hash lookup
Step 2: Generate migration
Run from apps/flowershow:
pnpm prisma migrate dev --name add_mcp_oauth_tables
Expected: migration SQL created and Prisma client updated.
Step 3: Run type/build checks
Run: pnpm --filter flowershow build
Expected: app compiles against new Prisma client.
Step 4: Commit
git add apps/flowershow/prisma/schema.prisma apps/flowershow/prisma/migrations
git commit -m "feat: add prisma models for mcp oauth clients codes and tokens"
Task 7: Build consent UI and browser handoff endpoint
Files:
- Create:
apps/flowershow/app/(cloud)/mcp/authorize/page.tsx - Create:
apps/flowershow/app/api/mcp-oauth/authorize/route.ts - Modify: shared UI/auth helpers if needed
Step 1: Add failing UI/API tests
Add tests asserting:
- unauthenticated users are redirected to
/login?callbackUrl=... - authenticated users see client + scope consent page
- approve action creates authorization code and redirects to OAuth
redirect_uri?code=...&state=... - deny action redirects with OAuth error (
access_denied)
Step 2: Implement consent page
Pattern after CLI authorize page:
- read OAuth parameters from query
- show client name, requested scopes, signed-in account
- approve/cancel buttons with loading/error states
Step 3: Implement authorize API route
Use session + parsed request to create auth challenge/code record and response redirect URL.
Step 4: Run tests
Run: pnpm --filter flowershow test
Expected: consent flow tests pass.
Step 5: Commit
git add apps/flowershow/app/(cloud)/mcp/authorize apps/flowershow/app/api/mcp-oauth/authorize
git commit -m "feat: add mcp oauth consent flow on cloud app"
Task 8: Wire environment variables and configuration validation
Files:
- Modify:
apps/flowershow/env.mjs - Modify:
apps/flowershow-mcp/src/index.ts - Modify:
apps/flowershow-mcp/src/local-dev.ts - Modify:
apps/flowershow-mcp/README.md - Modify: root/env example files as applicable
Step 1: Add env vars to main app
Add MCP_OAUTH_API_SECRET to server schema + runtimeEnv.
Step 2: Add env vars to MCP app config path
Require/configure:
FLOWERSHOW_API_URLMCP_OAUTH_API_BASE_URL(if separate fromFLOWERSHOW_API_URL)MCP_OAUTH_API_SECRETMCP_OAUTH_ISSUER_URL— the issuer identifier for JWTissclaim and AS metadataMCP_OAUTH_RESOURCE_URL— the canonical MCP resource URL (e.g.https://mcp.flowershow.app/mcp); used for PRMresourcefield, JWTaudclaim, andresourceServerUrlinmcpAuthRouterMCP_OAUTH_JWT_SECRETMCP_OAUTH_CONSENT_URL
Step 3: Update docs
Document local dev + production env setup and OAuth endpoint expectations.
Step 4: Run builds
Run:
pnpm --filter flowershow buildpnpm --filter @flowershow/mcp build
Expected: both builds pass.
Step 5: Commit
git add apps/flowershow/env.mjs apps/flowershow-mcp/src/index.ts apps/flowershow-mcp/src/local-dev.ts apps/flowershow-mcp/README.md
git commit -m "chore: configure env and docs for mcp oauth flow"
Task 9: End-to-end verification of OAuth and PAT compatibility
Files:
- Modify:
apps/flowershow-mcp/src/http.test.ts - Create:
apps/flowershow-mcp/src/oauth.integration.test.ts
Step 1: Add E2E-ish integration tests with mocked persistence API
Cover:
- dynamic client registration endpoint (public client with
token_endpoint_auth_method: "none") - authorization code exchange with PKCE S256 (reject plain, reject missing)
- authorization code exchange with
resourceparameter (RFC 8707) - refresh token exchange with
resourceparameter - JWT audience validation: token issued for wrong resource URL is rejected
- JWT issuer validation: token with wrong
issis rejected - expired token rejection
- revoked token failure
- PAT fallback still authorizes
/mcp WWW-Authenticateheader on 401 containsresource_metadataURL- PRM endpoint returns correct
authorization_serversandresourcevalues
Step 2: Run all targeted test suites
Run:
pnpm --filter @flowershow/mcp testpnpm --filter flowershow testpnpm turbo build --filter=@flowershow/api-contract
Expected: all pass.
Step 3: Manual smoke checks
Run local stack and verify:
- OAuth metadata endpoints return valid JSON
- browser consent flow redirects correctly
- Claude Desktop connector can complete OAuth
Step 4: Commit
git add apps/flowershow-mcp/src/http.test.ts apps/flowershow-mcp/src/oauth.integration.test.ts
git commit -m "test: verify oauth flows and pat backward compatibility"
Task 10: Session completion, issue updates, and push (mandatory)
Files:
- Modify: issue tracker state and git history
Step 1: File follow-up issues for deferred work
Examples: token cleanup cron, OAuth scopes hardening, audit logging.
Step 2: Close/update beads issues
Run:
bd close <id>for completed scopebd update <id> --status in_progressfor partials
Step 3: Sync and push
Run in order:
git pull --rebase
bd sync
git push
git status
Expected: git status shows branch is up to date with origin.
Step 4: Final verification summary
Record test/build command outputs and handoff notes in PR description or session summary.
Implementation Notes and Guardrails
- Keep API contract as source of truth for all new
apps/flowershow/app/api/mcp-oauth/*routes. - Do not add local request/response interfaces in routes; import types from
@flowershow/api-contract. - For request validation failures, return exactly:
{ error: 'bad_request', error_description: parsed.error.message }
- Use token hashing at rest for opaque refresh tokens and any stored bearer-like values.
- Keep OAuth access tokens short-lived; rely on refresh token rotation.
- Preserve backward compatibility by keeping PAT verification path in
verifyAccessToken. - Keep commits small and scoped to one task wherever possible.
Spec Compliance Notes (MCP 2025-06-18 / RFC 9728 / RFC 8707)
- PRM is mandatory. The MCP server MUST serve Protected Resource Metadata. The SDK's
mcpAuthRouterdoes this automatically viamcpAuthMetadataRouter— do not remove or bypass it. - WWW-Authenticate on 401 is mandatory. The
requireBearerAuthmiddleware handles this whenresourceMetadataUrlis provided. Always pass it. - Resource Indicators (RFC 8707) are mandatory. Clients MUST send
resourcein auth/token requests. The server MUST validate audience. JWTaudmust matchMCP_OAUTH_RESOURCE_URL. - PKCE S256 is required for all clients. Do not allow
code_challenge_method: "plain". The SDK'sOAuthServerProviderhasskipLocalPkceValidation— set it tofalse. - DCR is SHOULD, not MUST. But we implement it because Claude Desktop and MCP Inspector rely on it. Public clients (
token_endpoint_auth_method: "none") must be supported. - Token passthrough is forbidden. The MCP server must never forward client tokens to the Flowershow API. Internal API calls use
MCP_OAUTH_API_SECRET. - Anthropic MCP Connector (API) requires pre-obtained tokens. Users paste
authorization_tokeninto the API call. Our OAuth flow works for Claude Desktop / MCP Inspector, and PAT fallback covers the Connector API use case. - The MCP server is the issuer. From the client's perspective, the MCP server is both AS and RS. The PRM
authorization_serversfield points to the MCP server's own issuer URL. Internal delegation toapps/flowershowis invisible to clients.