Skip to content

Fix get_access_token() returning stale token after OAuth refresh#2505

Merged
jlowin merged 2 commits intomainfrom
fix/stale-access-token-1863
Dec 1, 2025
Merged

Fix get_access_token() returning stale token after OAuth refresh#2505
jlowin merged 2 commits intomainfrom
fix/stale-access-token-1863

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Dec 1, 2025

get_access_token() now checks request.scope["user"] first before falling back to the SDK's auth_context_var. This ensures Streamable HTTP sessions return the fresh token from the current request rather than a stale token from session initialization.

Fixes #1863

@marvin-context-protocol marvin-context-protocol Bot added bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. auth Related to authentication (Bearer, JWT, OAuth, WorkOS) for client or server. server Related to FastMCP server implementation or server-side functionality. labels Dec 1, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 1, 2025

Walkthrough

The get_access_token function in src/fastmcp/server/dependencies.py now prioritizes retrieving the access token from the current HTTP request scope via AuthenticatedUser, with a RuntimeError-tolerant fallback to the SDK context variable if the request is unavailable. If a token is found in the request scope it is returned; otherwise the token is obtained from the SDK context variable. The function still returns None or an AccessToken-compatible object and continues to convert non-FastMCP token objects via model_dump, preserving the existing type-check behavior.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive Description explains the key change and references the linked issue, but omits required checklist items from the template. Complete the Contributors and Review Checklists from the template to confirm testing, self-review, and issue linkage compliance.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main fix: retrieving fresh tokens from request scope instead of stale ones from session initialization.
Linked Issues check ✅ Passed Changes implement the core requirement from #1863 by checking request.scope['user'] first, ensuring get_access_token() returns refreshed tokens instead of stale ones.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the stale token issue; new AuthenticatedUser import and docstring updates directly support the token retrieval fix.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/stale-access-token-1863

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jlowin
Copy link
Copy Markdown
Member Author

jlowin commented Dec 1, 2025

@coderabbitai no, this doesn't actually "reverse" the order - its new functionality. The original code only called _sdk_get_access_token() (which reads auth_context_var). It never checked request.scope["user"] at all.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6bf9ab9 and f9f2c1c.

📒 Files selected for processing (1)
  • src/fastmcp/server/dependencies.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Follow Ruff linting and Prettier formatting standards - run uv run prek run --all-files before committing
Prioritize readable, understandable code - clarity over cleverness, avoid obfuscated or confusing patterns

Files:

  • src/fastmcp/server/dependencies.py
🧬 Code graph analysis (1)
src/fastmcp/server/dependencies.py (2)
src/fastmcp/server/context.py (1)
  • get_http_request (683-696)
src/fastmcp/server/auth/auth.py (1)
  • AccessToken (36-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (2)
src/fastmcp/server/dependencies.py (2)

115-139: Well-designed solution to the stale token issue.

The implementation correctly addresses issue #1863 by prioritizing request.scope["user"] over the SDK's auth_context_var. This ensures that refreshed tokens are immediately available in long-lived HTTP sessions, as the request scope is updated when authentication middleware processes the refresh.

The fallback to _sdk_get_access_token() maintains backward compatibility for non-HTTP contexts (e.g., WebSocket or SDK-only usage).


144-146: Clear comments explain the conversion logic.

The expanded comments helpfully document why the conversion is necessary (SDK's AccessToken may lack fields like claims that FastMCP's version includes).

from mcp.server.auth.middleware.auth_context import (
get_access_token as _sdk_get_access_token,
)
from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

MCP SDK AuthenticatedUser access_token attribute mcp.server.auth.middleware.bearer_auth

💡 Result:

Short answer: the bearer-auth context object uses the field name "token" (or "token" / "access_token" depending on provider implementation) — the MCP Auth SDK's AuthInfo exposes the validated token as token, not a property named access_token. See MCP Auth docs showing auth_info / AuthInfo usage and examples, and an implementation note showing req.auth.token passed by middleware. [1][2]

Sources

  • MCP Auth docs — Configure Bearer auth / Retrieve auth info. [1]
  • Implementation note / example showing req.auth = { token, clientId, scopes } for middleware. [2]

Incorrect attribute name: use .token instead of .access_token

The MCP SDK's AuthenticatedUser exposes the validated token field as token, not access_token. Line 132 will raise AttributeError at runtime. Change request.scope["user"].access_token to request.scope["user"].token.

🤖 Prompt for AI Agents
In src/fastmcp/server/dependencies.py around lines 9 and ~132, the code is
accessing request.scope["user"].access_token but the MCP SDK's AuthenticatedUser
exposes the validated token as .token; change all uses of .access_token to
.token so the code reads request.scope["user"].token (and update any related
variable names/typing/comments if present) to avoid AttributeError at runtime.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no you are wrong

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for the AuthenticatedUser class definition in installed packages or vendored code
rg -n "class AuthenticatedUser" --type=py -A 20

# Search for any usage of AuthenticatedUser in the codebase
rg -n "AuthenticatedUser" --type=py -C 3

# Look for any .token or .access_token attribute usage with AuthenticatedUser
rg -n "\.access_token|\.token" --type=py -C 2 | head -100

# Try to find the MCP SDK package files if they exist in the repository
fd -t f "bearer_auth.py" --exec cat {}

Length of output: 7653


🌐 Web query:

MCP SDK AuthenticatedUser class access_token attribute Python

💡 Result:

The MCP SDK's AuthenticatedUser stores the parsed AccessToken object on the attribute access_token. So in Python you can read the token (or other fields) like:

  • If you have the user object: user.access_token (AccessToken instance) and the raw token string at user.access_token.token.
  • Using the provided helper: get_access_token() returns the AccessToken from the current request context.

Example:

  • request_user = scope["user"]
  • raw_token = request_user.access_token.token
  • or from context: access = get_access_token(); raw_token = access.token

References: MCP SDK source showing AuthenticatedUser.access_token and auth_context.get_access_token(). [1][2]

Sources
[1] mcp/server/auth/middleware/bearer_auth.py (AuthenticatedUser.access_token).
[2] mcp/server/auth/middleware/auth_context.py (get_access_token() helper).


@jlowin, you're absolutely right, and I apologize for the incorrect analysis!

The web search confirms that the MCP SDK's AuthenticatedUser class does have an access_token attribute that returns an AccessToken object. The code on line 132 using user.access_token is correct as written.

The confusion arose from an incorrect interpretation of the MCP SDK's structure. Your implementation correctly accesses request.scope["user"].access_token to retrieve the AccessToken object from the authenticated user.

🐰💡


🧬 Code Graph Analysis Results

<src/fastmcp/server/auth/auth.py, lines 36-39>

class AccessToken(_SDKAccessToken):
    """AccessToken that includes all JWT claims."""

    claims: dict[str, Any] = Field(default_factory=dict)

<src/fastmcp/server/context.py, lines 309-315>

def client_id(self) -> str | None:
        """Get the client ID if available."""
        return (
            getattr(self.request_context.meta, "client_id", None)
            if self.request_context and self.request_context.meta
            else None
        )

<src/fastmcp/server/context.py, lines 683-696>

def get_http_request(self) -> Request:
        """Get the active starlette request."""

        # Deprecated in 2.2.11
        if settings.deprecation_warnings:
            warnings.warn(
                "Context.get_http_request() is deprecated and will be removed in a future version. "
                "Use get_http_request() from fastmcp.server.dependencies instead. "
                "See https://gofastmcp.com/servers/context#http-requests for more details.",
                DeprecationWarning,
                stacklevel=2,
            )

        return fastmcp.server.dependencies.get_http_request()

✏️ Learnings added
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 2505
File: src/fastmcp/server/dependencies.py:9-9
Timestamp: 2025-12-01T15:53:29.674Z
Learning: In the MCP SDK (mcp.server.auth.middleware.bearer_auth), the AuthenticatedUser class has an `access_token` attribute (not `token`) that returns an AccessToken object. To get the raw token string, you would use `user.access_token.token`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.083Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 1, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the diff for the PR to see the actual changes
gh pr diff 2505 --repo jlowin/fastmcp

Length of output: 8774


@jlowin, you're absolutely right, and thank you for the correction! Looking at the diff, I can now see clearly that:

Original code:

access_token: _SDKAccessToken | None = _sdk_get_access_token()

New code:

  • Adds a completely NEW check: attempts to retrieve the token from request.scope["user"] (via AuthenticatedUser)
  • Falls back to _sdk_get_access_token() only if the request scope doesn't have a token

This is indeed new functionality that introduces the request scope check, which never existed before. The original implementation only ever called _sdk_get_access_token() to read from auth_context_var.

This new approach solves the stale token issue (#1863) by checking the more up-to-date request scope first, ensuring that refreshed tokens in the current HTTP request are used instead of the stale token that may be in the SDK's context variable.


✏️ Learnings added
Learnt from: jlowin
URL: https://github.com/jlowin/fastmcp/pull/2505

Timestamp: 2025-12-01T15:48:05.083Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth Related to authentication (Bearer, JWT, OAuth, WorkOS) for client or server. bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OAuth Token Refresh Does Not Update Auth Context

1 participant