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