vat-validator-mcp 1.0.0
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/LICENSE +10 -0
- package/README.md +47 -0
- package/glama.json +19 -0
- package/package.json +18 -0
- package/server.json +17 -0
- package/smithery.yaml +66 -0
- package/src/server.js +518 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
PROPRIETARY LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kordagencies. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files are proprietary and confidential.
|
|
6
|
+
Unauthorized copying, distribution, modification, or use of this software,
|
|
7
|
+
via any medium, is strictly prohibited without the express written permission
|
|
8
|
+
of Kordagencies.
|
|
9
|
+
|
|
10
|
+
For licensing inquiries: ojas@kordagencies.com
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# VAT Validator MCP
|
|
2
|
+
|
|
3
|
+
Validate EU, UK, and Australian VAT numbers for AI agents. EU VIES, UK HMRC, and Australian ABN in one call. Required for EU ViDA mandatory e-invoicing compliance.
|
|
4
|
+
|
|
5
|
+
## Free Tier
|
|
6
|
+
|
|
7
|
+
20 validations/month. No API key required. Just connect and start validating.
|
|
8
|
+
|
|
9
|
+
## Tools
|
|
10
|
+
|
|
11
|
+
### validate_vat
|
|
12
|
+
Validate any VAT number — auto-detects country from prefix and routes to the correct authority.
|
|
13
|
+
- EU (all 27 member states) via VIES
|
|
14
|
+
- UK (GB prefix) via HMRC
|
|
15
|
+
- Australia (AU prefix or 11-digit ABN) via ABR
|
|
16
|
+
|
|
17
|
+
### validate_uk_vat
|
|
18
|
+
Validate UK VAT numbers against HMRC live records. Returns consultation number for audit trail.
|
|
19
|
+
|
|
20
|
+
### get_vat_rates
|
|
21
|
+
Get current VAT rates for any EU country, UK, or Australia. Returns standard and all reduced rates.
|
|
22
|
+
|
|
23
|
+
### batch_validate
|
|
24
|
+
Validate up to 10 VAT numbers in one call across any mix of EU, UK, and Australian numbers. Paid tier only.
|
|
25
|
+
|
|
26
|
+
## Pricing
|
|
27
|
+
|
|
28
|
+
- **Free**: 20 validations/month, no API key
|
|
29
|
+
- **Pro**: $99/month — 5,000 validations/month
|
|
30
|
+
- **Enterprise**: $299/month — unlimited + batch validation
|
|
31
|
+
|
|
32
|
+
Get your API key at [kordagencies.com](https://kordagencies.com)
|
|
33
|
+
|
|
34
|
+
## Legal
|
|
35
|
+
|
|
36
|
+
Results are for informational purposes only and do not constitute legal or tax advice. Verify all results with a qualified tax advisor. Full terms: kordagencies.com/terms.html
|
|
37
|
+
|
|
38
|
+
## MCP Config
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"vat-validator": {
|
|
43
|
+
"url": "https://vat-validator-mcp-production.up.railway.app",
|
|
44
|
+
"headers": { "x-api-key": "YOUR_API_KEY" }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
package/glama.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vat-validator-mcp",
|
|
3
|
+
"title": "VAT Validator MCP",
|
|
4
|
+
"description": "Validate EU, UK, and Australian VAT numbers for AI agents. EU VIES, UK HMRC, Australian ABN. Required for EU ViDA e-invoicing compliance.",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"homepage": "https://kordagencies.com",
|
|
7
|
+
"license": "UNLICENSED",
|
|
8
|
+
"tools": [
|
|
9
|
+
{ "name": "validate_vat", "description": "Validate any EU, UK, or Australian VAT number" },
|
|
10
|
+
{ "name": "validate_uk_vat", "description": "Validate UK VAT number against HMRC with consultation number" },
|
|
11
|
+
{ "name": "get_vat_rates", "description": "Get VAT rates by country for EU, UK, Australia" },
|
|
12
|
+
{ "name": "batch_validate", "description": "Validate up to 10 VAT numbers in one call (paid)" }
|
|
13
|
+
],
|
|
14
|
+
"pricing": {
|
|
15
|
+
"free": "20 validations/month, no API key",
|
|
16
|
+
"pro": "$99/month — 5,000 validations/month",
|
|
17
|
+
"enterprise": "$299/month — unlimited + batch"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vat-validator-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "VAT number validation for AI agents. EU VIES, UK HMRC, Australian ABN in one call.",
|
|
5
|
+
"main": "src/server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node src/server.js"
|
|
8
|
+
},
|
|
9
|
+
"mcpName": "io.github.OjasKord/vat-validator-mcp",
|
|
10
|
+
"keywords": ["mcp", "vat", "vat-validation", "eu-vat", "hmrc", "vies", "e-invoicing", "tax-compliance", "b2b", "invoice", "vida"],
|
|
11
|
+
"author": "Ojas Kord <ojas@kordagencies.com>",
|
|
12
|
+
"license": "UNLICENSED",
|
|
13
|
+
"homepage": "https://kordagencies.com",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/OjasKord/vat-validator-mcp"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.OjasKord/vat-validator-mcp",
|
|
4
|
+
"title": "VAT Validator MCP",
|
|
5
|
+
"description": "Validate EU, UK, AU VAT numbers for AI agents. EU ViDA e-invoicing compliance.",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"repository": { "url": "https://github.com/OjasKord/vat-validator-mcp", "source": "github" },
|
|
8
|
+
"packages": [{
|
|
9
|
+
"registryType": "npm",
|
|
10
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
11
|
+
"identifier": "vat-validator-mcp",
|
|
12
|
+
"version": "1.0.0",
|
|
13
|
+
"transport": { "type": "stdio" },
|
|
14
|
+
"environmentVariables": []
|
|
15
|
+
}],
|
|
16
|
+
"remotes": [{ "type": "streamable-http", "url": "https://vat-validator-mcp-production.up.railway.app" }]
|
|
17
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
startCommand:
|
|
2
|
+
type: http
|
|
3
|
+
url: https://vat-validator-mcp-production.up.railway.app
|
|
4
|
+
configSchema:
|
|
5
|
+
type: object
|
|
6
|
+
properties:
|
|
7
|
+
apiKey:
|
|
8
|
+
type: string
|
|
9
|
+
description: "VAT Validator MCP API key from kordagencies.com. Leave blank for free tier (20 validations/month)."
|
|
10
|
+
x-from:
|
|
11
|
+
header: "x-api-key"
|
|
12
|
+
required: []
|
|
13
|
+
|
|
14
|
+
systemPrompt: |
|
|
15
|
+
VAT Validator MCP validates VAT numbers for AI agents handling B2B invoicing, supplier onboarding, and e-invoicing compliance.
|
|
16
|
+
|
|
17
|
+
EU ViDA (VAT in the Digital Age) regulation now requires VAT validation as part of mandatory e-invoicing across EU member states. An invalid VAT number on a B2B invoice can result in tax authority fines, rejected invoices, and loss of VAT reclaim rights.
|
|
18
|
+
|
|
19
|
+
WHEN TO USE THIS SERVER
|
|
20
|
+
|
|
21
|
+
Call these tools whenever your agent:
|
|
22
|
+
- Is about to generate a B2B invoice for an EU, UK, or Australian business
|
|
23
|
+
- Is onboarding a new supplier or customer from the EU or UK
|
|
24
|
+
- Needs to apply the correct VAT rate to a cross-border transaction
|
|
25
|
+
- Is running a compliance audit on existing supplier VAT numbers
|
|
26
|
+
- Is processing a batch of invoices and needs to verify all VAT numbers
|
|
27
|
+
|
|
28
|
+
TOOLS
|
|
29
|
+
|
|
30
|
+
validate_vat
|
|
31
|
+
- Call for any VAT number from EU (all 27 countries), UK, or Australia
|
|
32
|
+
- Auto-detects country from prefix and routes to correct authority
|
|
33
|
+
- Returns valid/invalid, company name, address
|
|
34
|
+
- Use before generating any B2B invoice
|
|
35
|
+
- Free tier: no API key needed for first 20 calls/month
|
|
36
|
+
|
|
37
|
+
validate_uk_vat
|
|
38
|
+
- Call specifically for UK VAT numbers when you need the HMRC consultation number for your audit trail
|
|
39
|
+
- More detailed than validate_vat for UK numbers
|
|
40
|
+
- Returns company name, registered address, consultation number
|
|
41
|
+
- Free tier: no API key needed for first 20 calls/month
|
|
42
|
+
|
|
43
|
+
get_vat_rates
|
|
44
|
+
- Call when you need to apply the correct VAT rate to an invoice or quote
|
|
45
|
+
- Covers all 27 EU member states, UK, and Australia
|
|
46
|
+
- Returns standard rate and all reduced rates
|
|
47
|
+
- Always verify with official tax authority before use
|
|
48
|
+
- Free tier: no API key needed for first 20 calls/month
|
|
49
|
+
|
|
50
|
+
batch_validate
|
|
51
|
+
- Call when you need to validate multiple VAT numbers at once
|
|
52
|
+
- Up to 10 numbers per call, any mix of EU/UK/AU
|
|
53
|
+
- Use for supplier audits or onboarding batches
|
|
54
|
+
- Requires paid API key
|
|
55
|
+
|
|
56
|
+
LEGAL NOTICE
|
|
57
|
+
All results are for informational purposes only and do not constitute legal or tax advice. Operator must independently verify results with a qualified tax advisor before making compliance decisions. Full terms: kordagencies.com/terms.html
|
|
58
|
+
|
|
59
|
+
FREE TIER
|
|
60
|
+
20 validations/month with no API key.
|
|
61
|
+
Upgrade at kordagencies.com - Pro $99/month (5,000 validations), Enterprise $299/month (unlimited + batch).
|
|
62
|
+
|
|
63
|
+
COUNTRY CODES
|
|
64
|
+
EU: AT BE BG CY CZ DE DK EE EL ES FI FR HR HU IE IT LT LU LV MT NL PL PT RO SE SI SK
|
|
65
|
+
UK: GB prefix
|
|
66
|
+
Australia: AU prefix or 11-digit ABN
|
package/src/server.js
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const https = require('https');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
const PERSIST_FILE = '/tmp/vat_stats.json';
|
|
7
|
+
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || '';
|
|
8
|
+
const RESEND_API_KEY = process.env.RESEND_API_KEY || '';
|
|
9
|
+
const PORT = process.env.PORT || 3000;
|
|
10
|
+
const STATS_KEY = process.env.STATS_KEY || 'ojas2026';
|
|
11
|
+
|
|
12
|
+
const freeTierUsage = new Map();
|
|
13
|
+
const usageLog = [];
|
|
14
|
+
const FREE_TIER_LIMIT = 20;
|
|
15
|
+
const apiKeys = new Map();
|
|
16
|
+
const PLAN_LIMITS = { pro: 5000, enterprise: Infinity };
|
|
17
|
+
|
|
18
|
+
function saveStats() {
|
|
19
|
+
try {
|
|
20
|
+
fs.writeFileSync(PERSIST_FILE, JSON.stringify({
|
|
21
|
+
freeTierUsage: Array.from(freeTierUsage.entries()),
|
|
22
|
+
usageLog: usageLog.slice(-1000)
|
|
23
|
+
}));
|
|
24
|
+
} catch(e) { console.error('Stats save error:', e.message); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function loadStats() {
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(PERSIST_FILE)) {
|
|
30
|
+
const data = JSON.parse(fs.readFileSync(PERSIST_FILE, 'utf8'));
|
|
31
|
+
if (data.freeTierUsage) data.freeTierUsage.forEach(([k, v]) => freeTierUsage.set(k, v));
|
|
32
|
+
if (data.usageLog) usageLog.push(...data.usageLog);
|
|
33
|
+
console.log(`Stats loaded: ${freeTierUsage.size} IPs, ${usageLog.length} calls`);
|
|
34
|
+
}
|
|
35
|
+
} catch(e) { console.error('Stats load error:', e.message); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function generateApiKey() { return 'vat_' + crypto.randomBytes(24).toString('hex'); }
|
|
39
|
+
|
|
40
|
+
function getPlanFromProduct(name) {
|
|
41
|
+
if (!name) return 'pro';
|
|
42
|
+
return name.toLowerCase().includes('enterprise') ? 'enterprise' : 'pro';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function sendEmail(to, subject, html) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const body = JSON.stringify({ from: 'VAT Validator MCP <ojas@kordagencies.com>', to: [to], subject, html });
|
|
48
|
+
const req = https.request({
|
|
49
|
+
hostname: 'api.resend.com', path: '/emails', method: 'POST',
|
|
50
|
+
headers: { 'Authorization': `Bearer ${RESEND_API_KEY}`, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
|
|
51
|
+
}, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve({ status: res.statusCode, body: d })); });
|
|
52
|
+
req.on('error', e => resolve({ error: e.message }));
|
|
53
|
+
req.write(body); req.end();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function sendApiKeyEmail(email, apiKey, plan) {
|
|
58
|
+
const planLabel = plan === 'enterprise' ? 'Enterprise' : 'Pro';
|
|
59
|
+
const limit = plan === 'enterprise' ? 'Unlimited' : '5,000';
|
|
60
|
+
const html = `<!DOCTYPE html><html><body style="font-family:monospace;background:#080A0F;color:#E8EDF5;padding:40px;max-width:600px;margin:0 auto"><div style="border:1px solid rgba(0,229,195,0.3);border-radius:8px;padding:32px"><div style="color:#00E5C3;font-size:13px;letter-spacing:0.2em;text-transform:uppercase;margin-bottom:24px">VAT Validator MCP - ${planLabel} Plan</div><h1 style="font-size:24px;font-weight:700;margin-bottom:8px;color:#FFFFFF">Your API key is ready.</h1><p style="color:#8A95A8;margin-bottom:32px">Welcome to VAT Validator MCP. Here is everything you need to get started.</p><div style="background:#141B24;border:1px solid rgba(255,255,255,0.1);border-radius:6px;padding:20px;margin-bottom:24px"><div style="color:#5A6478;font-size:11px;letter-spacing:0.15em;text-transform:uppercase;margin-bottom:8px">Your API Key</div><div style="color:#00E5C3;font-size:14px;word-break:break-all;font-weight:500">${apiKey}</div></div><div style="background:#141B24;border:1px solid rgba(255,255,255,0.1);border-radius:6px;padding:20px;margin-bottom:24px"><div style="color:#5A6478;font-size:11px;letter-spacing:0.15em;text-transform:uppercase;margin-bottom:12px">Add to your MCP config</div><div style="color:#86EFAC;font-size:12px;line-height:2">{<br> "vat-validator": {<br> "url": "https://vat-validator-mcp-production.up.railway.app",<br> "headers": { "x-api-key": "${apiKey}" }<br> }<br>}</div></div><div style="background:#141B24;border:1px solid rgba(255,255,255,0.1);border-radius:6px;padding:20px;margin-bottom:24px"><div style="color:#5A6478;font-size:11px;letter-spacing:0.15em;text-transform:uppercase;margin-bottom:12px">Your Plan</div><div style="color:#E8EDF5;font-size:13px;line-height:2">Plan: ${planLabel}<br>VAT validations: ${limit}/month<br>Batch validation: ${plan === 'enterprise' ? 'Included' : 'Up to 10 per call'}<br>All tools included</div></div><div style="background:#0D1219;border:1px solid rgba(255,255,255,0.07);border-radius:6px;padding:16px;margin-bottom:24px;font-size:11px;color:#5A6478;line-height:1.7">By using your API key you agree to the VAT Validator MCP Terms of Service at <a href="https://kordagencies.com/terms.html" style="color:#00E5C3">kordagencies.com/terms.html</a>. Results are provided for informational purposes only and do not constitute legal or tax advice. You must independently verify all results with a qualified tax advisor before making compliance decisions. Provider maximum liability is limited to subscription fees paid in the preceding 3 months.</div><p style="color:#5A6478;font-size:12px">Questions? Email ojas@kordagencies.com</p><p style="color:#5A6478;font-size:12px;margin-top:8px">Ojas, Kordagencies</p></div></body></html>`;
|
|
61
|
+
return sendEmail(email, `Your VAT Validator MCP ${planLabel} API Key`, html);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validate EU VAT number via VIES REST API
|
|
65
|
+
async function validateVIES(countryCode, vatNumber) {
|
|
66
|
+
return new Promise((resolve) => {
|
|
67
|
+
const path = `/taxation_customs/vies/rest-api/ms/${countryCode}/vat/${vatNumber}`;
|
|
68
|
+
const req = https.request({
|
|
69
|
+
hostname: 'ec.europa.eu', path, method: 'GET',
|
|
70
|
+
headers: { 'Accept': 'application/json', 'User-Agent': 'VAT-Validator-MCP/1.0' }
|
|
71
|
+
}, res => {
|
|
72
|
+
let d = ''; res.on('data', c => d += c);
|
|
73
|
+
res.on('end', () => {
|
|
74
|
+
try { resolve({ source: 'VIES', data: JSON.parse(d) }); }
|
|
75
|
+
catch(e) { resolve({ source: 'VIES', error: 'Parse error', raw: d.slice(0, 200) }); }
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
req.on('error', e => resolve({ source: 'VIES', error: e.message }));
|
|
79
|
+
req.setTimeout(8000, () => { req.destroy(); resolve({ source: 'VIES', error: 'Timeout — VIES unavailable, try again later' }); });
|
|
80
|
+
req.end();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate UK VAT number via HMRC API (no key needed for basic check)
|
|
85
|
+
async function validateHMRC(vatNumber) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
const clean = vatNumber.replace(/^GB/i, '').replace(/\s/g, '');
|
|
88
|
+
const req = https.request({
|
|
89
|
+
hostname: 'api.service.hmrc.gov.uk',
|
|
90
|
+
path: `/organisations/vat/check-vat-number/lookup/${clean}`,
|
|
91
|
+
method: 'GET',
|
|
92
|
+
headers: { 'Accept': 'application/vnd.hmrc.1.0+json' }
|
|
93
|
+
}, res => {
|
|
94
|
+
let d = ''; res.on('data', c => d += c);
|
|
95
|
+
res.on('end', () => {
|
|
96
|
+
try { resolve({ source: 'HMRC', status: res.statusCode, data: JSON.parse(d) }); }
|
|
97
|
+
catch(e) { resolve({ source: 'HMRC', error: 'Parse error' }); }
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
req.on('error', e => resolve({ source: 'HMRC', error: e.message }));
|
|
101
|
+
req.setTimeout(8000, () => { req.destroy(); resolve({ source: 'HMRC', error: 'Timeout' }); });
|
|
102
|
+
req.end();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Validate Australian ABN via ABR API
|
|
107
|
+
async function validateABN(abn) {
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
const clean = abn.replace(/\s/g, '');
|
|
110
|
+
const path = `/json/?abn=${clean}&guid=f7b75e2e-6d6a-4c1c-a8d4-5b2e3c9d8f4a`;
|
|
111
|
+
const req = https.request({
|
|
112
|
+
hostname: 'abr.business.gov.au', path, method: 'GET',
|
|
113
|
+
headers: { 'Accept': 'application/json' }
|
|
114
|
+
}, res => {
|
|
115
|
+
let d = ''; res.on('data', c => d += c);
|
|
116
|
+
res.on('end', () => {
|
|
117
|
+
try { resolve({ source: 'ABR', data: JSON.parse(d) }); }
|
|
118
|
+
catch(e) { resolve({ source: 'ABR', error: 'Parse error' }); }
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
req.on('error', e => resolve({ source: 'ABR', error: e.message }));
|
|
122
|
+
req.setTimeout(8000, () => { req.destroy(); resolve({ source: 'ABR', error: 'Timeout' }); });
|
|
123
|
+
req.end();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Detect country from VAT number prefix and route appropriately
|
|
128
|
+
function detectCountry(vatNumber) {
|
|
129
|
+
const clean = vatNumber.trim().toUpperCase().replace(/\s/g, '');
|
|
130
|
+
if (clean.startsWith('GB')) return { country: 'GB', type: 'uk', number: clean.slice(2) };
|
|
131
|
+
if (clean.startsWith('AU') || /^\d{11}$/.test(clean)) return { country: 'AU', type: 'au', number: clean };
|
|
132
|
+
// EU country codes
|
|
133
|
+
const euCodes = ['AT','BE','BG','CY','CZ','DE','DK','EE','EL','ES','FI','FR','HR','HU','IE','IT','LT','LU','LV','MT','NL','PL','PT','RO','SE','SI','SK'];
|
|
134
|
+
for (const code of euCodes) {
|
|
135
|
+
if (clean.startsWith(code)) return { country: code, type: 'eu', number: clean.slice(2) };
|
|
136
|
+
}
|
|
137
|
+
return { country: null, type: 'unknown', number: clean };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const LEGAL_DISCLAIMER = 'Results are for informational purposes only and do not constitute legal or tax advice. Operator must independently verify all results with a qualified tax advisor before making compliance decisions. A VALID result does not guarantee compliance with all applicable tax laws. Provider maximum liability is limited to subscription fees paid in the preceding 3 months. Full terms: kordagencies.com/terms.html';
|
|
141
|
+
|
|
142
|
+
const VAT_RATES = {
|
|
143
|
+
AT: { standard: 20, reduced: [10, 13], country: 'Austria' },
|
|
144
|
+
BE: { standard: 21, reduced: [6, 12], country: 'Belgium' },
|
|
145
|
+
BG: { standard: 20, reduced: [9], country: 'Bulgaria' },
|
|
146
|
+
CY: { standard: 19, reduced: [5, 9], country: 'Cyprus' },
|
|
147
|
+
CZ: { standard: 21, reduced: [12], country: 'Czech Republic' },
|
|
148
|
+
DE: { standard: 19, reduced: [7], country: 'Germany' },
|
|
149
|
+
DK: { standard: 25, reduced: [], country: 'Denmark' },
|
|
150
|
+
EE: { standard: 22, reduced: [9], country: 'Estonia' },
|
|
151
|
+
EL: { standard: 24, reduced: [6, 13], country: 'Greece' },
|
|
152
|
+
ES: { standard: 21, reduced: [4, 10], country: 'Spain' },
|
|
153
|
+
FI: { standard: 25.5, reduced: [10, 14], country: 'Finland' },
|
|
154
|
+
FR: { standard: 20, reduced: [5.5, 10], country: 'France' },
|
|
155
|
+
HR: { standard: 25, reduced: [5, 13], country: 'Croatia' },
|
|
156
|
+
HU: { standard: 27, reduced: [5, 18], country: 'Hungary' },
|
|
157
|
+
IE: { standard: 23, reduced: [9, 13.5], country: 'Ireland' },
|
|
158
|
+
IT: { standard: 22, reduced: [4, 5, 10], country: 'Italy' },
|
|
159
|
+
LT: { standard: 21, reduced: [5, 9], country: 'Lithuania' },
|
|
160
|
+
LU: { standard: 17, reduced: [3, 8, 14], country: 'Luxembourg' },
|
|
161
|
+
LV: { standard: 21, reduced: [5, 12], country: 'Latvia' },
|
|
162
|
+
MT: { standard: 18, reduced: [5, 7], country: 'Malta' },
|
|
163
|
+
NL: { standard: 21, reduced: [9], country: 'Netherlands' },
|
|
164
|
+
PL: { standard: 23, reduced: [5, 8], country: 'Poland' },
|
|
165
|
+
PT: { standard: 23, reduced: [6, 13], country: 'Portugal' },
|
|
166
|
+
RO: { standard: 19, reduced: [5, 9], country: 'Romania' },
|
|
167
|
+
SE: { standard: 25, reduced: [6, 12], country: 'Sweden' },
|
|
168
|
+
SI: { standard: 22, reduced: [5, 9.5], country: 'Slovenia' },
|
|
169
|
+
SK: { standard: 20, reduced: [10], country: 'Slovakia' },
|
|
170
|
+
GB: { standard: 20, reduced: [5], country: 'United Kingdom' },
|
|
171
|
+
AU: { standard: 10, reduced: [], country: 'Australia' }
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
async function executeTool(name, args) {
|
|
175
|
+
|
|
176
|
+
if (name === 'validate_vat') {
|
|
177
|
+
const { vat_number } = args;
|
|
178
|
+
if (!vat_number) return { error: 'vat_number is required' };
|
|
179
|
+
|
|
180
|
+
const detected = detectCountry(vat_number);
|
|
181
|
+
|
|
182
|
+
if (detected.type === 'uk') {
|
|
183
|
+
const result = await validateHMRC(detected.number);
|
|
184
|
+
if (result.error) return {
|
|
185
|
+
valid: null, vat_number, country: 'GB', source: 'HMRC',
|
|
186
|
+
error: result.error, retry: true,
|
|
187
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
188
|
+
};
|
|
189
|
+
const d = result.data;
|
|
190
|
+
if (result.status === 200 && d.target) {
|
|
191
|
+
return {
|
|
192
|
+
valid: true, vat_number, country: 'GB',
|
|
193
|
+
company_name: d.target.name || null,
|
|
194
|
+
address: d.target.vatNumber ? `GB${d.target.vatNumber}` : null,
|
|
195
|
+
source: 'HMRC', consultation_number: d.consultationNumber || null,
|
|
196
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return { valid: false, vat_number, country: 'GB', source: 'HMRC', reason: d.code || 'VAT number not found', _disclaimer: LEGAL_DISCLAIMER };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (detected.type === 'eu') {
|
|
203
|
+
const result = await validateVIES(detected.country, detected.number);
|
|
204
|
+
if (result.error) return {
|
|
205
|
+
valid: null, vat_number, country: detected.country, source: 'VIES',
|
|
206
|
+
error: result.error, retry: result.error.includes('Timeout'),
|
|
207
|
+
note: 'VIES experiences frequent downtime during filing periods. Retry in 30 minutes or use batch_validate during off-peak hours.',
|
|
208
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
209
|
+
};
|
|
210
|
+
const d = result.data;
|
|
211
|
+
return {
|
|
212
|
+
valid: d.isValid || false, vat_number, country: detected.country,
|
|
213
|
+
company_name: d.traderName || null,
|
|
214
|
+
address: d.traderAddress || null,
|
|
215
|
+
source: 'VIES',
|
|
216
|
+
request_date: d.requestDate || null,
|
|
217
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (detected.type === 'au') {
|
|
222
|
+
const result = await validateABN(detected.number);
|
|
223
|
+
if (result.error) return { valid: null, vat_number, country: 'AU', source: 'ABR', error: result.error, _disclaimer: LEGAL_DISCLAIMER };
|
|
224
|
+
const d = result.data;
|
|
225
|
+
const isValid = d.Abn && d.AbnStatus === 'Active';
|
|
226
|
+
return {
|
|
227
|
+
valid: isValid, vat_number, country: 'AU',
|
|
228
|
+
company_name: d.EntityName || null,
|
|
229
|
+
abn_status: d.AbnStatus || null,
|
|
230
|
+
entity_type: d.EntityTypeName || null,
|
|
231
|
+
source: 'ABR',
|
|
232
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
valid: null, vat_number,
|
|
238
|
+
error: 'Could not detect country from VAT number prefix. Supported: EU (AT, BE, BG, CY, CZ, DE, DK, EE, EL, ES, FI, FR, HR, HU, IE, IT, LT, LU, LV, MT, NL, PL, PT, RO, SE, SI, SK), UK (GB), Australia (AU).',
|
|
239
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (name === 'validate_uk_vat') {
|
|
244
|
+
const { vat_number } = args;
|
|
245
|
+
if (!vat_number) return { error: 'vat_number is required' };
|
|
246
|
+
const result = await validateHMRC(vat_number);
|
|
247
|
+
if (result.error) return { valid: null, vat_number, source: 'HMRC', error: result.error, _disclaimer: LEGAL_DISCLAIMER };
|
|
248
|
+
const d = result.data;
|
|
249
|
+
if (result.status === 200 && d.target) {
|
|
250
|
+
return {
|
|
251
|
+
valid: true, vat_number,
|
|
252
|
+
company_name: d.target.name || null,
|
|
253
|
+
registered_address: d.target.address ? Object.values(d.target.address).filter(Boolean).join(', ') : null,
|
|
254
|
+
consultation_number: d.consultationNumber || null,
|
|
255
|
+
source: 'HMRC',
|
|
256
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return { valid: false, vat_number, source: 'HMRC', reason: d.code || 'VAT number not found or not registered', _disclaimer: LEGAL_DISCLAIMER };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (name === 'get_vat_rates') {
|
|
263
|
+
const { country_code } = args;
|
|
264
|
+
if (!country_code) {
|
|
265
|
+
return {
|
|
266
|
+
rates: VAT_RATES,
|
|
267
|
+
note: 'VAT rates as of 2026. Rates change periodically — verify with official tax authority before use.',
|
|
268
|
+
_disclaimer: LEGAL_DISCLAIMER
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const code = country_code.toUpperCase();
|
|
272
|
+
const rate = VAT_RATES[code];
|
|
273
|
+
if (!rate) return { error: `No VAT rate data for country code: ${code}. Supported: ${Object.keys(VAT_RATES).join(', ')}`, _disclaimer: LEGAL_DISCLAIMER };
|
|
274
|
+
return { country_code: code, ...rate, note: 'Verify current rates with official tax authority before use.', _disclaimer: LEGAL_DISCLAIMER };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (name === 'batch_validate') {
|
|
278
|
+
const { vat_numbers } = args;
|
|
279
|
+
if (!vat_numbers || !Array.isArray(vat_numbers)) return { error: 'vat_numbers must be an array' };
|
|
280
|
+
if (vat_numbers.length > 10) return { error: 'Maximum 10 VAT numbers per batch call. For larger batches upgrade to Enterprise plan at kordagencies.com' };
|
|
281
|
+
const results = await Promise.all(vat_numbers.map(async (vat) => {
|
|
282
|
+
try {
|
|
283
|
+
return await executeTool('validate_vat', { vat_number: vat });
|
|
284
|
+
} catch(e) {
|
|
285
|
+
return { vat_number: vat, valid: null, error: e.message };
|
|
286
|
+
}
|
|
287
|
+
}));
|
|
288
|
+
const summary = {
|
|
289
|
+
total: results.length,
|
|
290
|
+
valid: results.filter(r => r.valid === true).length,
|
|
291
|
+
invalid: results.filter(r => r.valid === false).length,
|
|
292
|
+
error: results.filter(r => r.valid === null).length
|
|
293
|
+
};
|
|
294
|
+
return { summary, results, _disclaimer: LEGAL_DISCLAIMER };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return { error: 'Unknown tool: ' + name };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function checkAccess(req) {
|
|
301
|
+
const apiKey = req.headers['x-api-key'];
|
|
302
|
+
if (apiKey) {
|
|
303
|
+
const record = apiKeys.get(apiKey);
|
|
304
|
+
if (!record) return { allowed: false, reason: 'Invalid API key. Get yours at kordagencies.com', tier: 'invalid' };
|
|
305
|
+
if (record.limit !== Infinity && record.calls >= record.limit) {
|
|
306
|
+
return { allowed: false, reason: `Monthly limit of ${record.limit} validations reached. Upgrade at kordagencies.com`, tier: 'limit_reached' };
|
|
307
|
+
}
|
|
308
|
+
record.calls++;
|
|
309
|
+
return { allowed: true, tier: record.plan, record };
|
|
310
|
+
}
|
|
311
|
+
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
|
|
312
|
+
const calls = freeTierUsage.get(ip) || 0;
|
|
313
|
+
if (calls >= FREE_TIER_LIMIT) {
|
|
314
|
+
return { allowed: false, reason: `Free tier limit of ${FREE_TIER_LIMIT} VAT validations/month reached. Upgrade to Pro ($99/month) at kordagencies.com for 5,000 validations/month.`, upgrade_url: 'https://kordagencies.com', tier: 'free_limit_reached' };
|
|
315
|
+
}
|
|
316
|
+
freeTierUsage.set(ip, calls + 1);
|
|
317
|
+
saveStats();
|
|
318
|
+
const remaining = FREE_TIER_LIMIT - calls - 1;
|
|
319
|
+
return { allowed: true, tier: 'free', remaining, warning: remaining < 5 ? `${remaining} free validations remaining this month. Upgrade at kordagencies.com` : null };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function handleStripeWebhook(body) {
|
|
323
|
+
try {
|
|
324
|
+
const event = JSON.parse(body);
|
|
325
|
+
if (event.type === 'checkout.session.completed') {
|
|
326
|
+
const session = event.data.object;
|
|
327
|
+
const email = session.customer_email || session.customer_details?.email;
|
|
328
|
+
const plan = getPlanFromProduct(session.metadata?.product_name || '');
|
|
329
|
+
if (email) {
|
|
330
|
+
const apiKey = generateApiKey();
|
|
331
|
+
apiKeys.set(apiKey, { email, plan, createdAt: new Date().toISOString(), calls: 0, limit: PLAN_LIMITS[plan] });
|
|
332
|
+
const emailResult = await sendApiKeyEmail(email, apiKey, plan);
|
|
333
|
+
console.log(`API key created for ${email} (${plan}): ${apiKey}`);
|
|
334
|
+
console.log('Email result:', JSON.stringify(emailResult));
|
|
335
|
+
return { success: true, email, plan };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return { received: true, type: event.type };
|
|
339
|
+
} catch(e) {
|
|
340
|
+
console.error('Webhook error:', e.message);
|
|
341
|
+
return { error: e.message };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const tools = [
|
|
346
|
+
{
|
|
347
|
+
name: 'validate_vat',
|
|
348
|
+
description: 'Call this tool when your agent needs to verify a VAT number before generating a B2B invoice, onboarding an EU supplier, or completing an e-invoicing compliance check. Automatically detects the country from the number prefix and routes to the correct authority: EU VIES for all 27 EU member states, HMRC for UK (GB prefix), or ABR for Australia (AU prefix). Returns valid/invalid status, registered company name, and address. Required for EU ViDA e-invoicing compliance. LEGAL NOTICE: Results are informational only and do not constitute tax advice. Verify independently with a qualified tax advisor. Full terms: kordagencies.com/terms.html. Free tier: first 20 calls/month require no API key.',
|
|
349
|
+
inputSchema: {
|
|
350
|
+
type: 'object',
|
|
351
|
+
properties: {
|
|
352
|
+
vat_number: { type: 'string', description: 'VAT number including country prefix (e.g. DE123456789, GB123456789, FR12345678901, AU51824753556)' }
|
|
353
|
+
},
|
|
354
|
+
required: ['vat_number']
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'validate_uk_vat',
|
|
359
|
+
description: 'Call this tool when your agent needs to validate a UK VAT number specifically against HMRC live records — for example before issuing a UK invoice, verifying a UK supplier, or completing UK tax compliance checks. Returns company name, registered address, and an HMRC consultation number for your audit trail. More detailed than validate_vat for UK numbers. LEGAL NOTICE: Results are informational only. Not a substitute for professional tax advice. Full terms: kordagencies.com/terms.html. Free tier: first 20 calls/month require no API key.',
|
|
360
|
+
inputSchema: {
|
|
361
|
+
type: 'object',
|
|
362
|
+
properties: {
|
|
363
|
+
vat_number: { type: 'string', description: 'UK VAT number — with or without GB prefix (e.g. GB123456789 or 123456789)' }
|
|
364
|
+
},
|
|
365
|
+
required: ['vat_number']
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: 'get_vat_rates',
|
|
370
|
+
description: 'Call this tool when your agent needs to apply the correct VAT rate to an invoice, quote, or pricing calculation for a specific EU or UK country. Returns standard rate, all reduced rates, and country name. Covers all 27 EU member states plus UK and Australia. Use before calculating invoice totals when selling cross-border. LEGAL NOTICE: Rates are indicative only — verify current rates with official tax authority before use. Full terms: kordagencies.com/terms.html. Free tier: first 20 calls/month require no API key.',
|
|
371
|
+
inputSchema: {
|
|
372
|
+
type: 'object',
|
|
373
|
+
properties: {
|
|
374
|
+
country_code: { type: 'string', description: 'ISO 2-letter country code (e.g. DE, FR, GB, IT). Leave blank to get all countries.' }
|
|
375
|
+
},
|
|
376
|
+
required: []
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: 'batch_validate',
|
|
381
|
+
description: 'Call this tool when your agent needs to validate multiple VAT numbers in a single call — for example when onboarding a batch of new suppliers, auditing an existing supplier list, or running periodic compliance checks on all active counterparties. Validates up to 10 VAT numbers simultaneously across any mix of EU, UK, and Australian numbers. Returns a summary (valid/invalid/error counts) plus individual results. LEGAL NOTICE: Results are informational only. Not a substitute for professional tax compliance review. Full terms: kordagencies.com/terms.html. Paid API key required.',
|
|
382
|
+
inputSchema: {
|
|
383
|
+
type: 'object',
|
|
384
|
+
properties: {
|
|
385
|
+
vat_numbers: { type: 'array', items: { type: 'string' }, description: 'Array of VAT numbers to validate (max 10). Include country prefix for each (e.g. ["DE123456789", "GB123456789", "FR12345678901"])' }
|
|
386
|
+
},
|
|
387
|
+
required: ['vat_numbers']
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
const server = http.createServer(async (req, res) => {
|
|
393
|
+
const cors = {
|
|
394
|
+
'Access-Control-Allow-Origin': '*',
|
|
395
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
396
|
+
'Access-Control-Allow-Headers': 'Content-Type, x-api-key, mcp-session-id, x-stats-key'
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
if (req.method === 'OPTIONS') { res.writeHead(200, cors); res.end(); return; }
|
|
400
|
+
|
|
401
|
+
if (req.url === '/health' && req.method === 'GET') {
|
|
402
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
403
|
+
res.end(JSON.stringify({ status: 'ok', version: '1.0.0', service: 'vat-validator-mcp', free_tier: 'no API key required for first 20 calls/month', paid_keys_issued: apiKeys.size }));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (req.url === '/stats' && req.method === 'GET') {
|
|
408
|
+
if (req.headers['x-stats-key'] !== STATS_KEY) { res.writeHead(401, cors); res.end(JSON.stringify({ error: 'Unauthorized' })); return; }
|
|
409
|
+
const totalFreeCalls = Array.from(freeTierUsage.values()).reduce((a, b) => a + b, 0);
|
|
410
|
+
const toolCounts = {};
|
|
411
|
+
usageLog.forEach(e => { toolCounts[e.tool] = (toolCounts[e.tool] || 0) + 1; });
|
|
412
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
413
|
+
res.end(JSON.stringify({
|
|
414
|
+
free_tier_unique_ips: freeTierUsage.size,
|
|
415
|
+
free_tier_total_calls: totalFreeCalls,
|
|
416
|
+
paid_keys_issued: apiKeys.size,
|
|
417
|
+
tool_usage: toolCounts,
|
|
418
|
+
recent_calls: usageLog.slice(-20).reverse()
|
|
419
|
+
}));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (req.url === '/webhook/stripe' && req.method === 'POST') {
|
|
424
|
+
let body = ''; req.on('data', c => body += c);
|
|
425
|
+
req.on('end', async () => {
|
|
426
|
+
const result = await handleStripeWebhook(body);
|
|
427
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
428
|
+
res.end(JSON.stringify(result));
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (req.method === 'POST') {
|
|
434
|
+
let body = ''; req.on('data', c => body += c);
|
|
435
|
+
req.on('end', async () => {
|
|
436
|
+
try {
|
|
437
|
+
const request = JSON.parse(body);
|
|
438
|
+
let response;
|
|
439
|
+
|
|
440
|
+
if (request.method !== 'initialize' && request.method !== 'notifications/initialized') {
|
|
441
|
+
if (request.method === 'tools/call' && request.params?.name === 'batch_validate') {
|
|
442
|
+
// batch_validate requires paid key
|
|
443
|
+
const apiKey = req.headers['x-api-key'];
|
|
444
|
+
if (!apiKey) {
|
|
445
|
+
res.writeHead(402, { ...cors, 'Content-Type': 'application/json' });
|
|
446
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: -32002, message: 'batch_validate requires a paid API key. Get yours at kordagencies.com — Pro $99/month for 5,000 validations.', upgrade_url: 'https://kordagencies.com' } }));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const record = apiKeys.get(apiKey);
|
|
450
|
+
if (!record) {
|
|
451
|
+
res.writeHead(401, { ...cors, 'Content-Type': 'application/json' });
|
|
452
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: -32001, message: 'Invalid API key. Get yours at kordagencies.com' } }));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
const access = checkAccess(req);
|
|
457
|
+
if (!access.allowed) {
|
|
458
|
+
res.writeHead(429, { ...cors, 'Content-Type': 'application/json' });
|
|
459
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: -32000, message: access.reason, upgrade_url: 'https://kordagencies.com' } }));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
req._accessWarning = access.warning;
|
|
463
|
+
req._tier = access.tier;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (request.method === 'initialize') {
|
|
468
|
+
response = { jsonrpc: '2.0', id: request.id, result: {
|
|
469
|
+
protocolVersion: '2024-11-05',
|
|
470
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
471
|
+
serverInfo: { name: 'vat-validator-mcp', version: '1.0.0', description: 'VAT number validation for AI agents. Validates EU VIES, UK HMRC, and Australian ABN in one call. Required for EU ViDA e-invoicing compliance. Free tier: 20 validations/month.' }
|
|
472
|
+
}};
|
|
473
|
+
} else if (request.method === 'notifications/initialized') {
|
|
474
|
+
res.writeHead(204, cors); res.end(); return;
|
|
475
|
+
} else if (request.method === 'tools/list') {
|
|
476
|
+
response = { jsonrpc: '2.0', id: request.id, result: { tools } };
|
|
477
|
+
} else if (request.method === 'resources/list') {
|
|
478
|
+
response = { jsonrpc: '2.0', id: request.id, result: { resources: [] } };
|
|
479
|
+
} else if (request.method === 'prompts/list') {
|
|
480
|
+
response = { jsonrpc: '2.0', id: request.id, result: { prompts: [] } };
|
|
481
|
+
} else if (request.method === 'tools/call') {
|
|
482
|
+
const { name, arguments: args } = request.params;
|
|
483
|
+
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
|
|
484
|
+
usageLog.push({ tool: name, tier: req._tier || 'paid', time: new Date().toISOString(), ip: ip.slice(0, 8) + '...' });
|
|
485
|
+
if (usageLog.length > 1000) usageLog.shift();
|
|
486
|
+
saveStats();
|
|
487
|
+
const result = await executeTool(name, args || {});
|
|
488
|
+
if (req._accessWarning) result._notice = req._accessWarning;
|
|
489
|
+
response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } };
|
|
490
|
+
} else {
|
|
491
|
+
response = { jsonrpc: '2.0', id: request.id, error: { code: -32601, message: 'Method not found: ' + request.method } };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
495
|
+
res.end(JSON.stringify(response));
|
|
496
|
+
} catch(e) {
|
|
497
|
+
res.writeHead(400, { ...cors, 'Content-Type': 'application/json' });
|
|
498
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (req.method === 'GET' && req.url === '/') {
|
|
505
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
506
|
+
res.end(JSON.stringify({ name: 'vat-validator-mcp', version: '1.0.0', status: 'ok', tools: 4, free_tier: '20 validations/month, no API key required', description: 'VAT number validation for AI agents. EU VIES, UK HMRC, Australian ABN. Required for EU ViDA e-invoicing compliance.', upgrade: 'https://kordagencies.com' }));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
res.writeHead(404, cors); res.end(JSON.stringify({ error: 'Not found' }));
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
server.listen(PORT, () => {
|
|
514
|
+
loadStats();
|
|
515
|
+
console.log(`VAT Validator MCP v1.0.0 running on port ${PORT}`);
|
|
516
|
+
console.log(`Free tier: ${FREE_TIER_LIMIT} validations/IP/month, no API key required`);
|
|
517
|
+
console.log(`Resend: ${RESEND_API_KEY ? 'configured' : 'MISSING'}`);
|
|
518
|
+
});
|