protect-mcp 0.7.4 → 0.7.6

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.
package/dist/cli.mjs CHANGED
@@ -11,11 +11,11 @@ import {
11
11
  readInstalledConnectorPilots,
12
12
  simulate,
13
13
  writeConnectorPilots
14
- } from "./chunk-36UID5WY.mjs";
14
+ } from "./chunk-JCMDLN5I.mjs";
15
15
  import {
16
16
  ProtectGateway,
17
17
  validateCredentials
18
- } from "./chunk-UBZJ3VI2.mjs";
18
+ } from "./chunk-VTPZ4G5I.mjs";
19
19
  import {
20
20
  buildActionReadback,
21
21
  evaluateCedar,
@@ -26,7 +26,7 @@ import {
26
26
  policySetFromSource,
27
27
  runEvaluatorSelfTest,
28
28
  signDecision
29
- } from "./chunk-D2RDY2JR.mjs";
29
+ } from "./chunk-WIPWNWMJ.mjs";
30
30
  import "./chunk-PQJP2ZCI.mjs";
31
31
 
32
32
  // src/cli.ts
@@ -36,7 +36,7 @@ import { basename as basenameCli, dirname as dirnameCli, join as joinCli, resolv
36
36
  import { homedir as homedirCli } from "os";
37
37
  function printHelp() {
38
38
  process.stderr.write(`
39
- protect-mcp \u2014 Enterprise security gateway for MCP servers & Claude Code hooks
39
+ protect-mcp: Enterprise security gateway for MCP servers & Claude Code hooks
40
40
 
41
41
  Usage:
42
42
  protect-mcp [options] -- <command> [args...]
@@ -59,6 +59,7 @@ Usage:
59
59
  protect-mcp status [--dir <path>]
60
60
  protect-mcp digest [--today] [--dir <path>]
61
61
  protect-mcp receipts [--last <n>] [--dir <path>]
62
+ protect-mcp record [--dir <path>] [--live] [--no-open]
62
63
  protect-mcp bundle [--output <path>] [--dir <path>]
63
64
  protect-mcp simulate --policy <path> [--log <path>] [--tier <tier>] [--json]
64
65
  protect-mcp report [--period <days>d] [--format md|json] [--output <path>] [--dir <path>]
@@ -96,6 +97,7 @@ Commands:
96
97
  status Show tool call statistics from the local decision log
97
98
  digest Generate a human-readable summary of agent activity
98
99
  receipts Show recent persisted signed receipts
100
+ record Open a local, searchable view of your record in the browser
99
101
  bundle Export an offline-verifiable audit bundle
100
102
 
101
103
  Examples:
@@ -194,7 +196,7 @@ async function handleInit(argv) {
194
196
  let keypair;
195
197
  {
196
198
  const { randomBytes } = await import("crypto");
197
- const { ed25519 } = await import("./ed25519-DZMMNNVE.mjs");
199
+ const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
198
200
  const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
199
201
  const privateKey = randomBytes(32);
200
202
  const publicKey = ed25519.getPublicKey(privateKey);
@@ -1942,6 +1944,214 @@ ${bold("\u{1F6E1}\uFE0F Recent Receipts")} (last ${recent.length})
1942
1944
  process.stdout.write(`
1943
1945
  `);
1944
1946
  }
1947
+ var _pkgV = null;
1948
+ async function pkgVersion() {
1949
+ if (_pkgV) return _pkgV;
1950
+ let v = "0.0.0";
1951
+ try {
1952
+ const { readFileSync, existsSync, realpathSync } = await import("fs");
1953
+ const { dirname, join, resolve } = await import("path");
1954
+ let base = "";
1955
+ try {
1956
+ base = dirname(realpathSync(resolve(process.argv[1] || "")));
1957
+ } catch {
1958
+ }
1959
+ const candidates = [
1960
+ base ? join(base, "..", "package.json") : "",
1961
+ base ? join(base, "package.json") : ""
1962
+ ].filter(Boolean);
1963
+ for (const p of candidates) {
1964
+ if (existsSync(p)) {
1965
+ const parsed = JSON.parse(readFileSync(p, "utf-8"));
1966
+ if (parsed && parsed.name === "protect-mcp" && parsed.version) {
1967
+ v = parsed.version;
1968
+ break;
1969
+ }
1970
+ }
1971
+ }
1972
+ } catch {
1973
+ }
1974
+ _pkgV = v;
1975
+ return v;
1976
+ }
1977
+ function mapRecordEntry(e) {
1978
+ const p = e && e.payload && typeof e.payload === "object" ? e.payload : e;
1979
+ const dec = String(p.decision || e.decision || "").toLowerCase();
1980
+ const verdict = /den|block|reject|refus/.test(dec) ? "blocked" : /ask|approv|hold|escal|review|pending/.test(dec) ? "held" : "allowed";
1981
+ const tsRaw = e.issued_at || e.timestamp || p.timestamp || p.issued_at;
1982
+ const ms = typeof tsRaw === "number" ? tsRaw : typeof tsRaw === "string" ? Date.parse(tsRaw) : NaN;
1983
+ const ts = isFinite(ms) ? new Date(ms).toISOString() : "";
1984
+ const tool = String(p.tool || e.tool || "action");
1985
+ const reason = String(p.reason_code || e.reason_code || p.policy_engine || "signed");
1986
+ const hook = String(p.hook_event || e.hook_event || "");
1987
+ const signed = !!(e.signature || e.sig || e.receipt_hash || typeof e.type === "string" && e.type.indexOf("receipt") >= 0);
1988
+ let digest = "";
1989
+ if (e.receipt_hash) digest = String(e.receipt_hash);
1990
+ else if (e.digest) digest = String(e.digest);
1991
+ else if (p.payload_digest && p.payload_digest.output_hash) digest = String(p.payload_digest.output_hash);
1992
+ return { ts, tool, verdict, reason, hook, signed, id: String(e.request_id || p.request_id || ""), digest };
1993
+ }
1994
+ async function handleRecord(argv) {
1995
+ const { readFileSync, existsSync, writeFileSync } = await import("fs");
1996
+ const { join } = await import("path");
1997
+ const osMod = await import("os");
1998
+ const cp = await import("child_process");
1999
+ let dir = process.cwd();
2000
+ const di = argv.indexOf("--dir");
2001
+ if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
2002
+ const recPath = join(dir, ".protect-mcp-receipts.jsonl");
2003
+ const logPath = join(dir, ".protect-mcp-log.jsonl");
2004
+ const pick = () => existsSync(recPath) ? recPath : existsSync(logPath) ? logPath : null;
2005
+ const chosen = pick();
2006
+ if (!chosen) {
2007
+ process.stderr.write(`
2008
+ ${bold("protect-mcp record")}
2009
+
2010
+ No record found in ${dir}.
2011
+ Start the gate with ${bold("npx protect-mcp serve")}, use your agent, then run this again.
2012
+ `);
2013
+ process.stderr.write(`Tip: run this in the directory where your gate is signing (where .protect-mcp-receipts.jsonl lives), or pass ${bold("--dir <path>")}.
2014
+
2015
+ `);
2016
+ process.exit(0);
2017
+ return;
2018
+ }
2019
+ const readRecs = (file) => readFileSync(file, "utf-8").split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((l) => {
2020
+ try {
2021
+ return JSON.parse(l);
2022
+ } catch {
2023
+ return null;
2024
+ }
2025
+ }).filter((x) => x !== null).map(mapRecordEntry);
2026
+ const openTarget = (target) => {
2027
+ if (argv.includes("--no-open")) return;
2028
+ const platform = process.platform;
2029
+ const opener = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
2030
+ const openArgs = platform === "win32" ? ["/c", "start", "", target] : [target];
2031
+ try {
2032
+ const child = cp.spawn(opener, openArgs, { stdio: "ignore", detached: true });
2033
+ child.unref();
2034
+ } catch {
2035
+ }
2036
+ };
2037
+ if (argv.includes("--live") || argv.includes("--watch")) {
2038
+ const http = await import("http");
2039
+ const pi = argv.indexOf("--port");
2040
+ const port = pi !== -1 && argv[pi + 1] ? parseInt(argv[pi + 1], 10) : 9378;
2041
+ const server = http.createServer((req, res) => {
2042
+ if (req.url && req.url.indexOf("/data") === 0) {
2043
+ let recs2 = [];
2044
+ const f = pick();
2045
+ try {
2046
+ if (f) recs2 = readRecs(f);
2047
+ } catch {
2048
+ }
2049
+ res.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
2050
+ res.end(JSON.stringify({ recs: recs2, signed: f === recPath }));
2051
+ return;
2052
+ }
2053
+ const meta2 = { file: chosen, signed: pick() === recPath, count: 0, live: true };
2054
+ const page = RECORD_HTML.replace("__DATA__", () => "[]").replace("__META__", () => JSON.stringify(meta2));
2055
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
2056
+ res.end(page);
2057
+ });
2058
+ server.on("error", (e) => {
2059
+ process.stderr.write(`
2060
+ protect-mcp record --live: could not start on port ${port}${e && e.code ? ` (${e.code})` : ""}. Try ${bold("--port <n>")}.
2061
+
2062
+ `);
2063
+ process.exit(1);
2064
+ });
2065
+ server.listen(port, "127.0.0.1", () => {
2066
+ const url = `http://127.0.0.1:${port}`;
2067
+ openTarget(url);
2068
+ process.stdout.write(`
2069
+ ${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} live at ${url}
2070
+ `);
2071
+ process.stdout.write(` Updates as your agent runs. All local, nothing uploaded. ${dim("Ctrl-C to stop.")}
2072
+
2073
+ `);
2074
+ });
2075
+ return;
2076
+ }
2077
+ const recs = readRecs(chosen);
2078
+ const meta = { file: chosen, signed: chosen === recPath, count: recs.length, live: false };
2079
+ const html = RECORD_HTML.replace("__DATA__", () => JSON.stringify(recs)).replace("__META__", () => JSON.stringify(meta));
2080
+ const out = join(osMod.tmpdir(), "protect-mcp-record-" + Date.now() + ".html");
2081
+ writeFileSync(out, html);
2082
+ openTarget(out);
2083
+ process.stdout.write(`
2084
+ ${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} ${recs.length} decision${recs.length === 1 ? "" : "s"}, all on this machine
2085
+ `);
2086
+ if (!meta.signed) process.stdout.write(` ${dim("(decision log; signed receipts appear in .protect-mcp-receipts.jsonl once signing is on)")}
2087
+ `);
2088
+ const fileUrl = "file://" + encodeURI(out);
2089
+ if (process.stdout.isTTY) {
2090
+ process.stdout.write(` Opened in your browser. If it did not open, click: \x1B]8;;${fileUrl}\x1B\\${bold("your record")}\x1B]8;;\x1B\\
2091
+ `);
2092
+ } else {
2093
+ process.stdout.write(` Opened in your browser. If it did not open, open: ${out}
2094
+ `);
2095
+ }
2096
+ process.stdout.write(` ${dim("Want it to update live as your agent runs? npx protect-mcp record --live")}
2097
+
2098
+ `);
2099
+ process.exit(0);
2100
+ }
2101
+ var RECORD_HTML = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>protect-mcp record</title>
2102
+ <style>
2103
+ :root{--paper:#f6f4ef;--ink:#1b1815;--soft:#524d46;--faint:#8a837a;--line:#ddd7c9;--g:#3f6146;--gb:#e7eee3;--a:#8f6216;--ab:#f2e8d3;--r:#7d3535;--rb:#f2e0dc}
2104
+ *{box-sizing:border-box}
2105
+ body{margin:0;background:var(--paper);color:var(--ink);font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;-webkit-font-smoothing:antialiased}
2106
+ .wrap{max-width:1000px;margin:0 auto;padding:26px 22px 60px}
2107
+ h1{font-size:24px;margin:0 0 4px;letter-spacing:-.012em}
2108
+ .meta{color:var(--faint);font-size:12.5px;font-family:ui-monospace,Menlo,Consolas,monospace}
2109
+ .bar{margin:18px 0 12px}
2110
+ input{width:100%;padding:10px 13px;border:1px solid var(--line);border-radius:9px;background:#fff;font:inherit}
2111
+ .chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px}
2112
+ .chip{cursor:pointer;font-size:12px;padding:3px 10px;border-radius:100px;border:1px solid var(--line);background:#fff;color:var(--soft)}
2113
+ .chip.on{background:var(--ink);color:var(--paper);border-color:var(--ink)}
2114
+ .count{color:var(--faint);font-size:12px;font-family:ui-monospace,Menlo,monospace;margin-bottom:8px}
2115
+ .row{border:1px solid var(--line);border-radius:9px;background:#fcfbf7;padding:11px 13px;margin-bottom:8px;cursor:pointer}
2116
+ .top{display:flex;gap:9px;align-items:center;flex-wrap:wrap}
2117
+ .pill{font-size:11px;font-weight:600;padding:2px 9px;border-radius:100px}
2118
+ .pill.allowed{background:var(--gb);color:var(--g)}.pill.held{background:var(--ab);color:var(--a)}.pill.blocked{background:var(--rb);color:var(--r)}
2119
+ .tag{font-size:11px;padding:1px 7px;border-radius:100px;background:var(--paper);border:1px solid var(--line);color:var(--faint)}
2120
+ .when{margin-left:auto;font-size:12px;color:var(--faint);font-family:ui-monospace,Menlo,monospace}
2121
+ .det{margin-top:8px;padding-top:8px;border-top:1px solid var(--line);font-size:12px;color:var(--soft);font-family:ui-monospace,Menlo,monospace;white-space:pre-wrap;word-break:break-all;display:none}
2122
+ .row.open .det{display:block}
2123
+ .foot{margin-top:22px;color:var(--faint);font-size:12px;line-height:1.6;border-top:1px solid var(--line);padding-top:14px}
2124
+ .foot b{color:var(--soft)}
2125
+ </style></head><body><div class="wrap">
2126
+ <h1>Your record</h1>
2127
+ <div class="meta" id="meta"></div>
2128
+ <div class="bar"><input id="q" placeholder="Search your record: tool, reason, verdict"></div>
2129
+ <div class="chips" id="chips"></div>
2130
+ <div class="count" id="count"></div>
2131
+ <div id="list"></div>
2132
+ <div class="foot">Signed decisions from your own gate. This page is local and nothing was uploaded. Verify authoritatively with <b>npx protect-mcp receipts</b> or the open verifier. protect-mcp governs proposed actions before they run.</div>
2133
+ </div>
2134
+ <script>
2135
+ var RECORDS=__DATA__;var META=__META__;var Q="",ACT={};
2136
+ function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c]})}
2137
+ function vlabel(v){return v==="allowed"?"Allowed":v==="held"?"Held":"Blocked"}
2138
+ function when(ts){if(!ts)return"";var d=new Date(ts);return d.toLocaleDateString(undefined,{month:"short",day:"numeric"})+" "+d.toLocaleTimeString(undefined,{hour:"2-digit",minute:"2-digit"})}
2139
+ function fvals(key){var m={};RECORDS.forEach(function(r){var v=key==="Decision"?vlabel(r.verdict):r[key.toLowerCase()];if(v){m[v]=(m[v]||0)+1}});return Object.keys(m).sort(function(a,b){return m[b]-m[a]}).slice(0,8).map(function(v){return[v,m[v]]})}
2140
+ function match(r){if(ACT.Decision&&vlabel(r.verdict)!==ACT.Decision)return false;if(ACT.Tool&&r.tool!==ACT.Tool)return false;if(ACT.Reason&&r.reason!==ACT.Reason)return false;if(Q){var h=(r.tool+" "+r.reason+" "+vlabel(r.verdict)+" "+r.hook).toLowerCase();if(h.indexOf(Q)<0)return false}return true}
2141
+ function render(){
2142
+ document.getElementById("meta").textContent=META.count+" decisions from "+META.file+(META.signed?" (signed)":" (decision log)")+" - all local"+(META.live?" \xB7 live, updating":"");
2143
+ var chips="";["Decision","Tool","Reason"].forEach(function(key){fvals(key).forEach(function(p){var on=ACT[key]===p[0];chips+='<span class="chip'+(on?" on":"")+'" data-k="'+key+'" data-v="'+esc(p[0])+'">'+esc(p[0])+" "+p[1]+"</span>"})});
2144
+ document.getElementById("chips").innerHTML=chips;
2145
+ var rows=RECORDS.filter(match);
2146
+ document.getElementById("count").textContent=rows.length+" of "+RECORDS.length+" records";
2147
+ var html="";rows.slice(0,800).forEach(function(r){html+='<div class="row"><div class="top"><span class="pill '+r.verdict+'">'+vlabel(r.verdict)+"</span><b>"+esc(r.tool)+'</b><span class="tag">'+esc(r.reason)+"</span>"+(r.hook?'<span class="tag">'+esc(r.hook)+"</span>":"")+'<span class="tag">'+(r.signed?"signed":"log")+'</span><span class="when">'+esc(when(r.ts))+'</span></div><div class="det">'+esc(JSON.stringify(r,null,2))+"</div></div>"});
2148
+ document.getElementById("list").innerHTML=html||'<p style="color:#8a837a">No records match.</p>'}
2149
+ document.getElementById("q").addEventListener("input",function(e){Q=e.target.value.toLowerCase().trim();render()});
2150
+ document.getElementById("chips").addEventListener("click",function(e){var c=e.target.closest(".chip");if(!c)return;var k=c.getAttribute("data-k"),v=c.getAttribute("data-v");ACT[k]=ACT[k]===v?undefined:v;render()});
2151
+ document.getElementById("list").addEventListener("click",function(e){var row=e.target.closest(".row");if(row)row.classList.toggle("open")});
2152
+ render();
2153
+ if(META.live){var poll=function(){fetch('/data',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){RECORDS=d.recs||[];META.count=RECORDS.length;if(typeof d.signed==='boolean')META.signed=d.signed;render()}).catch(function(){})};poll();setInterval(poll,2000);}
2154
+ </script></body></html>`;
1945
2155
  async function handleBundle(argv) {
1946
2156
  const { readFileSync, writeFileSync, existsSync } = await import("fs");
1947
2157
  const { join } = await import("path");
@@ -2063,7 +2273,7 @@ ${bold("protect-mcp connect")}
2063
2273
  `));
2064
2274
  process.stderr.write(` Receipts will be uploaded automatically.
2065
2275
  `);
2066
- process.stderr.write(dim(` Free tier: 20,000 receipts/month \u2014 no credit card required.
2276
+ process.stderr.write(dim(` Free tier: 20,000 receipts/month, no credit card required.
2067
2277
  `));
2068
2278
  process.stderr.write(`
2069
2279
  ${"\u2500".repeat(50)}
@@ -2106,7 +2316,7 @@ ${bold("protect-mcp quickstart")}
2106
2316
  const { randomBytes } = await import("crypto");
2107
2317
  let keypair;
2108
2318
  try {
2109
- const { ed25519 } = await import("./ed25519-DZMMNNVE.mjs");
2319
+ const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
2110
2320
  const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
2111
2321
  const privateKey = randomBytes(32);
2112
2322
  const publicKey = ed25519.getPublicKey(privateKey);
@@ -2161,7 +2371,7 @@ ${bold("protect-mcp quickstart")}
2161
2371
  `));
2162
2372
  process.stdout.write(` Receipts will be uploaded automatically.
2163
2373
  `);
2164
- process.stdout.write(dim(` Free tier: 20,000 receipts/month \u2014 no credit card required.
2374
+ process.stdout.write(dim(` Free tier: 20,000 receipts/month, no credit card required.
2165
2375
  `));
2166
2376
  process.stdout.write(`
2167
2377
  `);
@@ -2321,7 +2531,7 @@ ${bold("protect-mcp registry status")}
2321
2531
  async function handleKillerDemo(argv) {
2322
2532
  const { mkdtempSync } = await import("fs");
2323
2533
  const { tmpdir } = await import("os");
2324
- const { ed25519 } = await import("./ed25519-DZMMNNVE.mjs");
2534
+ const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
2325
2535
  const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
2326
2536
  const { randomBytes } = await import("crypto");
2327
2537
  const artifacts = await import("@veritasacta/artifacts");
@@ -2329,7 +2539,7 @@ async function handleKillerDemo(argv) {
2329
2539
  createSelectiveDisclosurePackage,
2330
2540
  signCommittedDecision,
2331
2541
  verifySelectiveDisclosurePackage
2332
- } = await import("./signing-committed-MMLJ6OGG.mjs");
2542
+ } = await import("./signing-committed-QXCW24RF.mjs");
2333
2543
  const registryMod = await import("./receipt-registry-6CAOY6RP.mjs");
2334
2544
  const dir = resolveCli(flagValue(argv, "--dir") || mkdtempSync(joinCli(tmpdir(), "scopeblind-killer-demo-")));
2335
2545
  mkdirSyncCli(dir, { recursive: true });
@@ -2598,7 +2808,7 @@ async function handleVerifyDisclosure(argv) {
2598
2808
  process.stderr.write("Usage: protect-mcp verify-disclosure --receipt <committed-receipt.json> --disclosure <selective-disclosure.json>\\n");
2599
2809
  process.exit(1);
2600
2810
  }
2601
- const { verifySelectiveDisclosurePackage } = await import("./signing-committed-MMLJ6OGG.mjs");
2811
+ const { verifySelectiveDisclosurePackage } = await import("./signing-committed-QXCW24RF.mjs");
2602
2812
  const receipt = JSON.parse(readFileSyncCli(resolveCli(receiptPath), "utf-8"));
2603
2813
  const disclosure = JSON.parse(readFileSyncCli(resolveCli(disclosurePath), "utf-8"));
2604
2814
  const result = verifySelectiveDisclosurePackage(receipt, disclosure);
@@ -2910,7 +3120,7 @@ ${bold("protect-mcp trace")}
2910
3120
  process.stdout.write(`${prefix}${connector}${typeEmoji} ${bold(type)} ${dim(shortId)} ${dim(time)}
2911
3121
  `);
2912
3122
  if (rendered.has(id)) {
2913
- process.stdout.write(`${prefix}${childPrefix}${dim("(cycle \u2014 already rendered)")}
3123
+ process.stdout.write(`${prefix}${childPrefix}${dim("(cycle: already rendered)")}
2914
3124
  `);
2915
3125
  return;
2916
3126
  }
@@ -3094,7 +3304,7 @@ ${bold("protect-mcp init-hooks")}
3094
3304
  if (!existsSync(keysDir)) mkdirSync(keysDir, { recursive: true });
3095
3305
  const { randomBytes: rb } = await import("crypto");
3096
3306
  try {
3097
- const { ed25519 } = await import("./ed25519-DZMMNNVE.mjs");
3307
+ const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
3098
3308
  const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
3099
3309
  const privateKey = rb(32);
3100
3310
  const publicKey = ed25519.getPublicKey(privateKey);
@@ -3113,7 +3323,7 @@ ${bold("protect-mcp init-hooks")}
3113
3323
 
3114
3324
  `);
3115
3325
  } catch {
3116
- process.stdout.write(` ${yellow("\u26A0")} Could not generate Ed25519 keys \u2014 signing disabled
3326
+ process.stdout.write(` ${yellow("\u26A0")} Could not generate Ed25519 keys, signing disabled
3117
3327
 
3118
3328
  `);
3119
3329
  }
@@ -3184,13 +3394,14 @@ ${bold("protect-mcp init-hooks")}
3184
3394
  process.stdout.write(` Every tool call will be receipted automatically.
3185
3395
 
3186
3396
  `);
3187
- process.stdout.write(` 3. Check receipts:
3397
+ process.stdout.write(` 3. See your record: a searchable view of every decision.
3188
3398
  `);
3189
- process.stdout.write(` ${dim(`curl http://127.0.0.1:${port}/receipts/latest`)}
3399
+ process.stdout.write(` ${dim(`npx protect-mcp record`)}
3190
3400
  `);
3191
- process.stdout.write(` ${dim(`npx protect-mcp receipts`)}
3401
+ process.stdout.write(` ${dim(`Everything stays on this machine. Nothing is uploaded.`)}
3402
+
3192
3403
  `);
3193
- process.stdout.write(` Or use ${dim("/verify-receipt")} inside Claude Code.
3404
+ process.stdout.write(` Prefer the terminal? ${dim(`npx protect-mcp receipts`)}, or ${dim("/verify-receipt")} in Claude Code.
3194
3405
 
3195
3406
  `);
3196
3407
  process.stdout.write(` 4. View policy suggestions:
@@ -3200,9 +3411,9 @@ ${bold("protect-mcp init-hooks")}
3200
3411
  `);
3201
3412
  process.stdout.write(`${bold("Key facts:")}
3202
3413
  `);
3203
- process.stdout.write(` \u2022 deny decisions are ${bold("AUTHORITATIVE")} \u2014 cannot be overridden
3414
+ process.stdout.write(` \u2022 deny decisions are ${bold("AUTHORITATIVE")}: they cannot be overridden
3204
3415
  `);
3205
- process.stdout.write(` \u2022 PostToolUse runs ${bold("async")} \u2014 zero latency impact on tool execution
3416
+ process.stdout.write(` \u2022 PostToolUse runs ${bold("async")}, so there is zero latency impact on tool execution
3206
3417
  `);
3207
3418
  process.stdout.write(` \u2022 Receipts are Ed25519-signed and append-only
3208
3419
  `);
@@ -3221,15 +3432,7 @@ async function sendInstallTelemetry() {
3221
3432
  if (existsSync(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
3222
3433
  return;
3223
3434
  }
3224
- let version = "unknown";
3225
- try {
3226
- const thisDir = dirname(fileURLToPath(import.meta.url));
3227
- const pkgPath = join(thisDir, "..", "package.json");
3228
- if (existsSync(pkgPath)) {
3229
- version = JSON.parse(readFileSync(pkgPath, "utf-8")).version;
3230
- }
3231
- } catch {
3232
- }
3435
+ const version = await pkgVersion();
3233
3436
  const controller = new AbortController();
3234
3437
  const timeout = setTimeout(() => controller.abort(), 3e3);
3235
3438
  fetch("https://api.scopeblind.com/telemetry/install", {
@@ -3403,6 +3606,7 @@ async function main() {
3403
3606
  sendInstallTelemetry().catch(() => {
3404
3607
  });
3405
3608
  const args = process.argv.slice(2);
3609
+ process.env.PROTECT_MCP_VERSION = process.env.PROTECT_MCP_VERSION || await pkgVersion();
3406
3610
  if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
3407
3611
  printHelp();
3408
3612
  process.exit(0);
@@ -3441,6 +3645,10 @@ async function main() {
3441
3645
  await startHookServer({ port, policyPath: policyPath2, cedarDir: cedarDir2, enforce: enforce2, verbose: verbose2 });
3442
3646
  return;
3443
3647
  }
3648
+ if (args[0] === "record") {
3649
+ await handleRecord(args.slice(1));
3650
+ return;
3651
+ }
3444
3652
  if (args[0] === "init-hooks") {
3445
3653
  await handleInitHooks(args.slice(1));
3446
3654
  process.exit(0);
@@ -3629,7 +3837,7 @@ async function main() {
3629
3837
  if (useHttp) {
3630
3838
  const portIdx = args.indexOf("--port");
3631
3839
  const httpPort = portIdx >= 0 && args[portIdx + 1] ? parseInt(args[portIdx + 1]) : 3e3;
3632
- const { startHttpTransport } = await import("./http-transport-4GMMRPW7.mjs");
3840
+ const { startHttpTransport } = await import("./http-transport-JBORN27G.mjs");
3633
3841
  startHttpTransport({ port: httpPort, config, serverCommand: childCommand });
3634
3842
  return;
3635
3843
  }
@@ -3698,7 +3906,7 @@ async function handleDoctor() {
3698
3906
  process.stdout.write(green2(`Node.js ${nodeVersion}
3699
3907
  `));
3700
3908
  } else {
3701
- process.stdout.write(red2(`Node.js ${nodeVersion} \u2014 requires >= 18
3909
+ process.stdout.write(red2(`Node.js ${nodeVersion}, requires >= 18
3702
3910
  `));
3703
3911
  issues++;
3704
3912
  }
@@ -3709,7 +3917,7 @@ async function handleDoctor() {
3709
3917
  if (config.signing?.private_key || config.signing?.key_file) {
3710
3918
  process.stdout.write(green2("Signing keys configured\n"));
3711
3919
  } else {
3712
- process.stdout.write(yellow2("Config found but no signing keys \u2014 run: protect-mcp init\n"));
3920
+ process.stdout.write(yellow2("Config found but no signing keys. Run: protect-mcp init\n"));
3713
3921
  issues++;
3714
3922
  }
3715
3923
  } catch {
@@ -3717,7 +3925,7 @@ async function handleDoctor() {
3717
3925
  issues++;
3718
3926
  }
3719
3927
  } else {
3720
- process.stdout.write(yellow2("No scopeblind.config.json \u2014 run: protect-mcp init\n"));
3928
+ process.stdout.write(yellow2("No scopeblind.config.json. Run: protect-mcp init\n"));
3721
3929
  }
3722
3930
  let policyFound = false;
3723
3931
  for (const dir of ["cedar", "policies", "."]) {
@@ -3742,14 +3950,14 @@ async function handleDoctor() {
3742
3950
  }
3743
3951
  }
3744
3952
  if (!policyFound) {
3745
- process.stdout.write(yellow2("No policy files found \u2014 running in shadow mode (allow all)\n"));
3953
+ process.stdout.write(yellow2("No policy files found, running in shadow mode (allow all)\n"));
3746
3954
  }
3747
3955
  try {
3748
3956
  const cedarAvailable = await isCedarAvailable();
3749
3957
  if (cedarAvailable) {
3750
3958
  process.stdout.write(green2("Cedar WASM engine available\n"));
3751
3959
  } else {
3752
- process.stdout.write(dim2(" Cedar WASM not installed \u2014 install: npm install @cedar-policy/cedar-wasm\n"));
3960
+ process.stdout.write(dim2(" Cedar WASM not installed. Install: npm install @cedar-policy/cedar-wasm\n"));
3753
3961
  }
3754
3962
  } catch {
3755
3963
  process.stdout.write(dim2(" Cedar WASM not installed\n"));
@@ -3765,7 +3973,7 @@ async function handleDoctor() {
3765
3973
  process.stdout.write(green2("Decision log exists\n"));
3766
3974
  }
3767
3975
  } else {
3768
- process.stdout.write(dim2(" No decision log yet \u2014 will be created on first tool call\n"));
3976
+ process.stdout.write(dim2(" No decision log yet, will be created on first tool call\n"));
3769
3977
  }
3770
3978
  if (existsSync(receiptFile)) {
3771
3979
  try {
@@ -3780,17 +3988,17 @@ async function handleDoctor() {
3780
3988
  execSync("npx @veritasacta/verify --version 2>/dev/null", { stdio: "pipe", timeout: 1e4 });
3781
3989
  process.stdout.write(green2("Verifier available: @veritasacta/verify\n"));
3782
3990
  } catch {
3783
- process.stdout.write(dim2(" Verifier not cached \u2014 install: npm install -g @veritasacta/verify\n"));
3991
+ process.stdout.write(dim2(" Verifier not cached. Install: npm install -g @veritasacta/verify\n"));
3784
3992
  }
3785
3993
  try {
3786
3994
  const res = await fetch("https://api.scopeblind.com/health", { signal: AbortSignal.timeout(5e3) });
3787
3995
  if (res.ok) {
3788
3996
  process.stdout.write(green2("ScopeBlind API reachable\n"));
3789
3997
  } else {
3790
- process.stdout.write(yellow2("ScopeBlind API returned non-200 \u2014 receipts will be stored locally\n"));
3998
+ process.stdout.write(yellow2("ScopeBlind API returned non-200, receipts will be stored locally\n"));
3791
3999
  }
3792
4000
  } catch {
3793
- process.stdout.write(dim2(" ScopeBlind API not reachable \u2014 offline mode (receipts stored locally)\n"));
4001
+ process.stdout.write(dim2(" ScopeBlind API not reachable, offline mode (receipts stored locally)\n"));
3794
4002
  }
3795
4003
  process.stdout.write("\nRestraint self-test:\n");
3796
4004
  try {
@@ -35115,7 +35115,7 @@ function handleRequest(request) {
35115
35115
  id: request.id,
35116
35116
  result: {
35117
35117
  protocolVersion: "2024-11-05",
35118
- serverInfo: { name: "protect-mcp-demo", version: "0.5.3" },
35118
+ serverInfo: { name: "protect-mcp-demo", version: process.env.PROTECT_MCP_VERSION || "0.5.3" },
35119
35119
  capabilities: { tools: {} }
35120
35120
  }
35121
35121
  });
@@ -35201,7 +35201,7 @@ function createSandboxServer() {
35201
35201
  const { z } = require_zod();
35202
35202
  const server = new McpServer({
35203
35203
  name: "protect-mcp",
35204
- version: "0.4.5",
35204
+ version: process.env.PROTECT_MCP_VERSION || "0.4.5",
35205
35205
  description: "Security gateway for MCP servers. Per-tool policies, Ed25519-signed receipts, human approval gates, trust tiers."
35206
35206
  });
35207
35207
  server.tool(
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createSandboxServer
4
- } from "./chunk-KPSICBAJ.mjs";
4
+ } from "./chunk-SETXVE2K.mjs";
5
5
  import "./chunk-PQJP2ZCI.mjs";
6
6
  export {
7
7
  createSandboxServer
@@ -15,7 +15,7 @@ import {
15
15
  ristretto255,
16
16
  ristretto255_hasher,
17
17
  x25519
18
- } from "./chunk-LYKNULYU.mjs";
18
+ } from "./chunk-LJQOALYR.mjs";
19
19
  import "./chunk-D733KAPG.mjs";
20
20
  import "./chunk-PQJP2ZCI.mjs";
21
21
  export {
@@ -1361,7 +1361,7 @@ async function startHookServer(options = {}) {
1361
1361
  res.end(JSON.stringify({
1362
1362
  status: "ok",
1363
1363
  server: "protect-mcp-hooks",
1364
- version: "0.5.0",
1364
+ version: process.env.PROTECT_MCP_VERSION || "unknown",
1365
1365
  uptime_ms: Date.now() - state.startTime,
1366
1366
  mode: enforce ? "enforce" : "shadow",
1367
1367
  policy_digest: policyDigest,
@@ -1448,9 +1448,10 @@ async function startHookServer(options = {}) {
1448
1448
  const pad = (s, n = 46) => s.padEnd(n);
1449
1449
  w(`
1450
1450
  `);
1451
- w(` protect-mcp v0.5.4
1451
+ w(process.env.PROTECT_MCP_VERSION ? ` protect-mcp v${process.env.PROTECT_MCP_VERSION}
1452
+ ` : ` protect-mcp
1452
1453
  `);
1453
- w(` ScopeBlind \u2014 https://scopeblind.com
1454
+ w(` ScopeBlind \xB7 https://scopeblind.com
1454
1455
  `);
1455
1456
  w(`
1456
1457
  `);
@@ -1478,7 +1479,13 @@ async function startHookServer(options = {}) {
1478
1479
  `);
1479
1480
  w(`
1480
1481
  `);
1481
- w(` deny is authoritative \u2014 cannot be overridden.
1482
+ w(` deny is authoritative: it cannot be overridden.
1483
+ `);
1484
+ w(`
1485
+ `);
1486
+ w(` See your record npx protect-mcp record
1487
+ `);
1488
+ w(` a searchable view of every decision, all on this machine
1482
1489
  `);
1483
1490
  w(`
1484
1491
  `);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  startHookServer
3
- } from "./chunk-NVJHGXXG.mjs";
4
- import "./chunk-D2RDY2JR.mjs";
3
+ } from "./chunk-6E2DHBAR.mjs";
4
+ import "./chunk-WIPWNWMJ.mjs";
5
5
  import "./chunk-PQJP2ZCI.mjs";
6
6
  export {
7
7
  startHookServer
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ProtectGateway
3
- } from "./chunk-UBZJ3VI2.mjs";
4
- import "./chunk-D2RDY2JR.mjs";
3
+ } from "./chunk-VTPZ4G5I.mjs";
4
+ import "./chunk-WIPWNWMJ.mjs";
5
5
  import "./chunk-PQJP2ZCI.mjs";
6
6
 
7
7
  // src/http-transport.ts
@@ -34,7 +34,7 @@ async function startHttpTransport(options) {
34
34
  res.end(JSON.stringify({
35
35
  status: "ok",
36
36
  server: "protect-mcp",
37
- version: "0.4.0",
37
+ version: process.env.PROTECT_MCP_VERSION || "unknown",
38
38
  transport: "streamable-http",
39
39
  mode: config.policy ? config.enforce ? "enforce" : "shadow" : "shadow",
40
40
  wrapping: serverCommand.join(" ")