""" MCP Indy custom — serveur FastMCP qui appelle l'API privee d'Indy avec ton token, recupere tes transactions et les convertit au format d'import de JB Compta. API non officielle d'Indy : pour TES donnees uniquement. Peut casser si Indy change son interface ; le token JWT expire et doit etre renouvele. """ import os import httpx from fastmcp import FastMCP import indy_mapping BASE_URL = os.getenv("INDY_BASE_URL", "https://app.indy.fr").rstrip("/") TOKEN = os.getenv("INDY_TOKEN", "") AUTH_HEADER = os.getenv("INDY_AUTH_HEADER", "Authorization") AUTH_PREFIX = os.getenv("INDY_AUTH_PREFIX", "Bearer ") COOKIE = os.getenv("INDY_COOKIE", "") TRANSACTIONS_PATH = os.getenv("INDY_TRANSACTIONS_PATH", "/api/transactions/transactions-list") mcp = FastMCP("indy-compta-custom") def _client(): headers = { "Accept": "application/json, text/plain, */*", "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0 Safari/537.36"), "x-client-app-type": "web", } if TOKEN: headers[AUTH_HEADER] = f"{AUTH_PREFIX}{TOKEN}" if COOKIE: headers["Cookie"] = COOKIE return httpx.Client(base_url=BASE_URL, headers=headers, timeout=30, follow_redirects=True) def _appel_api(path, method="GET", params=None, json_body=None): if not TOKEN and not COOKIE: return {"erreur": "INDY_TOKEN (ou INDY_COOKIE) non configure dans .env"} try: with _client() as c: r = c.request(method.upper(), path, params=params, json=json_body) ct = r.headers.get("content-type", "") body = r.json() if "json" in ct else r.text[:3000] return {"status": r.status_code, "url": str(r.request.url), "data": body} except Exception as ex: # noqa: BLE001 return {"erreur": f"{type(ex).__name__}: {ex}"} def _fetch_transactions(search="", date_debut=None, date_fin=None, page=1): params = {"search": search or "", "page": page} if date_debut: params["dateFrom"] = date_debut if date_fin: params["dateTo"] = date_fin res = _appel_api(TRANSACTIONS_PATH, "GET", params=params) if "erreur" in res: return res, [] data = res.get("data") or {} txs = data.get("transactions", []) if isinstance(data, dict) else [] return res, txs @mcp.tool def etat() -> dict: """Verifie la configuration : URL de base, presence du token, endpoint.""" return {"base_url": BASE_URL, "token_present": bool(TOKEN), "auth_header": AUTH_HEADER, "transactions_path": TRANSACTIONS_PATH} @mcp.tool def appel_api(path: str, method: str = "GET", params: dict | None = None, json_body: dict | None = None) -> dict: """Explorateur generique : appelle n'importe quel endpoint de l'API Indy.""" return _appel_api(path, method, params, json_body) @mcp.tool def lister_transactions(search: str = "", date_debut: str | None = None, date_fin: str | None = None, page: int = 1) -> dict: """Liste brute des transactions Indy (filtres : search, dates AAAA-MM-JJ, page).""" res, _ = _fetch_transactions(search, date_debut, date_fin, page) return res @mcp.tool def transactions_pour_import(date_debut: str | None = None, date_fin: str | None = None, search: str = "", page: int = 1) -> dict: """Recupere les transactions Indy au format CSV de JB Compta (a coller dans /import/). Colonnes : date;libelle;categorie;activite;taux_tva;regime;montant """ res, txs = _fetch_transactions(search, date_debut, date_fin, page) if "erreur" in res: return res data = res.get("data") or {} return { "nb_transactions_page": len(txs), "nb_total": data.get("nbTransactions") if isinstance(data, dict) else None, "csv": indy_mapping.vers_csv(txs), } if __name__ == "__main__": mcp.run()