@privacyscrubber/mcp-server 2.4.6 → 2.4.7

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
@@ -485,7 +485,7 @@ Returns a visual dashboard showing your current tier, session request count, act
485
485
  * **Response Example (Free Tier):**
486
486
  ```
487
487
  ╔══════════════════════════════════════════════════╗
488
- ║ PrivacyScrubber MCP Server v2.4.6 ║
488
+ ║ PrivacyScrubber MCP Server v2.4.7 ║
489
489
  ╠══════════════════════════════════════════════════╣
490
490
  ║ 🔓 Tier: FREE ║
491
491
  ║ 📊 Session requests: 5 ║
package/index.js CHANGED
@@ -43,6 +43,7 @@ if (rawCliArgs.includes('--help') || rawCliArgs.includes('-h') || rawCliArgs.inc
43
43
  console.log(`PrivacyScrubber MCP Server v${MCP_VERSION} — Zero-Trust Data Sanitization (ZTDS)`);
44
44
  console.log('\nUsage:');
45
45
  console.log(' npx @privacyscrubber/mcp-server Start MCP stdio server');
46
+ console.log(' npx @privacyscrubber/mcp-server --wrap <cmd> Transparent ZTDS proxy for any 3rd-party MCP server');
46
47
  console.log(' npx @privacyscrubber/mcp-server init Auto-configure Claude, Cursor, Windsurf, Cline');
47
48
  console.log(' npx @privacyscrubber/mcp-server uninstall Remove from IDE configurations');
48
49
  console.log(' npx @privacyscrubber/mcp-server trial Request 7-day B2B TEAMS/SDK evaluation key');
@@ -82,8 +83,9 @@ if (rawCliArgs.includes('--memo') || rawCliArgs.includes('memo')) {
82
83
  console.log(generateProcurementMemo({ targetTier: tierArg, format: 'text' }));
83
84
  process.exit(0);
84
85
  }
85
- if (rawCliArgs.includes('--hook')) {
86
+ if (rawCliArgs.includes('--hook') || rawCliArgs.includes('--wrap')) {
86
87
  await import('./pii-masking-run.js');
88
+ await new Promise(() => {});
87
89
  }
88
90
  if (rawCliArgs.some(arg => ['init', 'install', '--install', 'setup', '--setup', 'uninstall', '--uninstall', '--remove'].includes(arg))) {
89
91
  const { runInstaller } = await import('./install.js');
@@ -751,11 +753,27 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
751
753
  trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
752
754
  };
753
755
 
756
+ const agenticProtocol = {
757
+ protocolsSupported: ['MCP-1.0 (stdio/SSE)', 'A2A-1.0 (Agent2Agent)', 'ZTDS-RFC-v1.0'],
758
+ agentCardUrl: 'https://privacyscrubber.com/.well-known/agent-card.json',
759
+ ztdsStandardCardUrl: 'https://ztds.ai/.well-known/agent-card.json',
760
+ regulatorySafeHarbor: [
761
+ 'GDPR-Art-4(5)-Pseudonymization',
762
+ 'GDPR-Art-25-Privacy-by-Design',
763
+ 'GDPR-Art-32-TOMs',
764
+ 'EU-AI-Act-Art-50',
765
+ 'EU-AI-Act-Art-17-EN-18286-QMS',
766
+ 'CNIL-ICO-Agentic-Memory-Compartmentalization',
767
+ 'IL-PPL-Amendment-13-Sec-17'
768
+ ],
769
+ zeroCleartextEgress: true
770
+ };
771
+
754
772
  const statutoryCitations = [
755
773
  {
756
- statute: 'EU AI Act (Art. 50)',
757
- scope: 'AI Transparency & Data Minimization',
758
- rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
774
+ statute: 'EU AI Act (Art. 50 & Art. 17 / EN 18286:2026 QMS)',
775
+ scope: 'AI Transparency, Data Minimization & Harmonized QMS Governance',
776
+ rule: 'Sanitization runs locally by design; reduces the content sent to external AI models and supports Article 17 Quality Management System (EN 18286:2026) data governance controls. Informational mapping, not a compliance determination.'
759
777
  },
760
778
  {
761
779
  statute: 'GDPR (Art. 4, 25, 28, 32)',
@@ -787,6 +805,7 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
787
805
  frameworksList: Array.from(frameworksSet),
788
806
  statutoryCitations,
789
807
  ipHoldings,
808
+ agenticProtocol,
790
809
  multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
791
810
  };
792
811
  }
@@ -2489,10 +2508,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2489
2508
 
2490
2509
  const charLimit = (isAdvanced && !isAutoElevated && !license.isPro) ? 5000 : 15000;
2491
2510
  if (isAdvanced && !license.isPro) {
2492
- mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] Commercial TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade${colors.reset}\n`);
2511
+ mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] 7-Day B2B Trial / TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${encodeURIComponent(targetProfile)}&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade (or run: npx @privacyscrubber/mcp-server trial)${colors.reset}\n`);
2493
2512
  }
2494
2513
 
2495
- const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit);
2514
+ const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit, finalProfile);
2496
2515
 
2497
2516
  const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, currentIgnoreList, currentSession);
2498
2517
 
@@ -2851,10 +2870,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2851
2870
 
2852
2871
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
2853
2872
  if (isAdvanced && !license.isPro) {
2854
- mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] Commercial TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade${colors.reset}\n`);
2873
+ mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] 7-Day B2B Trial / TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${encodeURIComponent(targetProfile)}&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade (or run: npx @privacyscrubber/mcp-server trial)${colors.reset}\n`);
2855
2874
  }
2856
2875
 
2857
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
2876
+ const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit, finalProfile);
2858
2877
 
2859
2878
  const sanitized = performSanitization(processedContent, finalProfile, targetIgnoreList, targetSession);
2860
2879
  const { scrubbedText, newTokens } = sanitized;
@@ -3082,10 +3101,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3082
3101
 
3083
3102
  const charLimit = (isAdvanced && !isAutoElevated && !license.isPro) ? 5000 : 15000;
3084
3103
  if (isAdvanced && !license.isPro) {
3085
- mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] Commercial TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade${colors.reset}\n`);
3104
+ mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] 7-Day B2B Trial / TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${encodeURIComponent(targetProfile)}&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade (or run: npx @privacyscrubber/mcp-server trial)${colors.reset}\n`);
3086
3105
  }
3087
3106
 
3088
- const { processedText: processedContent2, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
3107
+ const { processedText: processedContent2, wasTruncated } = truncateIfFree(content, license.isPro, charLimit, finalProfile);
3089
3108
 
3090
3109
  const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile, targetIgnoreList, targetSession);
3091
3110
  const telemetry = buildCisoAuditTelemetry(newTokens);
@@ -3581,8 +3600,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3581
3600
  const isAdvanced = targetProfile.toLowerCase() !== "general";
3582
3601
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
3583
3602
 
3584
- const { processedText: cleanStdout, wasTruncated: truncOut } = truncateIfFree(stdout, license.isPro, charLimit);
3585
- const { processedText: cleanStderr, wasTruncated: truncErr } = truncateIfFree(stderr, license.isPro, charLimit);
3603
+ const { processedText: cleanStdout, wasTruncated: truncOut } = truncateIfFree(stdout, license.isPro, charLimit, targetProfile);
3604
+ const { processedText: cleanStderr, wasTruncated: truncErr } = truncateIfFree(stderr, license.isPro, charLimit, targetProfile);
3586
3605
 
3587
3606
  const resCommand = performSanitization(command, targetProfile, targetIgnoreList, targetSession);
3588
3607
  const resStdout = performSanitization(cleanStdout, targetProfile, targetIgnoreList, targetSession);
@@ -3751,7 +3770,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3751
3770
  const isAdvanced = targetProfile.toLowerCase() !== "general";
3752
3771
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
3753
3772
 
3754
- const { processedText, wasTruncated: wasCharTruncated } = truncateIfFree(capped, license.isPro, charLimit);
3773
+ const { processedText, wasTruncated: wasCharTruncated } = truncateIfFree(capped, license.isPro, charLimit, targetProfile);
3755
3774
  const currentSession = getSessionMap(session_id);
3756
3775
  const currentIgnoreList = getSessionIgnoreList(session_id);
3757
3776
  const { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, currentIgnoreList, currentSession);
@@ -3815,7 +3834,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3815
3834
  const isAdvanced = targetProfile.toLowerCase() !== "general";
3816
3835
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
3817
3836
 
3818
- const { processedText, wasTruncated } = truncateIfFree(diffOutput, license.isPro, charLimit);
3837
+ const { processedText, wasTruncated } = truncateIfFree(diffOutput, license.isPro, charLimit, targetProfile);
3819
3838
  const { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, targetIgnoreList, targetSession);
3820
3839
 
3821
3840
  const telemetry = buildCisoAuditTelemetry(newTokens);
@@ -4621,10 +4640,11 @@ export function performSanitization(text, profile, ignoreList = null, customSess
4621
4640
  return { scrubbedText: result.scrubbedText, newTokens };
4622
4641
  }
4623
4642
 
4624
- export function truncateIfFree(text, isPro, charLimit = 15000) {
4643
+ export function truncateIfFree(text, isPro, charLimit = 15000, profile = 'general') {
4625
4644
  if (!isPro && text.length > charLimit) {
4626
- mcpLog(`${colors.yellowBold}[PrivacyScrubber] Input truncated to ${charLimit.toLocaleString()} characters (Free Tier Limit).${colors.reset}\n${colors.cyan}[Upgrade] Set PRIVACYSCRUBBER_KEY to your PRO/TEAMS/SDK license key for unlimited throughput: https://privacyscrubber.com/pricing?tier=teams&utm_source=mcp&utm_medium=truncation&utm_campaign=teams_upgrade${colors.reset}\n`);
4627
- const upsellNotice = `\n\n[PrivacyScrubber Free Tier: Payload truncated to ${charLimit.toLocaleString()} chars. Upgrade to PRO ($15/mo or $110 Lifetime) or Developer SDK ($299/mo) for unlimited payload processing: https://privacyscrubber.com/pricing?tier=teams&utm_source=mcp&utm_medium=truncation&utm_campaign=teams_upgrade]`;
4645
+ const safeProf = encodeURIComponent(String(profile || 'general').toLowerCase());
4646
+ mcpLog(`${colors.yellowBold}[PrivacyScrubber] Input truncated to ${charLimit.toLocaleString()} characters (Free Tier Limit for profile '${profile || 'general'}').${colors.reset}\n${colors.cyan}[Upgrade] Set PRIVACYSCRUBBER_KEY to your PRO/TEAMS/SDK license key or start a 7-day B2B trial ('npx @privacyscrubber/mcp-server trial'): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${safeProf}&utm_source=mcp&utm_medium=truncation&utm_campaign=teams_upgrade${colors.reset}\n`);
4647
+ const upsellNotice = `\n\n[PrivacyScrubber Free Tier: Payload truncated to ${charLimit.toLocaleString()} chars (${profile || 'general'}). Upgrade to PRO ($15/mo or $110 LT), TEAMS ($99/mo flat) or Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${safeProf}&utm_source=mcp&utm_medium=truncation&utm_campaign=teams_upgrade | CLI Trial: npx @privacyscrubber/mcp-server trial]`;
4628
4648
  return { processedText: text.substring(0, charLimit), wasTruncated: true, upsellNotice, charLimit };
4629
4649
  }
4630
4650
  return { processedText: text, wasTruncated: false, upsellNotice: "", charLimit };
package/install.js CHANGED
@@ -496,7 +496,7 @@ Options:
496
496
  console.log(`[SUMMARY] ${options.remove ? 'Cleaned' : 'Configured'} ${configuredCount} AI client(s) successfully.`);
497
497
  if (!options.remove) {
498
498
  console.log('Next step: Restart your AI IDE or Claude Desktop to activate PrivacyScrubber.');
499
- console.log('\x1b[36m💼 B2B 7-Day Free Trial (TEAMS/SDK): npx @privacyscrubber/mcp-server trial\x1b[0m');
499
+ console.log('\x1b[36m💼 B2B 7-Day Free Trial (TEAMS $99/mo · SDK $299/mo): npx @privacyscrubber/mcp-server trial | https://privacyscrubber.com/pricing/?tier=teams&trial=true&utm_source=mcp_install\x1b[0m');
500
500
  console.log('\x1b[36m📋 Generate CISO Procurement Memo: npx @privacyscrubber/mcp-server memo\x1b[0m');
501
501
  console.log('\x1b[33m⭐ Enjoying zero-trust privacy? Star the repo on GitHub: https://github.com/moxno/privacyscrubber-mcp\x1b[0m');
502
502
  console.log('\x1b[90m One-click CLI: gh repo star moxno/privacyscrubber-mcp && gh repo star moxno/ztds.ai\x1b[0m');
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": "0.3",
3
3
  "name": "privacyscrubber/pii-masking-mcp",
4
- "version": "2.4.6",
4
+ "version": "2.4.7",
5
5
  "description": "CISO-Approved Zero-Trust PII & Secrets Redaction MCP Server for Cursor, Windsurf, and Claude Desktop. Prevent API leaks and comply with HIPAA/SOC 2 locally.",
6
6
  "author": {
7
7
  "name": "PrivacyScrubber",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privacyscrubber/mcp-server",
3
- "version": "2.4.6",
3
+ "version": "2.4.7",
4
4
  "mcpName": "io.github.moxno/privacyscrubber-mcp",
5
5
  "description": "CISO-Approved Zero-Trust PII & Secrets Redaction MCP Server for Cursor, Windsurf, and Claude Desktop. Prevent API leaks and comply with HIPAA/SOC 2 locally.",
6
6
  "main": "index.js",
@@ -27,7 +27,7 @@ const cmdIndex = args.indexOf("--");
27
27
 
28
28
  if (args.includes("--version") || args.includes("-v")) {
29
29
  const pkgPath = path.resolve(__dirname, './package.json');
30
- let version = '2.4.6';
30
+ let version = '2.4.7';
31
31
  try { version = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version; } catch (_) {}
32
32
  console.log(`ps-guard / pii-masking-run v${version} (PrivacyScrubber MCP)`);
33
33
  process.exit(0);
@@ -36,6 +36,7 @@ if (args.includes("--version") || args.includes("-v")) {
36
36
  const isDiffMode = args.includes("--diff");
37
37
  const isRulesMode = args.includes("--rules");
38
38
  const isHookMode = args.includes("--hook");
39
+ const isWrapMode = args.includes("--wrap");
39
40
  const isJsonMode = args.includes("--json");
40
41
  const isRestoreMode = args.includes("--restore") || args.includes("--unscrub");
41
42
  let tokenMapInput = null;
@@ -48,19 +49,44 @@ const payloadIdx = args.indexOf("--payload");
48
49
  if (payloadIdx !== -1 && args[payloadIdx + 1]) {
49
50
  hookPayloadArg = args[payloadIdx + 1];
50
51
  }
51
- const hasCommand = cmdIndex !== -1 && args.slice(cmdIndex + 1).length > 0;
52
+
53
+ let wrapCommandArgs = [];
54
+ if (isWrapMode) {
55
+ if (cmdIndex !== -1 && args.slice(cmdIndex + 1).length > 0) {
56
+ wrapCommandArgs = args.slice(cmdIndex + 1);
57
+ } else {
58
+ const wrapIdx = args.indexOf("--wrap");
59
+ const afterWrap = args.slice(wrapIdx + 1);
60
+ for (let i = 0; i < afterWrap.length; i++) {
61
+ const a = afterWrap[i];
62
+ if ((a === "--profile" || a === "--key" || a === "--token-map") && afterWrap[i + 1]) {
63
+ i++;
64
+ continue;
65
+ }
66
+ if (a === "--quiet" || a === "-q" || a === "--verbose" || a === "--json") {
67
+ continue;
68
+ }
69
+ wrapCommandArgs = afterWrap.slice(i);
70
+ break;
71
+ }
72
+ }
73
+ }
74
+
75
+ const hasCommand = (cmdIndex !== -1 && args.slice(cmdIndex + 1).length > 0) || wrapCommandArgs.length > 0;
52
76
  const hasStdin = !process.stdin.isTTY;
53
77
 
54
- if (args.includes("-h") || args.includes("--help") || (!hasCommand && !isDiffMode && !isRulesMode && !isHookMode && !isRestoreMode && !hasStdin)) {
78
+ if (args.includes("-h") || args.includes("--help") || (!hasCommand && !isDiffMode && !isRulesMode && !isHookMode && !isWrapMode && !isRestoreMode && !hasStdin)) {
55
79
  console.log("PrivacyScrubber CLI & Agentic Guard: ps-guard / pii-masking-run");
56
80
  console.log("\nUsage:");
57
81
  console.log(" npx ps-guard [options] -- <command> [args...]");
82
+ console.log(" npx ps-guard --wrap [options] [--] <mcp-server-cmd> [args...]");
58
83
  console.log(" cat <file> | npx ps-guard [options]");
59
84
  console.log(" npx ps-guard --diff [--staged] [options]");
60
85
  console.log(" npx ps-guard --restore --token-map <file|json> [options]");
61
86
  console.log(" npx ps-guard --rules [targets]");
62
87
  console.log(" npx ps-guard --hook (Claude Code & Cursor IDE PreToolUse / beforeReadFile hook)");
63
88
  console.log("\nOptions:");
89
+ console.log(" --wrap Transparent JSON-RPC 2.0 stdio proxy for any 3rd-party MCP server");
64
90
  console.log(" --profile <name> Specify detection profile (e.g. General, Dev, Medical, Legal, HR)");
65
91
  console.log(" --key <licKey> PrivacyScrubber PRO License Key");
66
92
  console.log(" --json Output structured JSON (scrubbedText, tokenMap, count, telemetry)");
@@ -75,6 +101,7 @@ if (args.includes("-h") || args.includes("--help") || (!hasCommand && !isDiffMod
75
101
  console.log(" -h, --help Show this help screen");
76
102
  console.log("\nExamples:");
77
103
  console.log(" npx ps-guard -- cat database-dump.sql");
104
+ console.log(" npx ps-guard --wrap -- npx -y @modelcontextprotocol/server-postgres postgresql://localhost/mydb");
78
105
  console.log(" npx ps-guard --profile dev -- npm test");
79
106
  console.log(" cat .env | npx ps-guard --profile dev --json");
80
107
  console.log(" cat ai_reply.txt | npx ps-guard --restore --token-map map.json");
@@ -425,11 +452,27 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
425
452
  trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
426
453
  };
427
454
 
455
+ const agenticProtocol = {
456
+ protocolsSupported: ['MCP-1.0 (stdio/SSE)', 'A2A-1.0 (Agent2Agent)', 'ZTDS-RFC-v1.0'],
457
+ agentCardUrl: 'https://privacyscrubber.com/.well-known/agent-card.json',
458
+ ztdsStandardCardUrl: 'https://ztds.ai/.well-known/agent-card.json',
459
+ regulatorySafeHarbor: [
460
+ 'GDPR-Art-4(5)-Pseudonymization',
461
+ 'GDPR-Art-25-Privacy-by-Design',
462
+ 'GDPR-Art-32-TOMs',
463
+ 'EU-AI-Act-Art-50',
464
+ 'EU-AI-Act-Art-17-EN-18286-QMS',
465
+ 'CNIL-ICO-Agentic-Memory-Compartmentalization',
466
+ 'IL-PPL-Amendment-13-Sec-17'
467
+ ],
468
+ zeroCleartextEgress: true
469
+ };
470
+
428
471
  const statutoryCitations = [
429
472
  {
430
- statute: 'EU AI Act (Art. 50)',
431
- scope: 'AI Transparency & Data Minimization',
432
- rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
473
+ statute: 'EU AI Act (Art. 50 & Art. 17 / EN 18286:2026 QMS)',
474
+ scope: 'AI Transparency, Data Minimization & Harmonized QMS Governance',
475
+ rule: 'Sanitization runs locally by design; reduces the content sent to external AI models and supports Article 17 Quality Management System (EN 18286:2026) data governance controls. Informational mapping, not a compliance determination.'
433
476
  },
434
477
  {
435
478
  statute: 'GDPR (Art. 4, 25, 28, 32)',
@@ -461,6 +504,7 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
461
504
  frameworksList: Array.from(frameworksSet),
462
505
  statutoryCitations,
463
506
  ipHoldings,
507
+ agenticProtocol,
464
508
  multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
465
509
  };
466
510
  }
@@ -775,8 +819,183 @@ if (isDiffMode) {
775
819
  }
776
820
  }
777
821
 
778
- // Mode 3: Stdin Stream Mode (pipe)
779
- if (hasStdin && !hasCommand && !isRestoreMode && !isDiffMode && !isRulesMode) {
822
+ // Mode 2.5: Transparent MCP JSON-RPC 2.0 Stdio Proxy Wrapper (--wrap)
823
+ if (isWrapMode) {
824
+ if (wrapCommandArgs.length === 0) {
825
+ console.error('Error: --wrap requires an upstream MCP server command (e.g. npx ps-guard --wrap -- npx -y @modelcontextprotocol/server-postgres ...)');
826
+ process.exit(1);
827
+ }
828
+
829
+ const SKIP_SANITIZE_KEYS = new Set([
830
+ 'jsonrpc', 'id', 'method', 'type', 'mimeType', 'uri', 'name', 'role',
831
+ 'protocolVersion', 'cursor', 'nextCursor', 'isError', 'version'
832
+ ]);
833
+
834
+ const deepDetokenizeParams = (node, depth = 0) => {
835
+ if (depth > 20 || node === null || node === undefined) return node;
836
+ if (typeof node === 'string') {
837
+ if (Object.keys(localSessionMap).length === 0) return node;
838
+ const res = PrivacyScrubberCore.unscrubText(node, localSessionMap);
839
+ return (res && typeof res.restoredText === 'string') ? res.restoredText : node;
840
+ }
841
+ if (Array.isArray(node)) {
842
+ return node.map(item => deepDetokenizeParams(item, depth + 1));
843
+ }
844
+ if (typeof node === 'object') {
845
+ const out = {};
846
+ for (const [k, v] of Object.entries(node)) {
847
+ if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;
848
+ out[k] = deepDetokenizeParams(v, depth + 1);
849
+ }
850
+ return out;
851
+ }
852
+ return node;
853
+ };
854
+
855
+ const deepSanitizeResult = (node, keyName = '', depth = 0) => {
856
+ if (depth > 20 || node === null || node === undefined) return node;
857
+ if (typeof node === 'string') {
858
+ if (SKIP_SANITIZE_KEYS.has(keyName) || node.length < 4) return node;
859
+ return performSanitization(node);
860
+ }
861
+ if (Array.isArray(node)) {
862
+ return node.map(item => deepSanitizeResult(item, keyName, depth + 1));
863
+ }
864
+ if (typeof node === 'object') {
865
+ const out = {};
866
+ for (const [k, v] of Object.entries(node)) {
867
+ if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;
868
+ if (k === 'inputSchema' || k === 'outputSchema' || k === 'capabilities' || k === 'serverInfo' || k === '_meta') {
869
+ out[k] = v;
870
+ } else {
871
+ out[k] = deepSanitizeResult(v, k, depth + 1);
872
+ }
873
+ }
874
+ return out;
875
+ }
876
+ return node;
877
+ };
878
+
879
+ const zeroizeSessionMap = () => {
880
+ for (const k of Object.keys(localSessionMap)) {
881
+ localSessionMap[k] = '';
882
+ delete localSessionMap[k];
883
+ }
884
+ };
885
+
886
+ const [cmdBin, ...cmdRest] = wrapCommandArgs;
887
+ const child = spawn(cmdBin, cmdRest, {
888
+ stdio: ['pipe', 'pipe', 'pipe'],
889
+ shell: process.platform === 'win32'
890
+ });
891
+
892
+ // Intercept client -> upstream MCP server (JSON-RPC requests on stdin)
893
+ const rlClientIn = readline.createInterface({
894
+ input: process.stdin,
895
+ terminal: false
896
+ });
897
+
898
+ rlClientIn.on('line', (line) => {
899
+ if (!child.stdin || child.stdin.destroyed) return;
900
+ const trimmed = line.trim();
901
+ if (!trimmed.startsWith('{')) {
902
+ child.stdin.write(line + '\n');
903
+ return;
904
+ }
905
+ try {
906
+ const msg = JSON.parse(trimmed);
907
+ if (msg && typeof msg === 'object' && msg.params && (msg.method === 'tools/call' || msg.method === 'prompts/get' || msg.method === 'resources/read' || msg.method === 'sampling/createMessage')) {
908
+ msg.params = deepDetokenizeParams(msg.params);
909
+ child.stdin.write(JSON.stringify(msg) + '\n');
910
+ return;
911
+ }
912
+ } catch (_) {}
913
+ child.stdin.write(line + '\n');
914
+ });
915
+
916
+ rlClientIn.on('close', () => {
917
+ if (child.stdin && !child.stdin.destroyed) {
918
+ child.stdin.end();
919
+ }
920
+ });
921
+
922
+ // Intercept upstream MCP server -> client (JSON-RPC responses/notifications on stdout)
923
+ const rlUpstreamOut = readline.createInterface({
924
+ input: child.stdout,
925
+ terminal: false
926
+ });
927
+
928
+ rlUpstreamOut.on('line', (line) => {
929
+ const trimmed = line.trim();
930
+ if (!trimmed.startsWith('{')) {
931
+ process.stdout.write(performSanitization(line) + '\n');
932
+ return;
933
+ }
934
+ try {
935
+ const msg = JSON.parse(trimmed);
936
+ if (msg && typeof msg === 'object') {
937
+ if (msg.result && typeof msg.result === 'object') {
938
+ const isSchemaListing = Array.isArray(msg.result.tools) || Array.isArray(msg.result.prompts) || Array.isArray(msg.result.resourceTemplates) || Boolean(msg.result.protocolVersion);
939
+ if (!isSchemaListing) {
940
+ msg.result = deepSanitizeResult(msg.result);
941
+ const receipt = buildPsr2Receipt(localSessionMap);
942
+ const telemetry = buildCisoAuditTelemetry(localSessionMap);
943
+ msg.result._meta = Object.assign({}, msg.result._meta || {}, {
944
+ ztds_receipt: {
945
+ version: receipt.version,
946
+ timestamp: receipt.timestamp,
947
+ manifestSha256: receipt.manifestSha256,
948
+ receiptSha256: receipt.receiptSha256,
949
+ entityCounts: receipt.entityCounts,
950
+ tokensTotal: receipt.tokensTotal,
951
+ riskLevel: telemetry.riskLevel,
952
+ regulatorySafeHarbor: telemetry.agenticProtocol ? telemetry.agenticProtocol.regulatorySafeHarbor : [],
953
+ zero_cleartext_egress: true
954
+ }
955
+ });
956
+ }
957
+ } else if (msg.params && typeof msg.params === 'object') {
958
+ msg.params = deepSanitizeResult(msg.params);
959
+ }
960
+ process.stdout.write(JSON.stringify(msg) + '\n');
961
+ return;
962
+ }
963
+ } catch (_) {}
964
+ process.stdout.write(performSanitization(line) + '\n');
965
+ });
966
+
967
+ const rlUpstreamErr = readline.createInterface({
968
+ input: child.stderr,
969
+ terminal: false
970
+ });
971
+
972
+ rlUpstreamErr.on('line', (line) => {
973
+ process.stderr.write(performSanitization(line) + '\n');
974
+ });
975
+
976
+ const handleSignal = (sig) => {
977
+ zeroizeSessionMap();
978
+ if (!child.killed) {
979
+ try { child.kill(sig); } catch (_) {}
980
+ }
981
+ };
982
+ process.on('SIGINT', () => handleSignal('SIGINT'));
983
+ process.on('SIGTERM', () => handleSignal('SIGTERM'));
984
+ process.on('exit', zeroizeSessionMap);
985
+
986
+ child.on('close', (code) => {
987
+ emitProofFeedback();
988
+ zeroizeSessionMap();
989
+ process.exit(code || 0);
990
+ });
991
+
992
+ child.on('error', (err) => {
993
+ zeroizeSessionMap();
994
+ console.error(`Error spawning wrapped MCP server: ${err.message}`);
995
+ process.exit(1);
996
+ });
997
+ } else if (hasStdin && !hasCommand && !isRestoreMode && !isDiffMode && !isRulesMode) {
998
+ // Mode 3: Stdin Stream Mode (pipe)
780
999
  if (isJsonMode) {
781
1000
  let rawInput = '';
782
1001
  process.stdin.setEncoding('utf8');