diff --git a/CLAUDE.md b/CLAUDE.md index e62fd188..ec826b1f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,4 +30,4 @@ The project is organized as a monorepo with workspaces: - `client/`: React frontend with Vite, TypeScript and Tailwind - `server/`: Express backend with TypeScript -- `bin/`: CLI scripts +- `cli/`: Command-line interface for testing and invoking MCP server methods directly diff --git a/cli/package.json b/cli/package.json index f1ff1c33..382eb1b4 100644 --- a/cli/package.json +++ b/cli/package.json @@ -21,7 +21,7 @@ }, "devDependencies": {}, "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "commander": "^13.1.0", "spawn-rx": "^5.1.2" } diff --git a/client/package.json b/client/package.json index 6d2e7d1a..12438aeb 100644 --- a/client/package.json +++ b/client/package.json @@ -25,7 +25,7 @@ "cleanup:e2e": "node e2e/global-teardown.js" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "@radix-ui/react-checkbox": "^1.1.4", "ajv": "^6.12.6", "@radix-ui/react-dialog": "^1.1.3", diff --git a/client/src/components/OAuthFlowProgress.tsx b/client/src/components/OAuthFlowProgress.tsx index b9b67c6c..0e260556 100644 --- a/client/src/components/OAuthFlowProgress.tsx +++ b/client/src/components/OAuthFlowProgress.tsx @@ -156,7 +156,7 @@ export const OAuthFlowProgress = ({ {authState.resourceMetadataError && (

- ℹ️ No resource metadata available from{" "} + ℹ️ Problem with resource metadata from{" "} ({ startAuthorization: jest.fn(), exchangeAuthorization: jest.fn(), discoverOAuthProtectedResourceMetadata: jest.fn(), + selectResourceURL: jest.fn(), })); // Import the functions to get their types @@ -88,7 +89,7 @@ describe("AuthDebugger", () => { const defaultAuthState = EMPTY_DEBUGGER_STATE; const defaultProps = { - serverUrl: "https://example.com", + serverUrl: "https://example.com/mcp", onBack: jest.fn(), authState: defaultAuthState, updateAuthState: jest.fn(), @@ -203,7 +204,7 @@ describe("AuthDebugger", () => { // Should first discover and save OAuth metadata expect(mockDiscoverOAuthMetadata).toHaveBeenCalledWith( - new URL("https://example.com"), + new URL("https://example.com/"), ); // Check that updateAuthState was called with the right info message @@ -320,6 +321,7 @@ describe("AuthDebugger", () => { isInitiatingAuth: false, resourceMetadata: null, resourceMetadataError: null, + resource: null, oauthTokens: null, oauthStep: "metadata_discovery", latestError: null, @@ -361,7 +363,7 @@ describe("AuthDebugger", () => { }); expect(mockDiscoverOAuthMetadata).toHaveBeenCalledWith( - new URL("https://example.com"), + new URL("https://example.com/"), ); }); @@ -496,11 +498,11 @@ describe("AuthDebugger", () => { it("should successfully fetch and display protected resource metadata", async () => { const updateAuthState = jest.fn(); const mockResourceMetadata = { - resource: "https://example.com/api", + resource: "https://example.com/mcp", authorization_servers: ["https://custom-auth.example.com"], bearer_methods_supported: ["header", "body"], - resource_documentation: "https://example.com/api/docs", - resource_policy_uri: "https://example.com/api/policy", + resource_documentation: "https://example.com/mcp/docs", + resource_policy_uri: "https://example.com/mcp/policy", }; // Mock successful metadata discovery @@ -538,7 +540,7 @@ describe("AuthDebugger", () => { // Wait for the metadata to be fetched await waitFor(() => { expect(mockDiscoverOAuthProtectedResourceMetadata).toHaveBeenCalledWith( - "https://example.com", + "https://example.com/mcp", ); }); @@ -584,7 +586,7 @@ describe("AuthDebugger", () => { // Wait for the metadata fetch to fail await waitFor(() => { expect(mockDiscoverOAuthProtectedResourceMetadata).toHaveBeenCalledWith( - "https://example.com", + "https://example.com/mcp", ); }); @@ -594,7 +596,7 @@ describe("AuthDebugger", () => { expect.objectContaining({ resourceMetadataError: mockError, // Should use the original server URL as fallback - authServerUrl: new URL("https://example.com"), + authServerUrl: new URL("https://example.com/"), oauthStep: "client_registration", }), ); @@ -602,7 +604,7 @@ describe("AuthDebugger", () => { // Verify that regular OAuth metadata discovery was still called expect(mockDiscoverOAuthMetadata).toHaveBeenCalledWith( - new URL("https://example.com"), + new URL("https://example.com/"), ); }); }); diff --git a/client/src/lib/auth-types.ts b/client/src/lib/auth-types.ts index 5e8113ef..f8f927c6 100644 --- a/client/src/lib/auth-types.ts +++ b/client/src/lib/auth-types.ts @@ -30,6 +30,7 @@ export interface AuthDebuggerState { oauthStep: OAuthStep; resourceMetadata: OAuthProtectedResourceMetadata | null; resourceMetadataError: Error | null; + resource: URL | null; authServerUrl: URL | null; oauthMetadata: OAuthMetadata | null; oauthClientInfo: OAuthClientInformationFull | OAuthClientInformation | null; @@ -47,6 +48,7 @@ export const EMPTY_DEBUGGER_STATE: AuthDebuggerState = { oauthMetadata: null, resourceMetadata: null, resourceMetadataError: null, + resource: null, authServerUrl: null, oauthClientInfo: null, authorizationUrl: null, diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts index 5f10a783..d87b3ecd 100644 --- a/client/src/lib/oauth-state-machine.ts +++ b/client/src/lib/oauth-state-machine.ts @@ -6,6 +6,7 @@ import { startAuthorization, exchangeAuthorization, discoverOAuthProtectedResourceMetadata, + selectResourceURL, } from "@modelcontextprotocol/sdk/client/auth.js"; import { OAuthMetadataSchema, @@ -29,17 +30,15 @@ export const oauthTransitions: Record = { metadata_discovery: { canTransition: async () => true, execute: async (context) => { - let authServerUrl = new URL(context.serverUrl); + // Default to discovering from the server's URL + let authServerUrl = new URL("/", context.serverUrl); let resourceMetadata: OAuthProtectedResourceMetadata | null = null; let resourceMetadataError: Error | null = null; try { resourceMetadata = await discoverOAuthProtectedResourceMetadata( context.serverUrl, ); - if ( - resourceMetadata && - resourceMetadata.authorization_servers?.length - ) { + if (resourceMetadata?.authorization_servers?.length) { authServerUrl = new URL(resourceMetadata.authorization_servers[0]); } } catch (e) { @@ -50,6 +49,13 @@ export const oauthTransitions: Record = { } } + const resource: URL | undefined = await selectResourceURL( + context.serverUrl, + context.provider, + // we default to null, so swap it for undefined if not set + resourceMetadata ?? undefined, + ); + const metadata = await discoverOAuthMetadata(authServerUrl); if (!metadata) { throw new Error("Failed to discover OAuth metadata"); @@ -58,6 +64,7 @@ export const oauthTransitions: Record = { context.provider.saveServerMetadata(parsedMetadata); context.updateState({ resourceMetadata, + resource, resourceMetadataError, authServerUrl, oauthMetadata: parsedMetadata, @@ -113,6 +120,7 @@ export const oauthTransitions: Record = { clientInformation, redirectUrl: context.provider.redirectUrl, scope, + resource: context.state.resource ?? undefined, }, ); @@ -163,6 +171,7 @@ export const oauthTransitions: Record = { authorizationCode: context.state.authorizationCode, codeVerifier, redirectUri: context.provider.redirectUrl, + resource: context.state.resource ?? undefined, }); context.provider.saveTokens(tokens); diff --git a/package-lock.json b/package-lock.json index cf561a99..0e74be50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@modelcontextprotocol/inspector-cli": "^0.14.3", "@modelcontextprotocol/inspector-client": "^0.14.3", "@modelcontextprotocol/inspector-server": "^0.14.3", - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.1", "concurrently": "^9.0.1", "open": "^10.1.0", "shell-quote": "^1.8.2", @@ -44,7 +44,7 @@ "version": "0.14.3", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "commander": "^13.1.0", "spawn-rx": "^5.1.2" }, @@ -67,7 +67,7 @@ "version": "0.14.3", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", @@ -2006,9 +2006,9 @@ "link": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", - "integrity": "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.13.1.tgz", + "integrity": "sha512-8q6+9aF0yA39/qWT/uaIj6zTpC+Qu07DnN/lb9mjoquCJsAh6l3HyYqc9O3t2j7GilseOQOQimLg7W3By6jqvg==", "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", @@ -10987,7 +10987,7 @@ "version": "0.14.3", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "cors": "^2.8.5", "express": "^5.1.0", "ws": "^8.18.0", diff --git a/package.json b/package.json index 3461976b..8f48f85a 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@modelcontextprotocol/inspector-cli": "^0.14.3", "@modelcontextprotocol/inspector-client": "^0.14.3", "@modelcontextprotocol/inspector-server": "^0.14.3", - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.1", "concurrently": "^9.0.1", "open": "^10.1.0", "shell-quote": "^1.8.2", diff --git a/server/package.json b/server/package.json index 8f81a9df..9c248635 100644 --- a/server/package.json +++ b/server/package.json @@ -27,7 +27,7 @@ "typescript": "^5.6.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "cors": "^2.8.5", "express": "^5.1.0", "ws": "^8.18.0",