diff --git a/packages/core/test/mcp-oauth.test.ts b/packages/core/test/mcp-oauth.test.ts index c40ed2e176fe..0a130f56c0f5 100644 --- a/packages/core/test/mcp-oauth.test.ts +++ b/packages/core/test/mcp-oauth.test.ts @@ -437,6 +437,40 @@ describe("MCP OAuth", () => { expect(tokenRequests[0]?.get("resource")).toBe(url) }) + test("preserves a pathless resource from metadata", async () => { + const tokenRequests: URLSearchParams[] = [] + const server = Bun.serve({ + port: 0, + async fetch(request) { + const url = new URL(request.url) + if (url.pathname === "/.well-known/oauth-authorization-server") + return Response.json({ + issuer: url.origin, + authorization_endpoint: `${url.origin}/authorize`, + token_endpoint: `${url.origin}/token`, + response_types_supported: ["code"], + }) + if (url.pathname === "/.well-known/oauth-protected-resource/mcp") + return Response.json({ resource: url.origin, authorization_servers: [url.origin] }) + if (request.method === "POST" && url.pathname === "/token") { + tokenRequests.push(new URLSearchParams(await request.text())) + return Response.json({ access_token: "next", token_type: "Bearer" }) + } + return new Response(null, { status: 404 }) + }, + }) + const url = `${server.url.origin}/mcp` + const oauthProvider = await connectProvider( + remote(url), + memoryCredentials([credential({ access: "expired", refresh: "refresh", url })]), + ) + + await auth(oauthProvider, { serverUrl: url }).finally(() => server.stop(true)) + + expect(tokenRequests[0]?.get("grant_type")).toBe("refresh_token") + expect(tokenRequests[0]?.get("resource")).toBe(server.url.origin) + }) + test("finds resource metadata through the 401 header when the well-known path is not served", async () => { const server = Bun.serve({ port: 0, diff --git a/patches/@modelcontextprotocol%2Fclient@2.3.1.patch b/patches/@modelcontextprotocol%2Fclient@2.3.1.patch index b8291f818c6a..a66dbb462d15 100644 --- a/patches/@modelcontextprotocol%2Fclient@2.3.1.patch +++ b/patches/@modelcontextprotocol%2Fclient@2.3.1.patch @@ -1,7 +1,16 @@ diff --git a/dist/index.cjs b/dist/index.cjs -index 71cf63076b073284aa14f63cd41efa653e8def0f..d4679fc706c76b87949fd674857cea54793e82ce 100644 +index 71cf63076b073284aa14f63cd41efa653e8def0f..789b1636fc6f0adf8137addb648b2af0ac8d9e25 100644 --- a/dist/index.cjs +++ b/dist/index.cjs +@@ -725,7 +725,7 @@ + } + if (freshDiscoveryState) await provider.saveDiscoveryState?.(freshDiscoveryState); + const selectedResource = await selectResourceURL(serverUrl, provider, resourceMetadata); +- const resource = selectedResource && resourceMetadata && !provider.validateResourceURL ? resourceMetadata.resource : selectedResource; ++ const resource = selectedResource && resourceMetadata && selectedResource.href === new URL(resourceMetadata.resource).href ? resourceMetadata.resource : selectedResource; + if (resource) await provider.saveResourceUrl?.(resourceIndicatorToString(resource)); + const resolvedScope = determineScope({ + requestedScope: scope, @@ -1038,7 +1038,6 @@ async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, o else { const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname); @@ -28,9 +37,18 @@ index 71cf63076b073284aa14f63cd41efa653e8def0f..d4679fc706c76b87949fd674857cea54 console.warn(`[mcp-sdk] excluding tool '${tool.name}' from tools/list: invalid x-mcp-header declaration — ${scan.reason}`); return false; diff --git a/dist/index.mjs b/dist/index.mjs -index c1a09a82b8121d6a1b038ccc6d02bae46d55dbcf..bf457bdfdef05ad8d09f3fbb94c6ac1aecc59487 100644 +index c1a09a82b8121d6a1b038ccc6d02bae46d55dbcf..3356a2e3df5122dc993b3a0cc39a73a8581b0cbe 100644 --- a/dist/index.mjs +++ b/dist/index.mjs +@@ -722,7 +722,7 @@ + } + if (freshDiscoveryState) await provider.saveDiscoveryState?.(freshDiscoveryState); + const selectedResource = await selectResourceURL(serverUrl, provider, resourceMetadata); +- const resource = selectedResource && resourceMetadata && !provider.validateResourceURL ? resourceMetadata.resource : selectedResource; ++ const resource = selectedResource && resourceMetadata && selectedResource.href === new URL(resourceMetadata.resource).href ? resourceMetadata.resource : selectedResource; + if (resource) await provider.saveResourceUrl?.(resourceIndicatorToString(resource)); + const resolvedScope = determineScope({ + requestedScope: scope, @@ -1035,7 +1035,6 @@ async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, o else { const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);