@privacyscrubber/mcp-server 2.4.6 → 2.4.8
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 +1 -1
- package/index.js +107 -50
- package/install.js +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/pii-masking-run.js +227 -8
- package/ps-license-manager.cjs +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/scrubber-core.cjs +1 -1
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -485,7 +485,7 @@ Returns a visual dashboard showing your current tier, session request count, act
|
|
|
485
485
|
* **Response Example (Free Tier):**
|
|
486
486
|
```
|
|
487
487
|
╔══════════════════════════════════════════════════╗
|
|
488
|
-
║ PrivacyScrubber MCP Server v2.4.
|
|
488
|
+
║ PrivacyScrubber MCP Server v2.4.8 ║
|
|
489
489
|
╠══════════════════════════════════════════════════╣
|
|
490
490
|
║ 🔓 Tier: FREE ║
|
|
491
491
|
║ 📊 Session requests: 5 ║
|
package/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ListToolsRequestSchema,
|
|
16
16
|
ListPromptsRequestSchema,
|
|
17
17
|
GetPromptRequestSchema,
|
|
18
|
+
ListResourcesRequestSchema,
|
|
18
19
|
ListResourceTemplatesRequestSchema,
|
|
19
20
|
ReadResourceRequestSchema,
|
|
20
21
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -43,6 +44,7 @@ if (rawCliArgs.includes('--help') || rawCliArgs.includes('-h') || rawCliArgs.inc
|
|
|
43
44
|
console.log(`PrivacyScrubber MCP Server v${MCP_VERSION} — Zero-Trust Data Sanitization (ZTDS)`);
|
|
44
45
|
console.log('\nUsage:');
|
|
45
46
|
console.log(' npx @privacyscrubber/mcp-server Start MCP stdio server');
|
|
47
|
+
console.log(' npx @privacyscrubber/mcp-server --wrap <cmd> Transparent ZTDS proxy for any 3rd-party MCP server');
|
|
46
48
|
console.log(' npx @privacyscrubber/mcp-server init Auto-configure Claude, Cursor, Windsurf, Cline');
|
|
47
49
|
console.log(' npx @privacyscrubber/mcp-server uninstall Remove from IDE configurations');
|
|
48
50
|
console.log(' npx @privacyscrubber/mcp-server trial Request 7-day B2B TEAMS/SDK evaluation key');
|
|
@@ -82,8 +84,9 @@ if (rawCliArgs.includes('--memo') || rawCliArgs.includes('memo')) {
|
|
|
82
84
|
console.log(generateProcurementMemo({ targetTier: tierArg, format: 'text' }));
|
|
83
85
|
process.exit(0);
|
|
84
86
|
}
|
|
85
|
-
if (rawCliArgs.includes('--hook')) {
|
|
87
|
+
if (rawCliArgs.includes('--hook') || rawCliArgs.includes('--wrap')) {
|
|
86
88
|
await import('./pii-masking-run.js');
|
|
89
|
+
await new Promise(() => {});
|
|
87
90
|
}
|
|
88
91
|
if (rawCliArgs.some(arg => ['init', 'install', '--install', 'setup', '--setup', 'uninstall', '--uninstall', '--remove'].includes(arg))) {
|
|
89
92
|
const { runInstaller } = await import('./install.js');
|
|
@@ -751,11 +754,27 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
751
754
|
trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
|
|
752
755
|
};
|
|
753
756
|
|
|
757
|
+
const agenticProtocol = {
|
|
758
|
+
protocolsSupported: ['MCP-1.0 (stdio/SSE)', 'A2A-1.0 (Agent2Agent)', 'ZTDS-RFC-v1.0'],
|
|
759
|
+
agentCardUrl: 'https://privacyscrubber.com/.well-known/agent-card.json',
|
|
760
|
+
ztdsStandardCardUrl: 'https://ztds.ai/.well-known/agent-card.json',
|
|
761
|
+
regulatorySafeHarbor: [
|
|
762
|
+
'GDPR-Art-4(5)-Pseudonymization',
|
|
763
|
+
'GDPR-Art-25-Privacy-by-Design',
|
|
764
|
+
'GDPR-Art-32-TOMs',
|
|
765
|
+
'EU-AI-Act-Art-50',
|
|
766
|
+
'EU-AI-Act-Art-17-EN-18286-QMS',
|
|
767
|
+
'CNIL-ICO-Agentic-Memory-Compartmentalization',
|
|
768
|
+
'IL-PPL-Amendment-13-Sec-17'
|
|
769
|
+
],
|
|
770
|
+
zeroCleartextEgress: true
|
|
771
|
+
};
|
|
772
|
+
|
|
754
773
|
const statutoryCitations = [
|
|
755
774
|
{
|
|
756
|
-
statute: 'EU AI Act (Art. 50)',
|
|
757
|
-
scope: 'AI Transparency &
|
|
758
|
-
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
|
|
775
|
+
statute: 'EU AI Act (Art. 50 & Art. 17 / EN 18286:2026 QMS)',
|
|
776
|
+
scope: 'AI Transparency, Data Minimization & Harmonized QMS Governance',
|
|
777
|
+
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models and supports Article 17 Quality Management System (EN 18286:2026) data governance controls. Informational mapping, not a compliance determination.'
|
|
759
778
|
},
|
|
760
779
|
{
|
|
761
780
|
statute: 'GDPR (Art. 4, 25, 28, 32)',
|
|
@@ -787,6 +806,7 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
787
806
|
frameworksList: Array.from(frameworksSet),
|
|
788
807
|
statutoryCitations,
|
|
789
808
|
ipHoldings,
|
|
809
|
+
agenticProtocol,
|
|
790
810
|
multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
|
|
791
811
|
};
|
|
792
812
|
}
|
|
@@ -2233,6 +2253,60 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
2233
2253
|
};
|
|
2234
2254
|
});
|
|
2235
2255
|
|
|
2256
|
+
const PROFILE_RESOURCE_DESCRIPTIONS = {
|
|
2257
|
+
"general": "Scrubs basic PII: Names, Emails, Phone Numbers, Credit Cards, SSN, IPv4/IPv6, and standard API Keys.",
|
|
2258
|
+
"dev": "Engineering profile. Scrubs AWS keys, JWTs, GCP keys, Stripe keys, database connection strings, and internal IP addresses.",
|
|
2259
|
+
"engineering": "Engineering profile. Scrubs AWS keys, JWTs, GCP keys, Stripe keys, database connection strings, internal IPs, Docker paths, and K8s internal endpoints.",
|
|
2260
|
+
"tech": "Technology profile. Scrubs cloud instance IDs, node/cluster IDs, environment tags, kubeconfig, and terraform states.",
|
|
2261
|
+
"medical": "HIPAA compliance profile. Scrubs ICD-10 codes, medical record numbers (MRN), DEA numbers, NPI, patient names, and health conditions.",
|
|
2262
|
+
"finance": "PCI/Finance profile. Scrubs IBAN, SWIFT codes, credit cards, routing numbers, and financial transaction IDs.",
|
|
2263
|
+
"wealthmgmt": "Wealth Management profile. Scrubs ABA/Routing numbers, trust names, portfolio values, net worth, RMDs, and account numbers.",
|
|
2264
|
+
"insurance": "Insurance profile. Scrubs claim numbers, policy numbers, settlement amounts, NAIC, adjustor IDs, and insured names.",
|
|
2265
|
+
"accounting": "Accounting profile. Scrubs EIN, FEIN, Tax IDs, AGI, tax forms (1040, W-2, 1099), refund amounts, and PTINs.",
|
|
2266
|
+
"legal": "Legal profile. Scrubs case numbers, matter numbers, attorney-client privilege markers, and litigation IDs.",
|
|
2267
|
+
"hr": "Human Resources profile. Scrubs employee IDs (EEID), resumes, DOBs, tenant IDs, and home addresses.",
|
|
2268
|
+
"security": "Security profile. Scrubs secrets, AWS keys, JWTs, Stripe keys, incident IDs, and breach reports.",
|
|
2269
|
+
"marketing": "Marketing profile. Scrubs lead IDs, prospect IDs, GCLID/FBCLID, LTV/CAC amounts, cohort IDs, and segment IDs.",
|
|
2270
|
+
"bizops": "BizOps profile. Scrubs deals, KPIs, vendor IDs, EBITDA/Revenue figures, NDAs, and M&A identifiers.",
|
|
2271
|
+
"sales": "Sales profile. Scrubs opportunity IDs, DocuSign hashes, ARR/MRR/Quota amounts, and SFDC/HubSpot IDs.",
|
|
2272
|
+
"support": "Support profile. Scrubs Zendesk/Jira ticket numbers, RMA/Return IDs, and loyalty/rewards numbers.",
|
|
2273
|
+
"realestate": "Real Estate profile. Scrubs MLS numbers, LIS IDs, parcel numbers, rent/escrow amounts, and gate/lobby codes.",
|
|
2274
|
+
"compliance": "Compliance profile. Scrubs GDPR/HIPAA/SOC2 audit IDs, DPA policy numbers, and SAR/DSAR request IDs.",
|
|
2275
|
+
"ccpa": "CCPA profile. Scrubs driver licenses, precise geolocation (Lat/Long), CCPA/CPRA opt-out markers, and account numbers.",
|
|
2276
|
+
"agents": "AI Agents profile. Scrubs agent IDs, vector IDs, task IDs, system prompts, and OpenAI API keys.",
|
|
2277
|
+
"academic": "Academic profile. Scrubs student/alumni IDs, course numbers, FERPA/IRB IDs, and academic grades.",
|
|
2278
|
+
"creative": "Creative profile. Scrubs project IDs, script drafts, spoiler/embargo tags, and ghostwriter names.",
|
|
2279
|
+
"personal": "Personal profile. Scrubs birthdays, passwords, PINs, and emergency contacts/family phone numbers.",
|
|
2280
|
+
"pharma": "Pharma/Clinical profile. Scrubs patient IDs, study protocols, IND/NDA numbers, IRB IDs, batch/lot serials, and dosages.",
|
|
2281
|
+
"underwriting": "Loan Underwriting & Mortgage profile. Scrubs borrower names, SSNs, EINs, addresses while preserving wages and YTD figures.",
|
|
2282
|
+
"automotive": "Automotive & EV profile. Scrubs VINs, ECU IDs, telematics, and repair orders while preserving DTCs and ASIL-D standards.",
|
|
2283
|
+
"energy": "Energy & Utilities profile. Scrubs ESI IDs, meter serials, SCADA points, PLC tags, and substation circuits.",
|
|
2284
|
+
"hospitality": "Hospitality & Travel profile. Scrubs PNRs, e-tickets, boarding pass barcodes, and guest folios.",
|
|
2285
|
+
"biotech": "Biotech & Genomics profile. Scrubs accessions, patient phenotypes, HGVS variants, DNA/RNA samples, and flowcell IDs.",
|
|
2286
|
+
"telecom": "Call Centers & Telephony profile. Scrubs CTI interaction IDs, Amazon Connect/Genesys sessions, agent extensions, ANI/DNIS, IVR DTMF, and audio transcript diarization headers."
|
|
2287
|
+
};
|
|
2288
|
+
|
|
2289
|
+
// List Resources
|
|
2290
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
2291
|
+
const featuredProfiles = ["general", "dev", "medical", "finance", "legal", "security", "compliance", "hr", "agents"];
|
|
2292
|
+
return {
|
|
2293
|
+
resources: [
|
|
2294
|
+
{
|
|
2295
|
+
uri: "mcp://privacyscrubber/profiles/catalog",
|
|
2296
|
+
name: "ZTDS 30 Industry Profiles Catalog",
|
|
2297
|
+
description: "Complete directory of all 30 specialized PrivacyScrubber PII & secrets detection profiles and regulatory coverage.",
|
|
2298
|
+
mimeType: "text/markdown"
|
|
2299
|
+
},
|
|
2300
|
+
...featuredProfiles.map((name) => ({
|
|
2301
|
+
uri: `mcp://privacyscrubber/profiles/${name}`,
|
|
2302
|
+
name: `Profile: ${name.charAt(0).toUpperCase() + name.slice(1)}`,
|
|
2303
|
+
description: PROFILE_RESOURCE_DESCRIPTIONS[name],
|
|
2304
|
+
mimeType: "text/markdown"
|
|
2305
|
+
}))
|
|
2306
|
+
]
|
|
2307
|
+
};
|
|
2308
|
+
});
|
|
2309
|
+
|
|
2236
2310
|
// List Resource Templates
|
|
2237
2311
|
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
2238
2312
|
return {
|
|
@@ -2249,42 +2323,24 @@ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
|
2249
2323
|
// Read Resource
|
|
2250
2324
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
2251
2325
|
const uri = request.params.uri;
|
|
2326
|
+
if (uri === "mcp://privacyscrubber/profiles/catalog") {
|
|
2327
|
+
const lines = Object.entries(PROFILE_RESOURCE_DESCRIPTIONS).map(
|
|
2328
|
+
([k, v]) => `- **\`${k}\`**: ${v}`
|
|
2329
|
+
);
|
|
2330
|
+
return {
|
|
2331
|
+
contents: [
|
|
2332
|
+
{
|
|
2333
|
+
uri,
|
|
2334
|
+
mimeType: "text/markdown",
|
|
2335
|
+
text: `# PrivacyScrubber ZTDS Profiles Catalog (30 Specialized Industry Profiles)\n\n${lines.join("\n")}\n\n*Usage: Pass \`"profile": "<name>"\` or \`"sector": "<name>"\` to \`sanitize_text\`, \`sanitize_file\`, or \`guard_read_file\`.*`
|
|
2336
|
+
}
|
|
2337
|
+
]
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2252
2340
|
const match = uri.match(/^mcp:\/\/privacyscrubber\/profiles\/(.+)$/);
|
|
2253
2341
|
if (match) {
|
|
2254
2342
|
const profileName = match[1];
|
|
2255
|
-
const
|
|
2256
|
-
"general": "Scrubs basic PII: Names, Emails, Phone Numbers, Credit Cards, SSN, IPv4/IPv6, and standard API Keys.",
|
|
2257
|
-
"dev": "Engineering profile. Scrubs AWS keys, JWTs, GCP keys, Stripe keys, database connection strings, and internal IP addresses.",
|
|
2258
|
-
"engineering": "Engineering profile. Scrubs AWS keys, JWTs, GCP keys, Stripe keys, database connection strings, internal IPs, Docker paths, and K8s internal endpoints.",
|
|
2259
|
-
"tech": "Technology profile. Scrubs cloud instance IDs, node/cluster IDs, environment tags, kubeconfig, and terraform states.",
|
|
2260
|
-
"medical": "HIPAA compliance profile. Scrubs ICD-10 codes, medical record numbers (MRN), DEA numbers, NPI, patient names, and health conditions.",
|
|
2261
|
-
"finance": "PCI/Finance profile. Scrubs IBAN, SWIFT codes, credit cards, routing numbers, and financial transaction IDs.",
|
|
2262
|
-
"wealthmgmt": "Wealth Management profile. Scrubs ABA/Routing numbers, trust names, portfolio values, net worth, RMDs, and account numbers.",
|
|
2263
|
-
"insurance": "Insurance profile. Scrubs claim numbers, policy numbers, settlement amounts, NAIC, adjustor IDs, and insured names.",
|
|
2264
|
-
"accounting": "Accounting profile. Scrubs EIN, FEIN, Tax IDs, AGI, tax forms (1040, W-2, 1099), refund amounts, and PTINs.",
|
|
2265
|
-
"legal": "Legal profile. Scrubs case numbers, matter numbers, attorney-client privilege markers, and litigation IDs.",
|
|
2266
|
-
"hr": "Human Resources profile. Scrubs employee IDs (EEID), resumes, DOBs, tenant IDs, and home addresses.",
|
|
2267
|
-
"security": "Security profile. Scrubs secrets, AWS keys, JWTs, Stripe keys, incident IDs, and breach reports.",
|
|
2268
|
-
"marketing": "Marketing profile. Scrubs lead IDs, prospect IDs, GCLID/FBCLID, LTV/CAC amounts, cohort IDs, and segment IDs.",
|
|
2269
|
-
"bizops": "BizOps profile. Scrubs deals, KPIs, vendor IDs, EBITDA/Revenue figures, NDAs, and M&A identifiers.",
|
|
2270
|
-
"sales": "Sales profile. Scrubs opportunity IDs, DocuSign hashes, ARR/MRR/Quota amounts, and SFDC/HubSpot IDs.",
|
|
2271
|
-
"support": "Support profile. Scrubs Zendesk/Jira ticket numbers, RMA/Return IDs, and loyalty/rewards numbers.",
|
|
2272
|
-
"realestate": "Real Estate profile. Scrubs MLS numbers, LIS IDs, parcel numbers, rent/escrow amounts, and gate/lobby codes.",
|
|
2273
|
-
"compliance": "Compliance profile. Scrubs GDPR/HIPAA/SOC2 audit IDs, DPA policy numbers, and SAR/DSAR request IDs.",
|
|
2274
|
-
"ccpa": "CCPA profile. Scrubs driver licenses, precise geolocation (Lat/Long), CCPA/CPRA opt-out markers, and account numbers.",
|
|
2275
|
-
"agents": "AI Agents profile. Scrubs agent IDs, vector IDs, task IDs, system prompts, and OpenAI API keys.",
|
|
2276
|
-
"academic": "Academic profile. Scrubs student/alumni IDs, course numbers, FERPA/IRB IDs, and academic grades.",
|
|
2277
|
-
"creative": "Creative profile. Scrubs project IDs, script drafts, spoiler/embargo tags, and ghostwriter names.",
|
|
2278
|
-
"personal": "Personal profile. Scrubs birthdays, passwords, PINs, and emergency contacts/family phone numbers.",
|
|
2279
|
-
"pharma": "Pharma/Clinical profile. Scrubs patient IDs, study protocols, IND/NDA numbers, IRB IDs, batch/lot serials, and dosages.",
|
|
2280
|
-
"underwriting": "Loan Underwriting & Mortgage profile. Scrubs borrower names, SSNs, EINs, addresses while preserving wages and YTD figures.",
|
|
2281
|
-
"automotive": "Automotive & EV profile. Scrubs VINs, ECU IDs, telematics, and repair orders while preserving DTCs and ASIL-D standards.",
|
|
2282
|
-
"energy": "Energy & Utilities profile. Scrubs ESI IDs, meter serials, SCADA points, PLC tags, and substation circuits.",
|
|
2283
|
-
"hospitality": "Hospitality & Travel profile. Scrubs PNRs, e-tickets, boarding pass barcodes, and guest folios.",
|
|
2284
|
-
"biotech": "Biotech & Genomics profile. Scrubs accessions, patient phenotypes, HGVS variants, DNA/RNA samples, and flowcell IDs.",
|
|
2285
|
-
"telecom": "Call Centers & Telephony profile. Scrubs CTI interaction IDs, Amazon Connect/Genesys sessions, agent extensions, ANI/DNIS, IVR DTMF, and audio transcript diarization headers."
|
|
2286
|
-
};
|
|
2287
|
-
const desc = descriptions[profileName.toLowerCase()] || `The '${profileName}' profile is a specialized detection ruleset tuned for specific industry compliance. It detects and sanitizes domain-specific identifiers.`;
|
|
2343
|
+
const desc = PROFILE_RESOURCE_DESCRIPTIONS[profileName.toLowerCase()] || `The '${profileName}' profile is a specialized detection ruleset tuned for specific industry compliance. It detects and sanitizes domain-specific identifiers.`;
|
|
2288
2344
|
|
|
2289
2345
|
return {
|
|
2290
2346
|
contents: [
|
|
@@ -2489,10 +2545,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2489
2545
|
|
|
2490
2546
|
const charLimit = (isAdvanced && !isAutoElevated && !license.isPro) ? 5000 : 15000;
|
|
2491
2547
|
if (isAdvanced && !license.isPro) {
|
|
2492
|
-
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade]
|
|
2548
|
+
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] 7-Day B2B Trial / TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${encodeURIComponent(targetProfile)}&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade (or run: npx @privacyscrubber/mcp-server trial)${colors.reset}\n`);
|
|
2493
2549
|
}
|
|
2494
2550
|
|
|
2495
|
-
const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit);
|
|
2551
|
+
const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit, finalProfile);
|
|
2496
2552
|
|
|
2497
2553
|
const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, currentIgnoreList, currentSession);
|
|
2498
2554
|
|
|
@@ -2851,10 +2907,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2851
2907
|
|
|
2852
2908
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
2853
2909
|
if (isAdvanced && !license.isPro) {
|
|
2854
|
-
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade]
|
|
2910
|
+
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] 7-Day B2B Trial / TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${encodeURIComponent(targetProfile)}&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade (or run: npx @privacyscrubber/mcp-server trial)${colors.reset}\n`);
|
|
2855
2911
|
}
|
|
2856
2912
|
|
|
2857
|
-
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
2913
|
+
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit, finalProfile);
|
|
2858
2914
|
|
|
2859
2915
|
const sanitized = performSanitization(processedContent, finalProfile, targetIgnoreList, targetSession);
|
|
2860
2916
|
const { scrubbedText, newTokens } = sanitized;
|
|
@@ -3082,10 +3138,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3082
3138
|
|
|
3083
3139
|
const charLimit = (isAdvanced && !isAutoElevated && !license.isPro) ? 5000 : 15000;
|
|
3084
3140
|
if (isAdvanced && !license.isPro) {
|
|
3085
|
-
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade]
|
|
3141
|
+
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] 7-Day B2B Trial / TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${encodeURIComponent(targetProfile)}&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade (or run: npx @privacyscrubber/mcp-server trial)${colors.reset}\n`);
|
|
3086
3142
|
}
|
|
3087
3143
|
|
|
3088
|
-
const { processedText: processedContent2, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
3144
|
+
const { processedText: processedContent2, wasTruncated } = truncateIfFree(content, license.isPro, charLimit, finalProfile);
|
|
3089
3145
|
|
|
3090
3146
|
const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile, targetIgnoreList, targetSession);
|
|
3091
3147
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
@@ -3581,8 +3637,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3581
3637
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
3582
3638
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
3583
3639
|
|
|
3584
|
-
const { processedText: cleanStdout, wasTruncated: truncOut } = truncateIfFree(stdout, license.isPro, charLimit);
|
|
3585
|
-
const { processedText: cleanStderr, wasTruncated: truncErr } = truncateIfFree(stderr, license.isPro, charLimit);
|
|
3640
|
+
const { processedText: cleanStdout, wasTruncated: truncOut } = truncateIfFree(stdout, license.isPro, charLimit, targetProfile);
|
|
3641
|
+
const { processedText: cleanStderr, wasTruncated: truncErr } = truncateIfFree(stderr, license.isPro, charLimit, targetProfile);
|
|
3586
3642
|
|
|
3587
3643
|
const resCommand = performSanitization(command, targetProfile, targetIgnoreList, targetSession);
|
|
3588
3644
|
const resStdout = performSanitization(cleanStdout, targetProfile, targetIgnoreList, targetSession);
|
|
@@ -3751,7 +3807,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3751
3807
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
3752
3808
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
3753
3809
|
|
|
3754
|
-
const { processedText, wasTruncated: wasCharTruncated } = truncateIfFree(capped, license.isPro, charLimit);
|
|
3810
|
+
const { processedText, wasTruncated: wasCharTruncated } = truncateIfFree(capped, license.isPro, charLimit, targetProfile);
|
|
3755
3811
|
const currentSession = getSessionMap(session_id);
|
|
3756
3812
|
const currentIgnoreList = getSessionIgnoreList(session_id);
|
|
3757
3813
|
const { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, currentIgnoreList, currentSession);
|
|
@@ -3815,7 +3871,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3815
3871
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
3816
3872
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
3817
3873
|
|
|
3818
|
-
const { processedText, wasTruncated } = truncateIfFree(diffOutput, license.isPro, charLimit);
|
|
3874
|
+
const { processedText, wasTruncated } = truncateIfFree(diffOutput, license.isPro, charLimit, targetProfile);
|
|
3819
3875
|
const { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, targetIgnoreList, targetSession);
|
|
3820
3876
|
|
|
3821
3877
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
@@ -4621,10 +4677,11 @@ export function performSanitization(text, profile, ignoreList = null, customSess
|
|
|
4621
4677
|
return { scrubbedText: result.scrubbedText, newTokens };
|
|
4622
4678
|
}
|
|
4623
4679
|
|
|
4624
|
-
export function truncateIfFree(text, isPro, charLimit = 15000) {
|
|
4680
|
+
export function truncateIfFree(text, isPro, charLimit = 15000, profile = 'general') {
|
|
4625
4681
|
if (!isPro && text.length > charLimit) {
|
|
4626
|
-
|
|
4627
|
-
|
|
4682
|
+
const safeProf = encodeURIComponent(String(profile || 'general').toLowerCase());
|
|
4683
|
+
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Input truncated to ${charLimit.toLocaleString()} characters (Free Tier Limit for profile '${profile || 'general'}').${colors.reset}\n${colors.cyan}[Upgrade] Set PRIVACYSCRUBBER_KEY to your PRO/TEAMS/SDK license key or start a 7-day B2B trial ('npx @privacyscrubber/mcp-server trial'): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${safeProf}&utm_source=mcp&utm_medium=truncation&utm_campaign=teams_upgrade${colors.reset}\n`);
|
|
4684
|
+
const upsellNotice = `\n\n[PrivacyScrubber Free Tier: Payload truncated to ${charLimit.toLocaleString()} chars (${profile || 'general'}). Upgrade to PRO ($15/mo or $110 LT), TEAMS ($99/mo flat) or Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${safeProf}&utm_source=mcp&utm_medium=truncation&utm_campaign=teams_upgrade | CLI Trial: npx @privacyscrubber/mcp-server trial]`;
|
|
4628
4685
|
return { processedText: text.substring(0, charLimit), wasTruncated: true, upsellNotice, charLimit };
|
|
4629
4686
|
}
|
|
4630
4687
|
return { processedText: text, wasTruncated: false, upsellNotice: "", charLimit };
|
package/install.js
CHANGED
|
@@ -496,7 +496,7 @@ Options:
|
|
|
496
496
|
console.log(`[SUMMARY] ${options.remove ? 'Cleaned' : 'Configured'} ${configuredCount} AI client(s) successfully.`);
|
|
497
497
|
if (!options.remove) {
|
|
498
498
|
console.log('Next step: Restart your AI IDE or Claude Desktop to activate PrivacyScrubber.');
|
|
499
|
-
console.log('\x1b[36m💼 B2B 7-Day Free Trial (TEAMS/SDK): npx @privacyscrubber/mcp-server trial\x1b[0m');
|
|
499
|
+
console.log('\x1b[36m💼 B2B 7-Day Free Trial (TEAMS $99/mo · SDK $299/mo): npx @privacyscrubber/mcp-server trial | https://privacyscrubber.com/pricing/?tier=teams&trial=true&utm_source=mcp_install\x1b[0m');
|
|
500
500
|
console.log('\x1b[36m📋 Generate CISO Procurement Memo: npx @privacyscrubber/mcp-server memo\x1b[0m');
|
|
501
501
|
console.log('\x1b[33m⭐ Enjoying zero-trust privacy? Star the repo on GitHub: https://github.com/moxno/privacyscrubber-mcp\x1b[0m');
|
|
502
502
|
console.log('\x1b[90m One-click CLI: gh repo star moxno/privacyscrubber-mcp && gh repo star moxno/ztds.ai\x1b[0m');
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "privacyscrubber/pii-masking-mcp",
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.8",
|
|
5
5
|
"description": "CISO-Approved Zero-Trust PII & Secrets Redaction MCP Server for Cursor, Windsurf, and Claude Desktop. Prevent API leaks and comply with HIPAA/SOC 2 locally.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "PrivacyScrubber",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privacyscrubber/mcp-server",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.8",
|
|
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
|
@@ -27,7 +27,7 @@ const cmdIndex = args.indexOf("--");
|
|
|
27
27
|
|
|
28
28
|
if (args.includes("--version") || args.includes("-v")) {
|
|
29
29
|
const pkgPath = path.resolve(__dirname, './package.json');
|
|
30
|
-
let version = '2.4.
|
|
30
|
+
let version = '2.4.8';
|
|
31
31
|
try { version = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version; } catch (_) {}
|
|
32
32
|
console.log(`ps-guard / pii-masking-run v${version} (PrivacyScrubber MCP)`);
|
|
33
33
|
process.exit(0);
|
|
@@ -36,6 +36,7 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
36
36
|
const isDiffMode = args.includes("--diff");
|
|
37
37
|
const isRulesMode = args.includes("--rules");
|
|
38
38
|
const isHookMode = args.includes("--hook");
|
|
39
|
+
const isWrapMode = args.includes("--wrap");
|
|
39
40
|
const isJsonMode = args.includes("--json");
|
|
40
41
|
const isRestoreMode = args.includes("--restore") || args.includes("--unscrub");
|
|
41
42
|
let tokenMapInput = null;
|
|
@@ -48,19 +49,44 @@ const payloadIdx = args.indexOf("--payload");
|
|
|
48
49
|
if (payloadIdx !== -1 && args[payloadIdx + 1]) {
|
|
49
50
|
hookPayloadArg = args[payloadIdx + 1];
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
+
|
|
53
|
+
let wrapCommandArgs = [];
|
|
54
|
+
if (isWrapMode) {
|
|
55
|
+
if (cmdIndex !== -1 && args.slice(cmdIndex + 1).length > 0) {
|
|
56
|
+
wrapCommandArgs = args.slice(cmdIndex + 1);
|
|
57
|
+
} else {
|
|
58
|
+
const wrapIdx = args.indexOf("--wrap");
|
|
59
|
+
const afterWrap = args.slice(wrapIdx + 1);
|
|
60
|
+
for (let i = 0; i < afterWrap.length; i++) {
|
|
61
|
+
const a = afterWrap[i];
|
|
62
|
+
if ((a === "--profile" || a === "--key" || a === "--token-map") && afterWrap[i + 1]) {
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (a === "--quiet" || a === "-q" || a === "--verbose" || a === "--json") {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
wrapCommandArgs = afterWrap.slice(i);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const hasCommand = (cmdIndex !== -1 && args.slice(cmdIndex + 1).length > 0) || wrapCommandArgs.length > 0;
|
|
52
76
|
const hasStdin = !process.stdin.isTTY;
|
|
53
77
|
|
|
54
|
-
if (args.includes("-h") || args.includes("--help") || (!hasCommand && !isDiffMode && !isRulesMode && !isHookMode && !isRestoreMode && !hasStdin)) {
|
|
78
|
+
if (args.includes("-h") || args.includes("--help") || (!hasCommand && !isDiffMode && !isRulesMode && !isHookMode && !isWrapMode && !isRestoreMode && !hasStdin)) {
|
|
55
79
|
console.log("PrivacyScrubber CLI & Agentic Guard: ps-guard / pii-masking-run");
|
|
56
80
|
console.log("\nUsage:");
|
|
57
81
|
console.log(" npx ps-guard [options] -- <command> [args...]");
|
|
82
|
+
console.log(" npx ps-guard --wrap [options] [--] <mcp-server-cmd> [args...]");
|
|
58
83
|
console.log(" cat <file> | npx ps-guard [options]");
|
|
59
84
|
console.log(" npx ps-guard --diff [--staged] [options]");
|
|
60
85
|
console.log(" npx ps-guard --restore --token-map <file|json> [options]");
|
|
61
86
|
console.log(" npx ps-guard --rules [targets]");
|
|
62
87
|
console.log(" npx ps-guard --hook (Claude Code & Cursor IDE PreToolUse / beforeReadFile hook)");
|
|
63
88
|
console.log("\nOptions:");
|
|
89
|
+
console.log(" --wrap Transparent JSON-RPC 2.0 stdio proxy for any 3rd-party MCP server");
|
|
64
90
|
console.log(" --profile <name> Specify detection profile (e.g. General, Dev, Medical, Legal, HR)");
|
|
65
91
|
console.log(" --key <licKey> PrivacyScrubber PRO License Key");
|
|
66
92
|
console.log(" --json Output structured JSON (scrubbedText, tokenMap, count, telemetry)");
|
|
@@ -75,6 +101,7 @@ if (args.includes("-h") || args.includes("--help") || (!hasCommand && !isDiffMod
|
|
|
75
101
|
console.log(" -h, --help Show this help screen");
|
|
76
102
|
console.log("\nExamples:");
|
|
77
103
|
console.log(" npx ps-guard -- cat database-dump.sql");
|
|
104
|
+
console.log(" npx ps-guard --wrap -- npx -y @modelcontextprotocol/server-postgres postgresql://localhost/mydb");
|
|
78
105
|
console.log(" npx ps-guard --profile dev -- npm test");
|
|
79
106
|
console.log(" cat .env | npx ps-guard --profile dev --json");
|
|
80
107
|
console.log(" cat ai_reply.txt | npx ps-guard --restore --token-map map.json");
|
|
@@ -425,11 +452,27 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
425
452
|
trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
|
|
426
453
|
};
|
|
427
454
|
|
|
455
|
+
const agenticProtocol = {
|
|
456
|
+
protocolsSupported: ['MCP-1.0 (stdio/SSE)', 'A2A-1.0 (Agent2Agent)', 'ZTDS-RFC-v1.0'],
|
|
457
|
+
agentCardUrl: 'https://privacyscrubber.com/.well-known/agent-card.json',
|
|
458
|
+
ztdsStandardCardUrl: 'https://ztds.ai/.well-known/agent-card.json',
|
|
459
|
+
regulatorySafeHarbor: [
|
|
460
|
+
'GDPR-Art-4(5)-Pseudonymization',
|
|
461
|
+
'GDPR-Art-25-Privacy-by-Design',
|
|
462
|
+
'GDPR-Art-32-TOMs',
|
|
463
|
+
'EU-AI-Act-Art-50',
|
|
464
|
+
'EU-AI-Act-Art-17-EN-18286-QMS',
|
|
465
|
+
'CNIL-ICO-Agentic-Memory-Compartmentalization',
|
|
466
|
+
'IL-PPL-Amendment-13-Sec-17'
|
|
467
|
+
],
|
|
468
|
+
zeroCleartextEgress: true
|
|
469
|
+
};
|
|
470
|
+
|
|
428
471
|
const statutoryCitations = [
|
|
429
472
|
{
|
|
430
|
-
statute: 'EU AI Act (Art. 50)',
|
|
431
|
-
scope: 'AI Transparency &
|
|
432
|
-
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
|
|
473
|
+
statute: 'EU AI Act (Art. 50 & Art. 17 / EN 18286:2026 QMS)',
|
|
474
|
+
scope: 'AI Transparency, Data Minimization & Harmonized QMS Governance',
|
|
475
|
+
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models and supports Article 17 Quality Management System (EN 18286:2026) data governance controls. Informational mapping, not a compliance determination.'
|
|
433
476
|
},
|
|
434
477
|
{
|
|
435
478
|
statute: 'GDPR (Art. 4, 25, 28, 32)',
|
|
@@ -461,6 +504,7 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
461
504
|
frameworksList: Array.from(frameworksSet),
|
|
462
505
|
statutoryCitations,
|
|
463
506
|
ipHoldings,
|
|
507
|
+
agenticProtocol,
|
|
464
508
|
multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
|
|
465
509
|
};
|
|
466
510
|
}
|
|
@@ -775,8 +819,183 @@ if (isDiffMode) {
|
|
|
775
819
|
}
|
|
776
820
|
}
|
|
777
821
|
|
|
778
|
-
// Mode
|
|
779
|
-
if (
|
|
822
|
+
// Mode 2.5: Transparent MCP JSON-RPC 2.0 Stdio Proxy Wrapper (--wrap)
|
|
823
|
+
if (isWrapMode) {
|
|
824
|
+
if (wrapCommandArgs.length === 0) {
|
|
825
|
+
console.error('Error: --wrap requires an upstream MCP server command (e.g. npx ps-guard --wrap -- npx -y @modelcontextprotocol/server-postgres ...)');
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const SKIP_SANITIZE_KEYS = new Set([
|
|
830
|
+
'jsonrpc', 'id', 'method', 'type', 'mimeType', 'uri', 'name', 'role',
|
|
831
|
+
'protocolVersion', 'cursor', 'nextCursor', 'isError', 'version'
|
|
832
|
+
]);
|
|
833
|
+
|
|
834
|
+
const deepDetokenizeParams = (node, depth = 0) => {
|
|
835
|
+
if (depth > 20 || node === null || node === undefined) return node;
|
|
836
|
+
if (typeof node === 'string') {
|
|
837
|
+
if (Object.keys(localSessionMap).length === 0) return node;
|
|
838
|
+
const res = PrivacyScrubberCore.unscrubText(node, localSessionMap);
|
|
839
|
+
return (res && typeof res.restoredText === 'string') ? res.restoredText : node;
|
|
840
|
+
}
|
|
841
|
+
if (Array.isArray(node)) {
|
|
842
|
+
return node.map(item => deepDetokenizeParams(item, depth + 1));
|
|
843
|
+
}
|
|
844
|
+
if (typeof node === 'object') {
|
|
845
|
+
const out = {};
|
|
846
|
+
for (const [k, v] of Object.entries(node)) {
|
|
847
|
+
if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;
|
|
848
|
+
out[k] = deepDetokenizeParams(v, depth + 1);
|
|
849
|
+
}
|
|
850
|
+
return out;
|
|
851
|
+
}
|
|
852
|
+
return node;
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
const deepSanitizeResult = (node, keyName = '', depth = 0) => {
|
|
856
|
+
if (depth > 20 || node === null || node === undefined) return node;
|
|
857
|
+
if (typeof node === 'string') {
|
|
858
|
+
if (SKIP_SANITIZE_KEYS.has(keyName) || node.length < 4) return node;
|
|
859
|
+
return performSanitization(node);
|
|
860
|
+
}
|
|
861
|
+
if (Array.isArray(node)) {
|
|
862
|
+
return node.map(item => deepSanitizeResult(item, keyName, depth + 1));
|
|
863
|
+
}
|
|
864
|
+
if (typeof node === 'object') {
|
|
865
|
+
const out = {};
|
|
866
|
+
for (const [k, v] of Object.entries(node)) {
|
|
867
|
+
if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;
|
|
868
|
+
if (k === 'inputSchema' || k === 'outputSchema' || k === 'capabilities' || k === 'serverInfo' || k === '_meta') {
|
|
869
|
+
out[k] = v;
|
|
870
|
+
} else {
|
|
871
|
+
out[k] = deepSanitizeResult(v, k, depth + 1);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return out;
|
|
875
|
+
}
|
|
876
|
+
return node;
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
const zeroizeSessionMap = () => {
|
|
880
|
+
for (const k of Object.keys(localSessionMap)) {
|
|
881
|
+
localSessionMap[k] = '';
|
|
882
|
+
delete localSessionMap[k];
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
const [cmdBin, ...cmdRest] = wrapCommandArgs;
|
|
887
|
+
const child = spawn(cmdBin, cmdRest, {
|
|
888
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
889
|
+
shell: process.platform === 'win32'
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
// Intercept client -> upstream MCP server (JSON-RPC requests on stdin)
|
|
893
|
+
const rlClientIn = readline.createInterface({
|
|
894
|
+
input: process.stdin,
|
|
895
|
+
terminal: false
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
rlClientIn.on('line', (line) => {
|
|
899
|
+
if (!child.stdin || child.stdin.destroyed) return;
|
|
900
|
+
const trimmed = line.trim();
|
|
901
|
+
if (!trimmed.startsWith('{')) {
|
|
902
|
+
child.stdin.write(line + '\n');
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
try {
|
|
906
|
+
const msg = JSON.parse(trimmed);
|
|
907
|
+
if (msg && typeof msg === 'object' && msg.params && (msg.method === 'tools/call' || msg.method === 'prompts/get' || msg.method === 'resources/read' || msg.method === 'sampling/createMessage')) {
|
|
908
|
+
msg.params = deepDetokenizeParams(msg.params);
|
|
909
|
+
child.stdin.write(JSON.stringify(msg) + '\n');
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
} catch (_) {}
|
|
913
|
+
child.stdin.write(line + '\n');
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
rlClientIn.on('close', () => {
|
|
917
|
+
if (child.stdin && !child.stdin.destroyed) {
|
|
918
|
+
child.stdin.end();
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
// Intercept upstream MCP server -> client (JSON-RPC responses/notifications on stdout)
|
|
923
|
+
const rlUpstreamOut = readline.createInterface({
|
|
924
|
+
input: child.stdout,
|
|
925
|
+
terminal: false
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
rlUpstreamOut.on('line', (line) => {
|
|
929
|
+
const trimmed = line.trim();
|
|
930
|
+
if (!trimmed.startsWith('{')) {
|
|
931
|
+
process.stdout.write(performSanitization(line) + '\n');
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
try {
|
|
935
|
+
const msg = JSON.parse(trimmed);
|
|
936
|
+
if (msg && typeof msg === 'object') {
|
|
937
|
+
if (msg.result && typeof msg.result === 'object') {
|
|
938
|
+
const isSchemaListing = Array.isArray(msg.result.tools) || Array.isArray(msg.result.prompts) || Array.isArray(msg.result.resourceTemplates) || Boolean(msg.result.protocolVersion);
|
|
939
|
+
if (!isSchemaListing) {
|
|
940
|
+
msg.result = deepSanitizeResult(msg.result);
|
|
941
|
+
const receipt = buildPsr2Receipt(localSessionMap);
|
|
942
|
+
const telemetry = buildCisoAuditTelemetry(localSessionMap);
|
|
943
|
+
msg.result._meta = Object.assign({}, msg.result._meta || {}, {
|
|
944
|
+
ztds_receipt: {
|
|
945
|
+
version: receipt.version,
|
|
946
|
+
timestamp: receipt.timestamp,
|
|
947
|
+
manifestSha256: receipt.manifestSha256,
|
|
948
|
+
receiptSha256: receipt.receiptSha256,
|
|
949
|
+
entityCounts: receipt.entityCounts,
|
|
950
|
+
tokensTotal: receipt.tokensTotal,
|
|
951
|
+
riskLevel: telemetry.riskLevel,
|
|
952
|
+
regulatorySafeHarbor: telemetry.agenticProtocol ? telemetry.agenticProtocol.regulatorySafeHarbor : [],
|
|
953
|
+
zero_cleartext_egress: true
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
} else if (msg.params && typeof msg.params === 'object') {
|
|
958
|
+
msg.params = deepSanitizeResult(msg.params);
|
|
959
|
+
}
|
|
960
|
+
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
} catch (_) {}
|
|
964
|
+
process.stdout.write(performSanitization(line) + '\n');
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
const rlUpstreamErr = readline.createInterface({
|
|
968
|
+
input: child.stderr,
|
|
969
|
+
terminal: false
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
rlUpstreamErr.on('line', (line) => {
|
|
973
|
+
process.stderr.write(performSanitization(line) + '\n');
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
const handleSignal = (sig) => {
|
|
977
|
+
zeroizeSessionMap();
|
|
978
|
+
if (!child.killed) {
|
|
979
|
+
try { child.kill(sig); } catch (_) {}
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
process.on('SIGINT', () => handleSignal('SIGINT'));
|
|
983
|
+
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
984
|
+
process.on('exit', zeroizeSessionMap);
|
|
985
|
+
|
|
986
|
+
child.on('close', (code) => {
|
|
987
|
+
emitProofFeedback();
|
|
988
|
+
zeroizeSessionMap();
|
|
989
|
+
process.exit(code || 0);
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
child.on('error', (err) => {
|
|
993
|
+
zeroizeSessionMap();
|
|
994
|
+
console.error(`Error spawning wrapped MCP server: ${err.message}`);
|
|
995
|
+
process.exit(1);
|
|
996
|
+
});
|
|
997
|
+
} else if (hasStdin && !hasCommand && !isRestoreMode && !isDiffMode && !isRulesMode) {
|
|
998
|
+
// Mode 3: Stdin Stream Mode (pipe)
|
|
780
999
|
if (isJsonMode) {
|
|
781
1000
|
let rawInput = '';
|
|
782
1001
|
process.stdin.setEncoding('utf8');
|