Files
vitya ca669d96e1 feat(lib): bundle MCP servers from .common/lib into factory/lib/
Copies source (no node_modules, dist, .tasks, .wiki, __pycache__) for:
- projects-meta-mcp v2.25.0 (TypeScript/Node)
- wiki-graph v0.3.1 (TypeScript/Node)
- interns-mcp v0.3.3 (Python/FastMCP)

.gitignore: exclude lib build artefacts (node_modules, dist, .venv, __pycache__, *.pyc)
bootstrap.ps1: add MCP build step — npm install+build for TS servers, venv+pip for Python

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:17:29 +03:00

43 lines
1.5 KiB
Python

"""OpenAI-compatible HTTP client, persistent per-endpoint."""
from __future__ import annotations
from typing import Any
from openai import OpenAI
# Bound every LLM call. Without this the SDK default (600s) lets a stalled
# endpoint hang the worker thread that ran the tool; enough such threads exhaust
# FastMCP's pool and wedge the whole stdio server. 90s is well above a healthy
# deepseek-v4-flash response yet short enough to fail fast and free the thread.
DEFAULT_REQUEST_TIMEOUT = 90.0
def _resolve_timeout(endpoint_cfg: dict[str, Any]) -> float:
"""Per-endpoint request timeout in seconds; falls back to the bounded default."""
return float(endpoint_cfg.get("request_timeout", DEFAULT_REQUEST_TIMEOUT))
def make_client(endpoint_cfg: dict[str, Any], api_key: str) -> OpenAI:
"""Create a persistent OpenAI client for an endpoint."""
kwargs: dict[str, Any] = {
"base_url": endpoint_cfg["base_url"],
"api_key": api_key,
"timeout": _resolve_timeout(endpoint_cfg),
}
defaults = endpoint_cfg.get("request_defaults", {})
if "extra_body" in defaults:
kwargs["default_query"] = {}
return OpenAI(**kwargs)
class ClientPool:
"""Manages persistent HTTP clients per endpoint."""
def __init__(self) -> None:
self._clients: dict[str, OpenAI] = {}
def get(self, name: str, endpoint_cfg: dict[str, Any], api_key: str) -> OpenAI:
if name not in self._clients:
self._clients[name] = make_client(endpoint_cfg, api_key)
return self._clients[name]