Skip to content

fix(auth): normalize explicit default ports in resource_url_from_server_url (RFC 3986) - #3310

Closed
teddiesloco wants to merge 2 commits into
modelcontextprotocol:mainfrom
teddiesloco:fix/oauth-resource-url-default-port
Closed

fix(auth): normalize explicit default ports in resource_url_from_server_url (RFC 3986)#3310
teddiesloco wants to merge 2 commits into
modelcontextprotocol:mainfrom
teddiesloco:fix/oauth-resource-url-default-port

Conversation

@teddiesloco

Copy link
Copy Markdown

Summary

Fixes #3297.

resource_url_from_server_url() lowercases the scheme/authority and strips URL fragments, but previously preserved explicitly specified default ports (:80 for HTTP, :443 for HTTPS).

Per RFC 3986 §6.2.3 (Scheme-Based Normalization), an explicit default port is equivalent to an omitted port. Furthermore, Pydantic's HttpUrl automatically normalizes metadata URLs by dropping default ports. Because resource_url_from_server_url() preserved them, check_resource_allowed() would reject an otherwise identical resource (e.g. comparing https://example.com:443/mcp against https://example.com/mcp), leading to false-positive authorization failures.

Changes

  1. _canonical_netloc() Helper: Normalizes netloc by stripping default port 80 for http and 443 for https, while preserving non-default ports, userinfo, and IPv6 literals.
  2. check_resource_allowed(): Canonicalizes both requested_resource and configured_resource before parsing to guarantee consistent comparison across equivalent URL representations.
  3. Tests:
    • Added unit tests verifying default ports 80 and 443 are stripped from canonical resource URLs while non-default ports like 8443 or 8080 are preserved.
    • Added tests verifying check_resource_allowed() correctly evaluates matching resources regardless of explicit default port notation in either parameter.

Verification

  • Ran uv run pytest tests/shared/test_auth_utils.py tests/client/test_auth.py: 155 passed, 1 xfailed (pre-existing).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/mcp/shared/auth_utils.py Outdated
- _canonical_netloc now catches ValueError from parsed.port for
  malformed explicit ports (e.g. out-of-range), falling back to the
  original netloc instead of letting check_resource_allowed() crash
- add test coverage for the userinfo-in-netloc and IPv6-literal
  branches that were previously untested (CI requires 100% coverage)
- add regression test for the malformed-port fallback
@github-actions github-actions Bot added the missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md) label Aug 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the contribution. This repository only keeps pull requests open when they're linked to an issue that a maintainer has assigned to the author — CONTRIBUTING.md explains why and how we work. This PR has been closed for now because you aren't currently assigned to #3297.

If a maintainer would like this change as a PR from you, they'll assign you to #3297 and this PR will reopen automatically — there's nothing more you need to do. (If you opened the issue, this PR already shows up on its timeline.)

There's no need to open a new PR — this one will be reopened. While it's closed, please push any updates as new commits rather than force-pushing, since GitHub can't reopen a PR whose branch has been rewritten.

Maintainers: reopening this PR, removing the missing-issue-link label, or adding bypass-issue-check bypasses the check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

resource_url_from_server_url preserves explicit default ports

1 participant