diff --git a/client/src/components/OAuthFlowProgress.tsx b/client/src/components/OAuthFlowProgress.tsx index 58d4e99fb..31ef4400c 100644 --- a/client/src/components/OAuthFlowProgress.tsx +++ b/client/src/components/OAuthFlowProgress.tsx @@ -6,7 +6,10 @@ import { useEffect, useMemo, useState } from "react"; import { OAuthClientInformation } from "@modelcontextprotocol/sdk/shared/auth.js"; import { validateRedirectUrl } from "@/utils/urlValidation"; import { useToast } from "@/lib/hooks/useToast"; -import { getAuthorizationServerMetadataDiscoveryUrl } from "@/utils/oauthUtils"; +import { + getAuthorizationServerMetadataDiscoveryUrl, + getProtectedResourceMetadataDiscoveryUrl, +} from "@/utils/oauthUtils"; interface OAuthStepProps { label: string; @@ -89,6 +92,12 @@ export const OAuthFlowProgress = ({ return getAuthorizationServerMetadataDiscoveryUrl(authState.authServerUrl); }, [authState.authServerUrl]); + // Mirrors the path-aware URL the MCP SDK actually fetches for the OAuth + // Protected Resource metadata, so the displayed URL matches the request. + const protectedResourceMetadataDiscoveryUrl = useMemo( + () => getProtectedResourceMetadataDiscoveryUrl(serverUrl), + [serverUrl], + ); const currentStepIdx = steps.findIndex((s) => s === authState.oauthStep); @@ -150,13 +159,7 @@ export const OAuthFlowProgress = ({
Resource Metadata:
- From{" "} - { - new URL( - "/.well-known/oauth-protected-resource", - serverUrl, - ).href - } + From {protectedResourceMetadataDiscoveryUrl}
{JSON.stringify(authState.resourceMetadata, null, 2)}
@@ -169,22 +172,12 @@ export const OAuthFlowProgress = ({
ℹ️ Problem with resource metadata from{" "}
- {
- new URL(
- "/.well-known/oauth-protected-resource",
- serverUrl,
- ).href
- }
+ {protectedResourceMetadataDiscoveryUrl}
diff --git a/client/src/utils/__tests__/oauthUtils.test.ts b/client/src/utils/__tests__/oauthUtils.test.ts
index b885b5ecb..758db863f 100644
--- a/client/src/utils/__tests__/oauthUtils.test.ts
+++ b/client/src/utils/__tests__/oauthUtils.test.ts
@@ -3,6 +3,7 @@ import {
parseOAuthCallbackParams,
generateOAuthState,
getAuthorizationServerMetadataDiscoveryUrl,
+ getProtectedResourceMetadataDiscoveryUrl,
} from "@/utils/oauthUtils.ts";
describe("parseOAuthCallbackParams", () => {
@@ -111,3 +112,23 @@ describe("getAuthorizationServerMetadataDiscoveryUrl", () => {
);
});
});
+
+describe("getProtectedResourceMetadataDiscoveryUrl", () => {
+ it("uses root discovery URL for root server URL", () => {
+ expect(getProtectedResourceMetadataDiscoveryUrl("https://example.com")).toBe(
+ "https://example.com/.well-known/oauth-protected-resource",
+ );
+ });
+
+ it("appends path for non-root server URL (matches the URL the SDK fetches)", () => {
+ expect(
+ getProtectedResourceMetadataDiscoveryUrl("https://example.com/mcp"),
+ ).toBe("https://example.com/.well-known/oauth-protected-resource/mcp");
+ });
+
+ it("strips trailing slash before appending path", () => {
+ expect(
+ getProtectedResourceMetadataDiscoveryUrl("https://example.com/mcp/"),
+ ).toBe("https://example.com/.well-known/oauth-protected-resource/mcp");
+ });
+});
diff --git a/client/src/utils/oauthUtils.ts b/client/src/utils/oauthUtils.ts
index cb6faf277..0f9ba7503 100644
--- a/client/src/utils/oauthUtils.ts
+++ b/client/src/utils/oauthUtils.ts
@@ -115,3 +115,33 @@ export const getAuthorizationServerMetadataDiscoveryUrl = (
url.origin,
).href;
};
+
+/**
+ * Returns the primary OAuth Protected Resource metadata discovery URL for a
+ * given resource (MCP server) URL, including path handling.
+ *
+ * Mirrors the path-aware discovery the MCP TypeScript SDK uses in
+ * `discoverOAuthProtectedResourceMetadata` so the URL the UI displays matches
+ * the URL that is actually fetched. See:
+ * https://github.com/modelcontextprotocol/inspector/issues/1166
+ */
+export const getProtectedResourceMetadataDiscoveryUrl = (
+ serverUrl: string | URL,
+): string => {
+ const url = typeof serverUrl === "string" ? new URL(serverUrl) : serverUrl;
+ const hasPath = url.pathname !== "/";
+
+ if (!hasPath) {
+ return new URL("/.well-known/oauth-protected-resource", url.origin).href;
+ }
+
+ // Strip trailing slash to avoid double slashes in path-aware discovery URLs.
+ const pathname = url.pathname.endsWith("/")
+ ? url.pathname.slice(0, -1)
+ : url.pathname;
+
+ return new URL(
+ `/.well-known/oauth-protected-resource${pathname}`,
+ url.origin,
+ ).href;
+};