data-compliance-mcp 1.0.19 → 1.0.21

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.21] - 2026-06-18
4
+ - feat: revoke API key on Stripe refund
5
+
6
+ ## [1.0.20] - 2026-06-17
7
+ - fix: Stripe webhook now validates payment_link ID — ignores events not belonging to this server
8
+
3
9
  ## [1.0.19] - 2026-06-16
4
10
  - feat: ATO optimisation — purpose verb, usage context, required fields, ToolRank badge
5
11
 
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.19",
4
+ "version": "1.0.21",
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/server.json CHANGED
@@ -1,25 +1,42 @@
1
- {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.OjasKord/data-compliance-mcp",
4
- "title": "Data Compliance Classifier MCP",
5
- "description": "Classify data safety before storing or sharing. GDPR, HIPAA, PCI-DSS, CCPA. AI-powered.",
6
- "version": "1.0.6",
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
- "identifier": "data-compliance-mcp",
16
- "version": "1.0.6",
17
- "transport": { "type": "stdio" },
18
- "environmentVariables": [
19
- { "name": "ANTHROPIC_API_KEY", "description": "Anthropic API key for AI classification", "isRequired": true, "isSecret": true },
20
- { "name": "ABUSEIPDB_API_KEY", "description": "AbuseIPDB API key for threat intelligence (optional)", "isRequired": false, "isSecret": true }
21
- ]
22
- }
23
- ],
24
- "remotes": [{ "type": "streamable-http", "url": "https://data-compliance-mcp-production.up.railway.app" }]
25
- }
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.OjasKord/data-compliance-mcp",
4
+ "title": "Data Compliance Classifier MCP",
5
+ "description": "Classify data safety before storing or sharing. GDPR, HIPAA, PCI-DSS, CCPA. AI-powered.",
6
+ "version": "1.0.19",
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
+ "identifier": "data-compliance-mcp",
16
+ "version": "1.0.19",
17
+ "transport": {
18
+ "type": "stdio"
19
+ },
20
+ "environmentVariables": [
21
+ {
22
+ "name": "ANTHROPIC_API_KEY",
23
+ "description": "Anthropic API key for AI classification",
24
+ "isRequired": true,
25
+ "isSecret": true
26
+ },
27
+ {
28
+ "name": "ABUSEIPDB_API_KEY",
29
+ "description": "AbuseIPDB API key for threat intelligence (optional)",
30
+ "isRequired": false,
31
+ "isSecret": true
32
+ }
33
+ ]
34
+ }
35
+ ],
36
+ "remotes": [
37
+ {
38
+ "type": "streamable-http",
39
+ "url": "https://data-compliance-mcp-production.up.railway.app"
40
+ }
41
+ ]
42
+ }
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.19';
6
+ const VERSION = '1.0.21';
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 || '';
@@ -41,6 +41,7 @@ function checkPerMinuteLimit(ip, toolName, limit) {
41
41
  const STRIPE_PRO_URL = 'https://buy.stripe.com/cNidR87s9dXD0pue7Sebu0r';
42
42
  const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/9B6bJ0aElbPv7RW9RCebu0s';
43
43
  const STRIPE_ENTERPRISE_URL = 'https://buy.stripe.com/cNi7sKeUB8Dj7RW7Juebu0d';
44
+ const ALLOWED_PAYMENT_LINK_IDS = ['plink_1TQzEjD6WvRe6sn35lz6hsVZ', 'plink_1TQzGlD6WvRe6sn3o85eJaLV', 'plink_1TObMjD6WvRe6sn3jOhhQVLR'];
44
45
 
45
46
  const REDIS_PREFIX = 'dcc';
46
47
  const FREE_TIER_REDIS_KEY = 'dcc:free_tier_usage';
@@ -129,6 +130,26 @@ async function redisSet(key, value) {
129
130
  } catch(e) { console.error('[Redis] redisSet failed:', e); }
130
131
  }
131
132
 
133
+ async function redisDelete(key) {
134
+ try {
135
+ const res = await fetch(
136
+ `${UPSTASH_URL}/del/${encodeURIComponent(key)}`,
137
+ { method: 'POST', headers: { Authorization: `Bearer ${UPSTASH_TOKEN}` } }
138
+ );
139
+ const data = await res.json();
140
+ if (data.error) console.error('[Redis] redisDelete error:', data.error, 'key:', key);
141
+ } catch(e) { console.error('[Redis] redisDelete failed:', e); }
142
+ }
143
+
144
+ async function findCheckoutSessionEmail(paymentIntentId) {
145
+ const res = await fetch(
146
+ `https://api.stripe.com/v1/checkout/sessions?payment_intent=${encodeURIComponent(paymentIntentId)}`,
147
+ { headers: { Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}` } }
148
+ );
149
+ const data = await res.json();
150
+ return data.data?.[0]?.customer_details?.email || data.data?.[0]?.customer_email || null;
151
+ }
152
+
132
153
  async function redisExpire(key, seconds) {
133
154
  try {
134
155
  const res = await fetch(
@@ -920,6 +941,11 @@ async function handleStripeWebhook(body, sig) {
920
941
  const event = JSON.parse(body);
921
942
  if (event.type === 'checkout.session.completed') {
922
943
  const session = event.data.object;
944
+ const paymentLinkId = session.payment_link;
945
+ if (paymentLinkId && !ALLOWED_PAYMENT_LINK_IDS.includes(paymentLinkId)) {
946
+ console.log('[data-compliance] Webhook received but payment link ' + paymentLinkId + ' not for this server — ignoring.');
947
+ return { received: true, ignored: true };
948
+ }
923
949
  const email = session.customer_email || session.customer_details?.email;
924
950
  const plan = getPlanFromProduct(session.metadata?.product_name || '');
925
951
  if (email) {
@@ -933,6 +959,40 @@ async function handleStripeWebhook(body, sig) {
933
959
  return { success: true, email, plan };
934
960
  }
935
961
  }
962
+ if (event.type === 'charge.refunded') {
963
+ if (!process.env.STRIPE_SECRET_KEY) {
964
+ console.error('[data-compliance] STRIPE_SECRET_KEY not set — cannot revoke key on refund');
965
+ return { received: true, ignored: true };
966
+ }
967
+ const paymentIntentId = event.data.object.payment_intent;
968
+ if (!paymentIntentId) {
969
+ console.log('[data-compliance] charge.refunded missing payment_intent — ignoring.');
970
+ return { received: true, ignored: true };
971
+ }
972
+ try {
973
+ const email = await findCheckoutSessionEmail(paymentIntentId);
974
+ if (!email) {
975
+ console.log('[data-compliance] No checkout session/email found for refunded payment_intent ' + paymentIntentId);
976
+ return { received: true, ignored: true };
977
+ }
978
+ let revokedKey = null;
979
+ for (const [key, record] of apiKeys.entries()) {
980
+ if (record.email === email) { revokedKey = key; break; }
981
+ }
982
+ if (!revokedKey) {
983
+ console.log('[data-compliance] No API key found for ' + email + ' — refund received, nothing to revoke');
984
+ return { received: true, ignored: true };
985
+ }
986
+ apiKeys.delete(revokedKey);
987
+ await redisDelete(`${REDIS_PREFIX}:key:${revokedKey}`);
988
+ saveApiKeys();
989
+ console.log('[Webhook] API key revoked for ' + email + ' — refund received');
990
+ return { received: true, revoked: true };
991
+ } catch(e) {
992
+ console.error('[data-compliance] charge.refunded handling error:', e.message);
993
+ return { received: true, ignored: true };
994
+ }
995
+ }
936
996
  return { received: true, type: event.type };
937
997
  } catch(e) { return { error: e.message, status: 400 }; }
938
998
  }