Skip to main content
Glama

jev-answers

npm CI License: MIT

A small MCP server and CLI that sends files and typed questions to TypeSafe's Jev decision model and returns the raw answer. Every request gets its own session folder that keeps the inputs, the exact request and the response.

  • Zero dependencies, plain Node.js (>= 22), no build step.

  • One MCP tool, jev_ask, for Claude Code, Codex, OpenCode, Cursor, Claude Desktop and any other MCP client.

  • A thin pipe: it never interprets answers. No thresholds, no verdicts, no "flags". You get the provider's response body unchanged.

  • It does not run git or any other command. If you want a diff reviewed, write the diff to a file and pass the path.

Jev answers typed questions with probabilities instead of prose:

type

question

answer

noul

a yes/no question

probability that the answer is YES (0.5 means it cannot tell)

choice

pick one option

the chosen option plus a probability per option

score

position on ordered levels

a number between 0 and (levels - 1), plus probabilities

This project is not affiliated with TypeSafe AI.

How it works

Each call runs three independent stages. Each stage reads only what the previous one wrote into the session folder.

collect  ->  build  ->  send
~/.jev-answers/sessions/20260930-164512-payment-review-7f3a/
  input.json       the call arguments, exactly as received
  inputs/          collected copies of every file (relative layout kept; outside files under _external/)
  manifest.json    what was collected and what was skipped, with sizes and sha256
  request.json     the exact body that was sent (never contains the API key)
  response.json    the provider's raw response
  error.json       only on failure: {stage, type, message, details}
  meta.json        id, label, provider, model, status, token estimate, timings, HTTP status

sessions/latest is a symlink to the newest session.

Related MCP server: jevcore-mcp

Install

Run the setup wizard. It asks for your provider, API key and model, writes the config file, and offers to register the server with the MCP clients it finds on your machine.

npx -y jev-answers setup

# or the latest main branch straight from GitHub
npx -y github:AmooEbrahim/jev-answers setup

# or from a clone
node bin/jev-answers.js setup

npx caches packages. To pick up a new release, run npx -y jev-answers@latest --version once.

Get a key at console.typesafe.ai (TypeSafe direct) or openrouter.ai/keys (OpenRouter). The key is stored in the config file (mode 0600), so your client configs need no environment variables.

Switching provider drops the stored key, model and base URL, because they belong to the old provider; in --yes mode you must pass --api-key when you switch. Optional flags: --model, --home, --base-url, --skill claude,agents (install the agent skill).

Non-interactive:

jev-answers setup --provider typesafe --api-key "$KEY" --yes
jev-answers setup --provider typesafe --api-key "$KEY" --yes --register claude,codex,opencode

Manual client configuration

Use whichever launch command fits: npx -y jev-answers serve, or /absolute/path/to/node /absolute/path/to/bin/jev-answers.js serve for a clone (setup writes the full path of the running Node). On Windows, setup prints the registration commands instead of running them.

Claude Code:

claude mcp add --scope user jev-answers -- npx -y jev-answers serve

Codex:

codex mcp add jev-answers -- npx -y jev-answers serve

or in ~/.codex/config.toml:

[mcp_servers.jev-answers]
command = "npx"
args = ["-y", "jev-answers", "serve"]

OpenCode:

opencode mcp add --global jev-answers -- npx -y jev-answers serve

or in ~/.config/opencode/opencode.json:

{
  "mcp": {
    "jev-answers": {
      "type": "local",
      "command": ["npx", "-y", "jev-answers", "serve"],
      "enabled": true
    }
  }
}

Cursor, Claude Desktop and other clients that use mcpServers:

{
  "mcpServers": {
    "jev-answers": {
      "command": "npx",
      "args": ["-y", "jev-answers", "serve"]
    }
  }
}

If you prefer environment variables over the config file, add "env": {"JEV_ANSWERS_API_KEY": "..."} to the client entry. Note that some clients do not expand ${VAR} references; the server reports a clear config_error if it sees an unexpanded placeholder.

Configuration

Precedence, low to high: built-in defaults, config file, environment variables, the per-call model.

The config file is $JEV_ANSWERS_CONFIG if set, otherwise $XDG_CONFIG_HOME/jev-answers/config.json, otherwise ~/.config/jev-answers/config.json.

key

environment variable

default

provider

JEV_ANSWERS_PROVIDER

typesafe (or openrouter)

base_url

JEV_ANSWERS_BASE_URL

per provider, see below

model

JEV_ANSWERS_MODEL

jev-latest (TypeSafe), ~typesafe/jev-latest (OpenRouter)

api_key

JEV_ANSWERS_API_KEY

falls back to TYPESAFE_API_KEY or OPENROUTER_API_KEY for the chosen provider

home

JEV_ANSWERS_HOME

~/.jev-answers

max_input_tokens

JEV_ANSWERS_MAX_INPUT_TOKENS

28000

max_total_tokens

JEV_ANSWERS_MAX_TOTAL_TOKENS

56000

timeout_ms

JEV_ANSWERS_TIMEOUT_MS

60000 (per attempt)

max_retries

JEV_ANSWERS_MAX_RETRIES

2

max_file_bytes

JEV_ANSWERS_MAX_FILE_BYTES

1048576

max_files

JEV_ANSWERS_MAX_FILES

200

allow_secret_files

JEV_ANSWERS_ALLOW_SECRET_FILES

false

retention_days

JEV_ANSWERS_RETENTION_DAYS

0 (keep forever)

debug

JEV_ANSWERS_DEBUG

false (logs go to stderr)

base_url is the full endpoint URL:

  • TypeSafe: https://api.typesafe.ai/v1/systemone

  • OpenRouter: https://openrouter.ai/api/alpha/decisions (an alpha endpoint that may move). https://openrouter.ai/api/v1/systemone is an alternative TypeSafe-compatible path you can set here.

Any gateway that speaks the same request format works too.

Check what is in effect with jev-answers config or jev-answers doctor.

The jev_ask tool

Input

{
  "files": [
    "/abs/path/app/Services/PaymentService.php",
    {"path": "/tmp/review/change.diff", "label": "git diff of the change under review"},
    "app/Http/Controllers/**/*.php"
  ],
  "base_dir": "/abs/project/root",
  "context": "Optional free text: the task, requirements, rules, notes.",
  "questions": { "...": "see below" },
  "label": "payment-review",
  "model": "jev-latest"
}
  • files: paths, directories, globs, or {path, label}. Relative paths resolve against base_dir (default: the server's working directory); absolute paths are safest.

  • context: free text that is added to the state next to the files.

  • At least one of files or context is required. questions is required.

  • Directories and globs (**, *, ?, [...], {a,b}) are expanded by the tool itself. Following the usual glob convention, both skip hidden entries (names starting with ., and everything inside hidden directories) unless the pattern segment itself starts with a dot: .github/**/*.yml and **/.eslintrc* work, **/*.yml does not reach into .github. Hidden entries are listed in manifest.json (a hidden directory once, not its contents). A *.md pattern only looks as deep as the pattern is long, a trailing slash (src/, src/**/) means the directory itself, and an expansion that visits more than 20,000 entries fails with invalid_input so you can narrow it. Unreadable directories are skipped with reason unreadable. Expansion skips any path inside .git, node_modules, vendor, dist, build, .next, .venv, venv, __pycache__, coverage or .jev-answers, plus binary files, secret files and symlinks (files and directories; they are never followed). Skipped files are listed in manifest.json under skipped with a reason.

  • A file you name explicitly is never skipped silently: if it is missing, binary, secret or too large, the call fails. An explicitly named symlink is allowed, but both the link and its target are checked against the secret-file patterns.

  • If nothing was collected and there is no context, the call fails with invalid_input instead of sending an empty state.

  • Unknown top-level fields (for example a file typo) are rejected with invalid_input.

  • label becomes part of the session folder name. model overrides the configured model for this call.

Questions

Question ids match ^[A-Za-z0-9_.-]{1,64}$. Each question has a type, instructions, and (for choice and score) criteria. Unknown fields are rejected.

{
  "card_logged": {
    "type": "noul",
    "instructions": "Does any changed line write a card number or CVV to a log?"
  },
  "layer": {
    "type": "choice",
    "instructions": "Which layer does this change mainly touch?",
    "criteria": {"frontend": "UI code", "backend": "server code", "database": "schema or queries"}
  },
  "risk": {
    "type": "score",
    "instructions": "How risky is deploying this change?",
    "criteria": ["No risk", "Low risk", "Moderate risk", "High risk"]
  }
}
  • noul: criteria is optional; if given it must be {"true": "...", "false": "..."}.

  • choice: criteria is an object of 2 to 255 options (name to description; a description may be null).

  • score: criteria is an array of at least 2 level descriptions, lowest first.

  • instructions is a string in the advertised tool schema. At runtime a JSON object or array is also accepted (Jev takes structured instructions) and sent as given.

Ready-to-use inputs are in examples/.

Output

{
  "session": "/home/u/.jev-answers/sessions/20260930-164512-payment-review-7f3a",
  "status": "ok",
  "response": {
    "model": "jev-1.13.0",
    "answers": {
      "card_logged": {"type": "noul", "noul": 0.03}
    },
    "usage": {"input_tokens": 476, "output_tokens": 12}
  }
}

response is the provider's body, unchanged. On failure:

{
  "session": "...",
  "status": "error",
  "error": {"stage": "build", "type": "context_too_large", "message": "...", "details": {}}
}

stage is one of config, session, collect, build, send. config and session errors happen before a session folder exists or is usable, so session may be null.

Error types: config_error, session_error, internal_error, invalid_input, invalid_question, file_not_found, file_unreadable, binary_file, secret_file, file_too_large, too_many_files, missing_artifact, context_too_large, auth_error, payment_required, invalid_request, rate_limited, overloaded, provider_error, timeout, network_error, invalid_response, cancelled.

Provider error bodies are kept in error.json under details.body: JSON bodies up to 8 KB as they are, larger ones cut to the first 8 KB as text with details.body_truncated: true. The API key and any Bearer ... token are replaced with [redacted] in every recorded or returned error.

Over MCP, errors come back as a normal tool result with isError: true.

Reviewing a diff

The tool does not run git. Write the diff to a file and pass it:

mkdir -p /tmp/jev
git diff main...HEAD > /tmp/jev/change.diff

git diff leaves out untracked files. Add them by diffing each one against /dev/null, which does not touch your index:

{
  git diff main...HEAD
  git ls-files --others --exclude-standard -z | xargs -0 -I{} git diff --no-index /dev/null {}
} > /tmp/jev/change.diff

Then pass {"path": "/tmp/jev/change.diff", "label": "git diff of the change under review"} together with the files that give the diff its context.

Writing good questions

  • One specific, checkable fact per question. Prefer "Does any changed line log a card number?" over "Is there a security issue?". Generic "is there a bug?" questions are unreliable.

  • Ask about written rules and requirements, and put those rules in context.

  • No arithmetic, counting or date math. Work it out yourself first, then ask about the result.

  • Keep the state small: pass only the relevant files. Irrelevant context lowers accuracy.

  • Many questions per call is fine and cheap; they are all answered in one pass.

CLI

jev-answers serve                 start the MCP server on stdio (also "mcp"; no args + piped stdin does the same)
jev-answers setup [options]       interactive setup (see Install for the non-interactive flags)
jev-answers ask [file|-]          run a tool-input JSON file or stdin, print the result; exit 0 ok / 1 error
jev-answers resend <session>      send an earlier session's request again, in a new session
jev-answers list [--limit N]      newest first (default 20)
jev-answers show <session> [--path]  print response.json (or error.json), or the folder path
jev-answers prune --older-than 30d [--yes]
jev-answers skill <action>        install | uninstall | status | show the optional agent skill
jev-answers doctor [--live]       effective config with sources, folder check; --live sends one tiny question
jev-answers config                config file path and effective config (key masked)

<session> is an id, a unique part of an id, a folder path, or latest.

Limits

  • Jev accepts about 64k tokens per request in total and about 32k for the state plus the single longest question. This tool estimates tokens conservatively (ASCII characters / 3.5, plus one token for every non-ASCII character, since Persian, CJK and similar text tokenizes much worse) and, by default, stops at 28k and 56k so oversized requests fail locally with context_too_large (listing the five largest files) instead of being sent and billed.

  • Per file: 1 MiB by default. Per call: 200 files by default.

  • Retries: network errors, timeouts and HTTP 408, 429, 500, 502, 503, 504, 524 and 529 are retried with exponential backoff (1s, 2s, 4s, capped at 30s), honouring Retry-After as a minimum. If the server asks for more than 60 seconds, retrying stops and the call fails (rate_limited or overloaded, with details.retry_after_seconds). 400, 401, 402, 403, 404, 413 and 422 are never retried, and neither are redirects (they are not followed; set base_url to the final URL) or invalid URLs.

  • Bounds: timeout_ms 1000 to 2147483647, max_retries 0 to 10; other numeric settings must be positive (retention_days may be 0).

Privacy and security

  • The contents of the files you pass, the context and the questions are sent to the configured provider (TypeSafe or OpenRouter). Nothing else is sent. The tool makes no other network calls.

  • Secret-looking files are blocked: .env and .env.* (but not .env.example, .sample, .template, .dist), *.pem, *.key, *.p12, *.pfx, *.jks, *.keystore, *.kdbx, SSH private keys (id_rsa, id_dsa, id_ecdsa, id_ed25519), .npmrc, .pypirc, .netrc, .git-credentials, .envrc, *.tfvars, terraform.tfstate (and .backup), *.ppk, .htpasswd, .docker/config.json, .aws/credentials, and SSH private keys with any suffix (id_rsa_work, but not *.pub). Any .env.* ending in .example, .sample, .template or .dist is allowed. Glob and directory expansion skips secret files; naming one explicitly is an error. Set allow_secret_files to override. This is a name-based safety net, not a scanner: secrets inside ordinary files are not detected.

  • The API key lives in the config file with mode 0600 (the directory is 0700) or in an environment variable. It is never written to a session folder and is masked in all output.

  • Session folders are stored locally and contain full copies of what was sent, so they may hold sensitive code. They live under ~/.jev-answers (mode 0700). Set retention_days to delete old sessions automatically, or run jev-answers prune.

  • base_url must use https, because the key is sent in the Authorization header. Plain http is accepted only for loopback hosts (localhost, 127.0.0.0/8, ::1). Redirects are not followed, so the key cannot be forwarded to another host.

  • Userinfo and query strings in base_url are masked in meta.json and in config / doctor output. Keys containing whitespace or non-ASCII characters are rejected without being echoed.

  • Session files are written with mode 0600 (folders 0700).

Agent skill (optional)

An MCP tool description teaches an agent how to call jev_ask. Some clients defer tool descriptions and show only tool names plus the server's short instructions, and none of them say when to reach for Jev in your workflow. The optional skill adds that: when to use it, how to gather rules and write the diff, how to phrase questions and how to read the probabilities. It is an Agent Skills folder that only loads when relevant.

target

path

read by

claude

~/.claude/skills/jev-answers/SKILL.md

Claude Code, OpenCode

agents

~/.agents/skills/jev-answers/SKILL.md

Codex, OpenCode, other Agent Skills clients

With --project the same folders are used under the current directory (./.claude/skills/..., ./.agents/skills/...), so a team can commit the skill.

jev-answers skill install [--target claude,agents] [--project] [--force]
jev-answers skill status      # not installed / installed vX (current|outdated) / foreign
jev-answers skill uninstall [--target claude,agents] [--project]
jev-answers skill show        # print the rendered SKILL.md

setup offers the skill for each client it detects; non-interactively use setup --yes --skill claude,agents. Without --target, install picks claude if claude is on your PATH and agents if codex or opencode is, or both if none is found. The skill is always copied, never symlinked. A SKILL.md that is not ours (no source: jev-answers in its frontmatter) is never overwritten without --force, and uninstall only removes our own file. OpenCode reads both locations, so installing both shows it the same skill twice, which is harmless. Re-run skill install after upgrading to refresh it; skill status and doctor tell you when it is outdated.

Without the skill, put this in CLAUDE.md / AGENTS.md:

jev-answers skill show > /tmp/jev-skill.md   # then paste the body into your instructions file

Troubleshooting

  • jev-answers doctor shows the effective config and where each value comes from. doctor --live sends one tiny question to verify the key and endpoint.

  • config_error: No API key configured: run jev-answers setup, or set JEV_ANSWERS_API_KEY.

  • config_error mentioning a placeholder: your client passed a literal ${...} as the key. Put the key in the config file instead.

  • context_too_large: the error lists the largest files. Drop or narrow them, or pass a smaller diff.

  • secret_file or binary_file for a file you named: it is blocked on purpose. Copy the relevant part to a plain file, or set allow_secret_files.

  • auth_error (401/403): wrong or revoked key. On OpenRouter use an inference key, not a management key.

  • invalid_input "Nothing to send": every file you listed was skipped (secret, binary, symlink, ...) and there was no context. See manifest.json for the reasons.

  • Server prints nothing in the client: run JEV_ANSWERS_DEBUG=1 jev-answers serve and watch stderr. stdout is reserved for JSON-RPC.

  • Look inside the session folder (jev-answers show latest --path): request.json is exactly what was sent, and error.json has the provider's error body.

Development

npm test        # node --test "test/**/*.test.js"; no network access needed

Tests use a local fake provider and temporary directories; they never touch ~/.jev-answers or your real config. The code is plain ESM with no dependencies. Layout:

bin/jev-answers.js      CLI entry
src/stages/             collect, build, send (independent, each reads the session folder)
src/providers/          typesafe.js, openrouter.js
src/pipeline.js         ask() runs the three stages and never throws
src/mcp/                stdio JSON-RPC server and the tool definition
src/cli/                setup, ask, resend, list, show, prune, doctor, config

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables MCP clients to call TypeSafe's JEV classifier and receive structured, typed judgments with probabilities for binary, choice, and scoring questions.
    1
    7
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables MCP hosts to query Jev's typed decision model—yes/no, choice, and score—with calibrated probabilities, while defaulting to an offline mock and disclosing all egress unless explicitly enabled.
    97
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    An MCP server that exposes eleven typed decision tools—check, choose, score, judge, route, triage, guard, grep, rank, compact, and ask—so agents can make fast, branchable yes/no, option-pick, score, and filtering decisions on text via TypeSafe's Jev model.
    134 npm
    3
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to get machine-readable yes/no, choice, and score decisions from TypeSafe Jev, bridging fast classification to Cursor and other clients.
    MIT