2121from concurrent .futures import ThreadPoolExecutor
2222from datetime import UTC , datetime
2323from typing import TYPE_CHECKING , Any
24+ import httpx
2425
2526from mcp .types import ClientRequest , ServerResult
2627from opentelemetry .sdk .trace .export import SpanExporter , SpanExportResult
@@ -323,6 +324,11 @@ def __init__(self, *, telemetry_url: str, api_key: str) -> None:
323324 self ._api_key = api_key
324325 # Track pending export futures so we can force-flush on shutdown
325326 self ._pending_futures : list [cf .Future [SpanExportResult ]] = []
327+ # Persistent HTTP client to reuse connections
328+ self ._client = httpx .Client (
329+ timeout = 30.0 ,
330+ limits = httpx .Limits (max_connections = 2000 , max_keepalive_connections = 512 , keepalive_expiry = 15.0 ),
331+ )
326332
327333 # ------------------------------------------------------------------
328334 # Core API
@@ -378,6 +384,7 @@ def _sync_export():
378384 url = url ,
379385 json = payload ,
380386 api_key = self ._api_key ,
387+ client = self ._client ,
381388 )
382389 except Exception as exc :
383390 logger .exception ("HUD exporter failed to send spans for task %s: %s" , run_id , exc )
@@ -435,6 +442,7 @@ def _cleanup_done(f: cf.Future[SpanExportResult]) -> None:
435442 url = url ,
436443 json = payload ,
437444 api_key = self ._api_key ,
445+ client = self ._client ,
438446 )
439447 except Exception as exc :
440448 logger .exception ("HUD exporter failed to send spans for task %s: %s" , run_id , exc )
@@ -452,6 +460,11 @@ def shutdown(self) -> None: # type: ignore[override]
452460 pass
453461 finally :
454462 self ._pending_futures .clear ()
463+ # Close persistent client
464+ try :
465+ self ._client .close ()
466+ except Exception :
467+ pass
455468
456469 def force_flush (self , timeout_millis : int | None = None ) -> bool : # type: ignore[override]
457470 # Wait for pending export futures
0 commit comments