@privacyscrubber/mcp-server 2.0.2 → 2.0.3

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
@@ -3,6 +3,9 @@
3
3
  [![NPM Version](https://img.shields.io/npm/v/@privacyscrubber/mcp-server?color=blue)](https://www.npmjs.com/package/@privacyscrubber/mcp-server)
4
4
  [![NPM Downloads](https://img.shields.io/npm/dm/@privacyscrubber/mcp-server?color=3b82f6)](https://www.npmjs.com/package/@privacyscrubber/mcp-server)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22058770.svg)](https://zenodo.org/records/22058770)
7
+ [![OSF DOI](https://img.shields.io/badge/OSF%20DOI-10.17605%2FOSF.IO%2F5BYJF-blue.svg)](https://osf.io/5byjf/)
8
+ [![SSRN](https://img.shields.io/badge/SSRN-7335581-darkred.svg)](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7335581)
6
9
  [![smithery badge](https://smithery.ai/badge/privacyscrubber/pii-masking-mcp)](https://smithery.ai/servers/privacyscrubber/pii-masking-mcp)
7
10
  [![Security: 100% Local](https://img.shields.io/badge/Security-100%25%20Local-emerald)](https://privacyscrubber.com)
8
11
  [![Parity: 100% Core Match](https://img.shields.io/badge/Parity-100%25%20Core%20Match-blueviolet)](https://privacyscrubber.com)
@@ -148,7 +151,7 @@ Reads a local file, extracts text, sanitizes it, and returns the redacted templa
148
151
 
149
152
  Looking for real-time protection directly inside your web browser?
150
153
  * **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/).
154
+ * **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
155
 
153
156
  ## 📄 License & Commercial Upgrade
154
157
 
@@ -166,7 +169,7 @@ By default, the server runs under the **Free Tier** (restricted to 50,000 charac
166
169
  | **Team Rules Sync (GPO)** | ❌ No | ❌ No | ✅ Yes (Shared Link) |
167
170
  | **Licensing Cost** | $0 | **$110 Lifetime** | **$99/mo Flat Rate** |
168
171
 
169
- 👉 **[Acquire a PRO / TEAMS License Key at privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)**
172
+ 👉 **[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
173
 
171
174
  ---
172
175
 
@@ -195,12 +198,12 @@ Returns a visual dashboard showing your current tier, session request count, act
195
198
  ║ 📋 Custom rules: 🔒 Locked — requires PRO ║
196
199
  ╠══════════════════════════════════════════════════╣
197
200
  ║ 💳 Upgrade to PRO — $110 Lifetime ║
198
- ║ https://privacyscrubber.com/pricing ║
201
+ ║ https://privacyscrubber.com/pricing?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server ║
199
202
  ╠══════════════════════════════════════════════════╣
200
203
  ║ After purchase, add your key to MCP config: ║
201
204
  ║ "PRIVACYSCRUBBER_KEY": "<your-key-here>" ║
202
205
  ║ Full setup guide: ║
203
- ║ https://privacyscrubber.com/features/mcp/ ║
206
+ ║ https://privacyscrubber.com/features/mcp/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server ║
204
207
  ╚══════════════════════════════════════════════════╝
205
208
  ```
206
209
 
@@ -208,7 +211,7 @@ Returns a visual dashboard showing your current tier, session request count, act
208
211
 
209
212
  ## 🔐 After Purchase: Activate PRO in Your MCP Client
210
213
 
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`.
214
+ 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
215
 
213
216
  ### Claude Desktop
214
217
 
@@ -271,3 +274,36 @@ Use the check_status tool from PrivacyScrubber MCP
271
274
 
272
275
  The dashboard should show **Tier: PRO** and all profiles unlocked.
273
276
 
277
+ ---
278
+
279
+ ## 📚 Academic Foundations & Regulatory Verification
280
+
281
+ PrivacyScrubber and the Zero-Trust Data Sanitization (ZTDS) protocol are backed by published scientific, clinical, and legal treatises:
282
+
283
+ | Repository / Archive | DOI / Identifier | Focus Area | Regulatory & Compliance Scope |
284
+ |---|---|---|---|
285
+ | **Zenodo / CERN** | [`10.5281/zenodo.22058770`](https://zenodo.org/records/22058770) | Zero-Trust Data Sanitization (ZTDS) Protocol Foundation | Cross-Border AI Privacy, ISO 27001 A.8.11 |
286
+ | **OSF (Center for Open Science)** | [`10.17605/OSF.IO/5BYJF`](https://osf.io/5byjf/) | Empirical Latency Benchmark & Memory Profiling (<2ms RAM) | Performance vs Cloud DLP Proxies |
287
+ | **SSRN / Elsevier** | [`SSRN ID: 7335581`](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7335581) | Enterprise Generative AI Governance | EU AI Act, UK GDPR, US State Privacy |
288
+ | **medRxiv (Cold Spring Harbor)** | [`MEDRXIV/2026/361661`](https://submit.medrxiv.org/) | Multi-Center Clinical Trial De-Identification | HIPAA Safe Harbor Section 164.514(b) |
289
+ | **Law Archive / OSF** | [`LawArchive ID: 4wc86`](https://osf.io/preprints/lawarchive/4wc86/) | Preserving Attorney-Client Privilege in AI Workflows | ABA Model Rules & Legal Ethics |
290
+
291
+ ### Citing PrivacyScrubber in Research & Audits
292
+ ```bibtex
293
+ @software{sibiryakov2026privacyscrubber,
294
+ author = {Sibiryakov, Ilya},
295
+ title = {PrivacyScrubber: Zero-Trust Data Sanitization (ZTDS) Engine & MCP Server},
296
+ year = {2026},
297
+ publisher = {Zenodo},
298
+ doi = {10.5281/zenodo.22058770},
299
+ url = {https://github.com/moxno/privacyscrubber-mcp}
300
+ }
301
+ ```
302
+
303
+ ---
304
+
305
+ ## 📄 License
306
+
307
+ MIT © [Ilya Sibiryakov](https://privacyscrubber.com) (BrandMeWeb)
308
+
309
+
package/index.js CHANGED
@@ -97,6 +97,8 @@ function checkLicenseStatus() {
97
97
 
98
98
  return { isPro: true, type: result.tier, error: null };
99
99
  }
100
+ // ── Zero-Trust Air-Gapped Operation ──────────────────────────────────────────
101
+ // 100% local stdio execution in memory. Zero external network calls, zero telemetry.
100
102
 
101
103
  // ── Free Tier Usage Tracking (Persistent) ─────────────────────────────
102
104
  const FREE_TIER_DAILY_LIMIT = 10;
@@ -230,16 +232,6 @@ function formatAuditReceipt(telemetry) {
230
232
  return `\\n\\n> 🛡️ **PrivacyScrubber Audit Receipt**\\n> * **Risk Level:** ${icon} ${telemetry.riskLevel}\\n> * **Compliance Enforced:** ${telemetry.frameworksList.join(', ')}\\n> * **Tokens Masked:** ${telemetry.totalCount} (${entitiesList})\\n`;
231
233
  }
232
234
 
233
- // Build a clean upsell content block (Disabled to prevent agent-visible marketing content)
234
- function buildUpsellBlock(reason) {
235
- return null;
236
- }
237
-
238
- // Decide if we should attach a soft periodic nudge (Disabled to maintain clean MCP output)
239
- function shouldNudge(isPro) {
240
- return false;
241
- }
242
-
243
235
  // Create the MCP server
244
236
  const server = new Server(
245
237
  {
@@ -540,6 +532,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
540
532
  // Handle tool execution calls
541
533
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
542
534
  const { name, arguments: args } = request.params;
535
+ const toolStart = Date.now();
543
536
 
544
537
  try {
545
538
  if (name === "sanitize_text") {
@@ -567,7 +560,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
567
560
  const isAdvanced = targetProfile.toLowerCase() !== "general";
568
561
  const license = checkLicenseStatus();
569
562
  let finalProfile = targetProfile;
570
- const extraBlocks = [];
571
563
 
572
564
  const limitStatus = checkFreeTierLimit(license.isPro);
573
565
  if (limitStatus.blocked) {
@@ -579,22 +571,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
579
571
 
580
572
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
581
573
  if (isAdvanced && !license.isPro) {
582
- 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`);
583
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
574
+ mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n`);
584
575
  }
585
576
 
586
- const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit);
587
- if (wasTruncated) {
588
- extraBlocks.push(buildUpsellBlock(`Input was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
589
- }
590
-
591
- if (!license.isPro) {
592
- const detected = detectSecrets(processedText);
593
- if (detected.length > 0) {
594
- mcpLog(`${colors.cyan}🔒 [PrivacyScrubber] DevOps Secret / API Key (${detected.join(', ')}) sanitized locally in RAM.${colors.reset}\n${colors.yellowBold}👉 Upgrade to PRO for 25 industry profiles & batch sanitization: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
595
- extraBlocks.push(buildUpsellBlock(`DevOps Secret / API Key (${detected.join(', ')}) sanitized. Upgrade to PRO for 25 specialized industry profiles & batch directory sanitization.`));
596
- }
597
- }
577
+ const { processedText } = truncateIfFree(text, license.isPro, charLimit);
598
578
 
599
579
  const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, sessionIgnoreList);
600
580
 
@@ -603,8 +583,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
603
583
 
604
584
  return {
605
585
  content: [
606
- { type: "text", text: scrubbedText + receiptMd },
607
- ...extraBlocks.filter(Boolean)
586
+ { type: "text", text: scrubbedText + receiptMd }
608
587
  ]
609
588
  };
610
589
  }
@@ -745,9 +724,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
745
724
  const targetProfile = profile.trim();
746
725
  const isAdvanced = targetProfile.toLowerCase() !== "general";
747
726
  const license = checkLicenseStatus();
748
-
749
727
  let finalProfile = targetProfile;
750
- const extraBlocks = [];
751
728
 
752
729
  const limitStatus = checkFreeTierLimit(license.isPro);
753
730
  if (limitStatus.blocked) {
@@ -759,23 +736,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
759
736
 
760
737
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
761
738
  if (isAdvanced && !license.isPro) {
762
- 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`);
763
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
739
+ mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n`);
764
740
  }
765
741
 
766
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
767
- if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
768
-
769
- if (!license.isPro) {
770
- const detected = detectSecrets(processedContent);
771
- if (detected.length > 0) {
772
- 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`);
773
- return {
774
- isError: true,
775
- 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` }]
776
- };
777
- }
778
- }
742
+ const { processedText: processedContent } = truncateIfFree(content, license.isPro, charLimit);
779
743
 
780
744
  const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
781
745
  const { scrubbedText, newTokens } = sanitized;
@@ -785,8 +749,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
785
749
 
786
750
  return {
787
751
  content: [
788
- { type: "text", text: combinedOutput },
789
- ...extraBlocks.filter(Boolean)
752
+ { type: "text", text: combinedOutput }
790
753
  ]
791
754
  };
792
755
  } catch (docxError) {
@@ -810,7 +773,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
810
773
  content: [
811
774
  {
812
775
  type: "text",
813
- 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`
776
+ text: `🔒 [PrivacyScrubber PRO] Sanitizing ${isPdf ? 'PDF' : 'Excel/XLSX'} files is a PRO feature.\n\n👉 Set your PRIVACYSCRUBBER_KEY environment variable to a valid PRO license key.\n👉 Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal\n👉 Alternatively, sanitize plain text, code, CSV, and DOCX files for free.`
814
777
  }
815
778
  ]
816
779
  };
@@ -822,40 +785,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
822
785
  const data = await pdf(buffer);
823
786
  const content = data.text;
824
787
  const targetProfile = profile.trim();
825
- const isAdvanced = targetProfile.toLowerCase() !== "general";
826
-
827
788
  let finalProfile = targetProfile;
828
- const extraBlocks = [];
829
-
830
- const limitStatus = checkFreeTierLimit(license.isPro);
831
- if (limitStatus.blocked) {
832
- return {
833
- isError: true,
834
- content: [{ type: "text", text: `Error: Free tier daily limit exhausted (${FREE_TIER_DAILY_LIMIT} requests). Please set your PRO license key to continue. Get a key at: https://privacyscrubber.com/pricing` }]
835
- };
836
- }
837
-
838
- const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
839
- if (isAdvanced && !license.isPro) {
840
- 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`);
841
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
842
- }
843
-
844
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
845
- if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
846
-
847
- if (!license.isPro) {
848
- const detected = detectSecrets(processedContent);
849
- if (detected.length > 0) {
850
- 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`);
851
- return {
852
- isError: true,
853
- 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` }]
854
- };
855
- }
856
- }
857
789
 
858
- const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
790
+ const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
859
791
  const { scrubbedText, newTokens } = sanitized;
860
792
  const telemetry = buildCisoAuditTelemetry(newTokens);
861
793
  const receiptMarkdown = formatAuditReceipt(telemetry);
@@ -863,8 +795,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
863
795
 
864
796
  return {
865
797
  content: [
866
- { type: "text", text: combinedOutput },
867
- ...extraBlocks.filter(Boolean)
798
+ { type: "text", text: combinedOutput }
868
799
  ]
869
800
  };
870
801
  } catch (pdfError) {
@@ -887,40 +818,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
887
818
  });
888
819
 
889
820
  const targetProfile = profile.trim();
890
- const isAdvanced = targetProfile.toLowerCase() !== "general";
891
-
892
821
  let finalProfile = targetProfile;
893
- const extraBlocks = [];
894
-
895
- const limitStatus = checkFreeTierLimit(license.isPro);
896
- if (limitStatus.blocked) {
897
- return {
898
- isError: true,
899
- content: [{ type: "text", text: `Error: Free tier daily limit exhausted (${FREE_TIER_DAILY_LIMIT} requests). Please set your PRO license key to continue. Get a key at: https://privacyscrubber.com/pricing` }]
900
- };
901
- }
902
-
903
- const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
904
- if (isAdvanced && !license.isPro) {
905
- 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`);
906
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
907
- }
908
-
909
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
910
- if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
911
-
912
- if (!license.isPro) {
913
- const detected = detectSecrets(processedContent);
914
- if (detected.length > 0) {
915
- 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`);
916
- return {
917
- isError: true,
918
- 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` }]
919
- };
920
- }
921
- }
922
822
 
923
- const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
823
+ const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
924
824
  const { scrubbedText, newTokens } = sanitized;
925
825
  const telemetry = buildCisoAuditTelemetry(newTokens);
926
826
  const receiptMarkdown = formatAuditReceipt(telemetry);
@@ -928,8 +828,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
928
828
 
929
829
  return {
930
830
  content: [
931
- { type: "text", text: combinedOutput },
932
- ...extraBlocks.filter(Boolean)
831
+ { type: "text", text: combinedOutput }
933
832
  ]
934
833
  };
935
834
  } catch (xlsxError) {
@@ -967,9 +866,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
967
866
  const targetProfile = profile.trim();
968
867
  const isAdvanced = targetProfile.toLowerCase() !== "general";
969
868
  const license = checkLicenseStatus();
970
-
971
869
  let finalProfile = targetProfile;
972
- const extraBlocks = [];
973
870
 
974
871
  const limitStatus = checkFreeTierLimit(license.isPro);
975
872
  if (limitStatus.blocked) {
@@ -981,23 +878,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
981
878
 
982
879
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
983
880
  if (isAdvanced && !license.isPro) {
984
- 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`);
985
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
881
+ mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n`);
986
882
  }
987
883
 
988
- const { processedText: processedContent2, wasTruncated: wasTruncated2 } = truncateIfFree(content, license.isPro, charLimit);
989
- if (wasTruncated2) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
990
-
991
- if (!license.isPro) {
992
- const detected = detectSecrets(processedContent2);
993
- if (detected.length > 0) {
994
- 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`);
995
- return {
996
- isError: true,
997
- 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` }]
998
- };
999
- }
1000
- }
884
+ const { processedText: processedContent2 } = truncateIfFree(content, license.isPro, charLimit);
1001
885
 
1002
886
  const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile, sessionIgnoreList);
1003
887
  const telemetry = buildCisoAuditTelemetry(newTokens);
@@ -1005,8 +889,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1005
889
 
1006
890
  return {
1007
891
  content: [
1008
- { type: "text", text: scrubbedText + receiptMd },
1009
- ...extraBlocks.filter(Boolean)
892
+ { type: "text", text: scrubbedText + receiptMd }
1010
893
  ]
1011
894
  };
1012
895
  }
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": "1.7.1",
4
+ "version": "2.0.3",
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.0.2",
3
+ "version": "2.0.3",
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",
@@ -69,38 +69,20 @@ const DEVOPS_SECRETS_DETECTOR = [
69
69
  { 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 }
70
70
  ];
71
71
 
72
- const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
73
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy1jZ9X4rO5C9U0qK57V1
74
- hJoYSkE4vLpcVYOt1FhRwEHs1APDSyss0HixboLz2eW2XQf2NbwajWtNlyxvgczO
75
- KE6ClnLomtsaKywwqB4alzdYnnnFJttFPjwmgPSO7D9AgN9sYaVkXOaOFrIZ90Ng
76
- TRhSHUeL7ReltWlCHwz9xf5m2FrKtxr2VBlEoyPjsFzalHMey1EX+yXe81zM7IIi
77
- t1Z8agLzo7WIfNBAIWmRlerTplaFFZrQgdF5g/Y0n8IIMZOtadgoY8E855psDNZV
78
- 7wIDAQAB
79
- -----END PUBLIC KEY-----`;
72
+ let LicenseManager = require('./ps-license-manager.js');
73
+ if (!LicenseManager || typeof LicenseManager.validate !== 'function') {
74
+ LicenseManager = global.LicenseManager;
75
+ }
80
76
 
81
77
  function checkLicenseStatus() {
82
78
  const key = (process.env.PRIVACYSCRUBBER_KEY || "").trim();
83
- if (!key) return { isPro: false, error: "No license key provided." };
84
-
85
- try {
86
- const [payloadBase64, signatureBase64] = key.split('.');
87
- const verifier = crypto.createVerify('SHA256');
88
- verifier.update(payloadBase64);
89
- const isVerified = verifier.verify(PUBLIC_KEY, signatureBase64, 'base64');
90
-
91
- if (!isVerified) return { isPro: false, error: "Signature verification failed." };
92
-
93
- const payload = JSON.parse(Buffer.from(payloadBase64, 'base64').toString('utf8'));
94
- if (payload.expires && payload.expires < Math.floor(Date.now() / 1000)) {
95
- return {
96
- isPro: false,
97
- error: `License expired on ${new Date(payload.expires * 1000).toLocaleDateString()}`
98
- };
99
- }
100
- return { isPro: true, error: null };
101
- } catch (e) {
102
- return { isPro: false, error: "Error parsing license: " + e.message };
79
+ if (!key) return { isPro: false, type: null, error: "No license key provided." };
80
+
81
+ const result = LicenseManager.validate(key);
82
+ if (!result.valid) {
83
+ return { isPro: false, type: null, error: result.reason || "Invalid license format or signature." };
103
84
  }
85
+ return { isPro: true, type: result.tier, error: null };
104
86
  }
105
87
 
106
88
  function resolveConfigPath() {