# 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](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_command` renvoie `[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) 1. **Heredoc** avec quotes simples/doubles, `$VAR`, backticks, `$(subshell)`, `!#&*(){}[]|;<>` → contenu intact, zéro expansion, exit code correct. 2. **Commande de 5 250 caractères** (heredoc 50 lignes × 100 chars + vérifications) → 5 050 octets écrits, 50/50 lignes conformes au motif. 3. **Busy-check + reset_session** : `sleep 300` lancé via send_keys → `run_command` refusé 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 … EOF` puis `python3 /tmp/s.py`. - Ne plus jamais utiliser le workaround base64 (2 appels) ni le découpage printf-append. - Session par défaut `claude` fiable ; `reset_session` avant 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 ``` ## À venir - **P1** : denylist ancrée (regex ancrées sur le début de commande) + résolution `nas.internal`.