@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 +69 -11
- package/bundle.mcpb +0 -0
- package/index.js +24 -137
- package/manifest.json +2 -2
- package/package.json +1 -1
- package/pii-masking-run.js +10 -28
- package/ps-pii-engine.cjs +387 -160
- package/ps-pii-engine.js +387 -160
- package/scrubber-core.cjs +2 -2
- package/server.json +3 -3
- package/.well-known/mcp/server-card.json +0 -78
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@privacyscrubber/mcp-server)
|
|
4
4
|
[](https://www.npmjs.com/package/@privacyscrubber/mcp-server)
|
|
5
|
+
[](https://www.npmjs.com/package/@privacyscrubber/sdk)
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://zenodo.org/records/22058770)
|
|
8
|
+
[](https://osf.io/5byjf/)
|
|
9
|
+
[](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7335581)
|
|
6
10
|
[](https://smithery.ai/servers/privacyscrubber/pii-masking-mcp)
|
|
11
|
+
[](https://cursor.directory/plugins/privacyscrubber-mcp)
|
|
12
|
+
[](https://glama.ai/mcp/servers/moxno/privacyscrubber-mcp)
|
|
13
|
+
[](https://github.com/TensorBlock/awesome-mcp-servers)
|
|
14
|
+
[](https://theresanaiforthat.com/ai/privacy-scrubber/)
|
|
7
15
|
[](https://privacyscrubber.com)
|
|
8
16
|
[](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
|
|
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** |
|
|
164
|
-
| **Industry Profiles** | General Only |
|
|
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.
|
|
213
|
+
β PrivacyScrubber MCP Server v1.7.1 β
|
|
189
214
|
β βββββββββββββββββββββββββββββββββββββββββββββββββββ£
|
|
190
215
|
β π Tier: FREE β
|
|
191
216
|
β π Session requests: 5 β
|
|
192
|
-
β π Input size limit:
|
|
217
|
+
β π Input size limit: 15,000 characters/request β
|
|
193
218
|
β βββββββββββββββββββββββββββββββββββββββββββββββββββ£
|
|
194
|
-
β π·οΈ Profiles: General only β PRO unlocks
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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}
|
|
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.
|
|
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": "
|
|
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
|
|
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",
|
package/pii-masking-run.js
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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() {
|