Skip to content

Refine hosted OAuth chat session flow#1936

Merged
chelojimenez merged 5 commits intomainfrom
codex/review-moonlit-zooming-teapot
Apr 26, 2026
Merged

Refine hosted OAuth chat session flow#1936
chelojimenez merged 5 commits intomainfrom
codex/review-moonlit-zooming-teapot

Conversation

@chelojimenez
Copy link
Copy Markdown
Contributor

@chelojimenez chelojimenez commented Apr 26, 2026

Summary

  • Add a hosted OAuth gate to keep chat sessions aligned with server auth state
  • Update chat session, chat tab, and hosted chat page behavior to sync history and session state
  • Expand tests around hosted OAuth, history sync, and chatbox editing flows

Testing

  • Unit tests updated for hosted OAuth, session sync, and chat UI behavior
  • Not run (not requested)

Note

Medium Risk
Changes hosted OAuth completion and token handling for chatbox/guest flows, which can affect login/authorization success paths and session recovery behavior.

Overview
Refines hosted OAuth handling so chatbox callbacks can be completed via completeHostedOAuthCallback even when the viewer is unauthenticated, using a guest bearer token (and emitting a clear “guest session expired” error when unavailable).

Removes reliance on locally stored OAuth access tokens for hosted chatbox sessions (editor/builder/chat page) by treating chatbox contexts as vault-backed: useHostedOAuthGate now validates using hosted context (workspaceId, serverId, chatboxToken) and ChatTabV2 suppresses hostedOAuthTokens whenever a chatboxToken is present.

Updates and expands unit tests to cover guest chatbox callback completion, missing guest bearer handling, chatbox-token validation context passed into validateHostedServer, and the new “no oauthTokens in chatbox contexts” behavior.

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

@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Apr 26, 2026
@chelojimenez
Copy link
Copy Markdown
Contributor Author

chelojimenez commented Apr 26, 2026

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.

@dosubot dosubot Bot added the enhancement New feature or request label Apr 26, 2026
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c099d68. Configure here.

Comment thread mcpjam-inspector/client/src/App.tsx
Comment thread mcpjam-inspector/client/src/App.tsx
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c099d689aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 101 to 103
} else if (isVaultBacked) {
status = existing?.status === "ready" ? "ready" : "verifying";
errorMessage = null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep chatbox OAuth servers in needs_auth before first consent

Switching the fallback state to verifying whenever isVaultBacked is true causes guest chatbox sessions (which always have chatboxToken) to skip the initial needs_auth state even before any OAuth consent has happened. That triggers immediate validation attempts, then error states on first load, and also prevents the welcome-first OAuth onboarding path from rendering because useChatboxHostIntroGate only treats all-needs_auth as first-consent idle. In practice, first-time OAuth chatboxes can jump straight to error/“Authorize again” instead of the expected initial consent flow.

Useful? React with 👍 / 👎.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Walkthrough

Hosted OAuth callback handling now detects guest chatbox session callbacks and, for those, fetches a guest bearer token and forwards it as Authorization: Bearer <token> to completeHostedOAuthCallback; if no guest token is available, it records a guest-expired failure. The hosted OAuth gate introduces isVaultBacked (true when authenticated or when chatboxToken exists) and derives hosted validation context (workspaceId, serverId, chatboxToken, accessScope). completeHostedOAuthCallback and hosted progress polling accept an optional authorizationHeader. Numerous formatting-only changes were applied.


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
mcpjam-inspector/client/src/components/hosted/ChatboxChatPage.tsx (1)

66-80: ⚠️ Potential issue | 🟠 Major

Don't silently downgrade signed-in viewers to guest auth.

getHostedBearerHeader() falls back to a guest bearer on any getAccessToken() failure. That means a transient WorkOS/token-refresh error can bootstrap the chatbox as a guest session, which is likely to flip member-only flows into misleading “guest blocked” or “access denied” states even though the viewer is signed in.

Possible fix
-async function getHostedBearerHeader(
-  getAccessToken: () => Promise<string | undefined | null>
-): Promise<string | null> {
+async function getHostedBearerHeader(
+  getAccessToken: () => Promise<string | undefined | null>,
+  isAuthenticated: boolean
+): Promise<string | null> {
   try {
     const workOsToken = await getAccessToken();
     if (workOsToken) {
       return `Bearer ${workOsToken}`;
     }
   } catch {
-    // Fall through to guest auth.
+    if (isAuthenticated) {
+      throw new Error("Unable to refresh your sign-in session.");
+    }
   }
 
   const guestToken = await getGuestBearerToken();
   return guestToken ? `Bearer ${guestToken}` : null;
 }
-const authorization = await getHostedBearerHeader(getAccessToken);
+const authorization = await getHostedBearerHeader(
+  getAccessToken,
+  isAuthenticated
+);

Also applies to: 444-448

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/components/hosted/ChatboxChatPage.tsx` around
lines 66 - 80, getHostedBearerHeader currently treats any exception from
getAccessToken as if the user is unauthenticated and falls back to guest auth;
change it so only an explicit falsy result from getAccessToken triggers guest
fallback. Specifically, remove or rethrow the try/catch around the await
getAccessToken() (or in the catch rethrow the error) so exceptions propagate
instead of falling through to getGuestBearerToken(), and keep the existing logic
that returns `Bearer ${workOsToken}` when getAccessToken() resolves to a token;
apply the same change to the related block at lines referenced (444-448) so
transient token refresh errors do not silently downgrade signed-in viewers to
guest sessions.
mcpjam-inspector/client/src/App.tsx (1)

499-552: ⚠️ Potential issue | 🟠 Major

Guard against re-spending the single-use OAuth code when isAuthenticated transitions during callback processing.

When this effect re-runs due to an isAuthenticated change, the cleanup function at line 549 only sets cancelled = true, which prevents the result handlers (finalizeHostedOAuth, setHostedOAuthHandling) from executing. However, the in-flight network request itself continues unaborted. If Convex auth resolves while completeHostedOAuthCallback or handleOAuthCallback is mid-request, the effect re-runs, extracts the same code from the URL params, and submits it again. Authorization servers reject reused codes, so the second request fails—the second run's .catch() fires with "Authorization could not be completed," even though the first request succeeded. The first run's success is silently discarded by the cancelled guard.

This is a realistic scenario for hosted chatbox guests who authenticate during the callback flow. Both callback functions lack signal support and internal code-reuse guards, making them vulnerable.

A small in-flight sentinel (checking whether the current code is already being exchanged) or passing an AbortController signal would prevent duplicate submissions.

🛡️ Example in-flight guard
+  const oauthCallbackInFlightRef = useRef<string | null>(null);
   useEffect(() => {
     const callbackContext = getHostedOAuthCallbackContext();
     if (!callbackContext || callbackContext.surface === "workspace") {
       setHostedOAuthHandling(false);
       return;
     }

     const urlParams = new URLSearchParams(window.location.search);
     const code = urlParams.get("code");
     const error = urlParams.get("error");
     const state = urlParams.get("state");

+    if (code && oauthCallbackInFlightRef.current === code) {
+      // Another invocation is already exchanging this code; let it finish.
+      return;
+    }
+    if (code) oauthCallbackInFlightRef.current = code;
+
     let cancelled = false;
     // ...
     completeCallback.finally(() => {
+      oauthCallbackInFlightRef.current = null;
       if (!cancelled) setHostedOAuthHandling(false);
     });
   }, [isAuthenticated]);
🧹 Nitpick comments (3)
mcpjam-inspector/client/src/App.tsx (1)

505-518: Optional: lift the guest-bearer block out of the IIFE for readability.

The (async () => { ... })() invocation works, but inlining an async IIFE inside a ternary makes the control flow harder to scan than it needs to be. A small named helper would keep completeCallback a flat assignment and put the "fetch guest bearer, then complete" sequence in one place — easier to add tests around, easier to add the in-flight guard suggested separately, and one fewer Promise wrapper.

♻️ Sketch
-    const completeCallback = shouldUseHostedCompletion
-      ? (async () => {
-          const guestAuthorizationHeader =
-            !isAuthenticated && callbackContext.chatboxToken
-              ? await getGuestBearerToken()
-              : null;
-          return completeHostedOAuthCallback(callbackContext, code, {
-            callbackState: state,
-            onTraceUpdate: handleLiveOAuthTrace,
-            authorizationHeader: guestAuthorizationHeader
-              ? `Bearer ${guestAuthorizationHeader}`
-              : undefined,
-          });
-        })()
-      : handleOAuthCallback(code, {
-          onTraceUpdate: handleLiveOAuthTrace,
-        });
+    const runHostedCompletion = async () => {
+      const guestBearer =
+        !isAuthenticated && callbackContext.chatboxToken
+          ? await getGuestBearerToken()
+          : null;
+      return completeHostedOAuthCallback(callbackContext, code, {
+        callbackState: state,
+        onTraceUpdate: handleLiveOAuthTrace,
+        authorizationHeader: guestBearer ? `Bearer ${guestBearer}` : undefined,
+      });
+    };
+
+    const completeCallback = shouldUseHostedCompletion
+      ? runHostedCompletion()
+      : handleOAuthCallback(code, { onTraceUpdate: handleLiveOAuthTrace });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/App.tsx` around lines 505 - 518, The inline async
IIFE assigned to completeCallback makes control flow hard to read; extract the
guest-bearer fetch + hosted completion logic into a named async helper (e.g.,
fetchGuestAndComplete or completeWithGuestIfNeeded) and then set
completeCallback = shouldUseHostedCompletion ?
fetchGuestAndComplete(callbackContext, code, state) : ...; inside the helper,
use isAuthenticated and callbackContext.chatboxToken to call
getGuestBearerToken() when needed, build the authorizationHeader string, and
call completeHostedOAuthCallback(callbackContext, code, { callbackState: state,
onTraceUpdate: handleLiveOAuthTrace, authorizationHeader }); this keeps
completeCallback a flat assignment and makes the sequence testable and readable.
mcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.ts (1)

219-242: isVaultBacked cleanly unifies authenticated and chatbox flows.

Folding isAuthenticated || !!chatboxToken into a single derived flag and threading it through buildHostedOAuthStateMap, the polling effects, and the deps array reads well. One tiny redundancy worth noting (no fix required): authorizeServer lists both isAuthenticated and isVaultBacked in its deps (Line 482‑489) — since the latter is derived from the former plus chatboxToken (also listed), isAuthenticated is effectively dead weight in that array. Harmless, just a touch of noise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.ts` around
lines 219 - 242, The derived flag isVaultBacked (isAuthenticated ||
!!chatboxToken) makes isAuthenticated redundant in authorizeServer's dependency
list; update the authorizeServer callback to remove isAuthenticated from its
deps and rely on isVaultBacked and chatboxToken (and any other true inputs)
instead, ensuring authorizeServer still references buildHostedOAuthStateMap /
oauthStateByServerIdRef / processingServerIdsRef as before so behavior doesn't
change.
mcpjam-inspector/client/src/lib/apis/web/servers-api.ts (1)

34-57: Recommended: tighten the contract so serverNameOrId and hostedContext.serverId cannot drift apart.

When hostedContext is supplied, serverNameOrId is silently discarded — the request keys off hostedContext.serverId instead. Today's only caller (validateWithRetry in use-hosted-oauth-gate.ts) passes the same value through both channels, but a future caller could pass mismatched arguments and have the positional one quietly ignored. Consider either dropping the positional parameter when hostedContext is present (overloads) or asserting equality, so the API can't lie to its callers.

♻️ One possible shape
 export async function validateHostedServer(
   serverNameOrId: string,
   oauthAccessToken?: string,
   clientCapabilities?: Record<string, unknown>,
   hostedContext?: HostedServerValidateContext
 ): Promise<HostedServerValidateResponse> {
+  if (hostedContext && hostedContext.serverId !== serverNameOrId) {
+    // Dev-time guard: keeps the positional arg and the context aligned
+    // so callers can't silently drop one of them.
+    console.warn(
+      "[validateHostedServer] serverNameOrId does not match hostedContext.serverId",
+      { serverNameOrId, contextServerId: hostedContext.serverId }
+    );
+  }
   const request: Record<string, unknown> = hostedContext
     ? {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/lib/apis/web/servers-api.ts` around lines 34 -
57, The function validateHostedServer currently ignores the positional
serverNameOrId when hostedContext is provided, which can allow callers to pass
mismatched values; update validateHostedServer to enforce consistency by
asserting that when hostedContext is present its serverId equals serverNameOrId
(throw a clear error if not) or remove the positional parameter in that branch
(use overloads) so callers can't supply conflicting values; locate the logic in
validateHostedServer (and adjust callers such as validateWithRetry in
use-hosted-oauth-gate.ts if you change the signature) and ensure either an
equality check between serverNameOrId and hostedContext.serverId or a
single-source API so the request is built only from the validated/authoritative
value (hostedContext.serverId or buildHostedServerRequest).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@mcpjam-inspector/client/src/components/hosted/ChatboxChatPage.tsx`:
- Around line 66-80: getHostedBearerHeader currently treats any exception from
getAccessToken as if the user is unauthenticated and falls back to guest auth;
change it so only an explicit falsy result from getAccessToken triggers guest
fallback. Specifically, remove or rethrow the try/catch around the await
getAccessToken() (or in the catch rethrow the error) so exceptions propagate
instead of falling through to getGuestBearerToken(), and keep the existing logic
that returns `Bearer ${workOsToken}` when getAccessToken() resolves to a token;
apply the same change to the related block at lines referenced (444-448) so
transient token refresh errors do not silently downgrade signed-in viewers to
guest sessions.

---

Nitpick comments:
In `@mcpjam-inspector/client/src/App.tsx`:
- Around line 505-518: The inline async IIFE assigned to completeCallback makes
control flow hard to read; extract the guest-bearer fetch + hosted completion
logic into a named async helper (e.g., fetchGuestAndComplete or
completeWithGuestIfNeeded) and then set completeCallback =
shouldUseHostedCompletion ? fetchGuestAndComplete(callbackContext, code, state)
: ...; inside the helper, use isAuthenticated and callbackContext.chatboxToken
to call getGuestBearerToken() when needed, build the authorizationHeader string,
and call completeHostedOAuthCallback(callbackContext, code, { callbackState:
state, onTraceUpdate: handleLiveOAuthTrace, authorizationHeader }); this keeps
completeCallback a flat assignment and makes the sequence testable and readable.

In `@mcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.ts`:
- Around line 219-242: The derived flag isVaultBacked (isAuthenticated ||
!!chatboxToken) makes isAuthenticated redundant in authorizeServer's dependency
list; update the authorizeServer callback to remove isAuthenticated from its
deps and rely on isVaultBacked and chatboxToken (and any other true inputs)
instead, ensuring authorizeServer still references buildHostedOAuthStateMap /
oauthStateByServerIdRef / processingServerIdsRef as before so behavior doesn't
change.

In `@mcpjam-inspector/client/src/lib/apis/web/servers-api.ts`:
- Around line 34-57: The function validateHostedServer currently ignores the
positional serverNameOrId when hostedContext is provided, which can allow
callers to pass mismatched values; update validateHostedServer to enforce
consistency by asserting that when hostedContext is present its serverId equals
serverNameOrId (throw a clear error if not) or remove the positional parameter
in that branch (use overloads) so callers can't supply conflicting values;
locate the logic in validateHostedServer (and adjust callers such as
validateWithRetry in use-hosted-oauth-gate.ts if you change the signature) and
ensure either an equality check between serverNameOrId and
hostedContext.serverId or a single-source API so the request is built only from
the validated/authoritative value (hostedContext.serverId or
buildHostedServerRequest).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4b9a691e-bbff-4df9-95eb-191f50a8d389

📥 Commits

Reviewing files that changed from the base of the PR and between 538007c and c099d68.

📒 Files selected for processing (14)
  • mcpjam-inspector/client/src/App.tsx
  • mcpjam-inspector/client/src/__tests__/App.hosted-oauth.test.tsx
  • mcpjam-inspector/client/src/components/ChatTabV2.tsx
  • mcpjam-inspector/client/src/components/__tests__/ChatTabV2.history-sync.test.tsx
  • mcpjam-inspector/client/src/components/chatboxes/ChatboxEditor.tsx
  • mcpjam-inspector/client/src/components/chatboxes/__tests__/ChatboxEditor.test.tsx
  • mcpjam-inspector/client/src/components/chatboxes/builder/ChatboxBuilderView.tsx
  • mcpjam-inspector/client/src/components/hosted/ChatboxChatPage.tsx
  • mcpjam-inspector/client/src/components/hosted/__tests__/ChatboxChatPage.test.tsx
  • mcpjam-inspector/client/src/hooks/__tests__/use-chat-session.hosted.test.tsx
  • mcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.ts
  • mcpjam-inspector/client/src/hooks/use-chat-session.ts
  • mcpjam-inspector/client/src/lib/apis/web/servers-api.ts
  • mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
mcpjam-inspector/client/src/App.tsx (2)

509-532: Guard the awaited bearer fetch against unexpected throws.

getGuestBearerToken currently swallows fetch failures and resolves to null, so the null branch covers most realities. Still, a stray localStorage.getItem throw (e.g., disabled storage in private modes) could escape and slip into the outer .catch, where it would surface as the generic "Authorization could not be completed. Try again." rather than the curated guest-expired copy. A small try/catch around the await preserves the precise UX for that fringe path.

🛡️ Optional defensive wrap
     const completeCallback = shouldUseHostedCompletion
       ? (async () => {
           let authorizationHeader: string | undefined;
           if (isGuestChatboxSessionCallback) {
-            const guestBearerToken = await getGuestBearerToken();
+            let guestBearerToken: string | null = null;
+            try {
+              guestBearerToken = await getGuestBearerToken();
+            } catch {
+              guestBearerToken = null;
+            }
             if (!guestBearerToken) {
               return {
                 success: false,
                 error:
                   "Your guest session expired. Reopen the chatbox link and try again.",
               };
             }
             authorizationHeader = `Bearer ${guestBearerToken}`;
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/App.tsx` around lines 509 - 532, The guest-token
fetch in the completeCallback branch can throw (e.g., localStorage access), so
wrap the await getGuestBearerToken() call in a try/catch inside the
isGuestChatboxSessionCallback branch (within completeCallback) and treat any
thrown error as a missing token: set guestBearerToken to null or directly return
the same { success: false, error: "Your guest session expired. Reopen the
chatbox link and try again." } response so the UX remains the curated
guest-expired message; keep the rest of the authorizationHeader logic and
subsequent call to completeHostedOAuthCallback unchanged.

514-520: Add a test for guest session error preservation. The sanitizer will pass "Your guest session expired. Reopen the chatbox link and try again." through verbatim—it matches no substitution patterns (lines 127–167). The existing test "preserves already-safe plain-language errors" validates this behavior generically. A dedicated test case would document explicitly that this message survives intact and guard against future regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/App.tsx` around lines 514 - 520, Add a dedicated
unit test that verifies the guest session error string "Your guest session
expired. Reopen the chatbox link and try again." is preserved verbatim by the
sanitizer; locate the sanitizer usage (the function tested by the existing
"preserves already-safe plain-language errors" test) and add a new test case
that passes this exact message through the sanitizer and asserts the output
equals the original string so future changes to substitution patterns cannot
alter it.
mcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.ts (1)

499-507: Redundant but harmless dependency entry.

Since isVaultBacked = isAuthenticated || !!chatboxToken, listing both isAuthenticated and isVaultBacked in the useCallback deps is redundant. Not a bug — React will simply re-evaluate equivalently — but trimming isAuthenticated (it isn't read directly inside authorizeServer outside the marker accessScope branch, which already uses it) would reduce noise. Optional polish.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.ts` around
lines 499 - 507, The dependency list for the useCallback that defines
authorizeServer includes a redundant isAuthenticated entry because isVaultBacked
is derived as isAuthenticated || !!chatboxToken; remove isAuthenticated from the
dependency array so the deps become [isVaultBacked, pendingKey, chatboxToken,
shareToken, surface, workspaceId], leaving authorizeServer, isVaultBacked,
pendingKey, chatboxToken, shareToken, surface, and workspaceId referenced as
before to preserve correct re-evaluation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@mcpjam-inspector/client/src/App.tsx`:
- Around line 509-532: The guest-token fetch in the completeCallback branch can
throw (e.g., localStorage access), so wrap the await getGuestBearerToken() call
in a try/catch inside the isGuestChatboxSessionCallback branch (within
completeCallback) and treat any thrown error as a missing token: set
guestBearerToken to null or directly return the same { success: false, error:
"Your guest session expired. Reopen the chatbox link and try again." } response
so the UX remains the curated guest-expired message; keep the rest of the
authorizationHeader logic and subsequent call to completeHostedOAuthCallback
unchanged.
- Around line 514-520: Add a dedicated unit test that verifies the guest session
error string "Your guest session expired. Reopen the chatbox link and try
again." is preserved verbatim by the sanitizer; locate the sanitizer usage (the
function tested by the existing "preserves already-safe plain-language errors"
test) and add a new test case that passes this exact message through the
sanitizer and asserts the output equals the original string so future changes to
substitution patterns cannot alter it.

In `@mcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.ts`:
- Around line 499-507: The dependency list for the useCallback that defines
authorizeServer includes a redundant isAuthenticated entry because isVaultBacked
is derived as isAuthenticated || !!chatboxToken; remove isAuthenticated from the
dependency array so the deps become [isVaultBacked, pendingKey, chatboxToken,
shareToken, surface, workspaceId], leaving authorizeServer, isVaultBacked,
pendingKey, chatboxToken, shareToken, surface, and workspaceId referenced as
before to preserve correct re-evaluation behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 803f96a9-ece4-4a82-9853-51f052b86f2e

📥 Commits

Reviewing files that changed from the base of the PR and between c099d68 and ad02595.

📒 Files selected for processing (5)
  • mcpjam-inspector/client/src/App.tsx
  • mcpjam-inspector/client/src/__tests__/App.hosted-oauth.test.tsx
  • mcpjam-inspector/client/src/components/hosted/ChatboxChatPage.tsx
  • mcpjam-inspector/client/src/components/hosted/__tests__/ChatboxChatPage.test.tsx
  • mcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • mcpjam-inspector/client/src/components/hosted/ChatboxChatPage.tsx

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad02595b03

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +501 to +507
const isGuestChatboxSessionCallback =
!isAuthenticated &&
!!callbackContext.chatboxToken &&
!!callbackContext.sessionId;
const shouldUseHostedCompletion =
hasHostedServerContext &&
(isAuthenticated || isGuestChatboxSessionCallback);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle guest chatbox callbacks without hosted session context

This branch now requires callbackContext.sessionId (and chatboxToken) before using completeHostedOAuthCallback, so guest chatbox callbacks that arrive after the hosted pending marker expires (10-minute TTL in hosted-oauth-callback) fall back to handleOAuthCallback. In this commit, chatbox chat requests no longer send browser oauthTokens when chatboxToken is present, so a local-only callback completion cannot satisfy later vault validation and users can get stuck in an authorize-again loop after a long consent flow. Guest chatbox callbacks should still use hosted completion when possible, even if the session marker is missing.

Useful? React with 👍 / 👎.

…zooming-teapot

# Conflicts:
#	mcpjam-inspector/client/src/components/hosted/ChatboxChatPage.tsx
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Apr 26, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 26, 2026

Internal preview

Preview URL: https://mcp-inspector-pr-1936.up.railway.app
Deployed commit: 94f11c6
PR head commit: 54ea6f1
Backend target: staging fallback.
Health: ❌ Convex unreachable — see upsert-preview job logs (staging may need convex deploy)
Access is employee-only in non-production environments.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 349d60cf45

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

[servers]
);
const isVaultBacked = isAuthenticated || !!chatboxToken;
const verifyVaultCredentialOnLoad = isAuthenticated;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Verify guest chatbox vault credentials on initial load

By setting verifyVaultCredentialOnLoad to isAuthenticated, guest chatbox sessions (chatboxToken present, not signed in) always initialize OAuth servers as needs_auth unless there is an active resume marker, even when a valid OAuth credential already exists in the hosted vault. After a successful guest OAuth callback, reloading the chatbox page will therefore block the composer and show authorize-again UI every time, because the gate never attempts a validation pass to transition back to ready.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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/components/hosted/__tests__/SharedServerChatPage.test.tsx (1)

256-262: Signature pin reads cleanly — a touch brittle, but defensible.

The assertion faithfully mirrors the widened validateHostedServer(serverId, context?, options?, …) shape introduced elsewhere in this PR, and the explicit undefined trio doubles as living documentation of the new signature. The only quiet concern: every future positional arg added to validateHostedServer will now ripple through this expectation. If you'd rather decouple the test from arity creep, a focused match works just as well:

♻️ Optional: assert only what this test cares about
-    expect(mockValidateHostedServer).toHaveBeenCalledWith(
-      "srv_asana",
-      undefined,
-      undefined,
-      undefined
-    );
-    expect(mockValidateHostedServer).toHaveBeenCalledTimes(1);
+    expect(mockValidateHostedServer).toHaveBeenCalledTimes(1);
+    expect(mockValidateHostedServer).toHaveBeenNthCalledWith(1, "srv_asana");

Note: toHaveBeenNthCalledWith in Vitest, like Jest, treats trailing undefined arguments as matching, so the shorter form still guards the meaningful contract — that the guest/shared path invokes validation without a hosted context, options, or auth header.

Happy to leave as-is if you prefer the explicit signature snapshot.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@mcpjam-inspector/client/src/components/hosted/__tests__/SharedServerChatPage.test.tsx`
around lines 256 - 262, The test currently asserts the full positional signature
of validateHostedServer by calling
expect(mockValidateHostedServer).toHaveBeenCalledWith("srv_asana", undefined,
undefined, undefined), which is brittle to future added parameters; replace that
assertion with a focused check that only verifies the meaningful argument(s)
(e.g., use expect(mockValidateHostedServer).toHaveBeenNthCalledWith(1,
"srv_asana") or an equivalent one-arg assertion) so the test continues to
confirm the guest/shared path calls validateHostedServer with the serverId
without coupling to extra positional params.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@mcpjam-inspector/client/src/components/hosted/__tests__/SharedServerChatPage.test.tsx`:
- Around line 256-262: The test currently asserts the full positional signature
of validateHostedServer by calling
expect(mockValidateHostedServer).toHaveBeenCalledWith("srv_asana", undefined,
undefined, undefined), which is brittle to future added parameters; replace that
assertion with a focused check that only verifies the meaningful argument(s)
(e.g., use expect(mockValidateHostedServer).toHaveBeenNthCalledWith(1,
"srv_asana") or an equivalent one-arg assertion) so the test continues to
confirm the guest/shared path calls validateHostedServer with the serverId
without coupling to extra positional params.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4b424623-870c-4487-b494-babf02d3cd0d

📥 Commits

Reviewing files that changed from the base of the PR and between ad02595 and 06e0404.

📒 Files selected for processing (1)
  • mcpjam-inspector/client/src/components/hosted/__tests__/SharedServerChatPage.test.tsx

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

https://github.com/MCPJam/inspector/blob/54ea6f1b00e1b81fcf3ecb773fd645e6b5274f74/mcpjam-inspector/mcpjam-inspector/client/src/App.tsx#L505-L507
P2 Badge Avoid local callback fallback when hosted context has expired

If OAuth consent takes longer than the hosted pending TTL (10 minutes), getHostedOAuthCallbackContext reconstructs chatbox callbacks without workspaceId/serverId; this branch then forces handleOAuthCallback even for authenticated users. In this commit chatbox requests no longer include browser oauthTokens (use-chat-session suppresses them when chatboxToken is present), so a local-only completion cannot restore vault credentials and users are redirected back into an immediate re-authorize flow after callback. This should stay on hosted completion (or surface a hard resume-expired error) instead of silently falling back.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chelojimenez chelojimenez merged commit bd0bd77 into main Apr 26, 2026
12 checks passed
@chelojimenez chelojimenez deleted the codex/review-moonlit-zooming-teapot branch April 26, 2026 02:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant