Skip to content

Commit d0ba418

Browse files
authored
Merge branch 'main' into allow-multiple-client-connections
2 parents 0c1962c + 520416b commit d0ba418

File tree

12 files changed

+972
-115
lines changed

12 files changed

+972
-115
lines changed

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"dependencies": {
2626
"@modelcontextprotocol/sdk": "^1.11.5",
2727
"@radix-ui/react-checkbox": "^1.1.4",
28+
"ajv": "^6.12.6",
2829
"@radix-ui/react-dialog": "^1.1.3",
2930
"@radix-ui/react-icons": "^1.3.0",
3031
"@radix-ui/react-label": "^2.1.0",

client/src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { OAuthTokensSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
2121
import { SESSION_KEYS, getServerSpecificKey } from "./lib/constants";
2222
import { AuthDebuggerState } from "./lib/auth-types";
23+
import { cacheToolOutputSchemas } from "./utils/schemaUtils";
2324
import React, {
2425
Suspense,
2526
useCallback,
@@ -473,6 +474,8 @@ const App = () => {
473474
);
474475
setTools(response.tools);
475476
setNextToolCursor(response.nextCursor);
477+
// Cache output schemas for validation
478+
cacheToolOutputSchemas(response.tools);
476479
};
477480

478481
const callTool = async (name: string, params: Record<string, unknown>) => {
@@ -759,6 +762,8 @@ const App = () => {
759762
clearTools={() => {
760763
setTools([]);
761764
setNextToolCursor(undefined);
765+
// Clear cached output schemas
766+
cacheToolOutputSchemas([]);
762767
}}
763768
callTool={async (name, params) => {
764769
clearError("tools");

client/src/components/AuthDebugger.tsx

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import { useCallback, useMemo } from "react";
22
import { Button } from "@/components/ui/button";
33
import { DebugInspectorOAuthClientProvider } from "../lib/auth";
4-
import {
5-
auth,
6-
discoverOAuthMetadata,
7-
} from "@modelcontextprotocol/sdk/client/auth.js";
8-
import { OAuthMetadataSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
94
import { AlertCircle } from "lucide-react";
105
import { AuthDebuggerState } from "../lib/auth-types";
116
import { OAuthFlowProgress } from "./OAuthFlowProgress";
@@ -124,22 +119,43 @@ const AuthDebugger = ({
124119

125120
updateAuthState({ isInitiatingAuth: true, statusMessage: null });
126121
try {
127-
const serverAuthProvider = new DebugInspectorOAuthClientProvider(
128-
serverUrl,
129-
);
130-
// First discover OAuth metadata separately so we can save it
131-
const metadata = await discoverOAuthMetadata(serverUrl);
132-
if (!metadata) {
133-
throw new Error("Failed to discover OAuth metadata");
122+
// Step through the OAuth flow using the state machine instead of the auth() function
123+
let currentState: AuthDebuggerState = {
124+
...authState,
125+
oauthStep: "metadata_discovery",
126+
authorizationUrl: null,
127+
latestError: null,
128+
};
129+
130+
const oauthMachine = new OAuthStateMachine(serverUrl, (updates) => {
131+
// Update our temporary state during the process
132+
currentState = { ...currentState, ...updates };
133+
// But don't call updateAuthState yet
134+
});
135+
136+
// Manually step through each stage of the OAuth flow
137+
while (currentState.oauthStep !== "complete") {
138+
await oauthMachine.executeStep(currentState);
139+
// In quick mode, we'll just redirect to the authorization URL
140+
if (
141+
currentState.oauthStep === "authorization_code" &&
142+
currentState.authorizationUrl
143+
) {
144+
// Open the authorization URL automatically
145+
window.location.href = currentState.authorizationUrl;
146+
break;
147+
}
134148
}
135-
const parsedMetadata = await OAuthMetadataSchema.parseAsync(metadata);
136-
serverAuthProvider.saveServerMetadata(parsedMetadata);
137149

138-
await auth(serverAuthProvider, { serverUrl: serverUrl });
150+
// After the flow completes or reaches a user-input step, update the app state
139151
updateAuthState({
152+
...currentState,
140153
statusMessage: {
141154
type: "info",
142-
message: "Starting OAuth authentication process...",
155+
message:
156+
currentState.oauthStep === "complete"
157+
? "Authentication completed successfully"
158+
: "Please complete authentication in the opened window and enter the code",
143159
},
144160
});
145161
} catch (error) {
@@ -153,7 +169,7 @@ const AuthDebugger = ({
153169
} finally {
154170
updateAuthState({ isInitiatingAuth: false });
155171
}
156-
}, [serverUrl, updateAuthState]);
172+
}, [serverUrl, updateAuthState, authState]);
157173

158174
const handleClearOAuth = useCallback(() => {
159175
if (serverUrl) {

client/src/components/OAuthFlowProgress.tsx

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { AuthDebuggerState, OAuthStep } from "@/lib/auth-types";
22
import { CheckCircle2, Circle, ExternalLink } from "lucide-react";
33
import { Button } from "./ui/button";
44
import { DebugInspectorOAuthClientProvider } from "@/lib/auth";
5+
import { useEffect, useMemo, useState } from "react";
6+
import { OAuthClientInformation } from "@modelcontextprotocol/sdk/shared/auth.js";
57

68
interface OAuthStepProps {
79
label: string;
@@ -54,24 +56,57 @@ interface OAuthFlowProgressProps {
5456
proceedToNextStep: () => Promise<void>;
5557
}
5658

59+
const steps: Array<OAuthStep> = [
60+
"metadata_discovery",
61+
"client_registration",
62+
"authorization_redirect",
63+
"authorization_code",
64+
"token_request",
65+
"complete",
66+
];
67+
5768
export const OAuthFlowProgress = ({
5869
serverUrl,
5970
authState,
6071
updateAuthState,
6172
proceedToNextStep,
6273
}: OAuthFlowProgressProps) => {
63-
const provider = new DebugInspectorOAuthClientProvider(serverUrl);
74+
const provider = useMemo(
75+
() => new DebugInspectorOAuthClientProvider(serverUrl),
76+
[serverUrl],
77+
);
78+
const [clientInfo, setClientInfo] = useState<OAuthClientInformation | null>(
79+
null,
80+
);
6481

65-
const steps: Array<OAuthStep> = [
66-
"metadata_discovery",
67-
"client_registration",
68-
"authorization_redirect",
69-
"authorization_code",
70-
"token_request",
71-
"complete",
72-
];
7382
const currentStepIdx = steps.findIndex((s) => s === authState.oauthStep);
7483

84+
useEffect(() => {
85+
const fetchClientInfo = async () => {
86+
if (authState.oauthClientInfo) {
87+
setClientInfo(authState.oauthClientInfo);
88+
} else {
89+
try {
90+
const info = await provider.clientInformation();
91+
if (info) {
92+
setClientInfo(info);
93+
}
94+
} catch (error) {
95+
console.error("Failed to fetch client information:", error);
96+
}
97+
}
98+
};
99+
100+
if (currentStepIdx > steps.indexOf("client_registration")) {
101+
fetchClientInfo();
102+
}
103+
}, [
104+
provider,
105+
authState.oauthStep,
106+
authState.oauthClientInfo,
107+
currentStepIdx,
108+
]);
109+
75110
// Helper to get step props
76111
const getStepProps = (stepName: OAuthStep) => ({
77112
isComplete:
@@ -110,13 +145,13 @@ export const OAuthFlowProgress = ({
110145
label="Client Registration"
111146
{...getStepProps("client_registration")}
112147
>
113-
{authState.oauthClientInfo && (
148+
{clientInfo && (
114149
<details className="text-xs mt-2">
115150
<summary className="cursor-pointer text-muted-foreground font-medium">
116151
Registered Client Information
117152
</summary>
118153
<pre className="mt-2 p-2 bg-muted rounded-md overflow-auto max-h-[300px]">
119-
{JSON.stringify(authState.oauthClientInfo, null, 2)}
154+
{JSON.stringify(clientInfo, null, 2)}
120155
</pre>
121156
</details>
122157
)}

0 commit comments

Comments
 (0)