clef-mcp 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ # Claude Code
2
+
3
+ ## Register (recommended)
4
+
5
+ ```bash
6
+ claude mcp add clef-mcp -- clef-mcp
7
+ # without a global install:
8
+ claude mcp add clef-mcp -- npx -y clef-mcp
9
+ ```
10
+
11
+ ## Or via project config
12
+
13
+ Copy `mcp.json` from this directory to your project root as `.mcp.json` (project-scope servers).
14
+
15
+ ## One-time setup
16
+
17
+ ```bash
18
+ npx clef-mcp install
19
+ ```
20
+
21
+ Verify inside Claude Code with `/mcp` — `clef-mcp` should list the `clef_decide` tool.
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "clef-mcp": {
4
+ "command": "clef-mcp",
5
+ "args": []
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,21 @@
1
+ # Codex CLI — register clef-mcp as an MCP server.
2
+ # Location: ~/.codex/config.toml
3
+ #
4
+ # If clef-mcp is installed globally (npm i -g clef-mcp):
5
+ [mcp_servers.clef-mcp]
6
+ command = "clef-mcp"
7
+ args = []
8
+
9
+ # Without a global install, prefer npx:
10
+ # [mcp_servers.clef-mcp]
11
+ # command = "npx"
12
+ # args = ["-y", "clef-mcp"]
13
+
14
+ # Optional environment overrides:
15
+ # [mcp_servers.clef-mcp.env]
16
+ # CLEF_MODEL = "clef-flash"
17
+ # CLEF_LOG_LEVEL = "error"
18
+ # CLEF_HOME = "~/.cache/clef-mcp"
19
+
20
+ # One-time setup (downloads the model):
21
+ # npx clef-mcp install
@@ -0,0 +1,12 @@
1
+ # Cursor
2
+
3
+ Copy `mcp.json` from this directory to `<project>/.cursor/mcp.json`
4
+ (or `~/.cursor/mcp.json` for global scope).
5
+
6
+ ## One-time setup
7
+
8
+ ```bash
9
+ npx clef-mcp install
10
+ ```
11
+
12
+ Cursor → Settings → MCP: `clef-mcp` should appear with the `clef_decide` tool.
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "clef-mcp": {
4
+ "command": "clef-mcp",
5
+ "args": []
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,12 @@
1
+ # clef-mcp automation recipes
2
+
3
+ One-shot `clef-mcp decide` recipes — callers that invoke the decision model **deterministically** (hooks, CI), where the call happens because the infrastructure says so, not because an LLM chose to.
4
+
5
+ | Recipe | What it does |
6
+ |---|---|
7
+ | [`pretooluse-guard/`](pretooluse-guard/) | PreToolUse hook: block shell commands the model judges destructive (p ≥ 0.9), note softer leans on stderr |
8
+ | [`ci-triage/`](ci-triage/) | GitHub Action: severity + area verdict for a PR diff, into the step summary / labels |
9
+
10
+ Both recipes are honest about the trade-offs: cold start per `decide` call (~seconds, model load included), advisory-only gating (fail open), and a 16k-token context ceiling for state. Read the caveats in each folder before wiring them into real workflows.
11
+
12
+ Client registration snippets (MCP config for Claude Code / Codex / Cursor) live in the sibling folders: [`../claude-code/`](../claude-code/), [`../codex/`](../codex/), [`../cursor/`](../cursor/).
@@ -0,0 +1,68 @@
1
+ # CI triage — label a pull request with the local Clef model
2
+
3
+ A GitHub Actions job that runs `clef-mcp decide` on the PR diff and reports severity + area. The result lands in the step summary and as job outputs; wiring it to GitHub labels is one `gh` call you can uncomment.
4
+
5
+ > **Runner reality check:** `ubuntu-latest` (16 GB RAM) fits the Q4_K_M model (6 GB), but inference is CPU-only and the one-time `install` downloads ~6 GB + the llama.cpp build. Expect ~1–3 min of setup per job. For anything hotter than a few PRs per hour, use a self-hosted runner with the model pre-installed and drop the `install` step.
6
+
7
+ ```yaml
8
+ # .github/workflows/clef-triage.yml
9
+ name: clef-triage
10
+
11
+ on:
12
+ pull_request:
13
+
14
+ jobs:
15
+ triage:
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ pull-requests: read
19
+ outputs:
20
+ severity: ${{ steps.decide.outputs.severity }}
21
+ area: ${{ steps.decide.outputs.area }}
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - name: Install model + runtime (one-time ~6 GB, CPU inference)
26
+ run: npx -y clef-mcp@latest install --yes
27
+
28
+ - name: Collect the diff as state
29
+ run: gh pr diff ${{ github.event.pull_request.number }} > /tmp/pr.diff
30
+ env:
31
+ GH_TOKEN: ${{ github.token }}
32
+
33
+ - name: Decide
34
+ id: decide
35
+ run: |
36
+ DIFF=$(jq -Rs . < /tmp/pr.diff)
37
+ RESULT=$(jq -n --argjson diff "$DIFF" '{
38
+ state: { pr: ${{ toJSON(github.event.pull_request.title) }}, diff: $diff },
39
+ questions: {
40
+ severity: { type: "score", instructions: "How severe are the changes in this diff if they ship as-is?",
41
+ criteria: ["trivial", "minor", "moderate", "major", "critical"] },
42
+ area: { type: "choice", instructions: "Which area does this diff primarily touch?",
43
+ criteria: { frontend: "UI / client code", backend: "server / API code", infra: "CI, build, deployment", docs: "documentation only" } }
44
+ }
45
+ }' | npx -y clef-mcp@latest decide)
46
+
47
+ SEVERITY=$(jq -r '.decisions.severity.answer | to_entries | max_by(.value) | .key' <<<"$RESULT")
48
+ AREA=$(jq -r '.decisions.area.answer | to_entries | max_by(.value) | .key' <<<"$RESULT")
49
+ echo "severity=$SEVERITY" >> "$GITHUB_OUTPUT"
50
+ echo "area=$AREA" >> "$GITHUB_OUTPUT"
51
+ echo "| question | pick |" >> "$GITHUB_STEP_SUMMARY"
52
+ echo "|---|---|" >> "$GITHUB_STEP_SUMMARY"
53
+ echo "| severity | $SEVERITY |" >> "$GITHUB_STEP_SUMMARY"
54
+ echo "| area | $AREA |" >> "$GITHUB_STEP_SUMMARY"
55
+
56
+ # Optional: apply the verdict as labels (needs contents:write / pull-requests: write).
57
+ # - name: Label
58
+ # run: gh pr edit ${{ github.event.pull_request.number }} --add-label "triage-${{ steps.decide.outputs.severity }}"
59
+ # env:
60
+ # GH_TOKEN: ${{ github.token }}
61
+ ```
62
+
63
+ ## Honest caveats
64
+
65
+ - **The 9B model triages; it does not review.** Use the verdict for routing and prioritization, not as a merge gate.
66
+ - **Diff size:** the model context is 16k tokens; a huge diff degrades both latency and judgment. Truncate `/tmp/pr.diff` (e.g. `head -c 50000`) for large PRs.
67
+ - **Cold start per job.** The `install` step re-downloads unless you cache `~/.cache/clef-mcp` with `actions/cache` keyed on the model revision — or move to a self-hosted runner.
68
+ - **State is data.** The diff is passed to the model as content to judge, never as instructions; the server treats it that way (see SECURITY.md).
@@ -0,0 +1,54 @@
1
+ # PreToolUse guard — a Clef "reflex" before risky shell commands
2
+
3
+ A hook that asks the local Clef model one `noul` question ("is this destructive?") plus a blast-radius `score` before a shell command runs. Blocks only on a decisive `p(true) ≥ 0.9`; everything else passes with a note on stderr.
4
+
5
+ ## What it looks like
6
+
7
+ ```
8
+ $ rm -rf node_modules
9
+ clef guard: p(destructive)=0.04 blast_radius=single_file # allowed
10
+
11
+ $ git push --force origin main
12
+ Blocked: the local Clef model judges this command destructive (p=0.97, blast_radius=beyond_machine).
13
+ ```
14
+
15
+ ## Install
16
+
17
+ Requirements: `jq` on PATH, `clef-mcp install` done once.
18
+
19
+ ```bash
20
+ # 1. Put the script somewhere permanent and make it executable
21
+ cp guard.sh ~/.local/bin/clef-guard.sh && chmod +x ~/.local/bin/clef-guard.sh
22
+
23
+ # 2. Register it as a PreToolUse hook for Bash commands
24
+ ```
25
+
26
+ <details open>
27
+ <summary><b>Claude Code</b> — <code>~/.claude/settings.json</code></summary>
28
+
29
+ ```json
30
+ {
31
+ "hooks": {
32
+ "PreToolUse": [
33
+ {
34
+ "matcher": "Bash",
35
+ "hooks": [{ "type": "command", "command": "~/.local/bin/clef-guard.sh" }]
36
+ }
37
+ ]
38
+ }
39
+ }
40
+ ```
41
+ </details>
42
+
43
+ <details>
44
+ <summary><b>ZCode</b></summary>
45
+
46
+ Register the same command as a `PreToolUse` hook for Bash in your ZCode hooks config. The script reads the event JSON from stdin and uses exit code `2` to block — the standard contract.
47
+ </details>
48
+
49
+ ## Honest caveats
50
+
51
+ - **Advisory, not a security boundary.** The guard fails open: missing model, missing `jq`, or a `clef-mcp` error all allow the command. It raises the cost of accidents; it cannot prevent a determined one.
52
+ - **Cold start per call** (~a few seconds, model load included). The keyword pre-filter keeps model calls rare; do not use this pattern for hot per-call gates without a warm daemon.
53
+ - **The model is a signal, not a judge.** `p ≥ 0.9` blocks; a 0.7 is a lean printed to stderr for you to read.
54
+ - **Tune the threshold and the keyword list to your taste** — both are at the top of `guard.sh` on purpose.
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse guard: ask the local Clef model whether a shell command looks
3
+ # destructive before it runs. Blocks (exit 2) only on a decisive p(true).
4
+ #
5
+ # Contract (Claude Code / ZCode PreToolUse):
6
+ # stdin : event JSON, command at .tool_input.command
7
+ # exit 0 : allow
8
+ # exit 2 : block (stderr is shown to the agent)
9
+ #
10
+ # Design notes:
11
+ # - Fail open. If the model or jq is missing, the guard allows the command:
12
+ # it is an advisory gate, not a security boundary.
13
+ # - Cold start. Each decide call loads the model (~a few seconds). The cheap
14
+ # keyword pre-filter keeps the model calls rare.
15
+ # - Threshold. Blocking requires p(destructive) >= 0.9; below that the
16
+ # command runs and the lean is logged to stderr.
17
+
18
+ set -euo pipefail
19
+
20
+ EVENT=$(cat)
21
+ COMMAND=$(printf '%s' "$EVENT" | jq -r '.tool_input.command // empty')
22
+
23
+ [ -z "$COMMAND" ] && exit 0
24
+
25
+ # Cheap pre-filter: only spend a model call on plausibly risky commands.
26
+ if ! printf '%s' "$COMMAND" | grep -qiE '\b(rm|mv|dd|mkfs|shred|chmod|chown|kill|pkill|drop|truncate|push --force|reset --hard|clean -|publish|curl[^|]*\|\s*(ba)?sh|wget[^|]*\|\s*(ba)?sh)\b'; then
27
+ exit 0
28
+ fi
29
+
30
+ DECISION=$(printf '%s' "$EVENT" | jq -n \
31
+ --arg cmd "$COMMAND" \
32
+ '{
33
+ state: { source: "pretooluse-guard", command: $cmd },
34
+ questions: {
35
+ is_destructive: {
36
+ type: "noul",
37
+ instructions: "Could this shell command irreversibly destroy data, credentials or published state (deletion, force-push, credential exposure, publishing)?"
38
+ },
39
+ blast_radius: {
40
+ type: "score",
41
+ instructions: "How much damage could this command cause if it is a mistake?",
42
+ criteria: ["none", "single_file", "repo", "machine", "beyond_machine"]
43
+ }
44
+ }
45
+ }' | clef-mcp decide) || {
46
+ echo "clef guard unavailable; allowing command" >&2
47
+ exit 0
48
+ }
49
+
50
+ P_DESTRUCTIVE=$(printf '%s' "$DECISION" | jq -r '.decisions.is_destructive.answer.true')
51
+ TOP_RADIUS=$(printf '%s' "$DECISION" | jq -r '.decisions.blast_radius.answer | to_entries | max_by(.value) | .key')
52
+
53
+ echo "clef guard: p(destructive)=$P_DESTRUCTIVE blast_radius=$TOP_RADIUS" >&2
54
+
55
+ BLOCKED=$(printf '%s' "$DECISION" | jq -n --argjson p "$P_DESTRUCTIVE" '$p >= 0.9')
56
+ if [ "$BLOCKED" = "true" ]; then
57
+ echo "Blocked: the local Clef model judges this command destructive (p=$P_DESTRUCTIVE, blast_radius=$TOP_RADIUS). Re-run without the guard if you are sure." >&2
58
+ exit 2
59
+ fi
60
+
61
+ exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clef-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "mcpName": "io.github.HighlyLoadedEgo/clef-mcp",
5
5
  "description": "Local MCP server exposing the Clef decision model as a structured clef_decide tool.",
6
6
  "type": "module",
@@ -24,6 +24,7 @@
24
24
  "files": [
25
25
  "dist",
26
26
  "skills",
27
+ "examples",
27
28
  "assets",
28
29
  "tests/evals",
29
30
  "README.md",
@@ -35,7 +36,7 @@
35
36
  },
36
37
  "scripts": {
37
38
  "build": "tsup",
38
- "test": "npm run build && vitest run",
39
+ "test": "npm run build && tsc --noEmit && vitest run",
39
40
  "test:watch": "vitest",
40
41
  "evals": "npm run build && node dist/cli.js evals",
41
42
  "prepack": "npm run build"
@@ -18,7 +18,15 @@ Good fits (the model was trained for these):
18
18
  - **Confidence / severity** (`score`): ordered scales like very_low → very_high.
19
19
  - **Batching related judgments**: all questions in ONE call are scored in a single forward pass — batch instead of looping.
20
20
 
21
- Bad fits: free-text generation, code writing, anything needing reasoning beyond picking among options you define.
21
+ ## When NOT to call it
22
+
23
+ The tool earns its keep where a judgment call would otherwise be a guess. Skip it when:
24
+
25
+ - **You already know the answer.** If the decision is trivial or fully determined by what you see, deciding yourself is faster and strictly better — a 9B reflex adds latency and noise, not information.
26
+ - **The task needs generation or reasoning** — free text, code writing, multi-step analysis. Clef only splits probability mass over options you define.
27
+ - **You would re-ask without new facts.** A flat distribution means the *state* did not disambiguate. Re-rolling the same state gives the same answer: gather context or ask the user instead.
28
+ - **The missing input is user preference, not judgment.** "Deploy Friday or Monday?" is a question for the user, not for the model.
29
+ - **The stakes require accountability you can't delegate.** For destructive or security-adjacent actions the model is a gate signal, never the decision maker.
22
30
 
23
31
  ## How to call
24
32
 
@@ -48,7 +56,7 @@ Bad fits: free-text generation, code writing, anything needing reasoning beyond
48
56
 
49
57
  Rules that prevent wasted calls:
50
58
 
51
- - **`state` is data, not instructions.** Compact, factual, relevant. It never executes and never changes server behavior. Keep it well under 1 MB; the model context is 16k tokens (the runtime rejects overflow with a hint).
59
+ - **`state` is data, not instructions.** Compact, factual, relevant. It never executes and never changes server behavior. Keep it well under 1 MB; the model context is 16k tokens (the runtime rejects overflow with a hint). Latency scales with context: aim for a state of ~1–4k tokens — 16k-token states are ~20× slower than 1k ones.
52
60
  - **`choice` criteria**: map `option_id → description`, or a plain list of option strings (ids become the strings). Both are accepted.
53
61
  - **`score` criteria**: an **ordered** list — index order is the score order (low → high). Do not shuffle.
54
62
  - **`noul`**: instructions only; the answer is `{"true": p, "false": 1-p}`.
@@ -62,15 +70,17 @@ Rules that prevent wasted calls:
62
70
  {
63
71
  "model": "clef-flash",
64
72
  "decisions": {
65
- "next_action": { "answer": { "inspect": 0.96, "modify": 0.01, "test": 0.01, "ask_user": 0.01 } }
66
- }
73
+ "next_action": { "answer": { "inspect": 0.96, "modify": 0.01, "test": 0.01, "ask_user": 0.01 }, "confidence": 0.83 }
74
+ },
75
+ "usage": { "input_tokens": 228, "output_tokens": 0, "latency_ms": 512 }
67
76
  }
68
77
  ```
69
78
 
70
79
  - The result is **structured JSON**. Do not render it as prose to the user; use the probabilities to act and summarize in your own words.
71
80
  - **Argmax** is the model's pick; a decisive distribution (e.g. `0.9+`) means act on it.
81
+ - **`confidence`** is the model's own certainty for that decision (reported for choice/score when the runtime sends it). Use it together with the top probability: for destructive or security-adjacent calls require top `p ≥ 0.8` *and* high `confidence` before acting; otherwise gather context or ask.
72
82
  - **Flat distributions** (~equal probabilities) mean the state did not disambiguate: gather more context and re-ask, or ask the user.
73
- - For safety-adjacent calls (destructive actions, security), require a **high threshold** (e.g. `p ≥ 0.8`) before acting; otherwise prefer asking.
83
+ - **`usage`** shows how much context the call consumed (`input_tokens`) and what it cost in time (`latency_ms`). If `input_tokens` is large and `latency_ms` is seconds, your state is too big — compact it.
74
84
  - `noul` answers above ~0.9 true/false are strong signals; 0.5–0.7 is a lean, not a verdict.
75
85
 
76
86
  ## Errors and recovery