vat-validator-mcp 1.4.4 → 1.4.6

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 CHANGED
@@ -1,10 +1,21 @@
1
- PROPRIETARY LICENSE
1
+ MIT License
2
2
 
3
- Copyright (c) 2026 Kordagencies. All rights reserved.
3
+ Copyright (c) 2026 Kord Agencies Pte Ltd
4
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.
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:
9
11
 
10
- For licensing inquiries: ojas@kordagencies.com
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": "vat-validator-mcp",
3
3
  "mcpName": "io.github.OjasKord/vat-validator-mcp",
4
- "version": "1.4.4",
4
+ "version": "1.4.6",
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": {
@@ -29,7 +29,7 @@
29
29
  "compliance"
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/vat-validator-mcp",
4
4
  "title": "VAT Validator MCP",
5
5
  "description": "Validate EU, UK, AU VAT numbers for AI agents. EU ViDA e-invoicing compliance.",
6
- "version": "1.4.4",
6
+ "version": "1.4.5",
7
7
  "websiteUrl": "https://kordagencies.com",
8
8
  "repository": {
9
9
  "url": "https://github.com/OjasKord/vat-validator-mcp",
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "vat-validator-mcp",
16
- "version": "1.4.4",
16
+ "version": "1.4.5",
17
17
  "transport": { "type": "stdio" },
18
18
  "environmentVariables": [
19
19
  { "name": "ANTHROPIC_API_KEY", "description": "Anthropic API key for AI-powered fraud risk analysis", "isRequired": true, "isSecret": true },
package/src/server.js CHANGED
@@ -4,7 +4,9 @@ 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.4';
7
+ const VERSION = '1.4.6';
8
+ const PRO_UPGRADE_URL = 'https://buy.stripe.com/28EeVceUB06N1ty3teebu0l';
9
+ const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/00w14m7s96vb1ty5Bmebu0m';
8
10
  const RESEND_API_KEY = process.env.RESEND_API_KEY || '';
9
11
  const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || '';
10
12
  const PORT = process.env.PORT || 3000;
@@ -224,36 +226,36 @@ async function executeTool(name, args) {
224
226
  if (name === 'validate_vat') {
225
227
  const vat_number = args.vat_number;
226
228
  const checkedAt = nowISO();
227
- if (!vat_number) return { error: 'vat_number is required', agent_action: 'PROVIDE_REQUIRED_FIELD' };
229
+ if (!vat_number) return { error: 'vat_number is required', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'validate_vat', trace_id: Math.random().toString(36).slice(2, 10) };
228
230
  const detected = detectCountry(vat_number);
229
231
  if (detected.type === 'uk') {
230
232
  const result = await validateHMRC(detected.number);
231
- if (result.error) return { valid: null, vat_number, country: 'GB', source: 'HMRC', error: result.error, agent_action: 'RETRY_IN_2_MIN', retry: true, _disclaimer: LEGAL_DISCLAIMER };
233
+ if (result.error) return { valid: null, vat_number, country: 'GB', source: 'HMRC', error: result.error, agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: 'validate_vat', trace_id: Math.random().toString(36).slice(2, 10), retry: true, _disclaimer: LEGAL_DISCLAIMER };
232
234
  const d = result.data;
233
235
  if (result.status === 200 && d.target) return { valid: true, vat_number, country: 'GB', company_name: d.target.name || null, source: 'HMRC', source_url: 'api.service.hmrc.gov.uk', consultation_number: d.consultationNumber || null, checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
234
236
  return { valid: false, vat_number, country: 'GB', source: 'HMRC', source_url: 'api.service.hmrc.gov.uk', reason: d.code || 'VAT number not found', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
235
237
  }
236
238
  if (detected.type === 'eu') {
237
239
  const result = await validateVIES(detected.country, detected.number);
238
- if (result.error) return { valid: null, vat_number, agent_action: 'RETRY_IN_30_MIN', country: detected.country, source: 'VIES', source_url: 'ec.europa.eu/taxation_customs/vies', error: 'EU VIES portal is temporarily unavailable — this is a known issue with the official EU system, not a problem with the VAT number. Retry in 30 minutes.', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
240
+ if (result.error) return { valid: null, vat_number, agent_action: 'RETRY_IN_30_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 1800000, fallback_tool: 'validate_vat', trace_id: Math.random().toString(36).slice(2, 10), country: detected.country, source: 'VIES', source_url: 'ec.europa.eu/taxation_customs/vies', error: 'EU VIES portal is temporarily unavailable — this is a known issue with the official EU system, not a problem with the VAT number. Retry in 30 minutes.', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
239
241
  const d = result.data;
240
242
  return { valid: d.isValid || false, vat_number, country: detected.country, company_name: d.traderName || null, address: d.traderAddress || null, source: 'VIES', source_url: 'ec.europa.eu/taxation_customs/vies', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
241
243
  }
242
244
  if (detected.type === 'au') {
243
245
  const result = await validateABN(detected.number);
244
- if (result.error) return { valid: null, vat_number, country: 'AU', source: 'ABR', error: result.error, agent_action: 'RETRY_IN_2_MIN', _disclaimer: LEGAL_DISCLAIMER };
246
+ if (result.error) return { valid: null, vat_number, country: 'AU', source: 'ABR', error: result.error, agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: 'validate_vat', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
245
247
  const d = result.data;
246
248
  return { valid: !!(d.Abn && d.AbnStatus === 'Active'), vat_number, country: 'AU', company_name: d.EntityName || null, abn_status: d.AbnStatus || null, source: 'ABR', source_url: 'abr.business.gov.au', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
247
249
  }
248
- return { valid: null, vat_number, agent_action: 'PROVIDE_COUNTRY_PREFIX', error: 'Could not detect country. Supported prefixes: 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).', _disclaimer: LEGAL_DISCLAIMER };
250
+ return { valid: null, vat_number, agent_action: 'PROVIDE_COUNTRY_PREFIX', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'validate_vat', trace_id: Math.random().toString(36).slice(2, 10), error: 'Could not detect country. Supported prefixes: 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).', _disclaimer: LEGAL_DISCLAIMER };
249
251
  }
250
252
 
251
253
  if (name === 'validate_uk_vat') {
252
254
  const vat_number = args.vat_number;
253
255
  const checkedAt = nowISO();
254
- if (!vat_number) return { error: 'vat_number is required', agent_action: 'PROVIDE_REQUIRED_FIELD' };
256
+ if (!vat_number) return { error: 'vat_number is required', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'validate_uk_vat', trace_id: Math.random().toString(36).slice(2, 10) };
255
257
  const result = await validateHMRC(vat_number);
256
- if (result.error) return { valid: null, vat_number, source: 'HMRC', source_url: 'api.service.hmrc.gov.uk', error: 'UK HMRC API is temporarily unavailable — this is not a problem with the VAT number. Retry in a few minutes.', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
258
+ if (result.error) return { valid: null, vat_number, source: 'HMRC', source_url: 'api.service.hmrc.gov.uk', error: 'UK HMRC API is temporarily unavailable — this is not a problem with the VAT number. Retry in a few minutes.', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: 'validate_uk_vat', trace_id: Math.random().toString(36).slice(2, 10), checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
257
259
  const d = result.data;
258
260
  if (result.status === 200 && d.target) return { valid: true, vat_number, company_name: d.target.name || null, registered_address: d.target.address ? Object.values(d.target.address).filter(Boolean).join(', ') : null, consultation_number: d.consultationNumber || null, source: 'HMRC', source_url: 'api.service.hmrc.gov.uk', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
259
261
  return { valid: false, vat_number, source: 'HMRC', source_url: 'api.service.hmrc.gov.uk', reason: d.code || 'VAT number not found', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
@@ -265,17 +267,17 @@ async function executeTool(name, args) {
265
267
  if (!country_code) return { rates: VAT_RATES, note: 'VAT rates as of 2026. Verify with official tax authority before use.', source_url: 'kordagencies.com', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
266
268
  const code = country_code.toUpperCase();
267
269
  const rate = VAT_RATES[code];
268
- if (!rate) return { error: 'No VAT rate data for: ' + code + '. Supported: ' + Object.keys(VAT_RATES).join(', '), _disclaimer: LEGAL_DISCLAIMER };
270
+ if (!rate) return { error: 'No VAT rate data for: ' + code + '. Supported: ' + Object.keys(VAT_RATES).join(', '), agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'get_vat_rates', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
269
271
  return Object.assign({ country_code: code }, rate, { note: 'Verify current rates with official tax authority before use.', source_url: 'kordagencies.com', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER });
270
272
  }
271
273
 
272
274
  if (name === 'batch_validate') {
273
275
  const vat_numbers = args.vat_numbers;
274
- if (!vat_numbers || !Array.isArray(vat_numbers)) return { error: 'vat_numbers must be an array', agent_action: 'PROVIDE_REQUIRED_FIELD' };
275
- if (vat_numbers.length > 10) return { error: 'Maximum 10 VAT numbers per batch. Upgrade to Enterprise at kordagencies.com for unlimited batches.', agent_action: 'Reduce batch to 10 or fewer, or upgrade to Enterprise at kordagencies.com' };
276
+ if (!vat_numbers || !Array.isArray(vat_numbers)) return { error: 'vat_numbers must be an array', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'batch_validate', trace_id: Math.random().toString(36).slice(2, 10) };
277
+ if (vat_numbers.length > 10) return { error: 'Maximum 10 VAT numbers per batch. Upgrade to Enterprise at kordagencies.com for unlimited batches.', agent_action: 'Reduce batch to 10 or fewer, or upgrade to Enterprise at kordagencies.com', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'batch_validate', trace_id: Math.random().toString(36).slice(2, 10) };
276
278
  const results = await Promise.all(vat_numbers.map(async (vat) => {
277
279
  try { return await executeTool('validate_vat', { vat_number: vat }); }
278
- catch(e) { return { vat_number: vat, valid: null, error: e.message }; }
280
+ catch(e) { return { vat_number: vat, valid: null, error: e.message, category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: 'validate_vat', trace_id: Math.random().toString(36).slice(2, 10) }; }
279
281
  }));
280
282
  return { summary: { total: results.length, valid: results.filter(r => r.valid === true).length, invalid: results.filter(r => r.valid === false).length, error: results.filter(r => r.valid === null).length }, results, _disclaimer: LEGAL_DISCLAIMER };
281
283
  }
@@ -285,20 +287,20 @@ async function executeTool(name, args) {
285
287
  const validation_result = args.validation_result;
286
288
  const invoice_amount = args.invoice_amount;
287
289
  const invoice_company_name = args.invoice_company_name;
288
- if (!vat_number || !validation_result) return { error: 'vat_number and validation_result are required', agent_action: 'PROVIDE_REQUIRED_FIELD' };
290
+ if (!vat_number || !validation_result) return { error: 'vat_number and validation_result are required', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'analyse_vat_risk', trace_id: Math.random().toString(36).slice(2, 10) };
289
291
  const prompt = 'You are a B2B fraud detection specialist. Analyse this VAT validation result for fraud signals.\n\nVAT Number: ' + vat_number + '\nValidation Result: ' + JSON.stringify(validation_result) + '\nInvoice Amount: ' + (invoice_amount ? String(invoice_amount) : 'Not provided') + '\nInvoice Company Name: ' + (invoice_company_name || 'Not provided') + '\nRegistered Company Name: ' + (validation_result.company_name || 'Not available') + '\nValid: ' + validation_result.valid + '\nCountry: ' + validation_result.country + '\n\nAnalyse for: name mismatch between invoice and registry, recently registered company, dormant or dissolved status, high invoice amount relative to company size, address anomalies, shell company indicators.\n\nReturn ONLY valid JSON with no preamble: {"recommendation":"CLEAR|REVIEW|BLOCK","risk_level":"LOW|MEDIUM|HIGH|CRITICAL","risk_score":50,"fraud_signals":[],"positive_indicators":[],"recommended_action":"one sentence","summary":"two sentences"}';
290
292
  try {
291
293
  const response = await callClaude(prompt);
292
294
  const result = JSON.parse(response.replace(/```json|```/g, '').trim());
293
295
  return Object.assign({}, result, { vat_number, _disclaimer: LEGAL_DISCLAIMER });
294
296
  } catch(e) {
295
- return { recommendation: 'REVIEW', risk_level: 'MEDIUM', risk_score: 50, vat_number, error: 'AI analysis unavailable - manual review recommended', _disclaimer: LEGAL_DISCLAIMER };
297
+ return { recommendation: 'REVIEW', risk_level: 'MEDIUM', risk_score: 50, vat_number, error: 'AI analysis unavailable - manual review recommended', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: 'analyse_vat_risk', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
296
298
  }
297
299
  }
298
300
 
299
301
  if (name === 'compare_invoice_details') {
300
302
  const { invoice_company_name, invoice_address, invoice_vat_number, validation_result } = args;
301
- if (!invoice_company_name || !invoice_vat_number || !validation_result) return { error: 'invoice_company_name, invoice_vat_number, and validation_result are required', agent_action: 'PROVIDE_REQUIRED_FIELD' };
303
+ if (!invoice_company_name || !invoice_vat_number || !validation_result) return { error: 'invoice_company_name, invoice_vat_number, and validation_result are required', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'compare_invoice_details', trace_id: Math.random().toString(36).slice(2, 10) };
302
304
  const prompt = 'You are an invoice fraud detection specialist. Compare invoice details against official registry records.\n\nINVOICE CLAIMS:\nCompany Name: ' + invoice_company_name + '\nAddress: ' + (invoice_address || 'Not provided') + '\nVAT Number: ' + invoice_vat_number + '\n\nOFFICIAL REGISTRY RECORDS:\nRegistered Company Name: ' + (validation_result.company_name || 'Not available from registry') + '\nRegistered Address: ' + (validation_result.address || validation_result.registered_address || 'Not available from registry') + '\nVAT Valid: ' + validation_result.valid + '\nCountry: ' + validation_result.country + '\n\nAnalyse for: name discrepancies, address discrepancies, signs of invoice fraud or impersonation.\n\nReturn ONLY valid JSON with no preamble: {"match_verdict":"MATCH|PARTIAL_MATCH|MISMATCH|UNVERIFIABLE","name_match":"EXACT|SIMILAR|DIFFERENT|UNVERIFIABLE","address_match":"MATCH|DIFFERENT|UNVERIFIABLE","vat_valid":true,"discrepancies":[],"fraud_risk":"LOW|MEDIUM|HIGH","recommendation":"APPROVE|REVIEW|REJECT","recommended_action":"one sentence","summary":"two sentences"}';
303
305
  try {
304
306
  const response = await callClaude(prompt);
@@ -306,11 +308,11 @@ async function executeTool(name, args) {
306
308
  const agentAction = result.recommendation === 'REJECT' ? 'BLOCK_PAYMENT' : result.recommendation === 'REVIEW' ? 'MANUAL_REVIEW_REQUIRED' : 'PROCEED_WITH_PAYMENT';
307
309
  return Object.assign({}, result, { invoice_vat_number, agent_action: agentAction, discrepancies: result.discrepancies || [], _disclaimer: LEGAL_DISCLAIMER });
308
310
  } catch(e) {
309
- return { match_verdict: 'UNVERIFIABLE', agent_action: 'RETRY_IN_2_MIN', fraud_risk: 'MEDIUM', invoice_vat_number, discrepancies: [], error: 'AI analysis unavailable -- manual review recommended', _disclaimer: LEGAL_DISCLAIMER };
311
+ return { match_verdict: 'UNVERIFIABLE', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: 'compare_invoice_details', trace_id: Math.random().toString(36).slice(2, 10), fraud_risk: 'MEDIUM', invoice_vat_number, discrepancies: [], error: 'AI analysis unavailable -- manual review recommended', _disclaimer: LEGAL_DISCLAIMER };
310
312
  }
311
313
  }
312
314
 
313
- return { error: 'Unknown tool: ' + name, agent_action: 'RETRY_IN_2_MIN' };
315
+ 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) };
314
316
  }
315
317
 
316
318
  function checkAccess(req) {
@@ -324,11 +326,11 @@ function checkAccess(req) {
324
326
  }
325
327
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
326
328
  const calls = freeTierUsage.get(ip) || 0;
327
- if (calls >= FREE_TIER_LIMIT) return { allowed: false, reason: 'Free tier limit of ' + FREE_TIER_LIMIT + ' 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' };
329
+ 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' };
328
330
  freeTierUsage.set(ip, calls + 1);
329
331
  saveStats();
330
332
  const remaining = FREE_TIER_LIMIT - calls - 1;
331
- return { allowed: true, tier: 'free', remaining, warning: remaining < 5 ? remaining + ' free validations remaining. Upgrade at kordagencies.com' : null };
333
+ 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 };
332
334
  }
333
335
 
334
336
  function verifyStripeSignature(body, sig, secret) {
@@ -397,6 +399,14 @@ const server = http.createServer(async (req, res) => {
397
399
  return;
398
400
  }
399
401
 
402
+ if (req.url === '/ready' && (req.method === 'GET' || req.method === 'HEAD')) {
403
+ const checks = { anthropic: !!ANTHROPIC_API_KEY, hmrc_client_id: !!(process.env.HMRC_CLIENT_ID), hmrc_client_secret: !!(process.env.HMRC_CLIENT_SECRET) };
404
+ const ready = checks.anthropic && checks.hmrc_client_id && checks.hmrc_client_secret;
405
+ res.writeHead(ready ? 200 : 503, { ...cors, 'Content-Type': 'application/json' });
406
+ res.end(JSON.stringify({ status: ready ? 'ready' : 'not_ready', version: VERSION, checks }));
407
+ return;
408
+ }
409
+
400
410
  if (req.url === '/deps' && req.method === 'GET') {
401
411
  const depCheck = (hostname, path, headers) => new Promise((resolve) => {
402
412
  const r = https.request({ hostname, path, method: 'GET', headers: Object.assign({ 'User-Agent': 'VAT-Validator-MCP-HealthCheck/1.0' }, headers || {}) }, (res2) => {
@@ -443,7 +453,7 @@ const server = http.createServer(async (req, res) => {
443
453
 
444
454
  if (req.url === '/.well-known/mcp/server-card.json' && req.method === 'GET') {
445
455
  res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
446
- res.end(JSON.stringify({ name: 'vat-validator-mcp', title: 'VAT Validator MCP', version: VERSION, description: 'VAT validation + AI fraud detection. EU VIES, UK HMRC, Australian ABN. Free tier: 20 calls/month.', tools: tools.map(t => t.name), transport: ['http', 'stdio'], homepage: 'https://kordagencies.com' }));
456
+ res.end(JSON.stringify({ name: 'vat-validator-mcp', title: 'VAT Validator MCP', version: VERSION, description: 'VAT validation + AI fraud detection. EU VIES, UK HMRC, Australian ABN. Free tier: 20 calls/month.', tools: tools.map(t => t.name), transport: 'streamable-http', homepage: 'https://kordagencies.com', token_footprint_min: 100, token_footprint_max: 600, token_footprint_avg: 200, idempotent_tools: ['validate_vat', 'validate_uk_vat', 'get_vat_rates', 'batch_validate', 'analyse_vat_risk', 'compare_invoice_details'], circuit_breaker: false, health_endpoint: '/health', ready_endpoint: '/ready' }));
447
457
  return;
448
458
  }
449
459
 
@@ -486,7 +496,7 @@ const server = http.createServer(async (req, res) => {
486
496
  } else if (request.method === 'tools/call') {
487
497
  const access = checkAccess(req);
488
498
  if (!access.allowed) {
489
- response = { jsonrpc: '2.0', id: request.id, error: { code: -32000, message: access.reason, upgrade_url: 'https://kordagencies.com', agent_action: 'Inform user free tier quota is exhausted. Upgrade required at kordagencies.com' } };
499
+ response = { jsonrpc: '2.0', id: request.id, error: { code: -32000, message: access.reason, upgrade_url: PRO_UPGRADE_URL, agent_action: 'Inform user free tier quota is exhausted. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire.' } };
490
500
  } else {
491
501
  const { name, arguments: args } = request.params;
492
502
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
@@ -518,12 +528,12 @@ const server = http.createServer(async (req, res) => {
518
528
  if (request.method !== 'initialize' && request.method !== 'notifications/initialized') {
519
529
  if (request.method === 'tools/call' && request.params?.name === 'batch_validate') {
520
530
  const apiKey = req.headers['x-api-key'];
521
- if (!apiKey) { res.writeHead(402, { ...cors, 'Content-Type': 'application/json' }); 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.', upgrade_url: 'https://kordagencies.com', agent_action: 'Paid API key required for batch_validate. Upgrade at kordagencies.com' } })); return; }
531
+ if (!apiKey) { res.writeHead(402, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: -32002, message: 'batch_validate requires a paid API key. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire.', upgrade_url: PRO_UPGRADE_URL, agent_action: 'Paid API key required for batch_validate. Get 500 calls for $8 at ' + PRO_UPGRADE_URL } })); return; }
522
532
  const record = apiKeys.get(apiKey);
523
533
  if (!record) { res.writeHead(401, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: -32001, message: 'Invalid API key. Get yours at kordagencies.com', agent_action: 'Invalid API key. Obtain a valid key at kordagencies.com' } })); return; }
524
534
  } else {
525
535
  const access = checkAccess(req);
526
- if (!access.allowed) { res.writeHead(429, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: -32000, message: access.reason, upgrade_url: 'https://kordagencies.com', agent_action: 'Inform user free tier quota is exhausted. Upgrade required at kordagencies.com' } })); return; }
536
+ if (!access.allowed) { res.writeHead(429, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: -32000, message: access.reason, upgrade_url: PRO_UPGRADE_URL, agent_action: 'Inform user free tier quota is exhausted. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire.' } })); return; }
527
537
  req._accessWarning = access.warning; req._tier = access.tier;
528
538
  }
529
539
  }
@@ -552,7 +562,7 @@ const server = http.createServer(async (req, res) => {
552
562
  // Gate address on free tier — company name + valid status visible
553
563
  const gated = ['registered_address', 'address', 'consultation_number'];
554
564
  gated.forEach(f => delete result[f]);
555
- result._upgrade_note = 'Free tier: ' + remaining + ' of ' + FREE_TIER_LIMIT + ' calls remaining. Upgrade to Pro ($39/month) at kordagencies.com for full registered address and HMRC consultation number.';
565
+ result._upgrade_note = 'Free tier: ' + remaining + ' of ' + FREE_TIER_LIMIT + ' calls remaining. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire. Includes full registered address and HMRC consultation number.';
556
566
  result._gated_fields = gated;
557
567
  }
558
568
 
@@ -560,7 +570,7 @@ const server = http.createServer(async (req, res) => {
560
570
  // Gate full reasoning — verdict visible, details gated
561
571
  const gated = ['fraud_signals', 'positive_indicators', 'recommended_action', 'summary'];
562
572
  gated.forEach(f => delete result[f]);
563
- result._upgrade_note = 'Free tier: ' + remaining + ' of ' + FREE_TIER_LIMIT + ' calls remaining. Upgrade to Pro ($39/month) at kordagencies.com for full fraud signal breakdown, positive indicators, and recommended action.';
573
+ result._upgrade_note = 'Free tier: ' + remaining + ' of ' + FREE_TIER_LIMIT + ' calls remaining. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire. Includes full fraud signal breakdown, positive indicators, and recommended action.';
564
574
  result._gated_fields = gated;
565
575
  }
566
576
 
@@ -568,11 +578,11 @@ const server = http.createServer(async (req, res) => {
568
578
  // Gate detail fields — match_status visible, discrepancies gated
569
579
  const gated = ['discrepancies', 'name_match', 'address_match', 'recommended_action', 'summary'];
570
580
  gated.forEach(f => delete result[f]);
571
- result._upgrade_note = 'Free tier: ' + remaining + ' of ' + FREE_TIER_LIMIT + ' calls remaining. Upgrade to Pro ($39/month) at kordagencies.com for full discrepancy analysis and recommended action.';
581
+ result._upgrade_note = 'Free tier: ' + remaining + ' of ' + FREE_TIER_LIMIT + ' calls remaining. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire. Includes full discrepancy analysis and recommended action.';
572
582
  result._gated_fields = gated;
573
583
  }
574
584
 
575
- if (isWarning) result._notice = 'Warning: only ' + remaining + ' free call' + (remaining === 1 ? '' : 's') + ' left this month. Upgrade to Pro at kordagencies.com to avoid interruption.';
585
+ if (isWarning) result._notice = 'Warning: only ' + remaining + ' free call' + (remaining === 1 ? '' : 's') + ' left this month. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire.';
576
586
  }
577
587
 
578
588
  response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } };
@@ -584,7 +594,7 @@ const server = http.createServer(async (req, res) => {
584
594
  return;
585
595
  }
586
596
 
587
- if (req.method === 'GET' && req.url === '/') { res.writeHead(200, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ name: 'vat-validator-mcp', version: VERSION, status: 'ok', tools: 6, free_tier: '20 calls/month, no API key required', description: 'VAT validation + AI fraud detection. EU VIES, UK HMRC, Australian ABN.', upgrade: 'https://kordagencies.com' })); return; }
597
+ if (req.method === 'GET' && req.url === '/') { res.writeHead(200, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ name: 'vat-validator-mcp', version: VERSION, status: 'ok', tools: 6, free_tier: '20 calls/month, no API key required', description: 'VAT validation + AI fraud detection. EU VIES, UK HMRC, Australian ABN.', upgrade: PRO_UPGRADE_URL })); return; }
588
598
  res.writeHead(404, cors); res.end(JSON.stringify({ error: 'Not found' }));
589
599
  });
590
600