mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-25 09:24:54 +10:00
feat(mcp): add OAuth 2.1 for claude.ai MCP connector (#2829)
* feat(mcp): add OAuth 2.1 authentication for claude.ai MCP connector Enable OAuth 2.1 (RFC 8414 + RFC 7591) for the MCP endpoint using better-auth's MCP plugin. This allows claude.ai and other MCP clients to authenticate via Dynamic Client Registration and Authorization Code flow with PKCE, using the existing login page. - Add `mcp()` plugin to better-auth config with login page redirect - Add `.well-known/oauth-authorization-server` discovery endpoint - Add `.well-known/oauth-protected-resource` metadata endpoint - Update MCP handler to accept Bearer tokens via `getMcpSession` - Retain `x-api-key` fallback for backward compatibility - Return proper HTTP 401 + WWW-Authenticate header for unauthed requests - Add `oauthApplication`, `oauthAccessToken`, `oauthConsent` tables Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(mcp): use typed AuthError and suppress noisy verifyApiKey throws - Replace string-matching error detection with instanceof AuthError - Wrap verifyApiKey in try-catch to avoid logging malformed key errors - Move console.error below auth check so 401s don't pollute logs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mcp): add database migration for OAuth tables Creates oauth_application, oauth_access_token, and oauth_consent tables required for MCP OAuth 2.1 Dynamic Client Registration flow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(mcp): resolve OAuth Bearer token auth for oRPC tool calls The oRPC context only checked session cookies and API keys, causing MCP tool calls from OAuth clients (claude.ai) to fail with Unauthorized even though the MCP endpoint itself authenticated successfully. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(mcp): look up user by userId from OAuth access token getMcpSession returns OAuthAccessToken (with userId), not a session object with a user property. Must query the user table by userId. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(mcp): migrate from deprecated mcp() plugin to @better-auth/oauth-provider The better-auth MCP plugin is marked for deprecation in favor of the OAuth Provider plugin. This refactors the entire OAuth 2.1 flow to use @better-auth/oauth-provider with JWT-based token verification, replacing the opaque token lookup via getMcpSession(). Key changes: - Replace mcp() with jwt() + oauthProvider() in auth config - Replace getMcpSession() with verifyAccessToken() (JWT/JWKS) - Replace oauthApplication table with oauthClient (RFC 7591 compliant) - Add oauthRefreshToken table and jwks table for JWT signing keys - Extract shared authBaseUrl and verifyOAuthToken helper - Hoist McpServer to module scope (avoid per-request reconstruction) - Update .well-known discovery endpoints for OAuth Provider Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(mcp): resolve OAuth 2.1 flow for claude.ai MCP connector Multiple fixes required to make the full MCP OAuth flow work with claude.ai's implementation: - Add RFC 8414 discovery route at /.well-known/oauth-authorization-server/api/auth (claude.ai appends the issuer path per spec) - Add /auth/oauth server route to handle login/consent flow (generates auth codes directly, bypassing h3 cookie issues) - Default token_endpoint_auth_method to "none" via onRequest plugin hook (claude.ai omits this field, causing confidential client rejection) - Strip prompt=consent from authorize requests via onRequest hook (better-auth checks prompt before skipConsent, causing redirect loops) - Add validAudiences for MCP resource URL (JWT aud claim contains the MCP URL, not the base URL) - Disable CSRF check for cross-origin OAuth flows - Log token endpoint errors for debugging - Set skipConsent on OAuth clients via /auth/oauth route Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(mcp): harden OAuth security and enforce lock on delete - Scope CSRF bypass to OAuth2 paths only instead of disabling globally - Validate redirect_uri against registered client URIs (prevents code interception) - Use pathname matching instead of fragile url.includes() for route guards - Replace biased modulo code generation with crypto.randomBytes - Enforce resume lock check on delete (previously silently ignored) - Remove debug console.error logging of OAuth token response bodies - Use Response.json() consistently for MCP 401 response Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update dependencies, refine ignore patterns, and enhance documentation - Updated various dependencies in package.json and pnpm-lock.yaml for improved stability and features. - Adjusted ignore patterns in knip.json to include specific component directories. - Enhanced documentation for the MCP server, clarifying authentication methods and configuration options. - Made minor adjustments to VSCode settings for better code organization. * fix(mcp): resolve OAuth client registration and stale token handling Claude.ai sends token_endpoint_auth_method: "client_secret_post" without a client_secret during Dynamic Client Registration, causing Better Auth to reject it as an unauthenticated confidential client. Force to "none" for unauthenticated registrations. Also catch JWKS verification errors (e.g. key rotation after redeployment) so stale Bearer tokens return 401 instead of 200 with an error body, allowing clients to re-initiate the OAuth flow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * reiterate on tests --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
This commit is contained in:
@@ -1,12 +1,71 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Cursor Cloud specific instructions
|
||||
## Overview
|
||||
|
||||
### Overview
|
||||
Reactive Resume is a single-package full-stack TypeScript app (not a monorepo) built with [TanStack Start](https://tanstack.com/start/latest/docs/framework/react/overview) (React, Vite, Nitro). It serves both frontend and API on port 3000.
|
||||
|
||||
Reactive Resume is a single-package full-stack TypeScript app (not a monorepo) built with TanStack Start (React 19, Vite, Nitro). It serves both frontend and API on port 3000.
|
||||
This project uses [Vite+](https://vite.dev/blog/announcing-viteplus), a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. All modules should be imported from the `vite-plus` dependency (e.g., `import { defineConfig } from 'vite-plus'` or `import { expect, test, vi } from 'vite-plus/test'`).
|
||||
|
||||
### Infrastructure services
|
||||
## Key Libraries
|
||||
|
||||
| Area | Library | Docs |
|
||||
| -------------------- | ------------------------------------------------------------------------ | ---------------------------------- |
|
||||
| Frontend framework | React | https://react.dev |
|
||||
| Full-stack framework | TanStack Start | https://tanstack.com/start/latest |
|
||||
| Router | TanStack React Router | https://tanstack.com/router/latest |
|
||||
| Server state | TanStack React Query | https://tanstack.com/query/latest |
|
||||
| Client state | Zustand (+ Zundo for undo/redo, Immer for immutable updates) | https://zustand.docs.pmnd.rs |
|
||||
| Type-safe API | oRPC | https://orpc.unnoq.com |
|
||||
| Database ORM | Drizzle ORM (PostgreSQL) | https://orm.drizzle.team |
|
||||
| Authentication | Better Auth (+ Drizzle adapter, OAuth provider, API keys, 2FA, Passkeys) | https://www.better-auth.com |
|
||||
| Styling | Tailwind CSS | https://tailwindcss.com |
|
||||
| UI Components | shadcn/ui (built on Base UI) | https://ui.shadcn.com |
|
||||
| Icons | Phosphor Icons | https://phosphoricons.com |
|
||||
| Forms | React Hook Form (+ Zod resolvers) | https://react-hook-form.com |
|
||||
| Rich text editor | Tiptap | https://tiptap.dev |
|
||||
| Validation | Zod | https://zod.dev |
|
||||
| AI | Vercel AI SDK (OpenAI, Anthropic, Google, Ollama providers) | https://ai-sdk.dev |
|
||||
| MCP | Model Context Protocol SDK | https://modelcontextprotocol.io |
|
||||
| i18n | Lingui | https://lingui.dev |
|
||||
| Animations | Motion (Framer Motion) | https://motion.dev |
|
||||
| PDF export | Puppeteer Core (via Browserless) | https://pptr.dev |
|
||||
| Drag and drop | dnd-kit | https://dndkit.com |
|
||||
| Server engine | Nitro | https://nitro.build |
|
||||
| PWA | Vite PWA Plugin | https://vite-pwa-org.netlify.app |
|
||||
| Unused deps | Knip | https://knip.dev |
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
components/ UI, resume, layout, animation, theme, locale components
|
||||
routes/ File-based routing (TanStack React Router)
|
||||
integrations/ Feature modules (auth, drizzle, orpc, ai, email, jobs, mcp, storage)
|
||||
schema/ Zod schemas for resume data validation
|
||||
utils/ Utility functions (locale, theme, env, resume processing)
|
||||
dialogs/ Modal/dialog components
|
||||
hooks/ Custom React hooks
|
||||
styles/ CSS and Tailwind configuration
|
||||
stores/ Zustand stores (resume, AI, dialog, command palette)
|
||||
migrations/ Drizzle database migrations
|
||||
locales/ Lingui i18n message catalogs (47+ locales)
|
||||
```
|
||||
|
||||
### Key Config Files
|
||||
|
||||
- `vite.config.ts` — Vite + Nitro + TanStack Start + PWA + Tailwind + Lingui
|
||||
- `drizzle.config.ts` — PostgreSQL dialect, schema at `./src/integrations/drizzle/schema.ts`
|
||||
- `tsconfig.json` — ES2022, strict mode, path alias `@/*` → `./src/*`
|
||||
- `lingui.config.ts` — i18n extraction and locale configuration
|
||||
- `components.json` — shadcn CLI configuration
|
||||
|
||||
### API Architecture
|
||||
|
||||
- **oRPC API** (`/api/rpc/*`) — Type-safe RPC with routers for: `ai`, `auth`, `resume`, `storage`, `printer`, `jobs`, `statistics`, `flags`. Three procedure types: `publicProcedure`, `protectedProcedure`, `serverOnlyProcedure`.
|
||||
- **Better Auth API** (`/api/auth/*`) — OAuth, session management, social provider callbacks.
|
||||
- **MCP Server** (`/mcp/`) — Model Context Protocol with OAuth Bearer tokens and API key auth. Exposes resumes as resources and tools for resume CRUD.
|
||||
|
||||
## Infrastructure Services
|
||||
|
||||
Before running the dev server, Docker must be running with at least PostgreSQL. Start services via `compose.dev.yml`:
|
||||
|
||||
@@ -18,7 +77,7 @@ sudo docker compose -f compose.dev.yml up -d postgres browserless
|
||||
- **PostgreSQL** (port 5432) — required. The app auto-runs Drizzle migrations on startup via a Nitro plugin.
|
||||
- **Browserless** (port 4000) — required for PDF export. Maps container port 3000 to host port 4000.
|
||||
|
||||
### Environment variables
|
||||
## Environment Variables
|
||||
|
||||
Copy `.env.example` to `.env` if not present. Key notes for local dev:
|
||||
|
||||
@@ -28,99 +87,47 @@ Copy `.env.example` to `.env` if not present. Key notes for local dev:
|
||||
- `DATABASE_URL` — PostgreSQL connection using `postgres:postgres` credentials on localhost:5432.
|
||||
- S3/Storage and SMTP vars can be left empty — the app falls back to local filesystem and console-logged emails.
|
||||
|
||||
### Common commands
|
||||
## Common Commands
|
||||
|
||||
See `scripts` in `package.json`. Key ones:
|
||||
`vp` is the global CLI for Vite+. Do not use pnpm/npm/yarn directly — Vite+ wraps the underlying package manager.
|
||||
|
||||
| Task | Command |
|
||||
| -------------- | --------------------------------------------------------------- |
|
||||
| Dev server | `pnpm dev` (port 3000) |
|
||||
| Lint (Oxlint) | `pnpm lint` |
|
||||
| Format (Oxfmt) | `pnpm fmt` |
|
||||
| Typecheck | `pnpm typecheck` |
|
||||
| DB migrations | `pnpm db:generate` / `pnpm db:migrate` (auto-runs on dev start) |
|
||||
| Task | Command |
|
||||
| -------------------------- | --------------------------------------------------------------- |
|
||||
| Install dependencies | `vp install` |
|
||||
| Dev server (port 3000) | `vp dev` |
|
||||
| Lint (Oxlint, type-aware) | `vp lint --type-aware` |
|
||||
| Format (Oxfmt) | `vp fmt` |
|
||||
| Check (lint + fmt + types) | `vp check` |
|
||||
| Typecheck | `pnpm typecheck` (uses tsgo) |
|
||||
| Run tests | `vp test` |
|
||||
| DB migrations | `pnpm db:generate` / `pnpm db:migrate` (auto-runs on dev start) |
|
||||
| DB studio | `pnpm db:studio` |
|
||||
| i18n extraction | `pnpm lingui:extract` |
|
||||
| Add a dependency | `vp add <package>` |
|
||||
| Remove a dependency | `vp remove <package>` |
|
||||
| One-off binary | `vp dlx <package>` |
|
||||
| Build for production | `vp build` |
|
||||
| Preview production build | `vp preview` |
|
||||
| Start production server | `pnpm start` |
|
||||
|
||||
### Gotchas
|
||||
## Vite+ Pitfalls
|
||||
|
||||
- **Do not use pnpm/npm/yarn directly** for package operations — use `vp add`, `vp remove`, `vp install`, etc.
|
||||
- **Do not run `vp vitest` or `vp oxlint`** — they don't exist. Use `vp test` and `vp lint`.
|
||||
- **Do not install Vitest, Oxlint, Oxfmt, or tsdown directly** — Vite+ bundles them.
|
||||
- **Import from `vite-plus`**, not from `vite` or `vitest` directly (e.g., `import { defineConfig } from 'vite-plus'`).
|
||||
- **Vite+ commands take precedence** over `package.json` scripts. If there's a naming conflict, use `vp run <script>`.
|
||||
- **Use `vp dlx`** instead of `npx` or `pnpm dlx`.
|
||||
- **Type-aware linting** works out of the box with `vp lint --type-aware` — no need to install `oxlint-tsgolint`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The Docker daemon needs `fuse-overlayfs` storage driver and `iptables-legacy` in the cloud VM (nested container environment).
|
||||
- `pnpm.onlyBuiltDependencies` in `package.json` controls which packages are allowed to run install scripts — no interactive `pnpm approve-builds` needed.
|
||||
- Email verification is optional in dev — after signup, click "Continue" to skip.
|
||||
- Vite 8 is beta (`^8.0.0-beta.15`); Nitro uses a nightly build. Occasional upstream issues may occur.
|
||||
|
||||
<!--VITE PLUS START-->
|
||||
|
||||
# Using Vite+, the Unified Toolchain for the Web
|
||||
|
||||
This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, but it invokes Vite through `vp dev` and `vp build`.
|
||||
|
||||
## Vite+ Workflow
|
||||
|
||||
`vp` is a global binary that handles the full development lifecycle. Run `vp help` to print a list of commands and `vp <command> --help` for information about a specific command.
|
||||
|
||||
### Start
|
||||
|
||||
- create - Create a new project from a template
|
||||
- migrate - Migrate an existing project to Vite+
|
||||
- config - Configure hooks and agent integration
|
||||
- staged - Run linters on staged files
|
||||
- install (`i`) - Install dependencies
|
||||
- env - Manage Node.js versions
|
||||
|
||||
### Develop
|
||||
|
||||
- dev - Run the development server
|
||||
- check - Run format, lint, and TypeScript type checks
|
||||
- lint - Lint code
|
||||
- fmt - Format code
|
||||
- test - Run tests
|
||||
|
||||
### Execute
|
||||
|
||||
- run - Run monorepo tasks
|
||||
- exec - Execute a command from local `node_modules/.bin`
|
||||
- dlx - Execute a package binary without installing it as a dependency
|
||||
- cache - Manage the task cache
|
||||
|
||||
### Build
|
||||
|
||||
- build - Build for production
|
||||
- pack - Build libraries
|
||||
- preview - Preview production build
|
||||
|
||||
### Manage Dependencies
|
||||
|
||||
Vite+ automatically detects and wraps the underlying package manager such as pnpm, npm, or Yarn through the `packageManager` field in `package.json` or package manager-specific lockfiles.
|
||||
|
||||
- add - Add packages to dependencies
|
||||
- remove (`rm`, `un`, `uninstall`) - Remove packages from dependencies
|
||||
- update (`up`) - Update packages to latest versions
|
||||
- dedupe - Deduplicate dependencies
|
||||
- outdated - Check for outdated packages
|
||||
- list (`ls`) - List installed packages
|
||||
- why (`explain`) - Show why a package is installed
|
||||
- info (`view`, `show`) - View package information from the registry
|
||||
- link (`ln`) / unlink - Manage local package links
|
||||
- pm - Forward a command to the package manager
|
||||
|
||||
### Maintain
|
||||
|
||||
- upgrade - Update `vp` itself to the latest version
|
||||
|
||||
These commands map to their corresponding tools. For example, `vp dev --port 3000` runs Vite's dev server and works the same as Vite. `vp test` runs JavaScript tests through the bundled Vitest. The version of all tools can be checked using `vp --version`. This is useful when researching documentation, features, and bugs.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Using the package manager directly:** Do not use pnpm, npm, or Yarn directly. Vite+ can handle all package manager operations.
|
||||
- **Always use Vite commands to run tools:** Don't attempt to run `vp vitest` or `vp oxlint`. They do not exist. Use `vp test` and `vp lint` instead.
|
||||
- **Running scripts:** Vite+ commands take precedence over `package.json` scripts. If there is a `test` script defined in `scripts` that conflicts with the built-in `vp test` command, run it using `vp run test`.
|
||||
- **Do not install Vitest, Oxlint, Oxfmt, or tsdown directly:** Vite+ wraps these tools. They must not be installed directly. You cannot upgrade these tools by installing their latest versions. Always use Vite+ commands.
|
||||
- **Use Vite+ wrappers for one-off binaries:** Use `vp dlx` instead of package-manager-specific `dlx`/`npx` commands.
|
||||
- **Import JavaScript modules from `vite-plus`:** Instead of importing from `vite` or `vitest`, all modules should be imported from the project's `vite-plus` dependency. For example, `import { defineConfig } from 'vite-plus';` or `import { expect, test, vi } from 'vite-plus/test';`. You must not install `vitest` to import test utilities.
|
||||
- **Type-Aware Linting:** There is no need to install `oxlint-tsgolint`, `vp lint --type-aware` works out of the box.
|
||||
- Vite and Nitro use beta/nightly builds. Occasional upstream issues may occur.
|
||||
|
||||
## Review Checklist for Agents
|
||||
|
||||
- [ ] Run `vp install` after pulling remote changes and before getting started.
|
||||
- [ ] Run `vp check` and `vp test` to validate changes.
|
||||
|
||||
<!--VITE PLUS END-->
|
||||
|
||||
Reference in New Issue
Block a user