Skip to content

fix: subclass AccessToken with custom fields - #406

Merged
taylorwilsdon merged 3 commits into
taylorwilsdon:mainfrom
chetan:fix/external-auth-token
Jan 30, 2026
Merged

fix: subclass AccessToken with custom fields#406
taylorwilsdon merged 3 commits into
taylorwilsdon:mainfrom
chetan:fix/external-auth-token

Conversation

@chetan

@chetan chetan commented Jan 29, 2026

Copy link
Copy Markdown
Contributor

Description

Using external auth, I got this error:

╭──────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ────────────────────────────────────────────────────────────────────────────╮
│ ~/.cache/uv/archive-v0/qEHTtmhGI_goRCb4FUyCF/lib/python3.10/site-packages/fastmcp/server/dependencies.py:612 in get_access_token                                     │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'types.SimpleNamespace' object has no attribute 'model_dump'

The above exception was the direct cause of the following exception:

╭──────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ────────────────────────────────────────────────────────────────────────────╮
│ ~/.cache/uv/archive-v0/qEHTtmhGI_goRCb4FUyCF/lib/python3.10/site-packages/core/utils.py:272 in wrapper                                                               │
│                                                                                                                                                                                           │
│   269 │   │   │                                                                                                                                                                           │
│   270 │   │   │   for attempt in range(max_retries):                                                                                                                                      │
│   271 │   │   │   │   try:                                                                                                                                                                │
│ ❱ 272 │   │   │   │   │   return await func(*args, **kwargs)                                                                                                                              │
│   273 │   │   │   │   except ssl.SSLError as e:                                                                                                                                           │
│   274 │   │   │   │   │   if is_read_only and attempt < max_retries - 1:                                                                                                                  │
│   275 │   │   │   │   │   │   delay = base_delay * (2**attempt)                                                                                                                           │
│                                                                                                                                                                                           │
│ ~/.cache/uv/archive-v0/qEHTtmhGI_goRCb4FUyCF/lib/python3.10/site-packages/auth/service_decorator.py:653 in wrapper                                                   │
│                                                                                                                                                                                           │
│   650 │   │   │   │   │   )                                                                                                                                                               │
│   651 │   │   │   │                                                                                                                                                                       │
│   652 │   │   │   │   # Authenticate service                                                                                                                                              │
│ ❱ 653 │   │   │   │   service, actual_user_email = await _authenticate_service(                                                                                                           │
│   654 │   │   │   │   │   use_oauth21,                                                                                                                                                    │
│   655 │   │   │   │   │   service_name,                                                                                                                                                   │
│   656 │   │   │   │   │   service_version,                                                                                                                                                │
│                                                                                                                                                                                           │
│                                                                                  ... 1 frames hidden ...                                                                                  │
│                                                                                                                                                                                           │
│ ~/.cache/uv/archive-v0/qEHTtmhGI_goRCb4FUyCF/lib/python3.10/site-packages/auth/service_decorator.py:239 in get_authenticated_google_service_oauth21                  │
│                                                                                                                                                                                           │
│   236 │   OAuth 2.1 authentication using the session store with security validation.                                                                                                      │
│   237 │   """                                                                                                                                                                             │
│   238 │   provider = get_auth_provider()                                                                                                                                                  │
│ ❱ 239 │   access_token = get_access_token()                                                                                                                                               │
│   240 │                                                                                                                                                                                   │
│   241 │   if provider and access_token:                                                                                                                                                   │
│   242 │   │   token_email = None                                                                                                                                                          │
│                                                                                                                                                                                           │
│ ~/.cache/uv/archive-v0/qEHTtmhGI_goRCb4FUyCF/lib/python3.10/site-packages/fastmcp/server/dependencies.py:623 in get_access_token                                     │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
TypeError: Expected fastmcp.server.auth.auth.AccessToken, got SimpleNamespace. Ensure the SDK is using the correct AccessToken type.

The above exception was the direct cause of the following exception:

╭──────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ────────────────────────────────────────────────────────────────────────────╮
│ ~/.cache/uv/archive-v0/qEHTtmhGI_goRCb4FUyCF/lib/python3.10/site-packages/fastmcp/tools/tool_manager.py:160 in call_tool                                             │
│                                                                                                                                                                                           │
│ ~/.cache/uv/archive-v0/qEHTtmhGI_goRCb4FUyCF/lib/python3.10/site-packages/fastmcp/tools/tool.py:383 in run                                                           │
│                                                                                                                                                                                           │
│ ~/.cache/uv/archive-v0/qEHTtmhGI_goRCb4FUyCF/lib/python3.10/site-packages/core/utils.py:354 in wrapper                                                               │
│                                                                                                                                                                                           │
│   351 │   │   │   │   except Exception as e:                                                                                                                                              │
│   352 │   │   │   │   │   message = f"An unexpected error occurred in {tool_name}: {e}"                                                                                                   │
│   353 │   │   │   │   │   logger.exception(message)                                                                                                                                       │
│ ❱ 354 │   │   │   │   │   raise Exception(message) from e                                                                                                                                 │
│   355 │   │                                                                                                                                                                               │
│   356 │   │   return wrapper                                                                                                                                                              │
│   357                                                                                                                                                                                     │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Exception: An unexpected error occurred in get_events: Expected fastmcp.server.auth.auth.AccessToken, got SimpleNamespace. Ensure the SDK is using the correct AccessToken type.

My initial tests worked ok via mcp-inspector but later got this error when sending requests from another app.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Testing

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested this change manually

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • I have enabled "Allow edits from maintainers" for this pull request

Additional Notes

Add any other context about the pull request here.


⚠️ IMPORTANT: This repository requires that you enable "Allow edits from maintainers" when creating your pull request. This allows maintainers to make small fixes and improvements directly to your branch, speeding up the review process.

To enable this setting:

  1. When creating the PR, check the "Allow edits from maintainers" checkbox
  2. If you've already created the PR, you can enable this in the PR sidebar under "Allow edits from maintainers"

@chetan

chetan commented Jan 29, 2026

Copy link
Copy Markdown
Contributor Author

Looks like this will conflict with #383 when it is merged but can fix when that's in. I'm going to test them together now in a separate branch.

@taylorwilsdon

Copy link
Copy Markdown
Owner

Looks like this will conflict with #383 when it is merged but can fix when that's in. I'm going to test them together now in a separate branch.

I'm not convinced that is the right approached to what he's describing but don't use external mode myself so I don't have an environment to properly put it through its paces.

@chetan

chetan commented Jan 30, 2026

Copy link
Copy Markdown
Contributor Author

@taylorwilsdon looking into it a bit deeper, you're right. There's an issue explaining the leak here. Looks like the proper fix is to re-use services created with build() as much as possible and dispose of them by calling close().

@taylorwilsdon
taylorwilsdon requested review from Copilot and taylorwilsdon and removed request for Copilot January 30, 2026 14:46
@taylorwilsdon taylorwilsdon self-assigned this Jan 30, 2026
@taylorwilsdon taylorwilsdon added the enhancement New feature or request label Jan 30, 2026
@taylorwilsdon
taylorwilsdon requested a review from Copilot January 30, 2026 14:46

Copilot AI left a comment

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.

Pull request overview

This PR fixes an authentication error where SimpleNamespace objects were being used instead of proper AccessToken instances, causing AttributeError: 'types.SimpleNamespace' object has no attribute 'model_dump' when using external OAuth authentication.

Changes:

  • Introduced a new WorkspaceAccessToken class that extends FastMCP's AccessToken with workspace-specific fields (session_id, sub, email)
  • Replaced all SimpleNamespace instantiations with WorkspaceAccessToken to ensure proper type compatibility
  • Added missing claims field assignments in token creation

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
auth/oauth_types.py Defines the new WorkspaceAccessToken class extending FastMCP's AccessToken
auth/external_oauth_provider.py Replaces SimpleNamespace with WorkspaceAccessToken for token creation
auth/auth_info_middleware.py Replaces SimpleNamespace with WorkspaceAccessToken in two token creation locations and adds claims field

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread auth/oauth_types.py
Comment on lines +15 to +16
"""AccessToken extended with workspace-specific fields."""

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

The class is missing documentation for its additional fields. Consider adding docstring descriptions for session_id, sub, and email to clarify their purpose and expected values.

Suggested change
"""AccessToken extended with workspace-specific fields."""
"""
AccessToken extended with workspace-specific fields for workspace identity.
Attributes:
session_id: Optional identifier for the current authenticated session
associated with this access token. May be ``None`` if the token is
not bound to a specific session.
sub: Optional subject identifier for the authenticated user, typically
taken from the identity provider's ``sub`` claim. Intended to be a
stable, opaque user identifier when available.
email: Optional email address associated with the authenticated user,
if provided by the identity provider or upstream authentication
system.
"""

Copilot uses AI. Check for mistakes.
Comment on lines +131 to +132
claims=getattr(verified_auth, "claims", {})
or {},

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

The or {} fallback is redundant since getattr(verified_auth, 'claims', {}) already provides an empty dict as the default. This creates unnecessary complexity.

Suggested change
claims=getattr(verified_auth, "claims", {})
or {},
claims=getattr(verified_auth, "claims", {}),

Copilot uses AI. Check for mistakes.
@taylorwilsdon
taylorwilsdon merged commit c5fd6a2 into taylorwilsdon:main Jan 30, 2026
4 checks passed
@chetan
chetan deleted the fix/external-auth-token branch February 2, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants