import { AuthDebuggerState, OAuthStep } from "@/lib/auth-types";
import { CheckCircle2, Circle, ExternalLink } from "lucide-react";
import { Button } from "./ui/button";
import { DebugInspectorOAuthClientProvider } from "@/lib/auth";
import { useEffect, useMemo, useState } from "react";
import { OAuthClientInformation } from "@modelcontextprotocol/sdk/shared/auth.js";
interface OAuthStepProps {
label: string;
isComplete: boolean;
isCurrent: boolean;
error?: Error | null;
children?: React.ReactNode;
}
const OAuthStepDetails = ({
label,
isComplete,
isCurrent,
error,
children,
}: OAuthStepProps) => {
return (
{isComplete ? (
) : (
)}
{label}
{/* Show children if current step or complete and children exist */}
{(isCurrent || isComplete) && children && (
{children}
)}
{/* Display error if current step and an error exists */}
{isCurrent && error && (
)}
);
};
interface OAuthFlowProgressProps {
serverUrl: string;
authState: AuthDebuggerState;
updateAuthState: (updates: Partial) => void;
proceedToNextStep: () => Promise;
}
const steps: Array = [
"metadata_discovery",
"client_registration",
"authorization_redirect",
"authorization_code",
"token_request",
"complete",
];
export const OAuthFlowProgress = ({
serverUrl,
authState,
updateAuthState,
proceedToNextStep,
}: OAuthFlowProgressProps) => {
const provider = useMemo(
() => new DebugInspectorOAuthClientProvider(serverUrl),
[serverUrl],
);
const [clientInfo, setClientInfo] = useState(
null,
);
const currentStepIdx = steps.findIndex((s) => s === authState.oauthStep);
useEffect(() => {
const fetchClientInfo = async () => {
if (authState.oauthClientInfo) {
setClientInfo(authState.oauthClientInfo);
} else {
try {
const info = await provider.clientInformation();
if (info) {
setClientInfo(info);
}
} catch (error) {
console.error("Failed to fetch client information:", error);
}
}
};
if (currentStepIdx > steps.indexOf("client_registration")) {
fetchClientInfo();
}
}, [
provider,
authState.oauthStep,
authState.oauthClientInfo,
currentStepIdx,
]);
// Helper to get step props
const getStepProps = (stepName: OAuthStep) => ({
isComplete:
currentStepIdx > steps.indexOf(stepName) ||
currentStepIdx === steps.length - 1, // last step is "complete"
isCurrent: authState.oauthStep === stepName,
error: authState.oauthStep === stepName ? authState.latestError : null,
});
return (
OAuth Flow Progress
Follow these steps to complete OAuth authentication with the server.
{authState.oauthMetadata && (
OAuth Metadata Sources
{!authState.resourceMetadata && " ℹ️"}
{authState.resourceMetadata && (
Resource Metadata:
From{" "}
{
new URL(
"/.well-known/oauth-protected-resource",
serverUrl,
).href
}
{JSON.stringify(authState.resourceMetadata, null, 2)}
)}
{authState.resourceMetadataError && (
)}
{authState.oauthMetadata && (
Authorization Server Metadata:
{authState.authServerUrl && (
From{" "}
{
new URL(
"/.well-known/oauth-authorization-server",
authState.authServerUrl,
).href
}
)}
{JSON.stringify(authState.oauthMetadata, null, 2)}
)}
)}
{clientInfo && (
Registered Client Information
{JSON.stringify(clientInfo, null, 2)}
)}
{authState.authorizationUrl && (
Authorization URL:
{authState.authorizationUrl}
Click the link to authorize in your browser. After
authorization, you'll be redirected back to continue the flow.
)}
Authorization Code
{
updateAuthState({
authorizationCode: e.target.value,
validationError: null,
});
}}
placeholder="Enter the code from the authorization server"
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 ${
authState.validationError ? "border-red-500" : "border-input"
}`}
/>
{authState.validationError && (
{authState.validationError}
)}
Once you've completed authorization in the link, paste the code
here.
{authState.oauthMetadata && (
Token Request Details
Token Endpoint:
{authState.oauthMetadata.token_endpoint}
)}
{authState.oauthTokens && (
Access Tokens
Authentication successful! You can now use the authenticated
connection. These tokens will be used automatically for server
requests.
{JSON.stringify(authState.oauthTokens, null, 2)}
)}
{authState.oauthStep !== "complete" && (
<>
{authState.isInitiatingAuth ? "Processing..." : "Continue"}
>
)}
{authState.oauthStep === "authorization_redirect" &&
authState.authorizationUrl && (
window.open(authState.authorizationUrl!, "_blank")}
>
Open in New Tab
)}
);
};