optimize mcp_client: persistent client, exponential backoff, inter-call delay
- Switch transport to streamable_http (matches solve_task.py) - Add _persistent_client global to reuse MultiServerMCPClient across calls - Add exponential backoff 15→30→60→120→240s on 429 responses - Add _INTER_CALL_DELAY=1.5s between consecutive MCP calls to prevent burst - Add _is_429() helper for clean 429 detection - Recreate client on non-429 errors to recover from stale sessions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+44
-7
@@ -1,6 +1,7 @@
|
|||||||
"""Загрузка инструментов BroJS Journal через MCP (HTTP transport)."""
|
"""Загрузка инструментов BroJS Journal через MCP (HTTP transport)."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
@@ -17,6 +18,15 @@ _JOURNAL_TOKEN = os.getenv("JOURNAL_TOKEN", "YOUR_JOURNAL_TOKEN_HERE")
|
|||||||
|
|
||||||
JOURNAL_MCP_URL = "https://platform.brojs.ru/jrnl-bh/api/mcp"
|
JOURNAL_MCP_URL = "https://platform.brojs.ru/jrnl-bh/api/mcp"
|
||||||
|
|
||||||
|
# Exponential backoff: 15, 30, 60, 120, 240 секунд
|
||||||
|
_BACKOFF = [15, 30, 60, 120, 240]
|
||||||
|
# Минимальная пауза между последовательными MCP-вызовами (предотвращает burst)
|
||||||
|
_INTER_CALL_DELAY = 1.5
|
||||||
|
|
||||||
|
# Персистентный клиент и время последнего вызова — переиспользуются в рамках одного запуска
|
||||||
|
_persistent_client: MultiServerMCPClient | None = None
|
||||||
|
_last_mcp_call_time: float = 0.0
|
||||||
|
|
||||||
# Инструменты для работы с курсами и уроками
|
# Инструменты для работы с курсами и уроками
|
||||||
JOURNAL_COURSES_LESSONS = frozenset({
|
JOURNAL_COURSES_LESSONS = frozenset({
|
||||||
"courses_list",
|
"courses_list",
|
||||||
@@ -44,7 +54,7 @@ class JournalToolsets:
|
|||||||
def _build_mcp_config() -> dict:
|
def _build_mcp_config() -> dict:
|
||||||
return {
|
return {
|
||||||
JOURNAL_SERVER_NAME: {
|
JOURNAL_SERVER_NAME: {
|
||||||
"transport": "http",
|
"transport": "streamable_http",
|
||||||
"url": JOURNAL_MCP_URL,
|
"url": JOURNAL_MCP_URL,
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": f"Bearer {_JOURNAL_TOKEN}",
|
"Authorization": f"Bearer {_JOURNAL_TOKEN}",
|
||||||
@@ -53,16 +63,43 @@ def _build_mcp_config() -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _is_429(exc: Exception) -> bool:
|
||||||
|
return "429" in str(exc)
|
||||||
|
|
||||||
|
|
||||||
async def _fetch_tools() -> dict[str, list]:
|
async def _fetch_tools() -> dict[str, list]:
|
||||||
|
"""Загружает MCP-инструменты с персистентным клиентом и экспоненциальным backoff при 429."""
|
||||||
|
global _persistent_client, _last_mcp_call_time
|
||||||
|
|
||||||
|
# Пауза между последовательными вызовами — предотвращает burst
|
||||||
|
elapsed = time.monotonic() - _last_mcp_call_time
|
||||||
|
if elapsed < _INTER_CALL_DELAY:
|
||||||
|
await asyncio.sleep(_INTER_CALL_DELAY - elapsed)
|
||||||
|
|
||||||
config = _build_mcp_config()
|
config = _build_mcp_config()
|
||||||
client = MultiServerMCPClient(config)
|
|
||||||
|
# Переиспользуем клиент если уже создан
|
||||||
|
if _persistent_client is None:
|
||||||
|
_persistent_client = MultiServerMCPClient(config)
|
||||||
|
|
||||||
out: dict[str, list] = {}
|
out: dict[str, list] = {}
|
||||||
for name in config:
|
for name in config:
|
||||||
try:
|
for i, pause in enumerate([0] + _BACKOFF):
|
||||||
out[name] = await client.get_tools(server_name=name)
|
try:
|
||||||
except Exception as exc:
|
if pause:
|
||||||
print(f"MCP '{name}': не удалось загрузить инструменты — {type(exc).__name__}: {exc}")
|
print(f" [mcp_client] '{name}' → 429, жду {pause}с (попытка {i+1})...")
|
||||||
out[name] = []
|
await asyncio.sleep(pause)
|
||||||
|
out[name] = await _persistent_client.get_tools(server_name=name)
|
||||||
|
_last_mcp_call_time = time.monotonic()
|
||||||
|
break
|
||||||
|
except Exception as exc:
|
||||||
|
if _is_429(exc) and i < len(_BACKOFF):
|
||||||
|
continue
|
||||||
|
print(f"MCP '{name}': не удалось загрузить инструменты — {type(exc).__name__}: {exc}")
|
||||||
|
# При неизвестной ошибке пересоздаём клиент перед следующей попыткой
|
||||||
|
_persistent_client = MultiServerMCPClient(config)
|
||||||
|
out[name] = []
|
||||||
|
break
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user