Yann Jouanin Jeremiah Lowin commited on
Commit
4ee2b0f
·
unverified ·
1 Parent(s): e4bf853

Enable Protected Resource Metadata to provide resource_name and resou… (#1371)

Browse files
pyproject.toml CHANGED
@@ -7,7 +7,7 @@ dependencies = [
7
  "python-dotenv>=1.1.0",
8
  "exceptiongroup>=1.2.2",
9
  "httpx>=0.28.1",
10
- "mcp>=1.10.0",
11
  "openapi-pydantic>=0.5.1",
12
  "rich>=13.9.4",
13
  "cyclopts>=3.0.0",
 
7
  "python-dotenv>=1.1.0",
8
  "exceptiongroup>=1.2.2",
9
  "httpx>=0.28.1",
10
+ "mcp>=1.12.4",
11
  "openapi-pydantic>=0.5.1",
12
  "rich>=13.9.4",
13
  "cyclopts>=3.0.0",
src/fastmcp/server/auth/auth.py CHANGED
@@ -140,6 +140,8 @@ class RemoteAuthProvider(AuthProvider):
140
  token_verifier: TokenVerifier,
141
  authorization_servers: list[AnyHttpUrl],
142
  resource_server_url: AnyHttpUrl | str,
 
 
143
  ):
144
  """Initialize the remote auth provider.
145
 
@@ -153,6 +155,8 @@ class RemoteAuthProvider(AuthProvider):
153
  super().__init__(resource_server_url=resource_server_url)
154
  self.token_verifier = token_verifier
155
  self.authorization_servers = authorization_servers
 
 
156
 
157
  async def verify_token(self, token: str) -> AccessToken | None:
158
  """Verify token using the configured token verifier."""
@@ -171,6 +175,8 @@ class RemoteAuthProvider(AuthProvider):
171
  resource_url=self.resource_server_url,
172
  authorization_servers=self.authorization_servers,
173
  scopes_supported=self.token_verifier.required_scopes,
 
 
174
  )
175
 
176
 
 
140
  token_verifier: TokenVerifier,
141
  authorization_servers: list[AnyHttpUrl],
142
  resource_server_url: AnyHttpUrl | str,
143
+ resource_name: str | None = None,
144
+ resource_documentation: AnyHttpUrl | None = None,
145
  ):
146
  """Initialize the remote auth provider.
147
 
 
155
  super().__init__(resource_server_url=resource_server_url)
156
  self.token_verifier = token_verifier
157
  self.authorization_servers = authorization_servers
158
+ self.resource_name = resource_name
159
+ self.resource_documentation = resource_documentation
160
 
161
  async def verify_token(self, token: str) -> AccessToken | None:
162
  """Verify token using the configured token verifier."""
 
175
  resource_url=self.resource_server_url,
176
  authorization_servers=self.authorization_servers,
177
  scopes_supported=self.token_verifier.required_scopes,
178
+ resource_name=self.resource_name,
179
+ resource_documentation=self.resource_documentation,
180
  )
181
 
182
 
tests/server/auth/test_remote_auth_provider.py CHANGED
@@ -327,3 +327,66 @@ class TestRemoteAuthProviderIntegration:
327
  # The RemoteAuthProvider correctly returns the full MCP endpoint URL
328
  assert data["resource"] == "https://my-server.com/mcp/"
329
  assert data["authorization_servers"] == ["https://accounts.google.com/"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  # The RemoteAuthProvider correctly returns the full MCP endpoint URL
328
  assert data["resource"] == "https://my-server.com/mcp/"
329
  assert data["authorization_servers"] == ["https://accounts.google.com/"]
330
+
331
+ async def test_resource_name_field(self):
332
+ """Test that RemoteAuthProvider correctly returns the resource_name.
333
+
334
+ This test confirms that RemoteAuthProvider works correctly and returns
335
+ the exact resource_name specified.
336
+ """
337
+ token_verifier = SimpleTokenVerifier()
338
+ auth_provider = RemoteAuthProvider(
339
+ token_verifier=token_verifier,
340
+ authorization_servers=[AnyHttpUrl("https://accounts.google.com")],
341
+ resource_server_url="https://my-server.com/mcp/",
342
+ resource_name="My Test Resource",
343
+ )
344
+
345
+ mcp = FastMCP("test-server", auth=auth_provider)
346
+ mcp_http_app = mcp.http_app()
347
+
348
+ async with httpx.AsyncClient(
349
+ transport=httpx.ASGITransport(app=mcp_http_app),
350
+ base_url="https://my-server.com",
351
+ ) as client:
352
+ response = await client.get("/.well-known/oauth-protected-resource")
353
+
354
+ assert response.status_code == 200
355
+ data = response.json()
356
+
357
+ # The RemoteAuthProvider correctly returns the resource_name
358
+ assert data["resource_name"] == "My Test Resource"
359
+
360
+ async def test_resource_documentation_field(self):
361
+ """Test that RemoteAuthProvider correctly returns the resource_documentation.
362
+
363
+ This test confirms that RemoteAuthProvider works correctly and returns
364
+ the exact resource_documentation specified.
365
+ """
366
+ token_verifier = SimpleTokenVerifier()
367
+ auth_provider = RemoteAuthProvider(
368
+ token_verifier=token_verifier,
369
+ authorization_servers=[AnyHttpUrl("https://accounts.google.com")],
370
+ resource_server_url="https://my-server.com/mcp/",
371
+ resource_documentation=AnyHttpUrl(
372
+ "https://doc.my-server.com/resource-docs"
373
+ ),
374
+ )
375
+
376
+ mcp = FastMCP("test-server", auth=auth_provider)
377
+ mcp_http_app = mcp.http_app()
378
+
379
+ async with httpx.AsyncClient(
380
+ transport=httpx.ASGITransport(app=mcp_http_app),
381
+ base_url="https://my-server.com",
382
+ ) as client:
383
+ response = await client.get("/.well-known/oauth-protected-resource")
384
+
385
+ assert response.status_code == 200
386
+ data = response.json()
387
+
388
+ # The RemoteAuthProvider correctly returns the resource_documentation
389
+ assert (
390
+ data["resource_documentation"]
391
+ == "https://doc.my-server.com/resource-docs"
392
+ )
uv.lock CHANGED
@@ -574,7 +574,7 @@ requires-dist = [
574
  { name = "cyclopts", specifier = ">=3.0.0" },
575
  { name = "exceptiongroup", specifier = ">=1.2.2" },
576
  { name = "httpx", specifier = ">=0.28.1" },
577
- { name = "mcp", specifier = ">=1.10.0" },
578
  { name = "msgspec", specifier = ">=0.19.0" },
579
  { name = "openapi-core", specifier = ">=0.19.5" },
580
  { name = "openapi-pydantic", specifier = ">=0.5.1" },
@@ -943,7 +943,7 @@ wheels = [
943
 
944
  [[package]]
945
  name = "mcp"
946
- version = "1.12.3"
947
  source = { registry = "https://pypi.org/simple" }
948
  dependencies = [
949
  { name = "anyio" },
@@ -958,9 +958,9 @@ dependencies = [
958
  { name = "starlette" },
959
  { name = "uvicorn", marker = "sys_platform != 'emscripten'" },
960
  ]
961
- 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" }
962
  wheels = [
963
- { 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" },
964
  ]
965
 
966
  [[package]]
 
574
  { name = "cyclopts", specifier = ">=3.0.0" },
575
  { name = "exceptiongroup", specifier = ">=1.2.2" },
576
  { name = "httpx", specifier = ">=0.28.1" },
577
+ { name = "mcp", specifier = ">=1.12.4" },
578
  { name = "msgspec", specifier = ">=0.19.0" },
579
  { name = "openapi-core", specifier = ">=0.19.5" },
580
  { name = "openapi-pydantic", specifier = ">=0.5.1" },
 
943
 
944
  [[package]]
945
  name = "mcp"
946
+ version = "1.12.4"
947
  source = { registry = "https://pypi.org/simple" }
948
  dependencies = [
949
  { name = "anyio" },
 
958
  { name = "starlette" },
959
  { name = "uvicorn", marker = "sys_platform != 'emscripten'" },
960
  ]
961
+ 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" }
962
  wheels = [
963
+ { 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" },
964
  ]
965
 
966
  [[package]]