Skip to main content
Glama
MartinCley

Excel MCP Cleyrop

by MartinCley
README.md
# Génération Excel → Cleyrop MCP

Serveur **MCP** qui **génère des fichiers Excel (.xlsx)** mis en forme à partir
d'une demande utilisateur et les dépose dans les **données de travail** d'un
projet **Cleyrop** (via `cleyrop-sdk`).

POC inspiré de la note interne *« Génération de documents »*, packagé selon la
convention des **tools MCP** de [La Fabrique](https://cleyrop.gitbook.io/docs/la-fabrique/devspace)
(même structure que `tool-sharepoint`).

## Principe : séparer contenu et mise en forme

Le LLM ne produit **jamais** de mise en forme : il produit du **contenu
structuré**, et un **moteur de rendu** distinct applique le style depuis un thème.

```
LLM ──> Contenu structuré (WorkbookSpec) ──> Moteur de rendu (openpyxl) ──> Thème ──> .xlsx
                                                                                        │
                                                          cleyrop-sdk.upload_file()  ──▶  Données de travail (projet Cleyrop)
```

Avantages : on change de thème sans toucher au prompt, on teste contenu et rendu
séparément, et le même contenu pourrait alimenter d'autres formats plus tard.
Bibliothèque : **openpyxl** (recommandée dans la note).

## Tools exposés

| Tool | Rôle |
|------|------|
| `generate_excel(spec, project=None, cleyrop_folder=None, user=None)` | Génère le .xlsx. **Sans `project`** : renvoie le fichier en **ressource téléchargeable** (blob xlsx). **Avec `project`** : tente le dépôt dans les données de travail (nécessite des droits d'écriture). |
| `cleyrop_list_projects()` | Lister les projets Cleyrop (id / slug) pour le paramètre `project` |
| `cleyrop_diagnose()` | Diagnostiquer l'authentification Cleyrop (sans fuite de secret) |
| `get_excel_schema()` | JSON Schema complet de la `WorkbookSpec` attendue |

> **Téléchargement vs dépôt.** Les **tokens personnels Cleyrop sont en lecture
> seule** : ils ne permettent pas d'écrire dans un projet. Le mode par défaut
> (sans `project`) renvoie donc le fichier en téléchargement. Le dépôt
> (`project`) ne réussit qu'avec un **compte de service disposant des droits
> d'écriture** (client_credentials injectés par La Fabrique).
>
> **Déploiement 100 % interne (cas Cleyrop).** Si le tool n'a **pas d'URL
> publique** (image buildée et déployée sur la plateforme, connexions chat→tool
> internes), un lien de téléchargement servi par le tool n'est **pas joignable
> depuis le navigateur** de l'utilisateur. La livraison se fait alors par
> **dépôt dans un projet** : définis `EXCEL_MCP_DEFAULT_PROJECT` (ou
> `CLEYROP_PROJECT_SLUG`) et le fichier est déposé dans les données de travail,
> où l'utilisateur le télécharge depuis l'UI *Projets*. Le dépôt exige un
> **compte de service avec droits d'écriture** (vérifie avec `cleyrop_diagnose`).
>
> **Ressource MCP lisible (sans URL).** `generate_excel` joint aussi le fichier
> comme **ressource MCP** (`ResourceLink` vers `excel://download/{token}`, +
> blob embarqué). Un client qui sait lire les ressources peut récupérer le
> fichier **via la connexion MCP elle-même** (`resources/read`), sans aucune URL
> publique. Dépend du support côté client (panneau ressources / pièces jointes).
>
> **Lien de téléchargement HTTP (si URL publique).** Le serveur expose aussi une
> route `GET /download/{token}`. À n'utiliser que si le tool est exposé
> publiquement : renseigne alors `EXCEL_MCP_PUBLIC_URL`.

### Capacités de rendu

Multi-feuilles · titre · entêtes stylées · colonnes typées
(`text/number/integer/currency/percent/date/datetime`) · formats de nombre auto
ou personnalisés · largeurs auto · zébrage · volet figé · filtres · ligne de
totaux (`sum/avg/count/min/max` via formules Excel) · mise en forme conditionnelle
(`color_scale/data_bar/greater_than/less_than`) · graphiques natifs
(`bar/line/pie`) · thèmes (`cleyrop`, `default`, `minimal`).

## Architecture

| Fichier | Rôle |
|---|---|
| [src/excel_mcp/models.py](src/excel_mcp/models.py) | Schéma du **contenu** (Pydantic) : classeur → feuilles → colonnes typées → lignes |
| [src/excel_mcp/themes.py](src/excel_mcp/themes.py) | Thèmes de **mise en forme** (couleurs, polices) |
| [src/excel_mcp/renderer.py](src/excel_mcp/renderer.py) | **Moteur de rendu** : spec → `openpyxl.Workbook` |
| [src/excel_mcp/cleyrop_io.py](src/excel_mcp/cleyrop_io.py) | Wrapper **cleyrop-sdk** (dépôt dans les données de travail) |
| [src/excel_mcp/storage.py](src/excel_mcp/storage.py) | Nommage `{timestamp}_{user}_{type}_{slug}.xlsx` + stockage local (fallback) |
| [src/excel_mcp/server.py](src/excel_mcp/server.py) | **Serveur MCP** (FastMCP) exposant les tools |
| [main.py](main.py) | Point d'entrée conteneur (transport HTTP streamable-http) |

---

## 1. Configuration côté Cleyrop

Le client `cleyrop-sdk` est construit dans
[src/excel_mcp/cleyrop_io.py](src/excel_mcp/cleyrop_io.py) selon le pattern de
production des tools/apps Cleyrop. **Deux modes d'authentification**, par ordre
de priorité :

| Priorité | Mode | Variables | Comportement |
|----------|------|-----------|--------------|
| 1 | **Compte de service** | `CLEYROP_CLIENT_ID` + `CLEYROP_CLIENT_SECRET` | Flux `client_credentials`. **Identité injectée par La Fabrique** au déploiement (rien à mettre dans le code — cf. Assistant_Contrat). Prioritaire si présent. |
| 2 | **Token pré-obtenu** | `CLEYROP_TOKEN` | Token de service ou **personnel**, utilisé tel quel (`Bearer`), sans login. Utilisé seulement si aucun client_secret n'est présent. |

> ⚠️ Le compte de service est **prioritaire sur** `CLEYROP_TOKEN` : un token
> résiduel ou invalide ne peut donc pas masquer les client_credentials injectés
> par la plateforme. Si tu veux forcer l'usage d'un token personnel, ne fournis
> pas de client_secret.
>
> Les `CLEYROP_CLIENT_ID` / `CLEYROP_CLIENT_SECRET` ne sont **jamais** stockés
> dans le dépôt : ils sont injectés par La Fabrique au niveau de l'instance
> (le `.env` d'Assistant_Contrat ne les contient pas non plus). Le code se
> contente de les lire via `os.getenv`.

**Résolution des URL** (auto-détectée) : in-cluster par défaut (FQDN
`apisix-gateway.<namespace>.svc.cluster.local`, `CLEYROP_NAMESPACE`), ou externe
via `CLEYROP_DOMAIN`, ou override explicite via
`CLEYROP_API_URL` + `CLEYROP_KEYCLOAK_URL`. Voir `.env.example`.

> Dans un **Tool/App de La Fabrique**, ces variables sont normalement
> **injectées automatiquement**. Détails :
> [doc cleyrop-sdk](https://cleyrop.gitbook.io/docs/support-and-ressources/references-techniques-api-sdk/sdk-cleyrop-sdk).

Le package `cleyrop-sdk` est privé (registre GitLab) : l'installation nécessite
`UV_INDEX_CLEYROP_SDK_GITLAB_USERNAME` / `..._PASSWORD` (injectées au build).

## 2. Installation (DevSpace)

```bash
cd /home/cleyrop/tool-excel
uv venv && source .venv/bin/activate

# cleyrop-sdk vient d'un registre privé GitLab :
export UV_INDEX_CLEYROP_SDK_GITLAB_USERNAME=...
export UV_INDEX_CLEYROP_SDK_GITLAB_PASSWORD=...
uv sync
```

Copie `.env.example` → `.env` si besoin de surcharger des variables.

## 3. Build de l'image (Docker / La Fabrique)

Le [Dockerfile](Dockerfile) suit la convention des tools MCP Cleyrop : base
`astral/uv`, `uv sync`, serveur exposé en **HTTP** (streamable-http) sur le port
`MCP_PORT` (défaut **8000**, endpoint `/mcp`).

`cleyrop-sdk` est résolu par **uv** via l'index `cleyrop-sdk-gitlab` déclaré dans
`pyproject.toml` ; uv s'authentifie avec les variables
`UV_INDEX_CLEYROP_SDK_GITLAB_USERNAME` / `_PASSWORD` **injectées par
l'environnement de build Cleyrop** — aucun secret à passer au build.

```bash
# build local (hors Cleyrop) : exporter d'abord les credentials du registre
export UV_INDEX_CLEYROP_SDK_GITLAB_USERNAME=...
export UV_INDEX_CLEYROP_SDK_GITLAB_PASSWORD=...
docker build -t excel-cleyrop-mcp .
docker run --rm -p 8000:8000 --env-file .env excel-cleyrop-mcp
```

> Dans **La Fabrique** : *Registre interne → Créer une image → Dépôt interne →*
> dépôt `tool-excel`, branche `v1`, chemin du Dockerfile `Dockerfile`.

### Variables d'environnement de l'instance (Cleyrop)

| Variable | Portée | Valeur |
|----------|--------|--------|
| `MCP_PORT` | globale | `8000` (= « port exposé » de l'instance) |
| `MCP_HOST` | globale (option) | `0.0.0.0` |
| `MCP_TRANSPORT` | globale (option) | `streamable-http` |
| `EXCEL_MCP_DEFAULT_PROJECT` | globale (recommandé en interne) | Projet Cleyrop où déposer par défaut (id/slug) quand aucun `project` n'est passé. `CLEYROP_PROJECT_SLUG` accepté en alias. |
| `EXCEL_MCP_PUBLIC_URL` | globale (si URL publique) | URL publique du tool (sans `/mcp`), pour les liens de téléchargement. Inutile en déploiement 100 % interne. |
| *(client credentials cleyrop-sdk)* | globale | injectées par La Fabrique (sinon `.env`) |

## 4. Usage local (client MCP de bureau, stdio)

Pour tester sans déploiement, le serveur tourne aussi en **stdio** via la
commande `excel-cleyrop-mcp` :

```json
{
  "mcpServers": {
    "excel-cleyrop": {
      "command": "/home/cleyrop/tool-excel/.venv/bin/excel-cleyrop-mcp",
      "env": { "EXCEL_MCP_EXPORT_DIR": "~/.excel-mcp/exports" }
    }
  }
}
```

Sans paramètre `project`, `generate_excel` sauvegarde le fichier localement
(`EXCEL_MCP_EXPORT_DIR`) — pratique pour tester le rendu sans projet Cleyrop.

## Exemple d'usage (depuis un client MCP)

1. `cleyrop_list_projects()` → récupère l'id/slug du projet.
2. *« fais-moi un tableau Excel des ventes T1 par région avec totaux et un
   graphique, dépose-le dans le projet X »* → le LLM construit la `WorkbookSpec`
   et appelle `generate_excel(spec, project="X", cleyrop_folder="exports/2026")`.

Voir [examples/example_spec.json](examples/example_spec.json) pour une spec
complète (devise, pourcentages, dates, totaux, mise en forme conditionnelle,
graphique).

## Tests

```bash
PYTHONPATH=src python -m pytest tests/ -q
```

Tests de non-régression du moteur de rendu (contenu, formats, formules, nommage).

## Feuille de route

- **Pré-visualisation** : convertir le .xlsx en aperçu (cf. note : XLSX→PDF via
  Gotenberg, déjà utilisé pour le flux DOCX).
- **Templates XLSX versionnés** : lire un classeur modèle (entêtes/styles nommés)
  plutôt que tout générer from scratch — `openpyxl` le permet.
- **Génération incrémentale** pour les gros classeurs (plan → données →
  validation → assemblage).

TDQS

A4.1/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: diagnose troubleshoots connections, list_projects retrieves project identifiers, generate_excel creates the file, and get_excel_schema provides data structure. No overlap.

Naming Consistency3/5

Naming is inconsistent: two tools use a 'cleyrop_' prefix while two use a direct verb_noun pattern (generate_excel, get_excel_schema). While readable, the lack of a uniform convention causes mild confusion.

Tool Count4/5

With 4 tools, the set is on the smaller side but well-scoped for the core task of generating Excel files. It covers necessary utility actions without being overly sparse.

Completeness4/5

The tools cover the main workflow: diagnose, list projects, generate, and schema retrieval. Minor gaps like lacking a tool to list generated files or manage projects are acceptable given the focused purpose of Excel generation.

Maintenance

ActivityInactive
ResponsivenessNo issues