Skip to content

Commit 684fba4

Browse files
committed
fix: added RFC RECOMMENDED property(scopes_supported) to protected resource and authorization server metadata
1 parent c195495 commit 684fba4

File tree

2 files changed

+68
-3
lines changed

2 files changed

+68
-3
lines changed

litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,14 @@ async def callback(code: str, state: str):
398398
async def oauth_protected_resource_mcp(
399399
request: Request, mcp_server_name: Optional[str] = None
400400
):
401+
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
402+
global_mcp_server_manager,
403+
)
401404
# Get the correct base URL considering X-Forwarded-* headers
402405
request_base_url = get_request_base_url(request)
406+
mcp_server: Optional[MCPServer] = None
407+
if mcp_server_name:
408+
mcp_server = global_mcp_server_manager.get_mcp_server_by_name(mcp_server_name)
403409
return {
404410
"authorization_servers": [
405411
(
@@ -413,6 +419,7 @@ async def oauth_protected_resource_mcp(
413419
if mcp_server_name
414420
else f"{request_base_url}/mcp"
415421
), # this is what Claude will call
422+
"scopes_supported": mcp_server.scopes if mcp_server else [],
416423
}
417424

418425
"""
@@ -428,6 +435,9 @@ async def oauth_protected_resource_mcp(
428435
async def oauth_authorization_server_mcp(
429436
request: Request, mcp_server_name: Optional[str] = None
430437
):
438+
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
439+
global_mcp_server_manager,
440+
)
431441
# Get the correct base URL considering X-Forwarded-* headers
432442
request_base_url = get_request_base_url(request)
433443

@@ -442,16 +452,21 @@ async def oauth_authorization_server_mcp(
442452
else f"{request_base_url}/token"
443453
)
444454

455+
mcp_server: Optional[MCPServer] = None
456+
if mcp_server_name:
457+
mcp_server = global_mcp_server_manager.get_mcp_server_by_name(mcp_server_name)
458+
445459
return {
446460
"issuer": request_base_url, # point to your proxy
447461
"authorization_endpoint": authorization_endpoint,
448462
"token_endpoint": token_endpoint,
449463
"response_types_supported": ["code"],
464+
"scopes_supported": mcp_server.scopes if mcp_server else [],
450465
"grant_types_supported": ["authorization_code", "refresh_token"],
451466
"code_challenge_methods_supported": ["S256"],
452467
"token_endpoint_auth_methods_supported": ["client_secret_post"],
453468
# Claude expects a registration endpoint, even if we just fake it
454-
"registration_endpoint": f"{request_base_url}/{mcp_server_name}/register",
469+
"registration_endpoint": f"{request_base_url}/{mcp_server_name}/register" if mcp_server_name else f"{request_base_url}/register",
455470
}
456471

457472

tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,9 +556,33 @@ async def test_oauth_protected_resource_respects_x_forwarded_proto():
556556
from litellm.proxy._experimental.mcp_server.discoverable_endpoints import (
557557
oauth_protected_resource_mcp,
558558
)
559+
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
560+
global_mcp_server_manager,
561+
)
562+
from litellm.types.mcp import MCPAuth
563+
from litellm.types.mcp_server.mcp_server_manager import MCPServer
564+
from litellm.proxy._types import MCPTransport
559565
from fastapi import Request
560566
except ImportError:
561567
pytest.skip("MCP discoverable endpoints not available")
568+
# Clear registry
569+
global_mcp_server_manager.registry.clear()
570+
571+
# Create mock OAuth2 server
572+
oauth2_server = MCPServer(
573+
server_id="test_oauth_server",
574+
name="test_oauth",
575+
server_name="test_oauth",
576+
alias="test_oauth",
577+
transport=MCPTransport.http,
578+
auth_type=MCPAuth.oauth2,
579+
client_id="test_client_id",
580+
client_secret="test_client_secret",
581+
authorization_url="https://provider.com/oauth/authorize",
582+
token_url="https://provider.com/oauth/token",
583+
scopes=["read", "write"],
584+
)
585+
global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server
562586

563587
# Mock request with http base_url but X-Forwarded-Proto: https
564588
mock_request = MagicMock(spec=Request)
@@ -568,13 +592,14 @@ async def test_oauth_protected_resource_respects_x_forwarded_proto():
568592
# Call the endpoint
569593
response = await oauth_protected_resource_mcp(
570594
request=mock_request,
571-
mcp_server_name="test_server",
595+
mcp_server_name="test_oauth",
572596
)
573597

574598
# Verify response uses HTTPS URLs
575599
assert response["authorization_servers"][0].startswith(
576600
"https://litellm.example.com/"
577601
)
602+
assert response["scopes_supported"] == oauth2_server.scopes
578603

579604

580605
@pytest.mark.asyncio
@@ -584,9 +609,33 @@ async def test_oauth_authorization_server_respects_x_forwarded_proto():
584609
from litellm.proxy._experimental.mcp_server.discoverable_endpoints import (
585610
oauth_authorization_server_mcp,
586611
)
612+
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
613+
global_mcp_server_manager,
614+
)
615+
from litellm.types.mcp import MCPAuth
616+
from litellm.types.mcp_server.mcp_server_manager import MCPServer
617+
from litellm.proxy._types import MCPTransport
587618
from fastapi import Request
588619
except ImportError:
589620
pytest.skip("MCP discoverable endpoints not available")
621+
# Clear registry
622+
global_mcp_server_manager.registry.clear()
623+
624+
# Create mock OAuth2 server
625+
oauth2_server = MCPServer(
626+
server_id="test_oauth_server",
627+
name="test_oauth",
628+
server_name="test_oauth",
629+
alias="test_oauth",
630+
transport=MCPTransport.http,
631+
auth_type=MCPAuth.oauth2,
632+
client_id="test_client_id",
633+
client_secret="test_client_secret",
634+
authorization_url="https://provider.com/oauth/authorize",
635+
token_url="https://provider.com/oauth/token",
636+
scopes=["read", "write"],
637+
)
638+
global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server
590639

591640
# Mock request with http base_url but X-Forwarded-Proto: https
592641
mock_request = MagicMock(spec=Request)
@@ -596,14 +645,15 @@ async def test_oauth_authorization_server_respects_x_forwarded_proto():
596645
# Call the endpoint
597646
response = await oauth_authorization_server_mcp(
598647
request=mock_request,
599-
mcp_server_name="test_server",
648+
mcp_server_name="test_oauth",
600649
)
601650

602651
# Verify response uses HTTPS URLs
603652
assert response["authorization_endpoint"].startswith("https://litellm.example.com/")
604653
assert response["token_endpoint"].startswith("https://litellm.example.com/")
605654
assert response["registration_endpoint"].startswith("https://litellm.example.com/")
606655
assert response["grant_types_supported"] == ["authorization_code", "refresh_token"]
656+
assert response["scopes_supported"] == oauth2_server.scopes
607657

608658

609659
@pytest.mark.asyncio

0 commit comments

Comments
 (0)