Skip to content

checkResourceAllowed rejects a protected resource that differs from the server URL only by a trailing slash (one direction only) #3041

Open

Description

@magnew

Summary

checkResourceAllowed (src/shared/auth-utils.ts) treats a protected resource that differs from the server URL only by a trailing slash as a mismatch, but only in one direction. An MCP client configured with https://host/mcp fails against a server whose RFC 9728 metadata advertises "resource": "https://host/mcp/", while https://host/mcp/ against "resource": "https://host/mcp" passes.

Affects the client auth() flow on both v1.x (verified on 1.31.0 and 1.32.1) and main.

Repro

import { checkResourceAllowed } from '@modelcontextprotocol/sdk/shared/auth-utils.js';

checkResourceAllowed({ requestedResource: 'https://host/mcp/', configuredResource: 'https://host/mcp' }); // true
checkResourceAllowed({ requestedResource: 'https://host/mcp',  configuredResource: 'https://host/mcp/' }); // false

Through auth() the second case surfaces as:

Error: Protected resource https://host/mcp/ does not match expected https://host/mcp (or origin)

Real-world trigger: an API gateway in front of the MCP server normalises paths to a trailing slash and builds the resource value from the normalised path. The client has no way to influence what the server advertises, and the user entering the server URL has no reason to know a trailing slash is significant.

Cause

The early return

// Handle cases like requested=/foo and configured=/foo/
if (requested.pathname.length < configured.pathname.length) {
    return false;
}

fires before the prefix check, which already normalises both paths to a trailing slash and would accept this case. The existing test should handle trailing slashes vs no trailing slashes asserts the asymmetric behaviour (/mcp/ vs /mcp is true, /folder vs /folder/ is false), so it appears intentional, but I could not find a rationale in the history. For comparison, the authorization-server issuer comparison in client/auth.ts deliberately tolerates a single trailing slash on either side.

Proposal

Remove the length early return so a single trailing slash is not treated as a different resource. The remaining origin check and normalised prefix check keep /mcp vs /mcp123, a different path, and a different origin rejected. The one existing assertion would flip to true.

If there is a reason the asymmetry must stay (for example an audience-binding argument I am missing), a note in the doc comment would help, and I am happy to send whichever change the maintainers prefer. We are currently carrying this as a pnpm patch and would like to drop it.

Activity

  1. AmineBenzaazaa commented on Oct 16, 2026

    @AmineBenzaazaa

    I can take this. Removing the length early-return is enough, since the prefix check below already normalises both paths to a trailing slash, and /mcp vs /mcp123, a shorter different path, and a different origin are still rejected. I'd flip the existing assertion and add cases for those. Happy to do v1.x as well. Could you assign me?

  2. added
    v1Issues / PRs related to v1.x
    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes
    on Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v1Issues / PRs related to v1.xv2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions