data-compliance-mcp 1.0.13 → 1.0.14
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 +3 -0
- package/package.json +1 -1
- package/src/server.js +39 -3
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-compliance-mcp",
|
|
3
3
|
"mcpName": "io.github.OjasKord/data-compliance-mcp",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.14",
|
|
5
5
|
"description": "Data safety classifier for AI agents. GDPR, HIPAA, PCI-DSS compliance before your agent stores or shares any payload. SAFE/ESCALATE verdict in one call.",
|
|
6
6
|
"main": "src/server.js",
|
|
7
7
|
"scripts": {
|
package/src/server.js
CHANGED
|
@@ -3,7 +3,7 @@ const https = require('https');
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
|
|
6
|
-
const VERSION = '1.0.
|
|
6
|
+
const VERSION = '1.0.14';
|
|
7
7
|
const PERSIST_FILE = '/tmp/datacompliance_stats.json';
|
|
8
8
|
const API_KEYS_FILE = '/tmp/datacompliance_apikeys.json';
|
|
9
9
|
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || '';
|
|
@@ -21,6 +21,23 @@ const PLAN_LIMITS = { pro: 5000, enterprise: Infinity };
|
|
|
21
21
|
const toolUsageCounts = {};
|
|
22
22
|
const trialExtensions = new Map();
|
|
23
23
|
const TRIAL_EXTENSION_CALLS = 10;
|
|
24
|
+
|
|
25
|
+
const perMinuteUsage = new Map();
|
|
26
|
+
|
|
27
|
+
function checkPerMinuteLimit(ip, toolName, limit) {
|
|
28
|
+
const minuteKey = ip + ':' + toolName + ':' + new Date().toISOString().slice(0, 16);
|
|
29
|
+
const count = perMinuteUsage.get(minuteKey) || 0;
|
|
30
|
+
if (count >= limit) return false;
|
|
31
|
+
perMinuteUsage.set(minuteKey, count + 1);
|
|
32
|
+
if (perMinuteUsage.size > 10000) {
|
|
33
|
+
const currentMinute = new Date().toISOString().slice(0, 16);
|
|
34
|
+
for (const [key] of perMinuteUsage) {
|
|
35
|
+
if (!key.includes(currentMinute)) perMinuteUsage.delete(key);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
const STRIPE_PRO_URL = 'https://buy.stripe.com/cNidR87s9dXD0pue7Sebu0r';
|
|
25
42
|
const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/9B6bJ0aElbPv7RW9RCebu0s';
|
|
26
43
|
const STRIPE_ENTERPRISE_URL = 'https://buy.stripe.com/cNi7sKeUB8Dj7RW7Juebu0d';
|
|
@@ -1103,6 +1120,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
1103
1120
|
response = { jsonrpc: '2.0', id: request.id, result: { prompts: [] } };
|
|
1104
1121
|
} else if (request.method === 'tools/call') {
|
|
1105
1122
|
const { name, arguments: toolArgs } = request.params;
|
|
1123
|
+
const killSwitchKey = 'TOOL_DISABLED_' + name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
1124
|
+
if (process.env[killSwitchKey] === 'true') {
|
|
1125
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
1126
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: 'This tool is temporarily unavailable for maintenance.', agent_action: 'RETRY_IN_30_MIN', retryable: true, retry_after_ms: 1800000 }) }] } }));
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const _rawIpKs = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
|
|
1130
|
+
const _clientIpKs = _rawIpKs.split(',')[0].trim();
|
|
1131
|
+
if (['validate_data_safety', 'get_safety_report'].includes(name) && !checkPerMinuteLimit(_clientIpKs, name, 5)) {
|
|
1132
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
1133
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: 'Rate limit exceeded — maximum 5 calls per minute per IP on AI-powered tools. Your workflow is calling this tool too rapidly.', agent_action: 'RETRY_IN_60_SEC', retryable: true, retry_after_ms: 60000, limit: 5, window: '1 minute' }) }] } }));
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1106
1136
|
const access = checkAccess(req, name);
|
|
1107
1137
|
|
|
1108
1138
|
if (!access.allowed) {
|
|
@@ -1172,8 +1202,14 @@ function setupStdio() {
|
|
|
1172
1202
|
response = { jsonrpc: '2.0', id: req.id, result: { prompts: [] } };
|
|
1173
1203
|
} else if (req.method === 'tools/call') {
|
|
1174
1204
|
try {
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1205
|
+
const _name = req.params.name;
|
|
1206
|
+
const _ks = 'TOOL_DISABLED_' + (_name || '').toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
1207
|
+
if (process.env[_ks] === 'true') {
|
|
1208
|
+
response = { jsonrpc: '2.0', id: req.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: 'This tool is temporarily unavailable for maintenance.', agent_action: 'RETRY_IN_30_MIN', retryable: true, retry_after_ms: 1800000 }) }] } };
|
|
1209
|
+
} else {
|
|
1210
|
+
const result = await executeTool(_name, req.params.arguments || {}, 'paid');
|
|
1211
|
+
response = { jsonrpc: '2.0', id: req.id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } };
|
|
1212
|
+
}
|
|
1177
1213
|
} catch(e) {
|
|
1178
1214
|
response = { jsonrpc: '2.0', id: req.id, error: { code: -32603, message: e.message, agent_action: 'RETRY_IN_2_MIN' } };
|
|
1179
1215
|
}
|