diff --git a/README.md b/README.md index 9d5017dc9..546fc4adc 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,8 @@ You can paste the Server Entry into your existing `mcp.json` file under your cho The inspector supports bearer token authentication for SSE connections. Enter your token in the UI when connecting to an MCP server, and it will be sent in the Authorization header. You can override the header name using the input field in the sidebar. +It also follows [the MCP specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#2-authorization-flow) for OAuth for remote transports. It uses all the scopes specified in the provider's `scopes_supported` field to help with debugging. + ### Security Considerations The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server. diff --git a/client/src/lib/hooks/__tests__/useConnection.test.tsx b/client/src/lib/hooks/__tests__/useConnection.test.tsx index 6e5748d77..f1780b505 100644 --- a/client/src/lib/hooks/__tests__/useConnection.test.tsx +++ b/client/src/lib/hooks/__tests__/useConnection.test.tsx @@ -42,6 +42,15 @@ jest.mock("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({ jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({ auth: jest.fn().mockResolvedValue("AUTHORIZED"), + discoverOAuthMetadata: jest.fn().mockResolvedValue({ + issuer: "https://oauth.example.com", + authorization_endpoint: "https://oauth.example.com/authorize", + token_endpoint: "https://oauth.example.com/token", + response_types_supported: ["code"], + grant_types_supported: ["authorization_code", "refresh_token"], + code_challenge_methods_supported: ["S256"], + scopes_supported: ["read", "write"], + }), })); // Mock the toast hook diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index d42b8d386..17871ebd8 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -30,12 +30,16 @@ import { Progress, } from "@modelcontextprotocol/sdk/types.js"; import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js"; +import { OAuthMetadataSchema } from "@modelcontextprotocol/sdk/shared/auth.js"; import { useState } from "react"; import { useToast } from "@/lib/hooks/useToast"; import { z } from "zod"; import { ConnectionStatus } from "../constants"; import { Notification, StdErrNotificationSchema } from "../notificationTypes"; -import { auth } from "@modelcontextprotocol/sdk/client/auth.js"; +import { + auth, + discoverOAuthMetadata, +} from "@modelcontextprotocol/sdk/client/auth.js"; import { InspectorOAuthClientProvider } from "../auth"; import packageJson from "../../../package.json"; import { @@ -265,7 +269,17 @@ export function useConnection({ if (is401Error(error)) { const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl); - const result = await auth(serverAuthProvider, { serverUrl: sseUrl }); + // Use all supported scopes if available + const metadata = await discoverOAuthMetadata(sseUrl); + if (!metadata) { + throw new Error("Failed to discover OAuth metadata"); + } + const parsedMetadata = await OAuthMetadataSchema.parseAsync(metadata); + const scope = parsedMetadata.scopes_supported?.join(" "); + const result = await auth(serverAuthProvider, { + serverUrl: sseUrl, + scope, + }); return result === "AUTHORIZED"; }