From a8bbd745fe488e09c712055fe7f108a32745f9e6 Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Thu, 4 Dec 2025 11:52:40 -0500 Subject: [PATCH 1/2] Improve rate limit detection for integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When GitHub's API rate limits cause asyncio shutdown issues, the test times out rather than failing with the underlying 429 error. Updated the detection logic to check for 429 indicators in the captured output when a timeout occurs. Also increased the per-test timeout from 15s to 30s to give remote API calls more breathing room. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/run-tests.yml | 4 ++-- tests/integration_tests/conftest.py | 25 ++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5c6d4d892..a3fb99999 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -99,8 +99,8 @@ jobs: run: uv sync --upgrade - name: Run integration tests - # use longer per-test timeout than the default 3s - run: uv run pytest tests -m "integration" --timeout=15 --numprocesses auto --maxprocesses 2 --dist worksteal + # use longer per-test timeout for remote API calls (default is 5s) + run: uv run pytest tests -m "integration" --timeout=30 --numprocesses auto --maxprocesses 2 --dist worksteal env: FASTMCP_GITHUB_TOKEN: ${{ secrets.FASTMCP_GITHUB_TOKEN }} FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID }} diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py index 4be7f6a9e..8bcdb9eca 100644 --- a/tests/integration_tests/conftest.py +++ b/tests/integration_tests/conftest.py @@ -3,8 +3,13 @@ import os import pytest -def _is_rate_limit_error(excinfo) -> bool: - """Check if an exception indicates a rate limit error from GitHub API.""" +def _is_rate_limit_error(excinfo, report=None) -> bool: + """Check if an exception indicates a rate limit error from GitHub API. + + Args: + excinfo: The exception info from pytest + report: Optional test report for additional context (captured output, longrepr) + """ if excinfo is None: return False @@ -28,6 +33,20 @@ def _is_rate_limit_error(excinfo) -> bool: if "429" in exc_str or "rate limit" in exc_str or "too many requests" in exc_str: return True + # Timeout exceptions may indicate rate limiting when the 429 causes asyncio + # shutdown issues. Check if it's a timeout and look for 429 in the captured output. + if "timeout" in exc_type.lower() or "timeout" in exc_str: + # Check captured output for 429 indicators + if report is not None: + longrepr_str = str(report.longrepr).lower() if report.longrepr else "" + if "429" in longrepr_str or "too many requests" in longrepr_str: + return True + + # Check captured stdout/stderr + for section_name, content in getattr(report, "sections", []): + if "429" in content or "too many requests" in content.lower(): + return True + return False @@ -43,7 +62,7 @@ def pytest_runtest_makereport(item, call): and report.failed and not hasattr(report, "wasxfail") and item.module.__name__ == "tests.integration_tests.test_github_mcp_remote" - and _is_rate_limit_error(call.excinfo) + and _is_rate_limit_error(call.excinfo, report) ): report.outcome = "skipped" report.longrepr = ( From 6faef9d96f505951942ac45b18618b2ebf1f70f6 Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Thu, 4 Dec 2025 12:05:55 -0500 Subject: [PATCH 2/2] Add timeout to client cleanup to prevent hangs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the MCP SDK's transport tries to terminate a session during cleanup, it can hang if the server is unresponsive (e.g., rate-limited). Adding a 5-second timeout ensures we don't block forever during `__aexit__`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/fastmcp/client/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/fastmcp/client/client.py b/src/fastmcp/client/client.py index 90bbd32d9..c48b91e5d 100644 --- a/src/fastmcp/client/client.py +++ b/src/fastmcp/client/client.py @@ -374,7 +374,11 @@ class Client(Generic[ClientTransportT]): return await self._connect() async def __aexit__(self, exc_type, exc_val, exc_tb): - await self._disconnect() + # Use a timeout to prevent hanging during cleanup if the connection is in a bad + # state (e.g., rate-limited). The MCP SDK's transport may try to terminate the + # session which can hang if the server is unresponsive. + with anyio.move_on_after(5): + await self._disconnect() async def _connect(self): """