Skip to content

Upgrade Better Auth to 1.7.10 so MCP clients can register - #324

Merged
wasimxyz merged 2 commits into
stagingfrom
upgrade-better-auth-1.7
Oct 16, 2026
Merged

wasimxyz merged 2 commits into
stagingfrom
upgrade-better-auth-1.7

Conversation

@wasimxyz

@wasimxyz wasimxyz commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Summary

Upgrades better-auth and @better-auth/oauth-provider from 1.6.27 to 1.7.10. Closes #266.

We rolled back from 1.7 in #218 because it rejected the OAuth client registration that Cursor and Claude Code send. The upstream fix (better-auth/better-auth#10956) shipped in 1.7.8. It adds a clientRegistrationDefaultApplicationType setting, and this PR sets it to "infer".

The upgrade also clears GHSA-p2fr-6hmx-4528 from npm audit. It replaces #308, the Dependabot PR that bumped only @better-auth/oauth-provider to 1.7.0, the version that broke registration.

What changed

Settings in web/lib/auth.ts

  • clientRegistrationDefaultApplicationType: "infer". When a client leaves out application_type, Better Auth registers it as native if any callback uses a custom scheme (Cursor's cursor://) or a loopback address (Claude Code's http://localhost:3118). Clients with only https callbacks stay web, and every callback of a native client must still pass the native rules.
  • resources: [mcpResourceAudience] replaces validAudiences, which 1.7 removed. Better Auth stores the audience as a row in oauth_resource.
  • enforcePerClientResources: false. In 1.7, each client must be linked to the audience in oauth_client_resource by default. Every client registered on staging and production under 1.6 has no link and would get invalid_target, so existing Cursor and Claude Code connections would break on deploy. With one audience, the link restricts nothing. The upstream upgrade guide recommends this setting for servers that accept dynamic registration.
  • Registered the three 1.7 OAuth tables that migration 0044 already created, and removed the silenceWarnings option, which 1.7 removed along with the warning it silenced.

Other code

  • web/lib/mcp/auth.ts: verifyAccessToken is renamed verifyBearerToken in 1.7. The options are the same.
  • web/package.json: removed the @better-auth/core override that kept every Better Auth package on 1.6. All of them now resolve to 1.7.10.

Database

No migration. npm run db:generate-auth-schema against 1.7.10 produces the same tables we already have, apart from formatting, and drizzle-kit generate reports no changes.

account.issuer stays as it is. Only 1.7.0 through 1.7.2 wrote it, and 1.7.3 went back to identifying accounts by providerId and accountId. Migration 0045 already made the column nullable, which is all upstream requires. I left the column in place so that rolling the code back doesn't fail on a missing column. Dropping it can be a later cleanup.

Test and seed setup

1.7.10 changes two things that only matter when tables are wiped while the server keeps running, which the integration tests and make db-reseed both do:

  • Better Auth inserts the oauth_resource row only once per server process. The new seedMcpResource puts it back after resetDb() and in the workspace seed. Without it, every MCP token request after a reset failed with invalid_target.
  • The MCP route now caches the published signing keys and refetches them at most every 30 seconds. clearAll no longer empties jwks, so tokens keep being signed with a key the route already knows. Without this, MCP calls after a reset returned 401.

Tests

  • Registration returns 201 in 1.7 instead of 200.
  • The Cursor test now sends Cursor's exact registration request: three callbacks including cursor://anysphere.cursor-mcp/oauth/callback, and no application_type. It checks that the client is stored as native and that the authorize step accepts the cursor:// callback.
  • The Claude Code test uses Claude Code's http://localhost:3118/callback.
  • New: a request that pairs a custom scheme with a routable http:// callback is rejected with invalid_redirect_uri.
  • getMcpAccessToken inserts clients the way 1.6 stored them (no audience link, no application_type), so every MCP integration test also checks that pre-upgrade clients keep working.

zod 4.6

Better Auth 1.7.10 requires zod 4.5.4 or later, so zod moves from 4.4.3 to 4.6.7. Two behavior changes arrived in zod 4.5.0:

  • JSON Schema output writes nullable fields as type: ["string", "null"] instead of anyOf. This changes the output schemas of 31 MCP tools; no tool input schema changes. mcp-catalog.snapshot.json is regenerated.
  • isoDateTime now requires seconds, as RFC 3339 does. 2026-10-16T12:00Z was accepted before and is now rejected. Of our request fields, only acquired_at and file_created_at on the run and file endpoints use it. The watcher formats both with Python's isoformat(), which always includes seconds.

No changelog entry. Users shouldn't notice any change, because Cursor and Claude Code already work on 1.6 and the watcher already sends seconds.

Google sign-in on preview deployments

Google sign-in on this PR's preview fails with state_mismatch on staging's login page. That's expected until staging runs 1.7:

  • Previews send Google sign-in through staging with Better Auth's oAuthProxy plugin.
  • 1.7 encrypts the hand-off with a separate key per purpose, while 1.6 uses the shared secret directly. 1.7 also moved the preview-side return endpoint from /oauth-proxy-callback to /callback/:id/oauth-proxy.
  • Staging on 1.6 can't decrypt the state that a 1.7 preview creates. It handles the callback as an ordinary sign-in, finds no matching state, and redirects to /login?error=state_mismatch.

After this merges, staging handles 1.7 previews. Previews of branches still on 1.6 hit the same error until they merge staging (currently #246), and so does a local make dev that uses OAUTH_PROXY_URL with a different Better Auth version than staging.

Before merging

Upstream asks for a check for duplicate account keys before upgrading, because 1.7 rejects a sign-in when more than one row matches. Our schema has no unique index on these columns, so please run this on staging and production and confirm it returns no rows:

SELECT "providerId", "accountId", count(*) FROM account GROUP BY 1, 2 HAVING count(*) > 1;

Test plan

  • make check
  • Frontend unit tests (799 pass)
  • Frontend integration tests (526 pass), including the MCP OAuth flow end to end
  • npm audit no longer reports GHSA-p2fr-6hmx-4528. The remaining high-severity findings come from the braces chain and were already on staging.
  • Local email sign-in as alice@example.com works, and the session reports the admin flag
  • Locally, Cursor's and Claude Code's registration requests return 201 as native, and an https-only client returns 201 as web
  • The real Cursor client connected to the local server: registration, sign-in, consent, and token exchange succeeded, and get_me returned the signed-in user
  • Duplicate account key query returns no rows on staging and production
  • After merge: reconnect Cursor to staging and run claude mcp login data-hub-staging
  • After merge: confirm an MCP client connected before the deploy still works without reconnecting
  • After merge: Google sign-in on staging (direct, no proxy)
  • After merge: Google sign-in on a preview of a branch that includes this change (proxied through staging)

Better Auth 1.7.8 added `clientRegistrationDefaultApplicationType`, which
accepts the registration that Cursor and Claude Code send without an
`application_type`. Upgrade `better-auth` and `@better-auth/oauth-provider`
together, set that option to "infer", and redo the 1.7 settings that the
rollback in #218 undid.

Closes #266

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
data-hub-docs Ready Ready Preview Oct 16, 2026 8:34pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
data-hub Ignored Ignored Preview Oct 16, 2026 8:34pm UTC

Request Review

Better Auth 1.7.10 requires zod 4.5.4 or later, and zod 4.5 writes nullable
fields as `type: [..., "null"]` and requires seconds in date-time patterns.
Only tool output schemas change.

Co-authored-by: Cursor <cursoragent@cursor.com>
@wasimxyz
wasimxyz merged commit 22c06c6 into staging Oct 16, 2026
6 checks passed
@wasimxyz
wasimxyz deleted the upgrade-better-auth-1.7 branch October 16, 2026 20:41

This branch was successfully deployed

2 active (1 outdated) deployments
Preview – data-hub-docs — e87a92b6 Deployed Oct 16, 2026 by vercel[bot]
Preview – data-hub — 2b1c536e Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upgrade Better Auth to 1.7 once upstream fixes MCP client registration

1 participant