Raccourcis clavier

Appuyez sur ← ou → pour passer d'un chapitre à l'autre

Appuyez sur S ou / pour rechercher dans la documentation

Appuyez sur ? pour afficher cette aide

Appuyez sur Échap pour masquer cette aide

Créer votre premier connecteur MCP

Ce tutoriel décrit le chemin réel pour ajouter un service HTTP à la passerelle MCP de Chatbotaurus : un fichier YAML déclaratif, une ligne dans la liste des serveurs autorisés, des identifiants chiffrés, puis un appel de test. Il s'adresse à un contributeur qui travaille sur le dépôt. À la fin, l'outil de votre service figure dans tools/list et un appel tools/call en rend les données.

Prérequis

  • Chatbotaurus installé et fonctionnel (voir Installation).
  • Le dépôt cloné : le connecteur est un fichier du dépôt, pas un réglage de l'interface.
  • Python 3 pour le validateur de connecteurs.
  • Un jeton de session d'un compte du locataire, obtenu comme décrit dans Swagger (section Authentification), puis rangé dans la variable JWT : export JWT=<token>.
  • De quoi reconstruire le backend : l'étape 3 modifie du code Rust, compilé dans le binaire.
  • Un accès au service que vous branchez (adresse et clé), avec les seuls droits utiles.

Les commandes de ce tutoriel visent le backend local http://localhost:3000. Sur un déploiement, remplacez cette adresse par celle de votre API (https://<hote-api> dans les chapitres API).

Ce qu'est un connecteur

Un connecteur déclaratif est un fichier connectors/<id>.yaml. Au démarrage, forge-api lit tous les fichiers de ce dossier, remplace les ${VAR:-défaut} par l'environnement, et enregistre un connecteur HTTP dans le registre. Un fichier invalide est ignoré avec un message de journal : il ne fait pas échouer le démarrage, il disparaît. C'est la première chose à vérifier quand un connecteur « n'apparaît pas ».

Un connecteur n'est pas un module Rust à écrire. Pour un service HTTP, le YAML suffit. Le code Rust n'intervient que pour des protocoles qui ne sont pas du HTTP (c'est le cas d'Odoo, dispatché en XML-RPC).

Étape 1 : partir du gabarit

Copiez le gabarit, qui est la forme de référence maintenue avec le parseur :

cp connectors/TEMPLATE.yaml connectors/mon-service.yaml

Remplissez les champs obligatoires :

id: mon-service                         # kebab-case, identique au nom de fichier
display_name: "Mon Service"
upstream_status: a_verifier             # hebergeable | a_verifier | non_http

base_url: "${MON_SERVICE_URL:-https://api.mon-service.eu}"
base_url_env: MON_SERVICE_URL

auth: { type: bearer, token_env: MON_SERVICE_API_KEY }

compliance:
  data_residency: self-hosted
  self_hosted: true
  gdpr_compliant: true
  license: "MIT"
  gaia_x: true

timeout_ms: 30000

tools:
  - name: mon_service_list_items
    description: "Lister les éléments, avec filtres optionnels."
    method: GET
    path: "/items"
    param_location: query
    input_schema:
      type: object
      properties:
        limit: { type: integer }

  - name: mon_service_get_item
    description: "Récupérer un élément par identifiant."
    method: GET
    path: "/items/{id}"
    param_location: path
    input_schema:
      type: object
      properties:
        id: { type: string }
      required: [id]

  - name: mon_service_create_item
    description: "Créer un élément."
    method: POST
    path: "/items"
    param_location: body
    input_schema:
      type: object
      properties:
        title: { type: string }
      required: [title]

Cinq règles qui évitent les pannes silencieuses :

  • Chaque outil porte method et path. Sans eux, le chargement du connecteur échoue et le catalogue avale l'erreur.
  • Avec param_location: path, seuls les arguments qui correspondent à un {placeholder} du chemin sont transmis. Les autres sont abandonnés sans message : un filtre déclaré ainsi ne filtre rien. Utilisez query ou body.
  • Un outil qui écrit commence par un verbe d'écriture (create, update, delete, entre autres). Le nom sert aux contrôles de sécurité à classer l'outil en lecture ou en écriture.
  • Le data_residency doit appartenir à eu, eu-west, eu-central, france, germany, finland, netherlands, ireland ou self-hosted, et gdpr_compliant doit être vrai : sinon le registre rejette le connecteur.
  • Les secrets ne sont jamais dans le YAML : seulement le nom de la variable (token_env). Les types d'authentification du gabarit sont none, bearer, api_key, basic et query_token. Le parseur en connaît d'autres (login_token, oauth2, jwt_exchange, entre autres) : voir l'énumération AuthConfig dans crates/forge-mcp/src/connector/http.rs.

Choisissez un base_url par défaut qui répond réellement : un défaut qui ne résout jamais fabrique un connecteur qu'aucune mesure ne pourra rendre vert.

Étape 2 : valider le fichier

python scripts/audit/validate-connectors.py

Le validateur lit le même schéma que le parseur. Résultat attendu : la ligne [OK] HARD checks all passed. et le code de sortie 0. Une ligne [FAIL] suivie de la liste des fautes dit ce qu'il faut corriger : corrigez tout message de niveau HARD avant de continuer. La licence déclarée sous compliance.license est aussi contrôlée par une garde du dépôt, qui refuse les licences à source disponible (SSPL, BSL, BUSL, Elastic-2.0 et assimilées).

Étape 3 : autoriser le connecteur

La passerelle refuse de dispatcher un connecteur dont l'identifiant ne figure pas dans la liste des serveurs autorisés. C'est le point d'étranglement de conformité européenne : sans cette ligne, le connecteur se charge, mais ses outils n'apparaissent pas dans tools/list (outils_autorises, crates/forge-mcp/src/gateway.rs) et l'appel est refusé. Les fiches dolibarr et freescout sont dans ce cas.

Ajoutez "mon-service" à ALLOWED_MCP_SERVERS dans crates/forge-core/src/compliance/allowed_servers.rs. Un test du même fichier compte les entrées de la liste : mettez à jour ce compte avec une justification dans le commit, c'est voulu.

Étape 4 : enregistrer les identifiants du locataire

Les identifiants d'un locataire sont chiffrés en AES-256-GCM en base. Au moment d'un appel, la passerelle déchiffre les identifiants du locataire et les publie comme variables d'environnement, pour cette seule requête. Tout champ de la fiche dont le nom ressemble à une variable de connecteur (majuscules et suffixe _KEY, _TOKEN, _SECRET, _PASSWORD, _USER, _URL, _ID, _DATABASE, entre autres) est publié tel quel : nommez donc les champs comme les variables du YAML.

curl -X POST http://localhost:3000/api/v1/credentials \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "mon-service-prod",
    "credentialName": "monServiceApi",
    "plainDataObj": {
      "MON_SERVICE_URL": "https://api.mon-service.eu",
      "MON_SERVICE_API_KEY": "votre-clé-secrète"
    }
  }'

Résultat attendu : 201, avec la fiche créée (id, name, credential_name, masked_value, workspace_id, created_at, updated_at). La valeur secrète n'est jamais renvoyée. Sans workspaceId, la fiche va dans le premier espace de travail accessible ; sans espace accessible, la réponse est 400 (NO_WORKSPACE).

La page /credentials de l'application propose la même action par un formulaire pour les fiches du catalogue de l'interface.

Étape 5 : recharger les connecteurs

Les fichiers de connectors/ ne sont lus qu'au démarrage du backend.

  • En développement, le surveillant du backend reconstruit et remplace le processus ; sinon relancez le lanceur du backend.
  • Sur un déploiement Podman, le dossier connectors/ et la liste de l'étape 3 sont copiés dans l'image du backend à la construction : reconstruisez l'image avec votre modification (voir Déploiement avec Podman), puis redémarrez le conteneur :
podman restart forge-api

Contrôlez ensuite le journal de démarrage : une ligne failed to load connector config ou connector rejected by registry nomme le fichier fautif. L'absence de ces deux lignes, puis la présence de vos outils dans tools/list (étape 6), confirment le chargement.

Étape 6 : tester par le protocole MCP

Le transport est Streamable HTTP sur un seul point d'entrée, /api/v1/mcp. Il faut d'abord ouvrir une session ; l'identifiant revient dans l'en-tête Mcp-Session-Id.

# 1. Ouvrir la session et lire l'en-tête de réponse Mcp-Session-Id
curl -i -X POST http://localhost:3000/api/v1/mcp \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}'

# 2. Lister les outils
curl -X POST http://localhost:3000/api/v1/mcp \
  -H "Authorization: Bearer $JWT" \
  -H "Mcp-Session-Id: $SESSION" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":2}'

# 3. Appeler un outil : le nom est <id du connecteur>.<nom de l'outil>
curl -X POST http://localhost:3000/api/v1/mcp \
  -H "Authorization: Bearer $JWT" \
  -H "Mcp-Session-Id: $SESSION" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "mon-service.mon_service_list_items",
      "arguments": { "limit": 5 }
    },
    "id": 3
  }'

Résultats attendus :

  • Appel 1 : 200, un en-tête mcp-session-id et un corps result (protocolVersion, capabilities, serverInfo). Copiez l'identifiant dans la variable SESSION : export SESSION=<session-id>.
  • Appel 2 : 200 et result.tools, une liste d'objets name, description, inputSchema et connector_id. Ajoutez | jq -r '.result.tools[].name | select(startswith("mon-service."))' à la commande pour ne garder que vos outils. Si votre connecteur n'y figure pas, vérifiez l'étape 3 (liste), l'étape 5 (rechargement) et le journal de démarrage.
  • Appel 3 : 200 aussi. result.type vaut success et result.content porte les blocs de texte. Avant l'exécution, un échec est un objet error à la place de result, et error.code reprend le code HTTP de l'échec (401 session expirée, 403 serveur hors de la liste autorisée, 404 outil inconnu). Si votre service amont échoue, result.type vaut error et result porte code et message.

Sans Mcp-Session-Id, tools/call répond 400. Une session expire après 30 minutes d'inactivité.

Quand le YAML ne suffit pas

Pour un protocole qui n'est pas du HTTP, un connecteur natif implémente le trait McpConnector du crate forge-mcp (crates/forge-mcp/src/connector/mod.rs) : id, display_name, compliance, list_tools, execute, health_check, et required_auth_env_vars si le connecteur consomme un secret. Le connecteur Odoo (odoo_xmlrpc_client.rs) est l'exemple à lire. Ce cas est rare.

Bonnes pratiques

  • Les lectures peuvent être mises en cache (cache_ttl_s, désactivé par défaut). Le cache n'est jamais actif sous un contexte de locataire, pour qu'aucune réponse ne passe d'un locataire à l'autre.
  • Déclarez timeout_ms en fonction du service cible.
  • Testez le connecteur contre le service réel avant de le proposer : un connecteur qui répond 200 sur une réponse non filtrée est un faux vert.

Voir aussi