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 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>&nbsp;&nbsp;"vat-validator": {<br>&nbsp;&nbsp;&nbsp;&nbsp;"url": "https://vat-validator-mcp-production.up.railway.app",<br>&nbsp;&nbsp;&nbsp;&nbsp;"headers": { "x-api-key": "${apiKey}" }<br>&nbsp;&nbsp;}<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
+ });