Integrate your agent

Once you've seen Axtary decide actions in fake mode, this is how you put it in front of a real agent so its actual tool calls go through policy. Two ways to route a real agent through Axtary: a runtime hook (gates Claude Code's file tools, Cursor's MCP tool calls, or Codex's shell commands and edits, from inside the agent) and the MCP wrapper (gates any MCP client — Claude Code, Cursor, Codex). All use the same policy file and the same hash-chained ledger.

Not sure which? Jump to Which to use at the end — but the short answer is: the hook for in-agent tool calls (Claude Code, Cursor, or Codex), the MCP wrapper for any MCP client, and the dashboard approval queue when you need guaranteed human approval.

Option 1 — Claude Code PreToolUse hook

Gates Claude Code's Read/Glob/Grep/Write/Edit tools and file-touching Bash commands by normalizing each into an Axtary content action and asking the local proxy for a decision.

1. Start the proxy in your project (leave it running):

axtary proxy --config axtary.yml

2. Install the hook config in the project:

axtary hook install claude --owner user:you@example.com --repo your-org/your-repo

The command writes .claude/settings.json with the resolved local Axtary CLI path and preserves unrelated hooks. Re-running it updates the Axtary hook entry instead of adding duplicates. The generated hook shape is:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Glob|Grep|Write|Edit|NotebookEdit|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/resolved/node /resolved/axtary/packages/cli/dist/bin.js hook claude-code --proxy http://127.0.0.1:7331 --owner user:you@example.com --repo repo:your-org/your-repo",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

3. In a Claude Code session in that project, ask it to read .env.production with either Read or a shell command like cat .env.production — it's denied with the blocked prefixes named, and Claude self-corrects. Every decision is a ledger record (.axtary/actions.jsonl).

What each decision does — and where it's enforced

Axtary decisionClaude Code CLIClaude Code VS Code extensionNotes
allowtool runstool runs
denyblockedblockedworks everywhere — secrets/denied paths can't be read or written
step_upaskprompts for approval⚠️ not prompted yetthe tool currently proceeds in the VS Code extension

Step-up caveat (important, honest): Axtary returns step_up correctly and records it in the ledger, and the Claude Code CLI honors it (prompts you). But the Claude Code VS Code extension currently ignores a PreToolUse ask decision and falls back to its own permission rules (anthropics/claude-code#13339) — so the edit can proceed without an approval prompt. deny is unaffected. For guaranteed, payload-bound step-up, use the dashboard approval queue / proxy-executed path (axtary run workflow … --hosted-approval), which binds approval to the exact payload hash rather than relying on the editor's prompt.

Known scope

  • File-touching Bash commands are governed with conservative shell classification (redirections plus common read/write programs such as cat/sed/awk/grep/rm/tee/touch). Copy/move commands (cp/mv/install/rsync, plus dd and tar) govern both the source read and the destination write, so copying a secret to an unprotected path is still blocked by the source read. A command with several file ops is authorized in full and takes the most restrictive decision. Commands with no file operation return no opinion and Claude Code's own permissioning applies.
  • The hook governs file tools as repo content actions. MCP tool calls are gated by the wrapper below, not the hook.

Option 2 — Cursor beforeMCPExecution hook

Gates the MCP tool calls Cursor's agent makes by normalizing recognized filesystem tools (read_text_file, read_media_file, write_file, edit_file, …) into the same Axtary content actions and asking the local proxy for a decision.

1. Start the proxy in your project (leave it running):

axtary proxy --config axtary.yml

2. Install the hook config in the project:

axtary hook install cursor --owner user:you@example.com --repo your-org/your-repo

The command writes .cursor/hooks.json with the resolved local Axtary CLI path, sets failClosed, and preserves unrelated hooks. Re-running it updates the Axtary hook entry instead of adding duplicates. The generated hook shape is:

{
  "version": 1,
  "hooks": {
    "beforeMCPExecution": [
      {
        "command": "/resolved/node /resolved/axtary/packages/cli/dist/bin.js hook cursor --proxy http://127.0.0.1:7331 --owner user:you@example.com --repo repo:your-org/your-repo",
        "timeout": 10,
        "failClosed": true
      }
    ]
  }
}

3. In a Cursor session in that project, have the agent read .env.production through an MCP filesystem server — Axtary returns deny, Cursor blocks the call, and the agent sees the reason in agent_message and self-corrects. Every decision is a ledger record (.axtary/actions.jsonl).

Decisions and fail-closed behavior

  • allow → the MCP tool runs; deny → Cursor blocks it; step_up → Cursor asks.
  • The hook maps to Axtary's { permission, user_message, agent_message } output. The human sees user_message; the agent receives agent_message so it can correct course.
  • Fail-closed: Cursor proceeds on a non-zero/unexpected hook exit, so an unparseable payload or an unreachable proxy is returned as an explicit deny (set "failClosed": true as a second backstop). Unrecognized MCP tools return no opinion, leaving Cursor's own permissioning in place.

Known scope

  • Recognized tools are the MCP filesystem family; other MCP servers' tools return no opinion (gate them with the MCP wrapper below).
  • Shell execution (beforeShellExecution) is not yet mapped.

Option 3 — Codex PreToolUse hook

Gates the Codex CLI by normalizing its tool calls and asking the local proxy for a decision. Codex's reliable hook surface is the shell (Bash) tool, so Axtary inspects the command for a file it reads or writes (e.g. cat .env) and maps it to a content action; it also maps apply_patch edits and MCP filesystem tools (best-effort — Codex fires hooks for those less reliably).

1. Start the proxy in your project (leave it running):

axtary proxy --config axtary.yml

2. Install the hook config in the project:

axtary hook install codex --owner user:you@example.com --repo your-org/your-repo

The command writes .codex/hooks.json with the resolved local Axtary CLI path and preserves unrelated hooks. Re-running it updates the Axtary hook entry instead of adding duplicates. The generated hook shape is:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|apply_patch",
        "hooks": [
          {
            "type": "command",
            "command": "/resolved/node /resolved/axtary/packages/cli/dist/bin.js hook codex --proxy http://127.0.0.1:7331 --owner user:you@example.com --repo repo:your-org/your-repo",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

(Codex also accepts inline [hooks] tables in config.toml.)

3. In a Codex session in that project, ask it to cat .env — Axtary returns deny, Codex blocks the command, and the reason is passed back so the agent self-corrects. Every decision is a ledger record (.axtary/actions.jsonl).

Decisions and known scope

  • deny → Codex blocks the call; step_up → also blocked (deny) with a message to approve via the dashboard/proxy path. (Live-verified: Codex's PreToolUse honors only deny — it rejects a permissionDecision:"allow" — so for an allow Axtary emits no decision and lets Codex proceed, and it fails closed on step-up rather than rely on an unverified interactive prompt. For guaranteed, payload-bound step-up use the dashboard approval queue.)
  • Fail-closed: an unparseable payload or unreachable proxy returns an explicit deny. Commands with no file operation (e.g. npm test) return no opinion, leaving Codex's own approval in place.
  • Shell parsing is conservative (redirections + common read/write programs incl. cat/sed/awk/grep/rm/tee/touch); it does not fully parse shell, but copy/move commands (cp/mv/install/rsync, dd, tar) govern the source read as well as the destination write, so cp secret dest is blocked by the source read — not just the destination. Exotic invocations (a tar archive streamed to stdout, a reader piped mid-pipeline) remain best-effort. apply_patch edits are governed too (every named file; they fired the hook in Codex v0.142.3); MCP-tool coverage is best-effort per Codex's hook reliability.

Option 4 — MCP wrapper (any MCP client)

axtary mcp serve is a stdio MCP server. Every tools/call is normalized, checked against policy with the tool's definition hash pinned (drift = deny), pass-signed, executed through the wrapped upstream, and ledgered.

Run it (demo mode exposes a deterministic read_fixture tool — no upstream needed):

axtary mcp serve --config axtary.yml
# or wrap a real upstream:
axtary mcp serve --config axtary.yml --wrap 'npx -y @modelcontextprotocol/server-everything'
# or wrap a remote Streamable HTTP upstream:
axtary mcp serve --config axtary.yml --wrap-url https://host/mcp

Prove a real server end to end

Run one explicitly selected live tool through the full governed path and write a dashboard-readable evidence receipt:

axtary mcp conformance \
  --wrap-url https://mcp.deepwiki.com/mcp \
  --tool read_wiki_structure \
  --arguments '{"repoName":"facebook/react"}' \
  --accept

The command deliberately pins new/drifted definitions only with --accept, executes through policy and ActionPass v1/DPoP, verifies the correlated hash-chained ledger records, proves a clearly labeled synthetic pin mismatch is quarantined, and independently verifies a signed ledger export. It stores hashes and proof state—not auth headers or response content—in .axtary/mcp-conformance.json. The dashboard shows old receipts as stale; this is point-in-time evidence, not uptime monitoring.

Connect it — pick the path that matches your client

Claude Code CLI (most reliable):

claude mcp add axtary -- node /ABS/PATH/axtary/packages/cli/dist/bin.js mcp serve --config /ABS/PATH/axtary.yml
claude mcp list      # confirm "axtary" is registered

Claude Code VS Code extension / Cursor — project-scoped config. Create .mcp.json (Cursor: .cursor/mcp.json) in the project root:

{
  "mcpServers": {
    "axtary": {
      "command": "node",
      "args": ["/ABS/PATH/axtary/packages/cli/dist/bin.js", "mcp", "serve", "--config", "/ABS/PATH/axtary.yml"]
    }
  }
}

MCP onboarding gotcha: a project .mcp.json is only loaded when the editor's workspace is that project folder, and you must approve the server when prompted (or via the client's MCP panel / /mcp). Opening a different folder, or adding .mcp.json to an already-open session, means the server won't appear. If your client has no claude/CLI on PATH, use the project-config route and open the folder directly.

Then ask the agent: "Call the axtary read_fixture tool with fixtureKey repo_primer." Confirm it landed:

tail -3 .axtary/actions.jsonl   # "tool":"mcp.tool.call", decision "allow"

The tool-poisoning block (deterministic, no client needed)

axtary mcp drift-demo            # exit 0 iff the drift was blocked

The tool runs at its reviewed hash, then the definition mutates (a hidden exfiltration instruction) and the identical call is denied with mcp_definition_hash_not_allowed before the upstream runs — a name-based allowlist would have executed the mutated tool.

MCP step-up note

step_up decisions for MCP tools currently deny at the wrapper (there's no interactive approval channel inside MCP). Route step-up workflows through the dashboard approval queue instead.

Which to use

  • Claude Code, file edits in an editor: the Claude Code hook (with the step-up caveat above).
  • Cursor, in-editor MCP filesystem tool calls: the Cursor hook.
  • Codex CLI, shell commands and file edits: the Codex hook.
  • Any MCP client, or you want definition-hash provenance: the MCP wrapper.
  • Guaranteed payload-bound human approval: the dashboard approval queue (works regardless of editor).