jev-bridge
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@jev-bridgeIs this support message urgent, and which team should handle it?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
jev-bridge
Let Claude Code ask TypeSafe's Jev a question, and get a probability back.
A zero-dependency MCP server for TypeSafe's System One model, Jev. It gives Claude Code — or any MCP client — calibrated, typed judgments: yes/no probabilities, one-of-N choices and graded scores, instead of generated prose. Repeated questions are answered from a local cache for free, and every call's cost is recorded.
It follows the official specs, not a guess at them: MCP 2026-07-28 and 2025-11-25 (a dual-era server), Claude Code's own limits on what a server may declare, and the TypeSafe API and SDK contract. It is shown working in real Claude Code sessions, recorded message by message and checked against the official MCP JSON Schema. Its install guide and every example in its documentation are run as written, with the output kept. See Proven with Claude Code and Alignment with the official docs.
state: "Help! My payouts have been failing for 3 days."
ask: Which team should handle this?
billing ████████████████████ 0.87
technical ███ 0.13
sales · 0.00 confidence 0.80 · $0.0000168jev-bridge is an independent project. It is not affiliated with or endorsed by TypeSafe AI.
Quick start
You need Node.js 18 or newer (22.5+ keeps the cache between sessions),
Claude Code and a
TypeSafe API key. There is nothing to
npm install.
git clone https://github.com/lhviet/jev-bridge.git && cd jev-bridge
mkdir -p ~/.jev-bridge && chmod 700 ~/.jev-bridge
printf 'TYPESAFE_API_KEY=%s\n' 'your-key-here' > ~/.jev-bridge/.env && chmod 600 ~/.jev-bridge/.env
claude mcp add --scope user jev -- node "$(pwd)/src/server.mjs"
node src/server.mjs --selftest # one live call: proves the key worksThen start a new Claude Code session and ask for a judgment in plain language — "Is this support message urgent, and which team should handle it?" Each step is explained under Install. This block and the full guide were run as written, by a new user with an empty home directory, with every output kept: evidence/install.md.
Related MCP server: jev-judge-mcp
Contents
Why this exists
TypeSafe publishes a Claude Code plugin, typesafe@typesafe-ai. It is a
skill: six files, 14 KB, and not one byte of executable code. It teaches an
agent how to write a good Jev question — but it has no way to send one. A skill
is text loaded into the model's context; it cannot hold an API key or open a
socket.
typesafe@typesafe-ai 0.5.7
├── .claude-plugin/
│ ├── marketplace.json 382 B
│ └── plugin.json 320 B
├── skills/typesafe-ai/
│ ├── LICENSE 1068 B identical to the LICENSE below
│ └── SKILL.md 10040 B the whole product: guidance for the agent
├── LICENSE 1068 B
└── README.md 1336 B
6 files · 14,214 bytes · 0 bytes executablejev-bridge is the missing wire. It runs as a small child process that Claude Code talks to over stdin and stdout, and it is the only thing that reaches the TypeSafe API. It works well alongside the plugin: the skill teaches the agent to design questions, and jev-bridge lets it ask them.
Features
Five tools —
jev_ask,jev_usage,jev_history,jev_review,jev_models, each with anoutputSchema,structuredContentand the four behaviour hints (read-only, destructive, idempotent, open-world).Both TypeSafe endpoints —
POST /v1/systemoneandGET /v1/models, with the official SDKs' retry policy, timeout and environment variable names.Resources, prompts and completions — a question-design guide, live usage and history as resources; three prompts that Claude Code turns into slash commands; argument completion.
Both MCP eras — the stateless 2026-07-28 protocol (
server/discover, per-request_meta, cache hints,subscriptions/listen) and theinitializehandshake of 2025-11-25 and earlier, chosen per request.Cancellation and progress — a cancelled call stops its HTTP request; a call that asks for progress hears about each retry.
An answer cache — a repeated question returns in about 0.08 ms instead of about 180 ms, and costs nothing.
Cost accounting — tokens, cost, latency and outcome for every call.
A call history you can review — what was asked, what came back, how long it took and whether it was right, in a local dashboard (
--ui). Written after each answer has gone back, so it costs the answer nothing.Zero dependencies — Node built-ins only, SQLite included.
Degrades instead of failing — on a Node without SQLite it caches in memory.
How it works
jev-bridge is a child process, not a service. Your MCP client starts it and talks to it over stdin and stdout; it opens no port and exits with the session. (The history dashboard is a separate command you start yourself, and it listens only on 127.0.0.1.) The only thing that leaves your machine is a cache miss.
flowchart TB
subgraph MAC["Your machine"]
direction TB
CC("Claude Code<br/>or any MCP client")
SRV["jev-bridge<br/>child process, stdio only"]
KEY[("API key<br/>~/.jev-bridge/.env · 0600")]
DB[("jev.db<br/>answers, usage, history")]
CC <== "JSON-RPC over<br/>stdin and stdout" ==> SRV
KEY -. "read per call" .-> SRV
SRV <== "cache hit, about 0.08 ms" ==> DB
end
subgraph ANT["Anthropic"]
MODEL("Claude<br/>decides when to ask")
end
subgraph TS["TypeSafe"]
API(["api.typesafe.ai/v1"]) --> JEV{{"Jev"}}
end
CC <-- "conversation and<br/>tool results" --> MODEL
SRV -- "cache miss only<br/>HTTPS + Bearer" --> API
classDef client fill:#ede9fe,stroke:#7c3aed,color:#3b0764
classDef bridge fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px
classDef store fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef secret fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px
classDef external fill:#ffedd5,stroke:#ea580c,color:#7c2d12
classDef jev fill:#ffedd5,stroke:#ea580c,color:#7c2d12,stroke-width:2px
class CC,MODEL client
class SRV bridge
class DB store
class KEY secret
class API external
class JEV jev
style MAC fill:#2563eb12,stroke:#2563eb,stroke-dasharray:6 4
style ANT fill:#7c3aed12,stroke:#7c3aed,stroke-dasharray:6 4
style TS fill:#ea580c12,stroke:#ea580c,stroke-dasharray:6 4
linkStyle 0 stroke:#2563eb,stroke-width:2px
linkStyle 1 stroke:#dc2626,stroke-width:2px
linkStyle 2 stroke:#16a34a,stroke-width:3px
linkStyle 3 stroke:#ea580c
linkStyle 4 stroke:#7c3aed
linkStyle 5 stroke:#ea580c,stroke-width:2pxThree trust zones. The key is read on your machine and attached only to a request bound for TypeSafe — it never travels to Anthropic, and it is never written into a config file. A repeated question never leaves your machine at all.
One request, start to finish
sequenceDiagram
autonumber
box rgba(37,99,235,0.08) Your machine
participant C as Claude Code
participant B as jev-bridge
participant D as jev.db
end
box rgba(234,88,12,0.08) TypeSafe
participant T as api.typesafe.ai
end
C->>B: tools/call jev_ask {state, questions}
B->>B: validate every question
Note right of B: a malformed question stops here,<br/>naming its own field, at no cost
B->>B: fingerprint the questions (ids excluded)
B->>D: look up state + questions + model
alt a fresh answer is stored for the same model
rect rgba(22,163,74,0.14)
D-->>B: answers
B-->>C: answers · cached: true · about 0.08 ms
end
else nothing usable is stored
rect rgba(234,88,12,0.12)
B->>T: POST /v1/systemone with Bearer key
opt 408, 429 or 5xx (529 is overloaded)
T-->>B: timed out, rate limited or overloaded
B->>B: back off, honouring Retry-After, then retry (at most twice)
end
T-->>B: 200 · answers and usage
B->>D: store the answers
B-->>C: answers · cached: false · about 180 ms
end
end
B-)D: once idle, log tokens and cost, and record the call for reviewThree things happen before any network call: every question is checked, the questions are fingerprinted by their meaning rather than their ids, and the cache is consulted. Only a successful answer is ever stored, so a transient failure can never be served back to you as a cached one. The usage log and the call history are written last — after the answer has gone back, once the bridge is idle — so keeping them costs the call nothing.
Reading the diagrams
Every diagram in this repository uses the same vocabulary. Shape says what a thing is; colour says whose it is.
Colour | Shape | Means |
🟦 blue | rectangle | jev-bridge — code that runs on your machine |
🟪 violet | rounded box | an MCP client, or Claude |
🟩 green | cylinder | stored data — the answer cache, usage log and call history |
🟥 red | cylinder | your API key |
🟧 orange | pill · hexagon | TypeSafe's API · Jev making a judgment |
🟨 yellow | parallelogram | input you supply |
⬜ grey | rectangle | your own code |
Lines are coloured by what they carry: green is a cache hit, orange is a trip to TypeSafe, red is the key being read. A dashed outline marks a trust zone — a boundary your data crosses.
Going deeper: components, the storage decision, the database schema, how cache keys work and how it is tested are in docs/architecture.md, or as one illustrated page at lhviet.github.io/jev-bridge/explainer.html.
Proven with Claude Code
Claude Code 2.1.278 was run headless against jev-bridge seven times, with only this server configured and a wire tap logging every JSON-RPC message in between. The prompts ask for an outcome and never name a tool. Everything below is read from those logs. The full report, with every request the agent wrote and every answer, is evidence/README.md.
24 of 24 checks passed across the 7 sessions: Claude Sonnet 5 and Claude Haiku 4.5, over both MCP 2025-11-25 and 2026-07-28, using all five tools, a resource and a prompt.
Every message validates against the official MCP JSON Schema: 49 from the server and 48 from Claude Code in those sessions, plus 53 server messages in a sweep of every method, error paths included. All 10 tool schemas pass the JSON Schema 2020-12 meta-schema that Claude Code checks them against.
Claude Code identified itself as
claude-codeon every connection, and the server's own history filed each call under that name.
sequenceDiagram
participant C as Claude Code 2.1.278
participant B as jev-bridge
participant T as TypeSafe API
alt default: MCP 2025-11-25
C->>B: initialize (protocolVersion 2025-11-25)
B-->>C: 2025-11-25 · tools, resources, prompts, completions · instructions
C->>B: tools/list · prompts/list · resources/list
else MCP_PROTOCOL_NEGOTIATION=auto: MCP 2026-07-28
C->>B: server/discover (_meta protocolVersion 2026-07-28)
B-->>C: supportedVersions 2026-07-28, 2025-11-25, …
C->>B: tools/list · prompts/list · resources/list, each carrying _meta
end
Note over C: tool search: the agent loads jev_ask when the task calls for it
C->>B: resources/read jev://guide (when the instructions sent it there)
C->>B: tools/call jev_ask: every question in one call, with a progressToken
B--)C: notifications/progress
B->>T: POST /v1/systemone
T-->>B: answers + usage
B-->>C: structuredContent · call_id · resource_linkSession | Protocol | What the agent did |
Four labels that can all be true, plus urgency; every skill disabled, so nothing but the server's own text told it about Jev | 2025-11-25 | Read |
Rerank four search results | 2026-07-28 | One |
Route a ticket, record the known truth, audit the spend | 2026-07-28 |
|
| 2025-11-25 |
|
"Read | 2025-11-25 |
|
The same on Haiku, without asking for Jev | 2025-11-25 | Read the guide, then called |
"Is my key working?" on Haiku | 2026-07-28 |
|
How reliably. One recording shows something can happen, not how often it
does, so three scenarios were recorded again, graded by the same checks.
Batching every question into one call held in 3 of 3 runs for the multi-label
and 3 of 3 for the rerank prompt. Haiku with the loose prompt called jev_ask
in 5 of 6 runs. In the other it read the guide and answered by itself.
That run is kept in the report, not dropped.
What the recordings changed. The first recordings exposed three weaknesses in what the server told the agent, and each was fixed in the text rather than in the prompts:
Seen in an early recording | Why | Changed | Since |
The rerank took two | Nothing said how to rank | "To rank candidates, give each its own score against the query in the same call" | One call, in every recording |
Haiku loaded | The description quoted | Raw endpoints removed from what the model reads; the instructions say the bridge holds the key | No more |
Haiku read the guide and answered by itself | The guide read as reference, not as a step | The guide opens: design the questions here, then send them with | 5 of 6 runs call |
And before any of this, jev-bridge 0.1.0 answered Claude Code's 2026-07-28
probe with -32601 Method not found (log).
The install guide and the examples, run as written. Two more records sit beside the sessions, each generated by a script that reads the commands and requests out of the documentation itself:
evidence/install.md — every step of Install through Using it, and the Quick start, run by a new user with an empty home directory: clone, tests, key file,
claude mcp add,--selftest,claude mcp list, a real Claude Code session with the "Using it" prompt, and the JSON other clients use.evidence/examples.md — every
jev_askrequest in this README and in docs/recipes.md, sent live, each checked against the decision its text describes. Raw responses: examples.json.
Reproduce it:
node evidence/run.mjs # record the sessions: needs claude and a TypeSafe key
node evidence/run.mjs --trials 3 modern-rerank # repeat one, to measure reliability
node evidence/validate-wire.mjs # installs Ajv into the temp directory, outside the project
node evidence/report.mjs # rewrite evidence/README.md
node evidence/install.mjs # follow the install guide in a sandbox: rewrites evidence/install.md
node evidence/examples.mjs # run every documented request: rewrites evidence/examples.mdUse cases
Six patterns, each shown with the real output it produced from
jev-1.13.0. All six together ran in 1.3 seconds and cost $0.000162. Each links
to its full request in docs/recipes.md. The latest live run
of all six, checked against what each section says, is in
evidence/examples.md.
Route a request and fill its arguments in one trip
Choose the handler and the arguments every branch would need, in parallel. Your code takes one branch and reads only its answers.
"Can you refund my last order? The mug arrived cracked and I have photos."
handler choice ████████████████████ issue_refund confidence 1.00
refund_reason choice ████████████████████ damaged confidence 1.00
has_evidence noul ███████████████████▊ 0.99
needs_human noul ████ 0.20A router and its arguments in one round trip: 233 ms, $0.000022. Full request →
Rerank what your search returned
Retrieval returns candidates but cannot tell which one answers the question. Give each its own comparable score against the query, then sort in code.
"How do I rotate the API key without downtime?"
How well does each passage answer the query? (score, 0–3)
b ███████████████████▉ 2.99 ← answers the query
c ███████▏ 1.06 ← on topic, but never answers it
d ▋ 0.10
a ▍ 0.06Passage c is about API keys and never answers the question. A keyword search would have ranked it first. Full request →
Check a claim against its evidence
The guard in front of anything a language model asserts. Ask about support and contradiction separately — they are different failures.
claim: "The free plan includes 10 GB of storage."
source: "Free accounts may store up to 2 GB…"
supported noul ▎ 0.01
contradicted noul ███████████████████ 0.95 ← the source says otherwiseNot merely unsupported: contradicted. That is the case to escalate rather than quietly drop. Full request →
Apply labels that can all be true at once
Reach for one noul per label, not a choice — a choice forces a single winner and would throw three true facts away.
"Third time this week the export button does nothing. I am on the Pro plan paying $40/mo and I want a refund if this is not fixed today."
reports_bug noul ███████████████████▍ 0.97
mentions_billing noul ███████████████████▊ 0.99
requests_refund noul ███████████████████▍ 0.97
churn_risk noul █████████████████▋ 0.88 ← inferred rather than statedAll four are true, in one sentence. Full request →
Let code find candidates, and Jev pick the right one
Do not ask a model to extract a date — ask it to choose one. A regular expression finds every candidate; only the choice needs judgment, and the value you copy is guaranteed to be in the text.
"Ordered 3 Jan, dispatched 5 Jan, and it should reach you by 11 Jan. Returns close 25 Jan."
Which candidate is the expected DELIVERY date? (choice)
11 Jan ████████████████████ 1.00 the delivery date
3 Jan · 0.00 ordered
5 Jan · 0.00 dispatched
25 Jan · 0.00 returns close
none · 0.00The three decoy dates land at zero. Always offer a none, or a list that misses the answer forces a confident wrong pick. Full request →
Score dimensions once, decide the policy in code
Keep judgment and policy apart. Jev rates each dimension; your code weights them — so changing a weight costs nothing and needs no new call.
A pull request description, in full: "Fixes the thing. See ticket."
clarity score 0–3 ▍ 0.05 confidence 0.95
testability score 0–2 ██ 0.20 confidence 0.69 ← least certain answer hereIn code, 0.6 × clarity/3 + 0.4 × testability/2 = 0.05. The 0.69 confidence is the model saying there is little to judge. Full request →
Prerequisites
You need three things.
1. Node.js 18 or newer — 22.5+ recommended
node --versionjev-bridge runs on any Node from 18. The persistent cache uses
node:sqlite, which arrived in Node 22.5;
on older versions everything still works, but the cache lasts only as long as
the session. The current LTS is the simplest choice.
If you need to install or upgrade Node:
# with nvm (https://github.com/nvm-sh/nvm)
nvm install --lts
# or with Homebrew on macOS
brew install nodeOr download it from nodejs.org.
2. A TypeSafe API key
Create one in the TypeSafe console. You can try Jev without any code in the Playground first.
3. An MCP client
These instructions use Claude Code. jev-bridge is a standard stdio MCP server, so Claude Desktop, Cursor, and other MCP clients work too — see Other MCP clients.
Install
git clone https://github.com/lhviet/jev-bridge.git
cd jev-bridgeThat is the whole installation. There is no npm install step, because
there are no dependencies. Optionally, run the tests:
npm testNote the absolute path of the server — you will need it in a moment:
echo "$(pwd)/src/server.mjs"Updating is a pull, then a new Claude Code session:
git -C /path/to/jev-bridge pullYour key and your data live in ~/.jev-bridge/, not in the clone, so an update
never touches them. If you also work on jev-bridge, keep that clone separate
from the one Claude Code runs, and clone the installed copy from your working
copy. Then the installed copy only ever runs what you have committed, never a
half-finished edit, and a git pull brings it up to date without a push.
Configure your API key
There are two ways. The first is recommended.
Option A — a key file (recommended)
mkdir -p ~/.jev-bridge && chmod 700 ~/.jev-bridge
printf 'TYPESAFE_API_KEY=%s\n' 'your-key-here' > ~/.jev-bridge/.env
chmod 600 ~/.jev-bridge/.envThis keeps the secret out of your MCP client's configuration file — a file people often sync between machines, paste into issues, or commit to dotfile repositories. jev-bridge reads the key fresh on every call, so rotating it is a one-file edit and needs no restart.
Option B — an environment variable
Pass TYPESAFE_API_KEY through your client's configuration, shown in the next
section. Simpler, but the key then sits in that config file in plain text.
Connect it to Claude Code
claude mcp add --scope user jev -- node /absolute/path/to/jev-bridge/src/server.mjs--scope usermakes it available in every project. Use--scope projectto share it with one repository through its.mcp.json, or omit--scopeto add it to the current project only.If you chose Option B, add the key with
-e:claude mcp add --scope user jev -e TYPESAFE_API_KEY=your-key-here \ -- node /absolute/path/to/jev-bridge/src/server.mjs
Use nvm, or have several Node versions installed? Give Claude Code the absolute path to the interpreter, not bare
node. Otherwise the server runs on whichever Node is first onPATHwhen Claude Code starts — possibly an old one without SQLite.claude mcp add --scope user jev -- "$(which node)" /absolute/path/to/jev-bridge/src/server.mjs
Which protocol Claude Code speaks. By default Claude Code opens a stdio
server with the initialize handshake of MCP 2025-11-25, and jev-bridge
answers it. To have Claude Code use the stateless 2026-07-28 revision instead,
start it with:
MCP_SDK_GENERATION=v2 MCP_PROTOCOL_NEGOTIATION=auto claudeIt then probes with server/discover, jev-bridge lists the versions it
supports, and every request after that carries its own _meta. Both are
recorded in evidence/.
How Claude finds the tools. With tool search on (the default), a session
starts with only the tool names and the server's instructions. The instructions
tell Claude which tasks call for Jev, so it loads jev_ask when one comes up.
You do not have to name the tool.
Other MCP clients
Any client that launches stdio servers can use the same command. The usual JSON
shape — for Claude Desktop's claude_desktop_config.json, Cursor's
.cursor/mcp.json, and similar:
{
"mcpServers": {
"jev": {
"command": "node",
"args": ["/absolute/path/to/jev-bridge/src/server.mjs"]
}
}
}With Option B, add "env": { "TYPESAFE_API_KEY": "your-key-here" } beside
args.
Check it works
1. Call the API directly. This bypasses the cache and proves the key is good:
node src/server.mjs --selftestYou should see key: loaded (…) followed by a JSON answer from jev-1.13.0.
2. Check Claude Code can reach the server:
claude mcp listjev: node /…/jev-bridge/src/server.mjs - ✔ Connected3. Start a new Claude Code session. A session that was already running when you added the server fixed its tool list at startup and will not see it.
What each of these printed on a new user's machine, from the clone to the first answer in Claude Code, is in evidence/install.md.
Using it
Ask for it in plain language. Claude Code writes the request:
Use jev_ask to decide whether this support message is urgent and which team
should handle it: "Help! My payouts have been failing for 3 days."The three question types
Type | Ask it when | You get back |
| A condition either holds or it doesn't | One probability from 0 to 1. 0.5 means genuinely torn, not "medium". |
| Exactly one option from a set you define | The winner, the full distribution, and a confidence. |
| A degree along a rubric you describe | A weighted position that can land between your levels, plus the distribution. |
A complete request
{
"state": "Help! My payouts have been failing for 3 days.",
"questions": {
"is_urgent": { "type": "noul", "instructions": "Does this convey urgency?" },
"department": { "type": "choice", "instructions": "Which team should handle this?",
"criteria": { "billing": "Payments, invoicing, refunds",
"technical": "Bugs, outages, integrations",
"sales": "Pricing, upgrades" } },
"frustration": { "type": "score", "instructions": "How frustrated is the customer?",
"criteria": ["Calm", "Frustrated", "Very angry"] }
}
}{
"model": "jev-1.13.0",
"answers": {
"is_urgent": { "type": "noul", "noul": 0.95 },
"department": { "type": "choice", "choice": "billing", "confidence": 0.8,
"probabilities": { "billing": 0.87, "technical": 0.13, "sales": 0 } },
"frustration": { "type": "score", "score": 1.05, "confidence": 0.92,
"legend": { "0": "Calm", "1": "Frustrated", "2": "Very angry" },
"probabilities": { "0": 0, "1": 0.95, "2": 0.05 } }
},
"usage": { "input_tokens": 399, "output_tokens": 73 },
"bridge": { "cached": false, "latency_ms": 184, "cost_usd": 0.0000168 }
}Getting good answers
Batch independent questions into one call. Jev reads the state once and answers every question in parallel — cheaper and faster than separate calls.
Use one
noulper label when several can be true at once. Achoiceforces a single winner and discards the rest.Include a no-match option in a
choicewhen nothing may fit, or the model is forced to pick a wrong answer confidently.Question ids are not sent to the model. Put the full meaning in
instructions.Keep policy in your code. Take the probabilities and apply your own thresholds; changing a threshold then costs nothing.
See Use cases for six patterns with real output, and docs/recipes.md for each full request.
Tools
Every tool declares a title, an outputSchema, and the four behaviour hints
the MCP spec defines. A client may use the hints to decide what to confirm with
you; they are hints, not guarantees.
Tool | Read-only | Destructive | Idempotent | Open world | Reaches |
| yes | no | yes | yes |
|
| yes | no | yes | no | the local database |
| yes | no | yes | no | the local database |
| no | no | yes | no | the local database: writes a review |
| yes | no | yes | yes |
|
jev_ask counts as read-only because it changes nothing you own; it is
open-world because it calls TypeSafe. Every result carries structuredContent
and the same JSON as text. A bad argument comes back as a tool error that names
the field (questions["q"] has an unknown field "options"…), so the model can
fix it and retry. An unknown tool is a JSON-RPC error, -32602.
jev_ask
Evaluate a state against typed questions. The input schema spells out each
question type — noul, choice, score — with its own criteria shape, so
the model sees the exact contract before it writes a request.
Input | Required | Description |
| yes | Text, or a JSON object or array. Text only — no images or binaries. |
| yes | A map of your own ids to |
| no | Model or alias. Defaults to |
| no |
|
Returns model, answers, usage, and a bridge object: cached,
latency_ms, cost_usd — what this call cost, which is 0 on a hit —
call_id, which names the call in the history, attempts, and TypeSafe's
request_id. On a cache hit, usage describes the original call. Clients on
2025-06-18 or later also get a resource_link to jev://history/<call_id>.
Before anything is sent, every question is checked against the API reference:
a known type, instructions present, no unknown fields, a noul's criteria keyed
only by true/false, 2–255 choice options, 2–10 score levels in an array.
jev_usage
Calls, cache hits, hit rate, tokens, cost, money saved by the cache, and a
per-day breakdown. Input: days (default 7).
jev_history
Looks back at earlier calls. With no id, it returns stats for the period and
the matching calls, each with the start of its state and its answers on one
line. With id, it returns that call in full: state, questions, answers,
timing, cost and review. Read-only.
Input | Default | Description |
| — | A |
|
| How far back to look. |
|
|
|
|
| The certainty cut for |
| — | Only calls whose state or answers contain this text. |
|
| Most calls to list. |
jev_review
Records whether a past call was right: verdict is correct, partial or
incorrect (or null to withdraw a review). Optional expected records what
the answers should have been, by question id, and note says why. Claude can
call it itself when you correct an answer, or you can review in the
dashboard.
jev_models
The model names and aliases your account may use (GET /v1/models, shaped as
{ models: [{ name, description, release_date }] }). Also the cheapest way to
check a key.
Resources, prompts and completions
Resources are context a client can attach. In Claude Code, type @ and
pick one, or write @jev:jev://guide; Claude can also read them itself.
URI | What | Cache hint (2026-07-28) |
| How to design Jev questions, condensed from TypeSafe's docs, with links | public, 1 day |
| The models this key may use, fetched live | private, 1 hour |
| Usage over the last 7 days, as | private, 0 |
| The last 20 calls and their stats, as | private, 0 |
| One call in full (a resource template) | private, 0 |
jev://usage, jev://history and jev://history/{id} can be subscribed to:
resources/subscribe in 2025-11-25, subscriptions/listen in 2026-07-28. The
server says when each changes.
Prompts are workflows you start. Claude Code lists each as a slash command,
/mcp__<server>__<prompt>, with arguments separated by spaces:
Prompt | Arguments | Does |
|
| Walks the calls Jev was least sure of, judges each, records verdicts with |
|
| Spend, hit rate, latency and the calls that should have been batched |
| — | Loads the guide and drafts the |
/mcp__jev__cost_report 30
/mcp__jev__review_uncertain 7 0.7Completions suggest values for prompt arguments (days, below) and for
the {id} of jev://history/{id} (recent call ids).
Caching
A repeated question is answered from ~/.jev-bridge/jev.db without touching
the network.
Live call | Cached | |
Latency | 184 ms (average of 3) | 0.077 ms (median of 20) |
Cost | about $0.000017 | $0 |
Leaves your machine | yes | no |
That is roughly 2,370× faster on a repeat.
What makes two requests "the same": the state, the model, and the meaning of each question. Question ids are deliberately ignored. TypeSafe never sends them to the model, and an agent invents a fresh id every run — so
{"urgency": q}and{"urgency_check": q}share one cache entry, and the answer comes back under whichever id you used.Object key order does not matter; array order does. The levels of a
scoreare ordered, and reversing them is a different question.Entries expire after 7 days and the least-recently-used are evicted past 20,000 entries.
A moved alias invalidates itself. Each entry records which model answered it. When
jev-lateststarts resolving to a newer version, older entries stop being served.Failures are never cached. Only a successful answer is stored.
Why ids are excluded — and how a real Claude Code session exposed the cache missing almost every time before they were — is told in docs/architecture.md.
A note on determinism. Jev's decisions are stable across repeated calls,
but its probabilities vary slightly — about ±0.02 in the second decimal. A
cached answer freezes one sample of that. Pass "cache": false when you are
measuring rather than deciding.
Expect hits within a session, and from code that sends a fixed question. Across separate agent sessions the model tends to rephrase, and a genuinely different question is correctly a miss.
Cost and usage
Jev bills input tokens only; output is free. At the published price of $0.042 per million input tokens, a typical 400-token call costs about $0.000017 — roughly 60,000 calls to the dollar.
Ask Claude Code to run jev_usage, or use the command line:
node src/server.mjs --stats 7{
"calls": 24, "live_calls": 3, "cache_hits": 21, "hit_rate": 0.875,
"input_tokens": 1197, "cost_usd": 0.00005027,
"saved_input_tokens": 8379, "saved_usd": 0.00035192
}Call history
Every jev_ask — answered live, from the cache, rejected, or timed out — is
kept for review, so you can come back later and ask two questions of it:
was it efficient, and was it right.
node src/server.mjs --uiThat opens a dashboard in your browser. It shows:
Speed and cost: typical and 95th-percentile latency of live calls, the cache hit rate, what was spent, and a dot per live call over time. Failed calls and calls that were retried after a rate limit stand out.
Batching: re-sent states counts live calls that sent a state already sent earlier. Their questions could have gone in the earlier call, which would have been cheaper and faster.
Certainty: each call is scored by its least sure answer — a
choiceorscoreby Jev's own confidence, anoulby its distance from 0.5 (so 0.5 is 0, and 0.95 is 0.9). The Least certain filter lists the calls most worth a second look first.Accuracy: open a call to see the state, every question, and each answer as bars. Mark it Correct, Partly right or Wrong, click the option that should have won, and add a note. Accuracy is the share of reviewed calls marked correct.
The same history is available to Claude through jev_history and
jev_review, and on the command line:
node src/server.mjs --history 7 uncertain # stats and calls as JSON
node src/server.mjs --clear-history # forget it all; the cache and usage log stayGoing further. The database is an ordinary SQLite file, and
docs/analytics.md shows how to query it safely: latency
by model, failures and retries, calls that should have been batched, whether
Jev's certainty predicts its accuracy, accuracy per question, and exports to
CSV, JSON or Python. It also turns your reviewed calls into an evaluation set
and replays it against a new model, so you can see whether jev-preview is
better on your questions before you switch:
node examples/eval-set.mjs > eval.jsonl
node examples/replay.mjs eval.jsonl jev-previewWhat is kept. TYPESAFE_HISTORY decides:
Mode | Keeps | Use it when |
| the state and questions, the answers, timing, tokens, cost | you want to judge whether answers were right |
| answers, timing, tokens, cost, and hashes of the state and questions — not the state or question text | the state is sensitive, and speed and cost are what you need |
| nothing | you want no history |
Each state is stored once however often it is sent. Calls older than 30 days are pruned, and no more than 10,000 are kept — except reviewed calls, which are never pruned: they have become labelled examples.
It does not slow calls down. Nothing is written while a call is being answered: records queue in memory and are written in one transaction once the bridge has been idle for 20 ms (at most 500 ms later, or after 100 calls). Measured end to end over MCP, against the version without history, with an instant fake API so that only the bridge's own time shows:
Before | With | |
Cache hit, median | 114–119 µs | 108–116 µs |
Cache hit, 95th percentile | 0.17–0.25 ms | 0.21–0.31 ms |
Live call, median | 450–533 µs | 437–484 µs |
Live call, 95th percentile | 0.8–2.0 ms | 0.9–1.1 ms |
Medians are unchanged — slightly faster, since the usage log moved off the answer's path too — and the 95th percentiles move by a tenth of a millisecond at most. The one cost shows only in an unbroken burst of thousands of back-to-back calls: the call that lands on a 100-call write waits for it, which adds 2–4 ms at the 99th percentile. Against the real API the write happens while the next call is waiting for the network, and between Claude's turns nobody is waiting at all.
Configuration
Everything is optional.
Variable | Default | Purpose |
| — | The API key, if not using a key file. |
| — | Read the key from this file instead. |
|
| Where the key file and database live. |
|
| The database path. |
|
| Default model, the SDKs' name for it. Pin a version such as |
|
| How long an answer stays fresh. |
|
| Cache entries kept before eviction. |
|
|
|
|
| How long an unreviewed call is kept. |
|
| Unreviewed calls kept before the oldest go. |
|
| Price used to compute cost. |
|
| Timeout per attempt, as in the SDKs. There is no total budget. |
|
| Retries after the first attempt, for 408, 429, 5xx, timeouts and dropped connections. |
|
| First backoff, doubling to 5 s with 25 % jitter. A |
|
| API root, the SDKs' name for it; |
| — | Full API base including |
The key is looked up in this order: TYPESAFE_API_KEY,
TYPESAFE_API_KEY_FILE, ~/.jev-bridge/.env, then a .env at the repository
root.
Where your data lives
Path | Contents |
| Created with mode |
| Your API key, if you used Option A. |
| Cached answers, the usage log and the call history. Mode |
The cache stores answers, keyed by hashes of the request, and the usage
log stores token counts, costs and timings. Neither holds your state or
your question text. The call history does, when TYPESAFE_HISTORY is
full — the default — because judging whether an answer was right needs what
was asked. Set it to meta to keep only answers, timings and hashes, or off
to keep nothing. Answers repeat your option names and score level labels, in
every mode. The database is never uploaded, but jev_history does hand stored
states back to your MCP client, and one database serves every project. Your
key is sent only to the TypeSafe API, only over HTTPS, and is never logged. See
SECURITY.md.
Alignment with the official docs
Each row names the rule, where it is written down, what jev-bridge does about
it, and what checks it. "Test" means npm test; "recorded" means a real Claude
Code session in evidence/; "schema" means
validate-wire.mjs, which runs every message
through the official MCP JSON Schema.
MCP, revisions 2026-07-28 and 2025-11-25
Rule | jev-bridge | Checked by |
A server may serve both eras, choosing per request (versioning) | A request whose | test, recorded both ways |
Servers MUST implement | Returns | test, recorded, schema |
An unsupported version is | Yes | test, schema |
A request without | Yes | test, schema |
Every result carries | In 2026-07-28 results; left out of older ones | test, schema |
List and read results carry | Lists: public, 1 hour. Reads: per resource | test, schema |
|
| test, schema |
| Echoes 2024-11-05 through 2025-11-25; anything else gets 2025-11-25 | test |
Tools: | All five tools | test (each result checked against its schema), schema |
Unknown tool: protocol error | Yes | test |
Resources, templates, and update notifications (resources, subscriptions) | 4 resources, 1 template; | test, schema |
A missing resource is | Chosen by era | test |
Prompts (prompts) and completion (completion) | 3 prompts; completions for their arguments and for call ids | test, recorded, schema |
Progress only for requests that sent a | On start and on each retry | test, recorded |
Cancellation: stop work, send no response (cancellation) | Aborts the HTTP request and its backoff wait | test |
stdio: only MCP messages on stdout; logs to stderr; exit when stdin closes (stdio) | Yes; in-flight answers are drained first | test |
Logging, sampling and roots are deprecated (deprecated features) | Not implemented, on purpose: diagnostics go to stderr | — |
Elicitation through multi round-trip requests (MRTR), the tasks extension, icons | Not used: no call needs your input halfway ( | — |
Claude Code as the client
Claude Code does this (MCP docs) | So jev-bridge |
Defers MCP tools behind tool search; a session starts with tool names and server instructions only | Server instructions say which tasks call for Jev and what each tool is for |
Cuts tool descriptions and server instructions at 2 KB | Keeps the longest, |
Drops a tool whose input schema is not valid JSON Schema 2020-12, or whose top-level property names break | Every schema passes Ajv's 2020-12 meta-schema check; a test checks the names |
Rewrites root-level | Has none at the root; the three question shapes are a |
Saves results over its output limit to a file |
|
Lists MCP prompts as | Every prompt argument is a single token |
Offers resources with | The guide is a resource the instructions point to; recorded being read |
Negotiates 2026-07-28 on its v2 runtime with | Answers the |
Sends a | Reports the start of each live call on it (recorded) and every retry (tested) |
The TypeSafe API and SDKs
Official contract (API, models, SDK retries, RetryPolicy, constants) | jev-bridge |
|
|
| Same checks locally, naming the field that fails |
|
|
Retry 408, 429 and 500–599, connection errors and timeouts; at most 2 retries; backoff 500 ms doubling to 5 s, 25 % jitter; honour | The same defaults, tested one by one |
Timeout 10 s per attempt, no total budget | The same |
Environment: | Read, as well as the bridge's older names |
Errors carry | Quoted in every error, and returned as |
64k tokens per request; 32k for state plus the longest question; text only | Stated in the |
Batch every question about one state in one call; ids are never sent to the model; include a none option; one noul per label (primitives, fan-out) | Stated first in the description; recorded agents batching |
Troubleshooting
claude mcp list shows the server as failed. Run the command it shows by
hand — node /path/to/src/server.mjs --version — to see the real error. The
usual causes are a wrong path or a node that is not on Claude Code's PATH.
Use absolute paths for both.
The tools do not appear in Claude Code. Start a new session. A running session fixed its tool list at startup.
TypeSafe API returned 401. The key is missing or rejected. Run
--selftest: it prints which key it loaded, by length and first eight
characters.
The TypeSafe API key in … has spaces, line breaks or other characters a key cannot have. The file holds something besides the key — a stray word on the
key's line, or a second bare line. Leave just TYPESAFE_API_KEY=<key> in it,
or the key alone.
The log says node:sqlite unavailable. Your Node is older than 22.5.
Everything works, but the cache is in memory and resets each session. Upgrade
Node, or register the server with the absolute path of a newer one.
429, 529 or other 5xx errors. jev-bridge already retries these —
and 408, timeouts and dropped connections — twice, with backoff, as the
TypeSafe SDKs do. If they persist, you are over your rate limit or TypeSafe is
under load. The error names TypeSafe's request id; quote it to their support.
The dashboard is empty. It reads the same database as the MCP server, so
check both see the same TYPESAFE_DB and JEV_BRIDGE_HOME, and that the MCP
server was not started with TYPESAFE_HISTORY=off. Calls made before history
existed are not in it. Without node:sqlite (Node < 22.5) each process keeps
its history in memory, and the dashboard cannot see it.
Answers seem stale. Pass "cache": false for one call, or clear the cache:
node src/server.mjs --clear-cacheDevelopment
npm test # the full suite, on the built-in node --test runner
npm run selftest # one live call against the real API
npm run stats # usage over the last 7 days
npm run ui # the call-history dashboard
npm run evidence # record real Claude Code sessions (costs a little), then validate and report
npm run numbers # check every number this documentation states against the repositoryjev-bridge/
├── src/
│ ├── server.mjs askJev, question checks, the MCP methods, the CLI
│ ├── mcp.mjs JSON-RPC over stdio, both protocol eras, cancellation, progress, subscriptions
│ ├── catalog.mjs server instructions, tool schemas and hints, the guide, prompts
│ ├── typesafe.mjs the TypeSafe API: key, SDK retry policy, timeouts, errors
│ ├── store.mjs SQLite cache, usage log and history; memory fallback
│ ├── history.mjs certainty, filters and the stats a review reads
│ ├── ui.mjs the dashboard's local server: token, Host check, JSON API
│ └── ui.html the dashboard page, no network dependencies
├── test/
│ ├── protocol.test.mjs 37 tests: both eras, every method, Claude Code's limits
│ ├── typesafe.test.mjs 35 tests: retry policy, Retry-After, timeouts, env names, the key, argument checks
│ ├── server.test.mjs 37 tests
│ ├── history.test.mjs 57 tests
│ ├── examples.test.mjs 4 tests
│ └── schema-check.mjs a small JSON Schema checker the tests use
├── evidence/
│ ├── README.md the report: real Claude Code sessions, checks, schema validation
│ ├── run.mjs records the sessions in scenarios.mjs through tap.mjs
│ ├── validate-wire.mjs checks every message against the official MCP JSON Schema
│ ├── report.mjs writes the report from the recordings
│ ├── runs/ the recorded sessions: wire logs, transcripts, trials
│ ├── install.mjs follows the install guide in a sandbox → install.md
│ ├── examples.mjs runs every documented request live → examples.md, examples.json
│ ├── numbers.mjs checks the numbers this documentation states, in CI
│ └── stdio-client.mjs the minimal MCP client those two use
├── examples/
│ ├── eval-set.mjs reviewed calls as an evaluation set (JSONL)
│ └── replay.mjs score a model against that set
└── docs/
├── architecture.md components, storage, cache keys, testing, decisions
├── analytics.md querying the database: performance, quality, evaluation
├── recipes.md six worked patterns with real output
└── explainer.html the same material as one illustrated pageThe explainer reads best as a page: lhviet.github.io/jev-bridge/explainer.html.
Only the TypeSafe API is faked in tests. SQLite, the MCP protocol and multi-process database contention run for real. See CONTRIBUTING.md — in particular, the project takes no runtime dependencies.
References
The documents this server was checked against, read on 2026-09-20.
Model Context Protocol
Specification 2026-07-28 and its changelog: versioning, base protocol and
_meta, stdio, discovery, tools, resources, prompts, completion, caching, progress, cancellation, subscriptions, deprecated featuresJSON Schema: 2026-07-28, 2025-11-25
Claude Code
Connect Claude Code to tools via MCP: client runtimes and protocol negotiation, tool search and server instructions, output limits and
anthropic/maxResultSizeChars, schema checks, resources, prompts as commands
TypeSafe
Primitives: noul, choice, score, structured instructions; confidence, state, speculative fan-out, Jev 1.13 jaggedness
SDKs: JavaScript
RetryPolicy, client config, Python retries, constants, exceptions
Related projects
typesafe-ai/skills — TypeSafe's official Claude Code skill. It teaches an agent to design Jev questions; jev-bridge lets the agent send them. They complement each other.
rashedInt32/jev-mcp — another Jev MCP server, built on the official TypeSafe and MCP SDKs, with a file-reading triage tool. Choose it if you want those; choose jev-bridge if you want no dependencies, an answer cache and cost tracking.
TypeSafe documentation — the API, the question types, and cookbooks.
Licence
MIT © 2026 Hoang Viet Le.
"TypeSafe" and "Jev" are names belonging to TypeSafe AI, used here only to describe what this project connects to.
This server cannot be deployed
Maintenance
Related MCP Connectors
A paid remote MCP for OpenAI Codex agent coordination MCP, built to return verdicts, receipts, usage
A paid remote MCP for Pydantic AI structured output, built to return verdicts, receipts, usage logs,
A paid remote MCP for OpenAI Codex context compressor, built to return verdicts, receipts, usage log
Hosted MCP server for LLM cost estimation, model comparison, and budget-aware routing.
Related MCP Servers
- AlicenseAqualityBmaintenanceEnables frontier coding agents to delegate routine probabilistic judgments to TypeSafe Jev, providing calibrated triage signals for failures, attempts, completion, context ranking, findings, risk, and generic evidence-grounded questions.7MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to consult TypeSafe's Jev through a judge tool, answering narrow typed questions with calibrated probabilities instead of prose.MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to submit bounded semantic-uncertainty judgments to the pinned TypeSafe Jev API, with tools for yes/no, choice, and score evaluations plus optional evidence or context selection.MIT
- AlicenseAqualityCmaintenanceEnables coding or reasoning agents to request structured judgments from TypeSafe's Jev model at decision points, including choices, scores, claim verification, and code reviews, with probabilities and confidence returned as data.5MIT