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 +2 -2
- package/package.json +1 -1
- package/src/server.js +26 -5
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
|
-
|
|
|
158
|
-
|
|
|
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.
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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'));
|