钩子要调 MCP,用 type mcp_tool,不要再包一层 shell
server / tool / input 直接打到已连接的 MCP。服务器没起来不会拦操作。prompt 和 agent 类型会解析但跳过。
生命周期钩子可以直接调 MCP,不必再写 npx … 包装。JSON 示例:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "scanner",
"tool": "scan_patch",
"input": { "patch": "${tool_input.command}" },
"timeout": 30,
"statusMessage": "Scanning edited files"
}
]
}
]
}
}
server 必须是已经连上的 MCP 名。钩子不会替你启动或重连。input 里用 ${field.nested} 从事件取值:整段占位保留 JSON 类型,嵌在字符串里就变成文本。
官方约束:
- 只跑
command和mcp_tool。prompt/agent写了也不会执行 mcp_tool始终同步,不走工具批准,也不会再触发别的钩子- 超时取钩子
timeout和服务器tool_timeout_sec里较短的那个 SessionEnd不支持mcp_tool;SessionStart可能在 MCP 就绪前就跑,那时不会拦会话- 工具返回 block 才会拦住操作。缺服务器、连不上、工具不存在、执行报错都不拦
不要把 mcp_tool 的 Stop 当成合规完成门。服务器没配或起不来时,codex exec --json 仍可能直接 turn.completed。讨论中的 failureMode 还没落地,不要抄进配置。真正的门继续用能 exit 2 / 返回 deny 的 command 钩子,并自己确认 MCP 已在 /mcp 里。
先 codex mcp add 再信任钩子。新开会话后 /hooks 应能看到 server 和 tool 字段。