From 454a6098bd5fcdca5d186f1642226e2bcdc75bb6 Mon Sep 17 00:00:00 2001
From: Paul Carleton
Connect to an MCP server to start inspecting
+ Need to configure authentication?
+ Authorization URL: {authorizationUrl}
+ Click the link to authorize in your browser. After authorization,
+ you'll be redirected back to continue the flow.
+
+ Once you've completed authorization in the link, paste the code
+ here.
+ Token Endpoint: Try listTools to use these credentials
+ Follow these steps to complete OAuth authentication with the server.
+ Error:
+ {latestError.message}
+
+ Configure authentication settings for your MCP server connection.
+
+ Use OAuth to securely authenticate with the MCP server.
+ Loading authentication status... Access Token:
+ Choose "Guided" for step-by-step instructions or "Quick" for
+ the standard automatic flow.
+
+ Please copy this authorization code and return to the Auth Debugger:
+
+ Close this tab and paste the code in the OAuth flow to complete
+ authentication.
+ Loading authentication status... Access Token:
Close this tab and paste the code in the OAuth flow to complete
From be222058b525770f24462e9665577f999c1de130 Mon Sep 17 00:00:00 2001
From: Paul Carleton
@@ -752,7 +747,7 @@ const App = () => {
diff --git a/client/src/components/AuthDebugger.tsx b/client/src/components/AuthDebugger.tsx
index 8f067e5a..8265c073 100644
--- a/client/src/components/AuthDebugger.tsx
+++ b/client/src/components/AuthDebugger.tsx
@@ -20,6 +20,9 @@ import {
} from "@modelcontextprotocol/sdk/shared/auth.js";
import { CheckCircle2, Circle, ExternalLink } from "lucide-react";
+// Type for the toast function from the useToast hook
+type ToastFunction = ReturnType Try listTools to use these credentials
+ Authentication successful! You can now use the authenticated
+ connection. These tokens will be used automatically for server
+ requests.
+
diff --git a/client/src/components/__tests__/AuthDebugger.test.tsx b/client/src/components/__tests__/AuthDebugger.test.tsx
index d108dc00..76196cae 100644
--- a/client/src/components/__tests__/AuthDebugger.test.tsx
+++ b/client/src/components/__tests__/AuthDebugger.test.tsx
@@ -44,9 +44,8 @@ jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
exchangeAuthorization: jest.fn(),
}));
-// Import mocked functions
+// Import mocked functions (removing unused mockAuth)
import {
- auth as mockAuth,
discoverOAuthMetadata as mockDiscoverOAuthMetadata,
registerClient as mockRegisterClient,
startAuthorization as mockStartAuthorization,
diff --git a/client/src/lib/constants.ts b/client/src/lib/constants.ts
index 327a7227..206864be 100644
--- a/client/src/lib/constants.ts
+++ b/client/src/lib/constants.ts
@@ -6,6 +6,10 @@ export const SESSION_KEYS = {
SERVER_URL: "mcp_server_url",
TOKENS: "mcp_tokens",
CLIENT_INFORMATION: "mcp_client_information",
+ /**
+ * Temporary storage for OAuth authorization code during debug flow
+ * Used when authentication is done in a separate tab
+ */
DEBUG_CODE: "mcp_code",
} as const;
From 6277126f83503f31cd4f7fd7a62cf71c749524bf Mon Sep 17 00:00:00 2001
From: Paul Carleton {statusMessage.message} {validationError}
Once you've completed authorization in the link, paste the code
here.
@@ -442,7 +449,6 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
+ Retrieved OAuth Metadata
+
+
+ {JSON.stringify(oauthMetadata, null, 2)}
+
+
+ Registered Client Information
+
+
+ {JSON.stringify(oauthClientInfo, null, 2)}
+
+
+ Token Request Details
+
+
+ {oauthMetadata.token_endpoint}
+
+ {/* TODO: break down the URL components */}
+
+ Access Tokens
+
+
+ {JSON.stringify(oauthTokens, null, 2)}
+
+ OAuth Flow Progress
+ Authentication Settings
+
+ OAuth Authentication
+
+ {parseOAuthCallbackParams(window.location.search).code ||
+ "No code found"}
+
+
- {parseOAuthCallbackParams(window.location.search).code ||
- "No code found"}
+ {callbackParams.successful
+ ? (
+ callbackParams as {
+ code: string;
+ }
+ ).code
+ : `No code found: ${callbackParams.error}, ${callbackParams.error_description}`}
- Retrieved OAuth Metadata
+ Retrieved OAuth Metadata from {sseUrl}
+ /.well-known/oauth-authorization-server
{JSON.stringify(oauthMetadata, null, 2)}
@@ -364,6 +389,8 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
target="_blank"
rel="noopener noreferrer"
className="flex items-center text-blue-500 hover:text-blue-700"
+ aria-label="Open authorization URL in new tab"
+ title="Open authorization URL"
>
Access Tokens
-
{JSON.stringify(oauthTokens, null, 2)}
diff --git a/client/src/components/OAuthDebugCallback.tsx b/client/src/components/OAuthDebugCallback.tsx
index 9c28f42f..6ebf91b6 100644
--- a/client/src/components/OAuthDebugCallback.tsx
+++ b/client/src/components/OAuthDebugCallback.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef } from "react";
+import { useEffect } from "react";
import { SESSION_KEYS } from "../lib/constants";
import { useToast } from "@/hooks/use-toast.ts";
import {
@@ -12,17 +12,16 @@ interface OAuthCallbackProps {
const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => {
const { toast } = useToast();
- const hasProcessedRef = useRef(false);
useEffect(() => {
+ let isProcessed = false;
+
const handleCallback = async () => {
// Skip if we've already processed this callback
- if (hasProcessedRef.current) {
+ if (isProcessed) {
return;
}
- hasProcessedRef.current = true;
-
- onConnect("");
+ isProcessed = true;
const notifyError = (description: string) =>
void toast({
@@ -42,6 +41,8 @@ const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => {
// authentication request in a new tab, so we don't have the same
// session storage
if (!serverUrl) {
+ // If there's no server URL, we're likely in a new tab
+ // Just display the code for manual copying
return;
}
@@ -51,12 +52,14 @@ const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => {
sessionStorage.setItem(SESSION_KEYS.DEBUG_CODE, params.code);
- // Finally, trigger auto-connect
+ // Finally, trigger navigation back to auth debugger
toast({
title: "Success",
- description: "Successfully authenticated with OAuth",
+ description: "Authorization code received. Please return to the Auth Debugger.",
variant: "default",
});
+
+ // Call onConnect to navigate back to the auth debugger
onConnect(serverUrl);
};
@@ -67,6 +70,10 @@ const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => {
window.history.replaceState({}, document.title, "/");
}
});
+
+ return () => {
+ isProcessed = true;
+ };
}, [toast, onConnect]);
const callbackParams = parseOAuthCallbackParams(window.location.search);
@@ -78,12 +85,8 @@ const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => {
Please copy this authorization code and return to the Auth Debugger:
- {callbackParams.successful
- ? (
- callbackParams as {
- code: string;
- }
- ).code
+ {callbackParams.successful && 'code' in callbackParams
+ ? callbackParams.code
: `No code found: ${callbackParams.error}, ${callbackParams.error_description}`}
Registered Client Information
@@ -417,11 +416,19 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
setAuthorizationCode(e.target.value)}
+ onChange={(e) => {
+ setAuthorizationCode(e.target.value);
+ setValidationError(null);
+ }}
placeholder="Enter the code from the authorization server"
- className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
+ className={`flex h-9 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
+ validationError ? "border-red-500" : "border-input"
+ }`}
/>
{oauthMetadata.token_endpoint}
- {/* TODO: break down the URL components */}
Loading authentication status...
) : ( @@ -619,7 +617,7 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => { )} - {oauthFlowVisible && renderOAuthFlow()} + {renderOAuthFlow()} From ecefbddb4895d9693034ceef51fad61e5a795d57 Mon Sep 17 00:00:00 2001 From: Paul Carleton{statusMessage.message}
+{state.statusMessage.message}
- {JSON.stringify(oauthMetadata, null, 2)} + {JSON.stringify(state.oauthMetadata, null, 2)}
- {JSON.stringify(oauthClientInfo, null, 2)} + {JSON.stringify(state.oauthClientInfo, null, 2)}
Authorization URL:
{authorizationUrl}
+{state.authorizationUrl}
{{validationError}
+ {state.validationError && ( +{state.validationError}
)}
Once you've completed authorization in the link, paste the code
@@ -439,7 +471,7 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
{
key: "token_request",
label: "Token Request",
- metadata: oauthMetadata && (
+ metadata: state.oauthMetadata && (
Token Endpoint:
Token Request Details
@@ -447,7 +479,7 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
- {oauthMetadata.token_endpoint}
+ {state.oauthMetadata.token_endpoint}
Access Tokens
@@ -467,7 +499,7 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
requests.
- {JSON.stringify(oauthTokens, null, 2)} + {JSON.stringify(state.oauthTokens, null, 2)}), @@ -483,9 +515,9 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
Error:
- {latestError.message} + {state.latestError.message}
- {JSON.stringify(state.oauthMetadata, null, 2)} + {JSON.stringify(authState.oauthMetadata, null, 2)}
- {JSON.stringify(state.oauthClientInfo, null, 2)} + {JSON.stringify(authState.oauthClientInfo, null, 2)}
Authorization URL:
}> +@@ -530,7 +530,7 @@ const AuthDebugger = ({
{message.message}
+{authState.statusMessage.message}
-Loading authentication status...
From dc875a0124c70e3115a5dd47be26cd30dd1a0eab Mon Sep 17 00:00:00 2001 From: Paul Carleton- {JSON.stringify(provider.getServerMetadata(), null, 2)} --
- {JSON.stringify(authState.oauthClientInfo, null, 2)} --
Authorization URL:
-{authState.authorizationUrl}
- -- Click the link to authorize in your browser. After authorization, - you'll be redirected back to continue the flow. -
-- {authState.validationError} -
- )} -- Once you've completed authorization in the link, paste the code - here. -
-Token Endpoint:
-
- {authState.oauthMetadata.token_endpoint}
-
- - Authentication successful! You can now use the authenticated - connection. These tokens will be used automatically for server - requests. -
-- {JSON.stringify(authState.oauthTokens, null, 2)} --
- Follow these steps to complete OAuth authentication with the server. -
- -Error:
-- {authState.latestError.message} -
-Error:
+{error.message}
++ Follow these steps to complete OAuth authentication with the server. +
+ ++ {JSON.stringify(provider.getServerMetadata(), null, 2)} ++
+ {JSON.stringify(authState.oauthClientInfo, null, 2)} ++
Authorization URL:
++ {authState.authorizationUrl} +
+ ++ Click the link to authorize in your browser. After + authorization, you'll be redirected back to continue the flow. +
++ {authState.validationError} +
+ )} ++ Once you've completed authorization in the link, paste the code + here. +
+Token Endpoint:
+
+ {authState.oauthMetadata.token_endpoint}
+
+ + Authentication successful! You can now use the authenticated + connection. These tokens will be used automatically for server + requests. +
++ {JSON.stringify(authState.oauthTokens, null, 2)} ++