5.0 KiB
MCP NAS — run_command v2 (P0) : heredocs, commandes longues, reset_session
Date : 2026-07-04 | Commit 30560984fa sur bolbol/linux-mcp-nas@main | Testé et validé le jour même.
Ce runbook remplace les RÈGLES 1–3 de MCP-NAS-SCRIPTING.md. Le chunking base64, la limite ~350 chars et l'interdiction de heredoc n'existent plus. La section « Services internes (172.17.0.1) » de l'ancien runbook reste valide.
Ce qui a changé
Avant, run_command envoyait la commande brute à tmux via send-keys : le parsing shell
cassait sur les quotes, les heredocs déclenchaient un timeout tueur de session, et au-delà
de ~350 caractères la commande était tronquée — d'où le workaround base64 en 2 appels.
Depuis le patch P0, le serveur écrit la commande dans un fichier script depuis Python
(open(f"/tmp/mcp_{uid}.sh","w").write(command) — zéro parsing shell) et n'envoie au pane que :
. /tmp/mcp_XXX.sh > /tmp/mcp_XXX.out 2>&1; echo $? > /tmp/mcp_XXX.rc
via send-keys -l (mode littéral). Conséquences :
| Avant (v1) | Maintenant (v2) |
|---|---|
| Heredoc → timeout 120 s, session morte | Heredocs natifs, y compris <<'EOF' avec quotes/$VAR/backticks |
| Limite ~350 chars, base64 obligatoire | Testé 5 250 chars en un appel, intégrité parfaite |
Quote simple ' → troncature silencieuse |
Quotes arbitraires OK (aucun parsing shell) |
Prompt de continuation > empoisonnait la session |
Mécaniquement impossible |
| Empilement sur pane occupé (vim/ssh/pager) | Busy-check : refus propre avec message d'options |
| Sessions poisonnées → new_session en chaîne | reset_session : reprise sans destruction |
Nouveaux comportements
- Busy-check : si le pane exécute déjà quelque chose,
run_commandrenvoie[SESSION OCCUPÉE] 'x' exécute actuellement 'sleep'…au lieu d'empiler. Options proposées :capture_pane,send_keys,reset_session, ou une autre session. - Timeout intelligent : à expiration, le serveur tente un Ctrl-C puis sonde le shell. S'il répond → session préservée (cwd/env conservés). Sinon → session recréée dans le même cwd.
reset_session(session): envoie Ctrl-C,q(pager), Ctrl-U, vérifie que le shell répond. Reprend la main sur une session bloquée SANS la détruire. Pour vim :send_keys ':q!'d'abord.- Cleanup au démarrage : sessions tmux orphelines et fichiers
/tmp/mcp_*purgés au boot du container (les 24 sessions zombies accumulées ont disparu au premier rebuild).
Tests de validation (2026-07-04)
- Heredoc avec quotes simples/doubles,
$VAR, backticks,$(subshell),!#&*(){}[]|;<>→ contenu intact, zéro expansion, exit code correct. - Commande de 5 250 caractères (heredoc 50 lignes × 100 chars + vérifications) → 5 050 octets écrits, 50/50 lignes conformes au motif.
- Busy-check + reset_session :
sleep 300lancé via send_keys →run_commandrefusé proprement →reset_session→ « shell réactif, cwd/env conservés (cwd: /tmp) ».
Bonnes pratiques v2
- Écrire les scripts Python directement en heredoc :
cat <<'EOF' > /tmp/s.py … EOFpuispython3 /tmp/s.py. - Ne plus jamais utiliser le workaround base64 (2 appels) ni le découpage printf-append.
- Session par défaut
claudefiable ;reset_sessionavant de créer une session neuve. - Le rebuild du container se fait depuis le Mac (
ssh nas), jamais depuis mcp-nas lui-même :ssh -p 22222 Best0f@192.168.100.33 cd /volume1/docker/linux-mcp-nas sudo docker compose build && sudo docker compose up -d --force-recreate curl -s http://127.0.0.1:3042/health
P1 — denylist ancrée + nas.internal (2026-07-04, commit 7fb13c5, déployé et testé)
- Denylist ancrée : les patterns ne sont plus cherchés n'importe où dans la chaîne brute.
La commande est découpée en segments (
;&|retours ligne), les préfixes neutres (sudo,env,VAR=…, chemin/sbin/…) sont retirés, et les patterns sont ancrés sur le début de chaque commande. Les corps de heredoc sont exclus de l'analyse.- Fini les faux positifs :
grep reboot /var/log,echo "ne pas faire reboot", un heredoc de doc contenant « mkfs » → acceptés. - Toujours refusés :
reboot,/sbin/reboot,echo ok && reboot,VAR=1 sudo shutdown,rm -rf /volume*|/opt|/mnt|/,mkfs.*,dd of=/dev/*, fork bomb,> /dev/sd*,docker rm -f,fdisk,parted,syno*,chmod -R 777 /. (30/30 tests unitaires.) - Changement assumé :
rm -rf /tmp/xest désormais autorisé (avant, tout chemin absolu était bloqué) ; les racines critiques restent protégées.
- Fini les faux positifs :
nas.internal:extra_hosts: nas.internal:host-gatewaydans le compose → depuis toute session,nas.internal= l'hôte NAS (172.17.0.1). Utiliserhttp://nas.internal:3232(Gitea),:9000(Portainer),:8787(NyoraNotes)… au lieu de mémoriser 172.17.0.1.