Skip to content

fix(auth): use asyncio.to_thread for credential resolution in signing hook#270

Open
nix-tkobayashi wants to merge 3 commits into
aws:mainfrom
nix-tkobayashi:fix/async-credential-resolution
Open

fix(auth): use asyncio.to_thread for credential resolution in signing hook#270
nix-tkobayashi wants to merge 3 commits into
aws:mainfrom
nix-tkobayashi:fix/async-credential-resolution

Conversation

@nix-tkobayashi
Copy link
Copy Markdown

@nix-tkobayashi nix-tkobayashi commented May 8, 2026

Summary

Changes

_sign_request_hook is an async httpx event hook, but it calls session_holder.session.get_credentials() and session_holder.refresh_if_needed() synchronously. For AWS profiles that use assumed IAM roles (chained credentials), get_credentials() triggers a blocking STS AssumeRole call that stalls the asyncio event loop, causing ~60 second connection timeouts.

This PR:

  • Adds SessionHolder.async_get_credentials() and SessionHolder.async_refresh_if_needed() which delegate blocking botocore calls to asyncio.to_thread()
  • Updates _sign_request_hook to use the async variants
  • Adds unit tests including a ticker-coroutine test that proves the event loop stays responsive during credential resolution

Note: A similar synchronous get_credentials() call exists in client.py:106 (aws_iam_streamablehttp_client). That code path is separate from the proxy signing hook and is not addressed in this PR.

User experience

Before: Using an AWS profile backed by an assumed IAM role (chained credentials) causes the MCP proxy connection to time out after ~60 seconds:

[error] Error connecting to MCP server: MCP error -32001: Request timed out

After: The connection succeeds normally. The STS AssumeRole call runs in a worker thread, keeping the event loop responsive.

Checklist

  • I have reviewed the contributing guidelines
  • I have performed a self-review of this change
  • Changes have been tested
  • Changes are documented

Is this a breaking change? (Y/N)

  • Yes
  • No

Please add details about how this change was tested.

  • All 183 unit tests pass with 96% coverage (80% required)
  • Pre-commit hooks pass
  • Ruff linting and formatting pass
  • Pyright passes (no new errors)
  • Verified in production with Lambda-based MCP client using cross-account AssumeRole
  • Integration tests not run locally (requires AWS OIDC credentials)

Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Fixes #176

… hook

`_sign_request_hook` is an async httpx event hook, but it calls
`session_holder.session.get_credentials()` and
`session_holder.refresh_if_needed()` synchronously. For profiles that
use assumed IAM roles (chained credentials), `get_credentials()` triggers
a blocking STS `AssumeRole` call that stalls the event loop and causes
connection timeouts (~60 s).

Add `SessionHolder.async_get_credentials()` and
`SessionHolder.async_refresh_if_needed()` which delegate to
`asyncio.to_thread`, keeping the event loop responsive. Update
`_sign_request_hook` to call the async variants.

Fixes aws#176

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nix-tkobayashi and others added 2 commits May 8, 2026 19:35
Address review feedback:
- Clarify that get_credentials() is the primary blocking path;
  async_refresh_if_needed() is a defensive measure
- Replace timeout-based non-blocking test with ticker coroutine that
  proves the event loop stays responsive during credential resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for assumed IAM role (chained credentials) in MCP proxy

1 participant