benjivers/jev-mcp Public Public: anyone can read this; nobody can change it.
workbenchthis machine
feat: ship as Claude Code plugin and npm package with one-command install
- Plugin manifest declares the server inline and launches the published package via npx, so installs need no build step. - Add marketplace manifest and thin jev skill bridging agents to the tools. - package.json: repository metadata, prepublishOnly gate, version 0.3.0. - Server reads its version from package.json instead of a hardcoded string. - README: one-command install for Claude Code and any MCP client, key file setup. - Drop stray pnpm lockfile; npm's package-lock.json is authoritative.
7 files
changed +290
−15
.claude-plugin/marketplace.json added +29
| @@ -0,0 +1,29 @@ | ||
| 1 | { | |
| 2 | "name": "jev-mcp", | |
| 3 | "description": "TypeSafe Jev as typed judgment tools for coding agents.", | |
| 4 | "owner": { | |
| 5 | "name": "rashed parvez", | |
| 6 | "url": "https://github.com/rashedInt32" | |
| 7 | }, | |
| 8 | "plugins": [ | |
| 9 | { | |
| 10 | "name": "jev", | |
| 11 | "source": "./", | |
| 12 | "displayName": "TypeSafe Jev", | |
| 13 | "description": "Typed, calibrated judgments from TypeSafe Jev: classify, score, check, a batched multi-question ask, and model discovery.", | |
| 14 | "version": "0.3.0", | |
| 15 | "homepage": "https://github.com/rashedInt32/jev-mcp", | |
| 16 | "repository": "https://github.com/rashedInt32/jev-mcp", | |
| 17 | "license": "MIT", | |
| 18 | "keywords": [ | |
| 19 | "mcp", | |
| 20 | "typesafe", | |
| 21 | "jev", | |
| 22 | "system-one", | |
| 23 | "classification", | |
| 24 | "confidence" | |
| 25 | ], | |
| 26 | "category": "productivity" | |
| 27 | } | |
| 28 | ] | |
| 29 | } |
.claude-plugin/plugin.json added +31
| @@ -0,0 +1,31 @@ | ||
| 1 | { | |
| 2 | "name": "jev", | |
| 3 | "displayName": "TypeSafe Jev", | |
| 4 | "description": "Typed, calibrated judgments from TypeSafe Jev: classify, score, check, a batched multi-question ask, and model discovery.", | |
| 5 | "version": "0.3.0", | |
| 6 | "author": { | |
| 7 | "name": "rashed parvez" | |
| 8 | }, | |
| 9 | "homepage": "https://github.com/rashedInt32/jev-mcp", | |
| 10 | "repository": "https://github.com/rashedInt32/jev-mcp", | |
| 11 | "license": "MIT", | |
| 12 | "keywords": [ | |
| 13 | "mcp", | |
| 14 | "typesafe", | |
| 15 | "jev", | |
| 16 | "system-one", | |
| 17 | "classification", | |
| 18 | "confidence" | |
| 19 | ], | |
| 20 | "skills": "./skills/", | |
| 21 | "mcpServers": { | |
| 22 | "jev": { | |
| 23 | "type": "stdio", | |
| 24 | "command": "npx", | |
| 25 | "args": [ | |
| 26 | "-y", | |
| 27 | "[email protected]" | |
| 28 | ] | |
| 29 | } | |
| 30 | } | |
| 31 | } |
.gitignore +2
| @@ -1,3 +1,5 @@ | ||
| 1 | 1 | node_modules/ |
| 2 | 2 | dist/ |
| 3 | 3 | .env |
| 4 | .env.* | |
| 5 | *.tgz |
README.md +89 −13
| @@ -11,28 +11,78 @@ it behind a label. | ||
| 11 | 11 | Requires Node 20.12+ and a TypeSafe API key from |
| 12 | 12 | [](https://console.typesafe.ai/settings/keys). |
| 13 | 13 | |
| 14 | bash | |
| 15 | npm install | |
| 16 | npm run build | |
| 17 | ||
| 14 | ### Claude Code plugin | |
| 18 | 15 | |
| 19 | Register with Claude Code: | |
| 16 | One marketplace add, one install. This registers the server and the agent skill | |
| 17 | together, so there is no separate skill step. | |
| 20 | 18 | |
| 21 | 19 | bash |
| 22 | claude mcp add --scope user jev -- node /absolute/path/to/jev-mcp/dist/index.js | |
| 20 | /plugin marketplace add rashedInt32/jev-mcp | |
| 21 | /plugin install jev@jev-mcp | |
| 23 | 22 | |
| 24 | 23 | |
| 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**: | |
| 24 | The plugin runs the published package through `npx -y jev-mcp@<version>`, so nothing | |
| 25 | needs building. The first launch downloads the package, so allow a few seconds before | |
| 26 | the server shows as connected in `claude mcp list`. | |
| 27 | ||
| 28 | ### Any MCP client | |
| 29 | ||
| 30 | bash | |
| 31 | claude mcp add --scope user jev -- npx -y jev-mcp | |
| 32 | ||
| 33 | ||
| 34 | Or in a client's JSON config: | |
| 35 | ||
| 36 | json | |
| 37 | { | |
| 38 | "mcpServers": { | |
| 39 | "jev": { "command": "npx", "args": ["-y", "jev-mcp"] } | |
| 40 | } | |
| 41 | } | |
| 42 | ||
| 43 | ||
| 44 | Do not register the server directly **and** install the plugin. Two servers named | |
| 45 | `jev` will otherwise both register. | |
| 46 | ||
| 47 | ### The key | |
| 48 | ||
| 49 | The server reads the key from `TYPESAFE_API_KEY` in its environment, then from | |
| 50 | `JEV_API_KEY`, then from `~/.config/typesafe/key`. It is never a tool argument, so it | |
| 51 | cannot land in a transcript or in a model's context. | |
| 52 | ||
| 53 | The key file is the most reliable source, because some MCP clients strip the | |
| 54 | environment before spawning servers: | |
| 28 | 55 | |
| 29 | 56 | sh |
| 30 | export TYPESAFE_API_KEY="ts_..." | |
| 57 | mkdir -p ~/.config/typesafe | |
| 58 | printf '%s' "ts_..." > ~/.config/typesafe/key | |
| 59 | chmod 600 ~/.config/typesafe/key | |
| 31 | 60 | |
| 32 | 61 | |
| 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. | |
| 62 | If you prefer the variable, put it in `~/.zshenv` rather than in any repo, and make | |
| 63 | sure it is **exported**. Without `export` the variable exists only in the shell that | |
| 64 | read it, and every server Claude Code spawns fails with a missing-key error. | |
| 65 | ||
| 66 | ### Plugin internals | |
| 67 | ||
| 68 | The server is declared **inline** under `mcpServers` in `.claude-plugin/plugin.json`. | |
| 69 | There is no `.mcp.json` anywhere in the repo, and that is deliberate. | |
| 70 | ||
| 71 | A `.mcp.json` at the plugin root is auto-discovered by the plugin loader, so it works. | |
| 72 | But any session opened in this directory *also* reads that same file as a project | |
| 73 | config, where `${CLAUDE_PLUGIN_ROOT}` is undefined. The result is a missing-variable | |
| 74 | warning and a scope conflict on the same server name. The inline form has exactly one | |
| 75 | loader and produces neither. Verify with `claude mcp list`: the server appears as | |
| 76 | `plugin:jev:jev` and the diagnostics section stays empty. | |
| 77 | ||
| 78 | One trap. `claude plugin details jev` reports `MCP servers (0)` for an inline | |
| 79 | declaration even while the server is connected and working. That is a gap in the | |
| 80 | inventory count, not a failure. Trust `claude mcp list` over `plugin details` here. | |
| 81 | ||
| 82 | The plugin deliberately ships **no `env` block**. Naming the key there would expand to | |
| 83 | an empty string when the variable is unset, and an empty string is not nullish, so it | |
| 84 | would shadow the `~/.config/typesafe/key` fallback and turn a working setup into a | |
| 85 | missing-key error. Leaving `env` out keeps all three key sources live. | |
| 36 | 86 | |
| 37 | 87 | ## Tools |
| 38 | 88 | |
| @@ -113,6 +163,19 @@ Question count and state size are local caps that bound cost on a single call, a | ||
| 113 | 163 | both are configurable. Oversized state is rejected rather than truncated, because |
| 114 | 164 | truncating silently changes the material the judgment rests on. |
| 115 | 165 | |
| 166 | ## Agent skill | |
| 167 | ||
| 168 | `skills/jev/SKILL.md` teaches an agent when to reach for these tools and how to shape | |
| 169 | the call. It is a bridge, not a tutorial. Primitive semantics, state design, and | |
| 170 | composition patterns live in TypeSafe's own `typesafe-ai` skill and in the docs, so | |
| 171 | this one deliberately does not repeat them. | |
| 172 | ||
| 173 | Its first section is a three-way test: answer it yourself, call a tool, or write SDK | |
| 174 | code. That test follows the same line as [](#when-not-to-reach-for-this) | |
| 175 | below, so an agent loading the skill does not end up arguing with this README. | |
| 176 | ||
| 177 | Point your client at the directory, or copy the file to `~/.claude/skills/jev/`. | |
| 178 | ||
| 116 | 179 | ## Configuration |
| 117 | 180 | |
| 118 | 181 | | Variable | Effect | |
| @@ -153,11 +216,24 @@ code. Typed output guarantees the interface, not the truth. Measure before adopt | ||
| 153 | 216 | ## Development |
| 154 | 217 | |
| 155 | 218 | bash |
| 219 | npm ci | |
| 220 | npm run build | |
| 156 | 221 | npm run typecheck |
| 157 | 222 | npm test # offline: unit tests plus regression tests against a local stand-in |
| 158 | 223 | npm run test:e2e # live, requires TYPESAFE_API_KEY |
| 159 | 224 | |
| 160 | 225 | |
| 226 | To run your local build instead of the npm release, register it directly and skip | |
| 227 | the plugin, since the plugin always launches the published version: | |
| 228 | ||
| 229 | bash | |
| 230 | claude mcp add --scope user jev -- node /absolute/path/to/jev-mcp/dist/index.js | |
| 231 | ||
| 232 | ||
| 233 | Releasing: bump `version` in `package.json`, `.claude-plugin/plugin.json`, and | |
| 234 | `.claude-plugin/marketplace.json` together, then `npm publish`. The plugin cache is | |
| 235 | keyed by version, so an unbumped plugin keeps serving the old snapshot. | |
| 236 | ||
| 161 | 237 | The offline suite runs the real built server over stdio against a local stand-in for |
| 162 | 238 | the TypeSafe API and asserts on the request bodies it actually sends, so a regression |
| 163 | 239 | in what reaches the model fails the build. |
package.json +14 −1
| @@ -1,14 +1,23 @@ | ||
| 1 | 1 | { |
| 2 | 2 | "name": "jev-mcp", |
| 3 | "version": "0.2.0", | |
| 3 | "version": "0.3.0", | |
| 4 | 4 | "description": "MCP server exposing TypeSafe Jev typed judgments: classify, score, check, a batched multi-question ask, and model discovery.", |
| 5 | 5 | "type": "module", |
| 6 | 6 | "license": "MIT", |
| 7 | "repository": { | |
| 8 | "type": "git", | |
| 9 | "url": "git+https://github.com/rashedInt32/jev-mcp.git" | |
| 10 | }, | |
| 11 | "homepage": "https://github.com/rashedInt32/jev-mcp#readme", | |
| 12 | "bugs": { | |
| 13 | "url": "https://github.com/rashedInt32/jev-mcp/issues" | |
| 14 | }, | |
| 7 | 15 | "bin": { |
| 8 | 16 | "jev-mcp": "./dist/index.js" |
| 9 | 17 | }, |
| 10 | 18 | "files": [ |
| 11 | 19 | "dist", |
| 20 | "skills", | |
| 12 | 21 | "README.md", |
| 13 | 22 | "LICENSE" |
| 14 | 23 | ], |
| @@ -25,6 +34,7 @@ | ||
| 25 | 34 | "scripts": { |
| 26 | 35 | "build": "tsc", |
| 27 | 36 | "prepare": "npm run build", |
| 37 | "prepublishOnly": "npm run typecheck && npm test", | |
| 28 | 38 | "start": "node dist/index.js", |
| 29 | 39 | "typecheck": "tsc --noEmit", |
| 30 | 40 | "test": "node --test test/*.test.mjs", |
| @@ -41,5 +51,8 @@ | ||
| 41 | 51 | }, |
| 42 | 52 | "engines": { |
| 43 | 53 | "node": ">=20.12" |
| 54 | }, | |
| 55 | "publishConfig": { | |
| 56 | "access": "public" | |
| 44 | 57 | } |
| 45 | 58 | } |
skills/jev/SKILL.md added +115
| @@ -0,0 +1,115 @@ | ||
| 1 | --- | |
| 2 | name: jev | |
| 3 | description: > | |
| 4 | Get typed, calibrated judgments from the connected `jev` MCP tools when the same | |
| 5 | semantic decision repeats across many items — triaging files, ranking candidates, | |
| 6 | checking claims against their sources, scoring a diff against each requirement — or | |
| 7 | when you need a calibrated number to threshold on. Prefer an ordinary answer for a | |
| 8 | single judgment in conversation. Not for judgments that ship inside the user's | |
| 9 | application; those belong in SDK code, not in a tool call. | |
| 10 | --- | |
| 11 | ||
| 12 | # Jev judgments over MCP | |
| 13 | ||
| 14 | Jev is a System One model. It returns a typed answer and a calibrated probability | |
| 15 | distribution, never prose. The `jev` MCP server exposes it as five tools. | |
| 16 | ||
| 17 | This skill covers **when to call those tools and how to shape the call**. For the | |
| 18 | underlying theory — primitive semantics, state design, composition patterns — use the | |
| 19 | `typesafe-ai` skill or the live docs at . Do not duplicate | |
| 20 | that material here. | |
| 21 | ||
| 22 | If the `jev_*` tools are not in your tool list, this skill does not apply. Call | |
| 23 | `jev_models` to confirm the key works before assuming a failure is your own. | |
| 24 | ||
| 25 | ## Three-way test | |
| 26 | ||
| 27 | Decide which of these you are in before calling anything. | |
| 28 | ||
| 29 | **Answer it yourself** when the judgment happens once, in conversation, and you can | |
| 30 | explain your reasoning. That is most judgments. An ordinary answer carries an argument | |
| 31 | the user can push back on. Jev returns a number and a label, which is weaker in | |
| 32 | dialogue. Do not reach for a tool to look rigorous. | |
| 33 | ||
| 34 | **Call the tool** when the same judgment repeats across many items, or when you need a | |
| 35 | calibrated number to threshold on. Triaging forty files. Ranking twenty candidates. | |
| 36 | Checking fifteen claims against their sources. Scoring a diff against each requirement | |
| 37 | in a spec. Enumerate the candidates in code or by hand first; Jev only selects among | |
| 38 | what you supply. Nothing is installed and no key is configured; the server holds it. | |
| 39 | ||
| 40 | **Write SDK code** when the judgment runs in the user's application, on their traffic, | |
| 41 | after you are gone. Then it belongs in their repo under test, not in a transcript. | |
| 42 | Follow the `typesafe-ai` skill for that. | |
| 43 | ||
| 44 | Do not call a tool to prototype what will become shipped code. Write the code. | |
| 45 | ||
| 46 | ## Pick the tool | |
| 47 | ||
| 48 | | The answer is | Tool | Notes | | |
| 49 | | --- | --- | --- | | |
| 50 | | One of a set you define | `jev_classify` | You supply `options`. Jev cannot invent one. | | |
| 51 | | A degree on an ordered scale | `jev_score` | You supply `levels`, lowest first, at least two. | | |
| 52 | | Yes or no | `jev_check` | Returns the probability of yes. No separate confidence. | | |
| 53 | | Several questions, one subject | `jev_ask` | Mixes all three types. Up to 64 questions. | | |
| 54 | ||
| 55 | When several labels can be true at once, that is not one `jev_classify`. It is one | |
| 56 | `jev_check` per label, batched through `jev_ask`. | |
| 57 | ||
| 58 | ## Prefer `jev_ask` | |
| 59 | ||
| 60 | Jev prefills the state once and scores every question in one forward pass. Extra | |
| 61 | questions add almost no latency or cost. One batched call beats a loop by a wide | |
| 62 | margin on document-heavy state. | |
| 63 | ||
| 64 | So ask everything you might need, including speculative branches. Questions cannot see | |
| 65 | each other's answers, so state any premise explicitly in the question itself. Let your | |
| 66 | own reasoning decide which answers apply afterwards. | |
| 67 | ||
| 68 | Give each question a stable `id`. The id is never sent to the model, so the question | |
| 69 | must carry its full meaning on its own. | |
| 70 | ||
| 71 | ## Shape the call | |
| 72 | ||
| 73 | Put the evidence in `state`. A string for text, an object or array when it has parts. | |
| 74 | Include what the question needs to be answerable: the diff, the requirement, the | |
| 75 | relevant file, the prior decision. | |
| 76 | ||
| 77 | Put the judgment in `question`. State it in full. This is the only instruction Jev sees. | |
| 78 | ||
| 79 | Describe every option and level concretely. A level must stand on its own without | |
| 80 | reading its neighbours. Weak option descriptions are the most common cause of a bad | |
| 81 | answer. | |
| 82 | ||
| 83 | Leave `add_none` alone unless one option must always apply. The default no-match option | |
| 84 | lets Jev decline rather than being forced into a wrong pick. | |
| 85 | ||
| 86 | ## Read the answer | |
| 87 | ||
| 88 | Never act on the label alone. Every response carries the full distribution. | |
| 89 | ||
| 90 | `jev_classify` and `jev_score` return `action`: `act`, `review`, or `abstain`. It is | |
| 91 | derived from `confidence` against `act_above` and `review_above`. `jev_check` returns | |
| 92 | `verdict`: `yes`, `no`, or `uncertain`. | |
| 93 | ||
| 94 | Treat `review` and `uncertain` as instructions to look at the evidence yourself, not as | |
| 95 | a soft yes. Raise the thresholds when being wrong is expensive, and say that you did. | |
| 96 | ||
| 97 | Confidence measures how concentrated the distribution is. It is not a claim that the | |
| 98 | answer is correct. Two good options split probability and look uncertain. | |
| 99 | ||
| 100 | For `jev_score`, the number is a position on the `levels` array you supplied. Read | |
| 101 | `legend` to map it back. | |
| 102 | ||
| 103 | For `jev_classify`, check `none_option` before interpreting a no-match result. If you | |
| 104 | already used the name `none`, the added option took a different key. | |
| 105 | ||
| 106 | ## Do not | |
| 107 | ||
| 108 | Do not present a Jev answer as fact. Report the answer, the confidence, and what you | |
| 109 | did about it. | |
| 110 | ||
| 111 | Do not batch questions about unrelated subjects. One state, one subject. | |
| 112 | ||
| 113 | Do not use a Score for a category, or a Choice for a degree. | |
| 114 | ||
| 115 | Do not paste secrets, keys, or credentials into `state`. |
src/index.ts +10 −1
| @@ -39,7 +39,16 @@ import { | ||
| 39 | 39 | readPositiveInt, |
| 40 | 40 | } from "./lib.js"; |
| 41 | 41 | |
| 42 | const VERSION = "0.2.0"; | |
| 42 | // Read from package.json so the advertised version cannot drift from the release. | |
| 43 | const VERSION: string = (() => { | |
| 44 | try { | |
| 45 | const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version?: unknown }; | |
| 46 | if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version; | |
| 47 | } catch { | |
| 48 | // fall through | |
| 49 | } | |
| 50 | return "0.0.0"; | |
| 51 | })(); | |
| 43 | 52 | |
| 44 | 53 | // ── Configuration ─────────────────────────────────────────────────────────── |
| 45 | 54 |