---
title: "The mcp_tool hook, direct MCP tool calls from Claude Code lifecycle events | Recipe — StudioMeyer Academy"
description: "Claude Code v2.1.118 added a new hook type: type: 'mcp_tool'. Skip the bash wrapper, call MCP server tools directly from Stop, PreCompact, UserPromptSubmit. The five-rule sanity check before you wire one."
canonical: "https://studiomeyer.academy/en/recipes/16.1-mcp-tool-hook-intro"
language: en
markdown_versions: ["https://studiomeyer.academy/en/recipes/16.1-mcp-tool-hook-intro.md"]
publisher: "StudioMeyer Academy, https://studiomeyer.academy (llms.txt: https://studiomeyer.academy/llms.txt)"
---

[← Alle Recipes](https://studiomeyer.academy/recipes)

Phase 16 · Claude Code Hooks·6 steps

# The mcp\_tool hook, direct MCP tool calls from Claude Code lifecycle events

Claude Code v2.1.118 added a new hook type: type: 'mcp\_tool'. Skip the bash wrapper, call MCP server tools directly from Stop, PreCompact, UserPromptSubmit. The five-rule sanity check before you wire one.

6 steps0%

Du liest ohne Account. Mit [Login](https://studiomeyer.academy/login?next=/recipes/16.1-mcp-tool-hook-intro) speichern wir Step-Fortschritt + Notes.

# The mcp\_tool hook, direct MCP tool calls from Claude Code lifecycle events

Until Claude Code v2.1.118 (released 2026-04-23), every hook was a shell command. If you wanted a Stop hook to summarize the session into your memory MCP, you had to write a bash wrapper that called the tool indirectly. Now there is a `type: "mcp_tool"` hook type that calls a MCP server tool directly. No shell, no JSON munging, just a four-field config in `~/.claude/settings.json`.

This recipe is the architectural intro. The next four recipes build the actual bundles for the five StudioMeyer SaaS MCPs (Memory, CRM, GEO, Crew, Academy).

### Step 1: The new hook schema

Add this to `~/.claude/settings.json` under `hooks.Stop[0].hooks`:

```json
{
  "type": "mcp_tool",
  "server": "studiomeyer-memory",
  "tool": "nex_summarize",
  "input": { "session_id": "${session_id}" },
  "timeout": 60,
  "statusMessage": "Auto-summarize on stop..."
}
```

Five fields. `type: "mcp_tool"` switches on the new dispatcher. `server` is the MCP server name as it appears in `claude mcp list`. `tool` is the tool name without the `mcp__server__` prefix. `input` is the JSON the tool expects. `timeout` is in seconds (default 60). `statusMessage` shows in the Claude Code statusline while the hook runs.

### Step 2: Substitution variables you can use

Inside `input` you can interpolate runtime values:

- `${cwd}`, current working directory
- `${tool_input.field}`, any field from the tool input object (PostToolUse only)
- `${tool_name}`, the tool name being called (Pre/PostToolUse only)
- `${session_id}`, Claude Code session UUID
- `${duration_ms}`, execution time (PostToolUse / PostToolUseFailure only)

So `nex_search` triggered on `UserPromptSubmit` with `{ "query": "${user_prompt}" }` works out of the box.

### Step 3: The five-rule sanity check before you wire a hook

Hooks fire on lifecycle events without explicit user approval. That makes them powerful and dangerous. Before you add a `mcp_tool` hook, the tool you call MUST satisfy all five:

1. **Idempotent.** N calls with the same input produce the same output without cumulative side effects. `nex_summarize` is idempotent (same session, same summary). `crm_create_company` is NOT idempotent (creates a duplicate every time).
2. **Fast.** Default timeout is 60 seconds. Recommended ceiling: 30 seconds in synchronous hooks (Stop, PreCompact). If your tool sometimes hits a cold database connection and takes 90 seconds, the hook silently fails.
3. **Deterministic.** Same input, same output. No `Math.random()` without snapshotting. No "current time" in the response unless time is explicit input.
4. **Side-effect-free without explicit user trigger.** A Read-tool on `UserPromptSubmit` must NOT persist anything. Only persist when the user clearly asked for it.
5. **GDPR-aware.** The hook fires automatically on every matching event. If the tool sends data to a third party (e.g. Anthropic, OpenAI, a logging endpoint), the user's consent must be documented in the recipe README.

A tool that fails any of these five doesn't go in a hook recipe. Period.

### Step 4: Where hooks live and how they merge

There are three settings files Claude Code reads:

- `~/.claude/settings.json`, your personal user-scope config
- `.claude/settings.json`, project-scope, committed to git
- `.claude/settings.local.json`, project-scope, gitignored

Hooks **merge** across all three. If your user-level Stop hook calls `nex_summarize` and a project-level Stop hook calls `geo_check`, both fire. There is no override semantics. To disable plugin-supplied hooks you need `disableAllHooks: true`, which is a managed-only setting (i.e. requires IT admin).

### Step 5: Verify your first hook fires

After editing `~/.claude/settings.json`, run a fresh `claude` session and trigger the event:

```bash
claude
# Type something, then Ctrl-D to trigger Stop
```

Watch the statusline, `statusMessage` shows while the hook runs. If the hook fails (server not connected, tool errored, timeout), Claude Code logs a non-blocking error and continues. Hooks are best-effort, never critical-path.

To debug:

```bash
claude --debug
```

`--debug` prints hook execution details including the full request and response. Use this when a hook silently doesn't fire, usually it's a typo in `server` or `tool` name.

### Step 6: When to use a Bash hook instead

Not every workflow fits `mcp_tool`. Use a `command` (bash) hook when:

- You need to chain multiple tool calls with intermediate logic
- You need to call something that isn't an MCP tool (a CLI, a curl, a script)
- You need the result to actually return a JSON object that Claude Code uses (`mcp_tool` results are best-effort)

Use `mcp_tool` when:

- You want to call exactly one MCP tool with deterministic input
- The tool already exists and is idempotent + fast + GDPR-aware
- You don't need to inspect or modify the tool's response

## Done

You understand the new hook type, the five-rule check, and how to verify a hook fires. The next four recipes (16.2 Memory, 16.3 CRM, 16.4 GEO+Crew, 16.5 Academy) build the actual recipe bundles for our SaaS MCPs.

## Sources

- [Claude Code Hooks Reference](https://code.claude.com/docs/en/hooks) (official, includes `mcp_tool` schema since v2.1.118)
- [Claude Code Settings Reference](https://code.claude.com/docs/en/settings)

[← Migration patterns, moving cus](https://studiomeyer.academy/recipes/15.5-migration) [Memory hook bundle, auto-persi →](https://studiomeyer.academy/recipes/16.2-memory-hook-bundle)

Phase 16

mcp\_tool hooks, Memory/CRM/GEO/Crew/Academy hook bundles

[Alle Phasen →](https://studiomeyer.academy/recipes)

Concepts

[Hooks](https://studiomeyer.academy/concepts/hooks) [MCP basics](https://studiomeyer.academy/concepts/mcp_basics)

## Sitemap

Public pages of this site as Markdown: [sitemap](https://studiomeyer.academy/sitemap.md). Curated entry point: [llms.txt](https://studiomeyer.academy/llms.txt), full text: [llms-full.txt](https://studiomeyer.academy/llms-full.txt).
