Claude Code v2.1.118 adds direct MCP tool hooks
Claude Code v2.1.118 introduces a new `mcp_tool` hook type that calls MCP servers directly over RPC, eliminating the subprocess overhead of traditional shell-based hooks.
Score breakdown
Teams using Claude Code hooks for security scanning, linting, or CI checks can now route those hooks through stateful MCP servers — eliminating subprocess overhead, shell environment fragility, and cold-start re-parsing on every file write.
- 01Claude Code v2.1.118 adds a fifth hook handler type: `mcp_tool`, which calls MCP servers directly over RPC with no subprocess.
- 02The three fields specific to `mcp_tool` hooks are `server`, `tool`, and `input`; `server` must exactly match the MCP config name or the hook silently fails.
- 03Input values support `${field.path}` dot-notation into the full hook event JSON (e.g., `tool_input.file_path`, `session_id`, `cwd`, `duration_ms`).
The post by speedy_devv describes a new `mcp_tool` hook type introduced in Claude Code `v2.1.118` that allows hooks to call MCP tools directly over an existing RPC connection, rather than spawning a shell subprocess for each invocation. The configuration lives in `.claude/settings.json` and requires three fields specific to this hook type: `server` (which must exactly match the server name in MCP config — one character off causes a silent non-blocking failure), `tool`, and `input`. Input values support `${field.path}` dot-notation into the full hook event JSON, giving access to fields like `tool_input.file_path`, `tool_input.content`, `session_id`, `cwd`, and `duration_ms`. The `if` field uses permission-rule syntax (e.g., `"Edit(*.py|*.ts|*.js)"`) to conditionally fire hooks only on matching file extensions, which the post notes is a meaningful performance difference on docs-heavy projects.
Second, no shell environment dependency: `command` hooks can fail silently when `PATH` is misconfigured or when `~/.zshrc` emits unexpected stdout; `mcp_tool` hooks bypass all of that.
Two concrete advantages over `command` hooks are highlighted. First, stateful servers: an MCP server is a live process that retains loaded configs, caches, and session context between calls — a linting server that pre-parsed `tsconfig.json` on startup does not re-parse it on every file write, whereas a command hook does. Second, no shell environment dependency: `command` hooks can fail silently when `PATH` is misconfigured or when `~/.zshrc` emits unexpected stdout; `mcp_tool` hooks bypass all of that.
The post also covers output handling: an MCP tool's text content is treated identically to a `command` hook's stdout — valid JSON is parsed for decision fields like `decision: "block"` or `permissionDecision: "deny"`, while non-JSON text becomes context for Claude. A field exclusive to `mcp_tool` hooks on `PostToolUse` is `updatedMCPToolOutput`, which lets a running MCP server replace what Claude sees as another tool's output before it enters the conversation. For `Stop` hooks, the post warns that the event JSON includes a `stop_hook_active` field set to `"true"` when Claude is already continuing from a previous Stop hook — MCP tools must check this field and return empty output to avoid infinite loops.
Key facts
- 01Claude Code v2.1.118 adds a fifth hook handler type: `mcp_tool`, which calls MCP servers directly over RPC with no subprocess.
- 02The three fields specific to `mcp_tool` hooks are `server`, `tool`, and `input`; `server` must exactly match the MCP config name or the hook silently fails.
- 03Input values support `${field.path}` dot-notation into the full hook event JSON (e.g., `tool_input.file_path`, `session_id`, `cwd`, `duration_ms`).
- 04The `if` field uses permission-rule syntax (e.g., `Edit(*.ts|*.tsx)`) to conditionally fire hooks only on matching file extensions.
- 05`SessionStart` and `Setup` hooks may return a 'server not connected' error on first run since MCP servers may still be initializing.
- 06A field exclusive to `mcp_tool` PostToolUse hooks, `updatedMCPToolOutput`, lets an MCP server replace what Claude sees as another tool's output before it enters the conversation.
- 07Stop hooks must check the `stop_hook_active` field in event JSON; ignoring it when it is `"true"` creates an infinite loop.
Topics
Summary and scoring are generated automatically from the original article. We always link back to the publisher and never republish images or paywalled content. Last processed Apr 24, 2026 · 17:11 UTC. How this works →