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).


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 include issuer, authorization_endpoint, token_endpoint, registration_endpoint
  • GET /.well-known/oauth-protected-resource/mcp — must include resource, authorization_servers, scopes_supported

Note: The PRM path suffix /mcp is derived from the resourceServerUrl passed to mcpAuthRouter. If resourceServerUrl is https://host.com/mcp, the SDK mounts PRM at /.well-known/oauth-protected-resource/mcp. If root, it's at /.well-known/oauth-protected-resource. Use getOAuthProtectedResourceMetadataUrl(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 /mcp without bearer returns 401 with WWW-Authenticate header containing resource_metadata=".../.well-known/oauth-protected-resource/mcp"
  • POST /mcp with invalid bearer returns 401 with WWW-Authenticate header
  • POST /mcp with 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) using jose with MCP_OAUTH_JWT_SECRET
    • Set iss to MCP_OAUTH_ISSUER_URL
    • Set aud to 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, and jti
    • If resource parameter was provided in the token request, include it in claims
  • verifyAccessToken(token) returning MCP AuthInfo
    • Validate iss matches MCP_OAUTH_ISSUER_URL (reject tokens from other issuers)
    • Validate aud matches MCP_OAUTH_RESOURCE_URL (RFC 8707 audience binding — reject tokens not intended for this server)
    • Enforce exp (reject expired), nbf (reject not-yet-valid), validate iat is present
    • Parse scope claim into string[] for AuthInfo.scopes
    • Populate AuthInfo.resource from claims if present
    • Return AuthInfo with token, clientId, scopes, expiresAt, resource

Step 3: Implement oauth-provider.ts

Implement OAuthServerProvider with:

  • clientsStore.getClient / clientsStore.registerClient
    • registerClient must validate token_endpoint_auth_method (accept "none" for public clients per MCP spec)
    • Validate redirect_uris: allow http://localhost:* and http://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?) — pass resource parameter through to token issuance (RFC 8707)
  • exchangeRefreshToken(client, refreshToken, scopes?, resource?) — pass resource parameter through to token reissuance
  • verifyAccessToken(...) with JWT-first, PAT fallback second
  • optional revokeToken(...)
  • skipLocalPkceValidation: set to false — 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: the OAuthServerProvider instance
    • issuerUrl: 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 suffix
    • scopesSupported: ['mcp:tools'] (or similar)
    • resourceName: 'Flowershow MCP Server'
  • protect /mcp with requireBearerAuth({ verifier: provider, resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resourceServerUrl) })
    • This ensures 401 responses include WWW-Authenticate: Bearer resource_metadata="..." per MCP spec 2025-06-18 / RFC 9728
  • create per-request FlowershowApi using req.auth token
  • remove direct extractPat hard requirement (retain helper only if used for PAT fallback tests)

Step 5: Add dependency updates

In apps/flowershow-mcp/package.json, add:

  • jose
  • cors
  • express-rate-limit
  • pkce-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)
  • authorization challenge creation payload (include resource parameter per RFC 8707)
  • authorization code exchange payload (include resource parameter per RFC 8707)
  • refresh token exchange payload (include resource parameter 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.ts for 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 401 JSON error on mismatch

Step 2: Implement each route with contract schemas

For each endpoint:

  • use .safeParse() with imported contract schemas
  • return 400 with { 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: accept http://localhost:<port> and http://127.0.0.1:<port> for desktop/CLI clients; require https:// 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:

  • McpOAuthClient
  • McpOAuthCode
  • McpOAuthToken

Include indexes/uniques for:

  • clientId unique
  • 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"

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_URL
  • MCP_OAUTH_API_BASE_URL (if separate from FLOWERSHOW_API_URL)
  • MCP_OAUTH_API_SECRET
  • MCP_OAUTH_ISSUER_URL — the issuer identifier for JWT iss claim and AS metadata
  • MCP_OAUTH_RESOURCE_URL — the canonical MCP resource URL (e.g. https://mcp.flowershow.app/mcp); used for PRM resource field, JWT aud claim, and resourceServerUrl in mcpAuthRouter
  • MCP_OAUTH_JWT_SECRET
  • MCP_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 build
  • pnpm --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 resource parameter (RFC 8707)
  • refresh token exchange with resource parameter
  • JWT audience validation: token issued for wrong resource URL is rejected
  • JWT issuer validation: token with wrong iss is rejected
  • expired token rejection
  • revoked token failure
  • PAT fallback still authorizes /mcp
  • WWW-Authenticate header on 401 contains resource_metadata URL
  • PRM endpoint returns correct authorization_servers and resource values

Step 2: Run all targeted test suites

Run:

  • pnpm --filter @flowershow/mcp test
  • pnpm --filter flowershow test
  • pnpm 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 scope
  • bd update <id> --status in_progress for 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 mcpAuthRouter does this automatically via mcpAuthMetadataRouter — do not remove or bypass it.
  • WWW-Authenticate on 401 is mandatory. The requireBearerAuth middleware handles this when resourceMetadataUrl is provided. Always pass it.
  • Resource Indicators (RFC 8707) are mandatory. Clients MUST send resource in auth/token requests. The server MUST validate audience. JWT aud must match MCP_OAUTH_RESOURCE_URL.
  • PKCE S256 is required for all clients. Do not allow code_challenge_method: "plain". The SDK's OAuthServerProvider has skipLocalPkceValidation — set it to false.
  • 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_token into 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_servers field points to the MCP server's own issuer URL. Internal delegation to apps/flowershow is invisible to clients.
Built with LogoFlowershow