@privacyscrubber/mcp-server 2.0.2 β†’ 2.1.0

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
@@ -2,8 +2,16 @@
2
2
 
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
+ [![NPM SDK](https://img.shields.io/npm/v/@privacyscrubber/sdk?label=%40privacyscrubber%2Fsdk&color=10b981)](https://www.npmjs.com/package/@privacyscrubber/sdk)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22058770.svg)](https://zenodo.org/records/22058770)
8
+ [![OSF DOI](https://img.shields.io/badge/OSF%20DOI-10.17605%2FOSF.IO%2F5BYJF-blue.svg)](https://osf.io/5byjf/)
9
+ [![SSRN](https://img.shields.io/badge/SSRN-7335581-darkred.svg)](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7335581)
6
10
  [![smithery badge](https://smithery.ai/badge/privacyscrubber/pii-masking-mcp)](https://smithery.ai/servers/privacyscrubber/pii-masking-mcp)
11
+ [![Cursor Directory](https://img.shields.io/badge/Cursor%20Directory-Verified%20Plugin-000000.svg)](https://cursor.directory/plugins/privacyscrubber-mcp)
12
+ [![Glama.ai](https://glama.ai/mcp/servers/moxno/privacyscrubber-mcp/badge)](https://glama.ai/mcp/servers/moxno/privacyscrubber-mcp)
13
+ [![TensorBlock MCP Index](https://img.shields.io/badge/TensorBlock-Indexed%20MCP-FF6B6B.svg)](https://github.com/TensorBlock/awesome-mcp-servers)
14
+ [![There's An AI For That](https://img.shields.io/badge/There's_An_AI_For_That-Live-06b6d4.svg)](https://theresanaiforthat.com/ai/privacy-scrubber/)
7
15
  [![Security: 100% Local](https://img.shields.io/badge/Security-100%25%20Local-emerald)](https://privacyscrubber.com)
8
16
  [![Parity: 100% Core Match](https://img.shields.io/badge/Parity-100%25%20Core%20Match-blueviolet)](https://privacyscrubber.com)
9
17
 
@@ -40,6 +48,23 @@ Run the server directly without local installation:
40
48
  npx -y @privacyscrubber/mcp-server
41
49
  ```
42
50
 
51
+ ### 3. Programmatic Node.js / TypeScript SDK
52
+ Need direct, in-memory zero-trust PII sanitization in your backend microservice or custom AI agent rather than an MCP server? Use our official zero-dependency SDK:
53
+
54
+ ```bash
55
+ npm install @privacyscrubber/sdk
56
+ ```
57
+
58
+ ```javascript
59
+ import OpenAI from 'openai';
60
+ import { wrapOpenAI } from '@privacyscrubber/sdk';
61
+
62
+ // Transparently masks PII before sending to LLM and rehydrates responses:
63
+ const openai = wrapOpenAI(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
64
+ ```
65
+
66
+ πŸ‘‰ [View @privacyscrubber/sdk on NPM](https://www.npmjs.com/package/@privacyscrubber/sdk) | Includes `wrapOpenAI()` middleware, TypeScript definitions, and 25 compliance profiles.
67
+
43
68
  ---
44
69
 
45
70
  ## βš™οΈ Client Integrations
@@ -148,11 +173,11 @@ Reads a local file, extracts text, sanitizes it, and returns the redacted templa
148
173
 
149
174
  Looking for real-time protection directly inside your web browser?
150
175
  * **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/).
176
+ * **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
177
 
153
178
  ## πŸ“„ License & Commercial Upgrade
154
179
 
155
- By default, the server runs under the **Free Tier** (restricted to 50,000 characters per request and the basic `General` PII profile). To unlock advanced engineering, medical, legal, and financial PII profiles, as well as team-wide custom rules, you can purchase a commercial license.
180
+ By default, the server runs under the **Free Tier** (restricted to 15,000 characters per request and the basic `General` PII profile). To unlock 25 specialized engineering, medical, legal, and financial PII profiles, as well as team-wide custom rules, you can purchase a commercial license.
156
181
 
157
182
  ### Feature Comparison
158
183
 
@@ -160,13 +185,13 @@ By default, the server runs under the **Free Tier** (restricted to 50,000 charac
160
185
  | :--- | :--- | :--- | :--- |
161
186
  | **Volatile Tokenization** | βœ… Yes | βœ… Yes | βœ… Yes |
162
187
  | **Standard PII Masking** | βœ… Yes | βœ… Yes | βœ… Yes |
163
- | **Max Character Length** | 50,000 chars | ♾️ Unlimited | ♾️ Unlimited |
164
- | **Industry Profiles** | General Only | 22+ Profiles | 22+ Profiles |
188
+ | **Max Character Length** | 15,000 chars | ♾️ Unlimited | ♾️ Unlimited |
189
+ | **Industry Profiles** | General Only | 25 Profiles | 25 Profiles |
165
190
  | **Custom Regex Rules** | ❌ Locked | ♾️ Unlimited | ♾️ Unlimited |
166
191
  | **Team Rules Sync (GPO)** | ❌ No | ❌ No | βœ… Yes (Shared Link) |
167
192
  | **Licensing Cost** | $0 | **$110 Lifetime** | **$99/mo Flat Rate** |
168
193
 
169
- πŸ‘‰ **[Acquire a PRO / TEAMS License Key at privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)**
194
+ πŸ‘‰ **[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
195
 
171
196
  ---
172
197
 
@@ -185,22 +210,22 @@ Returns a visual dashboard showing your current tier, session request count, act
185
210
  * **Response Example (Free Tier):**
186
211
  ```
187
212
  ╔══════════════════════════════════════════════════╗
188
- β•‘ PrivacyScrubber MCP Server v1.6.6 β•‘
213
+ β•‘ PrivacyScrubber MCP Server v1.7.1 β•‘
189
214
  ╠══════════════════════════════════════════════════╣
190
215
  β•‘ πŸ”“ Tier: FREE β•‘
191
216
  β•‘ πŸ“Š Session requests: 5 β•‘
192
- β•‘ πŸ“ Input size limit: 50,000 characters per requestβ•‘
217
+ β•‘ πŸ“ Input size limit: 15,000 characters/request β•‘
193
218
  ╠══════════════════════════════════════════════════╣
194
- β•‘ 🏷️ Profiles: General only β€” PRO unlocks 22 more β•‘
219
+ β•‘ 🏷️ Profiles: General only β€” PRO unlocks 25 more β•‘
195
220
  β•‘ πŸ“‹ Custom rules: πŸ”’ Locked β€” requires PRO β•‘
196
221
  ╠══════════════════════════════════════════════════╣
197
222
  β•‘ πŸ’³ Upgrade to PRO β€” $110 Lifetime β•‘
198
- β•‘ https://privacyscrubber.com/pricing β•‘
223
+ β•‘ https://privacyscrubber.com/pricing?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server β•‘
199
224
  ╠══════════════════════════════════════════════════╣
200
225
  β•‘ After purchase, add your key to MCP config: β•‘
201
226
  β•‘ "PRIVACYSCRUBBER_KEY": "<your-key-here>" β•‘
202
227
  β•‘ Full setup guide: β•‘
203
- β•‘ https://privacyscrubber.com/features/mcp/ β•‘
228
+ β•‘ https://privacyscrubber.com/features/mcp/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server β•‘
204
229
  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
205
230
  ```
206
231
 
@@ -208,7 +233,7 @@ Returns a visual dashboard showing your current tier, session request count, act
208
233
 
209
234
  ## πŸ” After Purchase: Activate PRO in Your MCP Client
210
235
 
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`.
236
+ 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
237
 
213
238
  ### Claude Desktop
214
239
 
@@ -271,3 +296,36 @@ Use the check_status tool from PrivacyScrubber MCP
271
296
 
272
297
  The dashboard should show **Tier: PRO** and all profiles unlocked.
273
298
 
299
+ ---
300
+
301
+ ## πŸ“š Academic Foundations & Regulatory Verification
302
+
303
+ PrivacyScrubber and the Zero-Trust Data Sanitization (ZTDS) protocol are backed by published scientific, clinical, and legal treatises:
304
+
305
+ | Repository / Archive | DOI / Identifier | Focus Area | Regulatory & Compliance Scope |
306
+ |---|---|---|---|
307
+ | **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 |
308
+ | **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 |
309
+ | **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 |
310
+ | **medRxiv (Cold Spring Harbor)** | [`MEDRXIV/2026/361661`](https://submit.medrxiv.org/) | Multi-Center Clinical Trial De-Identification | HIPAA Safe Harbor Section 164.514(b) |
311
+ | **Law Archive / OSF** | [`LawArchive ID: 4wc86`](https://osf.io/preprints/lawarchive/4wc86/) | Preserving Attorney-Client Privilege in AI Workflows | ABA Model Rules & Legal Ethics |
312
+
313
+ ### Citing PrivacyScrubber in Research & Audits
314
+ ```bibtex
315
+ @software{sibiryakov2026privacyscrubber,
316
+ author = {Sibiryakov, Ilya},
317
+ title = {PrivacyScrubber: Zero-Trust Data Sanitization (ZTDS) Engine & MCP Server},
318
+ year = {2026},
319
+ publisher = {Zenodo},
320
+ doi = {10.5281/zenodo.22058770},
321
+ url = {https://github.com/moxno/privacyscrubber-mcp}
322
+ }
323
+ ```
324
+
325
+ ---
326
+
327
+ ## πŸ“„ License
328
+
329
+ MIT Β© [Ilya Sibiryakov](https://privacyscrubber.com) (BrandMeWeb)
330
+
331
+
package/bundle.mcpb ADDED
Binary file
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;
@@ -142,6 +144,9 @@ function checkFreeTierLimit(isPro) {
142
144
 
143
145
  const count = incrementDailyUsage();
144
146
  // Feedback / Growth loop triggers
147
+ if (count === 2) {
148
+ mcpLog(`${colors.cyan}πŸ› οΈ [PrivacyScrubber] Need programmatic in-code redaction in your Node/TS backend or RAG pipeline? Try our official SDK: npm install @privacyscrubber/sdk${colors.reset}\n`);
149
+ }
145
150
  if (count === 3) {
146
151
  mcpLog(`${colors.cyan}πŸ’¬ We are building the ultimate privacy tool for developers. What feature should we add next? Let us know: https://privacyscrubber.com/feedback${colors.reset}\n`);
147
152
  }
@@ -230,16 +235,6 @@ function formatAuditReceipt(telemetry) {
230
235
  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
236
  }
232
237
 
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
238
  // Create the MCP server
244
239
  const server = new Server(
245
240
  {
@@ -262,7 +257,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
262
257
  tools: [
263
258
  {
264
259
  name: "sanitize_text",
265
- description: "STEP 1: Call this first. You MUST NOT process raw user data before calling this. Locally scrubs PII, secrets, and credentials (like API keys, passwords, emails, phones, names) from code, logs, or text. Replaces them with safe placeholders (e.g., [EMAIL_1], [API_KEY_1]). Keep your data secure before passing it to any LLM.",
260
+ description: "STEP 1: Call this first. You MUST NOT process raw user data before calling this. Locally scrubs PII, secrets, and credentials (like API keys, passwords, emails, phones, names) from code, logs, or text. Replaces them with safe placeholders (e.g., [EMAIL_1], [API_KEY_1]). Keep your data secure before passing it to any LLM. (For in-code backend services or RAG vector pipelines outside of MCP, use '@privacyscrubber/sdk': npm i @privacyscrubber/sdk)",
266
261
  inputSchema: {
267
262
  type: "object",
268
263
  properties: {
@@ -540,6 +535,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
540
535
  // Handle tool execution calls
541
536
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
542
537
  const { name, arguments: args } = request.params;
538
+ const toolStart = Date.now();
543
539
 
544
540
  try {
545
541
  if (name === "sanitize_text") {
@@ -567,7 +563,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
567
563
  const isAdvanced = targetProfile.toLowerCase() !== "general";
568
564
  const license = checkLicenseStatus();
569
565
  let finalProfile = targetProfile;
570
- const extraBlocks = [];
571
566
 
572
567
  const limitStatus = checkFreeTierLimit(license.isPro);
573
568
  if (limitStatus.blocked) {
@@ -579,22 +574,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
579
574
 
580
575
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
581
576
  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.`));
577
+ mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n`);
584
578
  }
585
579
 
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
- }
580
+ const { processedText } = truncateIfFree(text, license.isPro, charLimit);
598
581
 
599
582
  const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, sessionIgnoreList);
600
583
 
@@ -603,8 +586,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
603
586
 
604
587
  return {
605
588
  content: [
606
- { type: "text", text: scrubbedText + receiptMd },
607
- ...extraBlocks.filter(Boolean)
589
+ { type: "text", text: scrubbedText + receiptMd }
608
590
  ]
609
591
  };
610
592
  }
@@ -745,9 +727,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
745
727
  const targetProfile = profile.trim();
746
728
  const isAdvanced = targetProfile.toLowerCase() !== "general";
747
729
  const license = checkLicenseStatus();
748
-
749
730
  let finalProfile = targetProfile;
750
- const extraBlocks = [];
751
731
 
752
732
  const limitStatus = checkFreeTierLimit(license.isPro);
753
733
  if (limitStatus.blocked) {
@@ -759,23 +739,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
759
739
 
760
740
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
761
741
  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.`));
742
+ mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n`);
764
743
  }
765
744
 
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
- }
745
+ const { processedText: processedContent } = truncateIfFree(content, license.isPro, charLimit);
779
746
 
780
747
  const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
781
748
  const { scrubbedText, newTokens } = sanitized;
@@ -785,8 +752,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
785
752
 
786
753
  return {
787
754
  content: [
788
- { type: "text", text: combinedOutput },
789
- ...extraBlocks.filter(Boolean)
755
+ { type: "text", text: combinedOutput }
790
756
  ]
791
757
  };
792
758
  } catch (docxError) {
@@ -810,7 +776,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
810
776
  content: [
811
777
  {
812
778
  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`
779
+ 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
780
  }
815
781
  ]
816
782
  };
@@ -822,40 +788,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
822
788
  const data = await pdf(buffer);
823
789
  const content = data.text;
824
790
  const targetProfile = profile.trim();
825
- const isAdvanced = targetProfile.toLowerCase() !== "general";
826
-
827
791
  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
792
 
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
-
858
- const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
793
+ const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
859
794
  const { scrubbedText, newTokens } = sanitized;
860
795
  const telemetry = buildCisoAuditTelemetry(newTokens);
861
796
  const receiptMarkdown = formatAuditReceipt(telemetry);
@@ -863,8 +798,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
863
798
 
864
799
  return {
865
800
  content: [
866
- { type: "text", text: combinedOutput },
867
- ...extraBlocks.filter(Boolean)
801
+ { type: "text", text: combinedOutput }
868
802
  ]
869
803
  };
870
804
  } catch (pdfError) {
@@ -887,40 +821,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
887
821
  });
888
822
 
889
823
  const targetProfile = profile.trim();
890
- const isAdvanced = targetProfile.toLowerCase() !== "general";
891
-
892
824
  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
825
 
923
- const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
826
+ const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
924
827
  const { scrubbedText, newTokens } = sanitized;
925
828
  const telemetry = buildCisoAuditTelemetry(newTokens);
926
829
  const receiptMarkdown = formatAuditReceipt(telemetry);
@@ -928,8 +831,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
928
831
 
929
832
  return {
930
833
  content: [
931
- { type: "text", text: combinedOutput },
932
- ...extraBlocks.filter(Boolean)
834
+ { type: "text", text: combinedOutput }
933
835
  ]
934
836
  };
935
837
  } catch (xlsxError) {
@@ -967,9 +869,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
967
869
  const targetProfile = profile.trim();
968
870
  const isAdvanced = targetProfile.toLowerCase() !== "general";
969
871
  const license = checkLicenseStatus();
970
-
971
872
  let finalProfile = targetProfile;
972
- const extraBlocks = [];
973
873
 
974
874
  const limitStatus = checkFreeTierLimit(license.isPro);
975
875
  if (limitStatus.blocked) {
@@ -981,23 +881,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
981
881
 
982
882
  const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
983
883
  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.`));
884
+ mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n`);
986
885
  }
987
886
 
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
- }
887
+ const { processedText: processedContent2 } = truncateIfFree(content, license.isPro, charLimit);
1001
888
 
1002
889
  const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile, sessionIgnoreList);
1003
890
  const telemetry = buildCisoAuditTelemetry(newTokens);
@@ -1005,8 +892,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1005
892
 
1006
893
  return {
1007
894
  content: [
1008
- { type: "text", text: scrubbedText + receiptMd },
1009
- ...extraBlocks.filter(Boolean)
895
+ { type: "text", text: scrubbedText + receiptMd }
1010
896
  ]
1011
897
  };
1012
898
  }
@@ -1524,8 +1410,9 @@ function truncateIfFree(text, isPro, charLimit = 15000) {
1524
1410
  // Start the server transport
1525
1411
  const transport = new StdioServerTransport();
1526
1412
  server.connect(transport).then(() => {
1527
- mcpLog(`${colors.greenBold}βœ… PrivacyScrubber ZTDS MCP Server started successfully.${colors.reset}\n`);
1528
- mcpLog(`${colors.cyan}⭐ Star us on GitHub: https://github.com/moxno/privacyscrubber-mcp${colors.reset}\n`);
1413
+ mcpLog(`${colors.greenBold}βœ… PrivacyScrubber ZTDS MCP Server v${MCP_VERSION} started successfully.${colors.reset}\n`);
1414
+ mcpLog(`${colors.cyan}πŸ“¦ Need programmatic in-code redaction? Try: npm install @privacyscrubber/sdk${colors.reset}\n`);
1415
+ mcpLog(`${colors.yellowBold}⭐ Star us on GitHub: https://github.com/moxno/privacyscrubber-mcp${colors.reset}\n`);
1529
1416
  }).catch((error) => {
1530
1417
  console.error("Failed to connect MCP server transport:", error);
1531
1418
  process.exit(1);
package/manifest.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "manifest_version": "0.3",
3
3
  "name": "privacyscrubber/pii-masking-mcp",
4
- "version": "1.7.1",
4
+ "version": "2.1.0",
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",
8
- "email": "legal@privacyscrubber.com"
8
+ "email": "support@privacyscrubber.com"
9
9
  },
10
10
  "server": {
11
11
  "type": "node",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privacyscrubber/mcp-server",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
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() {