From 456b22ecf40429e805f8b3a3fe784d4664bcfb10 Mon Sep 17 00:00:00 2001 From: Yann Jouanin <4557670+yannj-fr@users.noreply.github.com> Date: Sun, 10 Aug 2025 15:01:12 +0200 Subject: [PATCH] =?UTF-8?q?Enable=20Protected=20Resource=20Metadata=20to?= =?UTF-8?q?=20provide=20resource=5Fname=20and=20resou=E2=80=A6=20(#1371)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> --- pyproject.toml | 2 +- src/fastmcp/server/auth/auth.py | 6 ++ .../server/auth/test_remote_auth_provider.py | 63 +++++++++++++++++++ uv.lock | 8 +-- 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f8e734dda..fa72fbd05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ dependencies = [ "python-dotenv>=1.1.0", "exceptiongroup>=1.2.2", "httpx>=0.28.1", - "mcp>=1.10.0", + "mcp>=1.12.4", "openapi-pydantic>=0.5.1", "rich>=13.9.4", "cyclopts>=3.0.0", diff --git a/src/fastmcp/server/auth/auth.py b/src/fastmcp/server/auth/auth.py index a41d19fdb..2dd316793 100644 --- a/src/fastmcp/server/auth/auth.py +++ b/src/fastmcp/server/auth/auth.py @@ -140,6 +140,8 @@ class RemoteAuthProvider(AuthProvider): token_verifier: TokenVerifier, authorization_servers: list[AnyHttpUrl], resource_server_url: AnyHttpUrl | str, + resource_name: str | None = None, + resource_documentation: AnyHttpUrl | None = None, ): """Initialize the remote auth provider. @@ -153,6 +155,8 @@ class RemoteAuthProvider(AuthProvider): super().__init__(resource_server_url=resource_server_url) self.token_verifier = token_verifier self.authorization_servers = authorization_servers + self.resource_name = resource_name + self.resource_documentation = resource_documentation async def verify_token(self, token: str) -> AccessToken | None: """Verify token using the configured token verifier.""" @@ -171,6 +175,8 @@ class RemoteAuthProvider(AuthProvider): resource_url=self.resource_server_url, authorization_servers=self.authorization_servers, scopes_supported=self.token_verifier.required_scopes, + resource_name=self.resource_name, + resource_documentation=self.resource_documentation, ) diff --git a/tests/server/auth/test_remote_auth_provider.py b/tests/server/auth/test_remote_auth_provider.py index 4e9414137..8400452fb 100644 --- a/tests/server/auth/test_remote_auth_provider.py +++ b/tests/server/auth/test_remote_auth_provider.py @@ -327,3 +327,66 @@ class TestRemoteAuthProviderIntegration: # The RemoteAuthProvider correctly returns the full MCP endpoint URL assert data["resource"] == "https://my-server.com/mcp/" assert data["authorization_servers"] == ["https://accounts.google.com/"] + + async def test_resource_name_field(self): + """Test that RemoteAuthProvider correctly returns the resource_name. + + This test confirms that RemoteAuthProvider works correctly and returns + the exact resource_name specified. + """ + token_verifier = SimpleTokenVerifier() + auth_provider = RemoteAuthProvider( + token_verifier=token_verifier, + authorization_servers=[AnyHttpUrl("https://accounts.google.com")], + resource_server_url="https://my-server.com/mcp/", + resource_name="My Test Resource", + ) + + mcp = FastMCP("test-server", auth=auth_provider) + mcp_http_app = mcp.http_app() + + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=mcp_http_app), + base_url="https://my-server.com", + ) as client: + response = await client.get("/.well-known/oauth-protected-resource") + + assert response.status_code == 200 + data = response.json() + + # The RemoteAuthProvider correctly returns the resource_name + assert data["resource_name"] == "My Test Resource" + + async def test_resource_documentation_field(self): + """Test that RemoteAuthProvider correctly returns the resource_documentation. + + This test confirms that RemoteAuthProvider works correctly and returns + the exact resource_documentation specified. + """ + token_verifier = SimpleTokenVerifier() + auth_provider = RemoteAuthProvider( + token_verifier=token_verifier, + authorization_servers=[AnyHttpUrl("https://accounts.google.com")], + resource_server_url="https://my-server.com/mcp/", + resource_documentation=AnyHttpUrl( + "https://doc.my-server.com/resource-docs" + ), + ) + + mcp = FastMCP("test-server", auth=auth_provider) + mcp_http_app = mcp.http_app() + + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=mcp_http_app), + base_url="https://my-server.com", + ) as client: + response = await client.get("/.well-known/oauth-protected-resource") + + assert response.status_code == 200 + data = response.json() + + # The RemoteAuthProvider correctly returns the resource_documentation + assert ( + data["resource_documentation"] + == "https://doc.my-server.com/resource-docs" + ) diff --git a/uv.lock b/uv.lock index df705fd41..6877fe25a 100644 --- a/uv.lock +++ b/uv.lock @@ -574,7 +574,7 @@ requires-dist = [ { name = "cyclopts", specifier = ">=3.0.0" }, { name = "exceptiongroup", specifier = ">=1.2.2" }, { name = "httpx", specifier = ">=0.28.1" }, - { name = "mcp", specifier = ">=1.10.0" }, + { name = "mcp", specifier = ">=1.12.4" }, { name = "msgspec", specifier = ">=0.19.0" }, { name = "openapi-core", specifier = ">=0.19.5" }, { name = "openapi-pydantic", specifier = ">=0.5.1" }, @@ -943,7 +943,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.12.3" +version = "1.12.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -958,9 +958,9 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/19/9955e2df5384ff5dd25d38f8e88aaf89d2d3d9d39f27e7383eaf0b293836/mcp-1.12.3.tar.gz", hash = "sha256:ab2e05f5e5c13e1dc90a4a9ef23ac500a6121362a564447855ef0ab643a99fed", size = 427203, upload-time = "2025-07-31T18:36:36.795Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/88/f6cb7e7c260cd4b4ce375f2b1614b33ce401f63af0f49f7141a2e9bf0a45/mcp-1.12.4.tar.gz", hash = "sha256:0765585e9a3a5916a3c3ab8659330e493adc7bd8b2ca6120c2d7a0c43e034ca5", size = 431148, upload-time = "2025-08-07T20:31:18.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8b/0be74e3308a486f1d127f3f6767de5f9f76454c9b4183210c61cc50999b6/mcp-1.12.3-py3-none-any.whl", hash = "sha256:5483345bf39033b858920a5b6348a303acacf45b23936972160ff152107b850e", size = 158810, upload-time = "2025-07-31T18:36:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/ad/68/316cbc54b7163fa22571dcf42c9cc46562aae0a021b974e0a8141e897200/mcp-1.12.4-py3-none-any.whl", hash = "sha256:7aa884648969fab8e78b89399d59a683202972e12e6bc9a1c88ce7eda7743789", size = 160145, upload-time = "2025-08-07T20:31:15.69Z" }, ] [[package]]