@privacyscrubber/mcp-server 2.4.6 → 2.4.8

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.8 ║
489
489
  ╠══════════════════════════════════════════════════╣
490
490
  ║ 🔓 Tier: FREE ║
491
491
  ║ 📊 Session requests: 5 ║
package/index.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  ListToolsRequestSchema,
16
16
  ListPromptsRequestSchema,
17
17
  GetPromptRequestSchema,
18
+ ListResourcesRequestSchema,
18
19
  ListResourceTemplatesRequestSchema,
19
20
  ReadResourceRequestSchema,
20
21
  } from "@modelcontextprotocol/sdk/types.js";
@@ -43,6 +44,7 @@ if (rawCliArgs.includes('--help') || rawCliArgs.includes('-h') || rawCliArgs.inc
43
44
  console.log(`PrivacyScrubber MCP Server v${MCP_VERSION} — Zero-Trust Data Sanitization (ZTDS)`);
44
45
  console.log('\nUsage:');
45
46
  console.log(' npx @privacyscrubber/mcp-server Start MCP stdio server');
47
+ console.log(' npx @privacyscrubber/mcp-server --wrap <cmd> Transparent ZTDS proxy for any 3rd-party MCP server');
46
48
  console.log(' npx @privacyscrubber/mcp-server init Auto-configure Claude, Cursor, Windsurf, Cline');
47
49
  console.log(' npx @privacyscrubber/mcp-server uninstall Remove from IDE configurations');
48
50
  console.log(' npx @privacyscrubber/mcp-server trial Request 7-day B2B TEAMS/SDK evaluation key');
@@ -82,8 +84,9 @@ if (rawCliArgs.includes('--memo') || rawCliArgs.includes('memo')) {
82
84
  console.log(generateProcurementMemo({ targetTier: tierArg, format: 'text' }));
83
85
  process.exit(0);
84
86
  }
85
- if (rawCliArgs.includes('--hook')) {
87
+ if (rawCliArgs.includes('--hook') || rawCliArgs.includes('--wrap')) {
86
88
  await import('./pii-masking-run.js');
89
+ await new Promise(() => {});
87
90
  }
88
91
  if (rawCliArgs.some(arg => ['init', 'install', '--install', 'setup', '--setup', 'uninstall', '--uninstall', '--remove'].includes(arg))) {
89
92
  const { runInstaller } = await import('./install.js');
@@ -751,11 +754,27 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
751
754
  trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
752
755
  };
753
756
 
757
+ const agenticProtocol = {
758
+ protocolsSupported: ['MCP-1.0 (stdio/SSE)', 'A2A-1.0 (Agent2Agent)', 'ZTDS-RFC-v1.0'],
759
+ agentCardUrl: 'https://privacyscrubber.com/.well-known/agent-card.json',
760
+ ztdsStandardCardUrl: 'https://ztds.ai/.well-known/agent-card.json',
761
+ regulatorySafeHarbor: [
762
+ 'GDPR-Art-4(5)-Pseudonymization',
763
+ 'GDPR-Art-25-Privacy-by-Design',
764
+ 'GDPR-Art-32-TOMs',
765
+ 'EU-AI-Act-Art-50',
766
+ 'EU-AI-Act-Art-17-EN-18286-QMS',
767
+ 'CNIL-ICO-Agentic-Memory-Compartmentalization',
768
+ 'IL-PPL-Amendment-13-Sec-17'
769
+ ],
770
+ zeroCleartextEgress: true
771
+ };
772
+
754
773
  const statutoryCitations = [
755
774
  {
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.'
775
+ statute: 'EU AI Act (Art. 50 & Art. 17 / EN 18286:2026 QMS)',
776
+ scope: 'AI Transparency, Data Minimization & Harmonized QMS Governance',
777
+ 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
778
  },
760
779
  {
761
780
  statute: 'GDPR (Art. 4, 25, 28, 32)',
@@ -787,6 +806,7 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
787
806
  frameworksList: Array.from(frameworksSet),
788
807
  statutoryCitations,
789
808
  ipHoldings,
809
+ agenticProtocol,
790
810
  multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
791
811
  };
792
812
  }
@@ -2233,6 +2253,60 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
2233
2253
  };
2234
2254
  });
2235
2255
 
2256
+ const PROFILE_RESOURCE_DESCRIPTIONS = {
2257
+ "general": "Scrubs basic PII: Names, Emails, Phone Numbers, Credit Cards, SSN, IPv4/IPv6, and standard API Keys.",
2258
+ "dev": "Engineering profile. Scrubs AWS keys, JWTs, GCP keys, Stripe keys, database connection strings, and internal IP addresses.",
2259
+ "engineering": "Engineering profile. Scrubs AWS keys, JWTs, GCP keys, Stripe keys, database connection strings, internal IPs, Docker paths, and K8s internal endpoints.",
2260
+ "tech": "Technology profile. Scrubs cloud instance IDs, node/cluster IDs, environment tags, kubeconfig, and terraform states.",
2261
+ "medical": "HIPAA compliance profile. Scrubs ICD-10 codes, medical record numbers (MRN), DEA numbers, NPI, patient names, and health conditions.",
2262
+ "finance": "PCI/Finance profile. Scrubs IBAN, SWIFT codes, credit cards, routing numbers, and financial transaction IDs.",
2263
+ "wealthmgmt": "Wealth Management profile. Scrubs ABA/Routing numbers, trust names, portfolio values, net worth, RMDs, and account numbers.",
2264
+ "insurance": "Insurance profile. Scrubs claim numbers, policy numbers, settlement amounts, NAIC, adjustor IDs, and insured names.",
2265
+ "accounting": "Accounting profile. Scrubs EIN, FEIN, Tax IDs, AGI, tax forms (1040, W-2, 1099), refund amounts, and PTINs.",
2266
+ "legal": "Legal profile. Scrubs case numbers, matter numbers, attorney-client privilege markers, and litigation IDs.",
2267
+ "hr": "Human Resources profile. Scrubs employee IDs (EEID), resumes, DOBs, tenant IDs, and home addresses.",
2268
+ "security": "Security profile. Scrubs secrets, AWS keys, JWTs, Stripe keys, incident IDs, and breach reports.",
2269
+ "marketing": "Marketing profile. Scrubs lead IDs, prospect IDs, GCLID/FBCLID, LTV/CAC amounts, cohort IDs, and segment IDs.",
2270
+ "bizops": "BizOps profile. Scrubs deals, KPIs, vendor IDs, EBITDA/Revenue figures, NDAs, and M&A identifiers.",
2271
+ "sales": "Sales profile. Scrubs opportunity IDs, DocuSign hashes, ARR/MRR/Quota amounts, and SFDC/HubSpot IDs.",
2272
+ "support": "Support profile. Scrubs Zendesk/Jira ticket numbers, RMA/Return IDs, and loyalty/rewards numbers.",
2273
+ "realestate": "Real Estate profile. Scrubs MLS numbers, LIS IDs, parcel numbers, rent/escrow amounts, and gate/lobby codes.",
2274
+ "compliance": "Compliance profile. Scrubs GDPR/HIPAA/SOC2 audit IDs, DPA policy numbers, and SAR/DSAR request IDs.",
2275
+ "ccpa": "CCPA profile. Scrubs driver licenses, precise geolocation (Lat/Long), CCPA/CPRA opt-out markers, and account numbers.",
2276
+ "agents": "AI Agents profile. Scrubs agent IDs, vector IDs, task IDs, system prompts, and OpenAI API keys.",
2277
+ "academic": "Academic profile. Scrubs student/alumni IDs, course numbers, FERPA/IRB IDs, and academic grades.",
2278
+ "creative": "Creative profile. Scrubs project IDs, script drafts, spoiler/embargo tags, and ghostwriter names.",
2279
+ "personal": "Personal profile. Scrubs birthdays, passwords, PINs, and emergency contacts/family phone numbers.",
2280
+ "pharma": "Pharma/Clinical profile. Scrubs patient IDs, study protocols, IND/NDA numbers, IRB IDs, batch/lot serials, and dosages.",
2281
+ "underwriting": "Loan Underwriting & Mortgage profile. Scrubs borrower names, SSNs, EINs, addresses while preserving wages and YTD figures.",
2282
+ "automotive": "Automotive & EV profile. Scrubs VINs, ECU IDs, telematics, and repair orders while preserving DTCs and ASIL-D standards.",
2283
+ "energy": "Energy & Utilities profile. Scrubs ESI IDs, meter serials, SCADA points, PLC tags, and substation circuits.",
2284
+ "hospitality": "Hospitality & Travel profile. Scrubs PNRs, e-tickets, boarding pass barcodes, and guest folios.",
2285
+ "biotech": "Biotech & Genomics profile. Scrubs accessions, patient phenotypes, HGVS variants, DNA/RNA samples, and flowcell IDs.",
2286
+ "telecom": "Call Centers & Telephony profile. Scrubs CTI interaction IDs, Amazon Connect/Genesys sessions, agent extensions, ANI/DNIS, IVR DTMF, and audio transcript diarization headers."
2287
+ };
2288
+
2289
+ // List Resources
2290
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
2291
+ const featuredProfiles = ["general", "dev", "medical", "finance", "legal", "security", "compliance", "hr", "agents"];
2292
+ return {
2293
+ resources: [
2294
+ {
2295
+ uri: "mcp://privacyscrubber/profiles/catalog",
2296
+ name: "ZTDS 30 Industry Profiles Catalog",
2297
+ description: "Complete directory of all 30 specialized PrivacyScrubber PII & secrets detection profiles and regulatory coverage.",
2298
+ mimeType: "text/markdown"
2299
+ },
2300
+ ...featuredProfiles.map((name) => ({
2301
+ uri: `mcp://privacyscrubber/profiles/${name}`,
2302
+ name: `Profile: ${name.charAt(0).toUpperCase() + name.slice(1)}`,
2303
+ description: PROFILE_RESOURCE_DESCRIPTIONS[name],
2304
+ mimeType: "text/markdown"
2305
+ }))
2306
+ ]
2307
+ };
2308
+ });
2309
+
2236
2310
  // List Resource Templates
2237
2311
  server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
2238
2312
  return {
@@ -2249,42 +2323,24 @@ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
2249
2323
  // Read Resource
2250
2324
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2251
2325
  const uri = request.params.uri;
2326
+ if (uri === "mcp://privacyscrubber/profiles/catalog") {
2327
+ const lines = Object.entries(PROFILE_RESOURCE_DESCRIPTIONS).map(
2328
+ ([k, v]) => `- **\`${k}\`**: ${v}`
2329
+ );
2330
+ return {
2331
+ contents: [
2332
+ {
2333
+ uri,
2334
+ mimeType: "text/markdown",
2335
+ text: `# PrivacyScrubber ZTDS Profiles Catalog (30 Specialized Industry Profiles)\n\n${lines.join("\n")}\n\n*Usage: Pass \`"profile": "<name>"\` or \`"sector": "<name>"\` to \`sanitize_text\`, \`sanitize_file\`, or \`guard_read_file\`.*`
2336
+ }
2337
+ ]
2338
+ };
2339
+ }
2252
2340
  const match = uri.match(/^mcp:\/\/privacyscrubber\/profiles\/(.+)$/);
2253
2341
  if (match) {
2254
2342
  const profileName = match[1];
2255
- const descriptions = {
2256
- "general": "Scrubs basic PII: Names, Emails, Phone Numbers, Credit Cards, SSN, IPv4/IPv6, and standard API Keys.",
2257
- "dev": "Engineering profile. Scrubs AWS keys, JWTs, GCP keys, Stripe keys, database connection strings, and internal IP addresses.",
2258
- "engineering": "Engineering profile. Scrubs AWS keys, JWTs, GCP keys, Stripe keys, database connection strings, internal IPs, Docker paths, and K8s internal endpoints.",
2259
- "tech": "Technology profile. Scrubs cloud instance IDs, node/cluster IDs, environment tags, kubeconfig, and terraform states.",
2260
- "medical": "HIPAA compliance profile. Scrubs ICD-10 codes, medical record numbers (MRN), DEA numbers, NPI, patient names, and health conditions.",
2261
- "finance": "PCI/Finance profile. Scrubs IBAN, SWIFT codes, credit cards, routing numbers, and financial transaction IDs.",
2262
- "wealthmgmt": "Wealth Management profile. Scrubs ABA/Routing numbers, trust names, portfolio values, net worth, RMDs, and account numbers.",
2263
- "insurance": "Insurance profile. Scrubs claim numbers, policy numbers, settlement amounts, NAIC, adjustor IDs, and insured names.",
2264
- "accounting": "Accounting profile. Scrubs EIN, FEIN, Tax IDs, AGI, tax forms (1040, W-2, 1099), refund amounts, and PTINs.",
2265
- "legal": "Legal profile. Scrubs case numbers, matter numbers, attorney-client privilege markers, and litigation IDs.",
2266
- "hr": "Human Resources profile. Scrubs employee IDs (EEID), resumes, DOBs, tenant IDs, and home addresses.",
2267
- "security": "Security profile. Scrubs secrets, AWS keys, JWTs, Stripe keys, incident IDs, and breach reports.",
2268
- "marketing": "Marketing profile. Scrubs lead IDs, prospect IDs, GCLID/FBCLID, LTV/CAC amounts, cohort IDs, and segment IDs.",
2269
- "bizops": "BizOps profile. Scrubs deals, KPIs, vendor IDs, EBITDA/Revenue figures, NDAs, and M&A identifiers.",
2270
- "sales": "Sales profile. Scrubs opportunity IDs, DocuSign hashes, ARR/MRR/Quota amounts, and SFDC/HubSpot IDs.",
2271
- "support": "Support profile. Scrubs Zendesk/Jira ticket numbers, RMA/Return IDs, and loyalty/rewards numbers.",
2272
- "realestate": "Real Estate profile. Scrubs MLS numbers, LIS IDs, parcel numbers, rent/escrow amounts, and gate/lobby codes.",
2273
- "compliance": "Compliance profile. Scrubs GDPR/HIPAA/SOC2 audit IDs, DPA policy numbers, and SAR/DSAR request IDs.",
2274
- "ccpa": "CCPA profile. Scrubs driver licenses, precise geolocation (Lat/Long), CCPA/CPRA opt-out markers, and account numbers.",
2275
- "agents": "AI Agents profile. Scrubs agent IDs, vector IDs, task IDs, system prompts, and OpenAI API keys.",
2276
- "academic": "Academic profile. Scrubs student/alumni IDs, course numbers, FERPA/IRB IDs, and academic grades.",
2277
- "creative": "Creative profile. Scrubs project IDs, script drafts, spoiler/embargo tags, and ghostwriter names.",
2278
- "personal": "Personal profile. Scrubs birthdays, passwords, PINs, and emergency contacts/family phone numbers.",
2279
- "pharma": "Pharma/Clinical profile. Scrubs patient IDs, study protocols, IND/NDA numbers, IRB IDs, batch/lot serials, and dosages.",
2280
- "underwriting": "Loan Underwriting & Mortgage profile. Scrubs borrower names, SSNs, EINs, addresses while preserving wages and YTD figures.",
2281
- "automotive": "Automotive & EV profile. Scrubs VINs, ECU IDs, telematics, and repair orders while preserving DTCs and ASIL-D standards.",
2282
- "energy": "Energy & Utilities profile. Scrubs ESI IDs, meter serials, SCADA points, PLC tags, and substation circuits.",
2283
- "hospitality": "Hospitality & Travel profile. Scrubs PNRs, e-tickets, boarding pass barcodes, and guest folios.",
2284
- "biotech": "Biotech & Genomics profile. Scrubs accessions, patient phenotypes, HGVS variants, DNA/RNA samples, and flowcell IDs.",
2285
- "telecom": "Call Centers & Telephony profile. Scrubs CTI interaction IDs, Amazon Connect/Genesys sessions, agent extensions, ANI/DNIS, IVR DTMF, and audio transcript diarization headers."
2286
- };
2287
- const desc = descriptions[profileName.toLowerCase()] || `The '${profileName}' profile is a specialized detection ruleset tuned for specific industry compliance. It detects and sanitizes domain-specific identifiers.`;
2343
+ const desc = PROFILE_RESOURCE_DESCRIPTIONS[profileName.toLowerCase()] || `The '${profileName}' profile is a specialized detection ruleset tuned for specific industry compliance. It detects and sanitizes domain-specific identifiers.`;
2288
2344
 
2289
2345
  return {
2290
2346
  contents: [
@@ -2489,10 +2545,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2489
2545
 
2490
2546
  const charLimit = (isAdvanced && !isAutoElevated && !license.isPro) ? 5000 : 15000;
2491
2547
  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`);
2548
+ 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
2549
  }
2494
2550
 
2495
- const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit);
2551
+ const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit, finalProfile);
2496
2552
 
2497
2553
  const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, currentIgnoreList, currentSession);
2498
2554
 
@@ -2851,10 +2907,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2851
2907
 
2852
2908
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
2853
2909
  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`);
2910
+ 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
2911
  }
2856
2912
 
2857
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
2913
+ const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit, finalProfile);
2858
2914
 
2859
2915
  const sanitized = performSanitization(processedContent, finalProfile, targetIgnoreList, targetSession);
2860
2916
  const { scrubbedText, newTokens } = sanitized;
@@ -3082,10 +3138,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3082
3138
 
3083
3139
  const charLimit = (isAdvanced && !isAutoElevated && !license.isPro) ? 5000 : 15000;
3084
3140
  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`);
3141
+ 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
3142
  }
3087
3143
 
3088
- const { processedText: processedContent2, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
3144
+ const { processedText: processedContent2, wasTruncated } = truncateIfFree(content, license.isPro, charLimit, finalProfile);
3089
3145
 
3090
3146
  const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile, targetIgnoreList, targetSession);
3091
3147
  const telemetry = buildCisoAuditTelemetry(newTokens);
@@ -3581,8 +3637,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3581
3637
  const isAdvanced = targetProfile.toLowerCase() !== "general";
3582
3638
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
3583
3639
 
3584
- const { processedText: cleanStdout, wasTruncated: truncOut } = truncateIfFree(stdout, license.isPro, charLimit);
3585
- const { processedText: cleanStderr, wasTruncated: truncErr } = truncateIfFree(stderr, license.isPro, charLimit);
3640
+ const { processedText: cleanStdout, wasTruncated: truncOut } = truncateIfFree(stdout, license.isPro, charLimit, targetProfile);
3641
+ const { processedText: cleanStderr, wasTruncated: truncErr } = truncateIfFree(stderr, license.isPro, charLimit, targetProfile);
3586
3642
 
3587
3643
  const resCommand = performSanitization(command, targetProfile, targetIgnoreList, targetSession);
3588
3644
  const resStdout = performSanitization(cleanStdout, targetProfile, targetIgnoreList, targetSession);
@@ -3751,7 +3807,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3751
3807
  const isAdvanced = targetProfile.toLowerCase() !== "general";
3752
3808
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
3753
3809
 
3754
- const { processedText, wasTruncated: wasCharTruncated } = truncateIfFree(capped, license.isPro, charLimit);
3810
+ const { processedText, wasTruncated: wasCharTruncated } = truncateIfFree(capped, license.isPro, charLimit, targetProfile);
3755
3811
  const currentSession = getSessionMap(session_id);
3756
3812
  const currentIgnoreList = getSessionIgnoreList(session_id);
3757
3813
  const { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, currentIgnoreList, currentSession);
@@ -3815,7 +3871,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3815
3871
  const isAdvanced = targetProfile.toLowerCase() !== "general";
3816
3872
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
3817
3873
 
3818
- const { processedText, wasTruncated } = truncateIfFree(diffOutput, license.isPro, charLimit);
3874
+ const { processedText, wasTruncated } = truncateIfFree(diffOutput, license.isPro, charLimit, targetProfile);
3819
3875
  const { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, targetIgnoreList, targetSession);
3820
3876
 
3821
3877
  const telemetry = buildCisoAuditTelemetry(newTokens);
@@ -4621,10 +4677,11 @@ export function performSanitization(text, profile, ignoreList = null, customSess
4621
4677
  return { scrubbedText: result.scrubbedText, newTokens };
4622
4678
  }
4623
4679
 
4624
- export function truncateIfFree(text, isPro, charLimit = 15000) {
4680
+ export function truncateIfFree(text, isPro, charLimit = 15000, profile = 'general') {
4625
4681
  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]`;
4682
+ const safeProf = encodeURIComponent(String(profile || 'general').toLowerCase());
4683
+ 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`);
4684
+ 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
4685
  return { processedText: text.substring(0, charLimit), wasTruncated: true, upsellNotice, charLimit };
4629
4686
  }
4630
4687
  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.8",
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.8",
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.8';
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');