Blog
Elf Bash-Wrapper, drei tote Scripts, ein neuer Hook-Type
Claude Code 2.1.118 ruft MCP-Tools jetzt direkt aus Lifecycle-Events auf, ohne Shell dazwischen. Ich hab meine Wrapper-Hooks migriert. Was wirklich besser wird, und was nicht konvertiert.
05. Juni 2026
Ich hatte elf Hook-Ideen und unter jeder lag ein Bash-Script. Memory-Summary beim Stop, CRM-Log nach jedem E-Mail-Draft, GEO-Audit nach Content-Edit. Jedes davon ein Wrapper der ein MCP-Tool aufrief, die Response mit jq parste und hoffte, dass die Shell auf dem Zielrechner genauso tickt wie bei mir. Drei dieser Scripts sind mir auf einem frisch aufgesetzten Rechner kommentarlos um die Ohren geflogen, weil dort kein jq installiert war. Kein Fehler im Chat, kein roter Text. Der Hook feuerte, das Script brach ab, und ich hab es zwei Tage nicht gemerkt.
Genau diese Klasse von Problemen räumt Claude Code 2.1.118 weg. Seit dem 23. April 2026 gibt es einen Hook-Type der das MCP-Tool direkt aufruft, ohne Shell, ohne jq, ohne PATH-Lotterie: `type: "mcp_tool"`. Ich hab am Wochenende meine Wrapper migriert und dabei gelernt, was der Type wirklich ändert und wo er nichts hilft.
## Der Umweg fällt weg
Bis 2.1.118 ging jeder Hook der ein MCP-Tool brauchte über einen command-Hook. Du startest eine Shell, die Shell ruft ein Script, das Script spricht den MCP-Server an, parst die Antwort und gibt was zurück. Vier bewegliche Teile, und jedes kann auf einem fremden Rechner anders aussehen. Welche Shell. Ob jq da ist. Wie der PATH gesetzt ist. Ob das Script-File ausführbar ist.
Beim mcp_tool-Hook schreibst Du den Tool-Call direkt in die `settings.json`:
```json
{
"type": "mcp_tool",
"server": "studiomeyer-memory",
"tool": "nex_search",
"input": { "query": "${user_prompt}" },
"timeout": 10
}
```
`server` zeigt auf deinen konfigurierten MCP-Server, `tool` ist der Tool-Name, `input` das Argument-Objekt. Claude Code feuert den Call selbst und parst die Antwort mit demselben internen Parser den ein command-Hook auch nutzt. Keine Shell mehr im Spiel. Das Script das auf meinem frischen Rechner gestorben ist, existiert in dieser Variante gar nicht erst.
Ein Detail zur Config-Lage, weil ich es selber verwechselt hab: Hooks liegen entweder in `~/.claude/settings.json` (gilt für alle Projekte) oder in `.claude/settings.json` im Projekt (gilt nur da, und gewinnt wenn beide existieren). Projekt schlägt global. Praktisch für geteilte Team-Setups, weil die Projekt-Hooks mit ins Repo wandern.
## Was sauber konvertiert ist, was nicht
Von meinen elf Hook-Ideen ließen sich genau sieben eins zu eins auf mcp_tool umstellen. Die sieben hatten eines gemeinsam: ein einziger Tool-Call mit Input das schon da ist. nex_search auf den User-Prompt. crm_log_interaction mit dem Dateipfad aus dem letzten Edit. geo_check nach einem Content-Edit. Alles Retrieval oder Logging, nichts wo das Modell unterwegs noch denken muss.
Die anderen vier blieben Bash. Und der Grund dafür war meine erste echte Lektion an dem Wochenende.
## Failure eins: ich wollte Generierung in einen statischen Hook stopfen
Ich hatte nex_summarize beim Stop-Event eingeplant. Claude ist fertig, schreib die Session-Summary weg. Klingt nach einem Ein-Zeilen-Hook. Ist es nicht. Ein mcp_tool-Hook übergibt statisches Input. Er kann Werte aus dem Lifecycle interpolieren, aber er kann das Modell nicht bitten "fass die letzten zwei Stunden zusammen". nex_summarize braucht aber genau diesen generierten Text. Der Hook hat ihn nicht. Er reicht weiter was schon existiert, und wenn nichts da ist, schreibt er leer.
Das fiese daran: die settings.json validiert nichts. Kein Schema-Check, keine Warnung, dass das Input-Feld leer bleibt. Der Hook feuerte brav, rief das Tool mit leerem Argument auf und legte eine Geister-Summary ab. Stiller Fehler, die schlimmste Sorte, weil alles grün aussieht. Für alles wo eine LLM-Generierung mitten drinsteckt bleibst Du beim command- oder prompt-Hook, der das Modell wieder einbindet.
## Failure zwei: der Matcher gilt nicht überall
Zweiter Stolperstein. Ich wollte einen Memory-Hook nur bei bestimmten Prompts feuern lassen, also mit einem Matcher filtern. Matcher gibt es aber nur für PreToolUse und PostToolUse. Alle anderen Events, auch UserPromptSubmit und SessionStart, ignorieren den Matcher komplett. Mein Hook feuerte bei jedem einzelnen Prompt, egal was ich tippte. Wenn Du wirklich nach Inhalt filtern willst, brauchst Du wieder einen command-Hook der die Logik selber macht. Das steht in der Referenz, aber ich hab es erst nach dem dritten Fehlversuch gelesen.
## Die Faustregel die ich mir aufgeschrieben hab
mcp_tool ist kein Ersatz für Bash-Wrapper. Es ist ein scharfes Werkzeug für einen engen Fall: ein deterministischer Tool-Call mit Input das schon existiert. Retrieval, Logging, Kontext laden. Genau da ist der Type Gold, weil das Modell nichts mehr vergessen kann und keine Shell mehr im Weg steht. Sobald Logik dazwischen muss, mehrere Calls, eine Generierung, ein Inhalts-Filter, bleibst Du bei command.
Und zwei Dinge die unabhängig vom Type gelten. Erstens, halt jeden Hook idempotent. Er feuert automatisch und manchmal doppelt, der gleiche Trigger darf keinen kumulativen Schaden anrichten. nex_search zweimal aufrufen ist egal, crm_create_company zweimal aufrufen macht ein Duplikat. Zweitens, ein Hook der bei jedem Prompt läuft sieht jeden Prompt. Wenn da sensibler Kram durchgeht, gehört das in deine DSGVO-Überlegung jetzt, nicht erst beim Audit ein halbes Jahr später.
Bei unserem Memory-Store mit 88 Sessions und 275 Learnings hat sich die Migration gelohnt, allein weil die drei toten Wrapper-Scripts jetzt Geschichte sind. Aber die vier Bash-Hooks die ich behalten hab, behalte ich aus gutem Grund.
Die Mechanik im Detail, alle fünf Config-Felder und wann der Type passt, liegt in unserer Lektion [mcp_tool Hooks](/levels/4/10-mcp-tool-hooks). Die Grundlagen zu Hooks und Skills davor stehen in [Hooks und Skills](/levels/4/04-hooks-und-skills). Wenn ein Hook gar nicht erst feuert, geh das Playbook [Hooks debuggen wenn nichts feuert](/playbooks/hooks-debuggen-wenn-nichts-feuert) durch. Die offizielle Referenz mit allen Events und Input-Variablen findest Du unter https://code.claude.com/docs/en/hooks.