Initial DocsMCP stack

This commit is contained in:
george
2026-06-05 23:02:55 +01:00
commit 421b6f973a
51 changed files with 7414 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
"""Async docs-api client for the WebUI."""
import os
from typing import Any, Dict, Optional
from httpx import AsyncClient, Timeout
class DocsAPIClient:
"""Small async HTTP client for the docs-api backend."""
def __init__(self, base_url: Optional[str] = None, api_key: Optional[str] = None):
self.base_url = (base_url or os.environ.get("DOCS_API_URL", "http://docs-api:8787")).rstrip("/")
self.api_key = api_key if api_key is not None else os.environ.get("WEBUI_API_KEY")
self.headers = {"X-API-Key": self.api_key} if self.api_key else {}
self._client: Optional[AsyncClient] = None
async def _get_client(self) -> AsyncClient:
if self._client is None or self._client.is_closed:
self._client = AsyncClient(
base_url=self.base_url,
headers=self.headers,
timeout=Timeout(120.0),
)
return self._client
async def request(self, method: str, path: str, **kwargs: Any) -> Dict[str, Any]:
client = await self._get_client()
resp = await client.request(method, path, **kwargs)
if resp.status_code >= 400:
raise RuntimeError(f"{method} {path} failed: {resp.status_code} {resp.text}")
if resp.headers.get("content-type", "").startswith("application/json"):
data = resp.json()
return data if isinstance(data, dict) else {"data": data}
return {"data": resp.text}
async def get(self, path: str, **kwargs: Any) -> Dict[str, Any]:
return await self.request("GET", path, **kwargs)
async def post(self, path: str, **kwargs: Any) -> Dict[str, Any]:
return await self.request("POST", path, **kwargs)
async def delete(self, path: str, **kwargs: Any) -> Dict[str, Any]:
return await self.request("DELETE", path, **kwargs)
async def health(self) -> Dict[str, Any]:
try:
return await self.get("/health")
except Exception as e:
return {"status": "error", "message": str(e)}
async def upload_file(self, library_id: str, filename: str, content: bytes) -> Dict[str, Any]:
files = {"file": (filename, content)}
return await self.post(f"/api/v1/upload/{library_id}", files=files)
async def close(self) -> None:
if self._client is not None and not self._client.is_closed:
await self._client.aclose()
_client_instance: Optional[DocsAPIClient] = None
async def get_client() -> DocsAPIClient:
global _client_instance
if _client_instance is None:
_client_instance = DocsAPIClient()
return _client_instance
async def close_client() -> None:
if _client_instance is not None:
await _client_instance.close()