This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
shira-hermes/mcp_server/espocrm_client.py
T
chaim 6f7b94d6f8 feat: initial shira-hermes service
Shira AI backend using OpenAI-compatible agent with tool-calling loop,
replacing the smart-assistant route in ai-gateway.

Components:
- FastAPI adapter (same HTTP contract as ai-gateway)
- Agent runner with agentic tool-calling loop via ai-gateway /v1/chat/completions
- EspoCRM REST API client
- 20 MCP tools (CRM, documents, legal assistance)
- Prompt builder ported from ai-gateway JS to Python
- SOUL.md personality definition
- Dockerfile for Coolify deployment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:29:35 +00:00

58 lines
1.7 KiB
Python

"""EspoCRM REST API client."""
from __future__ import annotations
import httpx
import logging
logger = logging.getLogger("shira.espocrm")
class EspoCrmClient:
"""Thin async wrapper around the EspoCRM REST API."""
def __init__(self, base_url: str, api_key: str, timeout: float = 30.0):
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.timeout = timeout
def _headers(self) -> dict[str, str]:
return {
"Content-Type": "application/json",
"X-Api-Key": self.api_key,
}
async def request(
self,
method: str,
endpoint: str,
body: dict | None = None,
) -> dict:
url = f"{self.base_url}/api/v1/{endpoint}"
logger.info("[crm-api] %s %s", method, endpoint)
async with httpx.AsyncClient(timeout=self.timeout) as client:
resp = await client.request(
method,
url,
headers=self._headers(),
json=body,
)
if not resp.is_success:
text = resp.text
logger.error("[crm-api] Error %s: %s", resp.status_code, text)
raise RuntimeError(f"EspoCRM API error {resp.status_code}: {text}")
return resp.json()
async def get(self, endpoint: str) -> dict:
return await self.request("GET", endpoint)
async def post(self, endpoint: str, body: dict) -> dict:
return await self.request("POST", endpoint, body)
async def put(self, endpoint: str, body: dict) -> dict:
return await self.request("PUT", endpoint, body)
async def delete(self, endpoint: str) -> dict:
return await self.request("DELETE", endpoint)