Source docs/mcp.fr.md · 1de96aa

Le serveur MCP : piloter Libris depuis un agent de code

Libris répond au Model Context Protocol sur POST /mcp. N’importe quel client MCP — opencode, Claude Code, Codex ou tout autre outil qui parle ce protocole — peut alors lire votre bibliothèque, configurer un livre, tenir le glossaire, lancer et diriger le travail, et relire la traduction, dans ses propres termes plutôt qu’à travers curl.

C’est la même installation, le même compte et les mêmes règles que dans l’interface : access() décide quels livres vous pouvez toucher, la garde de licence refuse toujours le travail qui consomme des mots, les budgets mettent toujours en pause un travail qui dépense trop, et tout aboutit dans le même journal d’audit. Il n’y a pas de seconde porte d’entrée.

Créer un jeton

Mon compte › Jetons d’API, Créer un jeton. Ne lui donnez que les permissions dont l’agent a besoin — c’est tout l’intérêt :

PermissionCe qu’elle ouvre
library:readLa bibliothèque, un livre, ses chapitres, sa qualité, son glossaire, sa bible, la licence, les estimations de coût
series:readLes séries et le rapport de cohérence
library:writeLes réglages d’un livre ou d’une série, l’archivage, les entrées de glossaire, le rattachement de volumes, l’aperçu et l’application de la propagation d’un terme
library:deleteLa suppression d’un livre, d’une série ou d’un terme — et seulement avec confirm
pipeline:startLe lancement du travail (celle-ci dépense de l’argent)
jobs:read, jobs:controlLe suivi du travail, ainsi que sa mise en pause, sa reprise, son annulation ou sa relance
results:readLa relecture de la traduction sous forme de texte
content:writeLa liste des providers

Un jeton qui ne fait que lire ne peut rien supprimer, quelle que soit la manière dont on le demande à l’agent. Le secret n’est affiché qu’une fois.

Le serveur MCP fait partie de l’API d’automatisation, que comprennent les formules Studio et Pro. Sur une installation dont la licence ne la comprend pas (Essai, Personnel), aucun jeton ne peut être créé, et POST /mcp répond 402 avec l’erreur JSON-RPC -32002 et data.code = "automation_not_licensed" ; les jetons existants sont conservés et refonctionnent dès que la licence comprend l’API.

Connecter un client

Le transport est le streamable HTTP et l’identifiant est un jeton porteur (bearer token) ordinaire.

# Claude Code
claude mcp add --transport http libris https://libris.example/mcp \
  --header "Authorization: Bearer lbr_…"
// opencode.json
{
  "mcp": {
    "libris": {
      "type": "remote",
      "url": "https://libris.example/mcp",
      "headers": { "Authorization": "Bearer lbr_…" }
    }
  }
}

Codex utilise le TOML, pas le format JSON d’opencode. Ajoutez ceci à ~/.codex/config.toml sur la machine où tourne Codex :

[mcp_servers.libris]
url = "https://libris.example/mcp"
bearer_token_env_var = "LIBRIS_MCP_TOKEN"

Fournissez LIBRIS_MCP_TOKEN dans l’environnement du processus qui lance Codex. Pour le rendre permanent, utilisez la configuration d’environnement protégée de ce lanceur ; l’exporter dans un autre terminal ne modifie pas un processus en cours. Redémarrez Codex après l’avoir modifié, et ne versionnez jamais le secret dans un dépôt. Libris utilise ses propres jetons d’API, pas OAuth : cette connexion ne demande ni connexion dans un navigateur ni rappel vers localhost. Un serveur MCP GitLab distinct peut avoir d’autres exigences d’authentification. Voir la référence officielle de configuration MCP de Codex.

Rien n’est conservé entre deux appels : le jeton porte tout le contexte, il n’y a donc aucune session à ouvrir ni à perdre. Le serveur ne parle jamais en premier, si bien que GET /mcp répond 405 — c’est la façon dont le protocole indique qu’il n’y a aucun flux à écouter.

Les outils

Le catalogue contient 28 outils. initialize indique ce nombre à partir du catalogue en service.

Lecture — list_books, get_book, list_chapters, book_quality, list_terms, read_bible, list_series, get_series, series_consistency, list_providers, licence_status, estimate_job.

Modification — configure_book, shelve_book, create_series, update_series, attach_volume, add_term, edit_term, propagate_term.

attach_volume prend un volume_number, ou un volume_label libre pour un volume spécial (« DX1 », « EX », « LN 14+ »), jamais les deux ; list_books donne les deux champs.

Suppression — delete_book, delete_series, delete_term.

Travail — start_job, list_jobs, control_job, control_series, export_book.

Trois choses qu’un agent est conçu pour gérer, et qu’il vaut la peine de connaître quand vous lisez ce que le vôtre a fait :

La confirmation n’accepte que le booléen JSON true. Les chaînes comme "false" ou "yes", les nombres et les tableaux n’autorisent jamais une suppression, même quand Python les considérerait comme vrais.

  • configure_book, update_series et edit_term ne modifient que ce qu’on leur donne. Les réglages ou le terme actuels sont lus d’abord : un appel qui mentionne la qualité ne réinitialise donc pas en douce la langue cible, et un appel qui change la traduction d’un terme ne le déverrouille pas (seul locked: false le fait) ;
  • tout ce qui supprime exige confirm: true. Sans cela, l’outil ne détruit rien et répond ce qui aurait disparu — le titre du livre, le nombre de volumes de la série ;
  • les listes sont paginées et les réponses longues sont tronquées, toujours avec le total indiqué. Une bibliothèque de cinq cents livres n’est pas une réponse, c’est une fenêtre de contexte. export_book renvoie du texte : demandez une plage de chapitres plutôt qu’un feuilleton entier.

Les arguments doivent suivre le schéma publié : un champ inconnu ou un mauvais type donne une réponse isError, pas une valeur convertie. limit est un entier de 1 à 200 ; offset est un entier positif ou nul. Un décalage au-delà de la fin renvoie une page vide. Un objet inaccessible et un objet absent donnent le même refus, sans révéler si un autre compte le possède.

Lancer, suspendre ou reprendre une série entière

control_series, avec la permission jobs:control existante, prend un series_id et une action : start, pause, resume ou cancel. Seul le propriétaire de la série peut l’utiliser ; le partage d’un volume ne donne aucun contrôle sur le reste de sa série.

start exécute toute la chaîne — analyse, traduction, relecture — sur chaque volume, et exige aussi pipeline:start : un agent à qui l’on a donné le droit d’arrêter une série ne peut pas être amené à la traduire. Il refuse une série suspendue (resume d’abord, volontairement) sauf si l’appel définit aussi resume_if_paused: true — désactivé par défaut, pour qu’un agent ne lève une pause que lorsque c’est réellement ce qu’on lui a demandé. Il laisse de côté les volumes archivés et ne lance pas un volume déjà en cours de travail.

pause enregistre durablement une suspension sur la série avant de passer en revue ses travaux. Les nouveaux lancements payants, y compris les démarrages automatiques de la surveillance des sources, sont refusés jusqu’à ce que resume lève cette suspension. Les appels déjà envoyés au provider peuvent se terminer ; aucune nouvelle admission payante ne franchit la suspension. La lecture, la modification et l’export restent disponibles. cancel annule les travaux mais ne lève pas de suspension et n’en crée pas.

La réponse est faite au mieux, pas un 409 global : volumes nomme chaque volume, le nombre de jobs modifiés, s’il est done, ainsi qu’un code et un message de refus le cas échéant. Les totaux done, refused et deferred distinguent la réussite, le refus et la reprise planifiée. Une file pleine au niveau du compte ou du jeton planifie durablement les reprises excédentaires ; les quotas d’exécution du provider, du compte et du jeton s’appliquent toujours. Les refus liés à la licence et au budget sont revérifiés même après une pause manuelle.

Un jeton ne peut reprendre que les travaux payants lancés à l’origine par ce même jeton ; le travail lancé depuis une session et celui d’un autre jeton sont refusés un par un. La pause et l’annulation restent à la disposition de leur propriétaire.

Propager une décision de glossaire

Modifier un terme du glossaire gouverne la traduction à venir. propagate_term reporte aussi cette décision sur les traductions existantes, sans appeler de modèle ni consommer de mots de licence :

{
  "name": "propagate_term",
  "arguments": {
    "book_id": "an-id-from-list_books",
    "previous": "Pilule Dorée",
    "replacement": "Noyau d’Or"
  }
}

Ceci n’est qu’un aperçu. dry_run vaut true par défaut, aussi bien dans le catalogue que dans le gestionnaire. La réponse compte les passages, les occurrences et les chapitres, signale à part les corrections humaines protégées, et fournit jusqu’à cinq exemples avant/après. Après les avoir examinés, répétez le même appel avec dry_run: false et le booléen JSON confirm: true pour écrire. Ni une confirmation absente ni des chaînes comme "false" n’autorisent de modification. Chaque passage modifié reçoit une version dans son historique, et un passage modifié en même temps est ignoré au lieu d’être écrasé.

series vaut true par défaut, mais n’inclut que les volumes que ce compte peut modifier ; les volumes frères en lecture seule ou privés sont exclus de l’aperçu comme de l’écriture. Définissez series: false pour ce volume seul, ou chapter_id pour un seul chapitre. Le remplacement est exact et sensible à la casse. Les corrections humaines restent exclues, sauf si include_human: true est demandé explicitement. Cet outil exige library:write même pour un aperçu et annonce readOnlyHint: false, destructiveHint: false. Une correction humaine incluse explicitement conserve sa protection humaine et son état de validation après la modification du terme : une sortie ultérieure du modèle ne peut toujours pas la remplacer.

Une analyse interactive accepte au plus 20 000 passages traduits et 10 millions de caractères sérialisés (source, traduction et métadonnées des passages), sur l’ensemble de la série sélectionnée. La sortie du remplacement a le même plafond de caractères. La base de données vérifie l’entrée avant de charger le texte ; une requête trop volumineuse n’écrit rien. Restreignez-la à un volume ou à un chapitre et refaites l’aperçu. Le thaï et le lao, comme les idéogrammes han et les kana, trouvent les termes littéraux à l’intérieur d’un texte sans espaces. La casse reste volontaire : Pilule et pilule exigent des aperçus distincts, afin qu’un nom propre ne devienne jamais en silence un nom commun. Les outils et les sessions de base de données s’exécutent ensemble dans le pool de workers borné de l’API, et non dans sa boucle d’événements asynchrone. Cela garde les requêtes sans rapport réactives sans partager une session entre plusieurs threads.

Ce que cela coûte

start_job dépense de l’argent chez votre provider et des mots de votre licence. estimate_job indique ce que coûterait une opération sur ce livre — mots, passages, prix, et ce qui reste du budget — et il faut demander à un agent de l’appeler d’abord. Un travail mis en pause par la garde de licence ou par un budget ne reprend jamais de lui-même : c’est control_job avec resume qui le relance.

Les travaux MCP conservent le jeton d’origine : son budget, ses quotas de travaux en attente et en cours et sa priorité maximale s’appliquent exactement comme pour les requêtes d’automatisation. Les appels MCP directs et les requêtes REST sont comptés une fois chacun. Un agent peut mettre en pause ou annuler tout travail qu’il peut modifier, mais il ne peut reprendre un travail payant que si ce travail a été lancé par le même jeton. Utilisez l’interface pour reprendre un travail lancé ailleurs. L’opération gratuite sync_memory échappe à cette restriction de propriété.

Une limite de débit n’est pas une réservation de dépenses : des travaux simultanés peuvent encore dépasser le plafond partagé du jeton pendant que des appels déjà lancés se terminent. Gardez des réglages de simultanéité et des limites côté provider prudents ; l’amélioration par réservation partagée est suivie séparément.

Limite de débit

Le même plafond par jeton que l’API d’automatisation (API_RATE_LIMIT_PER_MINUTE), car un agent qui tourne en boucle est le cas ordinaire plutôt que l’exception. Chaque message d’un lot JSON-RPC compte, notifications comprises, et pas seulement son enveloppe HTTP. Le lot entier est admis ou refusé avant l’exécution du moindre outil. Au-delà de la limite, la réponse est 429 avec Retry-After.

Un lot contient au plus 200 messages et un corps de requête au plus 1 Mio, indépendamment des cookies ou de la limite d’envoi de l’installation. Un cookie de session valide n’authentifie jamais /mcp : seul son jeton porteur le fait. Une requête avec id: null reçoit une réponse ; une notification sans id n’en reçoit pas. Les tableaux vides, null, les chaînes ou les nombres ne sont pas des objets de paramètres valides. Les demi-codets (surrogates) Unicode non appariés sont refusés comme JSON malformé avant qu’aucun outil de la requête ou du lot ne puisse écrire. Les paires de substitution correctes, les emoji et le texte CJK restent acceptés.