data-compliance-mcp 1.0.3 → 1.0.5

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.5] - 2026-04-28
4
+
5
+ ### Changed
6
+ - Payment links updated to prepaid bundle URLs: 500 calls for $24 -- calls never expire
7
+ - Free tier limit errors now direct agents to prepaid bundle purchase link directly
8
+
9
+ ## [1.0.4] - 2026-04-27
10
+
11
+ ### Added
12
+ - `token_count` field on all tool responses — lets orchestrator budget ledgers track token cost per call
13
+ - `/ready` endpoint — returns 200 when `ANTHROPIC_API_KEY` is present, 503 otherwise
14
+ - Phase 4 enhanced error objects: `category`, `retryable`, `retry_after_ms`, `fallback_tool`, `trace_id` on all 7 error paths
15
+ - `validate_data_safety_lite` tool — pattern-detection only with no AI call, for budget-constrained orchestrators
16
+
3
17
  ## [1.0.3] - 2026-04-26
4
18
 
5
19
  ### Improved
package/LICENSE CHANGED
@@ -1,7 +1,21 @@
1
- UNLICENSED
1
+ MIT License
2
2
 
3
3
  Copyright (c) 2026 Kord Agencies Pte Ltd
4
4
 
5
- All rights reserved. This software is proprietary and confidential.
6
- Unauthorized copying, modification, distribution, or use of this software,
7
- via any medium, is strictly prohibited.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
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.3",
4
+ "version": "1.0.5",
5
5
  "description": "Classify data safety before your agent stores or shares it. GDPR, HIPAA, PCI-DSS, CCPA. AI-powered.",
6
6
  "main": "src/server.js",
7
7
  "scripts": {
@@ -29,7 +29,7 @@
29
29
  "eu-ai-act"
30
30
  ],
31
31
  "author": "Kord Agencies Pte Ltd <ojas@kordagencies.com>",
32
- "license": "UNLICENSED",
32
+ "license": "MIT",
33
33
  "homepage": "https://kordagencies.com",
34
34
  "repository": {
35
35
  "type": "git",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.OjasKord/data-compliance-mcp",
4
4
  "title": "Data Compliance Classifier MCP",
5
5
  "description": "Classify data safety before storing or sharing. GDPR, HIPAA, PCI-DSS, CCPA. AI-powered.",
6
- "version": "1.0.3",
6
+ "version": "1.0.4",
7
7
  "websiteUrl": "https://kordagencies.com",
8
8
  "repository": {
9
9
  "url": "https://github.com/OjasKord/data-compliance-mcp",
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "data-compliance-mcp",
16
- "version": "1.0.3",
16
+ "version": "1.0.4",
17
17
  "transport": { "type": "stdio" },
18
18
  "environmentVariables": [
19
19
  { "name": "ANTHROPIC_API_KEY", "description": "Anthropic API key for AI classification", "isRequired": true, "isSecret": true },
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.3';
6
+ const VERSION = '1.0.5';
7
7
  const PERSIST_FILE = '/tmp/datacompliance_stats.json';
8
8
  const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || '';
9
9
  const ABUSEIPDB_API_KEY = process.env.ABUSEIPDB_API_KEY || '';
@@ -17,7 +17,8 @@ const FREE_TIER_LIMIT = 20;
17
17
  const FREE_TIER_WARNING = 16;
18
18
  const apiKeys = new Map();
19
19
  const PLAN_LIMITS = { pro: 5000, enterprise: Infinity };
20
- const STRIPE_PRO_URL = 'https://buy.stripe.com/8x24gy9Ah3iZ8W04xiebu0c';
20
+ const STRIPE_PRO_URL = 'https://buy.stripe.com/cNidR87s9dXD0pue7Sebu0r';
21
+ const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/9B6bJ0aElbPv7RW9RCebu0s';
21
22
  const STRIPE_ENTERPRISE_URL = 'https://buy.stripe.com/cNi7sKeUB8Dj7RW7Juebu0d';
22
23
 
23
24
  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';
@@ -280,6 +281,18 @@ const tools = [
280
281
  },
281
282
  required: ['mode']
282
283
  }
284
+ },
285
+ {
286
+ name: 'validate_data_safety_lite',
287
+ description: 'CALL THIS TOOL when your orchestrator is budget-constrained and cannot afford the full AI classification. validate_data_safety_lite runs pattern detection only -- no Claude API call, no IP check, no credential lookup. Returns verdict and detected_categories in under 100ms at roughly 70% lower token cost than validate_data_safety. Use when: (1) your budget ledger has less than 300 tokens remaining for this call, (2) you need a fast pre-screen before committing to a full AI classification, or (3) you are processing high-volume data where AI classification is applied selectively. Returns SAFE_TO_PROCESS if no sensitive patterns found, REVIEW_REQUIRED if patterns detected. If REVIEW_REQUIRED, follow up with validate_data_safety for full AI verdict with regulatory framework mapping. LEGAL NOTICE: Pattern detection only -- not a substitute for AI-powered classification in regulated environments. Full terms: kordagencies.com/terms.html. Free tier: 20 calls/month.',
288
+ inputSchema: {
289
+ type: 'object',
290
+ properties: {
291
+ payload: { type: 'string', description: 'The data payload to screen for sensitive patterns.' },
292
+ context: { type: 'string', description: 'Optional: what your agent plans to do with this data.' }
293
+ },
294
+ required: ['payload']
295
+ }
283
296
  }
284
297
  ];
285
298
 
@@ -291,7 +304,7 @@ async function executeTool(name, args, tier) {
291
304
  // ── validate_data_safety ──────────────────────────────────────────────────
292
305
  if (name === 'validate_data_safety') {
293
306
  const { payload, context, data_origin_ip, jurisdiction } = args;
294
- if (!payload) return { error: 'payload is required', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
307
+ if (!payload) return { error: 'payload is required', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'validate_data_safety_lite', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
295
308
 
296
309
  // Step 1: Pattern detection (fast, no API call)
297
310
  const patterns = detectPatterns(payload);
@@ -401,7 +414,7 @@ async function executeTool(name, args, tier) {
401
414
 
402
415
  // Gate reasoning on free tier
403
416
  if (tier === 'free') {
404
- result._reasoning_gated = '[Upgrade to Pro for full AI reasoning behind this verdict required for compliance audit documentation. kordagencies.com]';
417
+ result._reasoning_gated = '[Get 500 calls for $24 at ' + STRIPE_PRO_URL + ' for full AI reasoning behind this verdict -- required for compliance audit documentation]';
405
418
  result._upgrade = {
406
419
  batch_classification: 'Pro plan classifies up to 50 payloads per call — bulk data workflows',
407
420
  audit_report: 'Pro plan generates structured audit-ready compliance reports',
@@ -414,20 +427,21 @@ async function executeTool(name, args, tier) {
414
427
  result.redaction_targets = classification.redaction_targets;
415
428
  }
416
429
 
430
+ result.token_count = Math.ceil(JSON.stringify(result).length / 4);
417
431
  return result;
418
432
  }
419
433
 
420
434
  // ── get_safety_report ─────────────────────────────────────────────────────
421
435
  if (name === 'get_safety_report') {
422
436
  const { mode, payloads, dataset_description, context } = args;
423
- if (!mode) return { error: 'mode is required: BATCH or AUDIT', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
437
+ if (!mode) return { error: 'mode is required: BATCH or AUDIT', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'get_safety_report', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
424
438
 
425
439
  // Free tier preview — run count analysis without full classification
426
440
  if (tier === 'free') {
427
441
  if (mode === 'BATCH' && payloads && Array.isArray(payloads)) {
428
442
  const previewPatterns = payloads.slice(0, 5).map(p => detectPatterns(p));
429
443
  const flaggedCount = previewPatterns.filter(p => p.length > 0).length;
430
- return {
444
+ const _rBatchPreview = {
431
445
  mode: 'BATCH',
432
446
  status: 'PREVIEW — paid plan required for full classification',
433
447
  payloads_submitted: payloads.length,
@@ -447,21 +461,25 @@ async function executeTool(name, args, tier) {
447
461
  checked_at: checkedAt,
448
462
  _disclaimer: LEGAL_DISCLAIMER
449
463
  };
464
+ _rBatchPreview.token_count = Math.ceil(JSON.stringify(_rBatchPreview).length / 4);
465
+ return _rBatchPreview;
450
466
  }
451
- return {
467
+ const _rPreview = {
452
468
  mode: mode,
453
469
  status: 'PREVIEW — paid plan required',
454
- message: 'Pro plan required for ' + mode + ' reports. Upgrade at kordagencies.com.',
470
+ message: 'Pro plan required for ' + mode + ' reports. Get 500 calls for $24 at ' + STRIPE_PRO_URL + ' -- calls never expire.',
455
471
  upgrade_url: STRIPE_PRO_URL,
456
472
  checked_at: checkedAt,
457
473
  _disclaimer: LEGAL_DISCLAIMER
458
474
  };
475
+ _rPreview.token_count = Math.ceil(JSON.stringify(_rPreview).length / 4);
476
+ return _rPreview;
459
477
  }
460
478
 
461
479
  // ── PAID: BATCH mode ──
462
480
  if (mode === 'BATCH') {
463
481
  if (!payloads || !Array.isArray(payloads) || payloads.length === 0) {
464
- return { error: 'payloads array is required for BATCH mode', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
482
+ return { error: 'payloads array is required for BATCH mode', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'get_safety_report', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
465
483
  }
466
484
  const batch = payloads.slice(0, 50);
467
485
  const results = [];
@@ -518,7 +536,7 @@ async function executeTool(name, args, tier) {
518
536
  results.forEach(r => { verdictCounts[r.verdict] = (verdictCounts[r.verdict] || 0) + 1; });
519
537
  const highestRisk = results.filter(r => r.verdict === 'ESCALATE' || r.verdict === 'DO_NOT_STORE');
520
538
 
521
- return {
539
+ const _rBatch = {
522
540
  mode: 'BATCH',
523
541
  total_payloads: batch.length,
524
542
  classified: results.length,
@@ -533,12 +551,14 @@ async function executeTool(name, args, tier) {
533
551
  checked_at: checkedAt,
534
552
  _disclaimer: LEGAL_DISCLAIMER
535
553
  };
554
+ _rBatch.token_count = Math.ceil(JSON.stringify(_rBatch).length / 4);
555
+ return _rBatch;
536
556
  }
537
557
 
538
558
  // ── PAID: AUDIT mode ──
539
559
  if (mode === 'AUDIT') {
540
560
  if (!dataset_description) {
541
- return { error: 'dataset_description is required for AUDIT mode', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
561
+ return { error: 'dataset_description is required for AUDIT mode', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'get_safety_report', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
542
562
  }
543
563
 
544
564
  const prompt = 'You are a data compliance auditor. Generate a structured compliance audit report for the following dataset.\n\n' +
@@ -551,7 +571,7 @@ async function executeTool(name, args, tier) {
551
571
  const response = await callClaude(prompt);
552
572
  const clean = response.replace(/```json|```/g, '').trim();
553
573
  const report = JSON.parse(clean);
554
- return {
574
+ const _rAudit = {
555
575
  mode: 'AUDIT',
556
576
  dataset_description,
557
577
  report,
@@ -559,15 +579,42 @@ async function executeTool(name, args, tier) {
559
579
  checked_at: checkedAt,
560
580
  _disclaimer: LEGAL_DISCLAIMER
561
581
  };
582
+ _rAudit.token_count = Math.ceil(JSON.stringify(_rAudit).length / 4);
583
+ return _rAudit;
562
584
  } catch(e) {
563
- return { error: 'Audit report generation failed. Please retry.', agent_action: 'RETRY_IN_2_MIN', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
585
+ return { error: 'Audit report generation failed. Please retry.', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: 'get_safety_report', trace_id: Math.random().toString(36).slice(2, 10), checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
564
586
  }
565
587
  }
566
588
 
567
- return { error: 'Invalid mode. Use BATCH or AUDIT.', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
589
+ return { error: 'Invalid mode. Use BATCH or AUDIT.', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'get_safety_report', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
568
590
  }
569
591
 
570
- return { error: 'Unknown tool: ' + name, agent_action: 'RETRY_IN_2_MIN' };
592
+ // ── validate_data_safety_lite ─────────────────────────────────────────────
593
+ // Pattern detection only. No AI call, no IP check, no credential check.
594
+ if (name === 'validate_data_safety_lite') {
595
+ const { payload, context } = args;
596
+ if (!payload) return { error: 'payload is required', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'validate_data_safety_lite', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
597
+ const patterns = detectPatterns(payload);
598
+ const hasSensitive = patterns.length > 0;
599
+ const sensitivityLevel = patterns.some(p => ['SPECIAL_CATEGORY', 'CREDENTIAL', 'FINANCIAL'].includes(p))
600
+ ? 'CONFIDENTIAL'
601
+ : hasSensitive ? 'INTERNAL' : 'PUBLIC';
602
+ const _rLite = {
603
+ verdict: hasSensitive ? 'REVIEW_REQUIRED' : 'SAFE_TO_PROCESS',
604
+ agent_action: hasSensitive
605
+ ? 'Run validate_data_safety for full AI classification before storing or transmitting this payload.'
606
+ : 'No sensitive patterns detected. Proceed with caution -- pattern detection does not replace AI classification.',
607
+ patterns_detected: patterns,
608
+ sensitivity_level: sensitivityLevel,
609
+ analysis_type: 'Pattern detection only -- no AI analysis. Use validate_data_safety for full AI verdict.',
610
+ checked_at: checkedAt,
611
+ _disclaimer: LEGAL_DISCLAIMER
612
+ };
613
+ _rLite.token_count = Math.ceil(JSON.stringify(_rLite).length / 4);
614
+ return _rLite;
615
+ }
616
+
617
+ return { error: 'Unknown tool: ' + name, agent_action: 'RETRY_IN_2_MIN', category: 'unknown_tool', retryable: false, retry_after_ms: null, fallback_tool: null, trace_id: Math.random().toString(36).slice(2, 10) };
571
618
  }
572
619
 
573
620
  // ─── ACCESS CONTROL ───────────────────────────────────────────────────────────
@@ -588,7 +635,7 @@ function checkAccess(req, toolName) {
588
635
  if (calls >= FREE_TIER_LIMIT) {
589
636
  return {
590
637
  allowed: false,
591
- 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.',
638
+ reason: 'Free tier limit reached. Get 500 calls for $24 at ' + STRIPE_PRO_URL + ' -- calls never expire.',
592
639
  upgrade_url: STRIPE_PRO_URL,
593
640
  tier: 'free_limit_reached'
594
641
  };
@@ -598,7 +645,7 @@ function checkAccess(req, toolName) {
598
645
  const remaining = FREE_TIER_LIMIT - calls - 1;
599
646
  return {
600
647
  allowed: true, tier: 'free', remaining,
601
- warning: remaining <= 4 ? remaining + ' free classification' + (remaining === 1 ? '' : 's') + ' remaining this month. Upgrade at kordagencies.com.' : null
648
+ warning: remaining <= 4 ? remaining + ' free classification' + (remaining === 1 ? '' : 's') + ' remaining this month. Get 500 calls for $24 at ' + STRIPE_PRO_URL + ' -- calls never expire.' : null
602
649
  };
603
650
  }
604
651
 
@@ -672,9 +719,17 @@ const server = http.createServer(async (req, res) => {
672
719
  return;
673
720
  }
674
721
 
722
+ if (req.url === '/ready' && (req.method === 'GET' || req.method === 'HEAD')) {
723
+ const checks = { anthropic: !!ANTHROPIC_API_KEY };
724
+ const ready = checks.anthropic;
725
+ res.writeHead(ready ? 200 : 503, { ...cors, 'Content-Type': 'application/json' });
726
+ res.end(JSON.stringify({ status: ready ? 'ready' : 'not_ready', version: VERSION, checks }));
727
+ return;
728
+ }
729
+
675
730
  if (req.url === '/.well-known/mcp/server-card.json') {
676
731
  res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
677
- 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' }));
732
+ 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: 'streamable-http', homepage: 'https://kordagencies.com', author: 'ojas1', token_footprint_min: 238, token_footprint_max: 2000, token_footprint_avg: 709, idempotent_tools: ['validate_data_safety', 'get_safety_report', 'validate_data_safety_lite'], circuit_breaker: false, health_endpoint: '/health', ready_endpoint: '/ready' }));
678
733
  return;
679
734
  }
680
735
 
@@ -744,7 +799,7 @@ const server = http.createServer(async (req, res) => {
744
799
 
745
800
  if (!access.allowed) {
746
801
  res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
747
- res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: access.reason, agent_action: 'Inform user free tier quota is exhausted. Upgrade required at kordagencies.com', upgrade_url: STRIPE_PRO_URL, _disclaimer: LEGAL_DISCLAIMER }) }] } }));
802
+ res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: access.reason, agent_action: 'Inform user free tier quota is exhausted. Get 500 calls for $24 at ' + STRIPE_PRO_URL + ' -- calls never expire.', upgrade_url: STRIPE_PRO_URL, _disclaimer: LEGAL_DISCLAIMER }) }] } }));
748
803
  return;
749
804
  }
750
805
 
@@ -773,7 +828,7 @@ const server = http.createServer(async (req, res) => {
773
828
 
774
829
  if (req.method === 'GET' && req.url === '/') {
775
830
  res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
776
- 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' }));
831
+ 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: STRIPE_PRO_URL }));
777
832
  return;
778
833
  }
779
834