benjivers/jev-mcp Public Public: anyone can read this; nobody can change it.
workbenchthis machine
Files cbaceb505b32e2f74693377ae6d3e69cde6a98b6
| 1 | # jev-mcp |
| 2 | |
| 3 | An MCP server that exposes [](https://docs.typesafe.ai) as typed judgment tools. |
| 4 | |
| 5 | Jev is a System One model. It returns a typed answer and a calibrated probability |
| 6 | distribution, never prose. These tools surface that faithfully rather than hiding |
| 7 | it behind a label. |
| 8 | |
| 9 | ## Install |
| 10 | |
| 11 | Requires Node 20.12+ and a TypeSafe API key from |
| 12 | [](https://console.typesafe.ai/settings/keys). |
| 13 | |
| 14 | bash |
| 15 | npm install |
| 16 | npm run build |
| 17 | |
| 18 | |
| 19 | Register with Claude Code: |
| 20 | |
| 21 | bash |
| 22 | claude mcp add --scope user jev -- node /absolute/path/to/jev-mcp/dist/index.js |
| 23 | |
| 24 | |
| 25 | The key is read from `TYPESAFE_API_KEY` in the server's environment. It is never a |
| 26 | tool argument, so it cannot land in a transcript or in a model's context. Put it in |
| 27 | `~/.zshenv` rather than in any repo, and make sure it is **exported**: |
| 28 | |
| 29 | sh |
| 30 | export TYPESAFE_API_KEY="ts_..." |
| 31 | |
| 32 | |
| 33 | Without `export` the variable exists only in the shell that read it. Child processes, |
| 34 | including every MCP server Claude Code spawns, never see it, and every tool call fails |
| 35 | with a missing-key error. |
| 36 | |
| 37 | ## Tools |
| 38 | |
| 39 | | Tool | Primitive | Use when | |
| 40 | | --- | --- | --- | |
| 41 | | `jev_classify` | Choice | The answer is one of a fixed set you define | |
| 42 | | `jev_score` | Score | The answer is a degree on an ordered scale | |
| 43 | | `jev_check` | Noul | The answer is yes or no, and you want the probability | |
| 44 | | `jev_ask` | all three | You have several questions about the same state | |
| 45 | | `jev_models` | — | Confirm the key works and find a model id | |
| 46 | |
| 47 | Every tool returns the full probability distribution alongside the answer, plus |
| 48 | `confidence` for Choice and Score. Results come back as MCP structured content, so a |
| 49 | client gets typed data rather than a JSON string to re-parse. |
| 50 | |
| 51 | ### Prefer `jev_ask` |
| 52 | |
| 53 | Jev prefills the state once and scores every question in a single forward pass, so |
| 54 | extra questions add almost no latency or cost. TypeSafe's own |
| 55 | [](https://docs.typesafe.ai/cookbooks/parallel_questions) on a |
| 56 | document-dominated workload puts one batched call at 12.2x cheaper and 10.0x faster |
| 57 | than one call per question, with no change in the answers. |
| 58 | |
| 59 | Questions in one request cannot see each other's answers. State any speculative |
| 60 | premise explicitly and let your own code decide which answers apply. |
| 61 | |
| 62 | ## Design rules |
| 63 | |
| 64 | 1. **The caller owns the option set.** `jev_classify` requires options from you, so |
| 65 | the model can pick the wrong one but can never invent one. A selector cannot |
| 66 | choose a candidate the enumerator dropped. This is the single most common way |
| 67 | these integrations fail. |
| 68 | 2. **Probabilities are always returned.** Not just the winner. |
| 69 | 3. **The key lives in the environment.** Never in an argument. |
| 70 | 4. **Nothing is silently dropped, overwritten, or truncated.** A request that cannot |
| 71 | be honoured exactly fails with a reason instead of quietly changing meaning. |
| 72 | 5. **Only JSON-RPC reaches stdout.** Logs go to stderr, always. |
| 73 | |
| 74 | ### The no-match option |
| 75 | |
| 76 | Each selecting tool adds a `none` option by default so the model can decline rather |
| 77 | than being forced to pick. Turn it off with `add_none: false` when one option must |
| 78 | always apply. |
| 79 | |
| 80 | If you already use the name `none` for an option of your own, the added option takes |
| 81 | a different key instead of overwriting yours, and the response reports which key |
| 82 | carries the no-match meaning in `none_option`. Your option and its probability always |
| 83 | survive intact. |
| 84 | |
| 85 | ### Confidence gating |
| 86 | |
| 87 | Choice and Score answers include an `action` of `act`, `review`, or `abstain`, derived |
| 88 | from `confidence` and the thresholds you pass in `act_above` and `review_above` |
| 89 | (defaults 0.8 and 0.5). `jev_check` returns a `verdict` of `yes`, `no`, or `uncertain` |
| 90 | from `yes_at_or_above` and `no_at_or_below` (defaults 0.7 and 0.3). |
| 91 | |
| 92 | These defaults are starting points, not universal rules. Calibrate them on your own |
| 93 | data and on what it costs to be wrong; a destructive action deserves a higher bar than |
| 94 | a read-only one. Confidence describes how concentrated the distribution is. It is not |
| 95 | a claim that the answer is correct. |
| 96 | |
| 97 | A Noul near 0.5 means yes and no are close to equally likely, not that the answer is |
| 98 | "medium", which is why the middle band reports `uncertain` rather than rounding. |
| 99 | |
| 100 | ### Errors |
| 101 | |
| 102 | Failures come back with `isError` and a classified body: `kind`, `retryable`, and |
| 103 | where available `status`, `requestId`, and a `hint`. A rejected key (`authentication`, |
| 104 | never retryable) is distinguishable from a rate limit (`rate_limit`, retryable) and |
| 105 | from a malformed question (`invalid_request`). The SDK already retries 408, 429, and |
| 106 | 5xx with backoff before an error surfaces here. |
| 107 | |
| 108 | ### Limits |
| 109 | |
| 110 | A Choice question accepts at most 255 options, which is an API limit; past that, |
| 111 | search in two passes, one question picking a window and a second ranking within it. |
| 112 | Question count and state size are local caps that bound cost on a single call, and |
| 113 | both are configurable. Oversized state is rejected rather than truncated, because |
| 114 | truncating silently changes the material the judgment rests on. |
| 115 | |
| 116 | ## Configuration |
| 117 | |
| 118 | | Variable | Effect | |
| 119 | | --- | --- | |
| 120 | | `TYPESAFE_API_KEY` | Required. `JEV_API_KEY` also works. | |
| 121 | | `JEV_MODEL` | Model id. Defaults to `jev-latest`. | |
| 122 | | `JEV_TIMEOUT_MS` | Per-attempt timeout. Defaults to 15000. | |
| 123 | | `JEV_MAX_QUESTIONS` | Questions per `jev_ask`. Defaults to 64. | |
| 124 | | `JEV_MAX_STATE_CHARS` | Largest state accepted. Defaults to 200000. | |
| 125 | | `TYPESAFE_LOG_LEVEL` | SDK verbosity. Safe at any level; all output goes to stderr. | |
| 126 | |
| 127 | An unusable value for any numeric setting falls back to the default and warns on |
| 128 | stderr, rather than becoming `NaN` and disabling the limit it was meant to enforce. |
| 129 | |
| 130 | ## Troubleshooting |
| 131 | |
| 132 | **Every call reports a missing key.** Some MCP clients filter the environment before |
| 133 | spawning servers, which drops `TYPESAFE_API_KEY`. Confirm the variable is exported, |
| 134 | then pass it explicitly in the client's server config if it still does not arrive. |
| 135 | Run `jev_models` to check the key in isolation. |
| 136 | |
| 137 | **The server connects and then dies.** On a stdio transport, anything written to |
| 138 | stdout that is not JSON-RPC breaks the connection. This server routes all logging to |
| 139 | stderr, so `TYPESAFE_LOG_LEVEL=debug` is safe to turn on while debugging. |
| 140 | |
| 141 | ## When not to reach for this |
| 142 | |
| 143 | Jev earns its place on a decision that repeats thousands of times inside software, |
| 144 | where code can enumerate the options first and you need a number to threshold on. |
| 145 | |
| 146 | For a one-off judgment during a conversation, an ordinary model answer is usually |
| 147 | better, because it comes with reasoning you can argue with. Jev gives you a number |
| 148 | and a label. That is a feature at scale and a limitation in dialogue. |
| 149 | |
| 150 | If deterministic code already decides the case correctly, keep the deterministic |
| 151 | code. Typed output guarantees the interface, not the truth. Measure before adopting. |
| 152 | |
| 153 | ## Development |
| 154 | |
| 155 | bash |
| 156 | npm run typecheck |
| 157 | npm test # offline: unit tests plus regression tests against a local stand-in |
| 158 | npm run test:e2e # live, requires TYPESAFE_API_KEY |
| 159 | |
| 160 | |
| 161 | The offline suite runs the real built server over stdio against a local stand-in for |
| 162 | the TypeSafe API and asserts on the request bodies it actually sends, so a regression |
| 163 | in what reaches the model fails the build. |
| 164 | |
| 165 | ## License |
| 166 | |
| 167 | MIT |