protect-mcp 0.4.1 → 0.4.3

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
@@ -3,13 +3,15 @@ import {
3
3
  formatSimulation,
4
4
  parseLogFile,
5
5
  simulate
6
- } from "./chunk-VWUN6AI6.mjs";
6
+ } from "./chunk-VIA2B65K.mjs";
7
7
  import {
8
8
  ProtectGateway,
9
9
  initSigning,
10
+ isCedarAvailable,
11
+ loadCedarPolicies,
10
12
  loadPolicy,
11
13
  validateCredentials
12
- } from "./chunk-7HBHIKLN.mjs";
14
+ } from "./chunk-VF3OCG4D.mjs";
13
15
 
14
16
  // src/cli.ts
15
17
  function printHelp() {
@@ -31,6 +33,7 @@ Usage:
31
33
 
32
34
  Options:
33
35
  --policy <path> Policy/config JSON file (default: allow-all)
36
+ --cedar <dir> Cedar policy directory (alternative to --policy, evaluates locally via WASM)
34
37
  --slug <slug> ScopeBlind tenant slug (optional)
35
38
  --enforce Enable enforcement mode (default: shadow mode)
36
39
  --http Start HTTP/SSE server instead of stdio proxy
@@ -42,6 +45,7 @@ Commands:
42
45
  quickstart Zero-config onboarding: init + demo + show receipts in one command
43
46
  init Generate config template, Ed25519 keypair, and sample policy
44
47
  demo Start a demo server wrapped with protect-mcp (see receipts instantly)
48
+ doctor Check your setup: keys, policies, verifier, API connectivity
45
49
  trace <id> Visualize the receipt DAG from a given receipt_id (ASCII tree)
46
50
  status Show tool call statistics from the local decision log
47
51
  digest Generate a human-readable summary of agent activity
@@ -64,6 +68,7 @@ Examples:
64
68
  }
65
69
  function parseArgs(argv) {
66
70
  let policyPath;
71
+ let cedarDir;
67
72
  let slug;
68
73
  let enforce = false;
69
74
  let verbose = false;
@@ -88,6 +93,8 @@ function parseArgs(argv) {
88
93
  process.exit(0);
89
94
  } else if (arg === "--policy" && i + 1 < options.length) {
90
95
  policyPath = options[++i];
96
+ } else if (arg === "--cedar" && i + 1 < options.length) {
97
+ cedarDir = options[++i];
91
98
  } else if (arg === "--slug" && i + 1 < options.length) {
92
99
  slug = options[++i];
93
100
  } else if (arg === "--enforce") {
@@ -99,7 +106,7 @@ function parseArgs(argv) {
99
106
  `);
100
107
  }
101
108
  }
102
- return { policyPath, slug, enforce, verbose, childCommand };
109
+ return { policyPath, cedarDir, slug, enforce, verbose, childCommand };
103
110
  }
104
111
  async function handleInit(argv) {
105
112
  const { writeFileSync, existsSync, mkdirSync } = await import("fs");
@@ -217,8 +224,14 @@ Add --enforce when ready to block policy violations.
217
224
  async function handleDemo() {
218
225
  const { existsSync } = await import("fs");
219
226
  const { join, dirname, resolve } = await import("path");
227
+ const { realpathSync } = await import("fs");
220
228
  const cliPath = resolve(process.argv[1] || "dist/cli.js");
221
- const cliDir = dirname(cliPath);
229
+ let cliDir;
230
+ try {
231
+ cliDir = dirname(realpathSync(cliPath));
232
+ } catch {
233
+ cliDir = dirname(cliPath);
234
+ }
222
235
  const demoServerPath = join(cliDir, "demo-server.js");
223
236
  const configPath = join(process.cwd(), "protect-mcp.json");
224
237
  const hasConfig = existsSync(configPath);
@@ -727,7 +740,7 @@ async function handleTrace(argv) {
727
740
  process.stderr.write("[PROTECT_MCP] Usage: protect-mcp trace <receipt_id> [--endpoint <url>] [--depth <n>]\n");
728
741
  process.exit(1);
729
742
  }
730
- let endpoint = "https://evidence-indexer.tomjwxf.workers.dev";
743
+ let endpoint = "https://api.scopeblind.com/evidence";
731
744
  let depth = 3;
732
745
  for (let i = 1; i < argv.length; i++) {
733
746
  if (argv[i] === "--endpoint" && argv[i + 1]) {
@@ -977,12 +990,57 @@ async function main() {
977
990
  await handleReport(args.slice(1));
978
991
  process.exit(0);
979
992
  }
980
- const { policyPath, slug, enforce, verbose, childCommand } = parseArgs(args);
993
+ if (args[0] === "doctor") {
994
+ await handleDoctor();
995
+ process.exit(0);
996
+ }
997
+ const { policyPath, cedarDir, slug, enforce, verbose, childCommand } = parseArgs(args);
981
998
  let policy = null;
982
999
  let policyDigest = "none";
983
1000
  let credentials;
984
1001
  let signing;
985
- if (policyPath) {
1002
+ let cedarPolicySet = null;
1003
+ let effectiveCedarDir = cedarDir;
1004
+ if (!effectiveCedarDir && !policyPath) {
1005
+ const { existsSync, readdirSync } = await import("fs");
1006
+ for (const candidate of ["cedar", "policies", "."]) {
1007
+ try {
1008
+ if (existsSync(candidate) && readdirSync(candidate).some((f) => f.endsWith(".cedar"))) {
1009
+ effectiveCedarDir = candidate;
1010
+ process.stderr.write(`[PROTECT_MCP] Auto-detected Cedar policies in ./${candidate}/
1011
+ `);
1012
+ break;
1013
+ }
1014
+ } catch {
1015
+ }
1016
+ }
1017
+ }
1018
+ if (effectiveCedarDir) {
1019
+ try {
1020
+ const cedarAvailable = await isCedarAvailable();
1021
+ if (!cedarAvailable) {
1022
+ process.stderr.write("[PROTECT_MCP] Warning: @cedar-policy/cedar-wasm not installed. Install with: npm install @cedar-policy/cedar-wasm\n");
1023
+ process.stderr.write("[PROTECT_MCP] Cedar policies will be loaded but evaluated with fallback (allow-all).\n");
1024
+ }
1025
+ cedarPolicySet = loadCedarPolicies(effectiveCedarDir);
1026
+ policyDigest = cedarPolicySet.digest;
1027
+ policy = {
1028
+ tools: { "*": { require: "any" } },
1029
+ policy_engine: "cedar",
1030
+ cedar_dir: effectiveCedarDir
1031
+ };
1032
+ process.stderr.write(`[PROTECT_MCP] Cedar policy engine: loaded ${cedarPolicySet.fileCount} policies from ${effectiveCedarDir} (digest: ${policyDigest})
1033
+ `);
1034
+ if (verbose) {
1035
+ process.stderr.write(`[PROTECT_MCP] Cedar files: ${cedarPolicySet.files.join(", ")}
1036
+ `);
1037
+ }
1038
+ } catch (err) {
1039
+ process.stderr.write(`[PROTECT_MCP] Error loading Cedar policies: ${err instanceof Error ? err.message : err}
1040
+ `);
1041
+ process.exit(1);
1042
+ }
1043
+ } else if (policyPath) {
986
1044
  try {
987
1045
  const loaded = loadPolicy(policyPath);
988
1046
  policy = loaded.policy;
@@ -1028,11 +1086,14 @@ async function main() {
1028
1086
  if (useHttp) {
1029
1087
  const portIdx = args.indexOf("--port");
1030
1088
  const httpPort = portIdx >= 0 && args[portIdx + 1] ? parseInt(args[portIdx + 1]) : 3e3;
1031
- const { startHttpTransport } = await import("./http-transport-RIVV2RVQ.mjs");
1089
+ const { startHttpTransport } = await import("./http-transport-VLIPOPIC.mjs");
1032
1090
  startHttpTransport({ port: httpPort, config, serverCommand: childCommand });
1033
1091
  return;
1034
1092
  }
1035
1093
  const gateway = new ProtectGateway(config);
1094
+ if (cedarPolicySet) {
1095
+ gateway.setCedarPolicies(cedarPolicySet);
1096
+ }
1036
1097
  await gateway.start();
1037
1098
  }
1038
1099
  async function handleSimulate(args) {
@@ -1077,6 +1138,127 @@ async function handleSimulate(args) {
1077
1138
  process.stdout.write(formatSimulation(summary) + "\n");
1078
1139
  }
1079
1140
  }
1141
+ async function handleDoctor() {
1142
+ const { existsSync, readFileSync, readdirSync } = await import("fs");
1143
+ const { join } = await import("path");
1144
+ const { execSync } = await import("child_process");
1145
+ const green2 = (s) => `\x1B[32m\u2713\x1B[0m ${s}`;
1146
+ const red2 = (s) => `\x1B[31m\u2717\x1B[0m ${s}`;
1147
+ const yellow2 = (s) => `\x1B[33m\u26A0\x1B[0m ${s}`;
1148
+ const dim2 = (s) => `\x1B[2m${s}\x1B[0m`;
1149
+ process.stdout.write("\n\x1B[1mprotect-mcp doctor\x1B[0m\n");
1150
+ process.stdout.write(dim2("Checking your ScopeBlind setup...\n\n"));
1151
+ let issues = 0;
1152
+ const nodeVersion = process.version;
1153
+ const major = parseInt(nodeVersion.slice(1));
1154
+ if (major >= 18) {
1155
+ process.stdout.write(green2(`Node.js ${nodeVersion}
1156
+ `));
1157
+ } else {
1158
+ process.stdout.write(red2(`Node.js ${nodeVersion} \u2014 requires >= 18
1159
+ `));
1160
+ issues++;
1161
+ }
1162
+ const configPath = join(process.cwd(), "scopeblind.config.json");
1163
+ if (existsSync(configPath)) {
1164
+ try {
1165
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
1166
+ if (config.signing?.private_key || config.signing?.key_file) {
1167
+ process.stdout.write(green2("Signing keys configured\n"));
1168
+ } else {
1169
+ process.stdout.write(yellow2("Config found but no signing keys \u2014 run: protect-mcp init\n"));
1170
+ issues++;
1171
+ }
1172
+ } catch {
1173
+ process.stdout.write(red2("Invalid scopeblind.config.json\n"));
1174
+ issues++;
1175
+ }
1176
+ } else {
1177
+ process.stdout.write(yellow2("No scopeblind.config.json \u2014 run: protect-mcp init\n"));
1178
+ }
1179
+ let policyFound = false;
1180
+ for (const dir of ["cedar", "policies", "."]) {
1181
+ try {
1182
+ if (existsSync(dir) && readdirSync(dir).some((f) => f.endsWith(".cedar"))) {
1183
+ process.stdout.write(green2(`Cedar policies found in ./${dir}/
1184
+ `));
1185
+ policyFound = true;
1186
+ break;
1187
+ }
1188
+ } catch {
1189
+ }
1190
+ }
1191
+ if (!policyFound) {
1192
+ for (const name of ["policy.json", "protect-mcp.policy.json", "scopeblind-policy.json"]) {
1193
+ if (existsSync(name)) {
1194
+ process.stdout.write(green2(`JSON policy found: ${name}
1195
+ `));
1196
+ policyFound = true;
1197
+ break;
1198
+ }
1199
+ }
1200
+ }
1201
+ if (!policyFound) {
1202
+ process.stdout.write(yellow2("No policy files found \u2014 running in shadow mode (allow all)\n"));
1203
+ }
1204
+ try {
1205
+ const cedarAvailable = await isCedarAvailable();
1206
+ if (cedarAvailable) {
1207
+ process.stdout.write(green2("Cedar WASM engine available\n"));
1208
+ } else {
1209
+ process.stdout.write(dim2(" Cedar WASM not installed \u2014 install: npm install @cedar-policy/cedar-wasm\n"));
1210
+ }
1211
+ } catch {
1212
+ process.stdout.write(dim2(" Cedar WASM not installed\n"));
1213
+ }
1214
+ const logFile = join(process.cwd(), "protect-mcp-decisions.jsonl");
1215
+ const receiptFile = join(process.cwd(), "protect-mcp-receipts.jsonl");
1216
+ if (existsSync(logFile)) {
1217
+ try {
1218
+ const lines = readFileSync(logFile, "utf-8").trim().split("\n").length;
1219
+ process.stdout.write(green2(`Decision log: ${lines} entries
1220
+ `));
1221
+ } catch {
1222
+ process.stdout.write(green2("Decision log exists\n"));
1223
+ }
1224
+ } else {
1225
+ process.stdout.write(dim2(" No decision log yet \u2014 will be created on first tool call\n"));
1226
+ }
1227
+ if (existsSync(receiptFile)) {
1228
+ try {
1229
+ const lines = readFileSync(receiptFile, "utf-8").trim().split("\n").length;
1230
+ process.stdout.write(green2(`Receipt file: ${lines} signed receipts
1231
+ `));
1232
+ } catch {
1233
+ process.stdout.write(green2("Receipt file exists\n"));
1234
+ }
1235
+ }
1236
+ try {
1237
+ execSync("npx @veritasacta/verify --version 2>/dev/null", { stdio: "pipe", timeout: 1e4 });
1238
+ process.stdout.write(green2("Verifier available: @veritasacta/verify\n"));
1239
+ } catch {
1240
+ process.stdout.write(dim2(" Verifier not cached \u2014 install: npm install -g @veritasacta/verify\n"));
1241
+ }
1242
+ try {
1243
+ const res = await fetch("https://api.scopeblind.com/health", { signal: AbortSignal.timeout(5e3) });
1244
+ if (res.ok) {
1245
+ process.stdout.write(green2("ScopeBlind API reachable\n"));
1246
+ } else {
1247
+ process.stdout.write(yellow2("ScopeBlind API returned non-200 \u2014 receipts will be stored locally\n"));
1248
+ }
1249
+ } catch {
1250
+ process.stdout.write(dim2(" ScopeBlind API not reachable \u2014 offline mode (receipts stored locally)\n"));
1251
+ }
1252
+ process.stdout.write("\n");
1253
+ if (issues === 0) {
1254
+ process.stdout.write("\x1B[32m\x1B[1mAll checks passed.\x1B[0m Ready to wrap MCP servers.\n");
1255
+ process.stdout.write(dim2("\n npx protect-mcp -- node your-server.js\n\n"));
1256
+ } else {
1257
+ process.stdout.write(`\x1B[33m\x1B[1m${issues} issue(s) found.\x1B[0m Fix them and run doctor again.
1258
+
1259
+ `);
1260
+ }
1261
+ }
1080
1262
  async function handleReport(args) {
1081
1263
  let period = 30;
1082
1264
  let format = "json";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ProtectGateway
3
- } from "./chunk-7HBHIKLN.mjs";
3
+ } from "./chunk-VF3OCG4D.mjs";
4
4
 
5
5
  // src/http-transport.ts
6
6
  import { createServer } from "http";