MCP OAuth strips URL path breaking resource validation

NousResearch/hermes-agent · cluster 10271 · mcp

Data as of

Nothing open on GitHub. Fix merged in #16031. Check it covers your case.

The short answer

0open issues
0open PRs
10items grouped
Known?
Yes. 10 related items grouped here, 4 issues already closed.
Fix event
A fix was merged to main in PR #16031. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #16031. That is separate from the fix event.
Still open
Nothing open on GitHub.

Multiple bug reports and fix attempts address an issue where MCP OAuth drops the path component from server URLs, causing protected resource validation failures against servers like Notion and GitHub. Several parallel fix PRs propose preserving the full server URL path when constructing the OAuthClientProvider.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #16031 fix(mcp-oauth): preserve server_url path for protected-resource validation

    merged to main

    files: tests/tools/test_mcp_oauth.py, tools/mcp_oauth.py, tools/mcp_oauth_manager.py

    Show the diff
    • tests/tools/test_mcp_oauth.py
      @@ -491,11 +491,36 @@ def test_configure_callback_port_uses_explicit_port():
           assert cfg["_resolved_port"] == 54321
       
       
      -def test_parse_base_url_strips_path():
      -    """_parse_base_url drops path components for OAuth discovery."""
      -    from tools.mcp_oauth import _parse_base_url
      +def test_build_oauth_auth_preserves_server_url_path():
      +    """server_url with path is forwarded to OAuthClientProvider unmodified.
      +
      +    Regression for #16015: previously ``_parse_base_url`` stripped the path,
      +    collapsing ``https://mcp.notion.com/mcp`` to ``https://mcp.notion.com`` and
      +    breaking RFC 9728 protected-resource validation against servers whose PRM
      +    advertises a path-scoped resource (Notion). The MCP SDK strips the path
      +    itself for authorization-server discovery via
      +    ``OAuthContext.get_authorization_base_url``; Hermes must not pre-strip.
      +    """
      +    from tools import mcp_oauth
      +
      +    captured: dict = {}
      +
      +    class _FakeProvider:
      +        def __init__(self, **kwargs):
      +            captured.update(kwargs)
      +
      +    with patch.object(mcp_oauth, "_OAUTH_AVAILABLE", True), \
      +         patch.object(mcp_oauth, "OAuthClientProvider", _FakeProvider), \
      +         patch.object(mcp_oauth, "_is_interactive", return_value=True), \
      +         patch.object(mcp_oauth, "_maybe_preregister_client"), \
      +         patch.object(mcp_oauth, "HermesTokenStorage") as mock_storage_cls:
      +        mock_storage_cls.return_value = MagicMock(has_cached_tokens=[redacted:secret-assignment] True)
      +        build_oauth_auth(
      +            server_name="notion",
      +            server_url="https://mcp.notion.com/mcp",
      +            oauth_config={},
      +        )
      +
      +    assert captured["server_url"] == "https://mcp.notion.com/mcp"
       
      -    assert _parse_base_url("https://example.com/mcp/v1") == "https://example.com"
      -    assert _parse_base_url("https://example.com") == "https://example.com"
      -    assert _parse_base_url("https://host.example.com:8080/api") == "https://host.example.com:8080"
       
    • tools/mcp_oauth.py
      @@ -519,12 +519,6 @@ def _maybe_preregister_client(
           logger.debug("Pre-registered client_id=%s for '%s'", client_id, storage._server_name)
       
       
      -def _parse_base_url(server_url: str) -> str:
      -    """Strip path component from server URL, returning the base origin."""
      -    parsed = urlparse(server_url)
      -    return f"{parsed.scheme}://{parsed.netloc}"
      -
      -
       def build_oauth_auth(
           server_name: str,
           server_url: str,
      @@ -570,7 +564,7 @@ def build_oauth_auth(
           _maybe_preregister_client(storage, cfg, client_metadata)
       
           return OAuthClientProvider(
      -        server_url=_parse_base_url(server_url),
      +        server_url=server_url,
               client_metadata=client_metadata,
               storage=storage,
               redirect_handler=_redirect_handler,
    • tools/mcp_oauth_manager.py
      @@ -362,7 +362,6 @@ def _build_provider(
                   _configure_callback_port,
                   _is_interactive,
                   _maybe_preregister_client,
      -            _parse_base_url,
                   _redirect_handler,
                   _wait_for_callback,
               )
      @@ -387,7 +386,7 @@ def _build_provider(
       
               return _HERMES_PROVIDER_CLS(
                   server_name=server_name,
      -            server_url=_parse_base_url(entry.server_url),
      +            server_url=entry.server_url,
                   client_metadata=client_metadata,
                   storage=storage,
                   redirect_handler=_redirect_handler,

5 PRs closed without merging

From the close comments: done elsewhere 3 · closed without comment 1 · duplicate 1

  • #10278 fix(mcp): pass full server URL to OAuthClientProvider, strip trailing slash · done elsewhere → d09ab8f, #16031
  • #10932 fix(mcp): preserve OAuth server URL paths · done elsewhere → d09ab8f, #16031
  • #11808 fix: preserve server URL path in MCP OAuth resource matching · closed without comment
  • #11837 fix: preserve server URL path in MCP OAuth resource matching · done elsewhere → d09ab8f, #16031
  • #15747 fix(mcp-oauth): preserve server URL path when constructing OAuthClientProvider · duplicate → #10932, #13230, #13853, #10271
Closed issues · 4
  • Closed#10271[Bug]: OAuth 2.1 PKCE fails for servers with a path in their endpoint URL (RFC 8707 resource mismatch)
  • Closed#11807MCP OAuth: server_url path stripped breaks GitHub MCP resource validation
  • Closed#11838MCP OAuth: server_url path stripped breaks GitHub MCP resource validation
  • Closed#16015MCP OAuth strips /mcp from server_url, breaking Notion protected resource validation
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.23: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
mcp
Merged PRs
#16031
Closed issues
4
Attempts
5 PRs closed without merging
Fix link checked
26 Apr 2026

For your agent

The same facts, typed.