fix(site): auto-select MCP server after successful OAuth - #28155
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8cee56c1c2
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83894d4116
ℹ️ 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".
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
| useEffect(() => { | ||
| if (!mcpConnectingId || !mcpPopupRef.current) return; | ||
| if (!mcpConnectingId || !mcpAuthFlowRef.current) return; | ||
| const interval = setInterval(() => { | ||
| if (mcpPopupRef.current?.closed) { | ||
| if (mcpAuthFlowRef.current?.popup.closed) { | ||
| setMcpConnectingId(null); | ||
| mcpPopupRef.current = null; | ||
| } | ||
| }, 500); | ||
| return () => { | ||
| clearInterval(interval); | ||
| if (mcpPopupRef.current && !mcpPopupRef.current.closed) { | ||
| mcpPopupRef.current.close(); | ||
| mcpPopupRef.current = null; | ||
| const popup = mcpAuthFlowRef.current?.popup; | ||
| if (popup && !popup.closed) { | ||
| popup.close(); | ||
| mcpAuthFlowRef.current = null; | ||
| } | ||
| }; | ||
| }, [mcpConnectingId]); |
There was a problem hiding this comment.
Can I just ask the bot if the approach taken in this PR is definitely the correct modern React 19.2+ approach.
If the existing code makes it hard to write correct React, a small refactor is probably warranted. I'm just concerned about over-usage of useEffect and useRef. If it turns out it is absolutely warranted here then I'm fine but just make sure to fetch the latest React documentation to make an informed decision.
https://react.dev/llms.txt is a good start point
There was a problem hiding this comment.
Good prompt to double-check. I fetched the current React docs (react.dev, React 19.2) and audited every hook in this flow against them, and also ran the repo's compiler gate (site/scripts/check-compiler.mjs, which compiles AgentsPage/ with babel-plugin-react-compiler and fails on any bailout): 0 diagnostics.
What the docs say about this exact shape:
- A mount-once
window.addEventListener("message", ...)Effect that calls auseEffectEventhandler is the canonical pattern in the useEffectEvent reference ("Using an event listener with latest values"): subscribe once, and the Effect Event reads the latest props at message-arrival time instead of resubscribing on every selection change. - The
setIntervalpoll is the only way to observepopup.closed(there is no close event for a window.open popup), and Effects are the documented tool for synchronizing with an external system like a popup. - The
{popup, serverID}correlation is deliberately a ref (Referencing Values with Refs): mutable bookkeeping that never affects rendering, and it must survive the popup closing because coderd's callback page posts the completion message and then closes itself, so the close poll can win the race against the queued message.
So each useEffect/useRef is load-bearing rather than incidental, and none of the newer primitives fit (useSyncExternalStore is for reading external state, not one-shot authenticated events; actions don't model an indefinitely-pending popup).
That said, the docs do recommend not leaving raw Effects inline in a big component: "whenever you write an Effect, consider whether it would be clearer to also wrap it in a custom Hook" (Reusing Logic with Custom Hooks). I took that refactor in 386b8b7: the whole flow now lives in useMCPOAuthFlow (site/src/pages/AgentsPage/hooks/useMCPOAuthFlow.ts), which wraps the handler props in useEffectEvent exactly like the docs' useChatRoom example. AgentChatInput is left with a single declarative hook call and now contains fewer effects/refs than current main. Moving the code also surfaced a pre-existing stuck state that is fixed in the same commit: a blocked popup (window.open returning null) used to leave every Auth button disabled forever; the hook no longer enters the connecting state in that case (story-guarded, verified red-green).
Validated with tsc, biome, all 57 AgentChatInput stories, and lint:compiler.
Shux acted on @ibetitsmike's behalf.
62b6410 to
386b8b7
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 386b8b75da
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c079e1dbef
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1810f0d742
ℹ️ 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".
…ct story helper precondition
|
@codex review |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
When an MCP server requires OAuth, the chat MCP picker showed an "Auth" button, and after completing the popup flow the user still had to reopen the picker and flip the enable switch manually. Clicking Auth already expresses the intent to use the server, so the extra step was unnecessary.
This change auto-selects the server when its OAuth popup reports success. The flow lives in a
useMCPOAuthFlowhook (extracted fromAgentChatInputafter review feedback, following the React docs guidance on wrapping Effects in custom hooks): it tracks the flow it started as{popup, serverID}, and amcp-oauth2-completepostMessage reports success only when it comes from that popup and names that server.AgentChatInputthen adds the server to the selection only if it exists in the fetched list, is enabled, and is not already selected. The correlation deliberately survives popup close: coderd's callback page posts the message and then closes itself, so the close poll can observe the closed popup before the queued message is dispatched. Selection persistence is unchanged (parent callbacks on the chat page and create form store it as before), and failed or aborted auth flows post no message, so they cannot select anything. Moving the code also fixed a pre-existing stuck state: a blocked popup (window.openreturning null) no longer enters the connecting state that permanently disabled every Auth button.Covered by Storybook interaction stories: successful auto-enable through a real menu-open and Auth click with a mocked popup, auto-enable when the popup closes before the message is dispatched, no duplicate selection, unsolicited-message rejection (no Auth click), mismatched-server rejection (completion names a different server than the one authenticated), and no stuck connecting state when the popup is blocked. Each behavior story was verified red-green against the code it guards.
Dogfood UAT ran two rounds with a full OAuth round trip (real authorize, consent, callback, and PKCE token exchange against a local OAuth2 provider) on both the chat page and the create form, plus aborted-popup, forged-message, two-server isolation, manual toggle, force_on, and disconnect regression checks; all passed.