Skip to content

Commit ec818af

Browse files
committed
feat(auth): expose access token subject
1 parent 161834d commit ec818af

4 files changed

Lines changed: 33 additions & 1 deletion

File tree

src/mcp/server/auth/provider.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import dataclass
2-
from typing import Generic, Literal, Protocol, TypeVar
2+
from typing import Any, Generic, Literal, Protocol, TypeVar
33
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
44

55
from pydantic import AnyUrl, BaseModel
@@ -40,6 +40,8 @@ class AccessToken(BaseModel):
4040
scopes: list[str]
4141
expires_at: int | None = None
4242
resource: str | None = None # RFC 8707 resource indicator
43+
subject: str | None = None
44+
claims: dict[str, Any] | None = None
4345

4446

4547
RegistrationErrorCode = Literal[

src/mcp/server/mcpserver/context.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from pydantic import AnyUrl, BaseModel
77

8+
from mcp.server.auth.middleware.auth_context import get_access_token
89
from mcp.server.context import LifespanContextT, RequestT, ServerRequestContext
910
from mcp.server.elicitation import (
1011
ElicitationResult,
@@ -213,6 +214,12 @@ def client_id(self) -> str | None:
213214
"""Get the client ID if available."""
214215
return self.request_context.meta.get("client_id") if self.request_context.meta else None # pragma: no cover
215216

217+
@property
218+
def subject(self) -> str | None:
219+
"""Get the subject from the authenticated access token if available."""
220+
access_token = get_access_token()
221+
return access_token.subject if access_token else None
222+
216223
@property
217224
def request_id(self) -> str:
218225
"""Get the unique ID for this request."""

tests/server/auth/middleware/test_auth_context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ def valid_access_token() -> AccessToken:
4141
client_id="test_client",
4242
scopes=["read", "write"],
4343
expires_at=int(time.time()) + 3600, # 1 hour from now
44+
subject="user_123",
45+
claims={"tenant_id": "tenant_456"},
4446
)
4547

4648

@@ -77,6 +79,9 @@ async def send(message: Message) -> None: # pragma: no cover
7779

7880
# Verify the access token was available during the call
7981
assert app.access_token_during_call == valid_access_token
82+
assert app.access_token_during_call is not None
83+
assert app.access_token_during_call.subject == "user_123"
84+
assert app.access_token_during_call.claims == {"tenant_id": "tenant_456"}
8085

8186
# Verify context is reset after middleware
8287
assert auth_context_var.get() is None

tests/server/mcpserver/test_server.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
from starlette.routing import Mount, Route
1111

1212
from mcp.client import Client
13+
from mcp.server.auth.middleware.auth_context import auth_context_var
14+
from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser
15+
from mcp.server.auth.provider import AccessToken
1316
from mcp.server.context import ServerRequestContext
1417
from mcp.server.experimental.request_context import Experimental
1518
from mcp.server.mcpserver import Context, MCPServer
@@ -1516,3 +1519,18 @@ async def test_report_progress_passes_related_request_id():
15161519
message="halfway",
15171520
related_request_id="req-abc-123",
15181521
)
1522+
1523+
1524+
def test_context_subject_reads_authenticated_access_token():
1525+
"""Test that Context exposes the authenticated token subject."""
1526+
access_token = AccessToken(
1527+
token="valid_token",
1528+
client_id="test_client",
1529+
scopes=["read"],
1530+
subject="user_123",
1531+
)
1532+
token = auth_context_var.set(AuthenticatedUser(access_token))
1533+
try:
1534+
assert Context().subject == "user_123"
1535+
finally:
1536+
auth_context_var.reset(token)

0 commit comments

Comments
 (0)