vat-validator-mcp 1.4.7 → 1.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -154,8 +154,8 @@ Every response includes `source_url` and `checked_at` so agents can verify exact
154
154
  | Plan | Validations | Price |
155
155
  |---|---|---|
156
156
  | Free | 20/month | No API key required |
157
- | Pro | 5,000/month | $39/month |
158
- | Enterprise | Unlimited + batch | $199/month |
157
+ | Starter | 500-call bundle | $8 |
158
+ | Pro | 2,000-call bundle | $28 |
159
159
 
160
160
  Upgrade at **[kordagencies.com](https://kordagencies.com)**
161
161
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vat-validator-mcp",
3
3
  "mcpName": "io.github.OjasKord/vat-validator-mcp",
4
- "version": "1.4.7",
4
+ "version": "1.4.8",
5
5
  "description": "VAT number validation for AI agents. EU VIES, UK HMRC, Australian ABN in one call.",
6
6
  "main": "src/server.js",
7
7
  "scripts": {
package/src/server.js CHANGED
@@ -4,7 +4,8 @@ const crypto = require('crypto');
4
4
  const fs = require('fs');
5
5
 
6
6
  const PERSIST_FILE = '/tmp/vat_stats.json';
7
- const VERSION = '1.4.7';
7
+ const API_KEYS_FILE = '/tmp/vat_apikeys.json';
8
+ const VERSION = '1.4.8';
8
9
  const PRO_UPGRADE_URL = 'https://buy.stripe.com/28EeVceUB06N1ty3teebu0l';
9
10
  const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/00w14m7s96vb1ty5Bmebu0m';
10
11
  const RESEND_API_KEY = process.env.RESEND_API_KEY || '';
@@ -39,6 +40,22 @@ function loadStats() {
39
40
  } catch(e) { console.error('Stats load error:', e.message); }
40
41
  }
41
42
 
43
+ function getMonthKey(ip) { return ip + ':' + new Date().toISOString().slice(0, 7); }
44
+
45
+ function saveApiKeys() {
46
+ try { fs.writeFileSync(API_KEYS_FILE, JSON.stringify(Array.from(apiKeys.entries()))); } catch(e) { console.error('API keys save error:', e.message); }
47
+ }
48
+
49
+ function loadApiKeys() {
50
+ try {
51
+ if (fs.existsSync(API_KEYS_FILE)) {
52
+ const entries = JSON.parse(fs.readFileSync(API_KEYS_FILE, 'utf8'));
53
+ entries.forEach(([k, v]) => apiKeys.set(k, v));
54
+ console.log('API keys loaded: ' + apiKeys.size + ' keys');
55
+ }
56
+ } catch(e) { console.error('API keys load error:', e.message); }
57
+ }
58
+
42
59
  function generateApiKey() { return 'vat_' + crypto.randomBytes(24).toString('hex'); }
43
60
  function getPlanFromProduct(name) {
44
61
  if (!name) return 'pro';
@@ -325,9 +342,10 @@ function checkAccess(req) {
325
342
  return { allowed: true, tier: record.plan, record };
326
343
  }
327
344
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
328
- const calls = freeTierUsage.get(ip) || 0;
345
+ const monthKey = getMonthKey(ip);
346
+ const calls = freeTierUsage.get(monthKey) || 0;
329
347
  if (calls >= FREE_TIER_LIMIT) return { allowed: false, reason: 'Free tier limit reached. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire.', upgrade_url: PRO_UPGRADE_URL, tier: 'free_limit_reached' };
330
- freeTierUsage.set(ip, calls + 1);
348
+ freeTierUsage.set(monthKey, calls + 1);
331
349
  saveStats();
332
350
  const remaining = FREE_TIER_LIMIT - calls - 1;
333
351
  return { allowed: true, tier: 'free', remaining, warning: remaining < 5 ? remaining + ' free validations remaining this month. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire.' : null };
@@ -370,6 +388,7 @@ async function handleStripeWebhook(body, sig) {
370
388
  if (email) {
371
389
  const apiKey = generateApiKey();
372
390
  apiKeys.set(apiKey, { email, plan, createdAt: new Date().toISOString(), calls: 0, limit: PLAN_LIMITS[plan] });
391
+ saveApiKeys();
373
392
  await sendApiKeyEmail(email, apiKey, plan);
374
393
  console.log('[vat] API key created for ' + email + ' (' + plan + ')');
375
394
  return { success: true, email, plan };
@@ -434,7 +453,8 @@ const server = http.createServer(async (req, res) => {
434
453
  const toolCounts = {};
435
454
  usageLog.forEach(e => { toolCounts[e.tool] = (toolCounts[e.tool] || 0) + 1; });
436
455
  res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
437
- 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() }));
456
+ const freeUniqueIPs = new Set(Array.from(freeTierUsage.keys()).map(k => k.split(':')[0])).size;
457
+ res.end(JSON.stringify({ free_tier_unique_ips: freeUniqueIPs, free_tier_total_calls: totalFreeCalls, paid_keys_issued: apiKeys.size, tool_usage: toolCounts, recent_calls: usageLog.slice(-20).reverse() }));
438
458
  return;
439
459
  }
440
460
 
@@ -554,7 +574,7 @@ const server = http.createServer(async (req, res) => {
554
574
  // Partial response for free tier
555
575
  if (req._tier === 'free' && !result.error) {
556
576
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
557
- const used = freeTierUsage.get(ip) || 0;
577
+ const used = freeTierUsage.get(getMonthKey(ip)) || 0;
558
578
  const remaining = FREE_TIER_LIMIT - used;
559
579
  const isWarning = used >= FREE_TIER_WARNING;
560
580
 
@@ -641,6 +661,7 @@ setupStdio();
641
661
 
642
662
  server.listen(PORT, () => {
643
663
  loadStats();
664
+ loadApiKeys();
644
665
  console.log('VAT Validator MCP v' + VERSION + ' running on port ' + PORT);
645
666
  console.log('Free tier: ' + FREE_TIER_LIMIT + ' calls/IP/month, no API key required');
646
667
  console.log('Resend: ' + (RESEND_API_KEY ? 'configured' : 'MISSING'));