data-compliance-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/LICENSE +7 -0
- package/README.md +115 -0
- package/glama.json +18 -0
- package/package.json +35 -0
- package/server.json +43 -0
- package/smithery.yaml +12 -0
- package/src/server.js +787 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.0] - 2026-04-21
|
|
4
|
+
### Added
|
|
5
|
+
- Initial release
|
|
6
|
+
- validate_data_safety tool: AI-powered data classification with verdict SAFE_TO_PROCESS / REDACT_BEFORE_PASSING / DO_NOT_STORE / ESCALATE
|
|
7
|
+
- get_safety_report tool: batch classification and audit report generation (paid tier)
|
|
8
|
+
- IPinfo jurisdiction detection: automatically identifies applicable regulations from data origin IP
|
|
9
|
+
- HaveIBeenPwned k-anonymity credential breach checking
|
|
10
|
+
- AbuseIPDB threat intelligence for IP addresses in payload (paid tier)
|
|
11
|
+
- PII pattern pre-screening: email, credit card, SSN, IBAN, NI number, phone, passport, credentials
|
|
12
|
+
- Free tier: 20 classifications/month, no API key required
|
|
13
|
+
- Pro tier: 5,000 classifications/month at $49/month
|
|
14
|
+
- Enterprise tier: unlimited at $199/month
|
|
15
|
+
- Stripe webhook integration for automated API key delivery
|
|
16
|
+
- Regulation coverage: GDPR, UK GDPR, HIPAA, PCI-DSS, CCPA, PIPEDA, LGPD, PDPA (SG), Privacy Act (AU), DPDP (India)
|
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Data Compliance Classifier MCP
|
|
2
|
+
|
|
3
|
+
Your agent is about to store customer data. Is it safe to? This tool tells you in one call.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Before your agent stores, transmits, logs, or passes any data to another system — call `validate_data_safety`. Get back a clear verdict: **SAFE_TO_PROCESS**, **REDACT_BEFORE_PASSING**, **DO_NOT_STORE**, or **ESCALATE**. Your agent acts on the verdict immediately. No human interpretation needed.
|
|
8
|
+
|
|
9
|
+
Prevents GDPR, HIPAA, and PCI-DSS violations before they happen — not after.
|
|
10
|
+
|
|
11
|
+
## Why this exists
|
|
12
|
+
|
|
13
|
+
Autonomous agents handle data from users, APIs, forms, and external sources constantly. Most agents process that data without checking whether they should. When something goes wrong — a GDPR breach, a leaked credential, a PII write to an unencrypted store — it's already too late.
|
|
14
|
+
|
|
15
|
+
This tool gives agents a pre-action safety check. One call, clear verdict, agent proceeds or halts.
|
|
16
|
+
|
|
17
|
+
## Tools
|
|
18
|
+
|
|
19
|
+
### `validate_data_safety` (free tier)
|
|
20
|
+
|
|
21
|
+
Call this BEFORE your agent stores, transmits, or passes any data payload.
|
|
22
|
+
|
|
23
|
+
**Input:**
|
|
24
|
+
- `payload` — the data to classify (any string, JSON, form data, API response)
|
|
25
|
+
- `context` — what your agent is about to do with it (improves accuracy)
|
|
26
|
+
- `data_origin_ip` — optional IP for jurisdiction detection (GDPR if EU, CCPA if US, etc.)
|
|
27
|
+
- `jurisdiction` — optional override if IP unavailable
|
|
28
|
+
|
|
29
|
+
**Output:**
|
|
30
|
+
- `verdict` — SAFE_TO_PROCESS / REDACT_BEFORE_PASSING / DO_NOT_STORE / ESCALATE
|
|
31
|
+
- `sensitivity_level` — PUBLIC / INTERNAL / CONFIDENTIAL / RESTRICTED
|
|
32
|
+
- `detected_categories` — PII, PHI, PCI, CREDENTIALS, FINANCIAL, LOCATION, etc.
|
|
33
|
+
- `applicable_regulations` — GDPR, HIPAA, PCI-DSS, CCPA, PIPEDA, LGPD, etc.
|
|
34
|
+
- `recommended_action` — one sentence telling your agent exactly what to do next
|
|
35
|
+
- `jurisdiction_detected` — country detected from IP
|
|
36
|
+
- `credential_check` — breach status from HaveIBeenPwned k-anonymity API
|
|
37
|
+
- `patterns_detected` — pre-screened PII patterns found
|
|
38
|
+
|
|
39
|
+
### `get_safety_report` (paid tier)
|
|
40
|
+
|
|
41
|
+
Batch classification for up to 50 payloads plus audit-ready compliance reports.
|
|
42
|
+
|
|
43
|
+
**Modes:**
|
|
44
|
+
- `BATCH` — classify multiple payloads with full AI reasoning + AbuseIPDB threat intelligence
|
|
45
|
+
- `AUDIT` — generate a structured compliance report for a dataset description
|
|
46
|
+
|
|
47
|
+
## Data privacy
|
|
48
|
+
|
|
49
|
+
We do not store or log your data payloads. All payloads are analysed in memory and immediately discarded. Credential checks use the HaveIBeenPwned k-anonymity API — your credentials are never transmitted in full. Only the first 5 characters of a SHA-1 hash are sent.
|
|
50
|
+
|
|
51
|
+
## Data sources
|
|
52
|
+
|
|
53
|
+
- **Claude AI** — sensitivity classification and regulatory mapping
|
|
54
|
+
- **IPinfo** (ipinfo.io) — jurisdiction detection from IP address
|
|
55
|
+
- **HaveIBeenPwned** (haveibeenpwned.com) — credential breach checking via k-anonymity
|
|
56
|
+
- **AbuseIPDB** (abuseipdb.com) — IP threat intelligence (paid tier)
|
|
57
|
+
|
|
58
|
+
## Pricing
|
|
59
|
+
|
|
60
|
+
| Plan | Price | Classifications |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| Free | $0 | 20/month, no API key needed |
|
|
63
|
+
| Pro | $49/month | 5,000/month |
|
|
64
|
+
| Enterprise | $199/month | Unlimited |
|
|
65
|
+
|
|
66
|
+
Upgrade at [kordagencies.com](https://kordagencies.com)
|
|
67
|
+
|
|
68
|
+
## Quick start
|
|
69
|
+
|
|
70
|
+
No API key needed for free tier:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"data-compliance": {
|
|
75
|
+
"url": "https://data-compliance-mcp-production.up.railway.app"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
With paid API key:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"data-compliance": {
|
|
85
|
+
"url": "https://data-compliance-mcp-production.up.railway.app",
|
|
86
|
+
"headers": {
|
|
87
|
+
"x-api-key": "your_api_key_here"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Example call
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
curl -X POST https://data-compliance-mcp-production.up.railway.app \
|
|
97
|
+
-H "Content-Type: application/json" \
|
|
98
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"validate_data_safety","arguments":{"payload":"{\"name\":\"John Smith\",\"email\":\"john@example.com\",\"dob\":\"1985-03-12\",\"address\":\"14 Baker Street, London\"}","context":"write to customer database","jurisdiction":"EU"}}}'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Expected response:
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"verdict": "DO_NOT_STORE",
|
|
105
|
+
"sensitivity_level": "RESTRICTED",
|
|
106
|
+
"detected_categories": ["PII"],
|
|
107
|
+
"applicable_regulations": ["GDPR"],
|
|
108
|
+
"recommended_action": "Do not store without explicit consent and a documented lawful basis under GDPR Article 6.",
|
|
109
|
+
"jurisdiction_detected": "EU"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Legal
|
|
114
|
+
|
|
115
|
+
Classification is AI-powered and for informational purposes only. Does not constitute legal advice and does not guarantee regulatory compliance. Full terms: [kordagencies.com/terms.html](https://kordagencies.com/terms.html)
|
package/glama.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Data Compliance Classifier MCP",
|
|
3
|
+
"description": "Classify data safety before your agent stores or shares it. GDPR, HIPAA, PCI-DSS, CCPA. AI-powered with jurisdiction detection, credential breach checking, and threat intelligence. Free tier: 20/month.",
|
|
4
|
+
"url": "https://data-compliance-mcp-production.up.railway.app",
|
|
5
|
+
"homepage": "https://kordagencies.com",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"categories": ["compliance", "security", "data-privacy", "ai"],
|
|
8
|
+
"tools": [
|
|
9
|
+
{
|
|
10
|
+
"name": "validate_data_safety",
|
|
11
|
+
"description": "Classify a data payload before storing or transmitting. Returns SAFE_TO_PROCESS, REDACT_BEFORE_PASSING, DO_NOT_STORE, or ESCALATE with applicable regulations."
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "get_safety_report",
|
|
15
|
+
"description": "Batch classify up to 50 payloads or generate an audit-ready compliance report. Paid tier."
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "data-compliance-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Classify data safety before your agent stores or shares it. GDPR, HIPAA, PCI-DSS, CCPA. AI-powered.",
|
|
5
|
+
"main": "src/server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node src/server.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"mcp",
|
|
11
|
+
"agent",
|
|
12
|
+
"gdpr",
|
|
13
|
+
"hipaa",
|
|
14
|
+
"pci-dss",
|
|
15
|
+
"ccpa",
|
|
16
|
+
"data-classification",
|
|
17
|
+
"pii",
|
|
18
|
+
"pii-detection",
|
|
19
|
+
"data-safety",
|
|
20
|
+
"compliance",
|
|
21
|
+
"privacy",
|
|
22
|
+
"data-privacy",
|
|
23
|
+
"sensitive-data",
|
|
24
|
+
"validator"
|
|
25
|
+
],
|
|
26
|
+
"author": "ojas1",
|
|
27
|
+
"license": "UNLICENSED",
|
|
28
|
+
"homepage": "https://kordagencies.com",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/OjasKord/data-compliance-mcp"
|
|
32
|
+
},
|
|
33
|
+
"mcpName": "io.github.OjasKord/data-compliance-mcp",
|
|
34
|
+
"dependencies": {}
|
|
35
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.OjasKord/data-compliance-mcp",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Classify data safety before storing or sharing. GDPR, HIPAA, PCI-DSS, CCPA. AI-powered.",
|
|
6
|
+
"title": "Data Compliance Classifier MCP",
|
|
7
|
+
"websiteUrl": "https://kordagencies.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/OjasKord/data-compliance-mcp",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"packages": [
|
|
13
|
+
{
|
|
14
|
+
"registryType": "npm",
|
|
15
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
16
|
+
"identifier": "data-compliance-mcp",
|
|
17
|
+
"version": "1.0.0",
|
|
18
|
+
"transport": {
|
|
19
|
+
"type": "stdio"
|
|
20
|
+
},
|
|
21
|
+
"environmentVariables": [
|
|
22
|
+
{
|
|
23
|
+
"name": "ANTHROPIC_API_KEY",
|
|
24
|
+
"description": "Anthropic API key for AI classification",
|
|
25
|
+
"isRequired": true,
|
|
26
|
+
"isSecret": true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "ABUSEIPDB_API_KEY",
|
|
30
|
+
"description": "AbuseIPDB API key for threat intelligence (optional - disables IP threat checks if not set)",
|
|
31
|
+
"isRequired": false,
|
|
32
|
+
"isSecret": true
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"remotes": [
|
|
38
|
+
{
|
|
39
|
+
"type": "streamable-http",
|
|
40
|
+
"url": "https://data-compliance-mcp-production.up.railway.app"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
startCommand:
|
|
2
|
+
type: http
|
|
3
|
+
url: https://data-compliance-mcp-production.up.railway.app
|
|
4
|
+
configSchema:
|
|
5
|
+
type: object
|
|
6
|
+
properties:
|
|
7
|
+
apiKey:
|
|
8
|
+
type: string
|
|
9
|
+
description: "Data Compliance Classifier API key from kordagencies.com. Leave blank for free tier (20 classifications/month)."
|
|
10
|
+
x-from:
|
|
11
|
+
header: "x-api-key"
|
|
12
|
+
required: []
|
package/src/server.js
ADDED
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const https = require('https');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
const VERSION = '1.0.0';
|
|
7
|
+
const PERSIST_FILE = '/tmp/datacompliance_stats.json';
|
|
8
|
+
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || '';
|
|
9
|
+
const ABUSEIPDB_API_KEY = process.env.ABUSEIPDB_API_KEY || '';
|
|
10
|
+
const RESEND_API_KEY = process.env.RESEND_API_KEY || '';
|
|
11
|
+
const STATS_KEY = process.env.STATS_KEY || 'ojas2026';
|
|
12
|
+
const PORT = process.env.PORT || 3000;
|
|
13
|
+
|
|
14
|
+
const freeTierUsage = new Map();
|
|
15
|
+
const usageLog = [];
|
|
16
|
+
const FREE_TIER_LIMIT = 20;
|
|
17
|
+
const FREE_TIER_WARNING = 16;
|
|
18
|
+
const apiKeys = new Map();
|
|
19
|
+
const PLAN_LIMITS = { pro: 5000, enterprise: Infinity };
|
|
20
|
+
|
|
21
|
+
const LEGAL_DISCLAIMER = 'Classification is AI-powered and for informational purposes only. Does not constitute legal advice and does not guarantee regulatory compliance. We do not store or log your data payload — it is analysed in memory and immediately discarded. Jurisdiction detection uses IPinfo (ipinfo.io). Credential checks use the Pwned Passwords k-anonymity API (haveibeenpwned.com) — your credentials are never transmitted in full. Threat checks use AbuseIPDB (abuseipdb.com). Provider maximum liability is limited to subscription fees paid in the preceding 3 months. Full terms: kordagencies.com/terms.html';
|
|
22
|
+
|
|
23
|
+
function nowISO() { return new Date().toISOString(); }
|
|
24
|
+
|
|
25
|
+
function saveStats() {
|
|
26
|
+
try {
|
|
27
|
+
fs.writeFileSync(PERSIST_FILE, JSON.stringify({
|
|
28
|
+
freeTierUsage: Array.from(freeTierUsage.entries()),
|
|
29
|
+
usageLog: usageLog.slice(-1000)
|
|
30
|
+
}));
|
|
31
|
+
} catch(e) { console.error('Stats save error:', e.message); }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function loadStats() {
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(PERSIST_FILE)) {
|
|
37
|
+
const data = JSON.parse(fs.readFileSync(PERSIST_FILE, 'utf8'));
|
|
38
|
+
if (data.freeTierUsage) data.freeTierUsage.forEach(([k, v]) => freeTierUsage.set(k, v));
|
|
39
|
+
if (data.usageLog) usageLog.push(...data.usageLog);
|
|
40
|
+
console.log('Stats loaded: ' + freeTierUsage.size + ' IPs, ' + usageLog.length + ' calls');
|
|
41
|
+
}
|
|
42
|
+
} catch(e) { console.error('Stats load error:', e.message); }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function generateApiKey() { return 'dcc_' + crypto.randomBytes(24).toString('hex'); }
|
|
46
|
+
function getPlanFromProduct(name) {
|
|
47
|
+
if (!name) return 'pro';
|
|
48
|
+
return name.toLowerCase().includes('enterprise') ? 'enterprise' : 'pro';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── EXTERNAL APIs ────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
// IPinfo Lite — free, no key, country-level jurisdiction detection
|
|
54
|
+
async function getJurisdiction(ip) {
|
|
55
|
+
if (!ip || ip === 'unknown' || ip.startsWith('127.') || ip.startsWith('::1') || ip.startsWith('10.') || ip.startsWith('192.168.')) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const req = https.request({
|
|
60
|
+
hostname: 'ipinfo.io',
|
|
61
|
+
path: '/' + encodeURIComponent(ip) + '/country',
|
|
62
|
+
method: 'GET',
|
|
63
|
+
headers: { 'Accept': 'text/plain', 'User-Agent': 'DataCompliance-MCP/1.0' }
|
|
64
|
+
}, res => {
|
|
65
|
+
let d = ''; res.on('data', c => d += c);
|
|
66
|
+
res.on('end', () => {
|
|
67
|
+
const country = d.trim().toUpperCase();
|
|
68
|
+
if (country && country.length === 2) resolve(country);
|
|
69
|
+
else resolve(null);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
req.on('error', () => resolve(null));
|
|
73
|
+
req.setTimeout(3000, () => { req.destroy(); resolve(null); });
|
|
74
|
+
req.end();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Map country code to applicable regulations
|
|
79
|
+
function getRegulationsForCountry(countryCode) {
|
|
80
|
+
const EU_COUNTRIES = ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK','SI','ES','SE'];
|
|
81
|
+
const EEA_EXTRAS = ['IS','LI','NO'];
|
|
82
|
+
const regs = [];
|
|
83
|
+
if (EU_COUNTRIES.includes(countryCode) || EEA_EXTRAS.includes(countryCode)) regs.push('GDPR');
|
|
84
|
+
if (countryCode === 'GB') regs.push('UK_GDPR');
|
|
85
|
+
if (countryCode === 'US') regs.push('CCPA', 'HIPAA_IF_HEALTH', 'PCI_DSS_IF_PAYMENT');
|
|
86
|
+
if (countryCode === 'CA') regs.push('PIPEDA');
|
|
87
|
+
if (countryCode === 'AU') regs.push('PRIVACY_ACT_AU');
|
|
88
|
+
if (countryCode === 'BR') regs.push('LGPD');
|
|
89
|
+
if (countryCode === 'IN') regs.push('DPDP');
|
|
90
|
+
if (countryCode === 'SG') regs.push('PDPA_SG');
|
|
91
|
+
if (regs.length === 0) regs.push('LOCAL_PRIVACY_LAWS_APPLY');
|
|
92
|
+
return regs;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Pwned Passwords API — k-anonymity, free, no key
|
|
96
|
+
// Sends only first 5 chars of SHA-1 hash — full password never transmitted
|
|
97
|
+
async function checkPwnedPassword(password) {
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
const hash = crypto.createHash('sha1').update(password).digest('hex').toUpperCase();
|
|
100
|
+
const prefix = hash.slice(0, 5);
|
|
101
|
+
const suffix = hash.slice(5);
|
|
102
|
+
const req = https.request({
|
|
103
|
+
hostname: 'api.pwnedpasswords.com',
|
|
104
|
+
path: '/range/' + prefix,
|
|
105
|
+
method: 'GET',
|
|
106
|
+
headers: { 'User-Agent': 'DataCompliance-MCP/1.0', 'Add-Padding': 'true' }
|
|
107
|
+
}, res => {
|
|
108
|
+
let d = ''; res.on('data', c => d += c);
|
|
109
|
+
res.on('end', () => {
|
|
110
|
+
const lines = d.split('\r\n');
|
|
111
|
+
const match = lines.find(l => l.startsWith(suffix));
|
|
112
|
+
if (match) {
|
|
113
|
+
const count = parseInt(match.split(':')[1], 10);
|
|
114
|
+
resolve({ pwned: true, breach_count: count });
|
|
115
|
+
} else {
|
|
116
|
+
resolve({ pwned: false, breach_count: 0 });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
req.on('error', () => resolve(null));
|
|
121
|
+
req.setTimeout(3000, () => { req.destroy(); resolve(null); });
|
|
122
|
+
req.end();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// AbuseIPDB — requires API key, free 1000/day
|
|
127
|
+
async function checkAbuseIPDB(ip) {
|
|
128
|
+
if (!ABUSEIPDB_API_KEY || !ip || ip === 'unknown') return null;
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
const params = new URLSearchParams({ ipAddress: ip, maxAgeInDays: '90' });
|
|
131
|
+
const req = https.request({
|
|
132
|
+
hostname: 'api.abuseipdb.com',
|
|
133
|
+
path: '/api/v2/check?' + params.toString(),
|
|
134
|
+
method: 'GET',
|
|
135
|
+
headers: {
|
|
136
|
+
'Key': ABUSEIPDB_API_KEY,
|
|
137
|
+
'Accept': 'application/json',
|
|
138
|
+
'User-Agent': 'DataCompliance-MCP/1.0'
|
|
139
|
+
}
|
|
140
|
+
}, res => {
|
|
141
|
+
let d = ''; res.on('data', c => d += c);
|
|
142
|
+
res.on('end', () => {
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(d);
|
|
145
|
+
const data = parsed.data;
|
|
146
|
+
if (data) {
|
|
147
|
+
resolve({
|
|
148
|
+
abuse_confidence_score: data.abuseConfidenceScore,
|
|
149
|
+
total_reports: data.totalReports,
|
|
150
|
+
is_whitelisted: data.isWhitelisted,
|
|
151
|
+
usage_type: data.usageType,
|
|
152
|
+
isp: data.isp,
|
|
153
|
+
country_code: data.countryCode,
|
|
154
|
+
is_threat: data.abuseConfidenceScore >= 25
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
resolve(null);
|
|
158
|
+
}
|
|
159
|
+
} catch(e) { resolve(null); }
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
req.on('error', () => resolve(null));
|
|
163
|
+
req.setTimeout(4000, () => { req.destroy(); resolve(null); });
|
|
164
|
+
req.end();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Claude AI classification
|
|
169
|
+
async function callClaude(prompt) {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const body = JSON.stringify({
|
|
172
|
+
model: 'claude-sonnet-4-6',
|
|
173
|
+
max_tokens: 1024,
|
|
174
|
+
messages: [{ role: 'user', content: prompt }]
|
|
175
|
+
});
|
|
176
|
+
const req = https.request({
|
|
177
|
+
hostname: 'api.anthropic.com', path: '/v1/messages', method: 'POST',
|
|
178
|
+
headers: {
|
|
179
|
+
'x-api-key': ANTHROPIC_API_KEY,
|
|
180
|
+
'anthropic-version': '2023-06-01',
|
|
181
|
+
'content-type': 'application/json',
|
|
182
|
+
'content-length': Buffer.byteLength(body)
|
|
183
|
+
}
|
|
184
|
+
}, res => {
|
|
185
|
+
let d = ''; res.on('data', c => d += c);
|
|
186
|
+
res.on('end', () => {
|
|
187
|
+
try { resolve(JSON.parse(d).content?.[0]?.text || ''); }
|
|
188
|
+
catch(e) { reject(e); }
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
req.on('error', reject);
|
|
192
|
+
req.write(body); req.end();
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── PII PATTERN DETECTION ────────────────────────────────────────────────────
|
|
197
|
+
// Pre-screen payload before Claude — catches obvious patterns fast
|
|
198
|
+
function detectPatterns(payload) {
|
|
199
|
+
const str = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
200
|
+
const detected = [];
|
|
201
|
+
|
|
202
|
+
// Email addresses
|
|
203
|
+
if (/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g.test(str)) detected.push('EMAIL_ADDRESS');
|
|
204
|
+
// IP addresses
|
|
205
|
+
if (/\b(?:\d{1,3}\.){3}\d{1,3}\b/.test(str)) detected.push('IP_ADDRESS');
|
|
206
|
+
// Credit card patterns (Luhn-passable 13-19 digit sequences)
|
|
207
|
+
if (/\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b/.test(str.replace(/[\s\-]/g, ''))) detected.push('PAYMENT_CARD');
|
|
208
|
+
// US SSN
|
|
209
|
+
if (/\b\d{3}-\d{2}-\d{4}\b/.test(str)) detected.push('US_SSN');
|
|
210
|
+
// UK NI number
|
|
211
|
+
if (/\b[A-Z]{2}\d{6}[A-D]\b/i.test(str)) detected.push('UK_NI_NUMBER');
|
|
212
|
+
// IBAN
|
|
213
|
+
if (/\b[A-Z]{2}\d{2}[A-Z0-9]{4,30}\b/.test(str)) detected.push('IBAN');
|
|
214
|
+
// Phone numbers (loose)
|
|
215
|
+
if (/(\+\d{1,3}[\s\-]?)?\(?\d{3}\)?[\s\-]?\d{3}[\s\-]?\d{4}/.test(str)) detected.push('PHONE_NUMBER');
|
|
216
|
+
// Passport-like (letter + 7-9 digits)
|
|
217
|
+
if (/\b[A-Z]{1,2}\d{7,9}\b/.test(str)) detected.push('POSSIBLE_PASSPORT');
|
|
218
|
+
// Looks like a password/credential (contains special chars + length)
|
|
219
|
+
if (/(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}/.test(str)) detected.push('POSSIBLE_CREDENTIAL');
|
|
220
|
+
// Health-related keywords
|
|
221
|
+
if (/\b(diagnosis|prescription|medication|patient|dob|date.of.birth|blood.type|medical.record|health.condition|treatment|symptom|allergy|insurance.id)\b/i.test(str)) detected.push('POSSIBLE_HEALTH_DATA');
|
|
222
|
+
// AWS/API key patterns
|
|
223
|
+
if (/\b(AKIA|sk_live_|pk_live_|ghp_|xox[baprs]-)[A-Za-z0-9]{10,}\b/.test(str)) detected.push('API_KEY_OR_SECRET');
|
|
224
|
+
|
|
225
|
+
return detected;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Extract IPs from payload for threat checking
|
|
229
|
+
function extractIPs(payload) {
|
|
230
|
+
const str = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
231
|
+
const matches = str.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g) || [];
|
|
232
|
+
// Filter out private ranges
|
|
233
|
+
return [...new Set(matches)].filter(ip => {
|
|
234
|
+
const parts = ip.split('.').map(Number);
|
|
235
|
+
return !(parts[0] === 10 || parts[0] === 127 ||
|
|
236
|
+
(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
|
|
237
|
+
(parts[0] === 192 && parts[1] === 168));
|
|
238
|
+
}).slice(0, 5); // max 5 IPs to avoid burning API quota
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Extract possible credentials from payload
|
|
242
|
+
function extractCredentials(payload) {
|
|
243
|
+
const str = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
244
|
+
const results = [];
|
|
245
|
+
// Look for key:value or key=value patterns where value looks like a credential
|
|
246
|
+
const matches = str.matchAll(/(?:password|passwd|pwd|secret|token|credential|api.?key)\s*[=:]\s*["']?([A-Za-z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]{6,})/gi);
|
|
247
|
+
for (const m of matches) { results.push(m[1]); }
|
|
248
|
+
return results.slice(0, 3); // max 3 credentials
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ─── TOOL DEFINITIONS ─────────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
const tools = [
|
|
254
|
+
{
|
|
255
|
+
name: 'validate_data_safety',
|
|
256
|
+
description: 'Call this tool BEFORE your agent stores, transmits, logs, or passes any data payload to another system. Use when your agent has just received data from a user, form, API, webhook, or external source and needs to know whether it is safe to process. Prevents GDPR, HIPAA, and PCI-DSS violations before they happen — not after. Returns a clear verdict: SAFE_TO_PROCESS, REDACT_BEFORE_PASSING, DO_NOT_STORE, or ESCALATE. Each verdict tells the agent exactly what to do next — no human interpretation needed. Also use for: classifying customer records before database writes, screening scraped content before storage, checking API responses before caching, validating form submissions before processing. AI-powered analysis — NOT a simple pattern match. Combines Claude reasoning with live jurisdiction detection (IPinfo), credential breach checking (HaveIBeenPwned k-anonymity API), and PII pattern detection. LEGAL NOTICE: Classification is informational only and does not constitute legal advice. We do not store your data payload. Full terms: kordagencies.com/terms.html. Free tier: first 20 classifications/month, no API key needed.',
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: 'object',
|
|
259
|
+
properties: {
|
|
260
|
+
payload: { type: 'string', description: 'The data payload to classify. Can be any string, JSON object as string, form data, API response, or text content. The payload is analysed in memory and immediately discarded — never stored or logged.' },
|
|
261
|
+
context: { type: 'string', description: 'What your agent is about to do with this data (e.g. "write to database", "send to third-party API", "log to file", "pass to email tool"). Improves verdict accuracy.' },
|
|
262
|
+
data_origin_ip: { type: 'string', description: 'IP address of the data subject or data source. Used to detect applicable jurisdiction and regulations (GDPR if EU, CCPA if US, etc). Optional but improves regulatory accuracy.' },
|
|
263
|
+
jurisdiction: { type: 'string', description: 'Override jurisdiction if known (e.g. "EU", "US", "UK", "CA", "AU"). Use if data_origin_ip is unavailable but jurisdiction is known.' }
|
|
264
|
+
},
|
|
265
|
+
required: ['payload']
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'get_safety_report',
|
|
270
|
+
description: 'Call this tool when your agent needs to classify a batch of data payloads and generate an audit-ready compliance report. Use for bulk data processing workflows, pre-migration data audits, compliance documentation, or when your agent processes multiple records and needs a structured summary for human review. Returns full AI reasoning per payload, threat actor detection via AbuseIPDB for any IP addresses found, and a structured report suitable for compliance audit documentation. Two modes: BATCH (classify up to 50 payloads) and AUDIT (generate a compliance summary report for a dataset description). AI-powered analysis — NOT a simple database lookup. LEGAL NOTICE: Classification is informational only. We do not store your data payloads. Full terms: kordagencies.com/terms.html. Paid API key required — upgrade at kordagencies.com.',
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: 'object',
|
|
273
|
+
properties: {
|
|
274
|
+
mode: { type: 'string', enum: ['BATCH', 'AUDIT'], description: 'BATCH: classify up to 50 payloads with full reasoning. AUDIT: generate compliance summary report.' },
|
|
275
|
+
payloads: { type: 'array', items: { type: 'string' }, description: 'Array of data payloads to classify. Required for BATCH mode. Maximum 50.' },
|
|
276
|
+
dataset_description: { type: 'string', description: 'Description of the dataset for AUDIT mode (e.g. "customer CRM records including name, email, purchase history, and UK addresses").' },
|
|
277
|
+
context: { type: 'string', description: 'What will be done with this data. Used to improve verdict accuracy.' }
|
|
278
|
+
},
|
|
279
|
+
required: ['mode']
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
// ─── TOOL EXECUTION ───────────────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
async function executeTool(name, args, tier) {
|
|
287
|
+
const checkedAt = nowISO();
|
|
288
|
+
|
|
289
|
+
// ── validate_data_safety ──────────────────────────────────────────────────
|
|
290
|
+
if (name === 'validate_data_safety') {
|
|
291
|
+
const { payload, context, data_origin_ip, jurisdiction } = args;
|
|
292
|
+
if (!payload) return { error: 'payload is required', _disclaimer: LEGAL_DISCLAIMER };
|
|
293
|
+
|
|
294
|
+
// Step 1: Pattern detection (fast, no API call)
|
|
295
|
+
const patterns = detectPatterns(payload);
|
|
296
|
+
|
|
297
|
+
// Step 2: Jurisdiction detection via IPinfo
|
|
298
|
+
let detectedCountry = null;
|
|
299
|
+
let applicableRegulations = [];
|
|
300
|
+
if (data_origin_ip) {
|
|
301
|
+
detectedCountry = await getJurisdiction(data_origin_ip);
|
|
302
|
+
if (detectedCountry) applicableRegulations = getRegulationsForCountry(detectedCountry);
|
|
303
|
+
}
|
|
304
|
+
if (jurisdiction && applicableRegulations.length === 0) {
|
|
305
|
+
// Map jurisdiction string to regulations
|
|
306
|
+
const j = jurisdiction.toUpperCase();
|
|
307
|
+
if (j === 'EU' || j === 'EEA') applicableRegulations = ['GDPR'];
|
|
308
|
+
else if (j === 'UK') applicableRegulations = ['UK_GDPR'];
|
|
309
|
+
else if (j === 'US') applicableRegulations = ['CCPA', 'HIPAA_IF_HEALTH', 'PCI_DSS_IF_PAYMENT'];
|
|
310
|
+
else if (j === 'CA') applicableRegulations = ['PIPEDA'];
|
|
311
|
+
else if (j === 'AU') applicableRegulations = ['PRIVACY_ACT_AU'];
|
|
312
|
+
else if (j === 'BR') applicableRegulations = ['LGPD'];
|
|
313
|
+
else if (j === 'SG') applicableRegulations = ['PDPA_SG'];
|
|
314
|
+
else applicableRegulations = ['LOCAL_PRIVACY_LAWS_APPLY'];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Step 3: Credential breach check if credentials detected
|
|
318
|
+
let credentialCheck = null;
|
|
319
|
+
if (patterns.includes('POSSIBLE_CREDENTIAL') || patterns.includes('API_KEY_OR_SECRET')) {
|
|
320
|
+
const credentials = extractCredentials(payload);
|
|
321
|
+
if (credentials.length > 0) {
|
|
322
|
+
const checks = await Promise.all(credentials.map(c => checkPwnedPassword(c)));
|
|
323
|
+
const pwnedCount = checks.filter(c => c && c.pwned).length;
|
|
324
|
+
if (pwnedCount > 0) {
|
|
325
|
+
credentialCheck = {
|
|
326
|
+
credentials_found: credentials.length,
|
|
327
|
+
credentials_compromised: pwnedCount,
|
|
328
|
+
action: 'IMMEDIATE_ROTATION_REQUIRED',
|
|
329
|
+
source: 'HaveIBeenPwned k-anonymity API — credentials never transmitted in full'
|
|
330
|
+
};
|
|
331
|
+
} else {
|
|
332
|
+
credentialCheck = {
|
|
333
|
+
credentials_found: credentials.length,
|
|
334
|
+
credentials_compromised: 0,
|
|
335
|
+
action: 'CREDENTIALS_NOT_IN_KNOWN_BREACHES',
|
|
336
|
+
source: 'HaveIBeenPwned k-anonymity API'
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Step 4: Claude AI classification
|
|
343
|
+
const regulationsContext = applicableRegulations.length > 0
|
|
344
|
+
? 'Jurisdiction detected: ' + (detectedCountry || jurisdiction) + '. Applicable regulations: ' + applicableRegulations.join(', ') + '.'
|
|
345
|
+
: 'No jurisdiction detected — apply conservative global standards (assume GDPR-level protection).';
|
|
346
|
+
|
|
347
|
+
const prompt = 'You are a data safety classifier for AI agents. An agent is about to process data and needs to know if it is safe.\n\n' +
|
|
348
|
+
'DATA PAYLOAD:\n' + payload.slice(0, 2000) + (payload.length > 2000 ? '\n[truncated]' : '') + '\n\n' +
|
|
349
|
+
'CONTEXT (what agent will do with this data): ' + (context || 'not specified') + '\n\n' +
|
|
350
|
+
'PRE-DETECTED PATTERNS: ' + (patterns.length > 0 ? patterns.join(', ') : 'none detected') + '\n\n' +
|
|
351
|
+
regulationsContext + '\n\n' +
|
|
352
|
+
'Classify this data payload. Return ONLY valid JSON:\n' +
|
|
353
|
+
'{\n' +
|
|
354
|
+
' "verdict": "SAFE_TO_PROCESS|REDACT_BEFORE_PASSING|DO_NOT_STORE|ESCALATE",\n' +
|
|
355
|
+
' "confidence": "HIGH|MEDIUM|LOW",\n' +
|
|
356
|
+
' "sensitivity_level": "PUBLIC|INTERNAL|CONFIDENTIAL|RESTRICTED",\n' +
|
|
357
|
+
' "detected_categories": ["PII"|"PHI"|"PCI"|"CREDENTIALS"|"FINANCIAL"|"LOCATION"|"BIOMETRIC"|"CHILDREN_DATA"|"NONE"],\n' +
|
|
358
|
+
' "applicable_regulations": ["GDPR"|"HIPAA"|"PCI_DSS"|"CCPA"|"PIPEDA"|"LGPD"|"UK_GDPR"|"PDPA_SG"|"PRIVACY_ACT_AU"|"DPDP"],\n' +
|
|
359
|
+
' "recommended_action": "one specific sentence telling the agent exactly what to do next",\n' +
|
|
360
|
+
' "reasoning": "2-3 sentences explaining why this verdict was reached",\n' +
|
|
361
|
+
' "redaction_targets": ["field or pattern to redact if verdict is REDACT_BEFORE_PASSING, otherwise empty array"]\n' +
|
|
362
|
+
'}\n\n' +
|
|
363
|
+
'VERDICT GUIDE:\n' +
|
|
364
|
+
'SAFE_TO_PROCESS: No sensitive data detected, safe to proceed\n' +
|
|
365
|
+
'REDACT_BEFORE_PASSING: Sensitive data present but can be processed after redacting specific fields\n' +
|
|
366
|
+
'DO_NOT_STORE: Data can be used transiently but must not be persisted\n' +
|
|
367
|
+
'ESCALATE: High-risk data requiring human review before any processing';
|
|
368
|
+
|
|
369
|
+
let classification;
|
|
370
|
+
try {
|
|
371
|
+
const response = await callClaude(prompt);
|
|
372
|
+
const clean = response.replace(/```json|```/g, '').trim();
|
|
373
|
+
classification = JSON.parse(clean);
|
|
374
|
+
} catch(e) {
|
|
375
|
+
return {
|
|
376
|
+
error: 'AI classification temporarily unavailable — manual review recommended before processing this data.',
|
|
377
|
+
patterns_detected: patterns,
|
|
378
|
+
checked_at: checkedAt,
|
|
379
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const result = {
|
|
384
|
+
verdict: classification.verdict,
|
|
385
|
+
confidence: classification.confidence,
|
|
386
|
+
sensitivity_level: classification.sensitivity_level,
|
|
387
|
+
detected_categories: classification.detected_categories,
|
|
388
|
+
applicable_regulations: classification.applicable_regulations || applicableRegulations,
|
|
389
|
+
recommended_action: classification.recommended_action,
|
|
390
|
+
jurisdiction_detected: detectedCountry || jurisdiction || null,
|
|
391
|
+
patterns_detected: patterns,
|
|
392
|
+
credential_check: credentialCheck,
|
|
393
|
+
analysis_type: 'AI-powered classification — NOT a simple pattern match',
|
|
394
|
+
checked_at: checkedAt,
|
|
395
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Gate reasoning on free tier
|
|
399
|
+
if (tier === 'free') {
|
|
400
|
+
result._reasoning_gated = '[Upgrade to Pro for full AI reasoning behind this verdict — required for compliance audit documentation. kordagencies.com]';
|
|
401
|
+
result._upgrade = {
|
|
402
|
+
batch_classification: 'Pro plan classifies up to 50 payloads per call — bulk data workflows',
|
|
403
|
+
audit_report: 'Pro plan generates structured audit-ready compliance reports',
|
|
404
|
+
threat_intelligence: 'Pro plan checks IP addresses in payload against AbuseIPDB threat database',
|
|
405
|
+
full_reasoning: 'Pro plan includes full AI reasoning per verdict for compliance documentation',
|
|
406
|
+
upgrade_url: 'https://kordagencies.com'
|
|
407
|
+
};
|
|
408
|
+
} else {
|
|
409
|
+
result.reasoning = classification.reasoning;
|
|
410
|
+
result.redaction_targets = classification.redaction_targets;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ── get_safety_report ─────────────────────────────────────────────────────
|
|
417
|
+
if (name === 'get_safety_report') {
|
|
418
|
+
const { mode, payloads, dataset_description, context } = args;
|
|
419
|
+
if (!mode) return { error: 'mode is required: BATCH or AUDIT', _disclaimer: LEGAL_DISCLAIMER };
|
|
420
|
+
|
|
421
|
+
// Free tier preview — run count analysis without full classification
|
|
422
|
+
if (tier === 'free') {
|
|
423
|
+
if (mode === 'BATCH' && payloads && Array.isArray(payloads)) {
|
|
424
|
+
const previewPatterns = payloads.slice(0, 5).map(p => detectPatterns(p));
|
|
425
|
+
const flaggedCount = previewPatterns.filter(p => p.length > 0).length;
|
|
426
|
+
return {
|
|
427
|
+
mode: 'BATCH',
|
|
428
|
+
status: 'PREVIEW — paid plan required for full classification',
|
|
429
|
+
payloads_submitted: payloads.length,
|
|
430
|
+
payloads_flagged_in_preview: flaggedCount,
|
|
431
|
+
sample_patterns_found: [...new Set(previewPatterns.flat())].slice(0, 5),
|
|
432
|
+
message: flaggedCount > 0
|
|
433
|
+
? flaggedCount + ' of ' + Math.min(payloads.length, 5) + ' sampled payloads contain sensitive data patterns. Pro plan required to classify all ' + payloads.length + ' payloads with full AI reasoning and audit report.'
|
|
434
|
+
: 'No obvious patterns in sample. Pro plan runs full AI classification across all ' + payloads.length + ' payloads — AI detects contextual sensitivity pattern matching misses.',
|
|
435
|
+
what_you_get_on_pro: [
|
|
436
|
+
'Full AI classification for all payloads',
|
|
437
|
+
'Verdict + reasoning per payload',
|
|
438
|
+
'AbuseIPDB threat check for any IP addresses',
|
|
439
|
+
'Audit-ready compliance report',
|
|
440
|
+
'Redaction targets per flagged payload'
|
|
441
|
+
],
|
|
442
|
+
upgrade_url: 'https://kordagencies.com',
|
|
443
|
+
checked_at: checkedAt,
|
|
444
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
mode: mode,
|
|
449
|
+
status: 'PREVIEW — paid plan required',
|
|
450
|
+
message: 'Pro plan required for ' + mode + ' reports. Upgrade at kordagencies.com.',
|
|
451
|
+
upgrade_url: 'https://kordagencies.com',
|
|
452
|
+
checked_at: checkedAt,
|
|
453
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ── PAID: BATCH mode ──
|
|
458
|
+
if (mode === 'BATCH') {
|
|
459
|
+
if (!payloads || !Array.isArray(payloads) || payloads.length === 0) {
|
|
460
|
+
return { error: 'payloads array is required for BATCH mode', _disclaimer: LEGAL_DISCLAIMER };
|
|
461
|
+
}
|
|
462
|
+
const batch = payloads.slice(0, 50);
|
|
463
|
+
const results = [];
|
|
464
|
+
const errors = [];
|
|
465
|
+
|
|
466
|
+
for (let i = 0; i < batch.length; i++) {
|
|
467
|
+
const p = batch[i];
|
|
468
|
+
const patterns = detectPatterns(p);
|
|
469
|
+
|
|
470
|
+
// Extract and check IPs via AbuseIPDB
|
|
471
|
+
const ips = extractIPs(p);
|
|
472
|
+
let threatFlags = [];
|
|
473
|
+
for (const ip of ips) {
|
|
474
|
+
const abuseResult = await checkAbuseIPDB(ip);
|
|
475
|
+
if (abuseResult && abuseResult.is_threat) {
|
|
476
|
+
threatFlags.push({ ip, abuse_score: abuseResult.abuse_confidence_score, reports: abuseResult.total_reports });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Credential check
|
|
481
|
+
let credentialCheck = null;
|
|
482
|
+
const credentials = extractCredentials(p);
|
|
483
|
+
if (credentials.length > 0) {
|
|
484
|
+
const checks = await Promise.all(credentials.map(c => checkPwnedPassword(c)));
|
|
485
|
+
const pwnedCount = checks.filter(c => c && c.pwned).length;
|
|
486
|
+
credentialCheck = { credentials_found: credentials.length, credentials_compromised: pwnedCount };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Claude classification
|
|
490
|
+
try {
|
|
491
|
+
const prompt = 'Classify this data payload for safety. Context: ' + (context || 'not specified') + '\n\nPayload: ' + p.slice(0, 1000) + '\nPre-detected patterns: ' + (patterns.join(', ') || 'none') + '\n\nReturn ONLY valid JSON: {"verdict":"SAFE_TO_PROCESS|REDACT_BEFORE_PASSING|DO_NOT_STORE|ESCALATE","sensitivity_level":"PUBLIC|INTERNAL|CONFIDENTIAL|RESTRICTED","detected_categories":[],"recommended_action":"one sentence","reasoning":"2 sentences","redaction_targets":[]}';
|
|
492
|
+
const response = await callClaude(prompt);
|
|
493
|
+
const clean = response.replace(/```json|```/g, '').trim();
|
|
494
|
+
const classification = JSON.parse(clean);
|
|
495
|
+
results.push({
|
|
496
|
+
index: i,
|
|
497
|
+
verdict: classification.verdict,
|
|
498
|
+
sensitivity_level: classification.sensitivity_level,
|
|
499
|
+
detected_categories: classification.detected_categories,
|
|
500
|
+
recommended_action: classification.recommended_action,
|
|
501
|
+
reasoning: classification.reasoning,
|
|
502
|
+
redaction_targets: classification.redaction_targets || [],
|
|
503
|
+
patterns_detected: patterns,
|
|
504
|
+
threat_flags: threatFlags.length > 0 ? threatFlags : undefined,
|
|
505
|
+
credential_check: credentialCheck || undefined
|
|
506
|
+
});
|
|
507
|
+
} catch(e) {
|
|
508
|
+
errors.push({ index: i, error: 'Classification failed — manual review required' });
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Summary
|
|
513
|
+
const verdictCounts = {};
|
|
514
|
+
results.forEach(r => { verdictCounts[r.verdict] = (verdictCounts[r.verdict] || 0) + 1; });
|
|
515
|
+
const highestRisk = results.filter(r => r.verdict === 'ESCALATE' || r.verdict === 'DO_NOT_STORE');
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
mode: 'BATCH',
|
|
519
|
+
total_payloads: batch.length,
|
|
520
|
+
classified: results.length,
|
|
521
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
522
|
+
summary: {
|
|
523
|
+
verdict_breakdown: verdictCounts,
|
|
524
|
+
high_risk_count: highestRisk.length,
|
|
525
|
+
safe_count: verdictCounts['SAFE_TO_PROCESS'] || 0
|
|
526
|
+
},
|
|
527
|
+
results,
|
|
528
|
+
analysis_type: 'AI-powered batch classification with threat intelligence',
|
|
529
|
+
checked_at: checkedAt,
|
|
530
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ── PAID: AUDIT mode ──
|
|
535
|
+
if (mode === 'AUDIT') {
|
|
536
|
+
if (!dataset_description) {
|
|
537
|
+
return { error: 'dataset_description is required for AUDIT mode', _disclaimer: LEGAL_DISCLAIMER };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const prompt = 'You are a data compliance auditor. Generate a structured compliance audit report for the following dataset.\n\n' +
|
|
541
|
+
'DATASET DESCRIPTION: ' + dataset_description + '\n' +
|
|
542
|
+
'INTENDED USE: ' + (context || 'not specified') + '\n\n' +
|
|
543
|
+
'Return ONLY valid JSON:\n' +
|
|
544
|
+
'{"overall_risk_level":"LOW|MEDIUM|HIGH|CRITICAL","sensitivity_level":"PUBLIC|INTERNAL|CONFIDENTIAL|RESTRICTED","data_categories_present":[],"applicable_regulations":[],"compliance_requirements":[{"regulation":"name","requirement":"what must be done","action_required":"specific action"}],"recommended_actions":["action 1","action 2"],"data_handling_rules":["rule 1","rule 2"],"retention_guidance":"specific retention recommendation","transfer_restrictions":"any restrictions on data transfer","audit_summary":"3-4 sentence executive summary of compliance posture"}';
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
const response = await callClaude(prompt);
|
|
548
|
+
const clean = response.replace(/```json|```/g, '').trim();
|
|
549
|
+
const report = JSON.parse(clean);
|
|
550
|
+
return {
|
|
551
|
+
mode: 'AUDIT',
|
|
552
|
+
dataset_description,
|
|
553
|
+
report,
|
|
554
|
+
analysis_type: 'AI-powered compliance audit — NOT legal advice',
|
|
555
|
+
checked_at: checkedAt,
|
|
556
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
557
|
+
};
|
|
558
|
+
} catch(e) {
|
|
559
|
+
return { error: 'Audit report generation failed. Please retry.', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return { error: 'Invalid mode. Use BATCH or AUDIT.', _disclaimer: LEGAL_DISCLAIMER };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return { error: 'Unknown tool: ' + name };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// ─── ACCESS CONTROL ───────────────────────────────────────────────────────────
|
|
570
|
+
|
|
571
|
+
function checkAccess(req, toolName) {
|
|
572
|
+
const apiKey = req.headers['x-api-key'];
|
|
573
|
+
|
|
574
|
+
if (apiKey) {
|
|
575
|
+
const record = apiKeys.get(apiKey);
|
|
576
|
+
if (!record) return { allowed: false, reason: 'Invalid API key. Get yours at kordagencies.com', tier: 'invalid' };
|
|
577
|
+
if (record.limit !== Infinity && record.calls >= record.limit) return { allowed: false, reason: 'Monthly limit of ' + record.limit + ' classifications reached. Upgrade at kordagencies.com', tier: 'limit_reached' };
|
|
578
|
+
record.calls++;
|
|
579
|
+
return { allowed: true, tier: record.plan };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
|
|
583
|
+
const calls = freeTierUsage.get(ip) || 0;
|
|
584
|
+
if (calls >= FREE_TIER_LIMIT) {
|
|
585
|
+
return {
|
|
586
|
+
allowed: false,
|
|
587
|
+
reason: 'Free tier limit of ' + FREE_TIER_LIMIT + ' classifications/month reached. You have seen it work — upgrade to Pro ($49/month) at kordagencies.com for 5,000 classifications/month.',
|
|
588
|
+
upgrade_url: 'https://kordagencies.com',
|
|
589
|
+
tier: 'free_limit_reached'
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
freeTierUsage.set(ip, calls + 1);
|
|
593
|
+
saveStats();
|
|
594
|
+
const remaining = FREE_TIER_LIMIT - calls - 1;
|
|
595
|
+
return {
|
|
596
|
+
allowed: true, tier: 'free', remaining,
|
|
597
|
+
warning: remaining <= 4 ? remaining + ' free classification' + (remaining === 1 ? '' : 's') + ' remaining this month. Upgrade at kordagencies.com.' : null
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// ─── STRIPE ───────────────────────────────────────────────────────────────────
|
|
602
|
+
|
|
603
|
+
function verifyStripeSignature(body, sig, secret) {
|
|
604
|
+
if (!secret || !sig) return false;
|
|
605
|
+
try {
|
|
606
|
+
const parts = sig.split(',').reduce((acc, part) => { const [k, v] = part.split('='); acc[k] = v; return acc; }, {});
|
|
607
|
+
const timestamp = parts['t']; const expected = parts['v1'];
|
|
608
|
+
if (!timestamp || !expected) return false;
|
|
609
|
+
const computed = crypto.createHmac('sha256', secret).update(timestamp + '.' + body, 'utf8').digest('hex');
|
|
610
|
+
return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(expected));
|
|
611
|
+
} catch(e) { return false; }
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async function sendEmail(to, subject, html) {
|
|
615
|
+
return new Promise((resolve) => {
|
|
616
|
+
const body = JSON.stringify({ from: 'Data Compliance Classifier <ojas@kordagencies.com>', to: [to], subject, html });
|
|
617
|
+
const req = https.request({
|
|
618
|
+
hostname: 'api.resend.com', path: '/emails', method: 'POST',
|
|
619
|
+
headers: { 'Authorization': 'Bearer ' + RESEND_API_KEY, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
|
|
620
|
+
}, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve({ status: res.statusCode })); });
|
|
621
|
+
req.on('error', e => resolve({ error: e.message }));
|
|
622
|
+
req.write(body); req.end();
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async function sendApiKeyEmail(email, apiKey, plan) {
|
|
627
|
+
const planLabel = plan === 'enterprise' ? 'Enterprise' : 'Pro';
|
|
628
|
+
const limit = plan === 'enterprise' ? 'Unlimited' : '5,000';
|
|
629
|
+
const html = '<!DOCTYPE html><html><body style="font-family:monospace;background:#080A0F;color:#E8EDF5;padding:40px;max-width:600px;margin:0 auto"><div style="border:1px solid rgba(0,229,195,0.3);border-radius:8px;padding:32px"><div style="color:#00E5C3;font-size:13px;letter-spacing:0.2em;text-transform:uppercase;margin-bottom:24px">Data Compliance Classifier - ' + planLabel + ' Plan</div><h1 style="font-size:24px;font-weight:700;margin-bottom:8px;color:#FFFFFF">Your API key is ready.</h1><div style="background:#141B24;border:1px solid rgba(255,255,255,0.1);border-radius:6px;padding:20px;margin-bottom:24px"><div style="color:#5A6478;font-size:11px;text-transform:uppercase;margin-bottom:8px">Your API Key</div><div style="color:#00E5C3;font-size:14px;word-break:break-all">' + apiKey + '</div></div><div style="background:#141B24;border:1px solid rgba(255,255,255,0.1);border-radius:6px;padding:20px;margin-bottom:24px"><div style="color:#5A6478;font-size:11px;text-transform:uppercase;margin-bottom:8px">MCP Config</div><div style="color:#86EFAC;font-size:12px">{"data-compliance":{"url":"https://data-compliance-mcp-production.up.railway.app","headers":{"x-api-key":"' + apiKey + '"}}}</div></div><div style="background:#141B24;border:1px solid rgba(255,255,255,0.1);border-radius:6px;padding:20px;margin-bottom:24px"><div style="color:#E8EDF5;font-size:13px">Plan: ' + planLabel + ' | Classifications: ' + limit + '/month</div></div><div style="background:#0D1219;border-radius:6px;padding:16px;margin-bottom:24px;font-size:11px;color:#5A6478;line-height:1.7">Classification is AI-powered and for informational purposes only. We do not store your data payloads. Full terms: kordagencies.com/terms.html</div><p style="color:#5A6478;font-size:12px">Questions? ojas@kordagencies.com</p></div></body></html>';
|
|
630
|
+
return sendEmail(email, 'Your Data Compliance Classifier ' + planLabel + ' API Key', html);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function handleStripeWebhook(body, sig) {
|
|
634
|
+
const secret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
635
|
+
if (!secret) return { error: 'Webhook secret not configured', status: 400 };
|
|
636
|
+
if (!verifyStripeSignature(body, sig, secret)) return { error: 'Invalid signature', status: 400 };
|
|
637
|
+
try {
|
|
638
|
+
const event = JSON.parse(body);
|
|
639
|
+
if (event.type === 'checkout.session.completed') {
|
|
640
|
+
const session = event.data.object;
|
|
641
|
+
const email = session.customer_email || session.customer_details?.email;
|
|
642
|
+
const plan = getPlanFromProduct(session.metadata?.product_name || '');
|
|
643
|
+
if (email) {
|
|
644
|
+
const apiKey = generateApiKey();
|
|
645
|
+
apiKeys.set(apiKey, { email, plan, createdAt: nowISO(), calls: 0, limit: PLAN_LIMITS[plan] });
|
|
646
|
+
await sendApiKeyEmail(email, apiKey, plan);
|
|
647
|
+
console.log('[data-compliance] API key created for ' + email + ' (' + plan + ')');
|
|
648
|
+
return { success: true, email, plan };
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return { received: true, type: event.type };
|
|
652
|
+
} catch(e) { return { error: e.message, status: 400 }; }
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// ─── HTTP SERVER ──────────────────────────────────────────────────────────────
|
|
656
|
+
|
|
657
|
+
const server = http.createServer(async (req, res) => {
|
|
658
|
+
const cors = {
|
|
659
|
+
'Access-Control-Allow-Origin': '*',
|
|
660
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
661
|
+
'Access-Control-Allow-Headers': 'Content-Type, x-api-key, mcp-session-id, x-stats-key'
|
|
662
|
+
};
|
|
663
|
+
if (req.method === 'OPTIONS') { res.writeHead(200, cors); res.end(); return; }
|
|
664
|
+
|
|
665
|
+
if (req.url === '/health' && (req.method === 'GET' || req.method === 'HEAD')) {
|
|
666
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
667
|
+
res.end(JSON.stringify({ status: 'ok', version: VERSION, service: 'data-compliance-mcp', free_tier: 'first 20 classifications/month, no API key required', paid_keys_issued: apiKeys.size }));
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (req.url === '/.well-known/mcp/server-card.json') {
|
|
672
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
673
|
+
res.end(JSON.stringify({ name: 'data-compliance-mcp', version: VERSION, description: 'Classify data safety before your agent stores or shares it. GDPR, HIPAA, PCI-DSS. Free tier: 20/month.', tools: tools.map(t => ({ name: t.name, description: t.description.slice(0, 100) })), transport: 'stdio', homepage: 'https://kordagencies.com', author: 'ojas1' }));
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (req.url === '/deps' && req.method === 'GET') {
|
|
678
|
+
const depCheck = (hostname, path, method, body, headers) => new Promise((resolve) => {
|
|
679
|
+
const opts = { hostname, path, method: method || 'GET', headers: Object.assign({ 'User-Agent': 'DataCompliance-MCP-HealthCheck/1.0' }, headers || {}) };
|
|
680
|
+
const r = https.request(opts, (res2) => { res2.resume(); resolve({ ok: res2.statusCode < 500, status: res2.statusCode }); });
|
|
681
|
+
r.on('error', () => resolve({ ok: false, status: 0, error: 'unreachable' }));
|
|
682
|
+
r.setTimeout(5000, () => { r.destroy(); resolve({ ok: false, status: 0, error: 'timeout' }); });
|
|
683
|
+
if (body) r.write(body);
|
|
684
|
+
r.end();
|
|
685
|
+
});
|
|
686
|
+
const [ai, ipinfo, hibp, abuseipdb] = await Promise.all([
|
|
687
|
+
depCheck('api.anthropic.com', '/v1/models', 'GET', null, { 'x-api-key': ANTHROPIC_API_KEY, 'anthropic-version': '2023-06-01' }),
|
|
688
|
+
depCheck('ipinfo.io', '/8.8.8.8/country'),
|
|
689
|
+
depCheck('api.pwnedpasswords.com', '/range/21BD1'),
|
|
690
|
+
ABUSEIPDB_API_KEY ? depCheck('api.abuseipdb.com', '/api/v2/check?ipAddress=8.8.8.8&maxAgeInDays=90', 'GET', null, { 'Key': ABUSEIPDB_API_KEY, 'Accept': 'application/json' }) : Promise.resolve({ ok: false, status: 0, error: 'no key configured' })
|
|
691
|
+
]);
|
|
692
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
693
|
+
res.end(JSON.stringify({ server: 'data-compliance-mcp', checked_at: nowISO(), dependencies: { anthropic: ai, ipinfo: ipinfo, haveibeenpwned: hibp, abuseipdb: abuseipdb } }));
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (req.url === '/stats' && req.method === 'GET') {
|
|
698
|
+
if (req.headers['x-stats-key'] !== STATS_KEY) { res.writeHead(401, cors); res.end(JSON.stringify({ error: 'Unauthorized' })); return; }
|
|
699
|
+
const totalFreeCalls = Array.from(freeTierUsage.values()).reduce((a, b) => a + b, 0);
|
|
700
|
+
const toolCounts = {};
|
|
701
|
+
usageLog.forEach(e => { toolCounts[e.tool] = (toolCounts[e.tool] || 0) + 1; });
|
|
702
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
703
|
+
res.end(JSON.stringify({ free_tier_unique_ips: freeTierUsage.size, free_tier_total_calls: totalFreeCalls, paid_keys_issued: apiKeys.size, tool_usage: toolCounts, recent_calls: usageLog.slice(-20).reverse() }));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (req.url === '/webhook/stripe' && req.method === 'POST') {
|
|
708
|
+
let body = ''; req.on('data', c => body += c);
|
|
709
|
+
req.on('end', async () => {
|
|
710
|
+
const sig = req.headers['stripe-signature'] || '';
|
|
711
|
+
const result = await handleStripeWebhook(body, sig);
|
|
712
|
+
const status = result.status || 200;
|
|
713
|
+
delete result.status;
|
|
714
|
+
res.writeHead(status, { ...cors, 'Content-Type': 'application/json' });
|
|
715
|
+
res.end(JSON.stringify(result));
|
|
716
|
+
});
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (req.method === 'POST') {
|
|
721
|
+
let body = ''; req.on('data', c => body += c);
|
|
722
|
+
req.on('end', async () => {
|
|
723
|
+
try {
|
|
724
|
+
const request = JSON.parse(body);
|
|
725
|
+
let response;
|
|
726
|
+
|
|
727
|
+
if (request.method === 'initialize') {
|
|
728
|
+
response = { jsonrpc: '2.0', id: request.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {}, resources: {}, prompts: {} }, serverInfo: { name: 'data-compliance-mcp', version: VERSION, description: 'Classify data safety before your agent stores or shares it. GDPR, HIPAA, PCI-DSS, CCPA. 2 tools. Free tier: 20/month.' } } };
|
|
729
|
+
} else if (request.method === 'notifications/initialized') {
|
|
730
|
+
res.writeHead(204, cors); res.end(); return;
|
|
731
|
+
} else if (request.method === 'tools/list') {
|
|
732
|
+
response = { jsonrpc: '2.0', id: request.id, result: { tools } };
|
|
733
|
+
} else if (request.method === 'resources/list') {
|
|
734
|
+
response = { jsonrpc: '2.0', id: request.id, result: { resources: [] } };
|
|
735
|
+
} else if (request.method === 'prompts/list') {
|
|
736
|
+
response = { jsonrpc: '2.0', id: request.id, result: { prompts: [] } };
|
|
737
|
+
} else if (request.method === 'tools/call') {
|
|
738
|
+
const { name, arguments: toolArgs } = request.params;
|
|
739
|
+
const access = checkAccess(req, name);
|
|
740
|
+
|
|
741
|
+
if (!access.allowed) {
|
|
742
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
743
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: access.reason, upgrade_url: 'https://kordagencies.com', _disclaimer: LEGAL_DISCLAIMER }) }] } }));
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
|
|
748
|
+
usageLog.push({ tool: name, tier: access.tier, time: nowISO(), ip: ip.slice(0, 8) + '...' });
|
|
749
|
+
if (usageLog.length > 1000) usageLog.shift();
|
|
750
|
+
saveStats();
|
|
751
|
+
|
|
752
|
+
const result = await executeTool(name, toolArgs || {}, access.tier);
|
|
753
|
+
if (access.warning) result._notice = access.warning;
|
|
754
|
+
|
|
755
|
+
response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } };
|
|
756
|
+
} else {
|
|
757
|
+
response = { jsonrpc: '2.0', id: request.id, error: { code: -32601, message: 'Method not found: ' + request.method } };
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
761
|
+
res.end(JSON.stringify(response));
|
|
762
|
+
} catch(e) {
|
|
763
|
+
res.writeHead(400, { ...cors, 'Content-Type': 'application/json' });
|
|
764
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (req.method === 'GET' && req.url === '/') {
|
|
771
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
772
|
+
res.end(JSON.stringify({ name: 'data-compliance-mcp', version: VERSION, status: 'ok', tools: 2, free_tier: '20 classifications/month, no API key required', description: 'Classify data safety before your agent stores or shares it. GDPR, HIPAA, PCI-DSS, CCPA.', upgrade: 'https://kordagencies.com' }));
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
res.writeHead(404, cors); res.end(JSON.stringify({ error: 'Not found' }));
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
server.listen(PORT, () => {
|
|
780
|
+
loadStats();
|
|
781
|
+
console.log('Data Compliance Classifier MCP v' + VERSION + ' running on port ' + PORT);
|
|
782
|
+
console.log('Tools: 2 (validate_data_safety, get_safety_report)');
|
|
783
|
+
console.log('Free tier: ' + FREE_TIER_LIMIT + ' classifications/IP/month');
|
|
784
|
+
console.log('Anthropic: ' + (ANTHROPIC_API_KEY ? 'configured' : 'MISSING'));
|
|
785
|
+
console.log('AbuseIPDB: ' + (ABUSEIPDB_API_KEY ? 'configured' : 'MISSING — threat intelligence disabled'));
|
|
786
|
+
console.log('Resend: ' + (RESEND_API_KEY ? 'configured' : 'MISSING'));
|
|
787
|
+
});
|