diff --git a/.changeset/resource-trailing-slash.md b/.changeset/resource-trailing-slash.md new file mode 100644 index 0000000000..ccb852b810 --- /dev/null +++ b/.changeset/resource-trailing-slash.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/client': patch +--- + +`auth()` no longer rejects protected resource metadata whose `resource` differs from the server URL only by a trailing slash (for example a server at `https://host/mcp` advertising `https://host/mcp/`). `checkResourceAllowed` now treats a single trailing slash as insignificant in both directions. diff --git a/packages/core-internal/src/shared/authUtils.ts b/packages/core-internal/src/shared/authUtils.ts index a73575eb73..8fed613702 100644 --- a/packages/core-internal/src/shared/authUtils.ts +++ b/packages/core-internal/src/shared/authUtils.ts @@ -25,7 +25,8 @@ export function withoutIssuer(body: unknown): unknown { /** * Checks if a requested resource URL matches a configured resource URL. * A requested resource matches if it has the same scheme, domain, port, - * and its path starts with the configured resource's path. + * and its path starts with the configured resource's path. A single trailing + * slash is not significant, so `/mcp` and `/mcp/` match in either direction. * * @param options - The options object * @param options.requestedResource - The resource URL being requested @@ -47,11 +48,6 @@ export function checkResourceAllowed({ return false; } - // Handle cases like requested=/foo and configured=/foo/ - if (requested.pathname.length < configured.pathname.length) { - return false; - } - // Check if the requested path starts with the configured path // Ensure both paths end with / for proper comparison // This ensures that if we have paths like "/api" and "/api/users", diff --git a/packages/core-internal/test/shared/authUtils.test.ts b/packages/core-internal/test/shared/authUtils.test.ts index 85a6d266b6..751b0a48db 100644 --- a/packages/core-internal/test/shared/authUtils.test.ts +++ b/packages/core-internal/test/shared/authUtils.test.ts @@ -78,12 +78,27 @@ describe('auth-utils', () => { ).toBe(true); }); - it('should handle trailing slashes vs no trailing slashes', () => { + it('should treat a single trailing slash as the same resource in both directions', () => { expect( checkResourceAllowed({ requestedResource: 'https://example.com/mcp/', configuredResource: 'https://example.com/mcp' }) ).toBe(true); expect( - checkResourceAllowed({ requestedResource: 'https://example.com/folder', configuredResource: 'https://example.com/folder/' }) + checkResourceAllowed({ requestedResource: 'https://example.com/mcp', configuredResource: 'https://example.com/mcp/' }) + ).toBe(true); + }); + + it('should still reject a shorter path that is not the configured one', () => { + expect( + checkResourceAllowed({ + requestedResource: 'https://example.com/folder', + configuredResource: 'https://example.com/folder/sub/' + }) + ).toBe(false); + expect( + checkResourceAllowed({ requestedResource: 'https://example.com/', configuredResource: 'https://example.com/mcp/' }) + ).toBe(false); + expect( + checkResourceAllowed({ requestedResource: 'https://example.com/mc', configuredResource: 'https://example.com/mcp/' }) ).toBe(false); }); });