From 1d7e92ecaba5be516bf7f4140d4dee43ed856c88 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:06:43 -0500 Subject: [PATCH] Fix ty ignore syntax in OpenAPI provider (#3253) * Fix ty ignore syntax in OpenAPI provider * Fix flaky rate limiting and ping timing tests * Assert rate limit error message in flaky test fix * Catch only ToolError in rate limiting test --- .../server/providers/openapi/provider.py | 2 +- tests/server/middleware/test_ping.py | 4 +-- tests/server/middleware/test_rate_limiting.py | 28 +++++++++++-------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/fastmcp/server/providers/openapi/provider.py b/src/fastmcp/server/providers/openapi/provider.py index 975e0228b..8ece94262 100644 --- a/src/fastmcp/server/providers/openapi/provider.py +++ b/src/fastmcp/server/providers/openapi/provider.py @@ -122,7 +122,7 @@ class OpenAPIProvider(Provider): # Create openapi-core Spec and RequestDirector try: - self._spec = SchemaPath.from_dict(openapi_spec) # type: ignore[arg-type] + self._spec = SchemaPath.from_dict(openapi_spec) # ty: ignore[invalid-argument-type] self._director = RequestDirector(self._spec) except Exception as e: logger.exception("Failed to initialize RequestDirector") diff --git a/tests/server/middleware/test_ping.py b/tests/server/middleware/test_ping.py index ba5e10fdf..0d2677102 100644 --- a/tests/server/middleware/test_ping.py +++ b/tests/server/middleware/test_ping.py @@ -169,10 +169,10 @@ class TestPingLoop: middleware._active_sessions.add(session_id) # Run ping loop for a short time then cancel - with anyio.move_on_after(0.15): + with anyio.move_on_after(0.35): await middleware._ping_loop(mock_session, session_id) - # Should have sent at least 2 pings in 150ms with 50ms interval + # Should have sent at least 2 pings in 350ms with 50ms interval assert mock_session.send_ping.call_count >= 2 async def test_ping_loop_cleans_up_on_cancellation(self): diff --git a/tests/server/middleware/test_rate_limiting.py b/tests/server/middleware/test_rate_limiting.py index 4fe1fb7ef..23132324f 100644 --- a/tests/server/middleware/test_rate_limiting.py +++ b/tests/server/middleware/test_rate_limiting.py @@ -306,21 +306,27 @@ class TestRateLimitingMiddlewareIntegration: async def test_rate_limiting_blocks_rapid_requests(self, rate_limit_server): """Test that rate limiting blocks rapid successive requests.""" - # Very restrictive rate limit (accounting for initialization and list_tools calls) - # Requests: 1 initialize + 1 list_tools + 4 call_tools = 6 total before limit + # Use a generous burst but near-zero refill rate so tokens never + # replenish. The MCP SDK sends internal messages (initialize, + # notifications, list_tools for validation) whose exact count + # varies, so we give enough burst for init + several calls, then + # keep firing until we hit the limit. rate_limit_server.add_middleware( - RateLimitingMiddleware(max_requests_per_second=10.0, burst_capacity=6) + RateLimitingMiddleware(max_requests_per_second=0.001, burst_capacity=20) ) async with Client(rate_limit_server) as client: - # First few should succeed (within burst capacity) - await client.call_tool("quick_action", {"message": "1"}) - await client.call_tool("quick_action", {"message": "2"}) - await client.call_tool("quick_action", {"message": "3"}) - - # Next should be rate limited - with pytest.raises(ToolError, match="Rate limit exceeded"): - await client.call_tool("quick_action", {"message": "4"}) + # Fire enough calls to exhaust the burst. With near-zero + # refill, we must eventually hit the limit. + hit_limit = False + for i in range(30): + try: + await client.call_tool("quick_action", {"message": str(i)}) + except ToolError as exc: + assert "Rate limit exceeded" in str(exc) + hit_limit = True + break + assert hit_limit, "Rate limit was never triggered" async def test_rate_limiting_with_concurrent_requests(self, rate_limit_server): """Test rate limiting behavior with concurrent requests."""