Skip to main content
Glama
thedv91
by thedv91
README.md
# jev-agent-mcp

An MCP server that lets a coding or reasoning agent ask TypeSafe's Jev model for a typed judgment at a decision point. The agent keeps control of the workflow. The server returns answers, probabilities, and confidence as structured data, and it never returns generated prose.

It wraps `POST https://api.typesafe.ai/v1/systemone` through the official `@typesafe-ai/sdk`. See the [TypeSafe docs](https://docs.typesafe.ai/) for the model itself.

## Tools

| Tool | Use it to | Built on |
| --- | --- | --- |
| `judge` | Ask any mix of Choice, Noul, and Score questions about one state, in a single request | the three primitives |
| `rank_candidates` | Rank 2 to 30 candidates on several weighted dimensions | one Score per dimension, one request per candidate, weights applied in code |
| `verify_claim` | Check claims against supplied evidence | a string match for quotes, then one Choice per claim (`supports` / `contradicts` / `says_nothing`) |
| `review_changes` | Review a diff the agent supplies, one unified patch per changed file, in any language | the staged jev-review pipeline: five Noul screens per file, then Choice and Score follow-ups on the strongest signals |
| `review_files` | Review source files the agent supplies, as they stand, for issues that already exist | the same pipeline, asking about the source as it stands and not about a patch |

Each tool description tells the calling agent when to use the tool, what state to pass, and how to read the numbers. Every tool declares an `outputSchema` and returns `structuredContent`, with the same JSON repeated as text for clients that ignore structured results.

## Setup

Requires Node.js 20 or newer. The server is published on npm as [`jev-agent-mcp`](https://www.npmjs.com/package/jev-agent-mcp), and an MCP client can start it with `npx -y jev-agent-mcp` without a separate install step.

To run it from a checkout instead, build it once and point the client at `dist/index.js`:

```bash
npm install
```

```bash
npm run build
```

### API key

The server reads the key from the `TYPESAFE_API_KEY` environment variable and from nowhere else. Create a key in the [TypeSafe console](https://console.typesafe.ai/). The server does not log the key or write it to disk. Without a key the server still starts and lists its tools, and each tool call returns an error saying the key is missing.

The SDK also honors `TYPESAFE_DEFAULT_MODEL` (default `jev-latest`), `TYPESAFE_BASE_URL`, and `TYPESAFE_LOG_LEVEL`. All SDK logging goes to stderr because stdout carries the MCP protocol. `TYPESAFE_LOG_LEVEL=debug` logs request bodies, which include the state you send.

## Register it in Claude Code

Export `TYPESAFE_API_KEY` in the shell that launches Claude Code, then add the server to a `.mcp.json` file in the project root. Claude Code expands `${VAR}` in `.mcp.json` when it starts the server, so the file holds the variable name and the key itself stays out of it:

```json
{
  "mcpServers": {
    "jev": {
      "command": "npx",
      "args": ["-y", "jev-agent-mcp"],
      "env": { "TYPESAFE_API_KEY": "${TYPESAFE_API_KEY}" }
    }
  }
}
```

The CLI writes the same file when you use project scope. The single quotes stop the shell from expanding the variable, so `.mcp.json` receives the name and not the value:

```bash
claude mcp add --scope project --transport stdio jev -e 'TYPESAFE_API_KEY=${TYPESAFE_API_KEY}' -- npx -y jev-agent-mcp
```

Claude Code asks you to approve a project-scoped server the first time you start it in that directory.

To make the server available in every project, use user scope. Claude Code 2.1.270 expands `${VAR}` there as well (checked with a probe server that wrote its environment to a file), so `~/.claude.json` stores the variable name and not the key:

```bash
claude mcp add --scope user --transport stdio jev -e 'TYPESAFE_API_KEY=${TYPESAFE_API_KEY}' -- npx -y jev-agent-mcp
```

To run a checkout, replace `npx -y jev-agent-mcp` with `node /absolute/path/to/jev-mcp/dist/index.js` in any of the commands above, or set `"command": "node"` and `"args": ["/absolute/path/to/jev-mcp/dist/index.js"]` in `.mcp.json`.

The variable has to exist in the environment Claude Code starts from. An `export` typed into one terminal does not reach the desktop app, which reads your shell profile.

Run `/mcp` inside Claude Code to confirm that `jev` is connected and shows five tools.

### Agent skill

[`skills/jev-mcp/SKILL.md`](skills/jev-mcp/SKILL.md) tells the agent which tool fits which situation, how to chain them (review a diff, confirm the findings, verify the completion claims), and what to do with a flagged answer. The tool descriptions cover each tool's arguments and output on their own, so the server works without the skill. The npm package includes it under `skills/`, but the simplest copy comes from GitHub. Put it in `.claude/skills/` to use it in one project, or in `~/.claude/skills/` to use it everywhere:

```bash
mkdir -p .claude/skills/jev-mcp
```

```bash
curl -fsSL -o .claude/skills/jev-mcp/SKILL.md https://raw.githubusercontent.com/thedv91/jev-mcp/main/skills/jev-mcp/SKILL.md
```

## Reading the results

| Field | Meaning |
| --- | --- |
| `choice`, `probabilities` | The top option and the distribution over every option you supplied |
| `noul` | The probability that the answer is yes. Near 0.5 means the model cannot tell. It does not mean "medium" or "partly". Ask a Score question if you want a degree. |
| `score`, `max_level` | The probability-weighted mean level, from 0 to `max_level`. It can land between levels, and different distributions can give the same score. |
| `confidence` | How concentrated a Choice or Score distribution is, from 0 to 1. It describes the model's answer. It does not guarantee the answer is right. Noul answers have no confidence. |
| `certainty` | `high`, `medium`, or `low`, from applying the thresholds to `confidence` |
| `verdict` (Noul) | `yes`, `no`, or `uncertain`, from applying the Noul band |
| `needs_escalation` | The ids of every answer that came back `low` or `uncertain` |
| `top_margin`, `top_tied` (`rank_candidates`) | The composite gap between first and second place, and whether it is at or below `tie_margin` |

### Thresholds

| Threshold | Default | Effect |
| --- | --- | --- |
| `confidence_high` | 0.8 | Choice or Score confidence at or above this is `high` |
| `confidence_low` | 0.5 | Confidence below this is `low` and is flagged |
| `noul_band` | `[0.35, 0.65]` | A Noul inside the band is `uncertain` and is flagged |

The defaults come from the examples in TypeSafe's [confidence page](https://docs.typesafe.ai/confidence) and its citation-check cookbook. They have not been calibrated on your workload. Every tool accepts a `thresholds` object to override them for one call, so a risky action can demand more, for example `{ "confidence_high": 0.9, "noul_band": [0.1, 0.9] }`.

The server flags uncertain answers and leaves the response to the agent. What escalation means depends on the agent: it can gather the missing evidence and ask again, reason the point through itself, or ask the user. `medium` certainty is not flagged. Treat it as a reason to verify first when the action is hard to undo.

## Example calls

The arguments below are what an MCP client sends. `scripts/smoke.ts` holds the same cases plus ambiguous ones for each tool.

### judge

Guard a consequential tool call with three independent questions in one request:

```json
{
  "state": {
    "proposed_tool_call": { "tool": "issue_customer_refund", "order_id": "ord_7429", "amount_usd": 680 },
    "side_effects": ["Moves funds", "Changes the order payment state"],
    "safeguards": ["Customer identity and duplicate charge verified"],
    "policy": "Refunds above USD 500 require human approval."
  },
  "questions": {
    "action": {
      "type": "choice",
      "instructions": "What should happen to `proposed_tool_call` under `policy`?",
      "criteria": {
        "allow": "Execute the call now without asking anyone",
        "confirm": "A human must approve before the call executes",
        "deny": "The call must not execute at all"
      }
    },
    "policy_requires_approval": {
      "type": "noul",
      "instructions": "Does `policy` require human approval for `proposed_tool_call`?"
    },
    "risk": {
      "type": "score",
      "instructions": "How much damage would `proposed_tool_call` do if it turned out to be wrong?",
      "criteria": [
        "Nothing changes outside the agent's own workspace",
        "A change that is easy to undo completely",
        "Money or customer-visible state changes and can only partly be undone",
        "Irreversible loss of money or data"
      ]
    }
  }
}
```

Result from `jev-1.13.0` (the Score `legend` repeats the four levels and is omitted here):

```json
{
  "answers": {
    "action": {
      "type": "choice",
      "choice": "confirm",
      "probabilities": { "confirm": 1, "deny": 0, "allow": 0 },
      "confidence": 1,
      "certainty": "high"
    },
    "policy_requires_approval": { "type": "noul", "noul": 0.98, "verdict": "yes" },
    "risk": {
      "type": "score",
      "score": 2.09,
      "max_level": 3,
      "probabilities": { "0": 0, "1": 0.01, "2": 0.89, "3": 0.1 },
      "confidence": 0.9,
      "certainty": "high"
    }
  },
  "needs_escalation": [],
  "model": "jev-1.13.0",
  "usage": { "input_tokens": 558, "output_tokens": 72 }
}
```

The stop-or-continue case in `scripts/smoke.ts` ("Make the dashboard faster", an index added, nothing measured) came back decisive: `task_complete` was 0.05 (`no`) and `next_step` was `measure` with confidence 1. Thin evidence gave a confident "not done", not an uncertain answer.

A request with unclear scope did get flagged. The state held the message "Can you clean up the old branches?" and the facts that 4 of 12 branches are merged and 3 of the 8 unmerged ones are stale:

```json
{
  "answers": {
    "wants_unmerged_deleted": { "type": "noul", "noul": 0.55, "verdict": "uncertain" },
    "scope": {
      "type": "choice",
      "choice": "merged_and_stale",
      "probabilities": { "merged_only": 0.32, "all_but_main": 0.07, "merged_and_stale": 0.61 },
      "confidence": 0.41,
      "certainty": "low"
    }
  },
  "needs_escalation": ["wants_unmerged_deleted", "scope"],
  "model": "jev-1.13.0",
  "usage": { "input_tokens": 442, "output_tokens": 69 }
}
```

The Noul of 0.55 says the model cannot tell whether the user wants unmerged branches deleted. It does not say the user wants about half of them deleted. Deleting branches is hard to undo, so the right move for an agent here is to ask the user.

### rank_candidates

Route a task to a model. Each candidate is graded in its own request and sees only `context` and its own `content`, which the questions refer to as `candidate`. Order the levels so that a higher level is always better, including for cost.

```json
{
  "context": {
    "task": "Review a complex customer dispute: 100k tokens of history, needs tool use, a wrong answer is costly."
  },
  "candidates": [
    { "id": "fast-model", "content": "Fast general model. 32k context. No tool use. Very low cost." },
    { "id": "reasoning-model", "content": "Strong multi-step reasoning. 200k context. Tool use. High cost." },
    { "id": "mid-model", "content": "Solid general model. 128k context. Tool use. Moderate cost." }
  ],
  "dimensions": [
    {
      "id": "capability_fit",
      "instructions": "Can `candidate` handle `context.task`, given its context size, tool use, and reasoning needs?",
      "levels": [
        "Cannot do the task: a hard requirement such as context size or tool use is missing",
        "Meets the hard requirements but reasoning quality is a concern for this task",
        "Meets every requirement comfortably"
      ],
      "weight": 3
    },
    {
      "id": "cost",
      "instructions": "How expensive is `candidate` to run?",
      "levels": ["High cost", "Moderate cost", "Low or very low cost"],
      "weight": 1
    }
  ]
}
```

`composite` is the weighted mean of each dimension's `score / max_level`. The raw scores come back too, so the agent can change the weights without another call. `top_margin` is the gap between first and second place. `top_tied` is `true` when that gap is at or below `tie_margin` (default 0.05, a starting value that does not come from the TypeSafe docs), and the order of the top two then means nothing.

Result from `jev-1.13.0`:

```json
{
  "ranking": [
    {
      "id": "reasoning-model",
      "composite": 0.74,
      "dimensions": {
        "capability_fit": { "score": 1.97, "normalized": 0.985, "confidence": 0.96, "certainty": "high" },
        "cost": { "score": 0.01, "normalized": 0.005, "confidence": 0.98, "certainty": "high" }
      }
    },
    {
      "id": "mid-model",
      "composite": 0.675,
      "dimensions": {
        "capability_fit": { "score": 1.47, "normalized": 0.735, "confidence": 0.25, "certainty": "low" },
        "cost": { "score": 0.99, "normalized": 0.495, "confidence": 0.98, "certainty": "high" }
      }
    },
    {
      "id": "fast-model",
      "composite": 0.2488,
      "dimensions": {
        "capability_fit": { "score": 0, "normalized": 0, "confidence": 1, "certainty": "high" },
        "cost": { "score": 1.99, "normalized": 0.995, "confidence": 0.99, "certainty": "high" }
      }
    }
  ],
  "top_margin": 0.065,
  "top_tied": false,
  "needs_escalation": [{ "candidate": "mid-model", "dimension": "capability_fit" }],
  "model": "jev-1.13.0",
  "usage": { "input_tokens": 1358, "output_tokens": 99 }
}
```

The model could not place `mid-model` between "reasoning quality is a concern" and "meets every requirement" (confidence 0.25), so that pair is flagged. The runner-up's composite rests on that uncertain grade, and the 0.065 margin is close to the tie threshold, so an agent should check `mid-model`'s capabilities before treating the order as settled.

In the ambiguous case, two vague documents both landed on "Same topic but does not answer the query" with confidence 0.97 and 0.99. Their composites were 0.34 and 0.33, so `top_margin` was 0.01 and `top_tied` was `true`. `needs_escalation` stayed empty, because the model was sure that both documents are weak. The signals to read there are `top_tied` and the low composite of the winner.

### verify_claim

Check completion claims against real test output before reporting success:

```json
{
  "evidence": "$ npm test\n PASS  src/cart.test.ts (12 tests)\n PASS  src/checkout.test.ts (8 tests)\n FAIL  src/refund.test.ts\n   x refunds above the limit require approval (expected \"pending_approval\", received \"refunded\")\nTests: 1 failed, 27 passed, 28 total",
  "claims": [
    { "id": "cart_passes", "claim": "The cart tests pass." },
    { "id": "all_green", "claim": "All tests pass." },
    { "id": "quoted", "claim": "The refund suite passes.", "quote": "PASS  src/refund.test.ts" }
  ]
}
```

Result from `jev-1.13.0`:

```json
{
  "verdicts": [
    {
      "id": "cart_passes",
      "verdict": "verified",
      "checked_by": "model",
      "probabilities": { "contradicts": 0, "says_nothing": 0, "supports": 1 },
      "confidence": 1,
      "certainty": "high"
    },
    {
      "id": "all_green",
      "verdict": "contradicted",
      "checked_by": "model",
      "probabilities": { "contradicts": 1, "says_nothing": 0, "supports": 0 },
      "confidence": 1,
      "certainty": "high"
    },
    {
      "id": "quoted",
      "verdict": "fabricated",
      "checked_by": "string_match",
      "probabilities": null,
      "confidence": null,
      "certainty": null
    }
  ],
  "needs_escalation": [],
  "model": "jev-1.13.0",
  "usage": { "input_tokens": 584, "output_tokens": 90 }
}
```

The ambiguous case shows why a verdict has to be read together with its certainty. Against the same test output, the claim "The refund failure is caused by a missing approval-limit check in the refund service" came back `verified`, but with probabilities of 0.62 supports, 0.36 says_nothing, and 0.02 contradicts. Confidence was 0.42, so certainty was `low` and the claim appeared in `needs_escalation`. "The change did not slow down checkout" came back `unsupported` with confidence 0.96.

A claim with a `quote` that does not appear in the evidence comes back `fabricated` from an exact string match, without a model call, so its `probabilities`, `confidence`, and `certainty` are `null`. The match normalizes whitespace and curly quotes and nothing else, so a reworded or truncated quote also counts as fabricated. A verdict covers only the evidence passed in.

## Using it for code review

The server reviews code in two ways. `review_changes` and `review_files` run the whole staged review and return one report. With `judge`, `rank_candidates`, and `verify_claim` the agent runs each stage itself. The packaged tools give a ranked list of places to look. The separate tools are for reviews that need questions other than the fixed ones.

### The packaged review

`review_changes` reads nothing from disk and runs no git. The agent collects the diff and passes it in, so the agent and the tool always judge the same change:

```json
{
  "files": [
    { "path": "src/routes/invoices.ts", "patch": "diff --git a/src/routes/invoices.ts b/src/routes/invoices.ts\n--- a/src/routes/invoices.ts\n+++ b/src/routes/invoices.ts\n@@ -12,11 +12,8 @@ ..." }
  ],
  "test_files": []
}
```

Each `patch` is the unified diff of one file with its `@@` hunk headers, exactly as git printed it, because findings take their line numbers from those headers. The agent decides the scope first: which base to diff against, and whether uncommitted and untracked work is included. It leaves out generated files, lock files, and anything that must not leave the machine, since every patch goes to the TypeSafe API. `test_files` are not reviewed. The model receives them as evidence for the test-gap screen, so leaving them out makes that screen fire on every behavioral change.

`review_files` works the same way on whole files. It takes `files` as `{ "path", "content" }` entries holding the complete, unmodified text, plus optional `test_files`, and asks whether an issue exists in the code as it stands. Neither tool touches the disk or runs a command, and neither is tied to one language. The agent picks the files at the center of the flow it cares about. A whole repository does not fit, because every file passes through the agent's context first.

Each call runs these stages, with at most three requests in flight:

| Stage | Question | Limit |
| --- | --- | --- |
| Screen | Five Nouls per file: correctness, security, reliability, compatibility, test gap | every file; a patch over 40,000 characters is screened in slices and the highest probability per dimension is kept |
| Profile | A Choice for the kind of change or file role, and a Score for review priority | the 5 files with the strongest signals |
| Locate | A Choice over diff hunks or 80-line source regions, with a `noMatch` option | the 8 strongest signals at or above 0.7; a selection below 0.55 confidence is dropped |
| Classify | A Choice over the mechanisms of that dimension, with a `noIssue` option | `noIssue` drops the finding |
| Score | Severity on a 0 to 3 rubric | none |
| Route | A Choice over reviewer roles | only at severity 1.5 or above |

Those limits are constants in `src/review-core/domain/config.ts`. Both tools accept the same `thresholds` object as the other tools, which sets how the report is flagged.

### Reading the report

`src/review-report.ts` reshapes the pipeline's report for an agent:

| Field | Meaning |
| --- | --- |
| `findings` | Sorted by severity. Each carries the three confidences it rests on (`location_confidence`, `mechanism_confidence`, `severity_confidence`), a `certainty` taken from the lowest of them, and `weakest_judgment`, which names it |
| `needs_escalation` | The findings with `low` certainty |
| `unresolved_signals` | Every screen at or above 0.7 that produced no finding. `no_evidence_located` means it was followed and then dropped at the Locate or Classify stage. `not_followed` means it fell past the cap of 8. `request_failed` means the follow-up request itself failed |
| `screening`, `quiet_files` | Per file, the probability of each dimension that is not a clear no (at or above the lower bound of `noul_band`). Files where every dimension was a clear no are only counted |
| `profiles` | Category and review priority for the 5 strongest files, with a `certainty` |
| `failed_requests` | Every request that still failed after the SDK's retries, with its `file`, `stage` (`screen`, `profile`, `locate`), and error message. A file that failed at `screen` was not judged at all and is absent from `screening` and `quiet_files` |
| `workflow`, `config` | Stage counts and the pipeline constants |

The pipeline's `action` field (`request_changes` at severity 2 or above) is left out. That is jev-review's merge policy, and here the agent decides what a finding warrants.

A `review_changes` call on three patches (a removed ownership check, a dropped `Math.min` in a pagination helper, and a typo fix in a string constant) took 4.0 seconds on `jev-1.13.0`. All five signals at or above 0.7 became findings. The top one:

```json
{
  "file": "src/routes/invoices.ts",
  "line": 12,
  "dimension": "security",
  "probability": 0.97,
  "location_confidence": 1,
  "mechanism": "authorization",
  "mechanism_confidence": 1,
  "severity": 2.84,
  "severity_confidence": 0.84,
  "owner": "security",
  "owner_confidence": 1,
  "certainty": "high",
  "weakest_judgment": "severity"
}
```

The typo fix screened as a clear no on every dimension, so it appears only in `quiet_files: 1`. Its profile came back `routine` at `low` certainty (category confidence 0.5).

A `review_files` call with two of this repository's own files, `src/review-core/domain/patch.ts` and `src/verify-claim.ts`, took about 3 seconds. Neither has a test, and the test-gap screen fired on both (0.94 and 0.96). One became a finding at `medium` certainty, with `location` as its weakest judgment (confidence 0.61). The other was followed and dropped, and the report says so:

```json
{
  "unresolved_signals": [
    { "file": "src/verify-claim.ts", "dimension": "testGap", "probability": 0.96, "reason": "no_evidence_located" }
  ],
  "needs_escalation": [],
  "failed_requests": []
}
```

A 0.96 screen with no finding is a place the agent still has to read for itself.

`line` is the first line of the selected hunk or region, which is usually not the line of the defect. A finding tells the agent where to read, and the agent still has to open the file and confirm it. Progress lines go to stderr because stdout carries the MCP protocol.

### Where the review code comes from

`src/review-core/` is a copy of the `domain` and `review` layers of [jev-review](https://github.com/devagrawal09/jev-review). jev-review runs its TypeScript directly on Node.js 24 and this server compiles with `tsc`, so the code is copied and not imported. The copy differs from the original in four places. Relative imports end in `.js`. The judgment modules call the shared `typesafeClient()`, which is created on first use, so the server still starts without a key. `isReviewReport` is left out because only the jev-review dashboard uses it. `runReview` in `review/workflow.ts` no longer aborts on the first failed request: it records the failure in the report's `failures` list and carries on, and it raises an error only when every file fails to screen, which points at a bad key or an unreachable API and not at a partial result. Both review tools run the copied stages over what the agent passes in, so the parts of jev-review that find files are not copied: the `adapters` layer, `review/changes.ts`, `review/codebase.ts`, and the `SOURCE_FILE`, `TEST_FILE`, and `patchForNewFile` helpers that only they used. The dashboard and the report store were not copied, so these tools do not write `reviews/latest.json`. When jev-review changes a prompt or a threshold, copy the file again and reapply those four edits.

### Running the stages yourself

With `judge` the agent also owns the questions: it picks the files, cuts a large patch into pieces that fit one request, and writes each screen. The server only judges what it is sent. The stages are the ones `review_changes` runs.

| Review step | Tool |
| --- | --- |
| Screen a file's patch: one Noul per concern (correctness, security, reliability, compatibility, test gap), all in one call | `judge` |
| Pick the hunk that best supports a concern: a Choice over hunk ids plus a `no_match` option | `judge` |
| Classify the mechanism, then score severity | `judge`, as follow-up calls, because each needs the previous answer |
| Order files by how closely they need review | `rank_candidates` |
| Check a finding the agent wrote against the actual diff before reporting it | `verify_claim` |

`instructions`, Choice option descriptions, Noul `true`/`false` criteria, and Score levels accept a string, an object, or an array, as the TypeSafe API does. An object helps when a review question needs scoping:

```json
{
  "type": "noul",
  "instructions": {
    "question": "Does `file.patch` introduce or weaken a security boundary?",
    "focus": "Authorization, injection, secret exposure, unsafe defaults"
  },
  "criteria": {
    "true": { "what": "The patch opens a concrete path around a security control", "examples": ["An ownership check is removed"] },
    "false": { "what": "No security boundary is weakened", "not_for": "Code that merely uses security-related names" }
  }
}
```

When the agent quotes a line in a finding, pass it as `quote` to `verify_claim`. A line that is not in the patch character for character, including its `+`, `-`, or space prefix, comes back `fabricated`.

Results from `jev-1.13.0` on the three review cases in `scripts/smoke.ts`, which ask three Noul screens and one severity Score per patch:

| Patch | correctness | security | compatibility | severity | Flagged |
| --- | --- | --- | --- | --- | --- |
| Removes the account ownership check from `GET /invoices/:id` | 0.84 `yes` | 0.97 `yes` | 0.90 `yes` | 2.98 of 3, confidence 0.98 | none |
| Drops `Math.min(start + size, items.length)` from a pagination helper | 0.28 `no` | 0.05 `no` | 0.50 `uncertain` | 0.67 of 3, confidence 0.33 (`low`) | `compatibility`, `severity` |

The pagination patch is harmless only because `Array.prototype.slice` clamps its end index, which the patch does not show. The model leaned toward "no bug" and flagged the two answers it could not settle, so the agent knows to open the file before it writes a finding. On the first patch, the 0.90 for compatibility is debatable: no caller should depend on receiving a 403. Read each screen as a prompt to look.

`verify_claim` then checked three findings against the first patch. "Any authenticated user can read another account's invoice", quoted with the removed line, came back `verified` at confidence 0.99. "This patch introduces a SQL injection through req.params.id" came back `unsupported` (0.91 says_nothing, 0.09 contradicts, confidence 0.86). A finding that quoted the 404 line with a `-` prefix came back `fabricated`, because the patch carries that line as unchanged context.

Jev judges the patch text it is given. It does not compile the code, run tests, or follow calls into files it was not sent, so treat a fired screen as a place to look and not as proof of a defect.

## Smoke test

Run it from a checkout after `npm run build`:

```bash
npm run smoke
```

The script starts `dist/index.js` over stdio, lists the tools, and calls each one against the real API with a representative case and at least one ambiguous case. It needs `TYPESAFE_API_KEY` in the environment and Node.js 22.18 or newer, because it runs the TypeScript file directly.

## Limits

Answers are not bit-for-bit repeatable. Two runs of the same smoke cases on `jev-1.13.0` gave the same choices, verdicts, and flags, while individual numbers moved by a few hundredths (a risk score of 2.11 then 2.09, a confidence of 0.33 then 0.25). Leave room for that when you set a threshold close to a value you have observed.

One request carries the state and all its questions in a budget of roughly 32,000 tokens. `rank_candidates` sends one request per candidate in parallel, so 30 candidates means 30 requests. The SDK retries 408, 429, and 5xx responses with backoff, and each attempt times out after 10 seconds.

`review_changes` and `review_files` send at least one request per file, three at a time. Three files took about 4 seconds from call to report. Both accept at most 100 files per call, and a call that large can outlast an MCP client's tool timeout, so review in batches.

## License

[MIT](LICENSE)

TDQS

A4.6/5.0

Scored across 5 tools

Disambiguation4/5

The tools are generally distinct: review_files vs review_changes differ by input type (files vs diff), and judge/rank_candidates/verify_claim have clear use cases. However, review_files and review_changes could be confused by an agent scanning quickly, and judge with a choice question overlaps with rank_candidates for single-dimension ranking.

Naming Consistency4/5

All tool names follow a clear verb_noun pattern (review_files, review_changes, verify_claim, rank_candidates, judge). The pattern is consistent, though 'judge' is a single verb without a noun, which is a minor deviation from the otherwise consistent scheme.

Tool Count5/5

With 5 tools, the server is tightly scoped around the Jev review workflow: two review modes (files/changes), a verification tool, and two decision-support tools (judge, rank_candidates). Each tool has a distinct role and no redundancy; the count is ideal for this purpose.

Completeness4/5

The tool surface covers the core review lifecycle: review current code, review changes, verify claims, and make decisions. A minor gap is the lack of a tool to aggregate or compare multiple review results, but agents can work around that by calling review tools separately.

Maintenance

ActivityMaintained
ResponsivenessNo issues