6.8 KiB
MCP NAS — Référence de scripting fiable
Date : 2026-06-23 | Session debug Veille/Bifrost (40+ tool calls observés)
⚠️ OBSOLÈTE EN PARTIE depuis le patch P0 du 2026-07-04 — voir mcp-nas-run-command-v2.md. Les RÈGLES 1, 2 et 3 ci-dessous (poisoning, heredoc interdit, limite 350 chars/base64) ne s'appliquent plus : heredocs, quotes et commandes longues fonctionnent nativement, et
reset_sessionrécupère une session bloquée. La section « RÈGLE 4 — Services internes (172.17.0.1) » reste valide.
Environnement du container
| Propriété | Valeur |
|---|---|
| Shell | /bin/bash |
| User | root uid=0 |
| Python | 3.11.15 — /usr/local/bin/python3 |
| Modules Python | requests, sqlite3, json, urllib, subprocess |
Filesystem /mnt |
NAS Synology 11 To, R/W |
| Réseau host Docker | 172.17.0.1 |
| Réseau LAN NAS | 192.168.100.33 — INACCESSIBLE depuis container |
Binaires (vérifié 2026-06-23)
| Outil | Dispo | Note |
|---|---|---|
curl |
oui | /usr/bin/curl — soumis limite 350 chars |
base64 |
oui | /usr/bin/base64 |
python3 |
oui | 3.11 complet |
git |
oui | via subprocess Python |
sqlite3 CLI |
non | utiliser module Python import sqlite3 |
docker CLI |
non | pas de socket monté — Portainer API |
paramiko |
non | pas de SSH natif Python |
RÈGLE 1 — Sessions : poisoning
Après 3+ erreurs consécutives, la session refuse même echo hello.
Symptôme : {"error": "Error occurred during tool execution"} sur commande triviale.
Sessions mortes dans cette session : claude, db_update, verify.
Sessions stables : model_test, emergency, s1 (utilisées fraîchement).
Toujours spécifier session= dans run_command (sans = erreur systématique).
Après 2 erreurs d'affilée : changer de session immédiatement.
Si toutes poisonnées : new_session("fresh1").
RÈGLE 2 — Heredoc interdit
<< 'EOF' dans toute forme cause un timeout 120s qui tue la session.
Cela inclut cat > f << 'EOF', python3 << 'PYEOF', bash << 'EOF'.
Cause : l'injection tmux ne gère pas l'attente stdin que heredoc déclenche.
RÈGLE 3 — Limite 350 chars par commande
Au-delà de ~350 chars dans le champ command : timeout, troncature ou erreur silencieuse.
Méthode 1 — Code court (< 200 chars)
Deux appels séparés :
printf 'import json\nprint("ok")\n' > /tmp/s.py
python3 /tmp/s.py
Méthode 2 — Code moyen (200–350 chars)
Appender en plusieurs appels :
printf 'import sqlite3\nconn=sqlite3.connect("/mnt/docker/bifrost/data/config.db")\n' > /tmp/s.py
printf 'cur=conn.cursor()\ncur.execute("SELECT * FROM config_keys")\n' >> /tmp/s.py
printf 'for r in cur.fetchall(): print(r)\n' >> /tmp/s.py
python3 /tmp/s.py
Méthode 3 — Code long ou avec quotes simples (> 350 chars)
Base64 encode côté Claude (bash_tool), decode sur NAS (2 run_command) :
# bash_tool : base64 -w0 fichier.py → obtenir la chaîne B64
# run_command 1 : printf '<B64>' | base64 -d > /tmp/s.py
# run_command 2 : python3 /tmp/s.py
Piège : une quote simple ' dans printf termine la chaîne → code tronqué sans erreur.
Dès qu'il y a des ' dans le code Python → base64 obligatoire.
RÈGLE 4 — Services internes (172.17.0.1)
Portainer — restart container
import urllib.request, json
# Auth
body = json.dumps({"username":"bestof","password":"<depuis .env>"}).encode()
req = urllib.request.Request("http://172.17.0.1:9000/api/auth", data=body,
headers={"Content-Type":"application/json"})
jwt = json.loads(urllib.request.urlopen(req).read())["jwt"]
h = {"Authorization": "Bearer " + jwt}
# ID dynamique — change après recreate
req = urllib.request.Request(
"http://172.17.0.1:9000/api/endpoints/2/docker/containers/json", headers=h)
cs = json.loads(urllib.request.urlopen(req).read())
cid = [c["Id"][:12] for c in cs if "bifrost" in str(c.get("Names","")).lower()
and "proxy" not in str(c.get("Names","")).lower()][0]
# Stop + Start
for action in ["stop","start"]:
req = urllib.request.Request(
f"http://172.17.0.1:9000/api/endpoints/2/docker/containers/{cid}/{action}",
data=b"", headers=h)
urllib.request.urlopen(req)
n8n API — PUT workflow
# Uniquement ces 4 champs acceptés — autres -> 400
payload = {"name": wf["name"], "nodes": wf["nodes"],
"connections": wf["connections"], "settings": wf.get("settings", {})}
# Header : X-N8N-API-KEY (pas Authorization Bearer)
Bifrost
Auth : header x-bf-vk: <VK> — PAS Authorization Bearer (-> 401)
URL : http://172.17.0.1:3085
Gitea push
import subprocess, os
env = open("/mnt/docker/hermes-platform/.env").read()
pwd = [l.split("=",1)[1].strip() for l in env.splitlines() if "PASSWORD_GITEA" in l][0]
os.chdir("/mnt/docker/nas-runbooks")
subprocess.run(["git","add","."])
subprocess.run(["git","commit","-m","message"])
subprocess.run(["git","push", f"http://bolbol:{pwd}@172.17.0.1:3232/bolbol/nas-runbooks.git"])
NyoraNotes
tok = open("/mnt/docker/nyora-notes/data/hermes-perso.token").read().strip()
note = {"title":"...","content":"...","tags":["infra","runbook","terminee"]}
body = json.dumps(note).encode()
req = urllib.request.Request("http://172.17.0.1:8787/notes", data=body,
headers={"Authorization":"Bearer "+tok,"Content-Type":"application/json"})
urllib.request.urlopen(req) # -> 201
SQLite Bifrost DB
import sqlite3, shutil
# Lecture : copier d'abord (WAL actif)
shutil.copy("/mnt/docker/bifrost/data/config.db", "/tmp/bifrost.db")
conn = sqlite3.connect("/tmp/bifrost.db")
# Écriture live (restart Bifrost après pour prise en compte)
conn = sqlite3.connect("/mnt/docker/bifrost/data/config.db")
Tableau de décision rapide
| Besoin | Faire |
|---|---|
| Code Python < 200 chars | printf > /tmp + python3 (2 appels) |
| Code Python > 350 chars ou avec quotes | base64 encode -> decode -> exec |
| Heredoc | Jamais. Python open().write() en /tmp |
| Restart container | Portainer API, ID dynamique |
| sqlite3 | Python import sqlite3 |
| Session instable | list_sessions -> session fraîche |
| Gitea push | subprocess git avec http://bolbol:pwd@ |
Améliorations suggérées linux-mcp-nas
- Monter docker.sock :
-v /var/run/docker.sock:/var/run/docker.sock— Docker CLI direct, supprime Portainer API workaround - Installer sqlite3 CLI :
apt install -y sqlite3dans l'image — inspection DB sans Python - Augmenter timeout run_command : 120s trop court pour git sur gros repos
- Nommer sessions par rôle :
git,db,api,test— rotation propre entre tâches - Installer paramiko :
pip install paramiko— SSH natif si besoin accès NAS direct