History

Polish MCP OAuth completion and connected UI

Rhys Sullivan committed 8 months ago

Commit 12386ed1 parent 008fe733 files · +46 −39Browse files

Unified · Split

3 files
  1. executor/apps/web/src/app/mcp/oauth/complete/page.tsx +38 −35
  2. executor/apps/web/src/components/tools/add-source-auth-panel.tsx +7 −3
  3. executor/apps/web/src/components/tools/add-source-dialog.tsx +1 −1
executor/apps/web/src/app/mcp/oauth/complete/page.tsx+38−35
@@ -1,6 +1,7 @@
11 "use client";
22
3−import { useEffect } from "react";
3+import { useEffect, useRef } from "react";
4+import { useQuery as useTanstackQuery } from "@tanstack/react-query";
45
56 type PopupMessage =
67 | {
@@ -28,45 +29,47 @@ function fallbackErrorMessage(error: unknown): PopupMessage { · 21 unmodified lines
2829 };
2930 }
3031
32+async function fetchPopupResult(): Promise<PopupMessage> {
33+ const response = await fetch("/mcp/oauth/result", {
34+ method: "GET",
35+ cache: "no-store",
36+ credentials: "same-origin",
37+ });
38+
39+ const data = await response.json() as PopupMessage;
40+ if (!data || data.type !== "executor:mcp-oauth-result") {
41+ throw new Error("Invalid OAuth completion payload");
42+ }
43+ return data;
44+}
45+
3146 export default function McpOAuthCompletePage() {
32− useEffect(() => {
33− let cancelled = false;
47+ const sentRef = useRef(false);
48+ const resultQuery = useTanstackQuery({
49+ queryKey: ["mcp-oauth-popup-result"],
50+ queryFn: fetchPopupResult,
51+ retry: false,
52+ staleTime: 0,
53+ });
3454
35− const finish = async () => {
36− let message: PopupMessage;
37− try {
38− const response = await fetch("/mcp/oauth/result", {
39− method: "GET",
40− cache: "no-store",
41− credentials: "same-origin",
42− });
43− const data = await response.json() as PopupMessage;
44− if (!data || data.type !== "executor:mcp-oauth-result") {
45− throw new Error("Invalid OAuth completion payload");
46− }
47− message = data;
48− } catch (error) {
49− message = fallbackErrorMessage(error);
50− }
55+ useEffect(() => {
56+ if (sentRef.current || resultQuery.isPending) {
57+ return;
58+ }
5159
52− if (cancelled) {
53− return;
54− }
60+ sentRef.current = true;
61+ const message = resultQuery.isSuccess
62+ ? resultQuery.data
63+ : fallbackErrorMessage(resultQuery.error);
5564
56− try {
57− if (window.opener) {
58− window.opener.postMessage(message, window.location.origin);
59− }
60− } finally {
61− window.close();
65+ try {
66+ if (window.opener) {
67+ window.opener.postMessage(message, window.location.origin);
6268 }
63− };
64−
65− void finish();
66− return () => {
67− cancelled = true;
68− };
69− }, []);
69+ } finally {
70+ window.close();
71+ }
72+ }, [resultQuery.data, resultQuery.error, resultQuery.isPending, resultQuery.isSuccess]);
7073
7174 return (
7275 <main className="min-h-screen flex items-center justify-center bg-background p-6">
executor/apps/web/src/components/tools/add-source-auth-panel.tsx+7−3
@@ -98,6 +98,7 @@ export function SourceAuthPanel({ · 97 unmodified lines
9898 }
9999
100100 const badge = inferredAuthBadge(inferredSpecAuth);
101+ const mcpBearerConnected = sourceType === "mcp" && authType === "bearer" && mcpOAuthConnected;
101102 return (
102103 <div className="space-y-3">
103104
@@ -179,7 +180,7 @@ export function SourceAuthPanel({ · 75 unmodified lines
179180 <div className="space-y-1.5">
180181 <div className="flex items-center gap-2">
181182 <Label className="text-xs text-muted-foreground">OAuth</Label>
182− {mcpOAuthConnected ? (
183+ {mcpBearerConnected ? (
183184 <Badge variant="outline" className="text-[10px] uppercase tracking-wider text-terminal-green">
184185 Connected
185186 </Badge>
@@ -193,8 +194,11 @@ export function SourceAuthPanel({ · 7 unmodified lines
193194 disabled={mcpOAuthBusy}
194195 onClick={onMcpOAuthConnect}
195196 >
196− {mcpOAuthBusy ? "Connecting..." : mcpOAuthConnected ? "Reconnect OAuth" : "Connect OAuth in popup"}
197+ {mcpOAuthBusy ? "Connecting..." : mcpBearerConnected ? "Reconnect OAuth" : "Connect OAuth in popup"}
197198 </Button>
199+ {mcpBearerConnected ? (
200+ <p className="text-[11px] text-muted-foreground">OAuth linked successfully.</p>
201+ ) : null}
198202 </div>
199203 ) : null}
200204
@@ -210,7 +214,7 @@ export function SourceAuthPanel({ · 9 unmodified lines
210214 </div>
211215 ) : null}
212216
213− {authType === "bearer" ? (
217+ {authType === "bearer" && !mcpBearerConnected ? (
214218 <div className="space-y-1.5">
215219 <Label className="text-xs text-muted-foreground flex items-center gap-1.5">
216220 <LockKeyhole className="h-3 w-3" />
executor/apps/web/src/components/tools/add-source-dialog.tsx+1−1
@@ -149,7 +149,7 @@ export function AddSourceDialog({ · 148 unmodified lines
149149 form.handleAuthTypeChange("bearer");
150150 }
151151 form.handleAuthFieldChange("tokenValue", result.accessToken);
152− toast.success("OAuth linked. Bearer token populated.");
152+ toast.success("OAuth linked successfully.");
153153 } catch (error) {
154154 toast.error(error instanceof Error ? error.message : "Failed to connect OAuth");
155155 } finally {