protect-mcp 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/demo-server.ts
4
+ import { createInterface } from "readline";
5
+ var TOOLS = [
6
+ {
7
+ name: "read_file",
8
+ description: "Read the contents of a file",
9
+ inputSchema: {
10
+ type: "object",
11
+ properties: { path: { type: "string", description: "File path to read" } },
12
+ required: ["path"]
13
+ }
14
+ },
15
+ {
16
+ name: "write_file",
17
+ description: "Write content to a file",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ path: { type: "string", description: "File path to write" },
22
+ content: { type: "string", description: "Content to write" }
23
+ },
24
+ required: ["path", "content"]
25
+ }
26
+ },
27
+ {
28
+ name: "delete_file",
29
+ description: "Delete a file from the filesystem",
30
+ inputSchema: {
31
+ type: "object",
32
+ properties: { path: { type: "string", description: "File path to delete" } },
33
+ required: ["path"]
34
+ }
35
+ },
36
+ {
37
+ name: "web_search",
38
+ description: "Search the web for information",
39
+ inputSchema: {
40
+ type: "object",
41
+ properties: { query: { type: "string", description: "Search query" } },
42
+ required: ["query"]
43
+ }
44
+ },
45
+ {
46
+ name: "deploy",
47
+ description: "Deploy the application to production",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {
51
+ environment: { type: "string", description: "Target environment", enum: ["staging", "production"] },
52
+ reason: { type: "string", description: "Deployment reason" }
53
+ },
54
+ required: ["environment"]
55
+ }
56
+ }
57
+ ];
58
+ function handleRequest(request) {
59
+ if (request.method === "initialize") {
60
+ return JSON.stringify({
61
+ jsonrpc: "2.0",
62
+ id: request.id,
63
+ result: {
64
+ protocolVersion: "2024-11-05",
65
+ serverInfo: { name: "protect-mcp-demo", version: "0.2.0" },
66
+ capabilities: { tools: {} }
67
+ }
68
+ });
69
+ }
70
+ if (request.method === "notifications/initialized") {
71
+ return "";
72
+ }
73
+ if (request.method === "tools/list") {
74
+ return JSON.stringify({
75
+ jsonrpc: "2.0",
76
+ id: request.id,
77
+ result: { tools: TOOLS }
78
+ });
79
+ }
80
+ if (request.method === "tools/call") {
81
+ const toolName = request.params?.name || "unknown";
82
+ const args = request.params?.arguments || {};
83
+ let resultText;
84
+ switch (toolName) {
85
+ case "read_file":
86
+ resultText = `[demo] Read file: ${args.path || "/example.txt"}
87
+ Contents: Hello from protect-mcp demo server!`;
88
+ break;
89
+ case "write_file":
90
+ resultText = `[demo] Wrote ${String(args.content || "").length} bytes to ${args.path || "/example.txt"}`;
91
+ break;
92
+ case "delete_file":
93
+ resultText = `[demo] Deleted file: ${args.path || "/example.txt"}`;
94
+ break;
95
+ case "web_search":
96
+ resultText = `[demo] Search results for "${args.query || "test"}":
97
+ 1. Example result \u2014 scopeblind.com
98
+ 2. MCP security \u2014 modelcontextprotocol.io`;
99
+ break;
100
+ case "deploy":
101
+ resultText = `[demo] Deployed to ${args.environment || "staging"}${args.reason ? ` (reason: ${args.reason})` : ""}`;
102
+ break;
103
+ default:
104
+ resultText = `[demo] Unknown tool: ${toolName}`;
105
+ }
106
+ return JSON.stringify({
107
+ jsonrpc: "2.0",
108
+ id: request.id,
109
+ result: {
110
+ content: [{ type: "text", text: resultText }]
111
+ }
112
+ });
113
+ }
114
+ if (request.id !== void 0) {
115
+ return JSON.stringify({
116
+ jsonrpc: "2.0",
117
+ id: request.id,
118
+ error: { code: -32601, message: `Method not found: ${request.method}` }
119
+ });
120
+ }
121
+ return "";
122
+ }
123
+ var rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
124
+ rl.on("line", (line) => {
125
+ const trimmed = line.trim();
126
+ if (!trimmed) return;
127
+ try {
128
+ const request = JSON.parse(trimmed);
129
+ const response = handleRequest(request);
130
+ if (response) {
131
+ process.stdout.write(response + "\n");
132
+ }
133
+ } catch {
134
+ }
135
+ });
136
+ process.stderr.write("[DEMO_SERVER] protect-mcp demo server started \u2014 5 tools registered\n");
package/dist/index.d.mts CHANGED
@@ -21,6 +21,8 @@ interface ToolPolicy {
21
21
  rate_limit?: string;
22
22
  /** Explicitly block this tool */
23
23
  block?: boolean;
24
+ /** Require human approval before executing (non-blocking: returns MCP error for LLM to suspend) */
25
+ require_approval?: boolean;
24
26
  /** Minimum trust tier required for this tool (v2) */
25
27
  min_tier?: TrustTier;
26
28
  /** Tier-specific rate limits (v2) */
@@ -113,9 +115,9 @@ interface DecisionLog {
113
115
  /** Tool name that was called */
114
116
  tool: string;
115
117
  /** Decision: allow or deny */
116
- decision: 'allow' | 'deny';
118
+ decision: 'allow' | 'deny' | 'require_approval';
117
119
  /** Why this decision was made */
118
- reason_code: 'policy_allow' | 'policy_block' | 'rate_limit_exceeded' | 'observe_mode' | 'default_allow' | 'tier_insufficient' | 'external_pdp_allow' | 'external_pdp_deny' | 'external_pdp_error';
120
+ reason_code: string;
119
121
  /** SHA-256 digest of the canonicalized policy file */
120
122
  policy_digest: string;
121
123
  /** Which policy engine made the decision */
@@ -154,6 +156,63 @@ interface ProtectConfig {
154
156
  credentials?: Record<string, CredentialConfig>;
155
157
  }
156
158
 
159
+ /**
160
+ * Summary of evidence for tier evaluation.
161
+ */
162
+ interface EvidenceSummary$1 {
163
+ receipt_count: number;
164
+ epoch_span: number;
165
+ issuer_count: number;
166
+ }
167
+ /**
168
+ * Thresholds for the 'evidenced' tier.
169
+ */
170
+ interface EvidenceThresholds {
171
+ min_receipts: number;
172
+ min_epoch_span: number;
173
+ min_issuers: number;
174
+ }
175
+ /**
176
+ * Evidence store — tracks receipt history per agent.
177
+ */
178
+ declare class EvidenceStore {
179
+ private agents;
180
+ private filePath;
181
+ private dirty;
182
+ constructor(dir?: string);
183
+ /**
184
+ * Record a receipt observation for an agent.
185
+ */
186
+ record(agentId: string, issuer: string, timestamp?: string): void;
187
+ /**
188
+ * Get the evidence summary for an agent.
189
+ */
190
+ getSummary(agentId: string): EvidenceSummary$1;
191
+ /**
192
+ * Check if an agent meets the evidenced tier thresholds.
193
+ */
194
+ meetsEvidencedThreshold(agentId: string, thresholds?: EvidenceThresholds): boolean;
195
+ /**
196
+ * Persist to disk (call periodically or on shutdown).
197
+ */
198
+ save(): void;
199
+ /**
200
+ * Load from disk.
201
+ */
202
+ private load;
203
+ /**
204
+ * Get total agent count (for status display).
205
+ */
206
+ agentCount(): number;
207
+ /**
208
+ * Get all agent summaries (for status display).
209
+ */
210
+ allSummaries(): Array<{
211
+ agent_id: string;
212
+ summary: EvidenceSummary$1;
213
+ }>;
214
+ }
215
+
157
216
  /**
158
217
  * @scopeblind/protect-mcp — Trust Tier Admission Evaluator
159
218
  *
@@ -163,8 +222,7 @@ interface ProtectConfig {
163
222
  *
164
223
  * Tiers (ascending): unknown → signed-known → evidenced → privileged
165
224
  *
166
- * Sprint 2: Simple evaluation (has valid manifest = signed-known).
167
- * Full evidence evaluation (evidenced tier) is stubbed.
225
+ * v2: Real evidence evaluation via EvidenceStore when available.
168
226
  */
169
227
 
170
228
  /**
@@ -180,7 +238,7 @@ interface ManifestPresentation {
180
238
  public_key?: string;
181
239
  /** Whether the manifest signature was verified */
182
240
  signature_valid?: boolean;
183
- /** Optional evidence summary for tier upgrade */
241
+ /** Optional evidence summary for tier upgrade (inline, without store) */
184
242
  evidence_summary?: {
185
243
  receipt_count: number;
186
244
  epoch_span: number;
@@ -201,92 +259,56 @@ interface AdmissionResult {
201
259
  * Maps agent IDs to explicitly assigned tiers.
202
260
  */
203
261
  type TierOverrides = Record<string, TrustTier>;
262
+ /**
263
+ * Options for tier evaluation.
264
+ */
265
+ interface EvaluateTierOptions {
266
+ overrides?: TierOverrides;
267
+ evidenceStore?: EvidenceStore;
268
+ thresholds?: EvidenceThresholds;
269
+ }
204
270
  /**
205
271
  * Evaluate an agent's trust tier based on their presented credentials.
206
272
  *
207
273
  * @param manifest - Manifest presentation from the agent (or null if none)
208
- * @param overrides - Operator-configured tier overrides
274
+ * @param opts - Evaluation options (overrides, evidence store, thresholds)
209
275
  * @returns AdmissionResult with assigned tier
210
276
  */
211
- declare function evaluateTier(manifest: ManifestPresentation | null, overrides?: TierOverrides): AdmissionResult;
277
+ declare function evaluateTier(manifest: ManifestPresentation | null, opts?: TierOverrides | EvaluateTierOptions): AdmissionResult;
212
278
  /**
213
279
  * Check if a trust tier meets the minimum required tier.
214
280
  */
215
281
  declare function meetsMinTier(actual: TrustTier, required: TrustTier): boolean;
216
282
 
217
- /**
218
- * ProtectGateway — stdio MITM proxy for MCP servers.
219
- *
220
- * Sits between an MCP client (stdin/stdout) and a wrapped MCP server (child process).
221
- * Intercepts `tools/call` requests for policy enforcement and decision logging.
222
- * Passes through all other JSON-RPC messages transparently.
223
- *
224
- * v2 features:
225
- * - Shadow mode (default): observe + signed receipts, no blocking
226
- * - Trust-tier gating: evaluate manifest at admission, assign tier
227
- * - Credential vault: inject secrets, agent never sees raw keys
228
- * - BYOPE: pluggable policy decision via external HTTP webhook
229
- * - Signed receipts: every decision produces a signed artifact
230
- */
231
283
  declare class ProtectGateway {
232
284
  private child;
233
285
  private config;
234
286
  private rateLimitStore;
235
287
  private clientReader;
288
+ private logFilePath;
289
+ private receiptFilePath;
290
+ private evidenceStore;
291
+ private receiptBuffer;
292
+ /** Approval grants keyed by request_id (scoped to the specific action that was requested) */
293
+ private approvalStore;
294
+ /** Random nonce generated at startup — required for approval endpoint authentication */
295
+ private readonly approvalNonce;
236
296
  private currentTier;
237
297
  private admissionResult;
238
298
  constructor(config: ProtectConfig);
239
- /**
240
- * Start the gateway: spawn child process and wire up message relay.
241
- */
242
299
  start(): Promise<void>;
243
- /**
244
- * Set the trust tier for this session.
245
- * Called at admission (first interaction) or by explicit manifest presentation.
246
- */
247
300
  setManifest(manifest: ManifestPresentation | null): AdmissionResult;
248
- /**
249
- * Handle a message from the MCP client (stdin).
250
- * Intercept tools/call requests; pass through everything else.
251
- */
252
301
  private handleClientMessage;
253
- /**
254
- * Handle a message from the wrapped MCP server (child stdout).
255
- * Forward to client (stdout) transparently.
256
- */
302
+ private interceptToolCallAsync;
257
303
  private handleServerMessage;
258
- /**
259
- * Intercept a tools/call request. Returns a JSON-RPC error response if denied, null if allowed.
260
- */
304
+ private injectParamsCredentials;
261
305
  private interceptToolCall;
262
- /**
263
- * Get the applicable rate limit spec based on the agent's tier.
264
- */
265
306
  private getTierRateLimit;
266
- /**
267
- * Emit a structured decision log to stderr.
268
- * If signing is enabled, also emits a signed artifact.
269
- */
270
307
  private emitDecisionLog;
271
- /**
272
- * Create a JSON-RPC error response.
273
- */
274
308
  private makeErrorResponse;
275
- /**
276
- * Send a message to the child process (wrapped MCP server).
277
- */
278
309
  private sendToChild;
279
- /**
280
- * Send a message to the MCP client (stdout).
281
- */
282
310
  private sendToClient;
283
- /**
284
- * Log a message to stderr (debug output).
285
- */
286
311
  private log;
287
- /**
288
- * Stop the gateway: kill child process and exit.
289
- */
290
312
  stop(): void;
291
313
  }
292
314
 
package/dist/index.d.ts CHANGED
@@ -21,6 +21,8 @@ interface ToolPolicy {
21
21
  rate_limit?: string;
22
22
  /** Explicitly block this tool */
23
23
  block?: boolean;
24
+ /** Require human approval before executing (non-blocking: returns MCP error for LLM to suspend) */
25
+ require_approval?: boolean;
24
26
  /** Minimum trust tier required for this tool (v2) */
25
27
  min_tier?: TrustTier;
26
28
  /** Tier-specific rate limits (v2) */
@@ -113,9 +115,9 @@ interface DecisionLog {
113
115
  /** Tool name that was called */
114
116
  tool: string;
115
117
  /** Decision: allow or deny */
116
- decision: 'allow' | 'deny';
118
+ decision: 'allow' | 'deny' | 'require_approval';
117
119
  /** Why this decision was made */
118
- reason_code: 'policy_allow' | 'policy_block' | 'rate_limit_exceeded' | 'observe_mode' | 'default_allow' | 'tier_insufficient' | 'external_pdp_allow' | 'external_pdp_deny' | 'external_pdp_error';
120
+ reason_code: string;
119
121
  /** SHA-256 digest of the canonicalized policy file */
120
122
  policy_digest: string;
121
123
  /** Which policy engine made the decision */
@@ -154,6 +156,63 @@ interface ProtectConfig {
154
156
  credentials?: Record<string, CredentialConfig>;
155
157
  }
156
158
 
159
+ /**
160
+ * Summary of evidence for tier evaluation.
161
+ */
162
+ interface EvidenceSummary$1 {
163
+ receipt_count: number;
164
+ epoch_span: number;
165
+ issuer_count: number;
166
+ }
167
+ /**
168
+ * Thresholds for the 'evidenced' tier.
169
+ */
170
+ interface EvidenceThresholds {
171
+ min_receipts: number;
172
+ min_epoch_span: number;
173
+ min_issuers: number;
174
+ }
175
+ /**
176
+ * Evidence store — tracks receipt history per agent.
177
+ */
178
+ declare class EvidenceStore {
179
+ private agents;
180
+ private filePath;
181
+ private dirty;
182
+ constructor(dir?: string);
183
+ /**
184
+ * Record a receipt observation for an agent.
185
+ */
186
+ record(agentId: string, issuer: string, timestamp?: string): void;
187
+ /**
188
+ * Get the evidence summary for an agent.
189
+ */
190
+ getSummary(agentId: string): EvidenceSummary$1;
191
+ /**
192
+ * Check if an agent meets the evidenced tier thresholds.
193
+ */
194
+ meetsEvidencedThreshold(agentId: string, thresholds?: EvidenceThresholds): boolean;
195
+ /**
196
+ * Persist to disk (call periodically or on shutdown).
197
+ */
198
+ save(): void;
199
+ /**
200
+ * Load from disk.
201
+ */
202
+ private load;
203
+ /**
204
+ * Get total agent count (for status display).
205
+ */
206
+ agentCount(): number;
207
+ /**
208
+ * Get all agent summaries (for status display).
209
+ */
210
+ allSummaries(): Array<{
211
+ agent_id: string;
212
+ summary: EvidenceSummary$1;
213
+ }>;
214
+ }
215
+
157
216
  /**
158
217
  * @scopeblind/protect-mcp — Trust Tier Admission Evaluator
159
218
  *
@@ -163,8 +222,7 @@ interface ProtectConfig {
163
222
  *
164
223
  * Tiers (ascending): unknown → signed-known → evidenced → privileged
165
224
  *
166
- * Sprint 2: Simple evaluation (has valid manifest = signed-known).
167
- * Full evidence evaluation (evidenced tier) is stubbed.
225
+ * v2: Real evidence evaluation via EvidenceStore when available.
168
226
  */
169
227
 
170
228
  /**
@@ -180,7 +238,7 @@ interface ManifestPresentation {
180
238
  public_key?: string;
181
239
  /** Whether the manifest signature was verified */
182
240
  signature_valid?: boolean;
183
- /** Optional evidence summary for tier upgrade */
241
+ /** Optional evidence summary for tier upgrade (inline, without store) */
184
242
  evidence_summary?: {
185
243
  receipt_count: number;
186
244
  epoch_span: number;
@@ -201,92 +259,56 @@ interface AdmissionResult {
201
259
  * Maps agent IDs to explicitly assigned tiers.
202
260
  */
203
261
  type TierOverrides = Record<string, TrustTier>;
262
+ /**
263
+ * Options for tier evaluation.
264
+ */
265
+ interface EvaluateTierOptions {
266
+ overrides?: TierOverrides;
267
+ evidenceStore?: EvidenceStore;
268
+ thresholds?: EvidenceThresholds;
269
+ }
204
270
  /**
205
271
  * Evaluate an agent's trust tier based on their presented credentials.
206
272
  *
207
273
  * @param manifest - Manifest presentation from the agent (or null if none)
208
- * @param overrides - Operator-configured tier overrides
274
+ * @param opts - Evaluation options (overrides, evidence store, thresholds)
209
275
  * @returns AdmissionResult with assigned tier
210
276
  */
211
- declare function evaluateTier(manifest: ManifestPresentation | null, overrides?: TierOverrides): AdmissionResult;
277
+ declare function evaluateTier(manifest: ManifestPresentation | null, opts?: TierOverrides | EvaluateTierOptions): AdmissionResult;
212
278
  /**
213
279
  * Check if a trust tier meets the minimum required tier.
214
280
  */
215
281
  declare function meetsMinTier(actual: TrustTier, required: TrustTier): boolean;
216
282
 
217
- /**
218
- * ProtectGateway — stdio MITM proxy for MCP servers.
219
- *
220
- * Sits between an MCP client (stdin/stdout) and a wrapped MCP server (child process).
221
- * Intercepts `tools/call` requests for policy enforcement and decision logging.
222
- * Passes through all other JSON-RPC messages transparently.
223
- *
224
- * v2 features:
225
- * - Shadow mode (default): observe + signed receipts, no blocking
226
- * - Trust-tier gating: evaluate manifest at admission, assign tier
227
- * - Credential vault: inject secrets, agent never sees raw keys
228
- * - BYOPE: pluggable policy decision via external HTTP webhook
229
- * - Signed receipts: every decision produces a signed artifact
230
- */
231
283
  declare class ProtectGateway {
232
284
  private child;
233
285
  private config;
234
286
  private rateLimitStore;
235
287
  private clientReader;
288
+ private logFilePath;
289
+ private receiptFilePath;
290
+ private evidenceStore;
291
+ private receiptBuffer;
292
+ /** Approval grants keyed by request_id (scoped to the specific action that was requested) */
293
+ private approvalStore;
294
+ /** Random nonce generated at startup — required for approval endpoint authentication */
295
+ private readonly approvalNonce;
236
296
  private currentTier;
237
297
  private admissionResult;
238
298
  constructor(config: ProtectConfig);
239
- /**
240
- * Start the gateway: spawn child process and wire up message relay.
241
- */
242
299
  start(): Promise<void>;
243
- /**
244
- * Set the trust tier for this session.
245
- * Called at admission (first interaction) or by explicit manifest presentation.
246
- */
247
300
  setManifest(manifest: ManifestPresentation | null): AdmissionResult;
248
- /**
249
- * Handle a message from the MCP client (stdin).
250
- * Intercept tools/call requests; pass through everything else.
251
- */
252
301
  private handleClientMessage;
253
- /**
254
- * Handle a message from the wrapped MCP server (child stdout).
255
- * Forward to client (stdout) transparently.
256
- */
302
+ private interceptToolCallAsync;
257
303
  private handleServerMessage;
258
- /**
259
- * Intercept a tools/call request. Returns a JSON-RPC error response if denied, null if allowed.
260
- */
304
+ private injectParamsCredentials;
261
305
  private interceptToolCall;
262
- /**
263
- * Get the applicable rate limit spec based on the agent's tier.
264
- */
265
306
  private getTierRateLimit;
266
- /**
267
- * Emit a structured decision log to stderr.
268
- * If signing is enabled, also emits a signed artifact.
269
- */
270
307
  private emitDecisionLog;
271
- /**
272
- * Create a JSON-RPC error response.
273
- */
274
308
  private makeErrorResponse;
275
- /**
276
- * Send a message to the child process (wrapped MCP server).
277
- */
278
309
  private sendToChild;
279
- /**
280
- * Send a message to the MCP client (stdout).
281
- */
282
310
  private sendToClient;
283
- /**
284
- * Log a message to stderr (debug output).
285
- */
286
311
  private log;
287
- /**
288
- * Stop the gateway: kill child process and exit.
289
- */
290
312
  stop(): void;
291
313
  }
292
314