Skip to content

fix(oauth-debugger): persist registration strategy + protocol version through Convex#3040

Merged
chelojimenez merged 1 commit into
mainfrom
fix/oauth-profile-roundtrip
Jul 7, 2026
Merged

fix(oauth-debugger): persist registration strategy + protocol version through Convex#3040
chelojimenez merged 1 commit into
mainfrom
fix/oauth-profile-roundtrip

Conversation

@chelojimenez

@chelojimenez chelojimenez commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Inspector half of the OAuth Debugger persistence fix. Depends on MCPJam/mcpjam-backend#658 — the backend must deploy first (Convex mutations reject unexpected args, so sending the new fields against the old deployment would break server saves).

Problem

In authenticated projects, saving "Pre-registered" (or CIMD, or an older protocol version) in the debugger's "Configure Server to Test" modal silently reverts to Dynamic (DCR) / 2025-11-25:

  1. The save is correct in memory, but the Convex sync payload only carried oauthScopes / clientId / oauthResourceUrl — the rest of the test profile had nowhere to go (no columns).
  2. The Convex live query then rebuilds activeProject.servers from the flat row, reconstructing oauthFlowProfile without a strategy → defaults back to DCR.
  3. The localStorage fallback (mcp-oauth-config-*) is explicitly disabled in hosted mode on both write and read, so nothing backstops it.

This also meant reconnect ran the wrong flow: ensureAuthorizedForReconnect prefers server.oauthFlowProfile.registrationStrategy, but that field never survived the catalog round-trip.

Change

  • Sync payload (use-server-state.ts): sends oauthProtocolVersion + oauthRegistrationStrategy from the profile.
  • Deserialization (project-serialization.ts): restores both into the rebuilt profile. The backend columns are plain strings by design (so an old/new client never fails a whole server save over an unknown value); the client narrows them back to the closed unions via new normalizeOAuthProtocolVersion / normalizeOAuthRegistrationStrategy in lib/oauth/profile.ts — unknown wire values read as absent and fall to the reader-side defaults.
  • Reconnect: no code change needed — the orchestrator already prefers the profile; it just never received it. A new test drives the real pipeline end-to-end: flat Convex row → deserializeServersFromConvexensureAuthorizedForReconnectinitiateOAuth called with registrationStrategy: "preregistered" / protocolVersion: "2025-06-18".
  • RemoteServer type gains the two optional fields.

Tests

  • Deserialization: persisted values restored, strategy-only rows still build a profile, unknown wire values ("quantum", "2099-01-01") normalize to absent, legacy rows unchanged.
  • Reconnect integration test as above.
  • Full lib + state + OAuthFlowTab suites: 1161 passed; client typecheck clean.

Rollout

  1. Merge + deploy mcpjam-backend#658.
  2. Merge this. Legacy rows (no columns) behave exactly as today.

🤖 Generated with Claude Code


Note

Medium Risk
Touches OAuth reconnect and Convex server save payloads; rollout depends on the backend schema/mutations accepting the new fields first.

Overview
Fixes hosted OAuth debugger settings silently reverting to Dynamic (DCR) and protocol 2025-11-25 after save when the Convex catalog rebuilds oauthFlowProfile from flat server rows.

Convex sync (use-server-state.ts) now includes oauthProtocolVersion and oauthRegistrationStrategy from the in-memory test profile. RemoteServer types those fields.

Deserialize (project-serialization.ts) maps the flat columns back onto oauthFlowProfile, with normalizeOAuthProtocolVersion / normalizeOAuthRegistrationStrategy in lib/oauth/profile.ts so unknown wire strings are dropped instead of breaking saves.

Reconnect needs no orchestrator change: once the profile survives the round-trip, ensureAuthorizedForReconnect passes the saved strategy/version into initiateOAuth (new integration test via deserializeServersFromConvex).

Tests cover full round-trip, strategy-only rows, unknown values, and legacy rows without columns. Requires backend #658 deployed first so Convex accepts the new mutation fields.

Reviewed by Cursor Bugbot for commit 9d9fcfc. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Persist the OAuth Debugger’s registration strategy and protocol version through Convex so saved profiles don’t reset to DCR/2025-11-25, and reconnect uses the right flow.

  • Bug Fixes

    • Include oauthProtocolVersion and oauthRegistrationStrategy in the sync payload.
    • Restore and normalize both fields into oauthFlowProfile on read; unknown values fall back to defaults, legacy rows unchanged.
    • Extend RemoteServer with optional fields and add round‑trip + reconnect tests.
  • Dependencies

    • Requires MCPJam/mcpjam-backend#658 to be deployed first.

Written for commit 9d9fcfc. Summary will update on new commits.

Review in cubic

… through Convex

Saving "Pre-registered" (or CIMD, or an older protocol) in the OAuth
Debugger's Configure Server to Test modal silently reverted to
DCR/2025-11-25 in authenticated projects: the Convex sync payload only
carried oauthScopes/clientId/oauthResourceUrl, the servers table had no
columns for the rest of the test profile, and the catalog round-trip
rebuilt oauthFlowProfile without it. The localStorage fallback is
disabled in hosted mode on both ends, so nothing backstopped it.

- sync payload now sends oauthProtocolVersion + oauthRegistrationStrategy
  (requires mcpjam-backend#658 deployed first — Convex mutations reject
  unexpected args)
- deserializeServersFromConvex restores them into the rebuilt profile,
  narrowing the string columns back to the closed unions
  (normalizeOAuthProtocolVersion / normalizeOAuthRegistrationStrategy);
  unknown wire values read as absent so reader defaults apply
- reconnect picks the persisted strategy up for free: the orchestrator
  already prefers server.oauthFlowProfile — new test drives the real
  pipeline (Convex row -> deserialize -> reconnect options)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jul 7, 2026
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_2cb9c5ff-7d4a-492e-846a-ce78779fc727)

@chelojimenez

Copy link
Copy Markdown
Contributor Author

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Internal preview

Preview URL: https://mcp-inspector-pr-3040.up.railway.app
Deployed commit: f685316
PR head commit: 9d9fcfc
Backend target: staging fallback.
Health: ✅ Convex reachable
Access is employee-only in non-production environments.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 6 files

Re-trigger cubic

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This change adds persistence and normalization support for OAuth protocol version and registration strategy selections. Two normalizer functions map unknown stored values to known union types, defaulting to undefined for unrecognized inputs. The RemoteServer type gains corresponding optional fields, and the Convex write payload now includes these values from the OAuth flow profile. Deserialization logic is extended to reconstruct oauthFlowProfile from flat Convex columns using the new normalizers. Tests cover round-trip serialization behavior and a reconnect scenario verifying the persisted registration strategy is passed through correctly.

Changes

File(s) Change Summary
lib/oauth/profile.ts Added normalizeOAuthProtocolVersion and normalizeOAuthRegistrationStrategy exported functions with supporting lookup arrays
hooks/useProjects.ts Added optional oauthProtocolVersion and oauthRegistrationStrategy fields to RemoteServer
hooks/use-server-state.ts Included new OAuth fields in Convex write payload
lib/project-serialization.ts Extended deserializeServersFromConvex to normalize and persist protocol version/registration strategy into oauthFlowProfile
lib/__tests__/project-serialization.test.ts Added tests for OAuth field deserialization round-trip
state/__tests__/oauth-orchestrator.test.ts Added reconnect test verifying registration strategy propagation

Sequence Diagram(s)

sequenceDiagram
  participant ConvexRow
  participant deserializeServersFromConvex
  participant normalizeOAuthProtocolVersion
  participant normalizeOAuthRegistrationStrategy
  participant oauthFlowProfile

  ConvexRow->>deserializeServersFromConvex: flat OAuth columns
  deserializeServersFromConvex->>normalizeOAuthProtocolVersion: raw protocolVersion value
  normalizeOAuthProtocolVersion-->>deserializeServersFromConvex: normalized value or undefined
  deserializeServersFromConvex->>normalizeOAuthRegistrationStrategy: raw registrationStrategy value
  normalizeOAuthRegistrationStrategy-->>deserializeServersFromConvex: normalized value or undefined
  deserializeServersFromConvex->>oauthFlowProfile: persist protocolVersion, registrationStrategy
Loading

Estimated code review effort: 3/5 (moderate — touches type contracts, serialization logic, and persistence paths across several files)

Related issues: None specified.

Related PRs: None specified.

Suggested labels: oauth, serialization, testing

Suggested reviewers: None specified.

Poem
A rabbit hops through OAuth's maze,
Pinning strategies, counting protocol days,
Flat columns rise to profiles anew,
Undefined stays where values aren't true.
Tests confirm the round-trip's delight—
Hop, persist, deserialize, just right! 🐇

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.1)
mcpjam-inspector/client/src/lib/__tests__/project-serialization.test.ts

File contains syntax errors that prevent linting: Line 7: Declarations inside of a import declaration may not have duplicates

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

mcpjam-inspector/client/src/hooks/use-server-state.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: ESLint configuration in --config is invalid:

  • Unexpected top-level property "__esModule".

    at ConfigValidator.validateConfigSchema (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2177:19)
    at ConfigArrayFactory._normalizeConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:19)
    at ConfigArrayFactory._loadConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:21)
    at ConfigArrayFactory.loadFile (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
    at createCLIConfigArray (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)
    at new CascadingConfigArrayFactory (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3735:29)
    at new CLIEngine (/soundcheck/node_modules/eslint/lib/cli-engine/cli-engine.js:617:36)
    at new ESLint (/soundcheck/node_modules/eslint/lib/eslint/eslint.js:430:27)
    at Object.execute (/soundcheck/node_modules/eslint/lib/cli.js:410:24)
    at async main (/soundcheck/node_modules/eslint/bin/eslint.js:152:22)

mcpjam-inspector/client/src/hooks/useProjects.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: ESLint configuration in --config is invalid:

  • Unexpected top-level property "__esModule".

    at ConfigValidator.validateConfigSchema (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2177:19)
    at ConfigArrayFactory._normalizeConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:19)
    at ConfigArrayFactory._loadConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:21)
    at ConfigArrayFactory.loadFile (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
    at createCLIConfigArray (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)
    at new CascadingConfigArrayFactory (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3735:29)
    at new CLIEngine (/soundcheck/node_modules/eslint/lib/cli-engine/cli-engine.js:617:36)
    at new ESLint (/soundcheck/node_modules/eslint/lib/eslint/eslint.js:430:27)
    at Object.execute (/soundcheck/node_modules/eslint/lib/cli.js:410:24)
    at async main (/soundcheck/node_modules/eslint/bin/eslint.js:152:22)

mcpjam-inspector/client/src/lib/__tests__/project-serialization.test.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: ESLint configuration in --config is invalid:

  • Unexpected top-level property "__esModule".

    at ConfigValidator.validateConfigSchema (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2177:19)
    at ConfigArrayFactory._normalizeConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:19)
    at ConfigArrayFactory._loadConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:21)
    at ConfigArrayFactory.loadFile (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
    at createCLIConfigArray (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)
    at new CascadingConfigArrayFactory (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3735:29)
    at new CLIEngine (/soundcheck/node_modules/eslint/lib/cli-engine/cli-engine.js:617:36)
    at new ESLint (/soundcheck/node_modules/eslint/lib/eslint/eslint.js:430:27)
    at Object.execute (/soundcheck/node_modules/eslint/lib/cli.js:410:24)
    at async main (/soundcheck/node_modules/eslint/bin/eslint.js:152:22)

  • 3 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
mcpjam-inspector/client/src/lib/oauth/profile.ts (1)

34-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Lookup arrays aren't compile-time exhaustive against the union types.

OAUTH_PROTOCOL_VERSIONS/OAUTH_REGISTRATION_STRATEGIES are typed as readonly OAuthProtocolVersion[] / readonly OAuthRegistrationStrategy[], which permits a subset of the union. If a new protocol version or strategy is later added to the union type but the array update is missed, the normalizer would silently treat a legitimate value as "unknown" and fall back to defaults — exactly the failure mode this PR fixes for other cases.

Deriving the arrays from a satisfies Record<Union, true> object would make the compiler flag a missing entry.

♻️ Suggested exhaustiveness guard
-const OAUTH_PROTOCOL_VERSIONS: readonly OAuthProtocolVersion[] = [
-  "2025-03-26",
-  "2025-06-18",
-  "2025-11-25",
-];
-
-const OAUTH_REGISTRATION_STRATEGIES: readonly OAuthRegistrationStrategy[] = [
-  "cimd",
-  "dcr",
-  "preregistered",
-];
+const OAUTH_PROTOCOL_VERSIONS = Object.keys({
+  "2025-03-26": true,
+  "2025-06-18": true,
+  "2025-11-25": true,
+} satisfies Record<OAuthProtocolVersion, true>) as OAuthProtocolVersion[];
+
+const OAUTH_REGISTRATION_STRATEGIES = Object.keys({
+  cimd: true,
+  dcr: true,
+  preregistered: true,
+} satisfies Record<OAuthRegistrationStrategy, true>) as OAuthRegistrationStrategy[];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcpjam-inspector/client/src/lib/oauth/profile.ts` around lines 34 - 63, The
lookup lists in normalizeOAuthProtocolVersion and
normalizeOAuthRegistrationStrategy are not guaranteed to stay exhaustive with
OAuthProtocolVersion and OAuthRegistrationStrategy, so a future union expansion
could be silently treated as unknown. Update the OAUTH_PROTOCOL_VERSIONS and
OAUTH_REGISTRATION_STRATEGIES definitions in profile.ts to be compiler-checked
against the full unions (for example by deriving them from an
exhaustiveness-checked source), and keep the existing normalizer functions using
those symbols so any missing union member causes a TypeScript error instead of
falling back to defaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@mcpjam-inspector/client/src/lib/oauth/profile.ts`:
- Around line 34-63: The lookup lists in normalizeOAuthProtocolVersion and
normalizeOAuthRegistrationStrategy are not guaranteed to stay exhaustive with
OAuthProtocolVersion and OAuthRegistrationStrategy, so a future union expansion
could be silently treated as unknown. Update the OAUTH_PROTOCOL_VERSIONS and
OAUTH_REGISTRATION_STRATEGIES definitions in profile.ts to be compiler-checked
against the full unions (for example by deriving them from an
exhaustiveness-checked source), and keep the existing normalizer functions using
those symbols so any missing union member causes a TypeScript error instead of
falling back to defaults.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 26e621fe-fe82-483e-83c8-52ef96c37fab

📥 Commits

Reviewing files that changed from the base of the PR and between 2baa8c5 and 9d9fcfc.

📒 Files selected for processing (6)
  • mcpjam-inspector/client/src/hooks/use-server-state.ts
  • mcpjam-inspector/client/src/hooks/useProjects.ts
  • mcpjam-inspector/client/src/lib/__tests__/project-serialization.test.ts
  • mcpjam-inspector/client/src/lib/oauth/profile.ts
  • mcpjam-inspector/client/src/lib/project-serialization.ts
  • mcpjam-inspector/client/src/state/__tests__/oauth-orchestrator.test.ts

@chelojimenez chelojimenez merged commit 8f27804 into main Jul 7, 2026
14 checks passed
@chelojimenez chelojimenez deleted the fix/oauth-profile-roundtrip branch July 7, 2026 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant