@privacyscrubber/mcp-server 1.0.1 ā 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.well-known/mcp/server-card.json +60 -0
- package/LICENSE +21 -0
- package/README.md +27 -43
- package/bundle.mcpb +0 -0
- package/index.js +85 -8
- package/manifest.json +28 -0
- package/package.json +26 -4
- package/server.json +29 -0
- package/smithery.yaml +12 -0
- package/test-mcp.js +0 -114
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"serverInfo": {
|
|
3
|
+
"name": "privacyscrubber/pii-masking-mcp",
|
|
4
|
+
"version": "1.0.3"
|
|
5
|
+
},
|
|
6
|
+
"tools": [
|
|
7
|
+
{
|
|
8
|
+
"name": "sanitize_text",
|
|
9
|
+
"description": "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.",
|
|
10
|
+
"inputSchema": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"text": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "The raw text, code, or logs to sanitize."
|
|
16
|
+
},
|
|
17
|
+
"profile": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "The detection profile to use. Available: 'General' (Free), or PRO profiles. Defaults to 'General'."
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": ["text"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "reveal_text",
|
|
27
|
+
"description": "Replaces masked tokens (e.g., [EMAIL_1], [API_KEY_1]) in the LLM's response back with the original private data from the local volatile RAM-only session map.",
|
|
28
|
+
"inputSchema": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"text": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "The AI generated response containing placeholders to restore."
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"required": ["text"]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "sanitize_file",
|
|
41
|
+
"description": "Reads a local file, sanitizes its contents using the selected profile, and outputs the safe version for AI analysis. Securely keeps original identifiers in memory.",
|
|
42
|
+
"inputSchema": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"file_path": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Absolute path to the file to sanitize."
|
|
48
|
+
},
|
|
49
|
+
"profile": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "The detection profile to use. Available: 'General' (Free), or PRO profiles. Defaults to 'General'."
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"required": ["file_path"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"resources": [],
|
|
59
|
+
"prompts": []
|
|
60
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ilya Sibiryakov (BrandMeWeb)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,35 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# PrivacyScrubber MCP Server v1.0.3
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Locally scrubs PII, secrets, credentials, and custom patterns from files and text contexts before sending them to LLMs.
|
|
3
|
+
Zero-Trust Data Sanitization (ZTDS) Model Context Protocol (MCP) server for local PII and secrets masking.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
## What's New in v1.0.3
|
|
6
|
+
- **Robust Configuration Parsing**: Protected Smithery integration hooks from undefined configuration objects, ensuring reliable initialization across all MCP clients (Claude Desktop, Cursor, Windsurf).
|
|
7
|
+
- **Microsoft Word (.docx) Support**: Enhanced the `sanitize_file` tool to parse and redact sensitive information from Word documents locally.
|
|
8
|
+
- **Unified Branding & Metadata**: Corrected repository fields and aligned server identity across NPM, Smithery, and Glama registries.
|
|
9
|
+
- **Enhanced Scanner Discovery**: Implemented static `server-card.json` configurations to support automated registry scanning.
|
|
7
10
|
|
|
8
|
-
##
|
|
11
|
+
## Key Features & Security Guarantees
|
|
12
|
+
- **100% Local Processing**: All regex scanning, PII tokenization, and reverse-scrubbing occur directly in your machine's RAM.
|
|
13
|
+
- **Airplane Mode Verified**: Fully operational without an internet connection after the initial download.
|
|
14
|
+
- **Zero Server Logs**: No data, credentials, or prompts are sent to external APIs or remote databases.
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* **Support for Reverse-Scrubbing (Reveal):** Replaces tokens in LLM responses back with original values securely inside your local context.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## š Installation & Usage
|
|
17
|
-
|
|
18
|
-
### 1. Instant Run with NPX
|
|
19
|
-
You can run the MCP server directly using `npx`:
|
|
16
|
+
## Quick Start
|
|
17
|
+
Run the server instantly without local installation:
|
|
20
18
|
```bash
|
|
21
|
-
npx @privacyscrubber/mcp-server
|
|
19
|
+
npx -y @privacyscrubber/mcp-server
|
|
22
20
|
```
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
## āļø Client Integrations
|
|
22
|
+
## Client Integration Examples
|
|
27
23
|
|
|
28
24
|
### Claude Desktop
|
|
29
|
-
Add
|
|
30
|
-
* **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
31
|
-
* **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
32
|
-
|
|
25
|
+
Add to your `claude_desktop_config.json`:
|
|
33
26
|
```json
|
|
34
27
|
{
|
|
35
28
|
"mcpServers": {
|
|
@@ -37,7 +30,7 @@ Add the server configuration to your Claude Desktop config file:
|
|
|
37
30
|
"command": "npx",
|
|
38
31
|
"args": ["-y", "@privacyscrubber/mcp-server"],
|
|
39
32
|
"env": {
|
|
40
|
-
"PRIVACYSCRUBBER_KEY": "
|
|
33
|
+
"PRIVACYSCRUBBER_KEY": "YOUR_OPTIONAL_PRO_LICENSE_KEY"
|
|
41
34
|
}
|
|
42
35
|
}
|
|
43
36
|
}
|
|
@@ -45,22 +38,13 @@ Add the server configuration to your Claude Desktop config file:
|
|
|
45
38
|
```
|
|
46
39
|
|
|
47
40
|
### Cursor / Windsurf
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
1. `sanitize_text` ā Sanitizes a raw string using the selected detection profile.
|
|
60
|
-
2. `reveal_text` ā Detokenizes a response containing PII tokens back to the original text.
|
|
61
|
-
3. `sanitize_file` ā Reads a local file, sanitizes its contents, and outputs the safe version (supports up to 10MB).
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## š License & Commercial Key
|
|
66
|
-
Standard use includes the **Free Tier** (limits to the `General` PII profile). To unlock 22+ specialized industry profiles (DevOps, Medical, Legal, Finance) and custom regex rules, acquire a license at [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing).
|
|
41
|
+
Add a new command-type MCP server:
|
|
42
|
+
- **Name**: `privacyscrubber`
|
|
43
|
+
- **Command**: `npx -y @privacyscrubber/mcp-server`
|
|
44
|
+
|
|
45
|
+
## Useful Links
|
|
46
|
+
- **Official Website**: https://privacyscrubber.com
|
|
47
|
+
- **Chrome Web Store Extension**: https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol
|
|
48
|
+
- **MCP Documentation**: https://privacyscrubber.com/pii-mcp/
|
|
49
|
+
- **Pricing & Licensing**: https://privacyscrubber.com/pricing/
|
|
50
|
+
- **GitHub Repository**: https://github.com/moxno/privacyscrubber-mcp
|
package/bundle.mcpb
ADDED
|
Binary file
|
package/index.js
CHANGED
|
@@ -83,8 +83,8 @@ function checkLicenseStatus() {
|
|
|
83
83
|
// Create the MCP server
|
|
84
84
|
const server = new Server(
|
|
85
85
|
{
|
|
86
|
-
name: "privacyscrubber-mcp",
|
|
87
|
-
version: "1.0.
|
|
86
|
+
name: "privacyscrubber/pii-masking-mcp",
|
|
87
|
+
version: "1.0.3",
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
capabilities: {
|
|
@@ -135,7 +135,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
135
135
|
inputSchema: {
|
|
136
136
|
type: "object",
|
|
137
137
|
properties: {
|
|
138
|
-
|
|
138
|
+
file_path: {
|
|
139
139
|
type: "string",
|
|
140
140
|
description: "Absolute path to the file to sanitize."
|
|
141
141
|
},
|
|
@@ -144,7 +144,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
144
144
|
description: "The detection profile to use. Available: 'General' (Free), or PRO profiles: 'Dev' (Engineering/Code), 'Medical', 'Pharma', 'Legal', 'Compliance', 'CCPA', 'Finance', 'Bizops', 'Sales', 'WealthMgmt', 'Insurance', 'Accounting', 'HR', 'Security', 'Marketing', 'Support', 'RealEstate', 'Agents', 'Academic', 'Creative', 'Tech', 'Personal'. Defaults to 'General'."
|
|
145
145
|
}
|
|
146
146
|
},
|
|
147
|
-
required: ["
|
|
147
|
+
required: ["file_path"]
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
]
|
|
@@ -169,7 +169,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
169
169
|
content: [
|
|
170
170
|
{
|
|
171
171
|
type: "text",
|
|
172
|
-
text: `ā ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nTo unlock 22+ advanced industry profiles, custom regex, and unlimited logs protection, set PRIVACYSCRUBBER_KEY to your PRO license key.\nGet a key at: https://privacyscrubber.com/pricing\n\n--- Sanitized Output (General Profile) ---\n`
|
|
172
|
+
text: `ā ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nTo unlock 22+ advanced industry profiles, custom regex, and unlimited logs protection, set PRIVACYSCRUBBER_KEY to your PRO license key.\nGet a key at: https://privacyscrubber.com/pricing\nBrowser Extension: https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol\n\n--- Sanitized Output (General Profile) ---\n`
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
175
|
type: "text",
|
|
@@ -204,7 +204,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
if (name === "sanitize_file") {
|
|
207
|
-
const
|
|
207
|
+
const rawPath = args.file_path || args.filePath;
|
|
208
|
+
if (!rawPath) {
|
|
209
|
+
return {
|
|
210
|
+
isError: true,
|
|
211
|
+
content: [
|
|
212
|
+
{
|
|
213
|
+
type: "text",
|
|
214
|
+
text: "Error: Missing required parameter 'file_path'."
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const { profile = "General" } = args;
|
|
220
|
+
const filePath = rawPath;
|
|
208
221
|
|
|
209
222
|
const resolvedPath = path.resolve(filePath);
|
|
210
223
|
if (!fs.existsSync(resolvedPath)) {
|
|
@@ -246,7 +259,71 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
246
259
|
};
|
|
247
260
|
}
|
|
248
261
|
|
|
249
|
-
|
|
262
|
+
if (resolvedPath.toLowerCase().endsWith(".docx")) {
|
|
263
|
+
try {
|
|
264
|
+
const mammoth = require('mammoth');
|
|
265
|
+
const result = await mammoth.extractRawText({ path: resolvedPath });
|
|
266
|
+
const content = result.value;
|
|
267
|
+
const targetProfile = profile.trim();
|
|
268
|
+
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
269
|
+
const license = checkLicenseStatus();
|
|
270
|
+
|
|
271
|
+
let prefix = "";
|
|
272
|
+
let finalProfile = targetProfile;
|
|
273
|
+
|
|
274
|
+
if (isAdvanced && !license.isPro) {
|
|
275
|
+
const reason = license.error ? ` (${license.error})` : "";
|
|
276
|
+
prefix = `ā ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nGet a PRO key at: https://privacyscrubber.com/pricing\nBrowser Extension: https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol\n\n`;
|
|
277
|
+
finalProfile = "General";
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const sanitized = performSanitization(content, finalProfile);
|
|
281
|
+
return {
|
|
282
|
+
content: [
|
|
283
|
+
{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: `${prefix}${sanitized}`
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
};
|
|
289
|
+
} catch (docxError) {
|
|
290
|
+
return {
|
|
291
|
+
isError: true,
|
|
292
|
+
content: [
|
|
293
|
+
{
|
|
294
|
+
type: "text",
|
|
295
|
+
text: `Error: Failed to parse DOCX file: ${docxError.message}`
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const buffer = fs.readFileSync(resolvedPath);
|
|
303
|
+
|
|
304
|
+
// Check for null bytes in the first 8KB to detect binary files
|
|
305
|
+
let isBinary = false;
|
|
306
|
+
const checkLen = Math.min(buffer.length, 8000);
|
|
307
|
+
for (let i = 0; i < checkLen; i++) {
|
|
308
|
+
if (buffer[i] === 0) {
|
|
309
|
+
isBinary = true;
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (isBinary) {
|
|
315
|
+
return {
|
|
316
|
+
isError: true,
|
|
317
|
+
content: [
|
|
318
|
+
{
|
|
319
|
+
type: "text",
|
|
320
|
+
text: `Error: Binary file format detected. The local MCP server only supports plain text files (e.g., source code, logs, CSV, markdown, JSON). To sanitize complex formats like PDF or DOCX, please use the web interface at: https://privacyscrubber.com/`
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const content = buffer.toString("utf8");
|
|
250
327
|
const targetProfile = profile.trim();
|
|
251
328
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
252
329
|
const license = checkLicenseStatus();
|
|
@@ -256,7 +333,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
256
333
|
|
|
257
334
|
if (isAdvanced && !license.isPro) {
|
|
258
335
|
const reason = license.error ? ` (${license.error})` : "";
|
|
259
|
-
prefix = `ā ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nGet a PRO key at: https://privacyscrubber.com/pricing\n\n`;
|
|
336
|
+
prefix = `ā ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nGet a PRO key at: https://privacyscrubber.com/pricing\nBrowser Extension: https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol\n\n`;
|
|
260
337
|
finalProfile = "General";
|
|
261
338
|
}
|
|
262
339
|
|
package/manifest.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": "0.3",
|
|
3
|
+
"name": "privacyscrubber/pii-masking-mcp",
|
|
4
|
+
"version": "1.0.3",
|
|
5
|
+
"description": "Zero-Trust Data Sanitization (ZTDS) MCP Server for AI IDEs and Claude Desktop. Locally scrubs PII, secrets, and credentials before sending context to LLMs.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "PrivacyScrubber",
|
|
8
|
+
"email": "legal@privacyscrubber.com"
|
|
9
|
+
},
|
|
10
|
+
"server": {
|
|
11
|
+
"type": "node",
|
|
12
|
+
"entry_point": "index.js",
|
|
13
|
+
"mcp_config": {
|
|
14
|
+
"command": "node",
|
|
15
|
+
"args": ["${__dirname}/index.js"],
|
|
16
|
+
"env": {
|
|
17
|
+
"PRIVACYSCRUBBER_KEY": "${user_config.PRIVACYSCRUBBER_KEY}"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"user_config": {
|
|
22
|
+
"PRIVACYSCRUBBER_KEY": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"title": "PrivacyScrubber License Key",
|
|
25
|
+
"description": "Optional PRO or TEAMS license key. Leave blank to use the Free tier."
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privacyscrubber/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"mcpName": "io.github.moxno/privacyscrubber-mcp",
|
|
4
5
|
"description": "Zero-Trust Data Sanitization (ZTDS) MCP Server for AI IDEs and Claude Desktop. Locally scrubs PII, secrets, and credentials before sending context to LLMs.",
|
|
5
6
|
"main": "index.js",
|
|
6
7
|
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.js",
|
|
13
|
+
"scrubber-core.cjs",
|
|
14
|
+
"smithery.yaml",
|
|
15
|
+
"server.json",
|
|
16
|
+
"manifest.json",
|
|
17
|
+
"bundle.mcpb",
|
|
18
|
+
".well-known"
|
|
19
|
+
],
|
|
7
20
|
"bin": {
|
|
8
21
|
"mcp-server": "index.js",
|
|
9
22
|
"privacyscrubber-mcp": "index.js"
|
|
@@ -12,7 +25,8 @@
|
|
|
12
25
|
"start": "node index.js"
|
|
13
26
|
},
|
|
14
27
|
"dependencies": {
|
|
15
|
-
"@modelcontextprotocol/sdk": "^
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
+
"mammoth": "^1.8.0"
|
|
16
30
|
},
|
|
17
31
|
"keywords": [
|
|
18
32
|
"mcp",
|
|
@@ -26,6 +40,14 @@
|
|
|
26
40
|
"claude-desktop",
|
|
27
41
|
"cursor"
|
|
28
42
|
],
|
|
29
|
-
"author": "Ilya Sibiryakov (
|
|
30
|
-
"license": "MIT"
|
|
43
|
+
"author": "Ilya Sibiryakov (BrandMeWeb)",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"homepage": "https://privacyscrubber.com/pii-mcp/",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/moxno/privacyscrubber-mcp.git"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/moxno/privacyscrubber-mcp/issues"
|
|
52
|
+
}
|
|
31
53
|
}
|
package/server.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "privacyscrubber/pii-masking-mcp",
|
|
4
|
+
"description": "Zero-Trust PII & secrets sanitizer. Locally scrubs data in-memory before sending context to LLMs.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/moxno/privacyscrubber-mcp",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "1.0.3",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "@privacyscrubber/mcp-server",
|
|
14
|
+
"version": "1.0.3",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
},
|
|
18
|
+
"environmentVariables": [
|
|
19
|
+
{
|
|
20
|
+
"description": "Optional PRO or TEAMS license key to unlock specialized detection profiles.",
|
|
21
|
+
"isRequired": false,
|
|
22
|
+
"format": "string",
|
|
23
|
+
"isSecret": true,
|
|
24
|
+
"name": "PRIVACYSCRUBBER_KEY"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Smithery.ai configuration for PrivacyScrubber MCP Server
|
|
2
|
+
startCommand:
|
|
3
|
+
type: "stdio"
|
|
4
|
+
commandFunction: |-
|
|
5
|
+
(config) => ({
|
|
6
|
+
command: 'node',
|
|
7
|
+
args: ['./index.js'],
|
|
8
|
+
env: {
|
|
9
|
+
PRIVACYSCRUBBER_KEY: (config && config.PRIVACYSCRUBBER_KEY) || ''
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
exampleConfig: {}
|
package/test-mcp.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Complete Cryptographic Verification Test Suite for @privacyscrubber/mcp-server
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { createRequire } from 'module';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
|
-
|
|
9
|
-
const require = createRequire(import.meta.url);
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = path.dirname(__filename);
|
|
12
|
-
|
|
13
|
-
const scrubberCorePath = path.resolve(__dirname, '../chrome-extension/scrubber-core.js');
|
|
14
|
-
const mcpServerIndex = path.resolve(__dirname, 'index.js');
|
|
15
|
-
|
|
16
|
-
console.log("Loading modules for verification test...");
|
|
17
|
-
try {
|
|
18
|
-
const PrivacyScrubberCore = require(scrubberCorePath);
|
|
19
|
-
PrivacyScrubberCore.init();
|
|
20
|
-
|
|
21
|
-
// Load index.js to test its internal validate functions
|
|
22
|
-
// We can simulate its checkLicenseStatus by mocking environment variables
|
|
23
|
-
const indexModule = await import('./index.js?update=' + Date.now());
|
|
24
|
-
|
|
25
|
-
// Test Case 1: No license key
|
|
26
|
-
console.log("\n[Test 1] Testing with no license key (Free tier)...");
|
|
27
|
-
process.env.PRIVACYSCRUBBER_KEY = "";
|
|
28
|
-
const t1 = global.checkLicenseStatus ? global.checkLicenseStatus() : mockCheckLicenseStatus();
|
|
29
|
-
console.log("Status:", t1);
|
|
30
|
-
if (t1.isPro) {
|
|
31
|
-
console.error("ā Test 1 failed: keyless state should not be PRO.");
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
console.log("ā
Test 1 passed.");
|
|
35
|
-
|
|
36
|
-
// Test Case 2: Expired trial key
|
|
37
|
-
console.log("\n[Test 2] Testing with expired signed trial key...");
|
|
38
|
-
// Expired key generated by generate-license.js
|
|
39
|
-
const expiredKey = "eyJ0eXBlIjoidHJpYWwiLCJleHBpcmVzIjoxNzgzMjcwMTgzLCJpc3N1ZWRBdCI6MTc4MzcwMjE4Mywibm9uY2UiOiIzYzRkYzEwNmRkOGJiZjczIn0=.Wc1cPwvRpEQxG3BarmedBYm9sUsGdwznboh8Gx3a7qQZm2I+Dha8lk1CtzmaaBt5O8pQszGuTCHapASFwZX2Ku8uZxkGipPI+tbNI39KK7cM6wA+Txc0Ufec/lnDE/27WelBswStSIMgR5LPVScfamMO5BK6jvJ9RgOrEdJeqJPcpab0mwy4jzZwSATyZ1JOS61i8ZlJ+n99zppBrrMZQQo/FJ5t70RpqZiZoMuyNbeDJB9UWGDEAELiJLBkaGeYgBo65zp7fRcTuyjmoZjINnOhCcXmKsmeB1vkHdjPg8jXaGVWFN7bJryeV7j/OkpNqO2q74x3hXDvNwlGtg4PUg==";
|
|
40
|
-
process.env.PRIVACYSCRUBBER_KEY = expiredKey;
|
|
41
|
-
const t2 = mockCheckLicenseStatus();
|
|
42
|
-
console.log("Status:", t2);
|
|
43
|
-
if (t2.isPro || !t2.error.includes("expired")) {
|
|
44
|
-
console.error("ā Test 2 failed: expired key should be blocked with expiration reason.");
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
console.log("ā
Test 2 passed.");
|
|
48
|
-
|
|
49
|
-
// Test Case 3: Valid signed PRO key
|
|
50
|
-
console.log("\n[Test 3] Testing with valid signed PRO key...");
|
|
51
|
-
// Valid key generated in this session
|
|
52
|
-
const validKey = "eyJ0eXBlIjoicHJvIiwiZXhwaXJlcyI6MjA5OTA2MjE3NCwiaXNzdWVkQXQiOjE3ODM3MDIxNzQsIm5vbmNlIjoiMmI4MTllN2ZmM2ZmOTFmYiJ9.ZmhaKDGf9vG0nTH0nyOy3EInsuUmzjBOk9IOyiqzt6Y5s3UG+2yRExuKBoeXWymbpJt3NIJNM9sVxxl+lcop5wqbi2LNtPY4MuwBRA7pO4nn3Bes5R0lxLbEYVE8Iiw3zbfK4uVYQ53BJ7El6JCeFKPJ5WbKUsPLsjb1Lr2iQzW3ODOMaM5jKdgAGcNpuWQ373D7SW7I03Jec9kvP5hL7j3u4DV8ZzuQFzy2Nh+uMH54suydg2sNIpxrRQyxpGv7rZjTO1KT+xzzqnjqX4Pein0e6GrC5E4JUEggADtXPFMKW5SEiWzeeirB5RUPMO/F927S5fFuOYHAfuar2FqErA==";
|
|
53
|
-
process.env.PRIVACYSCRUBBER_KEY = validKey;
|
|
54
|
-
const t3 = mockCheckLicenseStatus();
|
|
55
|
-
console.log("Status:", t3);
|
|
56
|
-
if (!t3.isPro) {
|
|
57
|
-
console.error("ā Test 3 failed: valid key was not accepted.");
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
console.log("ā
Test 3 passed.");
|
|
61
|
-
|
|
62
|
-
console.log("\nš All local cryptographic validation tests passed!");
|
|
63
|
-
process.exit(0);
|
|
64
|
-
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error("ā Test run crashed:", error);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Mock checker mapping index.js logic locally for direct assertion
|
|
71
|
-
function mockCheckLicenseStatus() {
|
|
72
|
-
const crypto = require('crypto');
|
|
73
|
-
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
74
|
-
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw3f37srO402PU4++Baf8
|
|
75
|
-
FG8LY4l/IA3NKLlBnYmNHRTjfI/O/w5PDZn1xPcUQevojA1J+A5moKcjXsJ5b21X
|
|
76
|
-
hJoYSkE4vLpcVYOt1FhRwEHs1APDSyss0HixboLz2eW2XQf2NbwajWtNlyxvgczO
|
|
77
|
-
KE6ClnLomtsaKywwqB4alzdYnnnFJttFPjwmgPSO7D9AgN9sYaVkXOaOFrIZ90Ng
|
|
78
|
-
TRhSHUeL7ReltWlCHwz9xf5m2FrKtxr2VBlEoyPjsFzalHMey1EX+yXe81zM7IIi
|
|
79
|
-
t1Z8agLzo7WIfNBAIWmRlerTplaFFZrQgdF5g/Y0n8IIMZOtadgoY8E855psDNZV
|
|
80
|
-
7wIDAQAB
|
|
81
|
-
-----END PUBLIC KEY-----`;
|
|
82
|
-
|
|
83
|
-
const key = (process.env.PRIVACYSCRUBBER_KEY || "").trim();
|
|
84
|
-
if (!key) return { isPro: false, type: null, error: "No license key provided." };
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const [payloadBase64, signatureBase64] = key.split('.');
|
|
88
|
-
if (!payloadBase64 || !signatureBase64) {
|
|
89
|
-
return { isPro: false, type: null, error: "Invalid license key structure." };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const verifier = crypto.createVerify('SHA256');
|
|
93
|
-
verifier.update(payloadBase64);
|
|
94
|
-
const isVerified = verifier.verify(PUBLIC_KEY, signatureBase64, 'base64');
|
|
95
|
-
|
|
96
|
-
if (!isVerified) {
|
|
97
|
-
return { isPro: false, type: null, error: "Signature verification failed." };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const payload = JSON.parse(Buffer.from(payloadBase64, 'base64').toString('utf8'));
|
|
101
|
-
|
|
102
|
-
if (payload.expires && payload.expires < Math.floor(Date.now() / 1000)) {
|
|
103
|
-
return {
|
|
104
|
-
isPro: false,
|
|
105
|
-
type: payload.type,
|
|
106
|
-
error: `License expired on ${new Date(payload.expires * 1000).toLocaleDateString()}`
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return { isPro: true, type: payload.type, error: null };
|
|
111
|
-
} catch (e) {
|
|
112
|
-
return { isPro: false, type: null, error: "Error parsing license: " + e.message };
|
|
113
|
-
}
|
|
114
|
-
}
|