MCP Server FR - Kheopsian/Hydranos GitHub Wiki
S'applique à Hydranos 4.4.
Cette page est pour vous si vous voulez qu'un assistant IA, ou tout autre client MCP, consulte votre bibliothèque et agisse dessus via le Model Context Protocol. À la fin, vous saurez connecter un client, quels outils il obtient, lesquels peuvent supprimer des données et comment les déverrouiller, et ce que le serveur refuse. Vocabulaire : Concepts & glossaire.
Chaque instance Hydranos sert un serveur MCP sur la même écoute que l'interface web, port 8199, à POST /mcp. Il n'y a rien à installer ni à activer, et aucun écran ne lui est consacré dans l'interface web.
-
Transport : « streamable HTTP » sans état. Chaque
POST /mcptransporte un message JSON-RPC 2.0 et reçoit une réponse JSON. Il n'y a ni session ni flux SSE :GET /mcpetDELETE /mcprépondent405avecAllow: POST. -
Versions du protocole :
2025-06-18,2025-03-26,2024-11-05. Si le client demande une autre version, le serveur répond avec2025-06-18. -
Méthodes :
initialize,ping,tools/list,tools/call. Le serveur propose uniquement des outils : ni ressources, ni prompts.serverInfovaut{"name": "hydranos", "title": "Hydranos", "version": "4.x.y"}(la version en cours d'exécution). La réponse àinitializecontient aussi de courtesinstructionsqui disent au modèle de commencer paroverview, de paginer avecfind_torrentset de désigner les torrents par leur info hash. -
Pendant le démarrage d'Hydranos,
/mcprépond503avecRetry-After: 5, comme toutes les routes de l'API, jusqu'à ce que le catalogue soit chargé (API REST, Pendant le démarrage d'Hydranos). -
Les lots (un tableau JSON de messages) sont refusés. Un message sans
idest une notification :202avec un corps vide. -
Les écritures passent par l'API REST. Chaque outil d'écriture, sauf
add_torrent, envoie sa requête à la vraie route/apià l'intérieur du processus, et subit donc les mêmes contrôles et refus que l'interface web.add_torrentappelle la même fonction d'ajout que les routes d'ajout, sans passer par une route. Aucune écriture ne prend de filtre : chaque écriture désigne ses torrents par info hash.
sequenceDiagram
participant C as Client MCP
participant M as Hydranos POST /mcp
participant A as Routes /api dans le processus
C->>M: initialize avec la clé en Authorization Bearer
M-->>C: protocolVersion, capacité tools, serverInfo hydranos
C->>M: notifications/initialized, sans id
M-->>C: 202, sans corps
C->>M: tools/list
M-->>C: 19 outils, ou 21 avec allow_destructive
C->>M: tools/call find_torrents
M->>M: construit une page à partir de la liste du moteur
M-->>C: résultat avec content text et structuredContent
C->>M: tools/call pause avec info_hashes
loop chaque moteur qui détient une partie des hashes
M->>A: POST /api/engines/ENGINE/torrents/bulk
A-->>M: statut et réponse
end
M-->>C: résultat, isError false, ok et statut par moteur
Note over C,M: isError true quand l'outil lui-même échoue<br/>(arguments invalides, outil désactivé, torrent inconnu,<br/>ajout refusé, lecture en échec). Les échecs d'écriture<br/>par torrent restent isError false avec ok false
Il vous faut deux choses : l'URL http://<host>:8199/mcp et la clé API de l'instance ([daemon] api_key, voir Sécurité & accès). Le serveur accepte la clé sous l'une de ces formes :
| Comment | Exemple |
|---|---|
En-tête Authorization: Bearer (ce qu'envoient les clients MCP) |
Authorization: Bearer YOUR_API_KEY |
En-tête X-Api-Key
|
X-Api-Key: YOUR_API_KEY |
Paramètre de requête apikey, pour les clients qui ne peuvent pas définir d'en-têtes |
http://192.0.2.10:8199/mcp?apikey=YOUR_API_KEY |
Le cookie SID posé par la connexion au shim qBittorrent (POST /api/v2/auth/login) |
inutile pour un client MCP ; l'interface web elle-même n'utilise aucun cookie |
Une clé absente ou erronée reçoit 401 avec WWW-Authenticate: Bearer et {"error": "Invalid or missing API key"}. Si api_key est vide, personne n'entre. La forme en paramètre de requête met la clé dans les URL, qui finissent dans les journaux du proxy : utilisez un en-tête quand votre client le permet.
La plupart des clients MCP lisent un fichier JSON avec un objet mcpServers. Ajoutez Hydranos comme serveur HTTP :
{
"mcpServers": {
"hydranos": {
"type": "http",
"url": "http://192.0.2.10:8199/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}Certains clients appellent ce type streamable-http ou streamableHttp au lieu de http ; Hydranos ne s'en soucie pas, il ne voit que les POST. Si votre client ne prend en charge que les serveurs locaux (stdio), intercalez un pont stdio vers HTTP et donnez-lui la même URL et le même en-tête.
Avec Claude Code, par exemple, la même chose tient en une commande :
claude mcp add --transport http hydranos http://192.0.2.10:8199/mcp \
--header "Authorization: Bearer YOUR_API_KEY"# Handshake
curl -s http://192.0.2.10:8199/mcp \
-H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
# List the tools
curl -s http://192.0.2.10:8199/mcp \
-H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# Ten torrents of the hoard engine in error with their tracker
curl -s http://192.0.2.10:8199/mcp \
-H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"find_torrents","arguments":{"engine":"hoard","error_class":"dead","limit":10}}}'Comme le serveur est sans état, vous pouvez envoyer tools/call sans initialize préalable ; la poignée de main n'existe que pour les clients qui l'attendent.
19 outils sont listés par défaut. Chacun porte des annotations MCP (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) pour que le client puisse décider quand vous demander confirmation avant l'appel. Les arguments engine prennent un identifiant de moteur tel que overview le liste ; s'il est omis, les lectures utilisent hoard (ou le premier moteur s'il n'y a pas de hoard).
| Outil | Arguments | Ce qu'il renvoie |
|---|---|---|
overview |
aucun | Chaque moteur de cette instance : identifiant, rôle, en écoute ou non, nombre de torrents, débits d'envoi/réception en direct, pairs actifs, comptes par état, erreurs de tracker, erreurs de torrent, classes d'erreur, torrents sans catégorie, et les 15 premières catégories, trackers et tags. Prend quelques secondes sur une très grosse bibliothèque. |
find_torrents |
engine, search, category, tag, tracker, state, error_class, sort (par défaut added_time), order (asc/desc, par défaut desc), offset, limit (par défaut 25, max 200) |
Une page de torrents, filtres combinés en ET. Réponse : total, filtered, returned, rows, et next_offset tant qu'il reste des pages. search correspond à tous les mots du nom, ou à un préfixe d'info hash d'au moins 6 caractères hexadécimaux. Chaque ligne est un ensemble compact de champs (nom, info hash, état, progression, taille, catégorie, tags, hôte du tracker, ratio, envoi, débits, pairs, seeds, date d'ajout, chemin de sauvegarde, l'étiquette de ligne agent, indicateur d'arrêt par l'utilisateur, et l'erreur de tracker avec sa classe). |
torrent_detail |
info_hash (obligatoire) |
La ligne complète d'un torrent, le moteur qui le détient et sa liste de trackers en direct. |
torrent_files |
info_hash (obligatoire), limit (par défaut 100, max 1000) |
Nombre de fichiers, taille totale, et les fichiers avec leur taille. |
tracker_errors |
engine, error_class, samples (par défaut 3, max 20) |
Les torrents en erreur de tracker regroupés par classe (dead = non enregistré, auth = passkey refusée, throttled, unreachable, other), chacune avec ses principaux trackers et des exemples de messages. |
trackers |
problems_only, include_hidden (tous deux false par défaut), limit (par défaut 50, max 500) |
Chaque hôte de tracker : nombre de torrents, statut de la dernière annonce, dernière erreur, passkey et réglages d'annonce. Les plus gros d'abord. |
categories |
aucun | Chaque catégorie avec son chemin de sauvegarde et son mode. |
health |
aucun | Le rapport d'anomalies de /api/health/anomalies : torrents dont les données manquent, chemins de sauvegarde fantômes, seeds sans leurs pièces, téléchargements à l'arrêt faute de sources, re-téléchargements, hashes seedés deux fois, fichiers qu'un autre torrent détient avec d'autres octets (issus d'une passe de fond toutes les 5 minutes, generated_at en donne l'heure ; scan vaut pending jusqu'à la première), plus des comptes en direct des trackers en échec, des annonces en retard, des moteurs retenus et des disques à court d'espace, par moteur. Instantané. Voir Supervision & journaux. |
drain |
history (par défaut 10 ; 50 au plus reviennent, la route ne garde que les 50 derniers passages) |
État du drain race par volume et les passages de drain les plus récents. |
jobs |
state (par ex. running, done, failed), limit (par défaut 20 ; 100 au plus reviennent) |
Les jobs de fond, les plus récemment mis à jour d'abord, avec leur progression. Seuls les 100 jobs créés le plus récemment sont examinés, puis filtrés par state. |
logs |
level (ERROR, WARN, INFO, DEBUG ; niveau minimal, par défaut WARN), contains (insensible à la casse, dans le message), module (partie du chemin de module, insensible à la casse, par ex. announce ou store), limit (par défaut 50, max 500) |
Les lignes correspondantes les plus récentes du tampon de journal en mémoire (les 2000 dernières lignes), les plus anciennes d'abord, avec matched et returned. Il couvre des minutes, pas des jours. |
| Outil | Arguments | Ce qu'il fait | Hashes max |
|---|---|---|---|
pause |
info_hashes |
Arrête les torrents (les données et la ligne restent). | 1000 |
resume |
info_hashes |
Démarre les torrents arrêtés. | 1000 |
reannounce |
info_hashes |
Annonce maintenant au lieu d'attendre le prochain intervalle. | 100 |
recheck |
info_hashes |
Vérifie le hash des données en arrière-plan. Un torrent arrêté reste arrêté. Protégé comme Vérifier à nouveau (Recheck) dans le menu contextuel : quand un autre torrent nomme les mêmes fichiers avec d'autres octets, le torrent est retenu pendant que ces fichiers sont vérifiés d'abord, et reste arrêté en erreur s'ils ne se vérifient pas (Ajouter des torrents & données existantes). | 50 |
set_category |
info_hashes, category ("" la retire) |
Change seulement l'étiquette. Les fichiers restent en place et le torrent reste dans son moteur. | 100 |
set_tags |
info_hashes, tags (tableau ; [] vide la liste) |
Remplace les tags par exactement cette liste. | 100 |
move_to_category |
info_hashes, category, allow_breaking_hardlinks (false par défaut) |
Définit la catégorie et déplace les données vers son chemin de sauvegarde, sous forme de job (voir jobs) ; quand le mode de la catégorie diffère, le job est une graduation vers l'autre moteur. Refusé (409 dans le résultat par torrent, avec sa reason) si un fichier a un hardlink ailleurs et que allow_breaking_hardlinks vaut false, si l'espace libre manque, si un autre torrent lit les mêmes fichiers, ou si la destination contient déjà d'autres fichiers. Voir Jobs, actions groupées & déplacement. |
100 |
add_torrent |
category (obligatoire), exactement un parmi torrent_path / torrent_url / magnet, plus save_path, tags (tableau), paused, engine
|
Ajoute un torrent. torrent_path est un fichier .torrent sur la machine Hydranos ; torrent_url est récupéré en http(s) (délai 30 s, 15 MiB max) par la propre sortie réseau du démon, comme ses autres requêtes (VPN & sortie réseau) : quand le kill switch ne lui laisse aucune sortie, l'ajout échoue avec la raison. Un magnet répond {"info_hash": …, "status": "resolving"} et apparaît dès que ses métadonnées arrivent. save_path remplace le chemin de la catégorie et doit être l'endroit où se trouvent ou se trouveront les données. N'utilise jamais le mode seed : les données existantes sont vérifiées par hash. Pas de sous-dossier : [daemon] create_torrent_folder n'est pas appliqué. Le placement suit Catégories & routage ; les refus du chemin d'ajout (disque race plein, fichiers qu'un autre torrent détient avec path_collision = "refuse", déjà ajouté) reviennent comme erreur de l'outil. |
1 |
Chaque liste info_hashes est vérifiée avant toute action : chaque entrée doit faire 40 caractères hexadécimaux, les doublons sont retirés, et une liste qui dépasse la limite est refusée en bloc. Les outils par torrent répondent {"ok": n, "failed": n, "results": [...]} avec un {info_hash, ok, status, answer} par torrent ; pause/resume répondent une entrée par moteur plus une liste not_found.
| Outil | Arguments | Ce qu'il fait | Hashes max |
|---|---|---|---|
delete_torrents |
info_hashes, delete_files (false par défaut), engine
|
Supprime les torrents. Sans engine, toutes les copies de cette instance disparaissent ; avec delete_files, leurs données sont aussi effacées du disque, ce qui est irréversible. |
100 |
purge_race |
info_hashes |
Supprime la copie du moteur nommé race, avec ses fichiers (les fichiers qu'une autre copie ou un autre torrent lit encore sont conservés) ; une copie dans un autre moteur reste. Un hash absent de race → statut 404 (torrent not in race) dans son résultat. Seulement le moteur nommé race : pour un moteur supplémentaire de rôle race, utilisez delete_torrents avec engine. |
100 |
delete_torrents et purge_race ne sont ni listés ni appelables tant que vous ne les activez pas :
[mcp]
allow_destructive = trueLa section [mcp] ne figure pas dans le default.toml livré, et l'onglet Config ne peut modifier que des clés qui existent déjà : ajoutez-la donc à la main dans le fichier de configuration (voir Config : démon, auth & global) et redémarrez Hydranos (voir Exploiter Hydranos pour un arrêt propre). Une fois la section présente, elle apparaît sous Other dans Paramètres avancés (default.toml) (Advanced settings (default.toml)) ; l'interrupteur est relu à chaque requête, donc une modification enregistrée là s'applique immédiatement à /mcp.
Interrupteur désactivé, un client qui appelle malgré tout l'un de ces outils (par exemple depuis une liste d'outils en cache) reçoit un résultat isError : `delete_torrents` is disabled on this node; set `allow_destructive = true` under `[mcp]` in the configuration to enable it. Les deux outils portent destructiveHint: true, donc un client bien élevé vous demande confirmation avant chaque appel.
-
Des pages, pas toute la bibliothèque.
find_torrentsrenvoie au plus 200 lignes par appel ; suiveznext_offset. Les autres plafonds figurent dans les tableaux d'outils ci-dessus. -
La catégorie doit exister pour
add_torrent,set_category(sauf si vide) etmove_to_category. Un nom inconnu est refusé avec la liste des noms valides. C'est plus strict que l'API REST et le shim qBittorrent : leurs routes d'étiquetage enregistrent n'importe quel nom, et leurs routes d'ajout acceptent un nom inconnu tant qu'un chemin de sauvegarde est envoyé (le torrent atterrit alors dansrace). Créez d'abord les catégories dans l'onglet Catégories (Categories). -
Jamais de mode seed, jamais de sous-dossier.
add_torrentvérifie toujours par hash les données déjà sur disque, pour un magnet aussi, et n'applique pascreate_subfolder/[daemon] create_torrent_folder. Pour ajouter par-dessus des données complètes sans vérification, ou avec un dossier à lui, utilisez l'API REST ou le shim (voir Ajouter des torrents & données existantes). -
enginen'est pas vérifié pour un magnet. Avectorrent_pathoutorrent_url, unengineinconnu est refusé avec la liste des moteurs ; avecmagnet, unengineinconnu est ignoré et la catégorie décide où va le torrent. - Ce que MCP ne couvre pas en 4.4. Il n'existe aucun outil pour les flux et règles RSS, l'ordre de téléchargement (priorités de fichiers, séquentiel), les limites de débit ou de partage, la file, les réglages réseau ni les nœuds. Utilisez pour cela l'interface web ou l[[API REST|REST-API-FR]] (RSS et téléchargements automatiques, File d'attente, limites de partage et de débit).
-
Les lectures montrent la même liste fusionnée que l'interface web ; les écritures n'agissent que sur cette instance.
overview,find_torrentsettracker_errorsincluent les lignes des autres moteurs locaux de même rôle et, si vous avez déclaré des nœuds, leurs lignes aussi (le champagent, un nom historique, contient l'étiquette de lignelocal-<engine>ou<node>-<engine>).torrent_detailet toutes les écritures ne regardent que les moteurs de cette instance : pour un hash détenu uniquement par un nœud,torrent_detailrépondno torrent <hash> on this node,recheck,set_category,set_tagsetmove_to_categoryrépondentno such torrent on this node(statut 404),reannounceetdelete_torrentsrapportent le 404 de la route elle-même, etpause/resumele listent dansnot_found. Pour agir sur les torrents d'un nœud, faites pointer un client vers le/mcpde ce nœud. Voir Nœuds & plusieurs machines. -
Copies dans plusieurs moteurs locaux.
reannounce,recheck,set_categoryetmove_to_categoryagissent sur la copie du premier moteur qui la détient.set_tagschange les tags de chaque copie.pause/resumeécrivent l'indicateur d'arrêt sur la ligne de chaque copie mais n'arrêtent ou ne démarrent que la copie du premier moteur ; les autres copies continuent de tourner (ou restent arrêtées) jusqu'au prochain redémarrage, puis suivent l'indicateur.delete_torrentssansengineatteint toutes les copies. -
Pas de contrôle de l'
Origin. L'endpoint ne repose que sur la clé API. Gardez le port 8199 sur votre LAN ou derrière un VPN ou un reverse proxy, comme pour le reste de l'API.
Il existe deux sortes d'échecs, et les clients les affichent différemment.
Les erreurs d'outil sont des résultats normaux marqués isError: true avec un message texte que le modèle peut lire et exploiter, par exemple unknown category `tv`; this node has: movies, tv-hd, unknown engine `vpn2`; this node runs: race, hoard, info_hashes is empty, ou give one of torrent_path, torrent_url or magnet. Un appel réussi renvoie isError: false avec le JSON à la fois dans content[0].text et dans structuredContent.
Les erreurs de protocole sont des erreurs JSON-RPC :
| Situation | Statut HTTP | Code JSON-RPC |
|---|---|---|
| Le corps n'est pas du JSON | 400 |
-32700 parse error |
| Lot (tableau JSON) | 400 |
-32600 batching is not supported |
| Méthode inconnue | 200 |
-32601 method not found |
| Nom d'outil inconnu | 200 |
-32602 unknown tool |
Notification (sans id) |
202, corps vide | aucun |
GET ou DELETE sur /mcp
|
405, Allow: POST
|
aucun |
| Clé API absente ou erronée | 401, WWW-Authenticate: Bearer
|
aucun ({"error": "Invalid or missing API key"}) |
- API REST : toutes les routes qu'utilisent les outils d'écriture, les bouchons et les routes supprimées en 4.4.
- Sécurité & accès : la clé API, son renouvellement, l'exposition du port 8199.
-
Config : démon, auth & global : la clé
[mcp]. - Ajouter des torrents & données existantes : les chemins d'ajout comparés, le mode seed.