From 0ff7bc96b28fd51333284041216c2a6f0c60bdfc Mon Sep 17 00:00:00 2001 From: Ben Senescu <44480372+bensenescu@users.noreply.github.com> Date: Thu, 7 May 2026 23:38:00 -0400 Subject: [PATCH] Add stateless MCP server (#162) --- .github/workflows/ci.yml | 2 - .github/workflows/sourcemaps.yml | 2 - Dockerfile.selfhost | 2 +- package.json | 5 +- pnpm-lock.yaml | 1271 ++++++++++++++++- src/lib/auth-config.ts | 54 +- src/lib/auth.ts | 3 + src/lib/oauth-provider-resource-client.ts | 14 + src/lib/oauth-resource.ts | 16 + src/routeTree.gen.ts | 67 +- .../oauth-authorization-server/api/auth.ts | 31 + .../oauth-protected-resource/mcp.ts | 17 +- .../[.]well-known/openid-configuration.ts | 29 + src/routes/_authenticated.oauth-consent.tsx | 128 +- src/routes/api/auth/$.test.ts | 83 ++ src/routes/api/auth/$.ts | 45 +- src/server.ts | 14 +- src/server/mcp/context.ts | 26 + src/server/mcp/handler.test.ts | 252 ++++ src/server/mcp/handler.ts | 150 ++ src/server/mcp/server.ts | 63 + src/serverFunctions/oauth.ts | 33 + 22 files changed, 2240 insertions(+), 67 deletions(-) create mode 100644 src/lib/oauth-provider-resource-client.ts create mode 100644 src/routes/[.]well-known/oauth-authorization-server/api/auth.ts create mode 100644 src/routes/[.]well-known/openid-configuration.ts create mode 100644 src/routes/api/auth/$.test.ts create mode 100644 src/server/mcp/context.ts create mode 100644 src/server/mcp/handler.test.ts create mode 100644 src/server/mcp/handler.ts create mode 100644 src/server/mcp/server.ts create mode 100644 src/serverFunctions/oauth.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05fb6a7..f25c4ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: 9 - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/sourcemaps.yml b/.github/workflows/sourcemaps.yml index 6e0aa3e..b79dd57 100644 --- a/.github/workflows/sourcemaps.yml +++ b/.github/workflows/sourcemaps.yml @@ -20,8 +20,6 @@ jobs: uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: 9 - name: Setup Node.js uses: actions/setup-node@v4 with: diff --git a/Dockerfile.selfhost b/Dockerfile.selfhost index 532fde4..bb0b6e7 100644 --- a/Dockerfile.selfhost +++ b/Dockerfile.selfhost @@ -6,7 +6,7 @@ ENV PATH=$PNPM_HOME:$PATH WORKDIR /app -RUN corepack enable +RUN corepack enable && corepack prepare pnpm@10.30.1 --activate COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile diff --git a/package.json b/package.json index 9918d1f..b0ed436 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "sideEffects": false, "version": "0.0.10", "type": "module", + "packageManager": "pnpm@10.30.1", "scripts": { - "dev": "AUTH_MODE=local_noauth vite dev", + "dev": "vite dev", "dev:agents": "mkdir -p .logs && portless run vite dev 2>&1 | tee .logs/dev-server.log", "dev:agents:force": "mkdir -p .logs && portless --force run vite dev 2>&1 | tee .logs/dev-server.log", "build": "vite build && tsc --noEmit", @@ -51,6 +52,7 @@ "dependencies": { "@better-auth/oauth-provider": "^1.5.5", "@every-app/sdk": "^0.1.14", + "@modelcontextprotocol/sdk": "1.29.0", "@tanstack/query-core": "^5.90.9", "@tanstack/react-form": "^1.25.0", "@tanstack/react-query": "^5.90.9", @@ -58,6 +60,7 @@ "@tanstack/react-router-devtools": "^1.166.11", "@tanstack/react-start": "^1.167.16", "@tanstack/react-table": "^8.21.3", + "agents": "0.12.3", "autumn-js": "^1.1.7", "better-auth": "^1.5.5", "cheerio": "^1.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50e8c38..6b86017 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,10 +10,13 @@ importers: dependencies: '@better-auth/oauth-provider': specifier: ^1.5.5 - version: 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6)) + version: 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.2.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6)) '@every-app/sdk': specifier: ^0.1.14 version: 0.1.14(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(jose@6.1.3)(react@19.2.4) + '@modelcontextprotocol/sdk': + specifier: 1.29.0 + version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) '@tanstack/query-core': specifier: ^5.90.9 version: 5.90.20 @@ -35,12 +38,15 @@ importers: '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + agents: + specifier: 0.12.3 + version: 0.12.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(@cloudflare/workers-types@4.20260302.0)(ai@6.0.176(zod@4.3.6))(react@19.2.4)(rolldown@1.0.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))(zod@4.3.6) autumn-js: specifier: ^1.1.7 - version: 1.1.7(better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6))(react@19.2.4) + version: 1.1.7(better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.2.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(react@19.2.4) better-auth: specifier: ^1.5.5 - version: 1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.2.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) cheerio: specifier: ^1.2.0 version: 1.2.0 @@ -85,7 +91,7 @@ importers: version: 10.1.0(@types/react@19.2.14)(react@19.2.4) recharts: specifier: ^3.7.0 - version: 3.7.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@19.2.4)(react@19.2.4)(redux@5.0.1) + version: 3.7.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@19.2.6)(react@19.2.4)(redux@5.0.1) remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -180,6 +186,22 @@ importers: packages: + '@ai-sdk/gateway@3.0.111': + resolution: {integrity: sha512-gzdRuEH9Mqeuu8zG6j4of3EH3fFJUI0UIubyeaA8gep6KzhCJF7uaTfagSE7x2vLAf381g/NrxsXhhH7Hon9iA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.27': + resolution: {integrity: sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@3.0.10': + resolution: {integrity: sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==} + engines: {node: '>=18'} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -200,14 +222,28 @@ packages: resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.29.3': + resolution: {integrity: sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.28.6': resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} @@ -218,10 +254,24 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.28.6': resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -248,6 +298,18 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-proposal-decorators@7.29.0': + resolution: {integrity: sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.28.6': + resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.28.6': resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} @@ -272,6 +334,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime-corejs3@7.29.2': + resolution: {integrity: sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -361,6 +431,9 @@ packages: '@better-fetch/fetch@1.1.21': resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + '@cloudflare/kv-asset-handler@0.4.2': resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} @@ -420,15 +493,24 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} deprecated: 'Merged into tsx: https://tsx.is' @@ -889,6 +971,12 @@ packages: jose: ^6.0.0 react: ^18.0.0 || ^19.0.0 + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} @@ -1122,12 +1210,28 @@ packages: cpu: [x64] os: [win32] - '@mongodb-js/saslprep@1.4.6': - resolution: {integrity: sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@mongodb-js/saslprep@1.4.11': + resolution: {integrity: sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA==} '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} @@ -1171,6 +1275,10 @@ packages: resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} @@ -1239,6 +1347,9 @@ packages: resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} engines: {node: '>=14'} + '@oxc-project/types@0.129.0': + resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} + '@oxc-resolver/binding-android-arm-eabi@11.18.0': resolution: {integrity: sha512-EhwJNzbfLwQQIeyak3n08EB3UHknMnjy1dFyL98r3xlorje2uzHOT2vkB5nB1zqtTtzT31uSot3oGZFfODbGUg==} cpu: [arm] @@ -1555,6 +1666,121 @@ packages: react-redux: optional: true + '@rolldown/binding-android-arm64@1.0.0': + resolution: {integrity: sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0': + resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0': + resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0': + resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0': + resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0': + resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0': + resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0': + resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0': + resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0': + resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0': + resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0': + resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0': + resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0': + resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/plugin-babel@0.2.3': + resolution: {integrity: sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==} + engines: {node: '>=22.12.0 || ^24.0.0'} + peerDependencies: + '@babel/core': ^7.29.0 || ^8.0.0-rc.1 + '@babel/plugin-transform-runtime': ^7.29.0 || ^8.0.0-rc.1 + '@babel/runtime': ^7.27.0 || ^8.0.0-rc.1 + rolldown: ^1.0.0-rc.5 + vite: ^8.0.0 + peerDependenciesMeta: + '@babel/plugin-transform-runtime': + optional: true + '@babel/runtime': + optional: true + vite: + optional: true + + '@rolldown/pluginutils@1.0.0': + resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -2165,6 +2391,10 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2204,6 +2434,10 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -2213,6 +2447,58 @@ packages: resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} engines: {node: '>= 8.0.0'} + agents@0.12.3: + resolution: {integrity: sha512-DZn90m9TEhaVjAb9UAsc5BeBlZRaC0eet18Hz1uMXr+YZdsRrL/RkSSyzf3v3xkBmnBYCCVw7+3/i4bVsYCVRQ==} + hasBin: true + peerDependencies: + '@cloudflare/ai-chat': '>=0.6.1 <1.0.0' + '@cloudflare/codemode': '>=0.3.4 <1.0.0' + '@tanstack/ai': '>=0.10.2 <1.0.0' + '@x402/core': ^2.0.0 + '@x402/evm': ^2.0.0 + ai: ^6.0.0 + react: ^19.0.0 + vite: '>=6.0.0 <9.0.0' + zod: ^4.0.0 + peerDependenciesMeta: + '@cloudflare/ai-chat': + optional: true + '@cloudflare/codemode': + optional: true + '@tanstack/ai': + optional: true + '@x402/core': + optional: true + '@x402/evm': + optional: true + vite: + optional: true + + ai@6.0.176: + resolution: {integrity: sha512-dhxDef3VCIxaFr6tKyG0BrkkCelmnporlen8nHajIwCk7S4PvIaSVI/iyJenhFOZ9KBoKjCAoUs6TzZ3yrSjxw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + ansis@4.2.0: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} @@ -2346,6 +2632,10 @@ packages: blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2365,6 +2655,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -2373,6 +2667,10 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + caniuse-lite@1.0.30001774: resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} @@ -2414,6 +2712,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + cloudflare@5.2.0: resolution: {integrity: sha512-dVzqDpPFYR9ApEC9e+JJshFJZXcw4HzM8W+3DHzO5oy9+8rLC53G7x6fEf9A7/gSuSCxuvndzui5qJKftfIM9A==} @@ -2428,19 +2730,46 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie-es@2.0.1: resolution: {integrity: sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + core-js-pure@3.49.0: + resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} + core-js@3.49.0: resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cron-schedule@6.0.0: + resolution: {integrity: sha512-BoZaseYGXOo5j5HUwTaegIog3JJbuH4BbrY9A1ArLjXpy+RWb3mV28F/9Gv1dDA7E2L8kngWva4NWisnLTyfgQ==} + engines: {node: '>=20'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2542,6 +2871,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2677,9 +3010,19 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.302: resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + encoding-sniffer@0.2.1: resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} @@ -2748,6 +3091,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -2763,6 +3109,13 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-polyfill@0.0.4: + resolution: {integrity: sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -2770,20 +3123,44 @@ packages: eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + eventsource-parser@3.0.8: + resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express-rate-limit@8.5.1: + resolution: {integrity: sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-xml-builder@1.0.0: resolution: {integrity: sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==} @@ -2821,6 +3198,10 @@ packages: resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==} engines: {node: '>=14.16'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + form-data-encoder@1.7.2: resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} @@ -2841,6 +3222,14 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2853,6 +3242,14 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2911,12 +3308,20 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hono@4.12.18: + resolution: {integrity: sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==} + engines: {node: '>=16.9.0'} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} @@ -2924,12 +3329,19 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + immer@10.2.0: resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} immer@11.1.4: resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -2937,6 +3349,14 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -2969,6 +3389,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + isbot@5.1.37: resolution: {integrity: sha512-5bcicX81xf6NlTEV8rWdg7Pk01LFizDetuYGHx6d/f6y3lR2/oo8IfxjzJqn1UdDEyCcwT9e7NRloj8DwCYujQ==} engines: {node: '>=18'} @@ -3001,6 +3424,15 @@ packages: engines: {node: '>=6'} hasBin: true + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -3179,9 +3611,17 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -3278,10 +3718,21 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimetext@3.0.28: + resolution: {integrity: sha512-eQXpbNrtxLCjUtiVbR/qR09dbPgZ2o+KR1uA7QKqGhbn8QV7HIL16mXXsobBL4/8TqoYh1us31kfz+dNfCev9g==} + miniflare@4.20260219.0: resolution: {integrity: sha512-EIb5wXbWUnnC60XU2aiFOPNd4fgTXzECkwRSOXZ1vdcY9WZaEE9rVf+h+Apw+WkOHRkp3Dr9/ZhQ5y1R+9iZ4Q==} engines: {node: '>=18.0.0'} @@ -3294,8 +3745,8 @@ packages: resolution: {integrity: sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==} engines: {node: '>=20.19.0'} - mongodb@7.1.0: - resolution: {integrity: sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==} + mongodb@7.2.0: + resolution: {integrity: sha512-F/2+BMZtLVhY30ioZp0dAmZ+IRZMBqI+nrv6t5+9/1AIwCa8sMRC3jBf81lpxMhnZgqq8CoUD503Z1oZWq1/sw==} engines: {node: '>=20.19.0'} peerDependencies: '@aws-sdk/credential-providers': ^3.806.0 @@ -3329,10 +3780,19 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.1.11: + resolution: {integrity: sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==} + engines: {node: ^18 || >=20} + hasBin: true + nanostores@1.1.1: resolution: {integrity: sha512-EYJqS25r2iBeTtGQCHidXl1VfZ1jXM7Q04zXJOrMlxVVmD0ptxJaNux92n1mJ7c5lN3zTq12MhH/8x59nP+qmg==} engines: {node: ^20.0.0 || >=22.0.0} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -3361,6 +3821,21 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + oxc-resolver@11.18.0: resolution: {integrity: sha512-Fv/b05AfhpYoCDvsog6tgsDm2yIwIeJafpMFLncNwKHRYu+Y1xQu5Q/rgUn7xBfuhNgjtPO7C0jCf7p2fLDj1g==} @@ -3393,6 +3868,23 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + partyserver@0.5.5: + resolution: {integrity: sha512-7zub8oV8Od9dY2aXGrgzhX5GLceaWOg7xB5VWXtDcqt2BWVDIOCAgaF0AmBMSu3AXhJHsFdzPnA8SSZdybXMbQ==} + peerDependencies: + '@cloudflare/workers-types': ^4.20260424.1 + + partysocket@1.1.18: + resolution: {integrity: sha512-SyuvH9VavWOSa14v6dYdp3yfSUDII4BQB1+TkGOFBkjfZKjnDBiba4fhdhwBlqGBkqw4ea3gTA1DYhSffX24Wg==} + peerDependencies: + react: '>=17' + peerDependenciesMeta: + react: + optional: true + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3400,6 +3892,9 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -3426,6 +3921,10 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + portless@0.5.2: resolution: {integrity: sha512-LnJvnFUduG4QSIDqc4og9WCLRA6L0+btA96nn6icY6cxyEAR44cnWaIxVzmw7y74KQ+R7zJM1HpenankphympA==} engines: {node: '>=20'} @@ -3466,10 +3965,18 @@ packages: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + query-selector-shadow-dom@1.0.1: resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} @@ -3480,13 +3987,21 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: react: ^19.2.4 - react-is@19.2.4: - resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} + react-is@19.2.6: + resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} @@ -3553,6 +4068,10 @@ packages: remeda@2.33.6: resolution: {integrity: sha512-tazDGH7s75kUPGBKLvhgBEHMgW+TdDFhjUAMdQj57IoWz6HsGa5D2RX5yDUz6IIqiRRvZiaEHzCzWdTeixc/Kg==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -3567,6 +4086,11 @@ packages: resolution: {integrity: sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==} engines: {node: '>=10.0.0'} + rolldown@1.0.0: + resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3581,6 +4105,10 @@ packages: rou3@0.8.1: resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3599,6 +4127,10 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + seroval-plugins@1.5.2: resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==} engines: {node: '>=10'} @@ -3609,9 +4141,16 @@ packages: resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} engines: {node: '>=10'} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-cookie-parser@3.0.1: resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3628,6 +4167,22 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3677,12 +4232,24 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-json-comments@5.0.3: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} @@ -3746,6 +4313,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -3777,6 +4348,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -3820,6 +4395,10 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -3835,6 +4414,10 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -3999,6 +4582,13 @@ packages: '@cloudflare/workers-types': optional: true + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -4039,15 +4629,32 @@ packages: resolution: {integrity: sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==} engines: {node: '>=20.0'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + youch-core@0.3.3: resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -4059,6 +4666,24 @@ packages: snapshots: + '@ai-sdk/gateway@3.0.111(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider-utils': 4.0.27(zod@4.3.6) + '@vercel/oidc': 3.2.0 + zod: 4.3.6 + + '@ai-sdk/provider-utils@4.0.27(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.10 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.8 + zod: 4.3.6 + + '@ai-sdk/provider@3.0.10': + dependencies: + json-schema: 0.4.0 + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -4101,6 +4726,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + '@babel/helper-compilation-targets@7.28.6': dependencies: '@babel/compat-data': 7.29.0 @@ -4109,8 +4738,28 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/helper-globals@7.28.0': {} + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.28.6': dependencies: '@babel/traverse': 7.29.0 @@ -4127,8 +4776,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -4148,6 +4817,20 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -4168,6 +4851,12 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime-corejs3@7.29.2': + dependencies: + core-js-pure: 3.49.0 + + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -4222,18 +4911,18 @@ snapshots: '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 - '@better-auth/mongo-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0)': + '@better-auth/mongo-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.2.0)': dependencies: '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 - mongodb: 7.1.0 + mongodb: 7.2.0 - '@better-auth/oauth-provider@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6))': + '@better-auth/oauth-provider@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.2.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6))': dependencies: '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 - better-auth: 1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + better-auth: 1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.2.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) better-call: 1.3.2(zod@4.3.6) jose: 6.1.3 zod: 4.3.6 @@ -4253,6 +4942,8 @@ snapshots: '@better-fetch/fetch@1.1.21': {} + '@cfworker/json-schema@4.1.1': {} + '@cloudflare/kv-asset-handler@0.4.2': {} '@cloudflare/unenv-preset@2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260219.0)': @@ -4297,12 +4988,23 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 @@ -4313,6 +5015,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild-kit/core-utils@3.3.2': dependencies: esbuild: 0.18.20 @@ -4553,6 +5260,10 @@ snapshots: jsonc-parser: 3.3.1 react: 19.2.4 + '@hono/node-server@1.19.14(hono@4.12.18)': + dependencies: + hono: 4.12.18 + '@img/colour@1.0.0': {} '@img/sharp-darwin-arm64@0.34.5': @@ -4735,7 +5446,31 @@ snapshots: '@libsql/win32-x64-msvc@0.5.22': optional: true - '@mongodb-js/saslprep@1.4.6': + '@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.18) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.8 + express: 5.2.1 + express-rate-limit: 8.5.1(express@5.2.1) + hono: 4.12.18 + jose: 6.1.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.2(zod@4.3.6) + optionalDependencies: + '@cfworker/json-schema': 4.1.1 + transitivePeerDependencies: + - supports-color + + '@mongodb-js/saslprep@1.4.11': dependencies: sparse-bitfield: 3.0.3 @@ -4746,6 +5481,13 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.1 + optional: true + '@neon-rs/load@0.0.4': {} '@noble/ciphers@2.1.1': {} @@ -4785,6 +5527,8 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.1 + '@opentelemetry/api@1.9.0': {} + '@opentelemetry/api@1.9.1': {} '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.1)': @@ -4857,6 +5601,8 @@ snapshots: '@opentelemetry/semantic-conventions@1.40.0': {} + '@oxc-project/types@0.129.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.18.0': optional: true @@ -5047,6 +5793,66 @@ snapshots: react: 19.2.4 react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1) + '@rolldown/binding-android-arm64@1.0.0': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0': + optional: true + + '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(rolldown@1.0.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + dependencies: + '@babel/core': 7.29.0 + picomatch: 4.0.4 + rolldown: 1.0.0 + optionalDependencies: + '@babel/runtime': 7.29.2 + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + + '@rolldown/pluginutils@1.0.0': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rolldown/pluginutils@1.0.0-beta.40': {} @@ -5664,6 +6470,8 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@vercel/oidc@3.2.0': {} + '@vitejs/plugin-react@4.7.0(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': dependencies: '@babel/core': 7.29.0 @@ -5722,12 +6530,65 @@ snapshots: dependencies: event-target-shim: 5.0.1 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn@8.16.0: {} agentkeepalive@4.6.0: dependencies: humanize-ms: 1.2.1 + agents@0.12.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(@cloudflare/workers-types@4.20260302.0)(ai@6.0.176(zod@4.3.6))(react@19.2.4)(rolldown@1.0.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))(zod@4.3.6): + dependencies: + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) + '@cfworker/json-schema': 4.1.1 + '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(rolldown@1.0.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + ai: 6.0.176(zod@4.3.6) + cron-schedule: 6.0.0 + mimetext: 3.0.28 + nanoid: 5.1.11 + partyserver: 0.5.5(@cloudflare/workers-types@4.20260302.0) + partysocket: 1.1.18(react@19.2.4) + react: 19.2.4 + yargs: 18.0.0 + zod: 4.3.6 + optionalDependencies: + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + transitivePeerDependencies: + - '@babel/core' + - '@babel/plugin-transform-runtime' + - '@babel/runtime' + - '@cloudflare/workers-types' + - rolldown + - supports-color + + ai@6.0.176(zod@4.3.6): + dependencies: + '@ai-sdk/gateway': 3.0.111(zod@4.3.6) + '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider-utils': 4.0.27(zod@4.3.6) + '@opentelemetry/api': 1.9.0 + zod: 4.3.6 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + ansis@4.2.0: {} anymatch@3.1.3: @@ -5745,14 +6606,16 @@ snapshots: asynckit@0.4.0: {} - autumn-js@1.1.7(better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6))(react@19.2.4): + autumn-js@1.1.7(better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.2.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(better-call@1.3.2(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(react@19.2.4): dependencies: query-string: 9.3.1 rou3: 0.6.3 zod: 4.3.6 optionalDependencies: - better-auth: 1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + better-auth: 1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.2.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) better-call: 1.3.2(zod@4.3.6) + express: 5.2.1 + hono: 4.12.18 react: 19.2.4 babel-dead-code-elimination@1.0.12: @@ -5768,13 +6631,13 @@ snapshots: baseline-browser-mapping@2.10.0: {} - better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): + better-auth@1.5.5(@cloudflare/workers-types@4.20260302.0)(@tanstack/react-start@1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12))(mongodb@7.2.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): dependencies: '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1) '@better-auth/drizzle-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12)) '@better-auth/kysely-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.12) '@better-auth/memory-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1) - '@better-auth/mongo-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0) + '@better-auth/mongo-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.2.0) '@better-auth/prisma-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1))(@better-auth/utils@0.3.1) '@better-auth/telemetry': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260302.0)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.1.1)) '@better-auth/utils': 0.3.1 @@ -5791,7 +6654,7 @@ snapshots: '@tanstack/react-start': 1.167.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) drizzle-kit: 0.31.9 drizzle-orm: 0.44.7(@cloudflare/workers-types@4.20260302.0)(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(kysely@0.28.12) - mongodb: 7.1.0 + mongodb: 7.2.0 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) solid-js: 1.9.11 @@ -5812,6 +6675,20 @@ snapshots: blake3-wasm@2.1.5: {} + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} braces@3.0.3: @@ -5830,6 +6707,8 @@ snapshots: buffer-from@1.1.2: {} + bytes@3.1.2: {} + cac@6.7.14: {} call-bind-apply-helpers@1.0.2: @@ -5837,6 +6716,11 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + caniuse-lite@1.0.30001774: {} ccount@2.0.1: {} @@ -5896,6 +6780,12 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + cloudflare@5.2.0: dependencies: '@types/node': 18.19.130 @@ -5916,14 +6806,31 @@ snapshots: comma-separated-tokens@2.0.3: {} + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + convert-source-map@2.0.0: {} cookie-es@2.0.1: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + cookie@1.1.1: {} + core-js-pure@3.49.0: {} + core-js@3.49.0: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cron-schedule@6.0.0: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -6006,6 +6913,8 @@ snapshots: delayed-stream@1.0.0: {} + depd@2.0.0: {} + dequal@2.0.3: {} detect-libc@2.0.2: {} @@ -6062,8 +6971,14 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + ee-first@1.1.1: {} + electron-to-chromium@1.5.302: {} + emoji-regex@10.6.0: {} + + encodeurl@2.0.0: {} + encoding-sniffer@0.2.1: dependencies: iconv-lite: 0.6.3 @@ -6193,6 +7108,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@5.0.0: {} esprima@4.0.1: {} @@ -6203,16 +7120,66 @@ snapshots: dependencies: '@types/estree': 1.0.8 + etag@1.8.1: {} + + event-target-polyfill@0.0.4: {} + event-target-shim@5.0.1: {} eventemitter3@5.0.4: {} + eventsource-parser@3.0.8: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.8 + expect-type@1.3.0: {} + express-rate-limit@8.5.1(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + exsolve@1.0.8: {} extend@3.0.2: {} + fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6221,6 +7188,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-uri@3.1.2: {} + fast-xml-builder@1.0.0: {} fast-xml-parser@5.4.1: @@ -6253,6 +7222,17 @@ snapshots: filter-obj@5.1.0: {} + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + form-data-encoder@1.7.2: {} form-data@4.0.5: @@ -6276,6 +7256,10 @@ snapshots: dependencies: fetch-blob: 3.2.0 + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fsevents@2.3.3: optional: true @@ -6283,6 +7267,10 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6358,6 +7346,8 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hono@4.12.18: {} + html-url-attributes@3.0.1: {} htmlparser2@10.1.0: @@ -6367,6 +7357,14 @@ snapshots: domutils: 3.2.2 entities: 7.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + humanize-ms@1.2.1: dependencies: ms: 2.1.3 @@ -6375,14 +7373,24 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + immer@10.2.0: {} immer@11.1.4: {} + inherits@2.0.4: {} + inline-style-parser@0.2.7: {} internmap@2.0.3: {} + ip-address@10.2.0: {} + + ipaddr.js@1.9.1: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -6408,6 +7416,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-promise@4.0.0: {} + isbot@5.1.37: {} isexe@2.0.0: {} @@ -6428,6 +7438,12 @@ snapshots: jsesc@3.1.0: {} + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + json-schema@0.4.0: {} + json5@2.2.3: {} jsonc-parser@3.3.1: {} @@ -6697,8 +7713,12 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + media-typer@1.1.0: {} + memory-pager@1.5.0: {} + merge-descriptors@2.0.0: {} + merge2@1.4.1: {} micromark-core-commonmark@2.0.3: @@ -6899,10 +7919,23 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimetext@3.0.28: + dependencies: + '@babel/runtime': 7.29.2 + '@babel/runtime-corejs3': 7.29.2 + js-base64: 3.7.8 + mime-types: 2.1.35 + miniflare@4.20260219.0: dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -6922,9 +7955,9 @@ snapshots: '@types/whatwg-url': 13.0.0 whatwg-url: 14.2.0 - mongodb@7.1.0: + mongodb@7.2.0: dependencies: - '@mongodb-js/saslprep': 1.4.6 + '@mongodb-js/saslprep': 1.4.11 bson: 7.2.0 mongodb-connection-string-url: 7.0.1 @@ -6932,8 +7965,12 @@ snapshots: nanoid@3.3.11: {} + nanoid@5.1.11: {} + nanostores@1.1.1: {} + negotiator@1.0.0: {} + node-domexception@1.0.0: {} node-fetch@2.7.0: @@ -6954,6 +7991,18 @@ snapshots: dependencies: boolbase: 1.0.0 + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + oxc-resolver@11.18.0: optionalDependencies: '@oxc-resolver/binding-android-arm-eabi': 11.18.0 @@ -7034,10 +8083,25 @@ snapshots: dependencies: entities: 6.0.1 + parseurl@1.3.3: {} + + partyserver@0.5.5(@cloudflare/workers-types@4.20260302.0): + dependencies: + '@cloudflare/workers-types': 4.20260302.0 + nanoid: 5.1.11 + + partysocket@1.1.18(react@19.2.4): + dependencies: + event-target-polyfill: 0.0.4 + optionalDependencies: + react: 19.2.4 + path-key@3.1.1: {} path-to-regexp@6.3.0: {} + path-to-regexp@8.4.2: {} + pathe@2.0.3: {} pathval@2.0.1: {} @@ -7052,6 +8116,8 @@ snapshots: picomatch@4.0.4: {} + pkce-challenge@5.0.1: {} + portless@0.5.2: dependencies: chalk: 5.6.2 @@ -7105,8 +8171,17 @@ snapshots: '@types/node': 22.19.11 long: 5.3.2 + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + punycode@2.3.1: {} + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + query-selector-shadow-dom@1.0.1: {} query-string@9.3.1: @@ -7117,12 +8192,21 @@ snapshots: queue-microtask@1.2.3: {} + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 scheduler: 0.27.0 - react-is@19.2.4: {} + react-is@19.2.6: {} react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4): dependencies: @@ -7167,7 +8251,7 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 - recharts@3.7.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@19.2.4)(react@19.2.4)(redux@5.0.1): + recharts@3.7.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@19.2.6)(react@19.2.4)(redux@5.0.1): dependencies: '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4) clsx: 2.1.1 @@ -7177,7 +8261,7 @@ snapshots: immer: 10.2.0 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-is: 19.2.4 + react-is: 19.2.6 react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1) reselect: 5.1.1 tiny-invariant: 1.3.3 @@ -7229,6 +8313,8 @@ snapshots: remeda@2.33.6: {} + require-from-string@2.0.2: {} + reselect@5.1.1: {} resolve-pkg-maps@1.0.0: {} @@ -7237,6 +8323,27 @@ snapshots: robots-parser@3.0.1: {} + rolldown@1.0.0: + dependencies: + '@oxc-project/types': 0.129.0 + '@rolldown/pluginutils': 1.0.0 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0 + '@rolldown/binding-darwin-arm64': 1.0.0 + '@rolldown/binding-darwin-x64': 1.0.0 + '@rolldown/binding-freebsd-x64': 1.0.0 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0 + '@rolldown/binding-linux-arm64-gnu': 1.0.0 + '@rolldown/binding-linux-arm64-musl': 1.0.0 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0 + '@rolldown/binding-linux-s390x-gnu': 1.0.0 + '@rolldown/binding-linux-x64-gnu': 1.0.0 + '@rolldown/binding-linux-x64-musl': 1.0.0 + '@rolldown/binding-openharmony-arm64': 1.0.0 + '@rolldown/binding-wasm32-wasi': 1.0.0 + '@rolldown/binding-win32-arm64-msvc': 1.0.0 + '@rolldown/binding-win32-x64-msvc': 1.0.0 + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 @@ -7274,6 +8381,16 @@ snapshots: rou3@0.8.1: {} + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -7286,14 +8403,41 @@ snapshots: semver@7.7.4: {} + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + seroval-plugins@1.5.2(seroval@1.5.2): dependencies: seroval: 1.5.2 seroval@1.5.2: {} + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + set-cookie-parser@3.0.1: {} + setprototypeof@1.2.0: {} + sharp@0.34.5: dependencies: '@img/colour': 1.0.0 @@ -7333,6 +8477,34 @@ snapshots: shell-quote@1.8.3: {} + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} smol-toml@1.6.0: {} @@ -7371,13 +8543,25 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@3.10.0: {} + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-json-comments@5.0.3: {} strip-literal@3.1.0: @@ -7427,6 +8611,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tr46@0.0.3: {} tr46@5.1.1: @@ -7450,6 +8636,12 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typescript@5.9.3: {} ufo@1.6.3: {} @@ -7499,6 +8691,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + unpipe@1.0.0: {} + unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -7516,6 +8710,8 @@ snapshots: dependencies: react: 19.2.4 + vary@1.1.2: {} + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -7700,6 +8896,14 @@ snapshots: - bufferutil - utf-8-validate + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + ws@8.18.0: {} ws@8.19.0: {} @@ -7713,8 +8917,21 @@ snapshots: '@oozcitak/util': 10.0.0 js-yaml: 4.1.1 + y18n@5.0.8: {} + yallist@3.1.1: {} + yargs-parser@22.0.0: {} + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + youch-core@0.3.3: dependencies: '@poppinss/exception': 1.2.3 @@ -7728,6 +8945,10 @@ snapshots: cookie: 1.1.1 youch-core: 0.3.3 + zod-to-json-schema@3.25.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + zod@3.25.76: {} zod@4.3.6: {} diff --git a/src/lib/auth-config.ts b/src/lib/auth-config.ts index 357fe1b..5ee7f49 100644 --- a/src/lib/auth-config.ts +++ b/src/lib/auth-config.ts @@ -1,10 +1,29 @@ import { oauthProvider } from "@better-auth/oauth-provider"; import { jwt, organization } from "better-auth/plugins"; import { baseAuthOptions } from "@/lib/auth-options"; -import { getMcpResource, MCP_SCOPE } from "@/lib/oauth-resource"; +import { getActiveOrganizationId } from "@/lib/auth-session"; +import { + getMcpOrganizationIdClaim, + getMcpResource, + MCP_SCOPE, +} from "@/lib/oauth-resource"; + +const MCP_OAUTH_SCOPES = ["offline_access", MCP_SCOPE]; + +function assertSingleMcpAudience(audiences: string[]) { + if (audiences.length !== 1) { + throw new Error( + "MCP OAuth resource injection requires exactly one valid audience", + ); + } +} export function createBaseAuthConfig(baseUrl: string) { const mcpResource = getMcpResource(baseUrl); + const mcpOrganizationIdClaim = getMcpOrganizationIdClaim(baseUrl); + const validAudiences = [mcpResource]; + + assertSingleMcpAudience(validAudiences); return { ...baseAuthOptions, @@ -17,12 +36,41 @@ export function createBaseAuthConfig(baseUrl: string) { signup: { page: "/sign-up", }, - scopes: ["offline_access", MCP_SCOPE], + scopes: MCP_OAUTH_SCOPES, + // We publish /.well-known/oauth-authorization-server/api/auth via + // TanStack routes, so silence Better Auth's metadata reminder. + silenceWarnings: { + oauthAuthServerConfig: true, + }, allowDynamicClientRegistration: true, + clientRegistrationDefaultScopes: MCP_OAUTH_SCOPES, + clientRegistrationAllowedScopes: MCP_OAUTH_SCOPES, // TODO: drop once the MCP spec settles on a replacement for // unauthenticated DCR — better-auth has flagged this option for removal. allowUnauthenticatedClientRegistration: true, - validAudiences: [mcpResource], + // Single allowed audience — see `routes/api/auth/$.ts`, which defaults + // missing `resource` on /oauth2/token to this value. Adding a second + // audience here would make that injection unsafe (we'd no longer know + // which to pick) and require scope-conditional logic in the route. + validAudiences, + postLogin: { + page: "/oauth-consent", + shouldRedirect: () => false, + consentReferenceId: ({ session, scopes }) => { + if (!scopes.includes(MCP_SCOPE)) { + return undefined; + } + + return getActiveOrganizationId({ session }) ?? undefined; + }, + }, + customAccessTokenClaims: ({ referenceId, scopes }) => { + if (!scopes.includes(MCP_SCOPE)) { + return {}; + } + + return referenceId ? { [mcpOrganizationIdClaim]: referenceId } : {}; + }, }), ], }; diff --git a/src/lib/auth.ts b/src/lib/auth.ts index afecac5..77cda79 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -30,6 +30,9 @@ function createAuth() { const auth = betterAuth({ baseURL: baseUrl, secret: getHostedSecret(), + // Disable Better Auth's generic /token endpoint so OAuth access tokens only + // flow through /oauth2/token, where the MCP resource shim can run. + disabledPaths: ["/token"], ...baseAuthConfig, emailAndPassword: { ...baseAuthConfig.emailAndPassword, diff --git a/src/lib/oauth-provider-resource-client.ts b/src/lib/oauth-provider-resource-client.ts new file mode 100644 index 0000000..8ab19ee --- /dev/null +++ b/src/lib/oauth-provider-resource-client.ts @@ -0,0 +1,14 @@ +import { oauthProviderResourceClient } from "@better-auth/oauth-provider/resource-client"; +import { getAuth } from "@/lib/auth"; + +type ResourceClientAuth = Parameters[0]; + +export function getOAuthProviderResourceActions() { + // Better Auth documents passing the server auth instance here, but the + // resource-client package currently types the generic too narrowly for the + // concrete `betterAuth(...)` return type. + return oauthProviderResourceClient( + // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion + getAuth() as unknown as ResourceClientAuth, + ).getActions(); +} diff --git a/src/lib/oauth-resource.ts b/src/lib/oauth-resource.ts index deeea4c..9ae51c0 100644 --- a/src/lib/oauth-resource.ts +++ b/src/lib/oauth-resource.ts @@ -4,3 +4,19 @@ export const MCP_SCOPE = "mcp"; export function getMcpResource(baseUrl: string) { return new URL(MCP_RESOURCE_PATH, baseUrl).toString(); } + +export function getMcpOrganizationIdClaim(baseUrl: string) { + return new URL( + `${MCP_RESOURCE_PATH}/claims/organization-id`, + baseUrl, + ).toString(); +} + +export function getMcpProtectedResourceMetadataUrl(resource: string) { + const url = new URL(resource); + const pathname = url.pathname.endsWith("/") + ? url.pathname.slice(0, -1) + : url.pathname; + + return `${url.origin}/.well-known/oauth-protected-resource${pathname}`; +} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 169bbfd..7ac9750 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -24,6 +24,7 @@ import { Route as AuthSignInRouteImport } from './routes/_auth.sign-in' import { Route as AppSupportRouteImport } from './routes/_app/support' import { Route as AppSettingsRouteImport } from './routes/_app/settings' import { Route as AppBillingRouteImport } from './routes/_app/billing' +import { Route as DotwellKnownOpenidConfigurationRouteImport } from './routes/[.]well-known/openid-configuration' import { Route as DotwellKnownOauthAuthorizationServerRouteImport } from './routes/[.]well-known/oauth-authorization-server' import { Route as ApiAutumnSplatRouteImport } from './routes/api/autumn/$' import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$' @@ -40,6 +41,7 @@ import { Route as ProjectPProjectIdBrandLookupRouteImport } from './routes/_proj import { Route as ProjectPProjectIdBacklinksRouteImport } from './routes/_project/p/$projectId/backlinks' import { Route as ProjectPProjectIdAuditRouteImport } from './routes/_project/p/$projectId/audit' import { Route as ProjectPProjectIdAiRouteImport } from './routes/_project/p/$projectId/ai' +import { Route as DotwellKnownOauthAuthorizationServerApiAuthRouteImport } from './routes/[.]well-known/oauth-authorization-server/api/auth' import { Route as ProjectPProjectIdRankTrackingIndexRouteImport } from './routes/_project/p/$projectId/rank-tracking/index' import { Route as ProjectPProjectIdAuditIndexRouteImport } from './routes/_project/p/$projectId/audit/index' import { Route as ProjectPProjectIdRankTrackingConfigIdRouteImport } from './routes/_project/p/$projectId/rank-tracking/$configId' @@ -117,6 +119,12 @@ const AppBillingRoute = AppBillingRouteImport.update({ path: '/billing', getParentRoute: () => AppRouteRoute, } as any) +const DotwellKnownOpenidConfigurationRoute = + DotwellKnownOpenidConfigurationRouteImport.update({ + id: '/.well-known/openid-configuration', + path: '/.well-known/openid-configuration', + getParentRoute: () => rootRouteImport, + } as any) const DotwellKnownOauthAuthorizationServerRoute = DotwellKnownOauthAuthorizationServerRouteImport.update({ id: '/.well-known/oauth-authorization-server', @@ -204,6 +212,12 @@ const ProjectPProjectIdAiRoute = ProjectPProjectIdAiRouteImport.update({ path: '/ai', getParentRoute: () => ProjectPProjectIdRouteRoute, } as any) +const DotwellKnownOauthAuthorizationServerApiAuthRoute = + DotwellKnownOauthAuthorizationServerApiAuthRouteImport.update({ + id: '/api/auth', + path: '/api/auth', + getParentRoute: () => DotwellKnownOauthAuthorizationServerRoute, + } as any) const ProjectPProjectIdRankTrackingIndexRoute = ProjectPProjectIdRankTrackingIndexRouteImport.update({ id: '/', @@ -234,7 +248,8 @@ export interface FileRoutesByFullPath { '/forgot-password': typeof ForgotPasswordRoute '/reset-password': typeof ResetPasswordRoute '/verify-email': typeof VerifyEmailRoute - '/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRoute + '/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren + '/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute '/billing': typeof AppBillingRoute '/settings': typeof AppSettingsRoute '/support': typeof AppSupportRoute @@ -247,6 +262,7 @@ export interface FileRoutesByFullPath { '/help/dataforseo-api-key': typeof AppHelpDataforseoApiKeyRoute '/api/auth/$': typeof ApiAuthSplatRoute '/api/autumn/$': typeof ApiAutumnSplatRoute + '/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute '/p/$projectId/ai': typeof ProjectPProjectIdAiRoute '/p/$projectId/audit': typeof ProjectPProjectIdAuditRouteWithChildren '/p/$projectId/backlinks': typeof ProjectPProjectIdBacklinksRoute @@ -267,7 +283,8 @@ export interface FileRoutesByTo { '/forgot-password': typeof ForgotPasswordRoute '/reset-password': typeof ResetPasswordRoute '/verify-email': typeof VerifyEmailRoute - '/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRoute + '/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren + '/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute '/billing': typeof AppBillingRoute '/settings': typeof AppSettingsRoute '/support': typeof AppSupportRoute @@ -279,6 +296,7 @@ export interface FileRoutesByTo { '/help/dataforseo-api-key': typeof AppHelpDataforseoApiKeyRoute '/api/auth/$': typeof ApiAuthSplatRoute '/api/autumn/$': typeof ApiAutumnSplatRoute + '/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute '/p/$projectId/ai': typeof ProjectPProjectIdAiRoute '/p/$projectId/backlinks': typeof ProjectPProjectIdBacklinksRoute '/p/$projectId/brand-lookup': typeof ProjectPProjectIdBrandLookupRoute @@ -301,7 +319,8 @@ export interface FileRoutesById { '/forgot-password': typeof ForgotPasswordRoute '/reset-password': typeof ResetPasswordRoute '/verify-email': typeof VerifyEmailRoute - '/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRoute + '/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren + '/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute '/_app/billing': typeof AppBillingRoute '/_app/settings': typeof AppSettingsRoute '/_app/support': typeof AppSupportRoute @@ -315,6 +334,7 @@ export interface FileRoutesById { '/_app/help/dataforseo-api-key': typeof AppHelpDataforseoApiKeyRoute '/api/auth/$': typeof ApiAuthSplatRoute '/api/autumn/$': typeof ApiAutumnSplatRoute + '/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute '/_project/p/$projectId/ai': typeof ProjectPProjectIdAiRoute '/_project/p/$projectId/audit': typeof ProjectPProjectIdAuditRouteWithChildren '/_project/p/$projectId/backlinks': typeof ProjectPProjectIdBacklinksRoute @@ -338,6 +358,7 @@ export interface FileRouteTypes { | '/reset-password' | '/verify-email' | '/.well-known/oauth-authorization-server' + | '/.well-known/openid-configuration' | '/billing' | '/settings' | '/support' @@ -350,6 +371,7 @@ export interface FileRouteTypes { | '/help/dataforseo-api-key' | '/api/auth/$' | '/api/autumn/$' + | '/.well-known/oauth-authorization-server/api/auth' | '/p/$projectId/ai' | '/p/$projectId/audit' | '/p/$projectId/backlinks' @@ -371,6 +393,7 @@ export interface FileRouteTypes { | '/reset-password' | '/verify-email' | '/.well-known/oauth-authorization-server' + | '/.well-known/openid-configuration' | '/billing' | '/settings' | '/support' @@ -382,6 +405,7 @@ export interface FileRouteTypes { | '/help/dataforseo-api-key' | '/api/auth/$' | '/api/autumn/$' + | '/.well-known/oauth-authorization-server/api/auth' | '/p/$projectId/ai' | '/p/$projectId/backlinks' | '/p/$projectId/brand-lookup' @@ -404,6 +428,7 @@ export interface FileRouteTypes { | '/reset-password' | '/verify-email' | '/.well-known/oauth-authorization-server' + | '/.well-known/openid-configuration' | '/_app/billing' | '/_app/settings' | '/_app/support' @@ -417,6 +442,7 @@ export interface FileRouteTypes { | '/_app/help/dataforseo-api-key' | '/api/auth/$' | '/api/autumn/$' + | '/.well-known/oauth-authorization-server/api/auth' | '/_project/p/$projectId/ai' | '/_project/p/$projectId/audit' | '/_project/p/$projectId/backlinks' @@ -441,7 +467,8 @@ export interface RootRouteChildren { ForgotPasswordRoute: typeof ForgotPasswordRoute ResetPasswordRoute: typeof ResetPasswordRoute VerifyEmailRoute: typeof VerifyEmailRoute - DotwellKnownOauthAuthorizationServerRoute: typeof DotwellKnownOauthAuthorizationServerRoute + DotwellKnownOauthAuthorizationServerRoute: typeof DotwellKnownOauthAuthorizationServerRouteWithChildren + DotwellKnownOpenidConfigurationRoute: typeof DotwellKnownOpenidConfigurationRoute DotwellKnownOauthProtectedResourceMcpRoute: typeof DotwellKnownOauthProtectedResourceMcpRoute ApiAuthSplatRoute: typeof ApiAuthSplatRoute ApiAutumnSplatRoute: typeof ApiAutumnSplatRoute @@ -554,6 +581,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppBillingRouteImport parentRoute: typeof AppRouteRoute } + '/.well-known/openid-configuration': { + id: '/.well-known/openid-configuration' + path: '/.well-known/openid-configuration' + fullPath: '/.well-known/openid-configuration' + preLoaderRoute: typeof DotwellKnownOpenidConfigurationRouteImport + parentRoute: typeof rootRouteImport + } '/.well-known/oauth-authorization-server': { id: '/.well-known/oauth-authorization-server' path: '/.well-known/oauth-authorization-server' @@ -666,6 +700,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectPProjectIdAiRouteImport parentRoute: typeof ProjectPProjectIdRouteRoute } + '/.well-known/oauth-authorization-server/api/auth': { + id: '/.well-known/oauth-authorization-server/api/auth' + path: '/api/auth' + fullPath: '/.well-known/oauth-authorization-server/api/auth' + preLoaderRoute: typeof DotwellKnownOauthAuthorizationServerApiAuthRouteImport + parentRoute: typeof DotwellKnownOauthAuthorizationServerRoute + } '/_project/p/$projectId/rank-tracking/': { id: '/_project/p/$projectId/rank-tracking/' path: '/' @@ -823,6 +864,21 @@ const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren( AuthenticatedRouteChildren, ) +interface DotwellKnownOauthAuthorizationServerRouteChildren { + DotwellKnownOauthAuthorizationServerApiAuthRoute: typeof DotwellKnownOauthAuthorizationServerApiAuthRoute +} + +const DotwellKnownOauthAuthorizationServerRouteChildren: DotwellKnownOauthAuthorizationServerRouteChildren = + { + DotwellKnownOauthAuthorizationServerApiAuthRoute: + DotwellKnownOauthAuthorizationServerApiAuthRoute, + } + +const DotwellKnownOauthAuthorizationServerRouteWithChildren = + DotwellKnownOauthAuthorizationServerRoute._addFileChildren( + DotwellKnownOauthAuthorizationServerRouteChildren, + ) + const rootRouteChildren: RootRouteChildren = { AppRouteRoute: AppRouteRouteWithChildren, ProjectRouteRoute: ProjectRouteRouteWithChildren, @@ -832,7 +888,8 @@ const rootRouteChildren: RootRouteChildren = { ResetPasswordRoute: ResetPasswordRoute, VerifyEmailRoute: VerifyEmailRoute, DotwellKnownOauthAuthorizationServerRoute: - DotwellKnownOauthAuthorizationServerRoute, + DotwellKnownOauthAuthorizationServerRouteWithChildren, + DotwellKnownOpenidConfigurationRoute: DotwellKnownOpenidConfigurationRoute, DotwellKnownOauthProtectedResourceMcpRoute: DotwellKnownOauthProtectedResourceMcpRoute, ApiAuthSplatRoute: ApiAuthSplatRoute, diff --git a/src/routes/[.]well-known/oauth-authorization-server/api/auth.ts b/src/routes/[.]well-known/oauth-authorization-server/api/auth.ts new file mode 100644 index 0000000..0b6dc2f --- /dev/null +++ b/src/routes/[.]well-known/oauth-authorization-server/api/auth.ts @@ -0,0 +1,31 @@ +import { oauthProviderAuthServerMetadata } from "@better-auth/oauth-provider"; +import { createFileRoute } from "@tanstack/react-router"; +import { env } from "cloudflare:workers"; +import { getAuth, hasHostedAuthConfig } from "@/lib/auth"; +import { isHostedAuthMode } from "@/lib/auth-mode"; + +function unavailableMetadataResponse() { + if (!isHostedAuthMode(env.AUTH_MODE)) { + return new Response("Not found", { status: 404 }); + } + + return new Response("Missing Better Auth hosted configuration", { + status: 500, + }); +} + +export const Route = createFileRoute( + "/.well-known/oauth-authorization-server/api/auth", +)({ + server: { + handlers: { + GET: async ({ request }: { request: Request }) => { + if (!isHostedAuthMode(env.AUTH_MODE) || !hasHostedAuthConfig()) { + return unavailableMetadataResponse(); + } + + return oauthProviderAuthServerMetadata(getAuth())(request); + }, + }, + }, +}); diff --git a/src/routes/[.]well-known/oauth-protected-resource/mcp.ts b/src/routes/[.]well-known/oauth-protected-resource/mcp.ts index aa3e60b..c992b91 100644 --- a/src/routes/[.]well-known/oauth-protected-resource/mcp.ts +++ b/src/routes/[.]well-known/oauth-protected-resource/mcp.ts @@ -1,7 +1,8 @@ import { createFileRoute } from "@tanstack/react-router"; import { env } from "cloudflare:workers"; -import { getAuth, getHostedBaseUrl, hasHostedAuthConfig } from "@/lib/auth"; +import { getHostedBaseUrl, hasHostedAuthConfig } from "@/lib/auth"; import { isHostedAuthMode } from "@/lib/auth-mode"; +import { getOAuthProviderResourceActions } from "@/lib/oauth-provider-resource-client"; import { getMcpResource, MCP_SCOPE } from "@/lib/oauth-resource"; function unavailableMetadataResponse() { @@ -25,13 +26,13 @@ export const Route = createFileRoute( } const baseUrl = getHostedBaseUrl(); - const authServerMetadata = await getAuth().api.getOAuthServerConfig(); - const metadata = { - resource: getMcpResource(baseUrl), - authorization_servers: [authServerMetadata.issuer], - scopes_supported: [MCP_SCOPE], - resource_name: "OpenSEO MCP", - }; + const metadata = + await getOAuthProviderResourceActions().getProtectedResourceMetadata({ + resource: getMcpResource(baseUrl), + authorization_servers: [`${baseUrl}/api/auth`], + scopes_supported: [MCP_SCOPE], + resource_name: "OpenSEO MCP", + }); return new Response(JSON.stringify(metadata), { headers: { diff --git a/src/routes/[.]well-known/openid-configuration.ts b/src/routes/[.]well-known/openid-configuration.ts new file mode 100644 index 0000000..84c10aa --- /dev/null +++ b/src/routes/[.]well-known/openid-configuration.ts @@ -0,0 +1,29 @@ +import { oauthProviderOpenIdConfigMetadata } from "@better-auth/oauth-provider"; +import { createFileRoute } from "@tanstack/react-router"; +import { env } from "cloudflare:workers"; +import { getAuth, hasHostedAuthConfig } from "@/lib/auth"; +import { isHostedAuthMode } from "@/lib/auth-mode"; + +function unavailableMetadataResponse() { + if (!isHostedAuthMode(env.AUTH_MODE)) { + return new Response("Not found", { status: 404 }); + } + + return new Response("Missing Better Auth hosted configuration", { + status: 500, + }); +} + +export const Route = createFileRoute("/.well-known/openid-configuration")({ + server: { + handlers: { + GET: async ({ request }: { request: Request }) => { + if (!isHostedAuthMode(env.AUTH_MODE) || !hasHostedAuthConfig()) { + return unavailableMetadataResponse(); + } + + return oauthProviderOpenIdConfigMetadata(getAuth())(request); + }, + }, + }, +}); diff --git a/src/routes/_authenticated.oauth-consent.tsx b/src/routes/_authenticated.oauth-consent.tsx index c62c8b3..2c8e16b 100644 --- a/src/routes/_authenticated.oauth-consent.tsx +++ b/src/routes/_authenticated.oauth-consent.tsx @@ -1,16 +1,52 @@ +import { useQuery } from "@tanstack/react-query"; import { createFileRoute } from "@tanstack/react-router"; -import { ShieldCheck } from "lucide-react"; +import { Check, Database, KeyRound, User } from "lucide-react"; import { useState } from "react"; -import { authClient } from "@/lib/auth-client"; +import { authClient, useSession } from "@/lib/auth-client"; +import { getOAuthClientInfo } from "@/serverFunctions/oauth"; export const Route = createFileRoute("/_authenticated/oauth-consent")({ component: OAuthConsentPage, }); +const SCOPES = [ + { + icon: Database, + label: "Read your OpenSEO data", + description: "Projects, keyword reports, and audit results.", + }, + { + icon: KeyRound, + label: "Act on your behalf via MCP", + description: "Run tools and write results back to your workspace.", + }, +]; + function OAuthConsentPage() { + const { data: session } = useSession(); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); + const clientId = + typeof window !== "undefined" + ? new URLSearchParams(window.location.search).get("client_id") + : null; + + const clientInfoQuery = useQuery({ + queryKey: ["oauth-client-info", clientId], + queryFn: () => + clientId + ? getOAuthClientInfo({ data: { clientId } }) + : Promise.resolve(null), + enabled: Boolean(clientId), + staleTime: 60_000, + }); + + const clientName = clientInfoQuery.data?.name ?? null; + const userEmail = session?.user?.email ?? null; + const isLoadingClient = clientInfoQuery.isLoading; + const named = Boolean(clientName); + async function respond(accept: boolean) { setError(null); setIsSubmitting(true); @@ -35,29 +71,85 @@ function OAuthConsentPage() { } return ( -
-
-
- -
-
-

Authorize MCP access

-

- Allow this MCP client to access your OpenSEO workspace. -

-
+
+
+ OpenSEO + {isLoadingClient ? ( +
+ ) : ( +

+ {named ? ( + <> + Authorize {clientName} + + ) : ( + "Authorize MCP access" + )} +

+ )} +

+ {named + ? `${clientName} is requesting access to your OpenSEO workspace.` + : "An MCP client is requesting access to your OpenSEO workspace."} +

- {error ?

{error}

: null} + {!named && !isLoadingClient ? ( +
+ This client did not provide a name during registration. Only continue + if you started this connection yourself. +
+ ) : null} -
+ {userEmail ? ( +
+
+ +
+
+
Signed in as
+
{userEmail}
+
+
+ ) : null} + +
+
+ {named ? `This will allow ${clientName} to` : "This will allow it to"} +
+
    + {SCOPES.map((scope) => ( +
  • + +
    +
    {scope.label}
    +
    + {scope.description} +
    +
    +
  • + ))} +
+
+ + {error ? ( +
+ {error} +
+ ) : null} + +
+ +

+ You can revoke access at any time in Settings. +

); } diff --git a/src/routes/api/auth/$.test.ts b/src/routes/api/auth/$.test.ts new file mode 100644 index 0000000..af9e4e4 --- /dev/null +++ b/src/routes/api/auth/$.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("cloudflare:workers", () => ({ + env: { + AUTH_MODE: "hosted", + }, +})); + +vi.mock("@tanstack/react-router", () => ({ + createFileRoute: () => (routeConfig: unknown) => routeConfig, +})); + +vi.mock("@/lib/auth", () => ({ + getAuth: () => ({ handler: vi.fn() }), + getHostedBaseUrl: () => "https://open-seo.test", + hasHostedAuthConfig: () => true, +})); + +describe("maybeInjectMcpResource", () => { + it("injects the MCP resource into form token requests when missing", async () => { + const { maybeInjectMcpResource } = await import("@/routes/api/auth/$"); + const request = new Request("https://open-seo.test/api/auth/oauth2/token", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + grant_type: "authorization_code", + code: "code_123", + }), + }); + + const result = await maybeInjectMcpResource(request); + const params = new URLSearchParams(await result.text()); + + expect(params.get("resource")).toBe("https://open-seo.test/mcp"); + expect(params.get("grant_type")).toBe("authorization_code"); + expect(params.get("code")).toBe("code_123"); + }); + + it("leaves token requests alone when a resource is already present", async () => { + const { maybeInjectMcpResource } = await import("@/routes/api/auth/$"); + const request = new Request("https://open-seo.test/api/auth/oauth2/token", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + grant_type: "authorization_code", + resource: "https://other-resource.test/mcp", + }), + }); + + await expect(maybeInjectMcpResource(request)).resolves.toBe(request); + }); + + it("skips requests that are not matching form POST token requests", async () => { + const { maybeInjectMcpResource } = await import("@/routes/api/auth/$"); + const requests = [ + new Request("https://open-seo.test/api/auth/oauth2/token", { + method: "GET", + }), + new Request("https://open-seo.test/api/auth/oauth2/authorize", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ grant_type: "authorization_code" }), + }), + new Request("https://open-seo.test/api/auth/oauth2/token", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ grant_type: "authorization_code" }), + }), + ]; + + for (const request of requests) { + await expect(maybeInjectMcpResource(request)).resolves.toBe(request); + } + }); +}); diff --git a/src/routes/api/auth/$.ts b/src/routes/api/auth/$.ts index c59b28b..0cdebeb 100644 --- a/src/routes/api/auth/$.ts +++ b/src/routes/api/auth/$.ts @@ -1,9 +1,48 @@ import { createFileRoute } from "@tanstack/react-router"; import { env } from "cloudflare:workers"; -import { getAuth, hasHostedAuthConfig } from "@/lib/auth"; +import { getAuth, getHostedBaseUrl, hasHostedAuthConfig } from "@/lib/auth"; import { isHostedAuthMode } from "@/lib/auth-mode"; +import { getMcpResource } from "@/lib/oauth-resource"; -function handleAuthRequest(request: Request) { +const TOKEN_PATH = "/api/auth/oauth2/token"; + +// Inject RFC 8707 `resource` into /oauth2/token requests when the client +// omitted it. Some MCP clients (notably codex as of 2026-05) skip the +// resource indicator, which makes better-auth issue an opaque access token +// (see `checkResource` in @better-auth/oauth-provider — audience comes from +// `ctx.body.resource` at token-issuance time, not from the stored authorize +// query). Without an audience to bind, no `aud` claim → opaque token → no +// local JWT verify on the resource side. +// +// We only have one valid audience (`validAudiences: [mcpResource]` in +// auth-config.ts), so it is safe to default missing resources to it. Remove +// this shim once MCP clients reliably pass `resource` per spec. +export async function maybeInjectMcpResource( + request: Request, +): Promise { + if (request.method !== "POST") return request; + + const url = new URL(request.url); + if (url.pathname !== TOKEN_PATH) return request; + + const contentType = request.headers.get("content-type") ?? ""; + if (!contentType.includes("application/x-www-form-urlencoded")) + return request; + + const body = await request.clone().text(); + const params = new URLSearchParams(body); + if (params.has("resource")) return request; + + params.set("resource", getMcpResource(getHostedBaseUrl())); + + return new Request(request.url, { + method: request.method, + headers: request.headers, + body: params.toString(), + }); +} + +async function handleAuthRequest(request: Request) { if (!isHostedAuthMode(env.AUTH_MODE)) { return new Response("Not found", { status: 404, @@ -17,7 +56,7 @@ function handleAuthRequest(request: Request) { } const auth = getAuth(); - return auth.handler(request); + return auth.handler(await maybeInjectMcpResource(request)); } export const Route = createFileRoute("/api/auth/$")({ diff --git a/src/server.ts b/src/server.ts index b5cde11..303ec8f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,8 +7,20 @@ import { beginRankCheckRun } from "@/server/features/rank-tracking/services/rank import { customerHasPaidPlan } from "@/server/billing/subscription"; import { isHostedServerAuthMode } from "@/server/lib/runtime-env"; import { computeNextCheckAt } from "@/shared/rank-tracking"; +import { handleMcpRequest, MCP_ROUTE } from "@/server/mcp/handler"; -const fetch = createStartHandler(defaultStreamHandler); +const appFetch = createStartHandler(defaultStreamHandler); +const fetch = ( + request: Request, + env: Env, + ctx: ExecutionContext, +): Response | Promise => { + if (new URL(request.url).pathname === MCP_ROUTE) { + return handleMcpRequest(request, env, ctx); + } + + return appFetch(request); +}; // Export Workflow classes as named exports export { SiteAuditWorkflow } from "./server/workflows/SiteAuditWorkflow"; diff --git a/src/server/mcp/context.ts b/src/server/mcp/context.ts new file mode 100644 index 0000000..1a1f669 --- /dev/null +++ b/src/server/mcp/context.ts @@ -0,0 +1,26 @@ +import { getMcpAuthContext } from "agents/mcp"; +import { z } from "zod"; + +export const MCP_AUTH_CONTEXT_PROP = "openSeoAuth"; + +const mcpToolAuthContextSchema = z.object({ + userId: z.string().min(1), + organizationId: z.string().min(1), + clientId: z.string().nullable(), + scopes: z.array(z.string()), + audience: z.string().min(1), + subject: z.string().min(1), +}); + +type McpToolAuthContext = z.infer; + +export function requireMcpToolAuthContext(): McpToolAuthContext { + const rawContext = getMcpAuthContext()?.props[MCP_AUTH_CONTEXT_PROP]; + const result = mcpToolAuthContextSchema.safeParse(rawContext); + + if (!result.success) { + throw new Error(`MCP auth context missing: ${result.error.message}`); + } + + return result.data; +} diff --git a/src/server/mcp/handler.test.ts b/src/server/mcp/handler.test.ts new file mode 100644 index 0000000..d20e2ea --- /dev/null +++ b/src/server/mcp/handler.test.ts @@ -0,0 +1,252 @@ +import type { CreateMcpHandlerOptions } from "agents/mcp"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { z } from "zod"; +import { MCP_AUTH_CONTEXT_PROP } from "@/server/mcp/context"; + +const verifyMocks = vi.hoisted(() => ({ + verifyJwsAccessToken: vi.fn(), +})); + +const serverMocks = vi.hoisted(() => ({ + nextServerId: 0, + createdServerIds: [] as number[], + serverIds: new WeakMap(), +})); + +vi.mock("@/lib/auth", () => ({ + getAuth: () => ({ api: { getJwks: vi.fn() } }), + getHostedBaseUrl: () => "https://open-seo.test", + hasHostedAuthConfig: () => true, +})); + +vi.mock("better-auth/oauth2", () => ({ + verifyJwsAccessToken: verifyMocks.verifyJwsAccessToken, +})); + +vi.mock("@/server/mcp/server", () => ({ + createOpenSeoMcpServer: () => { + serverMocks.nextServerId += 1; + const server = new McpServer({ name: "Test MCP", version: "0.0.0" }); + serverMocks.createdServerIds.push(serverMocks.nextServerId); + serverMocks.serverIds.set(server, serverMocks.nextServerId); + return server; + }, +})); + +vi.mock("agents/mcp", () => ({ + createMcpHandler: (_server: McpServer, options: CreateMcpHandlerOptions) => { + return async () => + new Response( + JSON.stringify({ + serverId: serverMocks.serverIds.get(_server), + options, + }), + { + status: 200, + headers: { "Content-Type": "application/json" }, + }, + ); + }, +})); + +const ctx: ExecutionContext = { + waitUntil() {}, + passThroughOnException() {}, + props: {}, +}; + +const transportOptionsSchema = z.object({ + serverId: z.number().optional(), + options: z.object({ + route: z.string().optional(), + enableJsonResponse: z.boolean().optional(), + authContext: z + .object({ + props: z.record(z.string(), z.unknown()), + }) + .optional(), + }), +}); + +function createMcpRequest(token: string) { + return new Request("https://open-seo.test/mcp", { + method: "POST", + headers: { + Accept: "application/json, text/event-stream", + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "tools/list", + }), + }); +} + +const jwtShapedToken = "header.payload.signature"; +const organizationIdClaim = "https://open-seo.test/mcp/claims/organization-id"; + +function createAccessTokenPayload( + overrides: Record = {}, +): Record { + return { + sub: "user_123", + azp: "client_123", + scope: "offline_access mcp", + aud: "https://open-seo.test/mcp", + [organizationIdClaim]: "org_123", + ...overrides, + }; +} + +describe("handleMcpRequest", () => { + beforeEach(() => { + vi.clearAllMocks(); + serverMocks.nextServerId = 0; + serverMocks.createdServerIds = []; + serverMocks.serverIds = new WeakMap(); + verifyMocks.verifyJwsAccessToken.mockResolvedValue( + createAccessTokenPayload(), + ); + }); + + it("accepts access tokens verified by Better Auth", async () => { + const { handleMcpRequest } = await import("@/server/mcp/handler"); + + const response = await handleMcpRequest( + createMcpRequest(jwtShapedToken), + { + AUTH_MODE: "hosted", + }, + ctx, + ); + const body = transportOptionsSchema.parse(await response.json()); + + expect(response.status).toBe(200); + expect( + body.options.authContext?.props[MCP_AUTH_CONTEXT_PROP], + ).toMatchObject({ + userId: "user_123", + organizationId: "org_123", + clientId: "client_123", + scopes: ["offline_access", "mcp"], + }); + expect(body.options.route).toBe("/mcp"); + expect(body.options.enableJsonResponse).toBe(true); + + const functionMatcher: unknown = expect.any(Function); + expect(verifyMocks.verifyJwsAccessToken).toHaveBeenCalledWith( + jwtShapedToken, + expect.objectContaining({ + verifyOptions: { + audience: "https://open-seo.test/mcp", + issuer: "https://open-seo.test/api/auth", + }, + jwksFetch: functionMatcher, + }), + ); + }); + + it("creates a fresh server for each request without persisted transport state", async () => { + const { handleMcpRequest } = await import("@/server/mcp/handler"); + + const first = await handleMcpRequest( + createMcpRequest(jwtShapedToken), + { + AUTH_MODE: "hosted", + }, + ctx, + ); + const second = await handleMcpRequest( + createMcpRequest(jwtShapedToken), + { + AUTH_MODE: "hosted", + }, + ctx, + ); + const firstBody = transportOptionsSchema.parse(await first.json()); + const secondBody = transportOptionsSchema.parse(await second.json()); + + expect(serverMocks.createdServerIds).toEqual([1, 2]); + expect(firstBody.serverId).toBe(1); + expect(secondBody.serverId).toBe(2); + expect(firstBody.options).not.toHaveProperty("sessionIdGenerator"); + expect(firstBody.options).not.toHaveProperty("storage"); + expect(firstBody.options).not.toHaveProperty("transport"); + }); + + it("lets the MCP transport handle OPTIONS without token verification", async () => { + const { handleMcpRequest } = await import("@/server/mcp/handler"); + + const response = await handleMcpRequest( + new Request("https://open-seo.test/mcp", { method: "OPTIONS" }), + { + AUTH_MODE: "hosted", + }, + ctx, + ); + const body = transportOptionsSchema.parse(await response.json()); + + expect(response.status).toBe(200); + expect(verifyMocks.verifyJwsAccessToken).not.toHaveBeenCalled(); + expect(body.options.authContext).toBeUndefined(); + }); + + it("returns 401 when Better Auth rejects the access token", async () => { + const { handleMcpRequest } = await import("@/server/mcp/handler"); + verifyMocks.verifyJwsAccessToken.mockRejectedValue( + new Error("invalid audience"), + ); + + const response = await handleMcpRequest( + createMcpRequest(jwtShapedToken), + { + AUTH_MODE: "hosted", + }, + ctx, + ); + + expect(response.status).toBe(401); + expect(response.headers.get("WWW-Authenticate")).toBe( + 'Bearer resource_metadata="https://open-seo.test/.well-known/oauth-protected-resource/mcp"', + ); + }); + + it("returns 403 when the verified token is missing MCP organization context", async () => { + const { handleMcpRequest } = await import("@/server/mcp/handler"); + verifyMocks.verifyJwsAccessToken.mockResolvedValue( + createAccessTokenPayload({ + [organizationIdClaim]: undefined, + }), + ); + + const response = await handleMcpRequest( + createMcpRequest(jwtShapedToken), + { + AUTH_MODE: "hosted", + }, + ctx, + ); + + expect(response.status).toBe(403); + }); + + it("returns 401 when the token is missing the required mcp scope", async () => { + const { handleMcpRequest } = await import("@/server/mcp/handler"); + verifyMocks.verifyJwsAccessToken.mockResolvedValue( + createAccessTokenPayload({ scope: "offline_access" }), + ); + + const response = await handleMcpRequest( + createMcpRequest(jwtShapedToken), + { + AUTH_MODE: "hosted", + }, + ctx, + ); + + expect(response.status).toBe(401); + }); +}); diff --git a/src/server/mcp/handler.ts b/src/server/mcp/handler.ts new file mode 100644 index 0000000..638d837 --- /dev/null +++ b/src/server/mcp/handler.ts @@ -0,0 +1,150 @@ +import { createMcpHandler } from "agents/mcp"; +import { verifyJwsAccessToken } from "better-auth/oauth2"; +import type { JWTPayload } from "jose"; +import { getAuth, getHostedBaseUrl, hasHostedAuthConfig } from "@/lib/auth"; +import { isHostedAuthMode } from "@/lib/auth-mode"; +import { + getMcpOrganizationIdClaim, + getMcpProtectedResourceMetadataUrl, + getMcpResource, + MCP_SCOPE, +} from "@/lib/oauth-resource"; +import { MCP_AUTH_CONTEXT_PROP } from "@/server/mcp/context"; +import { createOpenSeoMcpServer } from "@/server/mcp/server"; + +// MCP request flow: +// 1. Resource (`resource=`) is injected into /oauth2/token requests by +// `routes/api/auth/$.ts` so Better Auth always issues audience-bound JWTs +// (some MCP clients skip RFC 8707; without it tokens would be opaque). +// 2. Here we verify the JWT in-process via `verifyJwsAccessToken`, reading +// the JWKS through `auth.api.getJwks()` rather than HTTP self-fetching +// `/api/auth/jwks` (which 500s under workerd dev's self-routing and is +// pointless in prod since the auth server and resource server are the +// same Worker). +// 3. We expect `iss = baseURL + basePath` (basePath defaults to `/api/auth`) +// and `aud = mcpResource`, both confirmed against the published +// /.well-known/oauth-authorization-server metadata. + +export const MCP_ROUTE = "/mcp"; + +type McpAccessTokenPayload = JWTPayload & { + azp?: unknown; + client_id?: unknown; + scope?: unknown; +}; + +function getTokenScopes(payload: McpAccessTokenPayload) { + return typeof payload.scope === "string" + ? payload.scope.split(/\s+/).filter(Boolean) + : []; +} + +function getStringClaim(payload: Record, claim: string) { + const value = payload[claim]; + return typeof value === "string" && value.length > 0 ? value : null; +} + +function unauthorizedResponse(resource: string) { + return new Response("Unauthorized", { + status: 401, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "WWW-Authenticate", + "WWW-Authenticate": `Bearer resource_metadata="${getMcpProtectedResourceMetadataUrl( + resource, + )}"`, + }, + }); +} + +export async function handleMcpRequest( + request: Request, + env: { AUTH_MODE?: unknown }, + ctx: ExecutionContext, +) { + const authMode = + typeof env.AUTH_MODE === "string" ? env.AUTH_MODE : undefined; + + if (!isHostedAuthMode(authMode)) { + return new Response("Not found", { status: 404 }); + } + + if (!hasHostedAuthConfig()) { + return new Response("Missing Better Auth hosted configuration", { + status: 500, + }); + } + + const baseUrl = getHostedBaseUrl(); + const auth = getAuth(); + const mcpResource = getMcpResource(baseUrl); + const issuer = `${baseUrl}/api/auth`; + const organizationIdClaim = getMcpOrganizationIdClaim(baseUrl); + const server = createOpenSeoMcpServer(); + + if (request.method === "OPTIONS") { + return createMcpHandler(server, { + route: MCP_ROUTE, + enableJsonResponse: true, + })(request, env, ctx); + } + + const accessToken = + request.headers + .get("Authorization") + ?.replace(/^Bearer\s+/i, "") + .trim() || undefined; + + let payload: McpAccessTokenPayload; + try { + if (!accessToken) throw new Error("missing access token"); + payload = await verifyJwsAccessToken(accessToken, { + jwksFetch: () => auth.api.getJwks(), + verifyOptions: { audience: mcpResource, issuer }, + }); + } catch { + return unauthorizedResponse(mcpResource); + } + + const scopes = getTokenScopes(payload); + if (!scopes.includes(MCP_SCOPE)) { + return unauthorizedResponse(mcpResource); + } + + const userId = getStringClaim(payload, "sub"); + const organizationId = getStringClaim(payload, organizationIdClaim); + const clientId = + getStringClaim(payload, "azp") ?? getStringClaim(payload, "client_id"); + + if (!userId || !organizationId) { + return new Response( + userId + ? "MCP organization context required" + : "MCP user context required", + { + status: 403, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "WWW-Authenticate", + }, + }, + ); + } + + return createMcpHandler(server, { + route: MCP_ROUTE, + enableJsonResponse: true, + authContext: { + props: { + [MCP_AUTH_CONTEXT_PROP]: { + userId, + organizationId, + clientId, + scopes, + audience: mcpResource, + subject: userId, + }, + }, + }, + })(request, env, ctx); +} diff --git a/src/server/mcp/server.ts b/src/server/mcp/server.ts new file mode 100644 index 0000000..dbe5f13 --- /dev/null +++ b/src/server/mcp/server.ts @@ -0,0 +1,63 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ProjectService } from "@/server/features/projects/services/ProjectService"; +import { requireMcpToolAuthContext } from "@/server/mcp/context"; + +function jsonToolResult(data: Record) { + return { + structuredContent: data, + content: [ + { + type: "text" as const, + text: JSON.stringify(data, null, 2), + }, + ], + }; +} + +export function createOpenSeoMcpServer() { + const server = new McpServer({ + name: "OpenSEO MCP", + version: "0.0.10", + }); + + server.registerTool( + "whoami", + { + title: "Who am I", + description: "Return the verified OpenSEO user and organization context.", + }, + async () => { + const auth = requireMcpToolAuthContext(); + + return jsonToolResult({ + userId: auth.userId, + activeOrganizationId: auth.organizationId, + account: { + clientId: auth.clientId, + scopes: auth.scopes, + audience: auth.audience, + subject: auth.subject, + }, + }); + }, + ); + + server.registerTool( + "list_projects", + { + title: "List projects", + description: "List projects in the verified OpenSEO organization.", + }, + async () => { + const auth = requireMcpToolAuthContext(); + const projects = await ProjectService.listProjects(auth.organizationId); + + return jsonToolResult({ + activeOrganizationId: auth.organizationId, + projects, + }); + }, + ); + + return server; +} diff --git a/src/serverFunctions/oauth.ts b/src/serverFunctions/oauth.ts new file mode 100644 index 0000000..f425794 --- /dev/null +++ b/src/serverFunctions/oauth.ts @@ -0,0 +1,33 @@ +import { createServerFn } from "@tanstack/react-start"; +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { db } from "@/db"; +import { oauthClient } from "@/db/better-auth-schema"; +import { requireAuthenticatedContext } from "@/serverFunctions/middleware"; + +const getOAuthClientInfoSchema = z.object({ + clientId: z.string().min(1), +}); + +export const getOAuthClientInfo = createServerFn({ method: "POST" }) + .middleware(requireAuthenticatedContext) + .inputValidator((data: unknown) => getOAuthClientInfoSchema.parse(data)) + .handler(async ({ data }) => { + const row = await db + .select({ + name: oauthClient.name, + icon: oauthClient.icon, + uri: oauthClient.uri, + }) + .from(oauthClient) + .where(eq(oauthClient.clientId, data.clientId)) + .get(); + + if (!row) return null; + + return { + name: row.name ?? null, + icon: row.icon ?? null, + uri: row.uri ?? null, + }; + });