Skip to content

Commit 9800e3f

Browse files
authored
Implement requests retries (#457)
* added retry mechanism for httpx requests Signed-off-by: Keval Mahajan <[email protected]> * linting Signed-off-by: Keval Mahajan <[email protected]> * configurable values for retries from settings Signed-off-by: Keval Mahajan <[email protected]> * added test cases for ResilientHttpClient Signed-off-by: Keval Mahajan <[email protected]> * minor change Signed-off-by: Keval Mahajan <[email protected]> --------- Signed-off-by: Keval Mahajan <[email protected]>
1 parent ecee5d2 commit 9800e3f

18 files changed

+587
-24
lines changed

.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ ALLOWED_ORIGINS='["http://localhost", "http://localhost:4444"]'
126126
# Enable CORS handling in the gateway
127127
CORS_ENABLED=true
128128

129+
#####################################
130+
# Retry Config for HTTP Requests
131+
#####################################
132+
133+
RETRY_MAX_ATTEMPTS=3
134+
RETRY_BASE_DELAY=1.0 # seconds
135+
RETRY_MAX_DELAY=60.0 # seconds
136+
RETRY_JITTER_MAX=0.5 # fraction of delay
137+
129138
#####################################
130139
# Logging
131140
#####################################

mcpgateway/admin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
ToolService,
6969
)
7070
from mcpgateway.utils.create_jwt_token import get_jwt_token
71+
from mcpgateway.utils.retry_manager import ResilientHttpClient
7172
from mcpgateway.utils.verify_credentials import require_auth, require_basic_auth
7273
from mcpgateway.utils.error_formatter import ErrorFormatter
7374

@@ -1383,7 +1384,7 @@ async def admin_test_gateway(request: GatewayTestRequest, user: str = Depends(re
13831384
logger.debug(f"User {user} testing server at {request.base_url}.")
13841385
try:
13851386
start_time = time.monotonic()
1386-
async with httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) as client:
1387+
async with ResilientHttpClient(client_args={"timeout": settings.federation_timeout, "verify": not settings.skip_ssl_verify}) as client:
13871388
response = await client.request(method=request.method.upper(), url=full_url, headers=request.headers, json=request.body)
13881389
latency_ms = int((time.monotonic() - start_time) * 1000)
13891390
try:

mcpgateway/alembic/env.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
from logging.config import fileConfig
55

66
# Third-Party
7-
from alembic import context
8-
97
# this is the Alembic Config object, which provides
108
# access to the values within the .ini file in use.
119
from alembic.config import Config
1210
from sqlalchemy import engine_from_config, pool
1311

1412
# First-Party
13+
from alembic import context
1514
from mcpgateway.config import settings
1615
from mcpgateway.db import Base
1716

mcpgateway/alembic/versions/b77ca9d2de7e_uuid_pk_and_slug_refactor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
import uuid
1313

1414
# Third-Party
15-
from alembic import op
1615
import sqlalchemy as sa
1716
from sqlalchemy.orm import Session
1817

1918
# First-Party
19+
from alembic import op
2020
from mcpgateway.config import settings
2121
from mcpgateway.utils.create_slug import slugify
2222

mcpgateway/alembic/versions/e4fc04d1a442_add_annotations_to_tables.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
from typing import Sequence, Union
1212

1313
# Third-Party
14-
from alembic import op
1514
import sqlalchemy as sa
1615

16+
# First-Party
17+
from alembic import op
18+
1719
# revision identifiers, used by Alembic.
1820
revision: str = "e4fc04d1a442"
1921
down_revision: Union[str, Sequence[str], None] = "b77ca9d2de7e"

mcpgateway/alembic/versions/e75490e949b1_add_improved_status_to_tables.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
from typing import Sequence, Union
1111

1212
# Third-Party
13-
from alembic import op
1413
import sqlalchemy as sa
1514

15+
# First-Party
16+
from alembic import op
17+
1618
# Revision identifiers.
1719
revision: str = "e75490e949b1"
1820
down_revision: Union[str, Sequence[str], None] = "e4fc04d1a442"

mcpgateway/bootstrap_db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
import logging
2626

2727
# Third-Party
28-
from alembic import command
2928
from alembic.config import Config
3029
from sqlalchemy import create_engine, inspect
3130

3231
# First-Party
32+
from alembic import command
3333
from mcpgateway.config import settings
3434
from mcpgateway.db import Base
3535

mcpgateway/cache/session_registry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333

3434
# Third-Party
3535
from fastapi import HTTPException, status
36-
import httpx
3736

3837
# First-Party
3938
from mcpgateway import __version__
@@ -42,6 +41,7 @@
4241
from mcpgateway.models import Implementation, InitializeResult, ServerCapabilities
4342
from mcpgateway.services import PromptService, ResourceService, ToolService
4443
from mcpgateway.transports import SSETransport
44+
from mcpgateway.utils.retry_manager import ResilientHttpClient
4545

4646
logger = logging.getLogger(__name__)
4747

@@ -780,7 +780,7 @@ async def generate_response(self, message: json, transport: SSETransport, server
780780
}
781781
headers = {"Authorization": f"Bearer {user['token']}", "Content-Type": "application/json"}
782782
rpc_url = base_url + "/rpc"
783-
async with httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) as client:
783+
async with ResilientHttpClient(client_args={"timeout": settings.federation_timeout, "verify": not settings.skip_ssl_verify}) as client:
784784
rpc_response = await client.post(
785785
url=rpc_url,
786786
json=rpc_input,

mcpgateway/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ class Settings(BaseSettings):
134134
"http://localhost:4444",
135135
}
136136

137+
# Max retries for HTTP requests
138+
retry_max_attempts: int = 3
139+
retry_base_delay: float = 1.0 # seconds
140+
retry_max_delay: int = 60 # seconds
141+
retry_jitter_max: float = 0.5 # fraction of base delay
142+
137143
@field_validator("allowed_origins", mode="before")
138144
@classmethod
139145
def _parse_allowed_origins(cls, v):

mcpgateway/main.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse
5050
from fastapi.staticfiles import StaticFiles
5151
from fastapi.templating import Jinja2Templates
52-
import httpx
5352
from sqlalchemy import text
5453
from sqlalchemy.orm import Session
5554
from starlette.middleware.base import BaseHTTPMiddleware
@@ -125,6 +124,7 @@
125124
)
126125
from mcpgateway.utils.db_isready import wait_for_db_ready
127126
from mcpgateway.utils.redis_isready import wait_for_redis_ready
127+
from mcpgateway.utils.retry_manager import ResilientHttpClient
128128
from mcpgateway.utils.verify_credentials import require_auth, require_auth_override
129129
from mcpgateway.validation.jsonrpc import (
130130
JSONRPCError,
@@ -1848,7 +1848,8 @@ async def websocket_endpoint(websocket: WebSocket):
18481848
while True:
18491849
try:
18501850
data = await websocket.receive_text()
1851-
async with httpx.AsyncClient(timeout=settings.federation_timeout, verify=not settings.skip_ssl_verify) as client:
1851+
client_args = {"timeout": settings.federation_timeout, "verify": not settings.skip_ssl_verify}
1852+
async with ResilientHttpClient(client_args=client_args) as client:
18521853
response = await client.post(
18531854
f"http://localhost:{settings.port}/rpc",
18541855
json=json.loads(data),

0 commit comments

Comments
 (0)