Skip to content

Commit 5e8e78c

Browse files
authored
Add Auth debugger tab (#355)
* wip auth debugger * cleanup types and validation * more cleanup * draft test * wip clean up some * rm toasts * consolidate state management * prettier * hoist state up to App * working with quick and guided * sort out displaying debugger * prettier * cleanup types * fix tests * cleanup comment * prettier * fixup types in tests * prettier * refactor debug to avoid toasting * callback shuffling * linting * types * rm toast in test * bump typescript sdk version to 0.11.2 for scope parameter passing * use proper scope handling * test scope parameter passing * move functions and s/sseUrl/serverUrl/ * extract status message into component * refactor progress and steps into components * fix test * rename quick handler * one less click * last step complete * add state machine * test and types
1 parent ad39ec2 commit 5e8e78c

File tree

11 files changed

+1391
-13
lines changed

11 files changed

+1391
-13
lines changed

client/src/App.tsx

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import {
1717
Tool,
1818
LoggingLevel,
1919
} from "@modelcontextprotocol/sdk/types.js";
20+
import { OAuthTokensSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
21+
import { SESSION_KEYS, getServerSpecificKey } from "./lib/constants";
22+
import { AuthDebuggerState } from "./lib/auth-types";
2023
import React, {
2124
Suspense,
2225
useCallback,
@@ -28,18 +31,21 @@ import { useConnection } from "./lib/hooks/useConnection";
2831
import { useDraggablePane } from "./lib/hooks/useDraggablePane";
2932
import { StdErrNotification } from "./lib/notificationTypes";
3033

31-
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
34+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
35+
import { Button } from "@/components/ui/button";
3236
import {
3337
Bell,
3438
Files,
3539
FolderTree,
3640
Hammer,
3741
Hash,
42+
Key,
3843
MessageSquare,
3944
} from "lucide-react";
4045

4146
import { z } from "zod";
4247
import "./App.css";
48+
import AuthDebugger from "./components/AuthDebugger";
4349
import ConsoleTab from "./components/ConsoleTab";
4450
import HistoryAndNotifications from "./components/History";
4551
import PingTab from "./components/PingTab";
@@ -111,6 +117,27 @@ const App = () => {
111117
}
112118
>
113119
>([]);
120+
const [isAuthDebuggerVisible, setIsAuthDebuggerVisible] = useState(false);
121+
122+
// Auth debugger state
123+
const [authState, setAuthState] = useState<AuthDebuggerState>({
124+
isInitiatingAuth: false,
125+
oauthTokens: null,
126+
loading: true,
127+
oauthStep: "metadata_discovery",
128+
oauthMetadata: null,
129+
oauthClientInfo: null,
130+
authorizationUrl: null,
131+
authorizationCode: "",
132+
latestError: null,
133+
statusMessage: null,
134+
validationError: null,
135+
});
136+
137+
// Helper function to update specific auth state properties
138+
const updateAuthState = (updates: Partial<AuthDebuggerState>) => {
139+
setAuthState((prev) => ({ ...prev, ...updates }));
140+
};
114141
const nextRequestId = useRef(0);
115142
const rootsRef = useRef<Root[]>([]);
116143

@@ -208,11 +235,64 @@ const App = () => {
208235
(serverUrl: string) => {
209236
setSseUrl(serverUrl);
210237
setTransportType("sse");
238+
setIsAuthDebuggerVisible(false);
211239
void connectMcpServer();
212240
},
213241
[connectMcpServer],
214242
);
215243

244+
// Update OAuth debug state during debug callback
245+
const onOAuthDebugConnect = useCallback(
246+
({
247+
authorizationCode,
248+
errorMsg,
249+
}: {
250+
authorizationCode?: string;
251+
errorMsg?: string;
252+
}) => {
253+
setIsAuthDebuggerVisible(true);
254+
if (authorizationCode) {
255+
updateAuthState({
256+
authorizationCode,
257+
oauthStep: "token_request",
258+
});
259+
}
260+
if (errorMsg) {
261+
updateAuthState({
262+
latestError: new Error(errorMsg),
263+
});
264+
}
265+
},
266+
[],
267+
);
268+
269+
// Load OAuth tokens when sseUrl changes
270+
useEffect(() => {
271+
const loadOAuthTokens = async () => {
272+
try {
273+
if (sseUrl) {
274+
const key = getServerSpecificKey(SESSION_KEYS.TOKENS, sseUrl);
275+
const tokens = sessionStorage.getItem(key);
276+
if (tokens) {
277+
const parsedTokens = await OAuthTokensSchema.parseAsync(
278+
JSON.parse(tokens),
279+
);
280+
updateAuthState({
281+
oauthTokens: parsedTokens,
282+
oauthStep: "complete",
283+
});
284+
}
285+
}
286+
} catch (error) {
287+
console.error("Error loading OAuth tokens:", error);
288+
} finally {
289+
updateAuthState({ loading: false });
290+
}
291+
};
292+
293+
loadOAuthTokens();
294+
}, [sseUrl]);
295+
216296
useEffect(() => {
217297
fetch(`${getMCPProxyAddress(config)}/config`)
218298
.then((response) => response.json())
@@ -446,6 +526,19 @@ const App = () => {
446526
setStdErrNotifications([]);
447527
};
448528

529+
// Helper component for rendering the AuthDebugger
530+
const AuthDebuggerWrapper = () => (
531+
<TabsContent value="auth">
532+
<AuthDebugger
533+
serverUrl={sseUrl}
534+
onBack={() => setIsAuthDebuggerVisible(false)}
535+
authState={authState}
536+
updateAuthState={updateAuthState}
537+
/>
538+
</TabsContent>
539+
);
540+
541+
// Helper function to render OAuth callback components
449542
if (window.location.pathname === "/oauth/callback") {
450543
const OAuthCallback = React.lazy(
451544
() => import("./components/OAuthCallback"),
@@ -457,6 +550,17 @@ const App = () => {
457550
);
458551
}
459552

553+
if (window.location.pathname === "/oauth/callback/debug") {
554+
const OAuthDebugCallback = React.lazy(
555+
() => import("./components/OAuthDebugCallback"),
556+
);
557+
return (
558+
<Suspense fallback={<div>Loading...</div>}>
559+
<OAuthDebugCallback onConnect={onOAuthDebugConnect} />
560+
</Suspense>
561+
);
562+
}
563+
460564
return (
461565
<div className="flex h-screen bg-background">
462566
<Sidebar
@@ -544,6 +648,10 @@ const App = () => {
544648
<FolderTree className="w-4 h-4 mr-2" />
545649
Roots
546650
</TabsTrigger>
651+
<TabsTrigger value="auth">
652+
<Key className="w-4 h-4 mr-2" />
653+
Auth
654+
</TabsTrigger>
547655
</TabsList>
548656

549657
<div className="w-full">
@@ -689,15 +797,36 @@ const App = () => {
689797
setRoots={setRoots}
690798
onRootsChange={handleRootsChange}
691799
/>
800+
<AuthDebuggerWrapper />
692801
</>
693802
)}
694803
</div>
695804
</Tabs>
805+
) : isAuthDebuggerVisible ? (
806+
<Tabs
807+
defaultValue={"auth"}
808+
className="w-full p-4"
809+
onValueChange={(value) => (window.location.hash = value)}
810+
>
811+
<AuthDebuggerWrapper />
812+
</Tabs>
696813
) : (
697-
<div className="flex items-center justify-center h-full">
814+
<div className="flex flex-col items-center justify-center h-full gap-4">
698815
<p className="text-lg text-gray-500">
699816
Connect to an MCP server to start inspecting
700817
</p>
818+
<div className="flex items-center gap-2">
819+
<p className="text-sm text-muted-foreground">
820+
Need to configure authentication?
821+
</p>
822+
<Button
823+
variant="outline"
824+
size="sm"
825+
onClick={() => setIsAuthDebuggerVisible(true)}
826+
>
827+
Open Auth Settings
828+
</Button>
829+
</div>
701830
</div>
702831
)}
703832
</div>

0 commit comments

Comments
 (0)