# Serveur MCP IgnitionRAG — documentation technique

Ce document décrit le serveur autonome `ignitionrag-mcp/` tel qu'il est
déployé aujourd'hui. Il complète le [README](./README.md) avec les contrats
d'architecture, de sécurité et d'exploitation.

## 1. Rôle dans la plateforme

Le serveur MCP est un **adaptateur d'interface**. Il traduit les appels MCP en
appels vers les API internes d'IgnitionRAG. Il ne doit pas devenir une seconde
implémentation du produit.

Le backend reste responsable de :

- la validation des clés client `ign_*` ;
- la résolution du scope utilisateur ou organisation ;
- les droits, plans, feature flags et limites ;
- les règles de configuration IA : BYOK historique en mode licence et
  capacités gérées par la plateforme en mode SaaS ;
- la persistence des workflows, collections, agents, widgets et évaluations ;
- l'exécution LangGraph et l'accès aux données.

Le serveur autonome est la surface exposée par le conteneur `mcp`. Le dossier
`back/src/mcp/` contient une surface parallèle historique et ne doit pas être
considéré comme le contrat du serveur déployé.

### Flux d'une requête

```text
Client MCP
  -> HTTPS /mcp/ (Nginx retire le préfixe /mcp/)
  -> serveur Hono :3002
  -> authentification Bearer et injection du scope
  -> handler de l'outil
  -> API interne backend :3001 avec X-MCP-*
  -> services et dépôts de la plateforme
```

Les handlers ne doivent jamais interroger directement PostgreSQL ou Qdrant.

## 2. Runtime et arborescence

| Élément | Valeur actuelle |
| --- | --- |
| Runtime | Bun 1.3+ |
| Langage | TypeScript strict |
| Framework HTTP | Hono |
| Validation locale | Zod |
| Version package/serveur | 1.1.0 |
| Protocole négocié | 2025-11-25 avec les clients actuels ; compatibilité 2024-11-05 |
| Port | 3002 |
| Outils exposés | 51 |
| Types de nœuds | 39 |

```text
ignitionrag-mcp/
├── src/
│   ├── index.ts                 # démarrage Bun
│   ├── server.ts                # routes JSON-RPC, HTTP et SSE
│   ├── config.ts                # variables et fail-fast production
│   ├── version.ts               # version serveur/protocole
│   ├── middleware/              # auth, CORS, rate-limit
│   ├── lib/backend-client.ts    # appels vers les API internes
│   ├── storage/
│   │   ├── backend.ts           # adaptateur workflow backend
│   │   └── memory.ts            # fallback local/test
│   ├── tools/                   # définitions et handlers des 51 outils
│   └── workflows/node-registry.ts
├── tests/
├── package.json
└── .env.example
```

## 3. Transport HTTP et JSON-RPC

### Endpoint canonique

Tous les messages MCP modernes passent par `POST /` sur le conteneur, soit
`POST /mcp/` sur le domaine public.

Méthodes JSON-RPC prises en charge :

| Méthode | Réponse |
| --- | --- |
| `initialize` | version, capacités `tools`, informations serveur et instructions |
| `tools/list` | tableau des 51 outils et leurs JSON Schemas |
| `tools/call` | résultat texte, avec `isError: true` sur les erreurs prises en charge |
| `ping` | objet vide |

Les méthodes inconnues renvoient l'erreur JSON-RPC `-32601`. Un JSON invalide
renvoie `-32700` avec HTTP 400.

Exemple d'initialisation directe :

```bash
curl -X POST http://localhost:3002/ \
  -H 'Authorization: Bearer dev-api-key' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  --data '{
    "jsonrpc":"2.0",
    "id":1,
    "method":"initialize",
    "params":{
      "protocolVersion":"2024-11-05",
      "capabilities":{},
      "clientInfo":{"name":"smoke","version":"1"}
    }
  }'
```

### Routage public

Nginx réécrit `^/mcp/(.*)` vers `/$1` :

| URL publique | Route dans le conteneur |
| --- | --- |
| `POST /mcp/` | `POST /` |
| `GET /mcp/` | `GET /` (canal SSE Streamable HTTP authentifié) |
| `GET /mcp/stream/:id` | `GET /stream/:id` |

Le chemin direct `/mcp/stream/:id` et les trois anciens endpoints JSON-RPC
segmentés restent disponibles quand on appelle directement le port 3002. Ils
ne constituent pas le chemin public recommandé.

### État de conformité transport

Le serveur utilise `WebStandardStreamableHTTPServerTransport` du SDK MCP
officiel, adapté à Bun et Hono. Le SDK gère l'initialisation, les notifications,
`ping`, la négociation de version, les réponses JSON et le canal SSE `GET /`.
Un test connecte le client TypeScript officiel, négocie `2025-11-25` et vérifie
l'inventaire des 51 outils.

Le mode reste volontairement stateless :

- aucun `Mcp-Session-Id` n'est émis ;
- `DELETE` ne termine pas de session persistante ;
- aucun event store ne permet une reprise via `Last-Event-ID` ;
- OAuth Protected Resource Metadata n'est pas encore exposée.

Les anciens endpoints directs `/mcp/initialize`, `/mcp/tools/list` et
`/mcp/tools/call` restent des adaptateurs de compatibilité hors du transport
canonique.

## 4. Authentification et isolation

### Clé client `ign_*`

1. Le client envoie `Authorization: Bearer ign_...`.
2. Le serveur calcule un hash SHA-256 pour son cache local de 60 secondes.
3. En cas de cache miss, il appelle
   `POST /api/internal/mcp/execute/validate-key` avec `MCP_API_KEY`.
4. Le backend renvoie `organizationId`, `userId` et `scopeType`.
5. Le serveur écrase les éventuels champs d'auth fournis par le client.
6. Chaque appel interne porte les en-têtes `X-MCP-Organization-Id`,
   `X-MCP-User-Id` et `X-MCP-Scope-Type`.

Une clé invalide ou expirée renvoie HTTP 401. Un droit MCP absent renvoie 403.
Une panne du service de validation renvoie 503 et n'est jamais transformée en
« clé invalide ».

### Clé service

`MCP_API_KEY` permet les appels internes de confiance. En production, elle
n'apporte pas seule un contexte tenant. Pour appeler un outil tenant-scoped,
le client de confiance ajoute :

```text
X-Organization-Id: org_...
X-User-Id: user_...       # optionnel, défaut = organizationId
```

Sans `X-Organization-Id`, `initialize` et `tools/list` restent possibles, mais
les outils nécessitant un scope renvoient une erreur claire.

### Règles impératives

- Ne jamais exposer `MCP_API_KEY` à un navigateur ou à un agent utilisateur.
- Ne jamais demander `organizationId`, `userId` ou `scopeType` comme argument
  d'outil : ces valeurs viennent exclusivement de l'authentification.
- Propager `scopeType` à chaque adaptateur backend. Une omission retombe par
  défaut sur `organization` et casse l'isolation des clés utilisateur.
- Pour une exécution locale de scope utilisateur, vérifier `userId`, pas
  seulement `organizationId`.

## 5. Configuration

```dotenv
PORT=3002
NODE_ENV=development
BACKEND_URL=http://localhost:3001
MCP_API_KEY=replace-with-the-backend-mcp-key
CORS_ORIGINS=*
RATE_LIMIT_MAX=100
RATE_LIMIT_WINDOW=60000
LOG_LEVEL=info
```

| Variable | Production | Notes |
| --- | --- | --- |
| `NODE_ENV` | `production` | Active le fail-fast |
| `BACKEND_URL` | obligatoire | URL interne du backend |
| `MCP_API_KEY` | obligatoire | Doit être identique côté backend et MCP |
| `PORT` | optionnel | `3002` par défaut |
| `CORS_ORIGINS` | recommandé | Liste séparée par des virgules ; vide par défaut en production |
| `RATE_LIMIT_MAX` | optionnel | `100` requêtes par fenêtre |
| `RATE_LIMIT_WINDOW` | optionnel | `60000` ms |
| `LOG_LEVEL` | optionnel | `warn` par défaut en production |

Le processus production s'arrête si `BACKEND_URL` ou `MCP_API_KEY` manque.
Le healthcheck est dégradé si le backend est inaccessible.

## 6. Catalogue des outils

### Découverte et ressources (7)

- `list_node_types` : catégories et nœuds disponibles.
- `get_node_definition` : ports, configuration et défauts d'un nœud.
- `list_workflow_resources` : collections, agents et configurations LLM
  accessibles en mode licence, avec exemples de configuration ; en SaaS, la
  découverte des ressources ne publie pas de configuration LLM.
- `list_workflows`, `list_collections`, `list_agents`, `list_llm_configs` :
  inventaires filtrés par le scope authentifié. `list_llm_configs` est réservé
  au mode licence ; en SaaS, l'outil n'est pas annoncé car l'IA est gérée par
  la plateforme.

### Workflows et exécution (10)

- `create_workflow`, `add_node`, `connect_nodes`, `update_node_config`.
- `remove_node`, `remove_edge`, `get_workflow`.
- `validate_workflow`, `execute_workflow`, `get_execution_status`.

`validate_workflow` contrôle notamment les nœuds obligatoires, les cycles, les
connexions, les ports et les entrées requises. En production,
`execute_workflow` délègue l'exécution réelle au backend LangGraph.

### Agent chat (1)

- `chat_with_agent` accepte `depth=quick` ou `depth=deep` et utilise l'agent,
  ses collections, ses skills et ses outils configurés côté plateforme. Le
  mode licence suit le LLM BYOK de l'agent ; en SaaS, la sélection modèle reste
  gérée par la plateforme.

### Widgets (7)

- `list_widgets`, `create_widget`, `get_widget`, `update_widget`,
  `delete_widget`, `regenerate_widget_key`, `get_widget_analytics`.

Les schémas SaaS n'annoncent pas `llmConfigId`, `llmModel` ou `llmSource` pour
la création et la modification d'un widget. Un appel direct qui transmet ces
champs est refusé avant l'API backend. Le mode licence conserve le contrat
BYOK existant.

### Évaluation et qualité RAG (7)

- `evaluate_collection`, `list_collection_evaluations`, `get_evaluation`.
- `get_ingestion_quality_report`, `list_collection_documents`.
- `apply_retrieval_preset`, `set_chunking_strategy`.

Les évaluations sont asynchrones. `evaluate_collection` renvoie un identifiant
à interroger avec `get_evaluation`. Les coûts LLM suivent la configuration BYOK
de l'utilisateur.

### Skills (8)

- `list_skills`, `create_skill`, `get_skill`, `update_skill`, `delete_skill`.
- `bind_skill_to_agent`, `unbind_skill_from_agent`, `list_agent_skills`.

### Agents et serveurs MCP attachés (6)

- `create_agent`, `get_agent`, `update_agent`.
- `list_mcp_catalog`, `attach_mcp_server_to_agent`,
  `list_agent_mcp_servers`.

En mode licence, les schémas agent couvrent le LLM principal et de fallback,
le modèle, le seuil de score, les outils intégrés, les skills, les workflow
tools et sous-agents. En SaaS, `llmConfigId`, `fallbackLlmConfigId` et `model`
ne sont pas annoncés et toute surcharge directe est refusée. La découverte
des agents retire aussi le modèle si le backend le renvoie.

### Connecteurs de données

`list_data_connectors` et `query_data_connector` ne sont pas annoncés ni
exécutables en SaaS : ce checkout ne monte pas leurs routes internes dans le
backend (`/api/internal/mcp/data-connectors`). Le schéma licence reste inchangé,
mais l'absence de route backend signifie que son parcours n'est pas validé et
doit être livré séparément avant de revendiquer son fonctionnement.

### Observabilité (2)

- `list_traces`, `get_trace` sont en lecture seule et filtrés par scope.

### Collections et ingestion (3)

- `create_collection`, `ingest_url`, `ingest_document`.

Les uploads binaires, imports S3, crawls de site complets et opérations admin ne
sont pas exposés par ces trois outils.

## 7. Registre des 39 nœuds

```text
trigger   trigger.manual, trigger.webhook, trigger.schedule, trigger.tool
llm       llm.chat, llm.structured, llm.streaming, llm.agent, agent.deep
rag       rag.retrieve, rag.hybrid, rag.rerank, rag.query_expand,
          rag.self_query, rag.hyde, rag.query_rewrite, rag.multi_query,
          rag.bm25, rag.llm_rerank, rag.contextual, rag.semantic_cache
evaluate  evaluate.relevance, evaluate.faithfulness, evaluate.llm_judge
tool      tool.web_search, tool.http, tool.code_exec, tool.custom
logic     logic.condition, logic.switch, logic.loop, logic.merge
transform transform.template, transform.json, transform.code,
          transform.filter
output    output.response, output.webhook, output.store
```

Le registre `src/workflows/node-registry.ts` est la source de vérité. Un agent
doit appeler `get_node_definition` au lieu de deviner les ports ou paramètres.

## 8. Construction d'un workflow

Séquence recommandée :

1. Lister les ressources accessibles.
2. Créer le workflow.
3. Ajouter un trigger.
4. Ajouter les nœuds RAG/LLM/agent nécessaires.
5. Ajouter un output.
6. Connecter les ports.
7. Valider.
8. Exécuter puis poller le statut.

Exemple de création sans champ d'authentification :

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "create_workflow",
    "arguments": {
      "name": "Assistant juridique",
      "description": "Recherche et synthèse"
    }
  }
}
```

Pour les nœuds RAG, choisir un `collectionId` renvoyé par
`list_workflow_resources`. En mode licence, les nœuds LLM peuvent utiliser une
configuration BYOK accessible ; ne jamais transmettre une clé fournisseur dans
le graphe. En mode SaaS, ne renseigner aucun fournisseur, modèle ou secret : la
capacité de génération est résolue côté plateforme.

## 9. Exécution et SSE

En mode licence, `execute_workflow` renvoie immédiatement un identifiant local :

```json
{
  "success": true,
  "executionId": "local-execution-id",
  "status": "running"
}
```

Le serveur conserve la correspondance avec l'identifiant backend, puis
`get_execution_status` interroge le backend avec le même scope que la clé.
En mode SaaS, le fallback local et les routes MCP historiques non réservées
sont refusés jusqu'à leur raccordement au parcours d'admission géré.

Pour les événements locaux :

```bash
curl -N https://app.ignitionrag.com/mcp/stream/local-execution-id \
  -H 'Authorization: Bearer ign_...'
```

Événements possibles : `start`, `node_start`, `node_output`, `node_complete`,
`token`, `complete`, `error`, plus un keep-alive `ping`.

Limite opérationnelle : la correspondance local/backend est en mémoire. Après
un redémarrage du conteneur, un ancien identifiant local peut ne plus être
pollable, même si l'exécution backend existe toujours.

## 10. Sécurité opérationnelle

### Défenses actives

- comparaison timing-safe de la clé service ;
- hash des clés client avant cache ;
- cache court de validation (60 s) ;
- injection serveur du scope ;
- rate-limit fixe par IP vue par le proxy ;
- Nginx écrase `X-Real-IP`, `X-Forwarded-For` et vide
  `CF-Connecting-IP` sur la route MCP ;
- CORS configurable ;
- erreurs backend structurées sans journaliser la clé.

### Points à renforcer

- ajouter un event store et des sessions seulement si un usage produit exige
  la reprise des flux transport ;
- valider explicitement les hôtes autorisés contre le DNS rebinding ;
- ajouter OAuth Protected Resource Metadata si des clients OAuth sont ciblés ;
- supprimer l'adaptateur PostgreSQL mort et ses dépendances après vérification
  de packaging ;
- consolider les schémas MCP et backend dans des contrats générés ou partagés.

## 11. Déploiement

Le Dockerfile production utilise Bun, un utilisateur non-root et le healthcheck
`GET /health`. Les fichiers Compose de `deploy/` injectent :

```yaml
environment:
  - NODE_ENV=production
  - PORT=3002
  - BACKEND_URL=http://backend:3001
```

`MCP_API_KEY` doit venir du fichier d'environnement du déploiement et doit
correspondre à celui du backend.

Contrôles après déploiement :

```bash
# Le serveur répond et le backend est joignable
curl -fsS http://127.0.0.1:3002/health

# La frontière publique refuse une initialisation sans Bearer
curl -sS -o /dev/null -w '%{http_code}\n' \
  -X POST https://app.ignitionrag.com/mcp/ \
  -H 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
# attendu : 401
```

## 12. Tests et validation

```bash
cd ignitionrag-mcp
bun install --frozen-lockfile
bun run typecheck
bun test
bun run build
```

Les tests de conformité runtime vérifient notamment :

- la propagation réelle de `X-MCP-Scope-Type: user` ;
- l'isolation du polling d'exécution par utilisateur ;
- le fail-fast de la configuration production ;
- les champs d'auth absents des schémas exposés ;
- `isError: true` sur les échecs critiques ;
- les en-têtes CORS et Nginx ;
- l'auth du chemin SSE réécrit par le proxy.
- la connexion d'un client officiel, la négociation `2025-11-25` et la liste
  complète des outils.

## 13. Dépannage

### `401 INVALID_API_KEY`

Vérifier le préfixe `ign_`, l'expiration de la clé et son activation côté
plateforme. Ne pas remplacer automatiquement la clé client par la clé service.

### `403 FEATURE_NOT_AVAILABLE`

La clé est authentifiée mais le plan, le scope ou un feature flag refuse la
capacité demandée.

### `503 SERVICE_UNAVAILABLE` pendant la validation

Vérifier `BACKEND_URL`, la disponibilité du backend et l'égalité de
`MCP_API_KEY` entre les deux services.

### `organizationId is required`

Un appel utilise probablement la clé service sans `X-Organization-Id`. Avec
une clé client `ign_*`, ce champ est normalement injecté automatiquement.

### Un outil utilisateur ne trouve pas sa ressource

Contrôler que le handler propage `scopeType` jusqu'au client backend et que
l'en-tête reçu vaut `user`, pas la valeur par défaut `organization`.

### Le stream public répond 404

Vérifier que le serveur expose `/stream/:executionId` et que Nginx réécrit
`/mcp/stream/:executionId` vers ce chemin. Ne pas ajouter un second préfixe
`/mcp/` dans l'upstream.
