@privacyscrubber/mcp-server 1.7.1 → 1.7.2

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
@@ -148,7 +148,7 @@ Reads a local file, extracts text, sanitizes it, and returns the redacted templa
148
148
 
149
149
  Looking for real-time protection directly inside your web browser?
150
150
  * **Chrome Extension:** Get the [PrivacyScrubber Chrome Extension](https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol) to sanitize prompts directly inside ChatGPT, Claude, and Gemini in real-time.
151
- * **Web Sandbox:** Use the zero-server browser sanitization tools at [PrivacyScrubber Homepage](https://privacyscrubber.com/).
151
+ * **Web Sandbox:** Use the zero-server browser sanitization tools at [PrivacyScrubber Homepage](https://privacyscrubber.com/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server).
152
152
 
153
153
  ## šŸ“„ License & Commercial Upgrade
154
154
 
@@ -166,7 +166,7 @@ By default, the server runs under the **Free Tier** (restricted to 50,000 charac
166
166
  | **Team Rules Sync (GPO)** | āŒ No | āŒ No | āœ… Yes (Shared Link) |
167
167
  | **Licensing Cost** | $0 | **$110 Lifetime** | **$99/mo Flat Rate** |
168
168
 
169
- šŸ‘‰ **[Acquire a PRO / TEAMS License Key at privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)**
169
+ šŸ‘‰ **[Acquire a PRO / TEAMS License Key at privacyscrubber.com/pricing](https://privacyscrubber.com/pricing?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server)**
170
170
 
171
171
  ---
172
172
 
@@ -195,12 +195,12 @@ Returns a visual dashboard showing your current tier, session request count, act
195
195
  ā•‘ šŸ“‹ Custom rules: šŸ”’ Locked — requires PRO ā•‘
196
196
  ╠══════════════════════════════════════════════════╣
197
197
  ā•‘ šŸ’³ Upgrade to PRO — $110 Lifetime ā•‘
198
- ā•‘ https://privacyscrubber.com/pricing ā•‘
198
+ ā•‘ https://privacyscrubber.com/pricing?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server ā•‘
199
199
  ╠══════════════════════════════════════════════════╣
200
200
  ā•‘ After purchase, add your key to MCP config: ā•‘
201
201
  ā•‘ "PRIVACYSCRUBBER_KEY": "<your-key-here>" ā•‘
202
202
  ā•‘ Full setup guide: ā•‘
203
- ā•‘ https://privacyscrubber.com/features/mcp/ ā•‘
203
+ ā•‘ https://privacyscrubber.com/features/mcp/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server ā•‘
204
204
  ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
205
205
  ```
206
206
 
@@ -208,7 +208,7 @@ Returns a visual dashboard showing your current tier, session request count, act
208
208
 
209
209
  ## šŸ” After Purchase: Activate PRO in Your MCP Client
210
210
 
211
- After purchasing a PRO license at [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing), you will receive a license key. Add it to your MCP client config as an environment variable: `PRIVACYSCRUBBER_KEY`.
211
+ After purchasing a PRO license at [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server), you will receive a license key. Add it to your MCP client config as an environment variable: `PRIVACYSCRUBBER_KEY`.
212
212
 
213
213
  ### Claude Desktop
214
214
 
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ // LicenseManager will be required below
2
3
 
3
4
  /**
4
5
  * PrivacyScrubber MCP Server
@@ -36,6 +37,7 @@ const MCP_VERSION = require('./package.json').version;
36
37
  // Import the production core engine with 100% parity
37
38
  const scrubberCorePath = path.resolve(__dirname, './scrubber-core.cjs');
38
39
  const PrivacyScrubberCore = require(scrubberCorePath);
40
+ const LicenseManager = require('./ps-license-manager.js');
39
41
 
40
42
  // Initialize core engine
41
43
  PrivacyScrubberCore.init();
@@ -74,83 +76,31 @@ const DEVOPS_SECRETS_DETECTOR = [
74
76
  { name: 'Database/API Secret', regex: /\b(DB|POSTGRES|REDIS|MYSQL|AWS|SECRET|PASSWORD|TOKEN|API|KEY)[A-Z0-9_]*\s*[:=]\s*[^ \t\r\n"']{8,}\b/gi }
75
77
  ];
76
78
 
77
- // Hardcoded public key for offline cryptographic verification
78
- const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
79
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqN700s8jMq9Rf09prTPB
80
- ekrFTXi+mnzSgMzwy+rMrJ4kyZQRDTxSFO1d9r5JQIhHfK5ToPiiS/Z/AS+Ga5bb
81
- BCy50SORDRn1HzrvFjNP+kogYyOeFk2lWw0Lt4/mX3M98zRE2D1HBDQJF8Y5Y/6l
82
- 735iwlF6T1oO0mEbb1FM5tKvO44OkTih7krc3w56R07S1WuYIhGLR6IfDbNU/drL
83
- nbhUR4HqBRepIsn9mcSODw/FP5t3QlFLfDvZo/1SIUSBa9NowhPtPk0Hq3hndSvR
84
- pXkyvjeA7HLyXU8hhvausn85QRdEYTMXP9BehDjhli4VELSAY3ARGzemcUgggejg
85
- CwIDAQAB
86
- -----END PUBLIC KEY-----`;
87
-
88
79
  function checkLicenseStatus() {
89
80
  const key = (process.env.PRIVACYSCRUBBER_KEY || "").trim();
90
81
  if (!key) return { isPro: false, type: null, error: "No license key provided." };
91
82
 
92
- try {
93
- const [payloadBase64, signatureBase64] = key.split('.');
94
- if (!payloadBase64 || !signatureBase64) {
95
- return { isPro: false, type: null, error: "Invalid license key structure." };
96
- }
97
-
98
- const verifier = crypto.createVerify('SHA256');
99
- verifier.update(payloadBase64);
100
- const isVerified = verifier.verify(PUBLIC_KEY, signatureBase64, 'base64');
101
-
102
- if (!isVerified) {
103
- return { isPro: false, type: null, error: "Signature verification failed." };
104
- }
105
-
106
- const payload = JSON.parse(Buffer.from(payloadBase64, 'base64').toString('utf8'));
107
-
108
- // Expiration check
109
- if (payload.expires && payload.expires < Math.floor(Date.now() / 1000)) {
110
- return {
111
- isPro: false,
112
- type: payload.type,
113
- error: `License expired on ${new Date(payload.expires * 1000).toLocaleDateString()}`
114
- };
115
- }
116
-
117
- return { isPro: true, type: payload.type, error: null };
118
- } catch (e) {
119
- return { isPro: false, type: null, error: "Error parsing license: " + e.message };
83
+ const result = LicenseManager.validate(key);
84
+
85
+ if (!result.valid) {
86
+ return { isPro: false, type: null, error: result.reason || "Invalid license format or signature." };
120
87
  }
88
+
89
+ return { isPro: true, type: result.tier, error: null };
121
90
  }
122
91
 
123
92
  // ── Session request counter (volatile in-memory) ─────────────────────────────
124
93
  let sessionRequestCount = 0;
125
94
  const UPSELL_EVERY_N = 10; // show upsell nudge every N free-tier requests
126
95
 
127
- // Build a clean upsell content block visible to the agent / user
96
+ // Build a clean upsell content block (Disabled to prevent agent-visible marketing content)
128
97
  function buildUpsellBlock(reason) {
129
- const count = sessionRequestCount;
130
- let lines = [
131
- '─────────────────────────────────────────────',
132
- '⚔ PrivacyScrubber PRO — Upgrade your AI setup',
133
- '─────────────────────────────────────────────',
134
- ];
135
- if (reason) lines.push(`ā„¹ļø ${reason}`);
136
- lines.push(
137
- `šŸ“Š Free session requests: ${count}`,
138
- 'šŸ”’ PRO unlocks: 22 industry profiles (Dev, Medical, Legal…)',
139
- ' + Custom regex rules (privacyscrubber.json)',
140
- ' + Unlimited input size',
141
- '',
142
- 'šŸ’³ PRO Lifetime: $110 → https://privacyscrubber.com/pricing',
143
- '',
144
- 'After purchase, add your key to your MCP config:',
145
- ' "PRIVACYSCRUBBER_KEY": "<your-key-here>"',
146
- '─────────────────────────────────────────────'
147
- );
148
- return { type: 'text', text: lines.join('\n') };
98
+ return null;
149
99
  }
150
100
 
151
- // Decide if we should attach a soft periodic nudge (every N requests, free tier)
101
+ // Decide if we should attach a soft periodic nudge (Disabled to maintain clean MCP output)
152
102
  function shouldNudge(isPro) {
153
- return !isPro && sessionRequestCount > 0 && sessionRequestCount % UPSELL_EVERY_N === 0;
103
+ return false;
154
104
  }
155
105
 
156
106
  // Create the MCP server
@@ -437,27 +387,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
437
387
 
438
388
  sessionRequestCount++;
439
389
 
390
+ const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
440
391
  if (isAdvanced && !license.isPro) {
441
- if (text.length > 5000) {
442
- const reason = license.error ? ` (${license.error})` : "";
443
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Profile '${targetProfile}' on Free tier is limited to 5,000 chars. Falling back to 'General'.${reason}${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key at: https://privacyscrubber.com/pricing${colors.reset}\n`);
444
- finalProfile = "General";
445
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' on Free tier is limited to 5,000 chars. Upgrade to PRO for unlimited scanning.`));
446
- } else {
447
- mcpLog(`${colors.cyan}ā„¹ļø [PrivacyScrubber] Profile '${targetProfile}' active (Free tier: up to 5,000 chars).${colors.reset}\n`);
448
- }
392
+ mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key for unlimited use: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
393
+ extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
449
394
  }
450
395
 
451
- const { processedText, wasTruncated } = truncateIfFree(text, license.isPro);
396
+ const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit);
452
397
  if (wasTruncated) {
453
- extraBlocks.push(buildUpsellBlock(`Input was truncated to 50,000 characters (Free Tier limit).`));
398
+ extraBlocks.push(buildUpsellBlock(`Input was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
454
399
  }
455
400
 
456
401
  if (!license.isPro) {
457
402
  const detected = detectSecrets(processedText);
458
403
  if (detected.length > 0) {
459
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing${colors.reset}\n`);
460
- extraBlocks.push(buildUpsellBlock(`API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).`));
404
+ mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
405
+ return {
406
+ isError: true,
407
+ content: [{ type: "text", text: `Error: API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data. Sanitizing DevOps secrets requires a PRO license. Get a key at: https://privacyscrubber.com/pricing` }]
408
+ };
461
409
  }
462
410
  }
463
411
 
@@ -471,7 +419,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
471
419
  return {
472
420
  content: [
473
421
  { type: "text", text: sanitized },
474
- ...extraBlocks
422
+ ...extraBlocks.filter(Boolean)
475
423
  ]
476
424
  };
477
425
  }
@@ -586,21 +534,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
586
534
 
587
535
  sessionRequestCount++;
588
536
 
537
+ const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
589
538
  if (isAdvanced && !license.isPro) {
590
- const reason = license.error ? ` (${license.error})` : "";
591
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key at: https://privacyscrubber.com/pricing${colors.reset}\n`);
592
- finalProfile = "General";
593
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' requires PRO. Using 'General' as fallback.`));
539
+ mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key for unlimited use: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
540
+ extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
594
541
  }
595
542
 
596
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro);
597
- if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to 50,000 characters (Free Tier limit).`));
543
+ const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
544
+ if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
598
545
 
599
546
  if (!license.isPro) {
600
547
  const detected = detectSecrets(processedContent);
601
548
  if (detected.length > 0) {
602
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing${colors.reset}\n`);
603
- extraBlocks.push(buildUpsellBlock(`API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).`));
549
+ mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
550
+ return {
551
+ isError: true,
552
+ content: [{ type: "text", text: `Error: API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data. Sanitizing DevOps secrets requires a PRO license. Get a key at: https://privacyscrubber.com/pricing` }]
553
+ };
604
554
  }
605
555
  }
606
556
 
@@ -613,7 +563,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
613
563
  return {
614
564
  content: [
615
565
  { type: "text", text: sanitized },
616
- ...extraBlocks
566
+ ...extraBlocks.filter(Boolean)
617
567
  ]
618
568
  };
619
569
  } catch (docxError) {
@@ -637,7 +587,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
637
587
  content: [
638
588
  {
639
589
  type: "text",
640
- text: `Error: PDF and Excel file sanitization is a PRO feature. Set PRIVACYSCRUBBER_KEY to your PRO license key to unlock local document parsing. Get a key at: https://privacyscrubber.com/pricing`
590
+ text: `Error: PDF and Excel file sanitization is a PRO feature. Set PRIVACYSCRUBBER_KEY to your PRO license key to unlock local document parsing. Get a key at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal`
641
591
  }
642
592
  ]
643
593
  };
@@ -656,21 +606,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
656
606
 
657
607
  sessionRequestCount++;
658
608
 
609
+ const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
659
610
  if (isAdvanced && !license.isPro) {
660
- const reason = license.error ? ` (${license.error})` : "";
661
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key at: https://privacyscrubber.com/pricing${colors.reset}\n`);
662
- finalProfile = "General";
663
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' requires PRO. Using 'General' as fallback.`));
611
+ mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key for unlimited use: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
612
+ extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
664
613
  }
665
614
 
666
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro);
667
- if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to 50,000 characters (Free Tier limit).`));
615
+ const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
616
+ if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
668
617
 
669
618
  if (!license.isPro) {
670
619
  const detected = detectSecrets(processedContent);
671
620
  if (detected.length > 0) {
672
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing${colors.reset}\n`);
673
- extraBlocks.push(buildUpsellBlock(`API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).`));
621
+ mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
622
+ return {
623
+ isError: true,
624
+ content: [{ type: "text", text: `Error: API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data. Sanitizing DevOps secrets requires a PRO license. Get a key at: https://privacyscrubber.com/pricing` }]
625
+ };
674
626
  }
675
627
  }
676
628
 
@@ -683,7 +635,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
683
635
  return {
684
636
  content: [
685
637
  { type: "text", text: sanitized },
686
- ...extraBlocks
638
+ ...extraBlocks.filter(Boolean)
687
639
  ]
688
640
  };
689
641
  } catch (pdfError) {
@@ -713,21 +665,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
713
665
 
714
666
  sessionRequestCount++;
715
667
 
668
+ const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
716
669
  if (isAdvanced && !license.isPro) {
717
- const reason = license.error ? ` (${license.error})` : "";
718
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key at: https://privacyscrubber.com/pricing${colors.reset}\n`);
719
- finalProfile = "General";
720
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' requires PRO. Using 'General' as fallback.`));
670
+ mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key for unlimited use: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
671
+ extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
721
672
  }
722
673
 
723
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro);
724
- if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to 50,000 characters (Free Tier limit).`));
674
+ const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
675
+ if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
725
676
 
726
677
  if (!license.isPro) {
727
678
  const detected = detectSecrets(processedContent);
728
679
  if (detected.length > 0) {
729
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing${colors.reset}\n`);
730
- extraBlocks.push(buildUpsellBlock(`API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).`));
680
+ mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
681
+ return {
682
+ isError: true,
683
+ content: [{ type: "text", text: `Error: API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data. Sanitizing DevOps secrets requires a PRO license. Get a key at: https://privacyscrubber.com/pricing` }]
684
+ };
731
685
  }
732
686
  }
733
687
 
@@ -740,7 +694,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
740
694
  return {
741
695
  content: [
742
696
  { type: "text", text: sanitized },
743
- ...extraBlocks
697
+ ...extraBlocks.filter(Boolean)
744
698
  ]
745
699
  };
746
700
  } catch (xlsxError) {
@@ -784,21 +738,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
784
738
 
785
739
  sessionRequestCount++;
786
740
 
741
+ const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
787
742
  if (isAdvanced && !license.isPro) {
788
- const reason = license.error ? ` (${license.error})` : "";
789
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key at: https://privacyscrubber.com/pricing${colors.reset}\n`);
790
- finalProfile = "General";
791
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' requires PRO. Using 'General' as fallback.`));
743
+ mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n${colors.cyan}šŸ‘‰ Get a PRO key for unlimited use: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
744
+ extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
792
745
  }
793
746
 
794
- const { processedText: processedContent2, wasTruncated: wasTruncated2 } = truncateIfFree(content, license.isPro);
795
- if (wasTruncated2) extraBlocks.push(buildUpsellBlock(`File content was truncated to 50,000 characters (Free Tier limit).`));
747
+ const { processedText: processedContent2, wasTruncated: wasTruncated2 } = truncateIfFree(content, license.isPro, charLimit);
748
+ if (wasTruncated2) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
796
749
 
797
750
  if (!license.isPro) {
798
751
  const detected = detectSecrets(processedContent2);
799
752
  if (detected.length > 0) {
800
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing${colors.reset}\n`);
801
- extraBlocks.push(buildUpsellBlock(`API Key / Secret (${detected.join(', ')}) detected! Sanitization skipped (Requires PRO Profile).`));
753
+ mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data (Requires PRO Profile).${colors.reset}\n${colors.cyan}šŸ‘‰ Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
754
+ return {
755
+ isError: true,
756
+ content: [{ type: "text", text: `Error: API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data. Sanitizing DevOps secrets requires a PRO license. Get a key at: https://privacyscrubber.com/pricing` }]
757
+ };
802
758
  }
803
759
  }
804
760
 
@@ -811,7 +767,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
811
767
  return {
812
768
  content: [
813
769
  { type: "text", text: sanitized },
814
- ...extraBlocks
770
+ ...extraBlocks.filter(Boolean)
815
771
  ]
816
772
  };
817
773
  }
@@ -873,7 +829,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
873
829
  }
874
830
 
875
831
  if (isSecretLeak) {
876
- return { isError: true, content: [{ type: "text", text: `Error: API Key or Secret detected. Sanitization of raw secrets requires PRO Profile. Upgrade at https://privacyscrubber.com/pricing` }] };
832
+ return { isError: true, content: [{ type: "text", text: `Error: API Key or Secret detected. Sanitization of raw secrets requires PRO Profile. Upgrade at https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal` }] };
877
833
  }
878
834
 
879
835
  if (!no_backup) {
@@ -906,7 +862,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
906
862
  }
907
863
 
908
864
  const defaultTemplate = {
909
- "_comment": "PrivacyScrubber local configuration. Add custom rules and restart your MCP client (Cursor/Windsurf). Details: https://privacyscrubber.com/docs/config",
865
+ "_comment": "PrivacyScrubber local configuration. Add custom rules and restart your MCP client (Cursor/Windsurf). Details: https://privacyscrubber.com/docs/config?utm_source=mcp_cli&utm_medium=terminal",
910
866
  "customRules": [
911
867
  {
912
868
  "pattern": "my-secret-pattern-\\d+",
@@ -955,7 +911,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
955
911
  const profileList = license.isPro
956
912
  ? 'All 23 profiles active (General, Dev, Medical, Legal, Finance, HR…)'
957
913
  : 'General only — PRO unlocks 22 industry profiles';
958
- const sizeLimit = license.isPro ? 'Unlimited' : '50,000 characters per request';
914
+ const sizeLimit = license.isPro ? 'Unlimited' : '15,000 characters per request';
959
915
 
960
916
  const configPath = resolveConfigPath();
961
917
  let rulesStatus = '';
@@ -1001,12 +957,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1001
957
  lines.push(
1002
958
  'ā•‘ āœ… PRO is active. All features unlocked. ā•‘',
1003
959
  'ā•‘ To regenerate your key or manage billing: ā•‘',
1004
- 'ā•‘ https://privacyscrubber.com/pricing ā•‘'
960
+ 'ā•‘ https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal ā•‘'
1005
961
  );
1006
962
  } else {
1007
963
  lines.push(
1008
964
  'ā•‘ šŸ’³ Upgrade to PRO — $110 Lifetime ā•‘',
1009
- 'ā•‘ https://privacyscrubber.com/pricing ā•‘',
965
+ 'ā•‘ https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal ā•‘',
1010
966
  '╠══════════════════════════════════════════════════╣',
1011
967
  'ā•‘ After purchase, add your key to MCP config: ā•‘',
1012
968
  'ā•‘ ā•‘',
@@ -1014,7 +970,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1014
970
  'ā•‘ "PRIVACYSCRUBBER_KEY": "<your-key-here>" ā•‘',
1015
971
  'ā•‘ ā•‘',
1016
972
  'ā•‘ Full setup guide: ā•‘',
1017
- 'ā•‘ https://privacyscrubber.com/features/mcp/ ā•‘'
973
+ 'ā•‘ https://privacyscrubber.com/features/mcp/?utm_source=mcp_cli&utm_medium=terminal ā•‘'
1018
974
  );
1019
975
  }
1020
976
 
@@ -1209,25 +1165,6 @@ function performSanitization(text, profile) {
1209
1165
  const normalizedProfile = (profile || "general").trim().toLowerCase();
1210
1166
  const license = checkLicenseStatus();
1211
1167
  const result = PrivacyScrubberCore.scrubText(text, customRules, {}, normalizedProfile, sessionMap, license.isPro);
1212
- if (!license.isPro && result.tokenMap) {
1213
- // Re-scan each masked token's original value against the shared detector.
1214
- // If it was a raw secret, restore it in the output and remove from sessionMap
1215
- // so it is never accidentally revealed by reveal_text.
1216
- Object.entries(result.tokenMap).forEach(([token, original]) => {
1217
- let isSecret = false;
1218
- for (const detector of DEVOPS_SECRETS_DETECTOR) {
1219
- detector.regex.lastIndex = 0;
1220
- if (detector.regex.test(original)) {
1221
- isSecret = true;
1222
- break;
1223
- }
1224
- }
1225
- if (isSecret) {
1226
- result.scrubbedText = result.scrubbedText.replace(token, original);
1227
- delete result.tokenMap[token];
1228
- }
1229
- });
1230
- }
1231
1168
 
1232
1169
  // Update our volatile map with new matches
1233
1170
  if (result.tokenMap) {
@@ -1239,11 +1176,10 @@ function performSanitization(text, profile) {
1239
1176
  return result.scrubbedText;
1240
1177
  }
1241
1178
 
1242
- function truncateIfFree(text, isPro) {
1243
- const MAX_FREE_CHARS = 5000;
1244
- if (!isPro && text.length > MAX_FREE_CHARS) {
1245
- mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Input truncated to ${MAX_FREE_CHARS} characters (Free Tier Limit).${colors.reset}\n${colors.cyan}šŸ‘‰ Set PRIVACYSCRUBBER_KEY to your PRO license key for unlimited size.${colors.reset}\n`);
1246
- return { processedText: text.substring(0, MAX_FREE_CHARS), wasTruncated: true };
1179
+ function truncateIfFree(text, isPro, charLimit = 15000) {
1180
+ if (!isPro && text.length > charLimit) {
1181
+ mcpLog(`${colors.yellowBold}āš ļø [PrivacyScrubber] Input truncated to ${charLimit.toLocaleString()} characters (Free Tier Limit).${colors.reset}\n${colors.cyan}šŸ‘‰ Set PRIVACYSCRUBBER_KEY to your PRO license key for unlimited size.${colors.reset}\n`);
1182
+ return { processedText: text.substring(0, charLimit), wasTruncated: true };
1247
1183
  }
1248
1184
  return { processedText: text, wasTruncated: false };
1249
1185
  }
package/install.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import os from 'os';
6
+
7
+ console.log('šŸ›”ļø PrivacyScrubber MCP Server 1-Click Installer');
8
+ console.log('================================================\n');
9
+
10
+ const mcpConfig = {
11
+ command: "npx",
12
+ args: ["-y", "@privacyscrubber/mcp-server"]
13
+ };
14
+
15
+ let claudeConfigPath = '';
16
+ if (process.platform === 'darwin') {
17
+ claudeConfigPath = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
18
+ } else if (process.platform === 'win32') {
19
+ claudeConfigPath = path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
20
+ } else {
21
+ // Linux fallback, though Claude Desktop is not officially supported on Linux yet, we check common paths
22
+ claudeConfigPath = path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
23
+ }
24
+
25
+ // 1. Configure Claude Desktop
26
+ let installedClaude = false;
27
+ try {
28
+ let configDir = path.dirname(claudeConfigPath);
29
+ if (!fs.existsSync(configDir)) {
30
+ fs.mkdirSync(configDir, { recursive: true });
31
+ }
32
+
33
+ let config = {};
34
+ if (fs.existsSync(claudeConfigPath)) {
35
+ const rawData = fs.readFileSync(claudeConfigPath, 'utf8');
36
+ if (rawData.trim() !== '') {
37
+ config = JSON.parse(rawData);
38
+ }
39
+ }
40
+
41
+ if (!config.mcpServers) {
42
+ config.mcpServers = {};
43
+ }
44
+
45
+ config.mcpServers['privacyscrubber'] = mcpConfig;
46
+
47
+ fs.writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
48
+ console.log(`āœ… [Claude Desktop] Successfully injected PrivacyScrubber MCP into:`);
49
+ console.log(` ${claudeConfigPath}`);
50
+ installedClaude = true;
51
+ } catch (e) {
52
+ console.log(`āš ļø [Claude Desktop] Could not configure automatically: ${e.message}`);
53
+ }
54
+
55
+ console.log('\n---');
56
+ console.log('\nšŸ”µ [Cursor / Windsurf Users]');
57
+ console.log('To install PrivacyScrubber in Cursor or Windsurf:');
58
+ console.log('1. Open Settings -> Features -> MCP Servers (or type "MCP" in settings).');
59
+ console.log('2. Click "+ Add New MCP Server".');
60
+ console.log('3. Set Name: privacyscrubber');
61
+ console.log('4. Set Type: command');
62
+ console.log('5. Set Command: npx -y @privacyscrubber/mcp-server');
63
+ console.log('\nšŸš€ Installation complete! Restart your IDE or Claude Desktop to start redacting PII locally.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privacyscrubber/mcp-server",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
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",
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "index.js",
13
+ "install.js",
13
14
  "pii-masking-run.js",
14
15
  "scrubber-core.cjs",
15
16
  "smithery.yaml",
@@ -21,7 +22,8 @@
21
22
  "bin": {
22
23
  "mcp-server": "index.js",
23
24
  "privacyscrubber-mcp": "index.js",
24
- "pii-masking-run": "pii-masking-run.js"
25
+ "pii-masking-run": "pii-masking-run.js",
26
+ "privacyscrubber-mcp-install": "install.js"
25
27
  },
26
28
  "scripts": {
27
29
  "start": "node index.js"
@@ -57,7 +59,7 @@
57
59
  ],
58
60
  "author": "Ilya Sibiryakov (BrandMeWeb)",
59
61
  "license": "MIT",
60
- "homepage": "https://privacyscrubber.com/pii-mcp/",
62
+ "homepage": "https://privacyscrubber.com/pii-mcp/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server",
61
63
  "repository": {
62
64
  "type": "git",
63
65
  "url": "git+https://github.com/moxno/privacyscrubber-mcp.git"
@@ -190,12 +190,12 @@ function performSanitization(text) {
190
190
  }
191
191
  if (isSecret) {
192
192
  hasSecrets = true;
193
- result.scrubbedText = result.scrubbedText.replace(token, original);
193
+ result.scrubbedText = result.scrubbedText.replace(token, '[SECRET_BLOCKED_UPGRADE_TO_PRO]');
194
194
  }
195
195
  });
196
196
 
197
197
  if (hasSecrets) {
198
- process.stderr.write(`${colors.yellowBold}āš ļø [PrivacyScrubber] Secrets/Keys detected in output. Redaction skipped (Requires PRO tier).${colors.reset}\n`);
198
+ process.stderr.write(`\x1b[1;31m🚫 [PrivacyScrubber] Secrets/Keys detected in output. Replaced with [SECRET_BLOCKED_UPGRADE_TO_PRO] to protect your data (Requires PRO tier).\x1b[0m\n`);
199
199
  }
200
200
  }
201
201