Guides

Host hooks by CLI#

Hooks are host lifecycle callbacks, not MCP tool calls. The hard part is that each CLI exposes hooks differently. agent-connector groups those differences into paradigms so a connector author can write one normalized handler and still get honest per-host behavior.

The hook mental model#

A hook runs because the host emitted an event: session started, a tool is about to run, a permission decision is needed, a tool failed, context was compacted, or a subagent changed state. The model does not choose a hook the way it chooses an MCP tool.

hook-paradigms.txt
text
Connector declares a normalized hook handler
        |
        v
Target host adapter decides the hook paradigm
        |
        +-- json-stdio: host config calls the home-bin hook command
        |
        +-- ts-plugin: generated plugin module imports/dispatches handlers
        |
        +-- mcp-only: no host hook layer, so hooks skip with a warning
        |
        v
Handler returns context / allow / block / warn where supported

Official host surfaces to know first#

Start by separating three facts: MCP tools are model-selected, hooks are host lifecycle callbacks, and each host chooses its own hook transport. The links below are the current public references used by this guide.

Host familyNative hook surfaceHow agent-connector connects it
Claude CodeSettings JSON registers hook events such as PreToolUse with a matcher and command hook entries.The adapter writes the command entry and dispatches stdin JSON into your normalized defineHook handler.
Gemini CLIGemini uses its own event vocabulary: BeforeTool, AfterTool, PreCompress, BeforeAgent, and related lifecycle events.agent-connector maps normalized events such as PreToolUse and PostToolUse to the Gemini event names before writing hooks.
Codex CLICodex exposes hook configuration separately from MCP server registration, with command hooks for supported lifecycle events.The Codex adapter renders a hooks.json entry that points at the same home-bin hook dispatcher.
OpenCode familyOpenCode loads a plugin module with event functions such as tool.execute.before and permission.ask.The adapter generates a plugin file that calls the connector runtime; connector authors still write normalized hooks.
MCP-only hostsSome hosts document MCP server registration but do not expose a hook layer to connector packages.The installer keeps MCP working and reports hooks as unsupported instead of pretending the policy will run.

Cross-validation before a hook claim#

The full host lists below are generated from adapter metadata, not typed by hand. A host is presented as hook-capable only when the registry reports a non-MCP-only paradigm and the docs drift tests keep the list in lock-step with loaded adapters. The examples table adds external evidence for representative host families.

Host or familyLocal proofExternal proof
Claude Codejson-stdio adapter, settings.json hooks, claude-code testsOfficial Claude Code hooks docs
Gemini CLIjson-stdio adapter, BeforeTool/AfterTool mapping, gemini-cli testsOfficial Gemini CLI hooks reference
Codex CLIhooks.json adapter, codex tests, command hook dispatcherOfficial Codex hooks docs
OpenCodets-plugin adapter, generated plugin module, opencode testsOfficial OpenCode plugin docs
MCP-only hostsmcp-only paradigm list is registry-derived and drift-guardedHost MCP docs where available; hooks intentionally unsupported

CLI behavior by hook paradigm#

ParadigmHow it worksHosts in this repo
json-stdioThe host calls a configured command. agent-connector receives a JSON payload on the home-bin hook entrypoint, normalizes it, runs your handler, and prints the host's expected response shape.Claude Code, CodeBuddy, Codex CLI, Cursor, VS Code Copilot, JetBrains Copilot, GitHub Copilot CLI, Gemini CLI, Qwen CLI, Kiro, Kimi CLI, Crush, Goose, Hermes, Droid (Factory), OpenHands, Antigravity (IDE), Antigravity CLI (agy), Continue, Amazon Q Developer CLI, Grok Build, Grok CLI, Devin CLI (Cognition), Open Interpreter
ts-pluginThe package emits a host plugin/module. That module exports the lifecycle functions the host expects and dispatches into the connector runtime.OpenCode, MiMoCode, Kilo CLI, Kilo Code, OMP, NVIDIA NemoClaw, OpenClaw, Amp
mcp-onlyThe host can register MCP servers but exposes no hook layer to agent-connector. Declared hooks are skipped with a warning instead of pretending to run.Warp, Cline, Trae, Zed, Codebuff, Mux, Pi, Windsurf, Junie, Mistral Vibe

Write the connector hook once#

The connector author writes against agent-connector's normalized event type. This example blocks one dangerous shell pattern, injects context for this connector's MCP tools, and leaves unsupported host behavior to the adapter.

agent-connector.config.ts
ts
import {
  defineConnector,
  defineHook,
} from "@ken-jo/agent-connector/sdk";

const guardRiskyWrites = defineHook("PreToolUse", {
  matcher: "Bash|Write|Edit|apply_patch|mcp__acme_db__.*",
  handler(evt) {
    const input = JSON.stringify(evt.toolInput ?? {});

    if (evt.toolName === "Bash" && /\brm\s+-rf\b/.test(input)) {
      return {
        decision: "deny",
        reason: "rm -rf is blocked by acme-db policy.",
      };
    }

    if (evt.toolName.startsWith("mcp__acme_db__")) {
      return {
        decision: "context",
        additionalContext: "acme-db MCP tools are read-only in this connector.",
      };
    }
  },
});

export default defineConnector({
  server: { transport: "stdio", command: "node", args: ["./my-mcp-server.mjs"] },
  hooks: { PreToolUse: guardRiskyWrites },
});
A hook is not a replacement for server-side validation. If the MCP tool can mutate data, the tool handler must validate the operation even when a host hook is installed.

What host config can look like#

These are representative rendered shapes, not extra files you maintain by hand. The stable part is the home-bin command: agent-connector hook <host> <event> --connector <id>.

settings.json
json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "agent-connector hook claude-code PreToolUse --connector acme-db"
          }
        ]
      }
    ]
  }
}

What happens during dispatch#

  • The host emits a native lifecycle payload in its own format.
  • The adapter converts that payload into one normalized event shape where the host has a matching concept.
  • The connector hook handler receives the event and returns context, allow/block, warning text, or another supported response.
  • The adapter translates that response back to the native host contract.
  • If the host cannot support that event or response, the installer and doctor surface the limitation as a warning or unavailable capability.

How to customize behavior safely#

  • Use matcher to narrow the hook to specific tool names or MCP tool prefixes before adding logic.
  • Use hosts overrides only when one host has different semantics; the top-level handler stays the mandatory fallback.
  • Use platforms.<id>.nativeHooks for host-specific events that have no normalized event yet, then keep the payload parsing local to that host.
  • Disable a surface per host with platforms.<id>.hooks = false when the host behavior is not mature enough for your package.

Beginner safety rules#

Hooks are additive policy, not your only guardrail

If a tool can delete files, run SQL, or mutate production state, the MCP tool handler itself must validate the operation. Hooks can add host-side policy and UX, but an MCP-only host may never run them.
  • Keep hook handlers fast. A hook sits on the host's interaction path.
  • Return small context. Large hook output becomes model context or host UI noise.
  • Treat hook payloads as host-specific observations, not guaranteed universal state.
  • Test one host from each paradigm before claiming cross-host behavior.