From 6c0ebb59fcecc47feefb2cb70c1c566d7f4f31a7 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Fri, 22 May 2026 03:56:41 -0700 Subject: [PATCH 1/2] docs(auth): clarify that resource_server_url must include the transport path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `resource_server_url` setting must equal the full public URL of the MCP endpoint, including the transport path, so that the value advertised in RFC 9728 Protected Resource Metadata matches the URL the client used to reach the server. RFC 9728 §3.3 requires strict equality. Update the `AuthSettings.resource_server_url` docstring to spell this out, fix the `examples/snippets/servers/oauth_server.py` snippet so it ends in `/mcp`, and select `/mcp` or `/sse` in the `simple-auth` example based on the chosen transport instead of hardcoding `/mcp` regardless of the `--transport=sse` flag. Refs #1264 --- README.v2.md | 8 ++++++-- .../servers/simple-auth/mcp_simple_auth/server.py | 7 +++++-- examples/snippets/servers/oauth_server.py | 8 ++++++-- src/mcp/server/auth/settings.py | 11 +++++++++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.v2.md b/README.v2.md index d0851c04e5..f26507f0be 100644 --- a/README.v2.md +++ b/README.v2.md @@ -1021,10 +1021,14 @@ mcp = MCPServer( "Weather Service", # Token verifier for authentication token_verifier=SimpleTokenVerifier(), - # Auth settings for RFC 9728 Protected Resource Metadata + # Auth settings for RFC 9728 Protected Resource Metadata. + # `resource_server_url` MUST be the full public URL of the MCP endpoint, including + # the transport path (e.g. `/mcp` for streamable-http, `/sse` for sse). RFC 9728 + # §3.3 requires strict equality between the client's resource identifier and the + # `resource` value advertised in the protected resource metadata. auth=AuthSettings( issuer_url=AnyHttpUrl("https://auth.example.com"), # Authorization Server URL - resource_server_url=AnyHttpUrl("http://localhost:3001"), # This server's URL + resource_server_url=AnyHttpUrl("http://localhost:3001/mcp"), # Public MCP endpoint URL required_scopes=["user"], ), ) diff --git a/examples/servers/simple-auth/mcp_simple_auth/server.py b/examples/servers/simple-auth/mcp_simple_auth/server.py index 0320871b12..d4bcec8854 100644 --- a/examples/servers/simple-auth/mcp_simple_auth/server.py +++ b/examples/servers/simple-auth/mcp_simple_auth/server.py @@ -126,9 +126,12 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http # Parse auth server URL auth_server_url = AnyHttpUrl(auth_server) - # Create settings + # Create settings. server_url is the public URL of the MCP endpoint and must + # include the transport path so it matches the URL the client used to reach + # the server (RFC 9728 §3.3 strict equality). host = "localhost" - server_url = f"http://{host}:{port}/mcp" + transport_path = "/sse" if transport == "sse" else "/mcp" + server_url = f"http://{host}:{port}{transport_path}" settings = ResourceServerSettings( host=host, port=port, diff --git a/examples/snippets/servers/oauth_server.py b/examples/snippets/servers/oauth_server.py index 962ef0615e..d3e4a30bfd 100644 --- a/examples/snippets/servers/oauth_server.py +++ b/examples/snippets/servers/oauth_server.py @@ -21,10 +21,14 @@ async def verify_token(self, token: str) -> AccessToken | None: "Weather Service", # Token verifier for authentication token_verifier=SimpleTokenVerifier(), - # Auth settings for RFC 9728 Protected Resource Metadata + # Auth settings for RFC 9728 Protected Resource Metadata. + # `resource_server_url` MUST be the full public URL of the MCP endpoint, including + # the transport path (e.g. `/mcp` for streamable-http, `/sse` for sse). RFC 9728 + # §3.3 requires strict equality between the client's resource identifier and the + # `resource` value advertised in the protected resource metadata. auth=AuthSettings( issuer_url=AnyHttpUrl("https://auth.example.com"), # Authorization Server URL - resource_server_url=AnyHttpUrl("http://localhost:3001"), # This server's URL + resource_server_url=AnyHttpUrl("http://localhost:3001/mcp"), # Public MCP endpoint URL required_scopes=["user"], ), ) diff --git a/src/mcp/server/auth/settings.py b/src/mcp/server/auth/settings.py index 1649826db2..2c59b9e7aa 100644 --- a/src/mcp/server/auth/settings.py +++ b/src/mcp/server/auth/settings.py @@ -25,6 +25,13 @@ class AuthSettings(BaseModel): # Resource Server settings (when operating as RS only) resource_server_url: AnyHttpUrl | None = Field( ..., - description="The URL of the MCP server to be used as the resource identifier " - "and base route to look up OAuth Protected Resource Metadata.", + description=( + "The full public URL of this MCP server, used as the resource identifier " + "and base route to look up OAuth Protected Resource Metadata (RFC 9728). " + "Must include the transport path (e.g. https://example.com/mcp for " + "streamable-http, https://example.com/sse for sse) so that the value " + "advertised in protected resource metadata exactly matches the URL the " + "client used to reach the server. RFC 9728 §3.3 requires strict equality " + "between the client's resource identifier and this value." + ), ) From d54b216cab559dff95ed0047ef6c15857ed5231a Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Fri, 22 May 2026 04:05:48 -0700 Subject: [PATCH 2/2] docs(auth): replace section symbol with ASCII in RFC 9728 comments The README code snippet at L1024-1028 is run through ruff via pytest_examples. On Windows the ruff subprocess pipe defaults to cp1252 and fails to decode the \xc2\xa7 byte sequence, breaking the 10 Windows CI jobs with 'stream did not contain valid UTF-8'. Linux and macOS pipes default to UTF-8 so they passed. Replace \xc2\xa7 with the word 'section' across the four files I introduced it in. Pre-existing \xc2\xa7 references elsewhere in the tree are unaffected. --- README.v2.md | 4 ++-- examples/servers/simple-auth/mcp_simple_auth/server.py | 2 +- examples/snippets/servers/oauth_server.py | 4 ++-- src/mcp/server/auth/settings.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.v2.md b/README.v2.md index f26507f0be..20567e534e 100644 --- a/README.v2.md +++ b/README.v2.md @@ -1024,8 +1024,8 @@ mcp = MCPServer( # Auth settings for RFC 9728 Protected Resource Metadata. # `resource_server_url` MUST be the full public URL of the MCP endpoint, including # the transport path (e.g. `/mcp` for streamable-http, `/sse` for sse). RFC 9728 - # §3.3 requires strict equality between the client's resource identifier and the - # `resource` value advertised in the protected resource metadata. + # section 3.3 requires strict equality between the client's resource identifier and + # the `resource` value advertised in the protected resource metadata. auth=AuthSettings( issuer_url=AnyHttpUrl("https://auth.example.com"), # Authorization Server URL resource_server_url=AnyHttpUrl("http://localhost:3001/mcp"), # Public MCP endpoint URL diff --git a/examples/servers/simple-auth/mcp_simple_auth/server.py b/examples/servers/simple-auth/mcp_simple_auth/server.py index d4bcec8854..b33a40eaeb 100644 --- a/examples/servers/simple-auth/mcp_simple_auth/server.py +++ b/examples/servers/simple-auth/mcp_simple_auth/server.py @@ -128,7 +128,7 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http # Create settings. server_url is the public URL of the MCP endpoint and must # include the transport path so it matches the URL the client used to reach - # the server (RFC 9728 §3.3 strict equality). + # the server (RFC 9728 section 3.3 strict equality). host = "localhost" transport_path = "/sse" if transport == "sse" else "/mcp" server_url = f"http://{host}:{port}{transport_path}" diff --git a/examples/snippets/servers/oauth_server.py b/examples/snippets/servers/oauth_server.py index d3e4a30bfd..dfd5de58ab 100644 --- a/examples/snippets/servers/oauth_server.py +++ b/examples/snippets/servers/oauth_server.py @@ -24,8 +24,8 @@ async def verify_token(self, token: str) -> AccessToken | None: # Auth settings for RFC 9728 Protected Resource Metadata. # `resource_server_url` MUST be the full public URL of the MCP endpoint, including # the transport path (e.g. `/mcp` for streamable-http, `/sse` for sse). RFC 9728 - # §3.3 requires strict equality between the client's resource identifier and the - # `resource` value advertised in the protected resource metadata. + # section 3.3 requires strict equality between the client's resource identifier and + # the `resource` value advertised in the protected resource metadata. auth=AuthSettings( issuer_url=AnyHttpUrl("https://auth.example.com"), # Authorization Server URL resource_server_url=AnyHttpUrl("http://localhost:3001/mcp"), # Public MCP endpoint URL diff --git a/src/mcp/server/auth/settings.py b/src/mcp/server/auth/settings.py index 2c59b9e7aa..d54e9898e3 100644 --- a/src/mcp/server/auth/settings.py +++ b/src/mcp/server/auth/settings.py @@ -31,7 +31,7 @@ class AuthSettings(BaseModel): "Must include the transport path (e.g. https://example.com/mcp for " "streamable-http, https://example.com/sse for sse) so that the value " "advertised in protected resource metadata exactly matches the URL the " - "client used to reach the server. RFC 9728 §3.3 requires strict equality " - "between the client's resource identifier and this value." + "client used to reach the server. RFC 9728 section 3.3 requires strict " + "equality between the client's resource identifier and this value." ), )