working metadata fetch
This commit is contained in:
parent
b2fa99668e
commit
73fde03aa4
6 changed files with 63 additions and 112 deletions
|
|
@ -19,7 +19,7 @@ import {
|
|||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { OAuthTokensSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
|
||||
import { SESSION_KEYS, getServerSpecificKey } from "./lib/constants";
|
||||
import { AuthDebuggerState } from "./lib/auth-types";
|
||||
import { AuthDebuggerState, EMPTY_DEBUGGER_STATE } from "./lib/auth-types";
|
||||
import { cacheToolOutputSchemas } from "./utils/schemaUtils";
|
||||
import React, {
|
||||
Suspense,
|
||||
|
|
@ -121,20 +121,7 @@ const App = () => {
|
|||
const [isAuthDebuggerVisible, setIsAuthDebuggerVisible] = useState(false);
|
||||
|
||||
// Auth debugger state
|
||||
const [authState, setAuthState] = useState<AuthDebuggerState>({
|
||||
isInitiatingAuth: false,
|
||||
oauthTokens: null,
|
||||
loading: true,
|
||||
oauthStep: "metadata_discovery",
|
||||
oauthMetadata: null,
|
||||
resourceMetadata: null,
|
||||
oauthClientInfo: null,
|
||||
authorizationUrl: null,
|
||||
authorizationCode: "",
|
||||
latestError: null,
|
||||
statusMessage: null,
|
||||
validationError: null,
|
||||
});
|
||||
const [authState, setAuthState] = useState<AuthDebuggerState>(EMPTY_DEBUGGER_STATE);
|
||||
|
||||
// Helper function to update specific auth state properties
|
||||
const updateAuthState = (updates: Partial<AuthDebuggerState>) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useCallback, useMemo } from "react";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { DebugInspectorOAuthClientProvider } from "../lib/auth";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import { AuthDebuggerState } from "../lib/auth-types";
|
||||
import { AuthDebuggerState, EMPTY_DEBUGGER_STATE } from "../lib/auth-types";
|
||||
import { OAuthFlowProgress } from "./OAuthFlowProgress";
|
||||
import { OAuthStateMachine } from "../lib/oauth-state-machine";
|
||||
|
||||
|
|
@ -178,14 +178,7 @@ const AuthDebugger = ({
|
|||
);
|
||||
serverAuthProvider.clear();
|
||||
updateAuthState({
|
||||
oauthTokens: null,
|
||||
oauthStep: "metadata_discovery",
|
||||
latestError: null,
|
||||
oauthClientInfo: null,
|
||||
authorizationCode: "",
|
||||
authorizationUrl: "",
|
||||
validationError: null,
|
||||
oauthMetadata: null,
|
||||
...EMPTY_DEBUGGER_STATE,
|
||||
statusMessage: {
|
||||
type: "success",
|
||||
message: "OAuth tokens cleared successfully",
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ interface OAuthFlowProgressProps {
|
|||
}
|
||||
|
||||
const steps: Array<OAuthStep> = [
|
||||
"resource_metadata_discovery",
|
||||
"metadata_discovery",
|
||||
"client_registration",
|
||||
"authorization_redirect",
|
||||
|
|
@ -139,6 +138,19 @@ export const OAuthFlowProgress = ({
|
|||
</pre>
|
||||
</details>
|
||||
)}
|
||||
{authState.resourceMetadataError && (
|
||||
<div className="mt-2 p-3 border border-yellow-300 bg-yellow-50 rounded-md">
|
||||
<p className="text-sm font-small text-yellow-700">
|
||||
Failed to retrieve resource metadata, falling back to /.well-known/oauth-authorization-server:
|
||||
</p>
|
||||
<p className="text-xs text-yellow-600 mt-1">
|
||||
{authState.resourceMetadataError.message}
|
||||
{authState.resourceMetadataError instanceof TypeError
|
||||
? " (This could indicate the endpoint doesn't exist or does not have CORS configured)"
|
||||
: authState.resourceMetadataError['status'] && ` (${authState.resourceMetadataError['status']})`}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{provider.getServerMetadata() && (
|
||||
<details className="text-xs mt-2">
|
||||
<summary className="cursor-pointer text-muted-foreground font-medium">
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import {
|
|||
auth,
|
||||
} from "@modelcontextprotocol/sdk/client/auth.js";
|
||||
import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
|
||||
import { EMPTY_DEBUGGER_STATE } from "@/lib/auth-types";
|
||||
|
||||
// Type the mocked functions properly
|
||||
const mockDiscoverOAuthMetadata = discoverOAuthMetadata as jest.MockedFunction<
|
||||
|
|
@ -84,19 +85,7 @@ Object.defineProperty(window, "location", {
|
|||
});
|
||||
|
||||
describe("AuthDebugger", () => {
|
||||
const defaultAuthState = {
|
||||
isInitiatingAuth: false,
|
||||
oauthTokens: null,
|
||||
loading: false,
|
||||
oauthStep: "metadata_discovery" as const,
|
||||
oauthMetadata: null,
|
||||
oauthClientInfo: null,
|
||||
authorizationUrl: null,
|
||||
authorizationCode: "",
|
||||
latestError: null,
|
||||
statusMessage: null,
|
||||
validationError: null,
|
||||
};
|
||||
const defaultAuthState = EMPTY_DEBUGGER_STATE;
|
||||
|
||||
const defaultProps = {
|
||||
serverUrl: "https://example.com",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
OAuthClientInformationFull,
|
||||
OAuthClientInformation,
|
||||
OAuthTokens,
|
||||
OAuthProtectedResourceMetadata,
|
||||
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
||||
|
||||
// OAuth flow steps
|
||||
|
|
@ -28,8 +29,8 @@ export interface AuthDebuggerState {
|
|||
oauthTokens: OAuthTokens | null;
|
||||
loading: boolean;
|
||||
oauthStep: OAuthStep;
|
||||
// TODO: use sdk type
|
||||
resourceMetadata: object | null;
|
||||
resourceMetadata: OAuthProtectedResourceMetadata | null;
|
||||
resourceMetadataError: Error | { status: number; statusText: string; message: string } | null;
|
||||
oauthMetadata: OAuthMetadata | null;
|
||||
oauthClientInfo: OAuthClientInformationFull | OAuthClientInformation | null;
|
||||
authorizationUrl: string | null;
|
||||
|
|
@ -38,3 +39,19 @@ export interface AuthDebuggerState {
|
|||
statusMessage: StatusMessage | null;
|
||||
validationError: string | null;
|
||||
}
|
||||
|
||||
export const EMPTY_DEBUGGER_STATE: AuthDebuggerState = {
|
||||
isInitiatingAuth: false,
|
||||
oauthTokens: null,
|
||||
loading: true,
|
||||
oauthStep: "metadata_discovery",
|
||||
oauthMetadata: null,
|
||||
resourceMetadata: null,
|
||||
resourceMetadataError: null,
|
||||
oauthClientInfo: null,
|
||||
authorizationUrl: null,
|
||||
authorizationCode: "",
|
||||
latestError: null,
|
||||
statusMessage: null,
|
||||
validationError: null,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import {
|
|||
registerClient,
|
||||
startAuthorization,
|
||||
exchangeAuthorization,
|
||||
discoverOAuthProtectedResourceMetadata,
|
||||
} from "@modelcontextprotocol/sdk/client/auth.js";
|
||||
import { OAuthMetadataSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
|
||||
import { OAuthMetadataSchema, OAuthProtectedResourceMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
|
||||
|
||||
export interface StateMachineContext {
|
||||
state: AuthDebuggerState;
|
||||
|
|
@ -20,29 +21,28 @@ export interface StateTransition {
|
|||
execute: (context: StateMachineContext) => Promise<void>;
|
||||
}
|
||||
|
||||
const fetchProtectedResourceMetadata = async (serverUrl: string): Promise<object> => {
|
||||
// TODO: use sdk
|
||||
const url = new URL("/.well-known/oauth-protected-resource", serverUrl);
|
||||
const response = await fetch(url);
|
||||
const resourceMetadata = await response.json();
|
||||
|
||||
return resourceMetadata;
|
||||
}
|
||||
|
||||
// State machine transitions
|
||||
export const oauthTransitions: Record<OAuthStep, StateTransition> = {
|
||||
metadata_discovery: {
|
||||
canTransition: async () => true,
|
||||
execute: async (context) => {
|
||||
const authServerUrl = context.serverUrl;
|
||||
// try {
|
||||
// const resourceMetadata = await fetchProtectedResourceMetadata(context.serverUrl);
|
||||
// if (resourceMetadata && resourceMetadata) {
|
||||
// authServerUrl = resourceMetadata
|
||||
// }
|
||||
// } catch (_error) {
|
||||
// // pass
|
||||
// }
|
||||
let authServerUrl = context.serverUrl;
|
||||
let resourceMetadata: OAuthProtectedResourceMetadata | null = null;
|
||||
let resourceMetadataError: Error | null = null;
|
||||
try {
|
||||
resourceMetadata = await discoverOAuthProtectedResourceMetadata(context.serverUrl);
|
||||
if (resourceMetadata && resourceMetadata.authorization_servers?.length) {
|
||||
authServerUrl = resourceMetadata.authorization_servers[0];
|
||||
}
|
||||
} catch (e) {
|
||||
console.info(`Failed to find protected resource metadata: ${e}`);
|
||||
console.log(e);
|
||||
if (e instanceof Error) {
|
||||
resourceMetadataError = e;
|
||||
} else {
|
||||
resourceMetadataError = new Error(String(e));
|
||||
}
|
||||
}
|
||||
|
||||
const metadata = await discoverOAuthMetadata(authServerUrl);
|
||||
if (!metadata) {
|
||||
|
|
@ -52,6 +52,7 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
|
|||
context.provider.saveServerMetadata(parsedMetadata);
|
||||
context.updateState({
|
||||
resourceMetadata,
|
||||
resourceMetadataError,
|
||||
oauthMetadata: parsedMetadata,
|
||||
oauthStep: "client_registration",
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue