This is an automated email from the ASF dual-hosted git repository.

yzheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris-tools.git


The following commit(s) were added to refs/heads/main by this push:
     new 49461c0  [MCP] feat: add retry for HTTP requests (#72)
49461c0 is described below

commit 49461c08f9e240e4970bd742530cbc7defed1ff2
Author: Yong Zheng <[email protected]>
AuthorDate: Tue Nov 25 01:33:58 2025 -0600

    [MCP] feat: add retry for HTTP requests (#72)
    
    * Added retry for HTTP requests
    
    * Variable renaming for consistency
    
    * Fix lint
    
    * Fix conflict
---
 mcp-server/README.md             |  3 ++-
 mcp-server/polaris_mcp/server.py | 19 ++++++++++++++++++-
 mcp-server/tests/test_server.py  | 40 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 2 deletions(-)

diff --git a/mcp-server/README.md b/mcp-server/README.md
index 2dbb1bd..7d87637 100644
--- a/mcp-server/README.md
+++ b/mcp-server/README.md
@@ -77,7 +77,8 @@ Please note: `--directory` specifies a local directory. It is 
not needed when we
 | `POLARIS_HTTP_TIMEOUT_SECONDS`                                 | Default 
timeout in seconds for all HTTP requests.              | `30.0`                 
                          |
 | `POLARIS_HTTP_CONNECT_TIMEOUT_SECONDS`                         | Timeout in 
seconds for establishing HTTP connections.          | `30.0`                    
                       |
 | `POLARIS_HTTP_READ_TIMEOUT_SECONDS`                            | Timeout in 
seconds for reading HTTP responses.                 | `30.0`                    
                       |
-
+| `POLARIS_HTTP_RETRIES_TOTAL`                                   | Total 
number of retries for HTTP requests.                     | `3`                  
                            |
+| `POLARIS_HTTP_RETRIES_BACKOFF_FACTOR`                          | Factor for 
exponential backoff between retries.                | `0.5`                     
                       |
 
 When OAuth variables are supplied, the server automatically acquires and 
refreshes tokens using the client credentials flow; otherwise a static bearer 
token is used if provided.
 
diff --git a/mcp-server/polaris_mcp/server.py b/mcp-server/polaris_mcp/server.py
index 5e5ebdc..02b37a0 100644
--- a/mcp-server/polaris_mcp/server.py
+++ b/mcp-server/polaris_mcp/server.py
@@ -63,6 +63,9 @@ OUTPUT_SCHEMA = {
 }
 DEFAULT_TOKEN_REFRESH_BUFFER_SECONDS = 60.0
 DEFAULT_HTTP_TIMEOUT = 30.0
+DEFAULT_HTTP_RETRIES_TOTAL = 3
+DEFAULT_HTTP_RETRIES_BACKOFF_FACTOR = 0.5
+HTTP_RETRIES_STATUS_FORCELIST = [401, 409, 429]
 LOGGING_CONFIG = {
     "version": 1,
     "disable_existing_loggers": False,
@@ -87,7 +90,21 @@ def create_server() -> FastMCP:
     """Construct a FastMCP server with Polaris tools."""
     base_url = _resolve_base_url()
     timeout = _resolve_http_timeout()
-    http = urllib3.PoolManager()
+    total_retries = int(
+        os.getenv("POLARIS_HTTP_RETRIES_TOTAL", DEFAULT_HTTP_RETRIES_TOTAL)
+    )
+    backoff_factor = float(
+        os.getenv(
+            "POLARIS_HTTP_RETRIES_BACKOFF_FACTOR",
+            DEFAULT_HTTP_RETRIES_BACKOFF_FACTOR,
+        )
+    )
+    retry_strategy = urllib3.Retry(
+        total=total_retries,
+        backoff_factor=backoff_factor,
+        status_forcelist=HTTP_RETRIES_STATUS_FORCELIST,
+    )
+    http = urllib3.PoolManager(retries=retry_strategy)
     authorization_provider = _resolve_authorization_provider(base_url, http, 
timeout)
     catalog_rest = PolarisRestTool(
         name="polaris.rest.catalog",
diff --git a/mcp-server/tests/test_server.py b/mcp-server/tests/test_server.py
index 906908c..0ba55e0 100644
--- a/mcp-server/tests/test_server.py
+++ b/mcp-server/tests/test_server.py
@@ -319,3 +319,43 @@ class TestServerConfiguration:
             timeout = server._resolve_http_timeout()
             assert timeout.connect_timeout == server.DEFAULT_HTTP_TIMEOUT
             assert timeout.read_timeout == server.DEFAULT_HTTP_TIMEOUT
+
+
+class TestServerRetry:
+    def test_create_server_default_retry(self) -> None:
+        """Verify that the HTTP client is created with a default retry 
strategy."""
+        with (
+            mock.patch("polaris_mcp.server.urllib3.PoolManager") as 
mock_pool_manager,
+            mock.patch("polaris_mcp.server.urllib3.Retry") as mock_retry,
+        ):
+            server.create_server()
+
+            mock_retry.assert_called_once_with(
+                total=3,
+                backoff_factor=0.5,
+                status_forcelist=server.HTTP_RETRIES_STATUS_FORCELIST,
+            )
+            
mock_pool_manager.assert_called_once_with(retries=mock_retry.return_value)
+
+    def test_create_server_custom_retry(self) -> None:
+        """Verify that the HTTP client is created with a custom retry strategy 
from environment variables."""
+        with (
+            mock.patch("polaris_mcp.server.urllib3.PoolManager") as 
mock_pool_manager,
+            mock.patch("polaris_mcp.server.urllib3.Retry") as mock_retry,
+            mock.patch.dict(
+                os.environ,
+                {
+                    "POLARIS_HTTP_RETRIES_TOTAL": "10",
+                    "POLARIS_HTTP_RETRIES_BACKOFF_FACTOR": "1.0",
+                },
+                clear=True,
+            ),
+        ):
+            server.create_server()
+
+        mock_retry.assert_called_once_with(
+            total=10,
+            backoff_factor=1.0,
+            status_forcelist=server.HTTP_RETRIES_STATUS_FORCELIST,
+        )
+        
mock_pool_manager.assert_called_once_with(retries=mock_retry.return_value)

Reply via email to