Skip to content

[v1.x] Preserve the exact OAuth resource indicator (backport #2925) - #3462

Draft
pcarleton wants to merge 2 commits into
v1.xfrom
paulc/v1x-backport-2925
Draft

[v1.x] Preserve the exact OAuth resource indicator (backport #2925)#3462
pcarleton wants to merge 2 commits into
v1.xfrom
paulc/v1x-backport-2925

Conversation

@pcarleton

@pcarleton pcarleton commented Sep 6, 2026

Copy link
Copy Markdown
Member

Backport of #2925 to the 1.x line, plus a small compat shim so the fix holds on pydantic < 2.12 (which 1.x still supports).

Motivation and Context

RFC 8707's resource parameter must be the protected resource's identifier byte-for-byte. On 1.x a path-less resource from the server's Protected Resource Metadata (http://host:port) is parsed into AnyHttpUrl and re-serialised as http://host:port/, so the client sends a different identifier in the authorization and token requests (#2578). main fixed this in #2925 via url_preserve_empty_path=True on the OAuth metadata models; that was never backported.

This now matters for conformance: modelcontextprotocol/conformance#488 adds a resource-parameter-matches-prm check (FAILURE, RFC 8707 MUST) to the scored auth/metadata-var2 client scenario, and the 1.x line fails it without this change while main, go, rust, csharp and the TS SDK pass.

What's in here

  1. Cherry-pick of Preserve empty URL paths on OAuth metadata models #2925 (url_preserve_empty_path=True on OAuthClientMetadata, OAuthMetadata, ProtectedResourceMetadata; the v2 migration-guide hunk is dropped since that file doesn't exist on 1.x).
  2. A compat commit for pydantic 2.11: the 1.x dependency range (and the lowest-direct CI leg) still resolves pydantic 2.11, where url_preserve_empty_path is silently ignored. ProtectedResourceMetadata now records the wire string (resource_str) and OAuthContext.get_resource_url() / _validate_resource_match use it, so the resource parameter is echoed verbatim on every supported pydantic version. The config is spelled as a cast dict so the 2.11 type stubs accept it, and the cherry-picked issuer assertion is made version-tolerant.

How Has This Been Tested?

  • uv run --frozen pytest tests/client/test_auth.py → 129 passed (new test test_get_resource_url_echoes_pathless_prm_resource_verbatim); ruff + pyright clean on the touched files; behaviour checked on pydantic 2.11.7 and 2.13.4.
  • Conformance (modelcontextprotocol/conformance main + feat(client/auth): check the resource parameter matches the PRM-published identifier conformance#488 ), client --scenario auth/metadata-var2 --spec-version 2025-11-25 with .github/actions/conformance/client.py:
    • before (v1.x @ 8c2fa6e): resource-parameter-matches-prm FAILURE → scenario FAILED
    • after (this branch): resource-parameter-matches-prm SUCCESS, 18/18 checks, scenario PASSED; auth/metadata-default PASSED before and after.

Breaking Changes

None intended. str(prm.resource) is unchanged on pydantic < 2.12 (still normalised); code that needs the exact identifier should use the new ProtectedResourceMetadata.resource_str.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

AI Disclaimer

Prepared with Claude Code; reviewed the diff and the test/conformance output.

Kludex and others added 2 commits September 6, 2026 19:22
The 1.x line still allows pydantic 2.11 (Python < 3.14), where the
url_preserve_empty_path config from #2925 is silently ignored and a
path-less PRM resource still renders with a trailing slash. Record the wire
string on ProtectedResourceMetadata (resource_str) and use it for the
resource parameter, so the client echoes the server's identifier verbatim on
every supported pydantic version. Also spell the config as a cast dict so the
2.11 type stubs accept it, and make the cherry-picked issuer assertion
version-tolerant.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants