Skip to main content
Glama
README.md
# mcp_jev

Local MCP server that runs **TypeSafe Jev** (System One) **packs** — typed **Choice / Noul / Score** judgments, not chat. When no pack fits, **`run_questions`** is the typed custom path (still System One, still not an essay).

Anyone runs it **on their own PC** with **their own** TypeSafe API key. This repo does not host Jev, proxy your key, or invent a free-form `ask_jev` tool.

**Configure the TypeSafe key once** during MCP install (`~/.mcp_jev/.env`). Any agent that attaches this MCP reuses it. Do **not** paste the key into host `mcp.json` / TOML.

**Docs (source of truth):** this repo — [https://github.com/pedroknigge/mcp_jev](https://github.com/pedroknigge/mcp_jev) · TypeSafe API: [docs.typesafe.ai](https://docs.typesafe.ai) · [llms.txt](https://docs.typesafe.ai/llms.txt)

Skill: [skills/mcp_jev/SKILL.md](skills/mcp_jev/SKILL.md) · Custom judgments: [docs/CUSTOM_JUDGMENTS.md](docs/CUSTOM_JUDGMENTS.md) · Blind dogfood: [docs/DOGFOOD.md](docs/DOGFOOD.md) · Product backlog: [docs/BACKLOG.md](docs/BACKLOG.md) · Host deep dive: [docs/INSTALL_AGENTS.md](docs/INSTALL_AGENTS.md) · Releases: [docs/RELEASES.md](docs/RELEASES.md)

- [Why mcp_jev](#why-mcp_jev)
- [Install (happy path)](#install-happy-path)
- [First-run](#first-run)
- [Verify](#verify)
- [Recipes](#recipes)
- [Blind-test protocol](#blind-test-protocol)
- [Update](#update)
- [Skill stays in sync](#skill-stays-in-sync)
- [Say this to your agent](#say-this-to-your-agent)
- [Install for agents & IDEs](#install-for-agents--ides)
- [Tools](#tools-closed-catalog)
- [Troubleshooting](#troubleshooting)

## Why mcp_jev

mcp_jev is the **judgment layer** for agents and harnesses. Jev is fast at **typed decisions** (one Choice, a few Nouls, a Score) over a short named state. It is not a chat model and not an executor.

Use it when the host already has structured state and needs a closed-catalog call:

| Layer | Owns |
| --- | --- |
| **Harness / agent** | Observation, tools, typed strings, merges, comments, stop rules |
| **mcp_jev** | Versioned packs or typed custom questions → `systemOne` → Choice / Noul / Score |
| **Host config** | Keyless `command` = `~/.mcp_jev/bin/mcp_jev` |

Closed tools only: `list_packs` / `describe_pack` / `run_pack` / `run_questions` / `ping`. Key-once in `~/.mcp_jev`. Patterns here follow common ecosystem loops (computer-use, review pipelines, model routing) reimplemented as packs — no copied code, no third-party trademarks. **Packs are shortcuts:** if one fits, `run_pack`. If none fits, build closed state + typed Choice / Noul / Score and call `run_questions`.

| | Jev (System One) | Chat LLM |
| --- | --- | --- |
| Input | State + closed questions | Prompt / conversation |
| Output | `choice` / `noul` / `score` + probabilities | Prose you must parse |
| Control | Your code composes answers | The model narrates a plan |
| This MCP | Runs a **pack** or typed **`run_questions`** | Out of scope |

JavaScript SDK (what this server calls):

```ts
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient(); // reads TYPESAFE_API_KEY
await client.systemOne({
  state: { /* pack state */ },
  questions: {
    intent: choice("…", { faq: "…", action: "…" }),
    jailbreak: noul("…"),
    urgency: score("…", ["routine", "soon", "emergency"]),
  },
  model: "jev-latest",
});
```

Python exists (`typesafe-sdk`, `client.system_one`) if you are writing app code. This server is TypeScript / Node 20+.

## Install (happy path)

**Prerequisites:** Node.js **20+**, git, a TypeSafe key from [console.typesafe.ai](https://console.typesafe.ai).

```bash
# macOS / Linux — checkout ~/mcp_jev; key + wrapper in ~/.mcp_jev (override: MCP_JEV_HOME)
git clone https://github.com/pedroknigge/mcp_jev.git ~/mcp_jev
~/mcp_jev/scripts/install.sh
```

```powershell
# Windows
git clone https://github.com/pedroknigge/mcp_jev.git $HOME\mcp_jev
$HOME\mcp_jev\scripts\install.ps1
```

Already cloned? Run `./scripts/install.sh` from that checkout. `MCP_JEV_HOME` overrides the **config dir** (`~/.mcp_jev`). Optional `MCP_JEV_CHECKOUT` overrides the git checkout.

The script: clones or pulls → `npm install && npm run build` → writes `~/.mcp_jev/bin/mcp_jev` → stores `TYPESAFE_API_KEY` **once** in `~/.mcp_jev/.env` (chmod 600) → prints **keyless** snippets for Cursor, Claude Desktop, Claude Code, Codex, Grok, and Antigravity.

Non-interactive and no key → writes `~/.mcp_jev/NOT_READY`, prints a loud next step, **exits non-zero**. `mcp_jev doctor` fails until `mcp_jev config set-key`. Do not treat a missing key as a successful install.

Optional host write: `MCP_JEV_WRITE_HOSTS=all ./scripts/install.sh` (or `cursor,claude_desktop,claude_code,codex,grok,antigravity`). Merge is additive; existing servers stay.

Paste this **keyless** block (the script prints your real wrapper path):

```json
{
  "mcpServers": {
    "mcp_jev": {
      "command": "/Users/YOU/.mcp_jev/bin/mcp_jev"
    }
  }
}
```

Then **`mcp_jev doctor`** → restart the host → `ping` → `list_packs`.

Non-interactive key: `TYPESAFE_API_KEY=… ./scripts/install.sh`  
Later: `node ~/mcp_jev/dist/index.js config set-key`

### Alternatives

| Style | When |
| --- | --- |
| **Install script (prefer this)** | Humans and agents. Key once, keyless host config. |
| Git clone + manual build | `npm install && npm run build && npm test` in `REPO_PATH`. Then `node dist/index.js config set-key`. |
| `npx -y github:pedroknigge/mcp_jev` | No clone. Slow first start. Still run `config set-key` so hosts stay keyless. |
| npm registry (`npx mcp_jev`) | **Not used.** This project is not published to npm. Install from GitHub; update with `scripts/update.sh`. See [docs/RELEASES.md](docs/RELEASES.md). |

`npm test` mocks TypeSafe and must pass without a live key.

## First-run

```bash
mcp_jev doctor          # checkout, dist, wrapper, api_key_set (boolean), host registration
# or: node ~/mcp_jev/dist/index.js doctor --json
mcp_jev smoke           # stdio JSON-RPC: initialize, tools/list, ping, list_packs
~/mcp_jev/scripts/verify-mcp.sh   # same smoke as a script
npm run smoke:packs               # ping → list_packs → describe+run every pack (mocked TypeSafe)
```

On the host, after restart:

1. **`ping`** — `ok`, `packs` ≥ 12, `api_key_set` boolean. Never invent `run_pack` / `run_questions` answers if the key is missing.
2. **`list_packs`** — pick an `id`.
3. If a pack fits: **`describe_pack`** then **`run_pack`**. If none fits: **`run_questions`** (closed state + typed Choice / Noul / Score).

CLI: `mcp_jev doctor` · `mcp_jev smoke` · `mcp_jev scan <path>` · `mcp_jev hosts print` · `mcp_jev hosts write all` · `mcp_jev config set-key` · `mcp_jev config status`.

## Verify

```bash
npm test                         # unit tests; mocked TypeSafe; no live key
./scripts/verify-mcp.sh          # stdio JSON-RPC: initialize, tools/list, ping, list_packs
npm run smoke:packs              # ping → list_packs → describe_pack + run_pack(example_state) for every pack
npm run smoke:packs -- --live    # real TypeSafe from TYPESAFE_API_KEY or ~/.mcp_jev/.env
```

Default `smoke:packs` injects the same mocked `systemOne` pattern as `npm test` (no network). It prints a table (`pack`, `ok`, `ms`, `error`) and exits non-zero if any pack fails. `--live` is skipped in CI unless `TYPESAFE_API_KEY` is set and `SMOKE_LIVE=1`.

## Recipes

### `run_questions` (first-class; no exact pack)

**Packs are shortcuts.** Same `systemOne` path as `run_pack`. Not “write me a review”. After a pattern repeats 2–3 times, upstream a named pack.

Blind dogfood: invent typed questions **before** opening the pack list; use a pack only if it matches exactly — [docs/DOGFOOD.md](docs/DOGFOOD.md). Model-lane cascades with **custom** option ids → [model route via `run_questions`](docs/CUSTOM_JUDGMENTS.md#recipe-model-route-via-run_questions) (do not stretch `model_router`).

Example (public-API / changelog break — no pack): state `{ path, change_summary, symbols:[{id,kind,note}] }` + Noul `is_breaking_for_callers` + Score `doc_debt` + Choice `hottest_symbol` over symbol ids plus `none`. Full recipe: [docs/CUSTOM_JUDGMENTS.md](docs/CUSTOM_JUDGMENTS.md).

#### i18n via `run_questions` (equal to `run_pack` `i18n_copy`)

Closed `candidates[]`, same Login / Submit / Error loading demo as the pack. Extra Noul `needs_locale_split` means **`i18n_copy` is not an exact match** — stay on `run_questions`. Runnable: `npm run smoke:blind-i18n` (mocked) or `--live`.

```json
{
  "state": {
    "path": "src/components/LoginForm.tsx",
    "language": "tsx",
    "framework_i18n": "next-intl",
    "uses_i18n_api": false,
    "locale_files_present": true,
    "candidates": [
      { "id": "login_heading", "text": "Login", "kind": "jsx_text", "line": 12 },
      { "id": "submit_btn", "text": "Submit", "kind": "jsx_attr", "line": 40 },
      { "id": "load_error", "text": "Error loading", "kind": "toast", "line": 55 }
    ]
  },
  "questions": [
    {
      "id": "has_user_facing_hardcoded_copy",
      "type": "noul",
      "instructions": "Given `path`, `candidates`, and `uses_i18n_api`, does this file contain user-facing hardcoded copy that is not already going through an i18n API?",
      "criteria": {
        "true": "At least one candidate is user-visible copy that would ship in one language.",
        "false": "Candidates are identifiers, logs, tests, or already passed through t() / useTranslations."
      }
    },
    {
      "id": "should_migrate_to_i18n",
      "type": "noul",
      "instructions": "Should the caller extract the user-facing strings in `candidates` into the project's i18n layer before a multi-locale ship?",
      "criteria": {
        "true": "Hardcoded user-facing copy should move to locale files / t() before shipping more locales.",
        "false": "No migration needed: already i18n, copy is dev-only, or a multi-locale ship is not indicated."
      }
    },
    {
      "id": "i18n_debt",
      "type": "score",
      "instructions": "How much i18n debt does this file add to a multi-locale ship, given `candidates` and `uses_i18n_api`?",
      "criteria": [
        "Clean: no user-facing hardcoded copy.",
        "Local leftover: a few strings, easy extract.",
        "Cross-cutting: many strings or mixed buckets; needs a focused pass.",
        "Blocking for a multi-locale ship: user-facing copy would ship untranslated."
      ]
    },
    {
      "id": "hottest_candidate",
      "type": "choice",
      "instructions": "Which `candidates[].id` is the hottest string to extract first? Options are only those ids plus `none`.",
      "criteria": {
        "login_heading": "kind=jsx_text; text=Login; line=12",
        "submit_btn": "kind=jsx_attr; text=Submit; line=40",
        "load_error": "kind=toast; text=Error loading; line=55",
        "none": "No single candidate stands out to extract first."
      }
    },
    {
      "id": "needs_locale_split",
      "type": "noul",
      "instructions": "Should labels and toasts in `candidates` land in different locale namespaces (UI strings vs errors) rather than one dump?",
      "criteria": {
        "true": "UI labels and error/toast copy should split across locale files or namespaces.",
        "false": "One locale namespace is enough, or there is no user-facing copy."
      }
    }
  ]
}
```

### Blind-test protocol

1. Invent typed Choice / Noul / Score questions **before** opening `list_packs`.
2. Use a pack only if it matches **exactly**.
3. Otherwise `run_questions`. Full protocol: [docs/DOGFOOD.md](docs/DOGFOOD.md).

### `computer_use_step` (harness)

Code owns OCR / accessibility / DOM, clicks, and the **writer LLM** for free text. Jev only picks the next operation and a target from your closed catalogs.

1. Observe in the harness. Build `items[]` (and optional `offscreen_items[]`) with stable ids. Cap the catalog **before 255** (Choice cap includes `none`). Prefer `source: "dom"` when ids are DOM-closed; `ax` / `ocr` when that is what you observed. Short `observation_summary`. **No screenshots, pixels, or image blobs in state** (rejected as `invalid_state`).
2. `run_pack` `computer_use_step` with `goal`, `app_or_url`, that observation, recent `history`, and `flags`.
3. One `systemOne` call fans out `operation` plus a separate target Choice per op (`click_target`, `type_target`, `offscreen_target`) plus `goal_achieved`, `observation_stale`, `step_confidence`. Each target question assumes its operation.
4. Prefer additive **`guidance`**: `target_for`, `ignore_targets`, `effective_targets`, `writer_owns_typed_string`, `harness_hints`. Raw `answers` stay intact. Act only on `guidance.effective_targets` / `guidance.target_for[operation]`.
5. For `type_text` / `type_email`, a writer LLM (or stored value) supplies the string. `max_steps` / `max_candidates` stay in the harness.
6. Example thresholds (tune on your traces): stop if `goal_achieved.noul ≥ 0.8` or `operation` is `done`; re-observe if `observation_stale.noul ≥ 0.65` or `step_confidence.score < 1.5`.

Example state:

```json
{
  "goal": "Sign in with the saved work account",
  "app_or_url": "https://app.example.com/login",
  "observation_summary": "Login form: email focused and empty, password empty, Sign in button, Forgot password link.",
  "focused_field": "email",
  "items": [
    { "id": "email", "role": "textfield", "label": "Work email", "region": "form", "source": "ax" },
    { "id": "password", "role": "textfield", "label": "Password", "region": "form", "source": "ax" },
    { "id": "sign_in", "role": "button", "label": "Sign in", "region": "form", "source": "ax" }
  ],
  "history": [{ "action": "wait", "result": "form_visible" }],
  "flags": { "loading": false, "login_required": true }
}
```

### `model_router` (per-turn lane)

Call at **turn start**, before tools. Map `route.choice` in **code** (unit-test the mapper; no TypeSafe key). Do not invent a sixth lane.

Closed lanes: `fast_local` | `strong_reasoner` | `tools_heavy` | `ask_user` | `skip`.

| Lane | Meaning |
| --- | --- |
| `fast_local` | Cheap/local or no model: lookup, format, one obvious tool |
| `strong_reasoner` | Ambiguous design, hard failure, plan not yet mechanical |
| `tools_heavy` | Long tool / browser / shell loop |
| `ask_user` | Missing preference, secret, or confirmation |
| `skip` | Already done, blocked, or out of scope |

Example thresholds (caller-owned): `route.confidence < 0.45` → `ask_user`; `unsafe_or_irreversible.noul ≥ 0.70` → refuse or confirm; `simple_lookup.noul ≥ 0.75` and `difficulty.score < 1.5` → force `fast_local`.

### Recipes table

| Need | Pack |
| --- | --- |
| Typed Choice / Noul / Score with **no exact pack** (incl. i18n + extra heads) | **`run_questions`** (not a pack) |
| Next GUI step from a closed element catalog | `computer_use_step` |
| Model cascade / per-turn compute lane | `model_router` |
| Skill select from a closed list | `skill_router` |
| Command risk signals | `command_risk` |
| Generic diff review | `review_diff` |
| Repo tree / architecture / "try Jev on these files" | `mcp_jev scan` → `code_audit` (Pass 1; not `pr_audit`) |
| Domain example: money / labor-hours / migration **PR-shaped** merge risk | `pr_audit` |
| Item / SKU locale from a **caller-supplied** closed country list | `locale_country` |
| Claimed behavior vs named tests / CI | `verify_gap` |
| One module’s imports/exports vs layer | `boundary_check` |
| Hardcoded UI copy vs i18n (`t()` / locale files) | `i18n_copy` if exact match; else **`run_questions`** (i18n recipe above) |

Code-owned policy: keep thresholds in **your** functions (see `src/policy-examples.ts`; unit-test them without a TypeSafe key). Jev returns signals; your gate decides. Allowlist/sandbox still required for shell.

`verify_gap`: one claim vs named evidence → `verifyGapCodeGate` (`ship` \| `add_proof` \| `block`). `boundary_check`: one module’s imports/exports → `fix` (`keep` \| `extract` \| `move_layer` \| `unclear`).

### `review_diff` (staged review)

Nouls `correctness` / `security` / `reliability` / `compat` / `test_gap` → Choice `hotspot_file` from the closed `files[]` catalog → Score `severity`. Orchestration (comment, block, open a file) stays in the caller.

```json
{
  "intent": "Fail closed on reserved catalog ids",
  "diff_summary": "Adds reserved-id checks and a unit test. No auth change.",
  "files": ["src/packs/catalog-choice.ts", "test/computer-use.test.ts"]
}
```

Example thresholds: request review if any Noul ≥ 0.65; block if `security.noul ≥ 0.75` or `severity.score ≥ 2.5`.

`pr_audit` is a **domain example** pack (money / labor-hours / migration merge risk, common in ops/fintech) — **not** the universal PR pack. Keep the id. **PR-shaped state only** (`title` / `body` / `files` / `diff_summary`). General users should start with `review_diff` (diff) or `code_audit` (tree). Do not use `pr_audit` for a repo-tree scan. Never put experiment narrative in `title`/`body`. Large path-only batches (~40 files) inflate `needs_review`/`block`; path tokens (`budget`, `migration`, `finance`) move Nouls without reading bodies.

### `code_audit` (full-repo / per-file scan)

Millisecond-tier structured engineering audit (layering / blast-radius / verification style checks). **Expect ~network RTT per file; parallelize N workers.** This is not a multi-second LLM review. Jev must not receive the monorepo as prose.

**First-class harness:** `mcp_jev scan <path>` (or `node dist/cli.js scan`). Do **not** dump a tree into `pr_audit`.

```bash
mcp_jev scan . --dry-run                 # files + signals; no TypeSafe
mcp_jev scan .                           # Pass 1, concurrency 8
mcp_jev scan . --concurrency 16 --pass2 5
```

Walks the tree (respects `.gitignore`; skips binaries, images, lockfiles, `node_modules`, `.git`, generated dirs), builds compact signals, and calls `code_audit` through the same `run_pack` / `systemOne` path. JSONL to stdout (or `--jsonl PATH`); summary on stderr (top-K severity, `primary_concern` histogram, hottest paths, path-token-only flag count). Progress on stderr: files done/total, rate files/min, ETA. Missing API key → clear error (except `--dry-run`).

**Pass 1 (default, all files):** harness lists files → filter screenshots/binaries/generated vendor dirs → compact `signals` only → parallel `run_pack` `code_audit` → aggregate top `problem_severity` / most frequent Nouls.

**Pass 2 (top-N only):** resend the hottest files with a short `excerpt` (hard max 1200 chars; oversized → `invalid_state`) for confirmation. `--pass2 N` on the CLI.

**6000 files / ~2 min class:** keep Pass 1 signals-only. Concurrency default **8**; **16–32** is typical for multi-k repos (`MCP_JEV_SCAN_CONCURRENCY` or `--concurrency`). `--max-files N` samples. `--resume` continues from `.mcp_jev-scan-checkpoint.json` (written every 50 files). Recipe: parallel Pass 1 → read top severity + path-token FP note → Pass 2 excerpts on top-K only.

```bash
mcp_jev scan . --concurrency 16 --summary-only --jsonl scan.jsonl
mcp_jev scan . --concurrency 16 --pass2 20 --summary-only
mcp_jev scan . --resume --summary-only --jsonl scan.jsonl   # after interrupt
```

Example gate (`src/policy-examples.ts` `gateCodeAudit`): `ok` | `glance` | `deep_review`. Unit-test the gate without a TypeSafe key.

Pass 1 state (no body):

```json
{
  "path": "src/billing/invoice-total.ts",
  "language": "ts",
  "role_hint": "domain",
  "signals": {
    "loc": 12,
    "import_count": 1,
    "top_imports": ["money"],
    "has_tests_nearby": false,
    "touches_money": true,
    "is_generated": false,
    "complexity_heuristic": 2
  },
  "repo_context": "Billing module: invoice line totals."
}
```

Optional Mode B: one call with `files[]` (paths only) + short `batch_notes` — Choice `hotspot_file` from that closed catalog, no bodies. If `path` is also set, single-file Mode A wins.

### `i18n_copy` (hardcoded UI copy)

**Shortcut pack** when invented heads match **exactly** (the three Nouls + `i18n_debt` + `hottest_candidate` + `primary_bucket`). Extra head such as `needs_locale_split` → **`run_questions`** (recipe above, equal JSON). Per-file audit of hardcoded strings that should live in an i18n layer. The harness extracts a **closed** `candidates[]` catalog (max ~20). Jev does not rewrite JSX or locale JSON.

Nouls `has_user_facing_hardcoded_copy` / `should_migrate_to_i18n` / `already_partially_internationalized` → Score `i18n_debt` (0 clean → 3 blocking for a multi-locale ship) → Choice `hottest_candidate` from `candidates[].id` plus `none` → Choice `primary_bucket` (`ui_copy` | `error_message` | `marketing` | `dev_only` | `mixed` | `none`).

```json
{
  "path": "src/components/LoginForm.tsx",
  "language": "tsx",
  "framework_i18n": "next-intl",
  "uses_i18n_api": false,
  "locale_files_present": true,
  "candidates": [
    { "id": "login_heading", "text": "Login", "kind": "jsx_text", "line": 12 },
    { "id": "submit_btn", "text": "Submit", "kind": "jsx_attr", "line": 40 },
    { "id": "load_error", "text": "Error loading", "kind": "toast", "line": 55 }
  ]
}
```

Example gate (`src/policy-examples.ts` `gateI18nCopy`): `ok` | `glance` | `block`. Unit-test the gate without a TypeSafe key.

## Update

Update from the **GitHub checkout**, not npm:

```bash
~/mcp_jev/scripts/update.sh          # git pull + npm install + build; keeps ~/.mcp_jev/.env
# Windows: ~\mcp_jev\scripts\update.ps1
```

Do not `npm update -g mcp_jev` or `npx mcp_jev@latest`. Public versions are GitHub Releases (`v0.0.9` onward). Scheme: [docs/RELEASES.md](docs/RELEASES.md).

Restart the MCP host. `update.sh` refreshes the skill **once** to `~/.agents/skills/mcp_jev`. Hosts that do not read that path: re-add **once** (do not also copy — nests `mcp_jev/mcp_jev`):

```bash
npx skills add pedroknigge/mcp_jev --skill mcp_jev
```

## Skill stays in sync

The agent skill lives in-repo at [`skills/mcp_jev/SKILL.md`](skills/mcp_jev/SKILL.md). Frontmatter `description` **always** starts with `VERSION — ` (the `package.json` version + em dash). `npx tsx scripts/sync-skill-catalog.ts` / `npm test` fail if it drifts. This is the template for Pedro skills.

`scripts/update.sh` pulls the checkout and refreshes the skill **once** to `~/.agents/skills/mcp_jev` (replaces that folder; never nests). Hosts that do not read that path: re-add **once** with `npx skills add pedroknigge/mcp_jev --skill mcp_jev`. Do not also copy.

Exact pack ids / question ids / state fields: [`skills/mcp_jev/references/pack-catalog.md`](skills/mcp_jev/references/pack-catalog.md) (generated from `src/packs/`). `npm test` fails if a new pack or question id is missing from the skill or that catalog.

## Say this to your agent

> Install and configure mcp_jev from https://github.com/pedroknigge/mcp_jev using the install script and skill. Then run doctor, ping, and list_packs.

Or: run `scripts/install.sh` (refreshes the skill once to `~/.agents/skills/mcp_jev`), paste the printed snippets, restart, `doctor` → `ping` → `list_packs`. Do not also `npx skills add` in the same pass.

## Install for agents & IDEs

**Easiest path:** install script → one keyless `command` pointing at `~/.mcp_jev/bin/mcp_jev` → `mcp_jev doctor` → restart → `ping`. Full stanzas in [docs/INSTALL_AGENTS.md](docs/INSTALL_AGENTS.md).

The server reads `TYPESAFE_API_KEY` from **`~/.mcp_jev/.env` (or `$MCP_JEV_HOME/.env`)**. Process env is a fallback only when the store is empty. Host configs must stay keyless.

Generic JSON (most hosts):

```json
{
  "mcpServers": {
    "mcp_jev": {
      "command": "/absolute/path/to/.mcp_jev/bin/mcp_jev"
    }
  }
}
```

Windows wrapper: `%USERPROFILE%\.mcp_jev\bin\mcp_jev.cmd`.

Print or merge snippets: `mcp_jev hosts print` · `mcp_jev hosts write all`.

### Cursor

Project: `.cursor/mcp.json` · User: `~/.cursor/mcp.json` (project wins on name clash).

Skill: `install.sh` refreshes `~/.agents/skills/mcp_jev` once. If you skipped the script, pick **one**: `npx skills add pedroknigge/mcp_jev --skill mcp_jev` **or** `rm -rf ~/.agents/skills/mcp_jev && cp -R skills/mcp_jev ~/.agents/skills/mcp_jev`. Never both.

### Claude Desktop

Settings → Developer → Edit Config, then **fully quit**.

| OS | File |
| --- | --- |
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |

Same keyless `mcpServers` JSON as Cursor.

### Claude Code

```bash
claude mcp add --scope user --transport stdio mcp_jev -- /Users/YOU/.mcp_jev/bin/mcp_jev
```

Or merge `~/.claude.json` with the generic JSON. Skill: copy to `.claude/skills/mcp_jev`.

### Codex

TOML, not JSON. `~/.codex/config.toml` or `.codex/config.toml`:

```toml
[mcp_servers.mcp_jev]
command = "/Users/YOU/.mcp_jev/bin/mcp_jev"
```

Or `codex mcp add mcp_jev -- /Users/YOU/.mcp_jev/bin/mcp_jev`.

### Grok

`~/.grok/config.toml` / `grok mcp add mcp_jev -- ~/.mcp_jev/bin/mcp_jev`. Same TOML table `[mcp_servers.mcp_jev]`.

### Antigravity

`~/.gemini/config/mcp_config.json` or `.agents/mcp_config.json` — generic JSON, `command` = wrapper.

### Other JSON / TOML hosts

Same wrapper `command`. Some editors use `context_servers` instead of `mcpServers` — see [docs/INSTALL_AGENTS.md](docs/INSTALL_AGENTS.md).

## Agent instructions

> You have **mcp_jev** (or you must install it from https://github.com/pedroknigge/mcp_jev). Jev is **not** chat. No `ask_jev`.
>
> If the MCP tools are missing: tell the user to run `scripts/install.sh` (or `install.ps1`) from that repo, paste the printed snippets, set the key once (`config set-key` / install prompt), run `mcp_jev doctor`, restart the host.
>
> Always: `list_packs`. Packs are shortcuts: if one fits, `describe_pack` → `run_pack`. If **no pack fits**, do not stop: build closed state + typed Choice / Noul / Score and call `run_questions`. Side effects stay in **your** code. Never put `TYPESAFE_API_KEY` in chat. If `ping.api_key_set` is false, run `mcp_jev config set-key` — do not embed the key in every host config.

## Tools (closed catalog)

| Tool | Args | What it does |
| --- | --- | --- |
| `list_packs` | none | `id`, `version`, `title`, `summary`, `when_to_use` |
| `describe_pack` | `pack_id` | JSON Schema, questions, `example_state`, `suggested_workflow`, `notes` |
| `run_pack` | `pack_id`, `state` | Validates state, calls TypeSafe `systemOne`, returns typed answers + usage. **No side effects.** |
| `run_questions` | `state`, `questions`, optional `model` | Fail-closed typed Choice / Noul / Score when **no pack fits**. Same `systemOne` path as `run_pack`. **No side effects.** Not chat. |
| `ping` | none | Versions, pack count, `api_key_set`, `api_key_source` (`env` \| `user_store` \| `none`). **Never** echoes the key |

There is **no** free-form ask tool. `list_packs` / `describe_pack` / `ping` never call TypeSafe. Only `run_pack` and `run_questions` do. Teaching for the custom path: [docs/CUSTOM_JUDGMENTS.md](docs/CUSTOM_JUDGMENTS.md).

## Packs

| id | What Jev judges | What **you** still do |
| --- | --- | --- |
| `pr_audit` | **Domain example** (ops/fintech): `merge_risk` (safe_ui \| needs_review \| block), Nouls money / hours / hours_money_boundary / migration, Score `blast_radius` | Staged review: risk Nouls → file Choice over `files[]` → severity. Compute **`code_gate`**. Jev does not merge or comment. Prefer `review_diff` / `code_audit` for general PR/tree review |
| `review_diff` | Nouls correctness / security / reliability / compat / test_gap; Choice `hotspot_file` from `files[]`; Score `severity` | **Default generic PR/diff pack.** Orchestration stays in the caller |
| `code_audit` | Nouls `wrong_layer` / `blast_radius` / `missing_verification` / `secret_or_credential_risk` / `inefficiency` / `dead_or_premature_abstraction`; Scores `problem_severity` + `change_cost`; Choice `primary_concern` | Pass 1: signals-only, ~RTT per file, N workers. Pass 2: short excerpt on top-N. Aggregate + `gateCodeAudit` in code |
| `skill_router` | Noul `needs_skill`; Choice `skill` from `available_skills[]`; Score `change_risk` | Load the skill in the host. Thresholds in your code |
| `command_risk` | Nouls `is_destructive` / `touches_credentials` / `scope_matches`; Score `severity` | Allowlist/sandbox still required. Signals only |
| `intent_router` | Closed intent Choice, jailbreak + policy Nouls, urgency Score | Route / refuse / hand off in code |
| `locale_country` | Catalogue item → one id from the caller’s closed `countries[]` catalog (or `unclear`) | Pass your own country list. Write the country to your catalogue |
| `computer_use_step` | Next GUI `operation` + speculative targets; Nouls `goal_achieved` / `observation_stale`; Score `step_confidence`; additive `guidance` | Observe (OCR/AX/DOM), execute the op, writer LLM for typed text, stop rules |
| `model_router` | `route` (fast_local \| strong_reasoner \| tools_heavy \| ask_user \| skip); Nouls code/browser/unsafe/lookup; Score `difficulty` | Map the lane in code. Thresholds stay in the caller |
| `verify_gap` | Nouls `has_adequate_verification` / `claim_is_testable` / `evidence_matches_claim`; Score `verification_gap` (0 none → 3 ship-blocker); Choice `next_proof` | Compute **`code_gate`** (`verifyGapCodeGate` → ship \| add_proof \| block). Jev does not write the test |
| `boundary_check` | Nouls `crosses_layer` / `leaks_domain_to_ui` / `leaks_infra_to_domain`; Score `boundary_risk`; Choice `fix` (keep \| extract \| move_layer \| unclear) | Extract / move / keep in caller code |
| `i18n_copy` | Nouls `has_user_facing_hardcoded_copy` / `should_migrate_to_i18n` / `already_partially_internationalized`; Score `i18n_debt` (0 clean → 3 blocking); Choice `hottest_candidate` from `candidates[].id`; Choice `primary_bucket` | Truncate to ~20 candidates. `gateI18nCopy` → `ok` \| `glance` \| `block`. Do not rewrite locale files here |

Packs live in `src/packs/` (in-repo). How to add one: [CONTRIBUTING.md](CONTRIBUTING.md).

**Breaking (`locale_country` 2.0.0):** country Choice options are no longer a hardcoded country list. Callers must pass `countries[]` (closed catalog of their own ids, e.g. `us`, `de`, `jp`). `unclear` remains the pack-owned refuse option.

## Architecture

```mermaid
flowchart LR
  Agent[Agent] -->|stdio MCP| Server["mcp_jev"]
  Server --> Packs[Pack registry]
  Server -->|run_pack / run_questions| SDK["TypeSafeClient.systemOne"]
  SDK --> API["POST /v1/systemone"]
  API --> Jev["Jev"]
  Jev -->|typed answers| SDK
  SDK --> Server
  Server --> Agent
  Agent -->|gates and side effects| Code[Your code]
```

## Environment

| Variable | Required | Default |
| --- | --- | --- |
| `TYPESAFE_API_KEY` | Yes, for `run_pack` / `run_questions` | From `~/.mcp_jev/.env` after install |
| `TYPESAFE_BASE_URL` | No | SDK: `https://api.typesafe.ai` |
| `JEV_MODEL` | No | `jev-latest` |
| `TYPESAFE_DEFAULT_MODEL` | No | Used if `JEV_MODEL` is unset |
| `MCP_JEV_HOME` | No | `~/.mcp_jev` (key + wrapper) |
| `MCP_JEV_CONFIG` | No | Alias for `MCP_JEV_HOME` |
| `MCP_JEV_CHECKOUT` | No | Git checkout (default `~/mcp_jev` or the repo you ran the script from) |
| `MCP_JEV_WRITE_HOSTS` | No | Install-time host write (`all` or comma list) |
| `MCP_JEV_SKILL_HOME` | No | Dest for the one skill refresh (default `~/.agents/skills/mcp_jev`) |
| `MCP_JEV_SCAN_CONCURRENCY` | No | Default parallel workers for `mcp_jev scan` (8; 16–32 typical for multi-k / 6000-files class) |

Repo `.env` is **not** auto-loaded. The **user store** `~/.mcp_jev/.env` is. The store wins; process env is fallback only.

CLI: `mcp_jev doctor` · `mcp_jev smoke` · `mcp_jev scan <path>` · `mcp_jev hosts print` · `mcp_jev config set-key` · `mcp_jev config status` · `mcp_jev config path`.

## Security

- Key lives in **`~/.mcp_jev/.env` (0600)**. Never in git, chat, or host JSON. Process env is fallback only if the store is empty.
- `ping` / `config status` / `doctor` never print the key.
- `scripts/update.sh` does not overwrite the key file.
- Run locally. Do not deploy this stdio binary as a public HTTP proxy.
- Pack state can contain tickets and diffs — keep diffs summarized.
- MIT licensed. Jev / TypeSafe are products of TypeSafe; this project is an independent open-source client.

## Troubleshooting

| Symptom | Fix |
| --- | --- |
| `doctor` / `NOT_READY` | Non-interactive install without a key. Run `mcp_jev config set-key`, then `mcp_jev doctor`. |
| `ping` → `api_key_set: false` | Same. Confirm `~/.mcp_jev/.env` exists. Restart the host. |
| `run_pack` / `run_questions` / `scan` → `missing_api_key` | Same. `--dry-run` still works. Do not fabricate answers. |
| `run_pack` → `auth` | Key rejected (HTTP 401). Rotate at the TypeSafe dashboard, then `config set-key`. |
| `invalid_state` | Re-read `describe_pack`. Starter packs reject extra fields. `computer_use_step` also rejects screenshots. Custom `run_questions` state must be a JSON object. |
| `invalid_questions` | `run_questions` failed schema: Choice needs 2–255 closed options; Score ≥2 levels; use `instructions` (alias `prompt` ok); no free-form types. |
| `unknown_pack` | `list_packs`. If none fits, `run_questions`. There is no `ask_jev`. |
| Wrong Node | `node -v` must be 20+. GUI hosts may not see nvm. |
| stdio pollution | Wrapper and server must not write to **stdout**. |
| `npx mcp_jev` 404 | Expected. mcp_jev is not on npm. Clone GitHub and run `scripts/install.sh`; later `scripts/update.sh`. See [docs/RELEASES.md](docs/RELEASES.md). |

## Scripts

```bash
./scripts/install.sh     # happy path
./scripts/update.sh
./scripts/verify-mcp.sh  # stdio smoke, no TypeSafe call
npm run smoke:packs      # mocked run_pack for every pack
npm run smoke:blind-i18n # mocked run_questions-shaped i18n (closed candidates)
npm run build            # tsc → dist/
npm start                # node dist/index.js (MCP stdio)
npm test
npm run typecheck
mcp_jev doctor
mcp_jev smoke            # same stdio check as verify-mcp.sh
```

## License

[MIT](LICENSE)

TDQS

A4.4/5.0

Scored across 5 tools

Disambiguation4/5

Each tool has a distinct role: listing packs, describing a pack, running a pack, running custom questions, and health checking. The only potential confusion is between run_pack and run_questions since both invoke the same TypeSafe System One path, but their descriptions clearly separate pack-based from custom typed workflows.

Naming Consistency4/5

Most tools follow a clear verb_noun pattern: list_packs, run_questions, describe_pack, run_pack. The one outlier is the one-word health tool 'ping', which breaks the pattern but is a common and recognizable convention.

Tool Count5/5

Five tools is a well-scoped size for a focused server that handles pack listing, description, execution, custom question runs, and health checks. Each tool has a clear purpose and none feel redundant.

Completeness5/5

The server covers the full intended workflow: discover packs via list_packs, understand via describe_pack, execute via run_pack, handle non-pack cases via run_questions, and verify health via ping. Since the pack catalog is closed and free-form ask is intentionally excluded, there are no significant gaps.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive