# Checklist — serveur MCP add-on + entitlement (sous-projets D + E)

**Date :** 2026-06-11 · **Epic :** ELEVA-93. Décisions brainstorm : MCP **remote hébergé, authentifié par clé API de compte** ; add-on **gated par entitlement org + flag** (billing plus tard) ; le MCP **lit** la spec + les résultats d'audit stockés (il ne re-checke pas).

## Vue d'ensemble
Un serveur MCP **remote (Streamable HTTP)** monté dans l'app Next (`/api/mcp`), authentifié par une **clé API liée à l'org**. Il donne aux agents IA de l'utilisateur la **vue d'ensemble de tous ses sites** : la spec (135 topics clonés) + les audits par site + l'overview cross-sites. Gated : seules les orgs avec l'add-on `checklist_mcp` activé peuvent l'utiliser.

## E — Entitlement + clés API (prérequis)

### Données (migration Drizzle)
- `org_addons` : `org_id` (fk organization, cascade), `addon` (text, ex. `"checklist_mcp"`), `enabled_at` (timestamptz). PK `(org_id, addon)`. Présence = activé.
- `mcp_api_keys` : `id` (pk), `org_id` (fk cascade, index), `name` (text), `prefix` (text, 8 chars affichables, ex. `sk_live_`+4), `key_hash` (text, **hash** de la clé — jamais la clé en clair), `created_at`, `last_used_at` (nullable), `revoked_at` (nullable). La clé en clair n'est montrée **qu'une fois** à la création.

### Génération de clé
- Format : `eli_` + 32 bytes base64url. Stocker `sha256(key)` dans `key_hash`, `prefix` = `eli_` + 4 premiers chars pour l'affichage. Comparaison à la validation par hash.

### Actions (`src/lib/mcp/keys-actions.ts`, "use server")
- `createApiKeyAction(name)` — owner de l'org ; génère, insère (hash), retourne la clé **en clair une seule fois** + l'id.
- `listApiKeysAction()` — liste (prefix, name, created/last_used, revoked) de l'org active, jamais le hash/clair.
- `revokeApiKeyAction(id)` — `revoked_at = now()` (soft delete), scoped org.
- `setOrgAddonAction(orgId, addon, enabled)` — **super-admin** (`assertSuperAdmin`, pattern god-mode), active/désactive l'entitlement.
- `isAddonEnabled(orgId, addon)` — lecture.

### UI Settings (`/settings/api-keys`, dans le SettingsShell)
- Nouvelle entrée nav « Clés API » (sous Compte/Équipe/Intégrations). Owner-only.
- Si add-on désactivé : état « L'add-on Checklist MCP n'est pas activé pour ton organisation » (+ contact). Si activé : liste des clés + « Créer une clé » (dialog → montre la clé une fois, copier) + révoquer. Affiche l'URL du MCP + un exemple de config client.
- Toggle super-admin de l'entitlement : sur cette page si `isCurrentUserSuperAdmin()` (god-mode), un switch « Add-on activé ».

## D — Serveur MCP (`/api/mcp`)

### Transport
- Route handler `src/app/api/mcp/route.ts` (POST + GET), Streamable HTTP. Utiliser `mcp-handler` (adaptateur Next pour `@modelcontextprotocol/sdk`) — `sfw bun add mcp-handler @modelcontextprotocol/sdk zod`. Stateless (pas de session SSE persistante requise pour des lectures ; le handler gère le protocole).

### Auth + gating (avant toute exécution d'outil)
- Lire le bearer (`Authorization: Bearer eli_…`). Hash → lookup `mcp_api_keys` non révoquée → résout `org_id`. 401 si absente/révoquée. Met à jour `last_used_at` (best-effort).
- Vérifie `isAddonEnabled(orgId, "checklist_mcp")` → 403 si non.
- **Toute** donnée d'outil est scopée à `org_id` (jamais cross-org).

### Outils exposés (lecture seule v1)
- `list_sites` → sites de l'org (id, name, url).
- `audit_overview` → pour chaque site : score du dernier audit, pass/fail/warn, date (réutilise `getOrgAuditOverview`).
- `get_site_audit(siteId)` → dernier audit détaillé d'un site (items + verdicts), tenancy vérifiée (site ∈ org).
- `list_spec_topics(category?)` → les 135 topics (id, title, status, category, summary) depuis `CHECKLIST`.
- `get_spec_topic(category, slug)` → contenu complet (markdown) depuis `src/content/spec` (`getSpecPage`).
- *(v1.1, optionnel)* `run_site_audit(siteId)` → déclenche un audit (mutation) — différé si on veut garder le MCP lecture-seule au départ.

### Réponses
- Texte/markdown structuré (les outils MCP renvoient du content). Pour `get_spec_topic`, renvoyer le markdown brut. Pour les audits, un résumé lisible + données.

## Tests
- Keys : `createApiKeyAction` insère un hash (jamais le clair), `listApiKeysAction` ne fuit pas le hash, `revokeApiKeyAction` ; validation par hash ; tenancy.
- Entitlement : `setOrgAddonAction` (super-admin gate), `isAddonEnabled`.
- MCP route : auth (clé invalide → 401, révoquée → 401, org sans add-on → 403), un outil de lecture renvoie des données scopées org (test d'intégration léger via `app.request`/fetch du handler). Mock le DB/sites.

## Sécurité
- Clés hashées (jamais en clair en base). Affichage one-time. Révocation immédiate. Scope strict org. Rate-limit best-effort (réutiliser le rate_limit existant si simple, sinon noter en suivi).
- Pas de billing : l'entitlement est posé à la main par le super-admin (comme le god-mode catalogue) jusqu'à ELEVA-70/billing.

## Ordre
1. **E** (migration + keys + entitlement + settings UI).
2. **D** (route MCP + outils), dépend de E.
3. Registry d'audit élargi (B) — indépendant, en parallèle.
