Request: allow hooks in MCP servers

Resolved 💬 5 comments Opened Sep 2, 2025 by ljw1004 Closed Jan 7, 2026

Currently hooks go in ~/.claude/settings.json

I think hooks should also be allowed within MCP servers. They should be invoked using the existing MCP resource template feature:

const hook_results = await client.readResource({
  uri: `hook://UserPromptSubmit/${encodeURIComponent(JSON.stringify(hook_input))}`
});

Claude Code hooks already take in JSON input. And each hook already gives JSON output. So the MCP readResource operation could happily return a list of zero or more TextContent blocks, each one containing the result of invoking the hook.

Why?

It's not enough just to write an MCP tool. Whenever we write a tool, we need it to go hand-in-hand with <system-reminders>. Whenever someone writes an MCP tool, I think it's best-practice for them to evaluate whether a UserPromptSubmitHook is needed too. And if so, then it should logically be deployed within the same MCP server. Here are examples:

  • the built-in TodoWrite tool attaches system-reminders to UserPromptSubmitHook that the tool should be used if it hasn't recently
  • the built-in ExitPlanMode tool attaches system-reminders to every UserPromptSubmitHook that we're in plan mode
  • the built-in Read tool attaches system-reminders to UserPromptSubmitHook if a file has been changed on disk since the last read

The current Claude Code VSCode extension uses a complicated protocol with push notifications for what text is selected, or what file is opened, or what diagnostics are current. But really it should be doing it with a UserPromptSubmitHook to express those things as normal <system-reminders>. If MCP servers were allowed to do hooks, then all those things could be done within the VSCode extension MCP server, and we could delete the push channel entirely!

Hooks often want state. Currently there isn't any very good place to store state for our hooks -- we need to persist them to disk in some arbitrary location. However, MCP servers are an ideal place to store state! They would become the natural way for people to write stateful hooks. For instance:

  • the built-in TodoWrite tool keeps state about how many UserPromptSubmitHooks have passed since the last TodoWrite tool invocation,
  • the built-in ExitPlanMode tool keeps state about whether we're in plan mode
  • the built-in Read tool keeps state about which files have been modified on disk since being read, and what was the most recent read content of them.

Proof of concept

I built a proof of concept to show that this all works: https://github.com/ljw1004/mini_agent/blob/main/mini_agent.py

  • It's an AI coding assistant with similar behavior to Claude Code
  • Except, it uses MCP resource templates to invoke hooks
  • I was able to put every single <system-reminder>, tool, sub-agent, system-prompt, all inside an MCP server: the main agentic loop became entirely agnostic, didn't contain a single system-reminder itself; all the system-reminders were delivered from the MCP server, for TodoWrite and ExitPlanMode and Read.

Scenarios

My team's project is HUGE and encompasses many different frameworks, libraries, paradigms. I have a PostToolUse hook which recognizes when there have been Read commands on a particular subsystem's directory. The hook will (not more than once every ten prompts while working in that directory) append a UserPromptSubmitHook reminding Claude about best-practices about how to use that subsystem's API or idioms. This hook would be good within MCP because

  • The team supporting that subsystem have already written several MCP tools specific to it
  • It would be practical for them to deploy their hooks as part of their existing MCP server
  • Their hooks need state, which would be best stored within their MCP server

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗