@privacyscrubber/mcp-server 2.0.0 → 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,25 +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.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`);
595
- return {
596
- isError: true,
597
- 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` }]
598
- };
599
- }
600
- }
577
+ const { processedText } = truncateIfFree(text, license.isPro, charLimit);
601
578
 
602
579
  const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, sessionIgnoreList);
603
580
 
@@ -606,8 +583,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
606
583
 
607
584
  return {
608
585
  content: [
609
- { type: "text", text: scrubbedText + receiptMd },
610
- ...extraBlocks.filter(Boolean)
586
+ { type: "text", text: scrubbedText + receiptMd }
611
587
  ]
612
588
  };
613
589
  }
@@ -748,9 +724,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
748
724
  const targetProfile = profile.trim();
749
725
  const isAdvanced = targetProfile.toLowerCase() !== "general";
750
726
  const license = checkLicenseStatus();
751
-
752
727
  let finalProfile = targetProfile;
753
- const extraBlocks = [];
754
728
 
755
729
  const limitStatus = checkFreeTierLimit(license.isPro);
756
730
  if (limitStatus.blocked) {
@@ -762,23 +736,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
762
736
 
763
737
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
764
738
  if (isAdvanced && !license.isPro) {
765
- 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`);
766
- 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`);
767
740
  }
768
741
 
769
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
770
- if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
771
-
772
- if (!license.isPro) {
773
- const detected = detectSecrets(processedContent);
774
- if (detected.length > 0) {
775
- 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`);
776
- return {
777
- isError: true,
778
- 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` }]
779
- };
780
- }
781
- }
742
+ const { processedText: processedContent } = truncateIfFree(content, license.isPro, charLimit);
782
743
 
783
744
  const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
784
745
  const { scrubbedText, newTokens } = sanitized;
@@ -788,8 +749,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
788
749
 
789
750
  return {
790
751
  content: [
791
- { type: "text", text: combinedOutput },
792
- ...extraBlocks.filter(Boolean)
752
+ { type: "text", text: combinedOutput }
793
753
  ]
794
754
  };
795
755
  } catch (docxError) {
@@ -813,7 +773,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
813
773
  content: [
814
774
  {
815
775
  type: "text",
816
- 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.`
817
777
  }
818
778
  ]
819
779
  };
@@ -825,40 +785,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
825
785
  const data = await pdf(buffer);
826
786
  const content = data.text;
827
787
  const targetProfile = profile.trim();
828
- const isAdvanced = targetProfile.toLowerCase() !== "general";
829
-
830
788
  let finalProfile = targetProfile;
831
- const extraBlocks = [];
832
-
833
- const limitStatus = checkFreeTierLimit(license.isPro);
834
- if (limitStatus.blocked) {
835
- return {
836
- isError: true,
837
- 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` }]
838
- };
839
- }
840
-
841
- const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
842
- if (isAdvanced && !license.isPro) {
843
- 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`);
844
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
845
- }
846
789
 
847
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
848
- if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
849
-
850
- if (!license.isPro) {
851
- const detected = detectSecrets(processedContent);
852
- if (detected.length > 0) {
853
- 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`);
854
- return {
855
- isError: true,
856
- 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` }]
857
- };
858
- }
859
- }
860
-
861
- const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
790
+ const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
862
791
  const { scrubbedText, newTokens } = sanitized;
863
792
  const telemetry = buildCisoAuditTelemetry(newTokens);
864
793
  const receiptMarkdown = formatAuditReceipt(telemetry);
@@ -866,8 +795,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
866
795
 
867
796
  return {
868
797
  content: [
869
- { type: "text", text: combinedOutput },
870
- ...extraBlocks.filter(Boolean)
798
+ { type: "text", text: combinedOutput }
871
799
  ]
872
800
  };
873
801
  } catch (pdfError) {
@@ -890,40 +818,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
890
818
  });
891
819
 
892
820
  const targetProfile = profile.trim();
893
- const isAdvanced = targetProfile.toLowerCase() !== "general";
894
-
895
821
  let finalProfile = targetProfile;
896
- const extraBlocks = [];
897
-
898
- const limitStatus = checkFreeTierLimit(license.isPro);
899
- if (limitStatus.blocked) {
900
- return {
901
- isError: true,
902
- 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` }]
903
- };
904
- }
905
822
 
906
- const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
907
- if (isAdvanced && !license.isPro) {
908
- 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`);
909
- extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
910
- }
911
-
912
- const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
913
- if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
914
-
915
- if (!license.isPro) {
916
- const detected = detectSecrets(processedContent);
917
- if (detected.length > 0) {
918
- 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`);
919
- return {
920
- isError: true,
921
- 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` }]
922
- };
923
- }
924
- }
925
-
926
- const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
823
+ const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
927
824
  const { scrubbedText, newTokens } = sanitized;
928
825
  const telemetry = buildCisoAuditTelemetry(newTokens);
929
826
  const receiptMarkdown = formatAuditReceipt(telemetry);
@@ -931,8 +828,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
931
828
 
932
829
  return {
933
830
  content: [
934
- { type: "text", text: combinedOutput },
935
- ...extraBlocks.filter(Boolean)
831
+ { type: "text", text: combinedOutput }
936
832
  ]
937
833
  };
938
834
  } catch (xlsxError) {
@@ -970,9 +866,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
970
866
  const targetProfile = profile.trim();
971
867
  const isAdvanced = targetProfile.toLowerCase() !== "general";
972
868
  const license = checkLicenseStatus();
973
-
974
869
  let finalProfile = targetProfile;
975
- const extraBlocks = [];
976
870
 
977
871
  const limitStatus = checkFreeTierLimit(license.isPro);
978
872
  if (limitStatus.blocked) {
@@ -984,23 +878,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
984
878
 
985
879
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
986
880
  if (isAdvanced && !license.isPro) {
987
- 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`);
988
- 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`);
989
882
  }
990
883
 
991
- const { processedText: processedContent2, wasTruncated: wasTruncated2 } = truncateIfFree(content, license.isPro, charLimit);
992
- if (wasTruncated2) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
993
-
994
- if (!license.isPro) {
995
- const detected = detectSecrets(processedContent2);
996
- if (detected.length > 0) {
997
- 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`);
998
- return {
999
- isError: true,
1000
- 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` }]
1001
- };
1002
- }
1003
- }
884
+ const { processedText: processedContent2 } = truncateIfFree(content, license.isPro, charLimit);
1004
885
 
1005
886
  const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile, sessionIgnoreList);
1006
887
  const telemetry = buildCisoAuditTelemetry(newTokens);
@@ -1008,8 +889,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1008
889
 
1009
890
  return {
1010
891
  content: [
1011
- { type: "text", text: scrubbedText + receiptMd },
1012
- ...extraBlocks.filter(Boolean)
892
+ { type: "text", text: scrubbedText + receiptMd }
1013
893
  ]
1014
894
  };
1015
895
  }
@@ -1159,8 +1039,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1159
1039
  const tier = license.isPro ? 'PRO' : 'FREE';
1160
1040
  const tierIcon = license.isPro ? '✅' : '🔓';
1161
1041
  const profileList = license.isPro
1162
- ? 'All 23 profiles active (General, Dev, Medical, Legal, Finance, HR…)'
1163
- : 'General only — PRO unlocks 22 industry profiles';
1042
+ ? 'All 25 profiles active (General, Dev, Medical, Legal, Finance, HR…)'
1043
+ : 'General only — PRO unlocks 25 industry profiles';
1164
1044
  const sizeLimit = license.isPro ? 'Unlimited' : '15,000 characters per request';
1165
1045
 
1166
1046
  const configPath = resolveConfigPath();
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.0",
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() {