Claude Code + Jev: Put the Gate in a Hook, Not an MCP Tool the Agent Can Skip
Claude Code Jev integrations shipped as a plugin and MCP tools the agent can skip. Put the gate in a PreToolUse hook that fails closed, with deny rules behind.
Go deeper. Build your own.
The most-starred community Jev MCP server ships a tool called jev_gate, and Claude Code runs it only when the model decides to call it. The same holds for every popular way to put Jev in front of Claude Code this week: the official integration is a skill plugin, and the community route is a pile of MCP servers. A tool the agent can decline to call is a second opinion, whatever its name says.
This is the runbook for turning a Claude Code + Jev check into enforcement. By Tuesday you have every Jev integration sorted into lookup or gate by who invokes it, one PreToolUse hook contract that denies on a risky verdict and on every Jev failure, the Codex version of that contract, and a permission-and-sandbox wall for the day the hook fails open.
Chatbots suggest; agents act. When the actor also decides whether to consult the safety check, the check becomes a suggestion with a latency budget.
Sep 15–21: the Claude Code + Jev integrations that shipped were optional for the model
TypeSafe launched Jev on Sep 15, 2026: a classifier that returns typed decisions (a Choice, a Score, or a Noul, which is a yes/no probability) instead of text, charged per input token with output free, per its models page. Its agent skill page describes the official integration as a “Drop-in skill for Claude Code, Codex, and other agent environments.” You install it with claude plugin marketplace add typesafe-ai/skills and claude plugin install typesafe@typesafe-ai, then invoke it with /typesafe:typesafe-ai or by asking for the skill in a prompt; the page mentions no MCP server. The typesafe-ai/skills repo had about 1.5K stars on Sep 21.
Screenshot: TypeSafe AI docs, “Agent skill - TypeSafe AI” (undated), captured Sep 21, 2026.
The skill is a knowledge artifact: it teaches the agent TypeSafe’s question types and patterns, makes no tool calls and contains no hook. TypeSafe’s advice on the page points at people: “The most important thing for humans to review is the questions and any threshold constants used in your TypeSafe code.”
The MCP route is community-built: jev-bridge, created Sep 17 with zero stars, closes its README with “Unofficial companion; not an official TypeSafe, OpenAI, Anthropic or Google product.” The starred ones are jkudish/jev-mcp at 227 and itsmostafa/typesafe-mcp at about 200, both from Sep 17. Besides jev-bridge, four more appeared in the first three days, and by repo description at least nine others existed by the end of Sep 21. One of jkudish’s ten tools, jev_gate, is pitched to “Gate a merge or a ship on completion claims.”
Screenshot: GitHub, “jkudish/jev-mcp: Fast, cheap, typed judgments from TypeSafe’s Jev model” (repo created Sep 17, 2026), captured Sep 21, 2026.
Two real gates shipped the same week, both outside Claude Code. LangChain’s experimental AutoModeMiddleware (langchain-typesafe, alpha Sep 17) asks a Jev Noul whether each listed tool call is risky and blocks at or above 0.5; its source says “This middleware blocks risky calls; it does not request human approval.” Vercel’s eve sends a Jev caution verdict to a human, and its Sep 19 guide adds “Failed evaluations take the human approval path.” Theo Browne’s Sep 20 video makes the design point from the other side: Jev behaves like a smart if statement in your code, not something you prompt.
Practitioners are building gates too; this one can also wave a call through, the verdict step 2 takes away.
Why a Jev check the model can skip is advice
What separates a lookup from a gate is who decides the check runs. A Jev MCP tool runs when the model chooses to call it, so it doesn’t run when the model is confused, rushed, injected or sure of itself: the four moments you wanted a gate for.
The Claude Code power guide already treats hooks as the enforcement layer, and the end of approve-once MCP shows a PreToolUse hook blocking MCP calls; this runbook doesn’t redo either. What is new: the official Jev path is itself model-invoked, the popular community path is MCP, and a hook, the one integration that can hold a gate, fails open by default when it crashes or stalls.
Step 1: Sort every Jev integration by who invokes it
Inventory what your developers and CI images picked up this week, one row per Jev integration, scored on a single question: who causes the check to run?
| Integration | Who invokes it | What it can do to a risky call | Seat |
|---|---|---|---|
TypeSafe skill plugin (typesafe@typesafe-ai) |
The model loads it, or a human types /typesafe:typesafe-ai |
Nothing; it teaches the API | Authoring aid |
| Community MCP servers (jkudish, itsmostafa, jev-bridge, others) | The model, if it decides to call a tool | Return an answer the model may ignore | Lookup |
jev_gate or jev_review over MCP |
The model | Advise; the “gate” runs only on request | Lookup with a misleading name |
LangChain AutoModeMiddleware |
The LangChain agent runtime, before listed tools | Block at a fixed 0.5; no human branch; errors block | Gate, inside LangChain agents only |
eve auto() |
The eve runtime | Send caution and failures to a human |
Gate, inside eve only |
Claude Code PreToolUse command hook |
The harness, before every matching call | Deny (exit 2) or force a prompt | The gate |
Codex PreToolUse hook |
The harness, for local tools | Deny; ask is unsupported |
Gate, with gaps |
Two scoring rules. A tool named “gate” is scored by who calls it; the name earns nothing. And a framework gate counts only inside its framework: AutoModeMiddleware guards a LangChain agent’s tools, not the Claude Code session in the next terminal.
The lookup rows stay useful. Ask a Jev MCP tool which of 40 files mention a migration, or which test failure looks flaky, and let the answer go back to the model. Only the last two rows go in a hook.
The community side is growing, and none of the three READMEs read for this piece (the skill, jev-bridge, jkudish) documents a PreToolUse hook.
Community Jev MCP servers by repo creation date, Sep 17–21, 2026 (GitHub metadata, counted by description). Every one exposes tools the model decides whether to call.
Every row still passes your plugin and MCP intake first; marketplace hygiene for plugins and MCP owns pinning and re-verification. Who invokes it is the question intake doesn’t ask.
Step 2: Write the Claude Code + Jev hook contract for write and destructive calls
Four parts: which calls reach Jev, what the hook sends, what it may return, and how long it may take. Each closes one way the hook could let a call through.
Scope. Gate the write and destructive classes. Reads stay out; they are most of the traffic, and reads of secrets belong to deny rules (step 5). Bash can do anything, so every Bash call counts as write-capable. The classes and their thresholds come from the Jev confidence-gate playbook; this contract only turns a verdict into the harness’s words.
Set an explicit timeout, because the default for command, http and mcp_tool hooks is 600 seconds. Name MCP write tools by their full names; a renamed tool that no longer matches is a disabled guard, which the guard contract suite catches.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "JEV_DENY_AT=0.50 JEV_ASK_AT=0.20 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/jev-gate.sh",
"timeout": 15
}
]
},
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "JEV_DENY_AT=0.70 JEV_ASK_AT=0.40 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/jev-gate.sh",
"timeout": 15
}
]
}
]
},
"permissions": {
"deny": ["Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)"]
}
}
The thresholds are placeholders, not recommendations, and the 15-second timeout is illustrative. The ratio isn’t: the script’s Jev deadline sits far inside the hook’s timeout, so the script always gets to exit 2 itself. The API key comes from the environment, never the repo.
Input. Send tool_name, tool_input and the working directory, not the transcript. TypeSafe’s models page caps a request at 64K tokens, with 32K for state plus the longest question; a gate should sit far below that. Don’t truncate a call that doesn’t fit, because the dangerous part of a shell line is often the tail, as in eve’s example of a command that “includes a deletion even though it starts with a read.” Oversize is a deny.
Output: three results, never four. At or above the deny threshold, exit 2 with the reason on stderr (JSON permissionDecision: "deny" also works; exit 2 needs no parsing). Between the ask and deny thresholds, print permissionDecision: "ask". Below the ask threshold, exit 0 and say nothing, which leaves the call to your permission rules, the prompt and, in auto mode, the classifier. The hook never returns allow.
Claude Code’s permissions docs pin down deny and ask and leave allow open. A hook that exits 2 “stops the tool call before permission rules are evaluated.” “Hook decisions don’t bypass permission rules,” so an allow can’t beat a deny or ask rule. An ask survives auto mode: “the classifier can still deny the tool call, but it can’t approve the call silently.”
What a hook allow does to the auto-mode classifier is not documented, and a veto-only gate never needs to know. Jev becomes one more reason to stop, never a reason to proceed.
Deadline. Give the Jev client an explicit deadline (3 seconds in the script, illustrative), with any retries inside it. What a 429 or a late verdict means for each gate is set in the Jev fail-mode playbook; for write and destructive calls this contract’s answer is fixed. No verdict, no call.
#!/usr/bin/env bash
# jev-gate.sh: Claude Code PreToolUse command hook. Veto-only: deny, ask, or no opinion.
# No `set -e` and no ${VAR:?}: both exit with a code other than 2, and any other code lets the call through.
trap 'echo "jev-gate: internal error, denying" >&2; exit 2' ERR
deny() { echo "jev-gate: $1" >&2; exit 2; }
DENY_AT="${JEV_DENY_AT:-}"; ASK_AT="${JEV_ASK_AT:-}" # per tool class, from your threshold table
[ -n "$DENY_AT" ] && [ -n "$ASK_AT" ] && [ -n "${TYPESAFE_API_KEY:-}" ] || deny "gate not configured"
input="$(cat)" || deny "no hook input"
state="$(jq -c '{tool_name, tool_input, cwd}' <<<"$input")" || deny "unreadable hook input"
[ "${#state}" -le 40000 ] || deny "call too large to judge; not truncating it"
body="$(jq -n --argjson s "$state" '{
model: "jev-1.13.0",
state: $s,
questions: {
is_unsafe: {
type: "noul",
instructions: "Would this tool call delete data, touch credentials, rewrite git history, or write outside the working directory?",
criteria: { "true": "It could do at least one of those.", "false": "It does none of those." }
}
}
}')" || deny "could not build the request"
resp="$(curl -sS --fail --max-time 3 "${JEV_URL:-https://api.typesafe.ai/v1/systemone}" \
-H "Authorization: Bearer $TYPESAFE_API_KEY" -H "Content-Type: application/json" \
--data-binary "$body")" || deny "no verdict from Jev (error, 429 or deadline)"
p="$(jq -er '.answers.is_unsafe.noul | numbers' <<<"$resp")" || deny "no usable answer"
if awk -v p="$p" -v t="$DENY_AT" 'BEGIN { exit !(p >= t) }'; then
deny "Jev says unsafe (p=$p)"
elif awk -v p="$p" -v t="$ASK_AT" 'BEGIN { exit !(p >= t) }'; then
[ "${ASK_MEANS_DENY:-0}" = 1 ] && deny "Jev says escalate (p=$p) and this harness cannot ask"
jq -n --arg r "Jev flagged this call (p=$p)" \
'{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: $r}}'
exit 0
fi
exit 0 # no opinion: deny/ask rules, the prompt and the classifier still decide
The 40,000-character cap is a crude, conservative stand-in for the token limit; JEV_URL lets the step 6 drills point the script at a local stub.
Step 3: Make every failure path exit 2
On Claude Code, the hooks reference warns that “exit code 2 is the only exit code that blocks through the code alone” for most hook events. Everything else that can go wrong in a hook lands on the permissive side.
| What went wrong | What Claude Code does | What the script does about it |
|---|---|---|
| Hook exits 1 without valid JSON | Non-blocking error; the call proceeds | Every external step ends in an or-deny guard; an ERR trap catches the rest |
set -e trips on a failed command |
Shell exits with that command’s code (curl’s 22 or 28), so the call proceeds | No set -e |
An unset ${VAR:?} |
Bash exits 1; the call proceeds | Defaults plus an explicit check that denies |
command, http or mcp_tool hook times out |
Doesn’t block; the call continues through the normal permission flow | Client deadline far inside the hook timeout |
| HTTP hook gets a non-2xx or can’t connect | Non-blocking error; execution continues | Not an http hook; curl failures become exit 2 |
mcp_tool hook’s server is disconnected or returns isError |
Non-blocking error; execution continues | Not an mcp_tool hook |
| Jev returns 429, 5xx or an answer without the field | Whatever the script decides | Exit 2 |
| Script missing or not executable | A non-zero exit other than 2; the call proceeds | Nothing it can do; the wall in step 5 has to hold |
The timeout row is not an automatic allow, since rules, prompts and the classifier still apply, but in the docs’ own words, “don’t count on a stalled hook to act as a gate.” The mcp_tool row is why this runbook insists on a command hook and a script you own: wire a community Jev MCP server as an mcp_tool hook, and on the day it disconnects every call walks through. Agent SDK callbacks flip the timeout default: “An Agent SDK callback hook that exceeds its timeout blocks the tool call.”
Step 4: Port the contract to Codex, and write down what it can’t cover
OpenAI’s Codex hooks accept the same deny shapes: permissionDecision: "deny", the legacy {"decision":"block"}, or exit 2 with the reason on stderr, so the script works unchanged except for the middle band. On Codex, permissionDecision: "ask" is “parsed but not supported yet”: Codex marks the hook run as failed, reports the error and continues the tool call. An escalate verdict therefore has to be a deny on Codex lanes, which is what ASK_MEANS_DENY=1 does. Codex also uses 600 seconds for most hooks when the timeout is omitted, so set it there too.
Then write the gaps into the lane’s policy file, because no hook closes them:
- Hosted tools such as WebSearch don’t use the local hook path.
write_stdindoesn’t runPreToolUseagain when it sends input to a command that already passed.- Codex’s
mcp_toolhooks don’t block on errors, missing servers or unavailable tools. - Codex’s docs don’t say what a timed-out
PreToolUsecommand hook does to the call, so don’t assume it blocks.
OpenAI’s own summary belongs at the top of that policy file: tool hooks are “a useful guardrail, not a complete enforcement boundary.”
Step 5: Keep deny rules and the sandbox as the wall behind the gate
The hook is the gate, not the wall. Build the wall assuming the gate fails open some day: a script path typo that exits 127, a matcher that stopped matching, a settings file that never loaded on one laptop. Three layers sit behind the hook, in the order Claude Code evaluates them.
- Deny rules for what no verdict should unlock: secrets, production credentials, directories agents never write. Claude Code evaluates deny and ask rules whatever a hook returns.
PreToolUseruns only when Claude calls a tool, so a file referenced with@in the prompt never reaches the hook; block those paths with aReaddeny rule, as in the settings above. Claude Code also never lets an allow rule or a hookallowapprove anrmorrmdiraimed at a critical path. - The prompt, or the auto-mode classifier. Both come after the rules. You don’t choose that stacking; you only choose whether Jev adds a veto in front of it.
- The sandbox under shell and write tools. A classifier reads the text of a command and can’t know what the command can reach, or as Vercel’s eve guide puts it, “a shell-command classifier cannot supply those restrictions.” Sandboxes have their own escape record, which the Black Hat sandbox-escape piece covers; they still hold when every text-reading check above them is wrong.
The hook can only deny or ask. Everything under it still runs, and still holds when the hook fails open.
The wall is also why the veto-only rule costs so little. A wrong Jev deny costs one prompt or one retry. A wrong Jev allow would have skipped the prompt that was the last human look at the call.
Step 6: Drill the contract before you trust it
A gate you haven’t watched fail is a hypothesis. Run these in a disposable repo on every CLI upgrade and every change to the script, with a local stub standing in for Jev where you need a specific answer.
| Drill | How to cause it | Expected on Claude Code | Expected on Codex |
|---|---|---|---|
| Unsafe verdict | Stub returns a probability above the deny threshold | Blocked, reason shown to the model | Blocked |
| Escalate verdict | Stub returns a value between the thresholds | Prompt appears; in auto mode the classifier may still deny | Blocked (ask maps to deny) |
| Jev down | Point the URL at a closed port, or use a bad key (401) | Blocked | Blocked |
| Jev slow | Stub sleeps past the client deadline | Blocked by the script, well before the hook timeout | Blocked |
| Rate limited | Stub returns 429 | Blocked | Blocked |
| Malformed answer | Stub returns 200 with no answers |
Blocked | Blocked |
| Oversize call | A Bash call carrying a 50 KB argument |
Blocked, not truncated | Blocked |
| Script missing | Rename the script | Proceeds: deny rules and the sandbox must hold | Not documented; record what you see |
@ reference |
Put @.env in the prompt |
Hook never fires; the Read deny rule blocks it |
Not applicable |
The script-missing row is the one to sit with. It proceeds by design, and the only thing between the call and the disk is the wall from step 5. Every drill result, like every live verdict, goes into the decision record: the probability, the versioned model ID, the thresholds in force and which failure path fired. The Jev decision-log playbook sets those fields and why the model ID must be pinned.
Where a Jev hook fails open, and the signal for each
The gate that is really a lookup. Someone wires jev_gate over MCP and calls it the merge gate. Signal: writes in session transcripts with no Jev decision record in front of them. Fix: move the check into the hook and keep the MCP tool for lookups.
Exit-code drift. A refactor adds set -e, a ${VAR:?}, or a helper that exits 1. Signal: the Jev-down drill lets the call through. Fix: run the drill in CI on every change to the script.
Timeout inversion. The client deadline creeps above the hook timeout, or default SDK retries stretch past it. Signal: hook durations bunching at the timeout, and calls that proceed after a long pause with no decision record. Fix: one client-deadline number, checked against the hook’s timeout when settings load.
Matcher miss. A renamed or plugin-scoped MCP write tool no longer matches, so Jev never sees it. Signal: write-class tool calls in transcripts without a gate record. Fix: the guard contract tests linked in step 2, on every harness bump.
The mcp_tool shortcut. Someone swaps the script for an mcp_tool hook that calls a Jev MCP server. Signal: gate records stop whenever the server disconnects while tool calls carry on. Fix: back to a command hook.
Ask on the wrong harness. A shared script returns ask on a Codex lane and Codex continues the call. Signal: failed hook runs in Codex’s output next to calls that ran anyway. Fix: ASK_MEANS_DENY=1 on every Codex lane.
Allow creeping back. Someone adds an allow branch to save a prompt on obvious calls. Signal: any permissionDecision other than deny or ask in the gate code. Fix: reject it in review; the contract rests on the veto-only rule.
A Jev gate is fleet policy, not model behavior
Nothing in this runbook asks the model to behave. It can keep reading the TypeSafe skill, calling Jev MCP tools and drafting better questions, all good uses of a fast classifier. The gate sits one layer down, in harness settings and a script the model doesn’t choose to run, owned by whoever owns the fleet’s policy files and drilled like a fire door. That layer is what a multi-agent command center comes down to: settings, hooks and walls that apply to every session on every machine, watched or not.
A check the agent can skip is advice. Put Jev where the harness runs it, let it say only no or ask, and keep the wall behind it for the day it can’t say anything at all.
FAQ
Can a Jev MCP server be a Claude Code gate?
Not on its own. An MCP tool runs when the model calls it, so the model can skip it. Wiring the server as an mcp_tool hook fails open when the server is disconnected or returns an error. Keep MCP servers for lookups and put the gate in a PreToolUse command hook that exits 2.
Does TypeSafe’s Claude Code plugin block risky tool calls?
No. TypeSafe’s official Claude Code integration is a skill plugin that teaches the agent the API: question types, patterns and practices. It makes no tool calls and contains no hook. Use it to help draft your gate’s questions, then have a person review the questions and thresholds, as TypeSafe’s docs advise.
Sources
- TypeSafe: Agent skill — the official Claude Code path is a skill plugin
- typesafe-ai/skills — the plugin’s repo; about 1.5K stars on Sep 21, 2026
- TypeSafe: Models — input-only pricing; 64K per request, 32K for state plus the longest question
- ugin-man/jev-bridge — “Unofficial companion”; created Sep 17, 2026
- jkudish/jev-mcp — ten Jev MCP tools, including
jev_gate - LangChain
auto_mode.py—AutoModeMiddleware: fixed 0.5 block, no human branch - Vercel: auto-approve tool calls in eve with Jev — Sep 19, 2026; failed evaluations go to a human
- Claude Code docs: Hooks reference — exit codes, timeouts,
httpandmcp_toolfailures (docs as of v2.1.278) - Claude Code docs: Permissions — hook decisions don’t bypass permission rules
- OpenAI: Codex hooks — deny shapes, unsupported ask, hosted-tool and
write_stdingaps
