protect-mcp 0.5.1 → 0.5.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/README.md CHANGED
@@ -248,9 +248,9 @@ Self-contained offline-verifiable bundle with receipts + signing keys. Verify wi
248
248
 
249
249
  ## Standards & IP
250
250
 
251
- - **IETF Internet-Draft**: [draft-farley-acta-signed-receipts-00](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) — Signed Decision Receipts for Machine-to-Machine Access Control
251
+ - **IETF Internet-Draft**: [draft-farley-acta-signed-receipts-01](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) — Signed Decision Receipts for Machine-to-Machine Access Control
252
252
  - **Patent Status**: 4 Australian provisional patents pending (2025-2026) covering decision receipts with configurable disclosure, tool-calling gateway, agent manifests, and portable identity
253
- - **Verification**: MIT-licensed — `npx @veritasacta/verify --self-test`
253
+ - **Verification**: Apache-2.0 — `npx @veritasacta/verify --self-test`
254
254
  - **Microsoft AGT Integration**: [PR #667](https://github.com/microsoft/agent-governance-toolkit/pull/667) — Cedar policy bridge for Agent Governance Toolkit
255
255
 
256
256
  ## License
@@ -7,7 +7,7 @@ import {
7
7
  parseRateLimit,
8
8
  signDecision,
9
9
  startStatusServer
10
- } from "./chunk-V52W3XIN.mjs";
10
+ } from "./chunk-YTBC72JJ.mjs";
11
11
 
12
12
  // src/evidence-store.ts
13
13
  import { readFileSync, writeFileSync, existsSync } from "fs";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  meetsMinTier
3
- } from "./chunk-IUFFDQYZ.mjs";
3
+ } from "./chunk-BYYWYSHM.mjs";
4
4
  import {
5
5
  checkRateLimit,
6
6
  getToolPolicy,
7
7
  parseRateLimit
8
- } from "./chunk-V52W3XIN.mjs";
8
+ } from "./chunk-YTBC72JJ.mjs";
9
9
 
10
10
  // src/simulate.ts
11
11
  import { readFileSync } from "fs";
@@ -11,7 +11,7 @@ import {
11
11
  loadPolicy,
12
12
  parseRateLimit,
13
13
  signDecision
14
- } from "./chunk-V52W3XIN.mjs";
14
+ } from "./chunk-YTBC72JJ.mjs";
15
15
 
16
16
  // src/hook-server.ts
17
17
  import { createServer } from "http";
@@ -272,6 +272,8 @@ context: inline
272
272
 
273
273
  # ScopeBlind Receipt Verification
274
274
 
275
+ Every AI agent tool call gets a cryptographic receipt. Verify offline. No vendor trust required.
276
+
275
277
  When the user asks to verify receipts or check the audit trail:
276
278
 
277
279
  1. **Check for receipt files:**
@@ -130,7 +130,14 @@ function signDecision(entry) {
130
130
  scope: entry.request_id,
131
131
  // request scope
132
132
  mode: entry.mode,
133
- request_id: entry.request_id
133
+ request_id: entry.request_id,
134
+ // Spec version: ties every receipt to the IETF standard
135
+ spec: "draft-farley-acta-signed-receipts-01",
136
+ // Issuer certification: distinguishes VOPRF-backed receipts from self-signed ones
137
+ // - scopeblind:verified = issued via ScopeBlind VOPRF backend (paid tier)
138
+ // - self-signed = signed with local Ed25519 key (free tier, protect-mcp default)
139
+ // - uncertified = unsigned receipt (shadow mode, no signing configured)
140
+ issuer_certification: signerState ? "self-signed" : "uncertified"
134
141
  };
135
142
  if (entry.tier) payload.tier = entry.tier;
136
143
  if (entry.credential_ref) payload.credential_ref = entry.credential_ref;
@@ -138,6 +145,12 @@ function signDecision(entry) {
138
145
  payload.rate_limit_remaining = entry.rate_limit_remaining;
139
146
  }
140
147
  if (entry.policy_engine) payload.policy_engine = entry.policy_engine;
148
+ if (entry.hook_event) payload.hook_event = entry.hook_event;
149
+ if (entry.sandbox_state) payload.sandbox_state = entry.sandbox_state;
150
+ if (entry.timing) payload.timing = entry.timing;
151
+ if (entry.swarm) payload.swarm = entry.swarm;
152
+ if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
153
+ if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
141
154
  const result = artifactsModule.createSignedArtifact(
142
155
  artifactType,
143
156
  payload,
@@ -232,7 +245,7 @@ function buildEntities(req) {
232
245
  }
233
246
  ];
234
247
  }
235
- async function evaluateCedar(policySet, req) {
248
+ async function evaluateCedar(policySet, req, schema) {
236
249
  const available = await ensureCedarWasm();
237
250
  if (!available) {
238
251
  return {
@@ -243,16 +256,21 @@ async function evaluateCedar(policySet, req) {
243
256
  }
244
257
  try {
245
258
  const agentId = req.agentId || req.tier;
259
+ const context = {
260
+ tier: req.tier,
261
+ ...req.context || {}
262
+ };
263
+ if (req.toolInput && Object.keys(req.toolInput).length > 0) {
264
+ context.input = req.toolInput;
265
+ }
246
266
  const authRequest = {
247
267
  principal: { type: "Agent", id: agentId },
248
268
  action: { type: "Action", id: "MCP::Tool::call" },
249
269
  resource: { type: "Tool", id: req.tool },
250
- context: {
251
- tier: req.tier,
252
- ...req.context || {}
253
- }
270
+ context
254
271
  };
255
272
  const entities = buildEntities(req);
273
+ const cedarSchema = schema?.schemaJson ?? null;
256
274
  let result;
257
275
  if (typeof cedarWasm.isAuthorized === "function") {
258
276
  result = cedarWasm.isAuthorized({
@@ -262,8 +280,7 @@ async function evaluateCedar(policySet, req) {
262
280
  action: authRequest.action,
263
281
  resource: authRequest.resource,
264
282
  context: authRequest.context,
265
- schema: null
266
- // No schema enforcement — Cedar still evaluates correctly
283
+ schema: cedarSchema
267
284
  });
268
285
  } else if (typeof cedarWasm.checkAuthorization === "function") {
269
286
  result = cedarWasm.checkAuthorization(
@@ -281,7 +298,7 @@ async function evaluateCedar(policySet, req) {
281
298
  action: authRequest.action,
282
299
  resource: authRequest.resource,
283
300
  context: authRequest.context,
284
- schema: null
301
+ schema: cedarSchema
285
302
  });
286
303
  } else {
287
304
  return {
package/dist/cli.js CHANGED
@@ -411,7 +411,14 @@ function signDecision(entry) {
411
411
  scope: entry.request_id,
412
412
  // request scope
413
413
  mode: entry.mode,
414
- request_id: entry.request_id
414
+ request_id: entry.request_id,
415
+ // Spec version: ties every receipt to the IETF standard
416
+ spec: "draft-farley-acta-signed-receipts-01",
417
+ // Issuer certification: distinguishes VOPRF-backed receipts from self-signed ones
418
+ // - scopeblind:verified = issued via ScopeBlind VOPRF backend (paid tier)
419
+ // - self-signed = signed with local Ed25519 key (free tier, protect-mcp default)
420
+ // - uncertified = unsigned receipt (shadow mode, no signing configured)
421
+ issuer_certification: signerState ? "self-signed" : "uncertified"
415
422
  };
416
423
  if (entry.tier) payload.tier = entry.tier;
417
424
  if (entry.credential_ref) payload.credential_ref = entry.credential_ref;
@@ -419,6 +426,12 @@ function signDecision(entry) {
419
426
  payload.rate_limit_remaining = entry.rate_limit_remaining;
420
427
  }
421
428
  if (entry.policy_engine) payload.policy_engine = entry.policy_engine;
429
+ if (entry.hook_event) payload.hook_event = entry.hook_event;
430
+ if (entry.sandbox_state) payload.sandbox_state = entry.sandbox_state;
431
+ if (entry.timing) payload.timing = entry.timing;
432
+ if (entry.swarm) payload.swarm = entry.swarm;
433
+ if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
434
+ if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
422
435
  const result = artifactsModule.createSignedArtifact(
423
436
  artifactType,
424
437
  payload,
@@ -682,7 +695,7 @@ function buildEntities(req) {
682
695
  }
683
696
  ];
684
697
  }
685
- async function evaluateCedar(policySet, req) {
698
+ async function evaluateCedar(policySet, req, schema) {
686
699
  const available = await ensureCedarWasm();
687
700
  if (!available) {
688
701
  return {
@@ -693,16 +706,21 @@ async function evaluateCedar(policySet, req) {
693
706
  }
694
707
  try {
695
708
  const agentId = req.agentId || req.tier;
709
+ const context = {
710
+ tier: req.tier,
711
+ ...req.context || {}
712
+ };
713
+ if (req.toolInput && Object.keys(req.toolInput).length > 0) {
714
+ context.input = req.toolInput;
715
+ }
696
716
  const authRequest = {
697
717
  principal: { type: "Agent", id: agentId },
698
718
  action: { type: "Action", id: "MCP::Tool::call" },
699
719
  resource: { type: "Tool", id: req.tool },
700
- context: {
701
- tier: req.tier,
702
- ...req.context || {}
703
- }
720
+ context
704
721
  };
705
722
  const entities = buildEntities(req);
723
+ const cedarSchema = schema?.schemaJson ?? null;
706
724
  let result;
707
725
  if (typeof cedarWasm.isAuthorized === "function") {
708
726
  result = cedarWasm.isAuthorized({
@@ -712,8 +730,7 @@ async function evaluateCedar(policySet, req) {
712
730
  action: authRequest.action,
713
731
  resource: authRequest.resource,
714
732
  context: authRequest.context,
715
- schema: null
716
- // No schema enforcement — Cedar still evaluates correctly
733
+ schema: cedarSchema
717
734
  });
718
735
  } else if (typeof cedarWasm.checkAuthorization === "function") {
719
736
  result = cedarWasm.checkAuthorization(
@@ -731,7 +748,7 @@ async function evaluateCedar(policySet, req) {
731
748
  action: authRequest.action,
732
749
  resource: authRequest.resource,
733
750
  context: authRequest.context,
734
- schema: null
751
+ schema: cedarSchema
735
752
  });
736
753
  } else {
737
754
  return {
@@ -4540,6 +4557,8 @@ context: inline
4540
4557
 
4541
4558
  # ScopeBlind Receipt Verification
4542
4559
 
4560
+ Every AI agent tool call gets a cryptographic receipt. Verify offline. No vendor trust required.
4561
+
4543
4562
  When the user asks to verify receipts or check the audit trail:
4544
4563
 
4545
4564
  1. **Check for receipt files:**
@@ -6047,6 +6066,7 @@ function formatSimulation(summary) {
6047
6066
 
6048
6067
  // src/cli.ts
6049
6068
  init_cedar_evaluator();
6069
+ var import_meta = {};
6050
6070
  function printHelp() {
6051
6071
  process.stderr.write(`
6052
6072
  protect-mcp \u2014 Enterprise security gateway for MCP servers & Claude Code hooks
@@ -6055,7 +6075,8 @@ Usage:
6055
6075
  protect-mcp [options] -- <command> [args...]
6056
6076
  protect-mcp serve [--port <port>] [--enforce] [--policy <path>] [--cedar <dir>]
6057
6077
  protect-mcp init-hooks [--dir <path>] [--port <port>]
6058
- protect-mcp quickstart
6078
+ protect-mcp quickstart [--connect]
6079
+ protect-mcp connect
6059
6080
  protect-mcp init [--dir <path>]
6060
6081
  protect-mcp demo
6061
6082
  protect-mcp trace <receipt_id> [--endpoint <url>] [--depth <n>]
@@ -6080,6 +6101,7 @@ Commands:
6080
6101
  serve Start HTTP hook server for Claude Code integration (port 9377)
6081
6102
  init-hooks Generate Claude Code hook config + skill + sample Cedar policy
6082
6103
  quickstart Zero-config onboarding: init + demo + show receipts in one command
6104
+ connect Create a ScopeBlind sandbox dashboard and configure receipt upload
6083
6105
  init Generate config template, Ed25519 keypair, and sample policy
6084
6106
  demo Start a demo server wrapped with protect-mcp (see receipts instantly)
6085
6107
  doctor Check your setup: keys, policies, verifier, API connectivity
@@ -6094,6 +6116,8 @@ Examples:
6094
6116
  protect-mcp serve --enforce --cedar ./cedar # Enforce Cedar policies
6095
6117
  protect-mcp init-hooks # One-command Claude Code setup
6096
6118
  protect-mcp quickstart
6119
+ protect-mcp quickstart --connect # Quickstart + create dashboard
6120
+ protect-mcp connect # Connect existing setup to dashboard
6097
6121
  protect-mcp -- node my-server.js
6098
6122
  protect-mcp init
6099
6123
  protect-mcp demo
@@ -6675,7 +6699,83 @@ ${bold("protect-mcp bundle")}
6675
6699
 
6676
6700
  `);
6677
6701
  }
6678
- async function handleQuickstart() {
6702
+ async function createSandbox() {
6703
+ const { mkdirSync, writeFileSync: writeFileSync2, existsSync: existsSync7, readFileSync: readFileSync9 } = await import("fs");
6704
+ const { join: join6 } = await import("path");
6705
+ const { homedir } = await import("os");
6706
+ let response;
6707
+ try {
6708
+ response = await fetch("https://api.scopeblind.com/sandbox/create", { method: "POST" });
6709
+ } catch {
6710
+ process.stderr.write(yellow(" \u26A0 Could not create dashboard (offline or server unavailable).\n"));
6711
+ process.stderr.write(` Run 'npx protect-mcp connect' later to set up the dashboard.
6712
+
6713
+ `);
6714
+ return null;
6715
+ }
6716
+ if (!response.ok) {
6717
+ process.stderr.write(yellow(" \u26A0 Could not create dashboard (offline or server unavailable).\n"));
6718
+ process.stderr.write(` Run 'npx protect-mcp connect' later to set up the dashboard.
6719
+
6720
+ `);
6721
+ return null;
6722
+ }
6723
+ let data;
6724
+ try {
6725
+ data = await response.json();
6726
+ } catch {
6727
+ process.stderr.write(yellow(" \u26A0 Could not create dashboard (unexpected response).\n"));
6728
+ process.stderr.write(` Run 'npx protect-mcp connect' later to set up the dashboard.
6729
+
6730
+ `);
6731
+ return null;
6732
+ }
6733
+ const dashboardUrl = `https://scopeblind.com/t/${data.slug}`;
6734
+ const configDir = join6(homedir(), ".protect-mcp");
6735
+ if (!existsSync7(configDir)) {
6736
+ mkdirSync(configDir, { recursive: true });
6737
+ }
6738
+ const configPath = join6(configDir, "config.json");
6739
+ let existing = {};
6740
+ if (existsSync7(configPath)) {
6741
+ try {
6742
+ existing = JSON.parse(readFileSync9(configPath, "utf-8"));
6743
+ } catch {
6744
+ }
6745
+ }
6746
+ writeFileSync2(configPath, JSON.stringify({
6747
+ ...existing,
6748
+ sandbox_slug: data.slug,
6749
+ dashboard_url: dashboardUrl
6750
+ }, null, 2) + "\n");
6751
+ return dashboardUrl;
6752
+ }
6753
+ async function handleConnect() {
6754
+ process.stderr.write(`
6755
+ ${bold("protect-mcp connect")}
6756
+ `);
6757
+ process.stderr.write(`${"\u2500".repeat(50)}
6758
+
6759
+ `);
6760
+ process.stderr.write(` Creating ScopeBlind sandbox dashboard...
6761
+
6762
+ `);
6763
+ const dashboardUrl = await createSandbox();
6764
+ if (dashboardUrl) {
6765
+ process.stderr.write(green(` \u2713 Dashboard created: ${dashboardUrl}
6766
+ `));
6767
+ process.stderr.write(` Receipts will be uploaded automatically.
6768
+ `);
6769
+ process.stderr.write(dim(` Free tier: 20,000 receipts/month \u2014 no credit card required.
6770
+ `));
6771
+ process.stderr.write(`
6772
+ ${"\u2500".repeat(50)}
6773
+
6774
+ `);
6775
+ }
6776
+ }
6777
+ async function handleQuickstart(argv) {
6778
+ const connectFlag = argv.includes("--connect");
6679
6779
  const { mkdtempSync, writeFileSync: writeFileSync2, existsSync: existsSync7, mkdirSync, readFileSync: readFileSync9 } = await import("fs");
6680
6780
  const { join: join6 } = await import("path");
6681
6781
  const { tmpdir } = await import("os");
@@ -6695,9 +6795,13 @@ ${bold("protect-mcp quickstart")}
6695
6795
  process.stdout.write(` 3. Start a demo MCP server with protect-mcp wrapping it
6696
6796
  `);
6697
6797
  process.stdout.write(` 4. Log signed receipts for every tool call
6698
-
6699
6798
  `);
6700
- process.stdout.write(` Working dir: ${dir}
6799
+ if (connectFlag) {
6800
+ process.stdout.write(` 5. Create a ScopeBlind dashboard for receipt viewing
6801
+ `);
6802
+ }
6803
+ process.stdout.write(`
6804
+ Working dir: ${dir}
6701
6805
 
6702
6806
  `);
6703
6807
  const keysDir = join6(dir, "keys");
@@ -6748,6 +6852,24 @@ ${bold("protect-mcp quickstart")}
6748
6852
  process.stdout.write(` \u2713 Signing enabled (Ed25519)
6749
6853
 
6750
6854
  `);
6855
+ if (connectFlag) {
6856
+ process.stdout.write(`${bold("Connecting to ScopeBlind dashboard...")}
6857
+
6858
+ `);
6859
+ const dashboardUrl = await createSandbox();
6860
+ if (dashboardUrl) {
6861
+ const updatedConfig = { ...config, dashboard_url: dashboardUrl };
6862
+ writeFileSync2(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
6863
+ process.stdout.write(green(` \u2713 Dashboard created: ${dashboardUrl}
6864
+ `));
6865
+ process.stdout.write(` Receipts will be uploaded automatically.
6866
+ `);
6867
+ process.stdout.write(dim(` Free tier: 20,000 receipts/month \u2014 no credit card required.
6868
+ `));
6869
+ process.stdout.write(`
6870
+ `);
6871
+ }
6872
+ }
6751
6873
  process.stdout.write(`${bold("Starting demo server...")}
6752
6874
 
6753
6875
  `);
@@ -7149,7 +7271,55 @@ ${bold("protect-mcp init-hooks")}
7149
7271
 
7150
7272
  `);
7151
7273
  }
7274
+ async function sendInstallTelemetry() {
7275
+ try {
7276
+ const { existsSync: existsSync7, mkdirSync, writeFileSync: writeFileSync2, readFileSync: readFileSync9 } = await import("fs");
7277
+ const { join: join6, dirname } = await import("path");
7278
+ const { homedir } = await import("os");
7279
+ const { fileURLToPath } = await import("url");
7280
+ const markerDir = join6(homedir(), ".protect-mcp");
7281
+ const markerFile = join6(markerDir, ".telemetry-sent");
7282
+ if (existsSync7(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
7283
+ return;
7284
+ }
7285
+ let version = "unknown";
7286
+ try {
7287
+ const thisDir = dirname(fileURLToPath(import_meta.url));
7288
+ const pkgPath = join6(thisDir, "..", "package.json");
7289
+ if (existsSync7(pkgPath)) {
7290
+ version = JSON.parse(readFileSync9(pkgPath, "utf-8")).version;
7291
+ }
7292
+ } catch {
7293
+ }
7294
+ const controller = new AbortController();
7295
+ const timeout = setTimeout(() => controller.abort(), 3e3);
7296
+ fetch("https://api.scopeblind.com/telemetry/install", {
7297
+ method: "POST",
7298
+ headers: { "Content-Type": "application/json" },
7299
+ body: JSON.stringify({
7300
+ package: "protect-mcp",
7301
+ version,
7302
+ os: process.platform,
7303
+ arch: process.arch,
7304
+ node: process.version,
7305
+ ts: Date.now()
7306
+ }),
7307
+ signal: controller.signal
7308
+ }).catch(() => {
7309
+ }).finally(() => clearTimeout(timeout));
7310
+ if (!existsSync7(markerDir)) {
7311
+ mkdirSync(markerDir, { recursive: true });
7312
+ }
7313
+ writeFileSync2(markerFile, String(Date.now()), "utf-8");
7314
+ process.stderr.write(
7315
+ "[protect-mcp] Anonymous install telemetry sent (disable: PROTECT_MCP_TELEMETRY=off)\n"
7316
+ );
7317
+ } catch {
7318
+ }
7319
+ }
7152
7320
  async function main() {
7321
+ sendInstallTelemetry().catch(() => {
7322
+ });
7153
7323
  const args = process.argv.slice(2);
7154
7324
  if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
7155
7325
  printHelp();
@@ -7177,9 +7347,13 @@ async function main() {
7177
7347
  process.exit(0);
7178
7348
  }
7179
7349
  if (args[0] === "quickstart") {
7180
- await handleQuickstart();
7350
+ await handleQuickstart(args.slice(1));
7181
7351
  return;
7182
7352
  }
7353
+ if (args[0] === "connect") {
7354
+ await handleConnect();
7355
+ process.exit(0);
7356
+ }
7183
7357
  if (args[0] === "init") {
7184
7358
  await handleInit(args.slice(1));
7185
7359
  process.exit(0);