@permission-protocol/mcp-guard 0.1.0 → 0.2.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +101 -59
  4. package/SECURITY.md +40 -0
  5. package/dist/src/approval-server.d.ts +69 -3
  6. package/dist/src/approval-server.js +345 -86
  7. package/dist/src/approval-server.js.map +1 -1
  8. package/dist/src/approval-ui.d.ts +5 -0
  9. package/dist/src/approval-ui.js +120 -81
  10. package/dist/src/approval-ui.js.map +1 -1
  11. package/dist/src/classify.d.ts +29 -0
  12. package/dist/src/classify.js +107 -0
  13. package/dist/src/classify.js.map +1 -0
  14. package/dist/src/cli.js +49 -12
  15. package/dist/src/cli.js.map +1 -1
  16. package/dist/src/config.d.ts +21 -1
  17. package/dist/src/config.js +92 -5
  18. package/dist/src/config.js.map +1 -1
  19. package/dist/src/enforcement/index.d.ts +12 -0
  20. package/dist/src/enforcement/index.js +53 -0
  21. package/dist/src/enforcement/index.js.map +1 -0
  22. package/dist/src/enforcement/policy.d.ts +29 -0
  23. package/dist/src/enforcement/policy.js +60 -0
  24. package/dist/src/enforcement/policy.js.map +1 -0
  25. package/dist/src/enforcement/types.d.ts +19 -0
  26. package/dist/src/enforcement/types.js +3 -0
  27. package/dist/src/enforcement/types.js.map +1 -0
  28. package/dist/src/enforcement/verifier.d.ts +4 -0
  29. package/dist/src/enforcement/verifier.js +110 -0
  30. package/dist/src/enforcement/verifier.js.map +1 -0
  31. package/dist/src/engine.d.ts +11 -2
  32. package/dist/src/engine.js +20 -14
  33. package/dist/src/engine.js.map +1 -1
  34. package/dist/src/frame.d.ts +67 -0
  35. package/dist/src/frame.js +113 -0
  36. package/dist/src/frame.js.map +1 -0
  37. package/dist/src/pending.d.ts +108 -12
  38. package/dist/src/pending.js +174 -24
  39. package/dist/src/pending.js.map +1 -1
  40. package/dist/src/proxy.d.ts +29 -2
  41. package/dist/src/proxy.js +274 -105
  42. package/dist/src/proxy.js.map +1 -1
  43. package/dist/src/receipt-verify.d.ts +36 -0
  44. package/dist/src/receipt-verify.js +88 -0
  45. package/dist/src/receipt-verify.js.map +1 -0
  46. package/dist/src/receipt.d.ts +81 -7
  47. package/dist/src/receipt.js +100 -17
  48. package/dist/src/receipt.js.map +1 -1
  49. package/dist/src/signer.d.ts +38 -0
  50. package/dist/src/signer.js +112 -0
  51. package/dist/src/signer.js.map +1 -0
  52. package/dist/src/verify-cli.d.ts +1 -0
  53. package/dist/src/verify-cli.js +133 -0
  54. package/dist/src/verify-cli.js.map +1 -0
  55. package/dist/src/verify-core.d.ts +22 -0
  56. package/dist/src/verify-core.js +50 -0
  57. package/dist/src/verify-core.js.map +1 -0
  58. package/dist/src/webhook.d.ts +35 -0
  59. package/dist/src/webhook.js +57 -0
  60. package/dist/src/webhook.js.map +1 -0
  61. package/example/test.sh +5 -7
  62. package/package.json +33 -7
  63. package/templates/github-actions/receipt-gate.yml +64 -0
  64. package/assets/demo.gif +0 -0
  65. package/demo/_demo-scenario.sh +0 -43
  66. package/dist/src/tests/config.test.d.ts +0 -1
  67. package/dist/src/tests/config.test.js +0 -63
  68. package/dist/src/tests/config.test.js.map +0 -1
  69. package/dist/src/tests/engine.test.d.ts +0 -1
  70. package/dist/src/tests/engine.test.js +0 -46
  71. package/dist/src/tests/engine.test.js.map +0 -1
  72. package/dist/src/tests/receipt.test.d.ts +0 -1
  73. package/dist/src/tests/receipt.test.js +0 -44
  74. package/dist/src/tests/receipt.test.js.map +0 -1
  75. package/docs/index.html +0 -771
  76. package/permission-protocol-mcp-guard-0.1.0.tgz +0 -0
  77. package/src/approval-server.ts +0 -136
  78. package/src/approval-ui.ts +0 -154
  79. package/src/cli.ts +0 -84
  80. package/src/config.ts +0 -68
  81. package/src/engine.ts +0 -31
  82. package/src/pending.ts +0 -65
  83. package/src/proxy.ts +0 -173
  84. package/src/receipt.ts +0 -52
  85. package/src/tests/config.test.ts +0 -61
  86. package/src/tests/engine.test.ts +0 -47
  87. package/src/tests/receipt.test.ts +0 -44
  88. package/tsconfig.json +0 -16
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getApprovalHTML = getApprovalHTML;
4
+ /**
5
+ * The built-in approval page. It is static and holds no secret: the access token
6
+ * arrives in the URL fragment (`#t=…`, never sent to any server), is kept in
7
+ * sessionStorage for this tab, and is sent as `Authorization: Bearer` on every call.
8
+ */
4
9
  function getApprovalHTML() {
5
10
  return `<!DOCTYPE html>
6
11
  <html lang="en">
@@ -21,12 +26,9 @@ function getApprovalHTML() {
21
26
  display: inline-block; padding: 2px 8px; border-radius: 12px;
22
27
  font-size: 12px; font-weight: 600; text-transform: uppercase;
23
28
  }
24
- .badge-pending { background: #d29922; color: #0d1117; }
25
- .badge-approved { background: #238636; color: #fff; }
26
- .badge-denied { background: #da3633; color: #fff; }
29
+ .badge-pending, .badge-held_for_approval { background: #d29922; color: #0d1117; }
27
30
  .badge-allowed { background: #238636; color: #fff; }
28
31
  .badge-blocked { background: #da3633; color: #fff; }
29
- .badge-held_for_approval { background: #d29922; color: #0d1117; }
30
32
  .card {
31
33
  border: 1px solid #30363d; border-radius: 8px; padding: 16px;
32
34
  margin-bottom: 12px; background: #161b22;
@@ -35,19 +37,13 @@ function getApprovalHTML() {
35
37
  .tool-name { font-size: 18px; font-weight: 700; color: #f0f6fc; }
36
38
  .meta { font-size: 13px; color: #8b949e; margin-bottom: 8px; }
37
39
  .meta span { margin-right: 16px; }
38
- .args-toggle {
39
- background: none; border: 1px solid #30363d; color: #58a6ff;
40
- padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;
41
- margin-bottom: 8px;
42
- }
43
- .args-toggle:hover { border-color: #58a6ff; }
40
+ .summary { margin-bottom: 8px; }
44
41
  .args-content {
45
- display: none; background: #0d1117; border: 1px solid #30363d;
42
+ background: #0d1117; border: 1px solid #30363d;
46
43
  border-radius: 4px; padding: 12px; margin-bottom: 12px;
47
44
  font-family: 'SF Mono', Consolas, monospace; font-size: 13px;
48
45
  overflow-x: auto; white-space: pre-wrap; word-break: break-all;
49
46
  }
50
- .args-content.open { display: block; }
51
47
  .actions { display: flex; gap: 8px; }
52
48
  .btn {
53
49
  padding: 6px 16px; border-radius: 6px; border: none;
@@ -57,100 +53,143 @@ function getApprovalHTML() {
57
53
  .btn:disabled { opacity: 0.4; cursor: not-allowed; }
58
54
  .btn-approve { background: #238636; color: #fff; }
59
55
  .btn-deny { background: #da3633; color: #fff; }
60
- .empty {
56
+ .empty, .notice {
61
57
  text-align: center; padding: 40px; color: #8b949e;
62
58
  border: 1px dashed #30363d; border-radius: 8px; margin-bottom: 24px;
63
59
  }
60
+ .notice { color: #d29922; border-color: #d29922; }
64
61
  h2 { color: #c9d1d9; font-size: 18px; margin: 24px 0 12px; }
65
62
  table { width: 100%; border-collapse: collapse; font-size: 13px; }
66
63
  th { text-align: left; padding: 8px; border-bottom: 2px solid #30363d; color: #8b949e; font-weight: 600; }
67
64
  td { padding: 8px; border-bottom: 1px solid #21262d; }
68
- .pulse { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #3fb950; margin-right: 8px; animation: pulse 2s infinite; }
69
- @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
70
65
  .status-bar { display: flex; align-items: center; font-size: 12px; color: #8b949e; margin-bottom: 16px; }
71
66
  </style>
72
67
  </head>
73
68
  <body>
74
69
  <h1>MCP Guard — Approval Queue</h1>
75
70
  <p class="subtitle">Human-in-the-loop approval for AI tool calls</p>
76
- <div class="status-bar"><span class="pulse"></span> Polling every 3s</div>
71
+ <div class="status-bar" id="status">Connecting…</div>
77
72
 
78
73
  <div id="pending"></div>
79
74
  <h2>Recent Decisions</h2>
80
75
  <div id="receipts"></div>
81
76
 
82
77
  <script>
83
- function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
78
+ (function () {
79
+ var token = null;
80
+ var m = location.hash.match(/(?:^#|&)t=([^&]+)/);
81
+ if (m) {
82
+ token = decodeURIComponent(m[1]);
83
+ try { sessionStorage.setItem('mcp-guard-token', token); } catch (e) {}
84
+ history.replaceState(null, '', location.pathname + location.search);
85
+ } else {
86
+ try { token = sessionStorage.getItem('mcp-guard-token'); } catch (e) {}
87
+ }
84
88
 
85
- function toggleArgs(btn) {
86
- const c = btn.nextElementSibling;
87
- c.classList.toggle('open');
88
- btn.textContent = c.classList.contains('open') ? '▾ Hide arguments' : '▸ Show arguments';
89
- }
89
+ var statusEl = document.getElementById('status');
90
+ var pEl = document.getElementById('pending');
91
+ var rEl = document.getElementById('receipts');
90
92
 
91
- async function act(id, action) {
92
- const btns = document.querySelectorAll('[data-id="'+id+'"]');
93
- btns.forEach(b => b.disabled = true);
94
- try {
95
- const r = await fetch('/api/' + action + '/' + id, { method: 'POST' });
96
- if (!r.ok) { const e = await r.text(); alert('Error: ' + e); }
97
- } catch(e) { alert('Network error'); }
98
- refresh();
99
- }
93
+ function el(tag, cls, text) {
94
+ var n = document.createElement(tag);
95
+ if (cls) n.className = cls;
96
+ if (text != null) n.textContent = text;
97
+ return n;
98
+ }
100
99
 
101
- async function refresh() {
102
- try {
103
- const [pRes, rRes] = await Promise.all([
104
- fetch('/api/pending'), fetch('/api/receipts')
105
- ]);
106
- const pending = await pRes.json();
107
- const receipts = await rRes.json();
100
+ function api(path, method) {
101
+ return fetch(path, { method: method || 'GET', headers: { Authorization: 'Bearer ' + token }, cache: 'no-store' });
102
+ }
108
103
 
109
- const pEl = document.getElementById('pending');
110
- if (pending.length === 0) {
111
- pEl.innerHTML = '<div class="empty">No pending approvals</div>';
112
- } else {
113
- pEl.innerHTML = pending.map(p => \`
114
- <div class="card">
115
- <div class="card-header">
116
- <span class="tool-name">\${esc(p.tool_name)}</span>
117
- <span class="badge badge-pending">pending</span>
118
- </div>
119
- <div class="meta">
120
- <span>Agent: <strong>\${esc(p.agent_id)}</strong></span>
121
- <span>Rule: <strong>\${esc(p.rule_id)}</strong></span>
122
- <span>\${esc(new Date(p.timestamp).toLocaleTimeString())}</span>
123
- </div>
124
- <button class="args-toggle" onclick="toggleArgs(this)">▸ Show arguments</button>
125
- <div class="args-content">\${esc(JSON.stringify(p.tool_args, null, 2))}</div>
126
- <div class="actions">
127
- <button class="btn btn-approve" data-id="\${p.id}" onclick="act('\${p.id}','approve')">✅ Approve</button>
128
- <button class="btn btn-deny" data-id="\${p.id}" onclick="act('\${p.id}','deny')">❌ Deny</button>
129
- </div>
130
- </div>
131
- \`).join('');
132
- }
104
+ function noToken(msg) {
105
+ pEl.textContent = '';
106
+ pEl.appendChild(el('div', 'notice', msg));
107
+ statusEl.textContent = 'Not connected';
108
+ }
133
109
 
134
- const rEl = document.getElementById('receipts');
135
- if (receipts.length === 0) {
136
- rEl.innerHTML = '<div class="empty">No recent decisions</div>';
137
- } else {
138
- rEl.innerHTML = \`<table>
139
- <thead><tr><th>Time</th><th>Tool</th><th>Decision</th><th>Agent</th><th>Rule</th></tr></thead>
140
- <tbody>\${receipts.map(r => \`<tr>
141
- <td>\${esc(new Date(r.timestamp).toLocaleTimeString())}</td>
142
- <td><strong>\${esc(r.tool_name)}</strong></td>
143
- <td><span class="badge badge-\${r.decision}">\${esc(r.decision)}</span></td>
144
- <td>\${esc(r.agent_id)}</td>
145
- <td>\${esc(r.rule_id || '—')}</td>
146
- </tr>\`).join('')}</tbody>
147
- </table>\`;
148
- }
149
- } catch(e) { /* retry next cycle */ }
150
- }
110
+ async function act(id, verb, btns) {
111
+ btns.forEach(function (b) { b.disabled = true; });
112
+ try {
113
+ var r = await api('/api/' + verb + '/' + id, 'POST');
114
+ if (!r.ok) statusEl.textContent = 'Could not ' + verb + ' (HTTP ' + r.status + ')';
115
+ } catch (e) { statusEl.textContent = 'Network error'; }
116
+ refresh();
117
+ }
118
+
119
+ function renderPending(pending) {
120
+ pEl.textContent = '';
121
+ if (pending.length === 0) { pEl.appendChild(el('div', 'empty', 'No pending approvals')); return; }
122
+ pending.forEach(function (p) {
123
+ var card = el('div', 'card');
124
+ var head = el('div', 'card-header');
125
+ head.appendChild(el('span', 'tool-name', p.tool_name));
126
+ head.appendChild(el('span', 'badge badge-pending', p.acted ? 'ran · confirm' : 'pending'));
127
+ card.appendChild(head);
128
+ var meta = el('div', 'meta');
129
+ meta.appendChild(el('span', null, 'Agent: ' + p.agent_id));
130
+ meta.appendChild(el('span', null, 'Rule: ' + (p.rule_id || 'default action')));
131
+ meta.appendChild(el('span', null, new Date(p.created_at).toLocaleTimeString()));
132
+ if (p.countdown_remaining != null) meta.appendChild(el('span', null, 'Auto-releases in ' + p.countdown_remaining + 's'));
133
+ card.appendChild(meta);
134
+ card.appendChild(el('div', 'summary', p.summary));
135
+ if (p.scope) {
136
+ card.appendChild(el('div', 'meta', 'Approving signs for: ' + p.scope.scope
137
+ + (p.scope.ref ? ' · ' + p.scope.ref : '') + (p.scope.sha ? ' @ ' + p.scope.sha : '')));
138
+ }
139
+ card.appendChild(el('div', 'args-content', p.args_full || p.args_preview));
140
+ var actions = el('div', 'actions');
141
+ var approve = el('button', 'btn btn-approve', p.acted ? 'Acknowledge' : 'Approve');
142
+ var btns = [approve];
143
+ approve.addEventListener('click', function () { act(p.id, 'approve', btns); });
144
+ actions.appendChild(approve);
145
+ if (!p.acted) {
146
+ var deny = el('button', 'btn btn-deny', 'Deny');
147
+ btns.push(deny);
148
+ deny.addEventListener('click', function () { act(p.id, 'deny', btns); });
149
+ actions.appendChild(deny);
150
+ }
151
+ card.appendChild(actions);
152
+ pEl.appendChild(card);
153
+ });
154
+ }
155
+
156
+ function renderReceipts(receipts) {
157
+ rEl.textContent = '';
158
+ if (receipts.length === 0) { rEl.appendChild(el('div', 'empty', 'No recent decisions')); return; }
159
+ var table = el('table');
160
+ var thead = el('thead'); var hr = el('tr');
161
+ ['Time', 'Action', 'Decision', 'Approved by', 'Rule', 'Receipt'].forEach(function (h) { hr.appendChild(el('th', null, h)); });
162
+ thead.appendChild(hr); table.appendChild(thead);
163
+ var tbody = el('tbody');
164
+ receipts.forEach(function (r) {
165
+ var e = r.envelope || {};
166
+ var tr = el('tr');
167
+ tr.appendChild(el('td', null, new Date(e.issued_at).toLocaleTimeString()));
168
+ tr.appendChild(el('td', null, e.tool || e.method));
169
+ var td = el('td'); td.appendChild(el('span', 'badge badge-' + e.decision, e.decision)); tr.appendChild(td);
170
+ tr.appendChild(el('td', null, e.approval ? e.approval.kind + ': ' + e.approval.id : '—'));
171
+ tr.appendChild(el('td', null, e.rule_id || '—'));
172
+ tr.appendChild(el('td', null, e.receipt_id));
173
+ tbody.appendChild(tr);
174
+ });
175
+ table.appendChild(tbody);
176
+ rEl.appendChild(table);
177
+ }
178
+
179
+ async function refresh() {
180
+ if (!token) { noToken('Open the approval URL printed in the terminal. It ends with #t=… and carries the access token.'); return; }
181
+ try {
182
+ var res = await Promise.all([api('/api/pending'), api('/api/receipts')]);
183
+ if (res[0].status === 401) { noToken('The access token is missing or wrong. Open the URL printed in the terminal again.'); return; }
184
+ renderPending(await res[0].json());
185
+ renderReceipts(await res[1].json());
186
+ statusEl.textContent = 'Live · refreshes every 3s';
187
+ } catch (e) { statusEl.textContent = 'Disconnected · retrying'; }
188
+ }
151
189
 
152
- refresh();
153
- setInterval(refresh, 3000);
190
+ refresh();
191
+ setInterval(refresh, 3000);
192
+ })();
154
193
  </script>
155
194
  </body>
156
195
  </html>`;
@@ -1 +1 @@
1
- {"version":3,"file":"approval-ui.js","sourceRoot":"","sources":["../../src/approval-ui.ts"],"names":[],"mappings":";;AAAA,0CAyJC;AAzJD,SAAgB,eAAe;IAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAuJD,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"approval-ui.js","sourceRoot":"","sources":["../../src/approval-ui.ts"],"names":[],"mappings":";;AAKA,0CA2LC;AAhMD;;;;GAIG;AACH,SAAgB,eAAe;IAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAyLD,CAAC;AACT,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { Rule } from './config.js';
2
+ import type { Decision } from './engine.js';
3
+ /**
4
+ * Permission Deck Slice 1 — the reversibility/confidence classifier.
5
+ *
6
+ * The base engine produces allow | block | held_for_approval from a rule match. This
7
+ * layer enriches an attention-requiring decision with:
8
+ * - which human lane it belongs in (decide | unblock | verify)
9
+ * - reversibility (the primary risk axis)
10
+ * - a countdown for reversible holds
11
+ *
12
+ * It can also DOWNGRADE a protected spend under its cap into an "act now, surface in
13
+ * Verify" decision. The amount is read only from the argument the rule names
14
+ * (`amount_arg`) — the argument the tool actually acts on — never from agent metadata.
15
+ *
16
+ * Agent metadata under `_pp` (confidence, requires_input) can only ADD human attention:
17
+ * it routes lanes but never turns a held or blocked call into an allowed one.
18
+ *
19
+ * Pure function. No I/O. Fully unit-testable.
20
+ */
21
+ export interface ActionMeta {
22
+ confidence?: number;
23
+ requires_input?: boolean;
24
+ }
25
+ /** Pull Permission Deck lane hints out of the tool-call args (under `_pp`). */
26
+ export declare function extractMeta(toolArgs: Record<string, unknown> | undefined): ActionMeta;
27
+ /** The capped amount, or undefined when it is missing or not a finite non-negative number. */
28
+ export declare function readAmount(rule: Rule, toolArgs: Record<string, unknown> | undefined): number | undefined;
29
+ export declare function classify(base: Decision, rule: Rule | null, meta: ActionMeta, toolArgs?: Record<string, unknown>): Decision;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractMeta = extractMeta;
4
+ exports.readAmount = readAmount;
5
+ exports.classify = classify;
6
+ /** Pull Permission Deck lane hints out of the tool-call args (under `_pp`). */
7
+ function extractMeta(toolArgs) {
8
+ const pp = toolArgs?._pp;
9
+ const meta = {};
10
+ if (!pp || typeof pp !== 'object' || Array.isArray(pp))
11
+ return meta;
12
+ const p = pp;
13
+ if (typeof p.confidence === 'number' && Number.isFinite(p.confidence))
14
+ meta.confidence = clamp(p.confidence, 0, 100);
15
+ if (typeof p.requires_input === 'boolean')
16
+ meta.requires_input = p.requires_input;
17
+ return meta;
18
+ }
19
+ /** The capped amount, or undefined when it is missing or not a finite non-negative number. */
20
+ function readAmount(rule, toolArgs) {
21
+ if (!rule.amount_arg)
22
+ return undefined;
23
+ const v = toolArgs?.[rule.amount_arg];
24
+ return typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined;
25
+ }
26
+ function classify(base, rule, meta, toolArgs) {
27
+ // Explicit hard block or plain allow with no protected rule: pass through untouched.
28
+ if (base.decision === 'blocked')
29
+ return base;
30
+ if (base.decision === 'allowed') {
31
+ // Allowed actions can still be surfaced for Verify when confidence is low.
32
+ return maybeVerify(base, rule, meta);
33
+ }
34
+ // From here: the action is held_for_approval. Resolve the lane.
35
+ const reversibility = rule?.reversibility ?? 'irreversible'; // fail safe: unknown = irreversible
36
+ const confidence = meta.confidence !== undefined ? { confidence: meta.confidence } : {};
37
+ // 1) Needs an input only the human has -> Unblock. No countdown. An irreversible hold
38
+ // stays a Decide hard stop whatever the agent claims.
39
+ if (meta.requires_input && reversibility === 'reversible') {
40
+ return {
41
+ ...base,
42
+ lane: 'unblock',
43
+ reversibility,
44
+ ...confidence,
45
+ reason: `${base.reason}; needs human-only input`,
46
+ };
47
+ }
48
+ // 2) Spend with a cap -> act+verify under cap, hard-stop at/over cap or when unreadable.
49
+ if (rule?.cap_usd !== undefined) {
50
+ const amount = readAmount(rule, toolArgs);
51
+ if (amount === undefined) {
52
+ return {
53
+ ...base,
54
+ lane: 'decide',
55
+ reversibility: 'irreversible',
56
+ ...confidence,
57
+ reason: `${base.reason}; "${rule.amount_arg}" missing or not a non-negative number — hard stop`,
58
+ };
59
+ }
60
+ if (amount < rule.cap_usd) {
61
+ return {
62
+ ...base,
63
+ decision: 'allowed', // under cap: act now under the standing rule...
64
+ lane: 'verify', // ...but surface for post-hoc confirmation
65
+ reversibility,
66
+ ...confidence,
67
+ reason: `${base.reason}; under the $${rule.cap_usd} cap — acted, routed to Verify`,
68
+ };
69
+ }
70
+ return {
71
+ ...base,
72
+ lane: 'decide',
73
+ reversibility: 'irreversible', // over-cap spend is treated as a hard stop
74
+ ...confidence,
75
+ reason: `${base.reason}; at or over the $${rule.cap_usd} cap — hard stop`,
76
+ };
77
+ }
78
+ // 3) General held action -> Decide. Reversible gets a countdown; irreversible is a hard stop.
79
+ const decideCard = { ...base, lane: 'decide', reversibility, ...confidence };
80
+ if (reversibility === 'reversible' && rule?.countdown_seconds) {
81
+ decideCard.countdown_seconds = rule.countdown_seconds;
82
+ decideCard.reason = `${base.reason}; reversible — auto-releases in ${rule.countdown_seconds}s unless held`;
83
+ }
84
+ else {
85
+ decideCard.reason = `${base.reason}; ${reversibility} — hard stop until approved`;
86
+ }
87
+ return decideCard;
88
+ }
89
+ /** An allowed action routes to Verify when its self-reported confidence is below threshold. */
90
+ function maybeVerify(base, rule, meta) {
91
+ if (rule?.verify_threshold === undefined || meta.confidence === undefined)
92
+ return base;
93
+ const threshold = rule.verify_threshold;
94
+ if (meta.confidence < threshold) {
95
+ return {
96
+ ...base,
97
+ lane: 'verify',
98
+ confidence: meta.confidence,
99
+ reason: `${base.reason}; confidence ${meta.confidence} < ${threshold} — routed to Verify`,
100
+ };
101
+ }
102
+ return base;
103
+ }
104
+ function clamp(n, lo, hi) {
105
+ return Math.max(lo, Math.min(hi, n));
106
+ }
107
+ //# sourceMappingURL=classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/classify.ts"],"names":[],"mappings":"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"}
package/dist/src/cli.js CHANGED
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.main = main;
4
4
  const config_js_1 = require("./config.js");
5
5
  const proxy_js_1 = require("./proxy.js");
6
+ const verify_cli_js_1 = require("./verify-cli.js");
7
+ const signer_js_1 = require("./signer.js");
6
8
  const node_path_1 = require("node:path");
7
9
  const node_child_process_1 = require("node:child_process");
8
10
  function main() {
@@ -13,14 +15,24 @@ function main() {
13
15
  const result = (0, node_child_process_1.spawnSync)('bash', [scriptPath], { stdio: 'inherit' });
14
16
  process.exit(result.status ?? 0);
15
17
  }
18
+ if (args[0] === 'verify') {
19
+ process.exit((0, verify_cli_js_1.runVerify)(args.slice(1)));
20
+ }
21
+ if (args[0] === 'export-key') {
22
+ // Write the signing public key (the trust anchor verifiers pin). Never the private key.
23
+ const out = args[1] || 'mcp-guard.pub.pem';
24
+ (0, signer_js_1.exportPublicKey)(out);
25
+ console.log(`Wrote public key ${(0, signer_js_1.currentKeyId)()} to ${out} (private key stays in ${(0, signer_js_1.keysDir)()})`);
26
+ process.exit(0);
27
+ }
16
28
  let configPath = './pp.config.yaml';
17
29
  let agentId = 'unknown';
18
30
  let modeOverride;
19
31
  let approvalPort;
20
- let serverCommand = [];
32
+ let operator;
21
33
  const dashDashIndex = args.indexOf('--');
22
34
  const cliArgs = dashDashIndex >= 0 ? args.slice(0, dashDashIndex) : args;
23
- serverCommand = dashDashIndex >= 0 ? args.slice(dashDashIndex + 1) : [];
35
+ const serverCommand = dashDashIndex >= 0 ? args.slice(dashDashIndex + 1) : [];
24
36
  for (let i = 0; i < cliArgs.length; i++) {
25
37
  if (cliArgs[i] === '--config' && cliArgs[i + 1]) {
26
38
  configPath = cliArgs[++i];
@@ -28,10 +40,16 @@ function main() {
28
40
  else if (cliArgs[i] === '--agent-id' && cliArgs[i + 1]) {
29
41
  agentId = cliArgs[++i];
30
42
  }
43
+ else if (cliArgs[i] === '--operator' && cliArgs[i + 1]) {
44
+ operator = cliArgs[++i];
45
+ }
31
46
  else if (cliArgs[i] === '--mode' && cliArgs[i + 1]) {
32
47
  const m = cliArgs[++i];
33
- if (m === 'enforce' || m === 'observe')
34
- modeOverride = m;
48
+ if (m !== 'enforce' && m !== 'observe') {
49
+ process.stderr.write('[mcp-guard] Error: --mode must be enforce or observe\n');
50
+ process.exit(1);
51
+ }
52
+ modeOverride = m;
35
53
  }
36
54
  else if (cliArgs[i] === '--approval-port' && cliArgs[i + 1]) {
37
55
  approvalPort = parseInt(cliArgs[++i], 10);
@@ -49,31 +67,50 @@ function main() {
49
67
  process.stderr.write('[mcp-guard] Error: No server command. Use: mcp-guard [options] -- <server command>\n');
50
68
  process.exit(1);
51
69
  }
52
- const config = (0, config_js_1.loadConfig)(configPath);
70
+ let config;
71
+ try {
72
+ config = (0, config_js_1.loadConfig)(configPath);
73
+ }
74
+ catch (err) {
75
+ process.stderr.write(`[mcp-guard] Error: ${err.message}\n`);
76
+ process.exit(1);
77
+ }
53
78
  if (modeOverride)
54
79
  config.mode = modeOverride;
55
80
  process.stderr.write(`[mcp-guard] Loaded ${config.rules.length} rules (default: ${config.default_action}, mode: ${config.mode})\n`);
56
- process.stderr.write(`[mcp-guard] Starting server: ${serverCommand.join(' ')}\n`);
57
- (0, proxy_js_1.startProxy)(config, agentId, serverCommand, approvalPort);
81
+ if (config.default_action === 'allow') {
82
+ process.stderr.write('[mcp-guard] WARNING: default_action is "allow" — any tool or method without a rule runs unchecked. Use "block" or "require_approval" to fail closed.\n');
83
+ }
84
+ process.stderr.write(`[mcp-guard] Starting server: ${serverCommand[0]}\n`);
85
+ try {
86
+ (0, proxy_js_1.startProxy)(config, agentId, serverCommand, { approvalPort, operator });
87
+ }
88
+ catch (err) {
89
+ process.stderr.write(`[mcp-guard] Error: ${err.message}\n`);
90
+ process.exit(1);
91
+ }
58
92
  }
59
93
  function printHelp() {
60
94
  const help = `
61
- mcp-guard — MCP tool-call policy proxy
95
+ mcp-guard — MCP policy proxy
62
96
 
63
97
  Usage:
64
98
  mcp-guard [options] -- <server command>
99
+ mcp-guard verify --receipt-file <path> --pubkey <pem> [--scope s] [--scope-ref r] [--scope-sha h]
100
+ mcp-guard export-key [out.pem]
65
101
 
66
102
  Options:
67
103
  --config <path> Path to config file (default: ./pp.config.yaml)
68
104
  --agent-id <id> Agent identifier for receipts (default: "unknown")
105
+ --operator <name> Approver name recorded on receipts (default: OS username)
69
106
  --mode <mode> enforce or observe (overrides config; default: enforce)
70
- --approval-port <port> Enable approval UI on this port (e.g. 3100)
107
+ --approval-port <port> Enable the approval UI on 127.0.0.1:<port>
71
108
  -h, --help Show this help
72
109
 
73
- Example:
74
- mcp-guard --config pp.config.yaml -- node my-mcp-server.js
110
+ The approval UI URL printed at startup carries its access token after "#t=".
111
+ Open that exact URL; requests without the token are refused.
75
112
 
76
- # With approval UI:
113
+ Example:
77
114
  mcp-guard --config pp.config.yaml --approval-port 3100 -- node my-mcp-server.js
78
115
  `.trim();
79
116
  console.log(help);
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,33 @@
1
1
  export type Action = 'allow' | 'block' | 'require_approval';
2
+ export type Reversibility = 'reversible' | 'irreversible';
2
3
  export interface Rule {
3
4
  id: string;
4
- tool: string;
5
+ /** JSON-RPC method this rule governs. Defaults to `tools/call`. */
6
+ method?: string;
7
+ /** Tool name; required for `tools/call` rules, forbidden for other methods. */
8
+ tool?: string;
5
9
  action: Action;
10
+ reversibility?: Reversibility;
11
+ countdown_seconds?: number;
12
+ cap_usd?: number;
13
+ amount_arg?: string;
14
+ verify_threshold?: number;
6
15
  }
7
16
  export type Mode = 'enforce' | 'observe';
8
17
  export interface Config {
9
18
  default_action: Action;
10
19
  mode: Mode;
11
20
  rules: Rule[];
21
+ /** Held requests expire after this many seconds. */
22
+ approval_ttl_seconds: number;
23
+ /** Signed receipts expire this many seconds after issue. */
24
+ receipt_ttl_seconds: number;
25
+ /** sha256 of the policy source, bound into every receipt. */
26
+ policy_sha256: string;
12
27
  }
28
+ export declare const DEFAULT_APPROVAL_TTL_SECONDS = 900;
29
+ export declare const DEFAULT_RECEIPT_TTL_SECONDS = 900;
30
+ export declare const TOOLS_CALL = "tools/call";
13
31
  export declare function loadConfig(configPath: string): Config;
32
+ /** Parse and strictly validate a policy. Anything unrecognized is an error, never ignored. */
33
+ export declare function parseConfig(raw: string): Config;