Merge branch 'main' into main
This commit is contained in:
commit
3ea9a19a76
3 changed files with 80 additions and 35 deletions
|
|
@ -1,11 +1,6 @@
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { DebugInspectorOAuthClientProvider } from "../lib/auth";
|
import { DebugInspectorOAuthClientProvider } from "../lib/auth";
|
||||||
import {
|
|
||||||
auth,
|
|
||||||
discoverOAuthMetadata,
|
|
||||||
} from "@modelcontextprotocol/sdk/client/auth.js";
|
|
||||||
import { OAuthMetadataSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
||||||
import { AlertCircle } from "lucide-react";
|
import { AlertCircle } from "lucide-react";
|
||||||
import { AuthDebuggerState } from "../lib/auth-types";
|
import { AuthDebuggerState } from "../lib/auth-types";
|
||||||
import { OAuthFlowProgress } from "./OAuthFlowProgress";
|
import { OAuthFlowProgress } from "./OAuthFlowProgress";
|
||||||
|
|
@ -124,22 +119,43 @@ const AuthDebugger = ({
|
||||||
|
|
||||||
updateAuthState({ isInitiatingAuth: true, statusMessage: null });
|
updateAuthState({ isInitiatingAuth: true, statusMessage: null });
|
||||||
try {
|
try {
|
||||||
const serverAuthProvider = new DebugInspectorOAuthClientProvider(
|
// Step through the OAuth flow using the state machine instead of the auth() function
|
||||||
serverUrl,
|
let currentState: AuthDebuggerState = {
|
||||||
);
|
...authState,
|
||||||
// First discover OAuth metadata separately so we can save it
|
oauthStep: "metadata_discovery",
|
||||||
const metadata = await discoverOAuthMetadata(serverUrl);
|
authorizationUrl: null,
|
||||||
if (!metadata) {
|
latestError: null,
|
||||||
throw new Error("Failed to discover OAuth metadata");
|
};
|
||||||
}
|
|
||||||
const parsedMetadata = await OAuthMetadataSchema.parseAsync(metadata);
|
|
||||||
serverAuthProvider.saveServerMetadata(parsedMetadata);
|
|
||||||
|
|
||||||
await auth(serverAuthProvider, { serverUrl: serverUrl });
|
const oauthMachine = new OAuthStateMachine(serverUrl, (updates) => {
|
||||||
|
// Update our temporary state during the process
|
||||||
|
currentState = { ...currentState, ...updates };
|
||||||
|
// But don't call updateAuthState yet
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manually step through each stage of the OAuth flow
|
||||||
|
while (currentState.oauthStep !== "complete") {
|
||||||
|
await oauthMachine.executeStep(currentState);
|
||||||
|
// In quick mode, we'll just redirect to the authorization URL
|
||||||
|
if (
|
||||||
|
currentState.oauthStep === "authorization_code" &&
|
||||||
|
currentState.authorizationUrl
|
||||||
|
) {
|
||||||
|
// Open the authorization URL automatically
|
||||||
|
window.location.href = currentState.authorizationUrl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After the flow completes or reaches a user-input step, update the app state
|
||||||
updateAuthState({
|
updateAuthState({
|
||||||
|
...currentState,
|
||||||
statusMessage: {
|
statusMessage: {
|
||||||
type: "info",
|
type: "info",
|
||||||
message: "Starting OAuth authentication process...",
|
message:
|
||||||
|
currentState.oauthStep === "complete"
|
||||||
|
? "Authentication completed successfully"
|
||||||
|
: "Please complete authentication in the opened window and enter the code",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -153,7 +169,7 @@ const AuthDebugger = ({
|
||||||
} finally {
|
} finally {
|
||||||
updateAuthState({ isInitiatingAuth: false });
|
updateAuthState({ isInitiatingAuth: false });
|
||||||
}
|
}
|
||||||
}, [serverUrl, updateAuthState]);
|
}, [serverUrl, updateAuthState, authState]);
|
||||||
|
|
||||||
const handleClearOAuth = useCallback(() => {
|
const handleClearOAuth = useCallback(() => {
|
||||||
if (serverUrl) {
|
if (serverUrl) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { AuthDebuggerState, OAuthStep } from "@/lib/auth-types";
|
||||||
import { CheckCircle2, Circle, ExternalLink } from "lucide-react";
|
import { CheckCircle2, Circle, ExternalLink } from "lucide-react";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { DebugInspectorOAuthClientProvider } from "@/lib/auth";
|
import { DebugInspectorOAuthClientProvider } from "@/lib/auth";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { OAuthClientInformation } from "@modelcontextprotocol/sdk/shared/auth.js";
|
||||||
|
|
||||||
interface OAuthStepProps {
|
interface OAuthStepProps {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -54,24 +56,57 @@ interface OAuthFlowProgressProps {
|
||||||
proceedToNextStep: () => Promise<void>;
|
proceedToNextStep: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const steps: Array<OAuthStep> = [
|
||||||
|
"metadata_discovery",
|
||||||
|
"client_registration",
|
||||||
|
"authorization_redirect",
|
||||||
|
"authorization_code",
|
||||||
|
"token_request",
|
||||||
|
"complete",
|
||||||
|
];
|
||||||
|
|
||||||
export const OAuthFlowProgress = ({
|
export const OAuthFlowProgress = ({
|
||||||
serverUrl,
|
serverUrl,
|
||||||
authState,
|
authState,
|
||||||
updateAuthState,
|
updateAuthState,
|
||||||
proceedToNextStep,
|
proceedToNextStep,
|
||||||
}: OAuthFlowProgressProps) => {
|
}: OAuthFlowProgressProps) => {
|
||||||
const provider = new DebugInspectorOAuthClientProvider(serverUrl);
|
const provider = useMemo(
|
||||||
|
() => new DebugInspectorOAuthClientProvider(serverUrl),
|
||||||
|
[serverUrl],
|
||||||
|
);
|
||||||
|
const [clientInfo, setClientInfo] = useState<OAuthClientInformation | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
const steps: Array<OAuthStep> = [
|
|
||||||
"metadata_discovery",
|
|
||||||
"client_registration",
|
|
||||||
"authorization_redirect",
|
|
||||||
"authorization_code",
|
|
||||||
"token_request",
|
|
||||||
"complete",
|
|
||||||
];
|
|
||||||
const currentStepIdx = steps.findIndex((s) => s === authState.oauthStep);
|
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
|
// Helper to get step props
|
||||||
const getStepProps = (stepName: OAuthStep) => ({
|
const getStepProps = (stepName: OAuthStep) => ({
|
||||||
isComplete:
|
isComplete:
|
||||||
|
|
@ -110,13 +145,13 @@ export const OAuthFlowProgress = ({
|
||||||
label="Client Registration"
|
label="Client Registration"
|
||||||
{...getStepProps("client_registration")}
|
{...getStepProps("client_registration")}
|
||||||
>
|
>
|
||||||
{authState.oauthClientInfo && (
|
{clientInfo && (
|
||||||
<details className="text-xs mt-2">
|
<details className="text-xs mt-2">
|
||||||
<summary className="cursor-pointer text-muted-foreground font-medium">
|
<summary className="cursor-pointer text-muted-foreground font-medium">
|
||||||
Registered Client Information
|
Registered Client Information
|
||||||
</summary>
|
</summary>
|
||||||
<pre className="mt-2 p-2 bg-muted rounded-md overflow-auto max-h-[300px]">
|
<pre className="mt-2 p-2 bg-muted rounded-md overflow-auto max-h-[300px]">
|
||||||
{JSON.stringify(authState.oauthClientInfo, null, 2)}
|
{JSON.stringify(clientInfo, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -207,16 +207,10 @@ describe("AuthDebugger", () => {
|
||||||
"https://example.com",
|
"https://example.com",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Then should call auth with the server provider
|
|
||||||
expect(mockAuth).toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Check that updateAuthState was called with the right info message
|
// Check that updateAuthState was called with the right info message
|
||||||
expect(updateAuthState).toHaveBeenCalledWith(
|
expect(updateAuthState).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
statusMessage: {
|
oauthStep: "authorization_code",
|
||||||
type: "info",
|
|
||||||
message: "Starting OAuth authentication process...",
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue