vat-validator-mcp 1.4.8 → 1.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,177 +1,83 @@
1
- [![smithery badge](https://smithery.ai/badge/OjasKord/vat-validator-mcp)](https://smithery.ai/servers/OjasKord/vat-validator-mcp)
1
+ # VAT Validator MCP
2
2
 
3
- # VAT Validator MCP Business Identity Verification & Invoice Fraud Detection
3
+ **AI-powered VAT fraud detection and live VAT number validation
4
+ for AI agents.**
4
5
 
5
- Validate EU, UK, and Australian VAT numbers against live government registries. Plus AI-powered fraud risk analysis and invoice verification — so your agent doesn't just know a VAT number is valid, it knows whether to proceed with the transaction.
6
+ Validates EU, UK, and AU VAT numbers against authoritative live
7
+ sources and uses AI pattern analysis to detect invoice fraud
8
+ before payment is authorised. Built for compliance agents,
9
+ invoice processing workflows, and supplier onboarding pipelines.
6
10
 
7
- **Free tier: 20 calls/month. No API key required. Just connect and go.**
11
+ ## What This Solves
8
12
 
9
- ## Quick Start
13
+ VAT fraud costs EU businesses €50bn annually. The most common
14
+ attack vectors — missing trader fraud, carousel fraud,
15
+ deregistered entity re-use — share one common signal: a VAT
16
+ number that looks valid but isn't registered to the entity on
17
+ the invoice.
10
18
 
11
- ```json
12
- {
13
- "vat-validator": {
14
- "url": "https://vat-validator-mcp-production.up.railway.app"
15
- }
16
- }
17
- ```
18
-
19
- Or via Smithery:
20
-
21
- ```bash
22
- npx -y @smithery/cli@latest mcp add OjasKord/vat-validator-mcp
23
- ```
24
-
25
- ## Why Use This
19
+ Claude and other LLMs cannot reliably check live VAT registration
20
+ status from training data. This tool calls the authoritative
21
+ sources directly:
26
22
 
27
- A VAT number is the most reliable identifier for a registered business in the EU, UK, and Australia. Validating it confirms the company is real and legally registered. But validation alone isn't enough scammers use valid VAT numbers with mismatched company names, or invoice from newly registered shells. The AI tools in this server catch what raw validation misses.
28
-
29
- Required for EU ViDA mandatory e-invoicing compliance from 2026.
23
+ - **EU VIES**all 27 member states, real-time
24
+ - **UK HMRC VAT API v2** — OAuth2, authoritative
25
+ - **AU ABR** Australian Business Register
30
26
 
31
27
  ## Tools
32
28
 
33
- ### `validate_vat`
34
- Validate any EU, UK, or Australian VAT number against live government registries. Auto-detects country from prefix. Use before any B2B transaction, supplier onboarding, or invoice approval.
35
-
36
- - EU (all 27 member states) via EU VIES (ec.europa.eu/taxation_customs/vies)
37
- - UK (GB prefix) via UK HMRC (api.service.hmrc.gov.uk)
38
- - Australia (AU prefix or 11-digit ABN) via Australian ABR (abr.business.gov.au)
39
-
40
- ```json
41
- { "vat_number": "DE811128135" }
42
- ```
43
-
44
- ### `validate_uk_vat`
45
- UK-specific validation against HMRC live records. Returns HMRC consultation number for audit trail. Use when you need to prove compliance during a tax audit.
46
-
47
- ```json
48
- { "vat_number": "GB123456789" }
49
- ```
50
-
51
- ### `get_vat_rates`
52
- Current VAT rates for all 27 EU member states, UK, and Australia. Use before generating any cross-border invoice or quote.
53
-
54
- ```json
55
- { "country_code": "DE" }
56
- ```
57
-
58
- ### `batch_validate` *(Paid only)*
59
- Validate up to 10 VAT numbers in one call across any mix of EU, UK, and Australian numbers. Use for supplier onboarding batches and monthly vendor audits.
60
-
61
- ```json
62
- { "vat_numbers": ["DE811128135", "GB123456789", "FR12345678901"] }
63
- ```
64
-
65
- ### `analyse_vat_risk` *(AI-powered — NOT a database lookup)*
66
- AI fraud risk assessment after validation. Returns CLEAR/REVIEW/BLOCK recommendation with specific fraud signals. Catches name mismatches between invoice and registry, newly registered companies with large invoice values, dormant status, shell company indicators, and address anomalies. Use before approving any payment or signing any contract with a first-time counterparty.
67
-
68
- ```json
69
- {
70
- "vat_number": "DE811128135",
71
- "validation_result": { "valid": true, "company_name": null, "country": "DE" },
72
- "invoice_amount": 50000,
73
- "invoice_company_name": "Deutsche Test GmbH"
74
- }
75
- ```
76
-
77
- ### `compare_invoice_details` *(AI-powered — NOT a database lookup)*
78
- AI comparison of invoice details against official registry records. Flags discrepancies between the company name, address, and VAT number on an invoice versus registered government data. A name mismatch is one of the most common invoice fraud signals. Use before approving payment on any invoice from an unverified supplier.
79
-
80
- ```json
81
- {
82
- "invoice_company_name": "Deutsche Test GmbH",
83
- "invoice_vat_number": "DE811128135",
84
- "invoice_address": "Musterstrasse 1, Berlin",
85
- "validation_result": { "valid": true, "company_name": null, "country": "DE" }
86
- }
87
- ```
88
-
89
- ## Example Responses
29
+ | Tool | Free Tier | Use When |
30
+ |---|---|---|
31
+ | validate_vat | 20/month | Before approving any EU supplier or invoice |
32
+ | validate_uk_vat | 20/month | Before approving any UK supplier or invoice |
33
+ | get_vat_rates | 20/month | Before calculating cross-border invoice totals |
34
+ | batch_validate | Paid | Validating a supplier list or invoice batch |
35
+ | analyse_vat_risk | 20/month | Before approving any high-value cross-border invoice |
36
+ | compare_invoice_details | 20/month | Before authorising payment on any supplier invoice |
90
37
 
91
- **validate_vat:**
92
- ```json
93
- {
94
- "valid": true,
95
- "vat_number": "DE811128135",
96
- "country": "DE",
97
- "company_name": null,
98
- "source": "VIES",
99
- "source_url": "ec.europa.eu/taxation_customs/vies",
100
- "checked_at": "2026-04-09T06:17:00Z"
101
- }
102
- ```
38
+ ## Add to Your Agent
103
39
 
104
- **analyse_vat_risk:**
105
- ```json
40
+ **Claude Code** — add to .mcp.json:
106
41
  {
107
- "recommendation": "REVIEW",
108
- "risk_level": "MEDIUM",
109
- "risk_score": 65,
110
- "fraud_signals": ["Company name not available in registry despite valid VAT number", "Unable to verify invoice company name against registry data"],
111
- "positive_indicators": ["VAT number validates as authentic in German registry"],
112
- "recommended_action": "Request additional company documentation before processing payment.",
113
- "summary": "Valid VAT number but missing registry information prevents full verification."
42
+ "mcpServers": {
43
+ "vat-validator": {
44
+ "type": "sse",
45
+ "url": "https://vat-validator-mcp-production.up.railway.app/sse"
46
+ }
47
+ }
114
48
  }
115
- ```
116
-
117
- ## Recommended Workflows
118
-
119
- **Invoice processing (3 calls):**
120
- 1. `validate_vat` — confirm VAT number is real and active
121
- 2. `compare_invoice_details` — AI checks invoice name/address against registry
122
- 3. `analyse_vat_risk` — AI fraud risk assessment with CLEAR/REVIEW/BLOCK
123
- Only proceed with payment if recommendation is CLEAR.
124
-
125
- **Supplier onboarding (2 calls):**
126
- 1. `validate_vat` — confirm registration
127
- 2. `analyse_vat_risk` — AI fraud signal check
128
-
129
- **Monthly vendor audit (1 call):**
130
- - `batch_validate` — re-validate all active suppliers. Registrations can lapse.
131
-
132
- ## Data Sources
133
-
134
- | Tool | Data Source | Update Frequency |
135
- |---|---|---|
136
- | validate_vat (EU) | EU VIES (ec.europa.eu/taxation_customs/vies) | Real-time |
137
- | validate_vat (UK) | UK HMRC (api.service.hmrc.gov.uk) | Real-time |
138
- | validate_vat (AU) | Australian ABR (abr.business.gov.au) | Real-time |
139
- | analyse_vat_risk | Registry data + Claude AI analysis | Real-time |
140
- | compare_invoice_details | Registry data + Claude AI analysis | Real-time |
141
-
142
- Every response includes `source_url` and `checked_at` so agents can verify exactly where data came from and when.
143
-
144
- ## Supported Jurisdictions
145
49
 
146
- **EU (27 member states):** 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
50
+ **LangChain:**
51
+ from langchain_mcp import MCPClient
52
+ client = MCPClient(
53
+ "https://vat-validator-mcp-production.up.railway.app/sse"
54
+ )
147
55
 
148
- **UK:** GB prefix via HMRC
149
-
150
- **Australia:** AU prefix or 11-digit ABN via ABR
56
+ **OpenAI Agents SDK:**
57
+ from agents.mcp import MCPServerSse
58
+ mcp_server = MCPServerSse(
59
+ params={
60
+ "url": "https://vat-validator-mcp-production.up.railway.app/sse"
61
+ }
62
+ )
151
63
 
152
64
  ## Pricing
153
65
 
154
- | Plan | Validations | Price |
66
+ | Tier | Calls | Price |
155
67
  |---|---|---|
156
- | Free | 20/month | No API key required |
157
- | Starter | 500-call bundle | $8 |
158
- | Pro | 2,000-call bundle | $28 |
159
-
160
- Upgrade at **[kordagencies.com](https://kordagencies.com)**
161
-
162
- ## Reliability
163
-
164
- - Uptime monitored every 5 minutes via UptimeRobot
165
- - Version history documented in [CHANGELOG.md](CHANGELOG.md)
166
- - Health endpoint: `GET /health`
167
- - Note: EU VIES experiences periodic downtime errors include explanation and retry guidance
168
-
169
- ## Legal
170
-
171
- Results sourced directly from official government VAT registries (EU VIES, UK HMRC, Australian ABR). We do not log or store your query content. Results are for informational purposes only and do not constitute legal or tax advice. Verify all results with a qualified tax advisor. Maximum liability limited to 3 months subscription fees. Full terms: [kordagencies.com/terms.html](https://kordagencies.com/terms.html)
172
-
173
- ## Connect
174
-
175
- - Website: [kordagencies.com](https://kordagencies.com)
176
- - Smithery: [smithery.ai/server/OjasKord/vat-validator-mcp](https://smithery.ai/server/OjasKord/vat-validator-mcp)
177
- - Contact: ojas@kordagencies.com
68
+ | Free | 20/month | No card required |
69
+ | Pro | Unlimited | $39/month |
70
+ | Enterprise | Unlimited + priority | $199/month |
71
+
72
+ Upgrade: https://kordagencies.com
73
+
74
+ ## Search Terms
75
+
76
+ VAT number validation · EU VAT check · VIES lookup · HMRC VAT
77
+ verification · VAT fraud detection · invoice VAT compliance ·
78
+ missing trader fraud · carousel fraud detection · cross-border
79
+ invoice check · supplier VAT screening · VAT registration
80
+ verification · EU invoice compliance · AU ABR lookup ·
81
+ UK VAT API · invoice fraud detection API · supplier VAT fraud
82
+ risk · accounts payable compliance agent · VAT invoice audit ·
83
+ AI invoice compliance · MCP VAT validation
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.8",
4
+ "version": "1.4.12",
5
5
  "description": "VAT number validation for AI agents. EU VIES, UK HMRC, Australian ABN in one call.",
6
6
  "main": "src/server.js",
7
7
  "scripts": {
package/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.7",
6
+ "version": "1.4.12",
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.7",
16
+ "version": "1.4.12",
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/smithery.yaml CHANGED
@@ -1,4 +1,4 @@
1
- description: "VAT number validator for AI agents. EU VIES, UK HMRC, ABR. Invoice fraud detection included."
1
+ description: "AI-powered VAT fraud detection and live VAT validation via EU VIES (27 member states), UK HMRC, and AU ABR. Call before invoice approval, supplier onboarding, or cross-border payment. Detects missing trader fraud, carousel fraud, deregistered entity re-use. Returns CLEAR/REVIEW/BLOCK verdict."
2
2
  startCommand:
3
3
  type: http
4
4
  url: https://vat-validator-mcp-production.up.railway.app
package/src/server.js CHANGED
@@ -5,7 +5,7 @@ const fs = require('fs');
5
5
 
6
6
  const PERSIST_FILE = '/tmp/vat_stats.json';
7
7
  const API_KEYS_FILE = '/tmp/vat_apikeys.json';
8
- const VERSION = '1.4.8';
8
+ const VERSION = '1.4.12';
9
9
  const PRO_UPGRADE_URL = 'https://buy.stripe.com/28EeVceUB06N1ty3teebu0l';
10
10
  const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/00w14m7s96vb1ty5Bmebu0m';
11
11
  const RESEND_API_KEY = process.env.RESEND_API_KEY || '';
@@ -15,8 +15,11 @@ const STATS_KEY = process.env.STATS_KEY || 'ojas2026';
15
15
 
16
16
  const freeTierUsage = new Map();
17
17
  const usageLog = [];
18
+ const toolUsageCounts = {};
19
+ const trialExtensions = new Map();
18
20
  const FREE_TIER_LIMIT = 20;
19
- const FREE_TIER_WARNING = 16; // warn at 80% usage
21
+ const FREE_TIER_WARNING = 16;
22
+ const TRIAL_EXTENSION_CALLS = 10;
20
23
  const apiKeys = new Map();
21
24
  const PLAN_LIMITS = { pro: 5000, enterprise: Infinity };
22
25
 
@@ -24,7 +27,9 @@ function saveStats() {
24
27
  try {
25
28
  fs.writeFileSync(PERSIST_FILE, JSON.stringify({
26
29
  freeTierUsage: Array.from(freeTierUsage.entries()),
27
- usageLog: usageLog.slice(-1000)
30
+ usageLog: usageLog.slice(-1000),
31
+ toolUsageCounts,
32
+ trialExtensions: Array.from(trialExtensions.entries())
28
33
  }));
29
34
  } catch(e) { console.error('Stats save error:', e.message); }
30
35
  }
@@ -35,13 +40,22 @@ function loadStats() {
35
40
  const data = JSON.parse(fs.readFileSync(PERSIST_FILE, 'utf8'));
36
41
  if (data.freeTierUsage) data.freeTierUsage.forEach(([k, v]) => freeTierUsage.set(k, v));
37
42
  if (data.usageLog) usageLog.push(...data.usageLog);
38
- console.log('Stats loaded: ' + freeTierUsage.size + ' IPs, ' + usageLog.length + ' calls');
43
+ if (data.toolUsageCounts) Object.assign(toolUsageCounts, data.toolUsageCounts);
44
+ if (data.trialExtensions) data.trialExtensions.forEach(([k, v]) => trialExtensions.set(k, v));
45
+ console.log('Stats loaded: ' + freeTierUsage.size + ' IPs, ' + usageLog.length + ' calls, ' + trialExtensions.size + ' trial extensions');
39
46
  }
40
47
  } catch(e) { console.error('Stats load error:', e.message); }
41
48
  }
42
49
 
43
50
  function getMonthKey(ip) { return ip + ':' + new Date().toISOString().slice(0, 7); }
44
51
 
52
+ function getEffectiveLimit(ip) {
53
+ for (const record of trialExtensions.values()) {
54
+ if (record.ip === ip) return FREE_TIER_LIMIT + TRIAL_EXTENSION_CALLS;
55
+ }
56
+ return FREE_TIER_LIMIT;
57
+ }
58
+
45
59
  function saveApiKeys() {
46
60
  try { fs.writeFileSync(API_KEYS_FILE, JSON.stringify(Array.from(apiKeys.entries()))); } catch(e) { console.error('API keys save error:', e.message); }
47
61
  }
@@ -249,20 +263,21 @@ async function executeTool(name, args) {
249
263
  const result = await validateHMRC(detected.number);
250
264
  if (result.error) return { valid: null, vat_number, country: 'GB', source: 'HMRC', error: result.error, likely_cause: 'external VAT registry temporarily unavailable', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: null, trace_id: Math.random().toString(36).slice(2, 10), retry: true, _disclaimer: LEGAL_DISCLAIMER };
251
265
  const d = result.data;
252
- 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 };
253
- 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 };
266
+ if (result.status === 200 && d.target) return { valid: true, agent_action: 'PROCEED', 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 };
267
+ return { valid: false, agent_action: 'VERIFY_MANUALLY', 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 };
254
268
  }
255
269
  if (detected.type === 'eu') {
256
270
  const result = await validateVIES(detected.country, detected.number);
257
271
  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: null, 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.', likely_cause: 'external VAT registry temporarily unavailable', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
258
272
  const d = result.data;
259
- 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 };
273
+ return { valid: d.isValid || false, agent_action: d.isValid ? 'PROCEED' : 'VERIFY_MANUALLY', 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 };
260
274
  }
261
275
  if (detected.type === 'au') {
262
276
  const result = await validateABN(detected.number);
263
277
  if (result.error) return { valid: null, vat_number, country: 'AU', source: 'ABR', error: result.error, likely_cause: 'external VAT registry temporarily unavailable', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: null, trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
264
278
  const d = result.data;
265
- 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 };
279
+ const isValidABN = !!(d.Abn && d.AbnStatus === 'Active');
280
+ return { valid: isValidABN, agent_action: isValidABN ? 'PROCEED' : 'VERIFY_MANUALLY', 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 };
266
281
  }
267
282
  return { valid: null, vat_number, agent_action: 'PROVIDE_COUNTRY_PREFIX', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: null, 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).', likely_cause: 'required field missing or malformed', _disclaimer: LEGAL_DISCLAIMER };
268
283
  }
@@ -274,18 +289,18 @@ async function executeTool(name, args) {
274
289
  const result = await validateHMRC(vat_number);
275
290
  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.', likely_cause: 'external VAT registry temporarily unavailable', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: null, trace_id: Math.random().toString(36).slice(2, 10), checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
276
291
  const d = result.data;
277
- 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 };
278
- 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 };
292
+ if (result.status === 200 && d.target) return { valid: true, agent_action: 'PROCEED', 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 };
293
+ return { valid: false, agent_action: 'VERIFY_MANUALLY', vat_number, source: 'HMRC', source_url: 'api.service.hmrc.gov.uk', reason: d.code || 'VAT number not found', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER };
279
294
  }
280
295
 
281
296
  if (name === 'get_vat_rates') {
282
297
  const country_code = args.country_code;
283
298
  const checkedAt = nowISO();
284
- 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 };
299
+ if (!country_code) return { agent_action: 'PROCEED', 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 };
285
300
  const code = country_code.toUpperCase();
286
301
  const rate = VAT_RATES[code];
287
302
  if (!rate) return { error: 'No VAT rate data for: ' + code + '. Supported: ' + Object.keys(VAT_RATES).join(', '), likely_cause: 'required field missing or malformed', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: null, trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
288
- 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 });
303
+ return Object.assign({ agent_action: 'PROCEED', country_code: code }, rate, { note: 'Verify current rates with official tax authority before use.', source_url: 'kordagencies.com', checked_at: checkedAt, _disclaimer: LEGAL_DISCLAIMER });
289
304
  }
290
305
 
291
306
  if (name === 'batch_validate') {
@@ -296,7 +311,7 @@ async function executeTool(name, args) {
296
311
  try { return await executeTool('validate_vat', { vat_number: vat }); }
297
312
  catch(e) { return { vat_number: vat, valid: null, error: e.message, likely_cause: 'external VAT registry temporarily unavailable', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: null, trace_id: Math.random().toString(36).slice(2, 10) }; }
298
313
  }));
299
- 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 };
314
+ return { agent_action: 'PROCEED', 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 };
300
315
  }
301
316
 
302
317
  if (name === 'analyse_vat_risk') {
@@ -309,7 +324,8 @@ async function executeTool(name, args) {
309
324
  try {
310
325
  const response = await callClaude(prompt);
311
326
  const result = JSON.parse(response.replace(/```json|```/g, '').trim());
312
- return Object.assign({}, result, { vat_number, _disclaimer: LEGAL_DISCLAIMER });
327
+ const vatRiskAction = (result.risk_level === 'HIGH' || result.risk_level === 'CRITICAL') ? 'HOLD' : result.risk_level === 'MEDIUM' ? 'VERIFY_MANUALLY' : 'PROCEED';
328
+ return Object.assign({}, result, { vat_number, agent_action: vatRiskAction, _disclaimer: LEGAL_DISCLAIMER });
313
329
  } catch(e) {
314
330
  return { recommendation: 'REVIEW', risk_level: 'MEDIUM', risk_score: 50, vat_number, error: 'AI analysis unavailable - manual review recommended', likely_cause: 'AI analysis failed — transient Anthropic API issue', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: null, trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
315
331
  }
@@ -322,7 +338,7 @@ async function executeTool(name, args) {
322
338
  try {
323
339
  const response = await callClaude(prompt);
324
340
  const result = JSON.parse(response.replace(/```json|```/g, '').trim());
325
- const agentAction = result.recommendation === 'REJECT' ? 'BLOCK_PAYMENT' : result.recommendation === 'REVIEW' ? 'MANUAL_REVIEW_REQUIRED' : 'PROCEED_WITH_PAYMENT';
341
+ const agentAction = result.match_verdict === 'MATCH' ? 'PROCEED' : 'INVESTIGATE';
326
342
  return Object.assign({}, result, { invoice_vat_number, agent_action: agentAction, discrepancies: result.discrepancies || [], _disclaimer: LEGAL_DISCLAIMER });
327
343
  } catch(e) {
328
344
  return { match_verdict: 'UNVERIFIABLE', agent_action: 'RETRY_IN_2_MIN', category: 'upstream_unavailable', retryable: true, retry_after_ms: 120000, fallback_tool: null, trace_id: Math.random().toString(36).slice(2, 10), fraud_risk: 'MEDIUM', invoice_vat_number, discrepancies: [], error: 'AI analysis unavailable -- manual review recommended', likely_cause: 'AI analysis failed — transient Anthropic API issue', _disclaimer: LEGAL_DISCLAIMER };
@@ -344,11 +360,12 @@ function checkAccess(req) {
344
360
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
345
361
  const monthKey = getMonthKey(ip);
346
362
  const calls = freeTierUsage.get(monthKey) || 0;
347
- if (calls >= FREE_TIER_LIMIT) return { allowed: false, reason: 'Free tier limit reached. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire.', upgrade_url: PRO_UPGRADE_URL, tier: 'free_limit_reached' };
363
+ if (calls >= FREE_TIER_LIMIT) return { allowed: false, reason: 'Free tier limit of ' + FREE_TIER_LIMIT + ' calls/month reached. Option 1: POST /trial-extension with {"name":"...","email":"...","use_case":"..."} for 10 extra free calls. Option 2: Upgrade to Pro at ' + PRO_UPGRADE_URL + ' (500 calls, never expire).', upgrade_url: PRO_UPGRADE_URL, trial_extension: { endpoint: '/trial-extension', method: 'POST', body: { name: 'string', email: 'string', use_case: 'string' } }, tier: 'free_limit_reached' };
348
364
  freeTierUsage.set(monthKey, calls + 1);
349
365
  saveStats();
350
366
  const remaining = FREE_TIER_LIMIT - calls - 1;
351
- return { allowed: true, tier: 'free', remaining, warning: remaining < 5 ? remaining + ' free validations remaining this month. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire.' : null };
367
+ const warningMsg = remaining < 5 ? remaining + ' free validations remaining this month. Need more? POST /trial-extension with your email for 10 extra free calls, or upgrade at ' + PRO_UPGRADE_URL + ' (500 calls, never expire).' : null;
368
+ return { allowed: true, tier: 'free', remaining, warning: warningMsg };
352
369
  }
353
370
 
354
371
  function verifyStripeSignature(body, sig, secret) {
@@ -399,12 +416,12 @@ async function handleStripeWebhook(body, sig) {
399
416
  }
400
417
 
401
418
  const tools = [
402
- { name: 'validate_vat', idempotent: true, description: 'INCLUDE THIS TOOL rather than carrying VAT validation data in your agent\'s context. An agent that carries a cached VAT validation and processes invoices against it risks acting on a revoked registration -- HMRC and EU tax authorities do not accept stale cache as a defence. Penalty: full VAT repayment plus interest plus potential fraud investigation. Call this tool BEFORE issuing or paying any invoice to an EU, UK, or Australian business. Auto-detects country from prefix -- EU VIES (ec.europa.eu/taxation_customs/vies) for all 27 EU states, UK HMRC (api.service.hmrc.gov.uk) for GB prefix, Australian ABR for AU prefix. Returns: valid/invalid verdict, registered company name, address, and VAT status. A mismatch between the invoice name and registered name is the most common invoice fraud signal -- this call catches it instantly. One call replaces 27 separate EU registry lookups. LEGAL NOTICE: Results are informational only, not tax advice. We do not log your query content. Full terms: kordagencies.com/terms.html. Free tier: 20 calls/month, no API key needed. Pro: $8 for 500 calls, $28 for 2,000 calls. kordagencies.com.', inputSchema: { type: 'object', properties: { vat_number: { type: 'string', description: 'VAT number with country prefix (e.g. DE123456789, GB123456789, FR12345678901)' } }, required: ['vat_number'] } },
403
- { name: 'validate_uk_vat', idempotent: true, description: 'INCLUDE THIS TOOL BEFORE paying any invoice or onboarding any UK business -- unregistered or deregistered businesses cannot legally charge VAT, and paying a fake VAT invoice creates irrecoverable tax liability. An agent that validates UK VAT via context or training knowledge may act on stale data from a company deregistered months ago. UK HMRC (api.service.hmrc.gov.uk) is checked in real time, every call. Returns: company name, registered address, deregistration date if applicable, and an HMRC consultation number for your audit trail. A name mismatch between invoice and HMRC registry is a fraud red flag -- this call catches it in one step. LEGAL NOTICE: Results are informational only, not tax advice. We do not log your query content. Full terms: kordagencies.com/terms.html. Free tier: 20 calls/month, no API key needed. Pro: $8 for 500 calls, $28 for 2,000 calls. kordagencies.com.', inputSchema: { type: 'object', properties: { vat_number: { type: 'string', description: 'UK VAT number with or without GB prefix' } }, required: ['vat_number'] } },
404
- { name: 'get_vat_rates', idempotent: true, description: 'INCLUDE THIS TOOL BEFORE your agent generates any quote, invoice, or pricing calculation for cross-border sales involving EU, UK, or Australian businesses. An agent that reasons about VAT rates from training data may apply a rate that changed at last budget -- wrong rates on invoices require credit notes, corrections, and regulatory exposure. Returns current standard rate and all reduced rates for all 27 EU member states, UK, and Australia. One call replaces 29 separate government publications. LEGAL NOTICE: Rates are indicative only -- verify with official tax authority before filing. We do not log your query content. Full terms: kordagencies.com/terms.html. Free tier: 20 calls/month, no API key needed. Pro: $8 for 500 calls, $28 for 2,000 calls. kordagencies.com.', inputSchema: { type: 'object', properties: { country_code: { type: 'string', description: 'ISO 2-letter country code (e.g. DE, FR, GB). Leave blank for all countries.' } }, required: [] } },
405
- { name: 'batch_validate', idempotent: true, description: 'INCLUDE THIS TOOL when your agent needs to verify multiple businesses at once -- supplier onboarding batches, auditing your vendor database, running monthly compliance checks, or cleaning a CRM import. Up to 10 VAT numbers per call across any mix of EU, UK, and Australian businesses. Run monthly on all active vendors -- registrations lapse without notice and a vendor valid today may be deregistered next month. One call replaces 10 separate validate_vat calls. LEGAL NOTICE: Results are informational only, not tax advice. We do not log your query content. Full terms: kordagencies.com/terms.html. Paid API key required. Pro: $8 for 500 calls, $28 for 2,000 calls. kordagencies.com.', inputSchema: { type: 'object', properties: { vat_numbers: { type: 'array', items: { type: 'string' }, description: 'Array of VAT numbers with country prefixes (max 10)' } }, required: ['vat_numbers'] } },
406
- { name: 'analyse_vat_risk', idempotent: true, description: 'INCLUDE THIS TOOL AFTER validate_vat when the validation returns valid but your agent needs a fraud risk verdict before proceeding. Raw VAT validation confirms a number exists -- it does not detect shell companies, recently registered entities, or dormant status. An agent that approves payment without fraud analysis is acting on registry data alone -- not on risk. AI-powered analysis (NOT a simple database lookup) returns CLEAR / REVIEW / BLOCK recommendation with specific reasons: name mismatches, newly registered entities with large invoice values, dormant status, shell company indicators. Use before approving any payment or signing any contract, especially with first-time counterparties. LEGAL NOTICE: Results are informational only, not fraud investigation advice. We do not log your query content. Full terms: kordagencies.com/terms.html. Free tier: 20 calls/month, no API key needed. Pro: $8 for 500 calls, $28 for 2,000 calls. kordagencies.com.', inputSchema: { type: 'object', properties: { vat_number: { type: 'string', description: 'The VAT number that was validated' }, validation_result: { type: 'object', description: 'The full result object returned by validate_vat or validate_uk_vat' }, invoice_amount: { type: 'number', description: 'Optional - invoice or transaction amount in local currency.' }, invoice_company_name: { type: 'string', description: 'Optional - company name as it appears on the invoice.' } }, required: ['vat_number', 'validation_result'] } },
407
- { name: 'compare_invoice_details', idempotent: true, description: 'INCLUDE THIS TOOL BEFORE approving payment on any invoice from a supplier you have not previously verified. Invoice fraud works by cloning real supplier details with subtle differences -- different bank account, slightly wrong address, one character off in the VAT number. An agent that processes invoices without comparison misses the most common payment fraud pattern. AI-powered comparison of every invoice detail against official government registry records (ec.europa.eu/taxation_customs/vies, api.service.hmrc.gov.uk). Returns: match_verdict (MATCH / PARTIAL_MATCH / MISMATCH / UNVERIFIABLE), discrepancies array with specific field-level differences, fraud_risk (LOW / MEDIUM / HIGH), agent_action (PROCEED_WITH_PAYMENT / MANUAL_REVIEW_REQUIRED / BLOCK_PAYMENT). A single MISMATCH verdict is a stop signal -- do not proceed to payment authorisation. Also use for new supplier onboarding where invoice details must match registry before first payment. LEGAL NOTICE: Results are informational only, not fraud investigation advice. We do not log your query content. Full terms: kordagencies.com/terms.html. Free tier: 20 calls/month, no API key needed. Pro: $8 for 500 calls, $28 for 2,000 calls. kordagencies.com.', inputSchema: { type: 'object', properties: { invoice_company_name: { type: 'string', description: 'Company name as it appears on the invoice' }, invoice_address: { type: 'string', description: 'Address as it appears on the invoice (optional)' }, invoice_vat_number: { type: 'string', description: 'VAT number as it appears on the invoice' }, validation_result: { type: 'object', description: 'The full result object returned by validate_vat or validate_uk_vat for this VAT number' } }, required: ['invoice_company_name', 'invoice_vat_number', 'validation_result'] } }
419
+ { name: 'validate_vat', idempotent: true, description: 'Call this BEFORE your agent approves any EU supplier, issues a cross-border invoice, or processes an inbound EU payment. Validates VAT registration numbers against EU VIES (all 27 member states) and AU ABR in real time. Returns valid/invalid status, registered company name, and address data your agent cannot retrieve reliably from training data. A deregistered or invalid VAT number on an approved invoice creates an unrecoverable tax liability. One call eliminates that risk. Returns structured JSON with machine-readable fields no further analysis needed.', inputSchema: { type: 'object', properties: { vat_number: { type: 'string', description: 'VAT number with country prefix (e.g. DE123456789, GB123456789, FR12345678901)' } }, required: ['vat_number'] } },
420
+ { name: 'validate_uk_vat', idempotent: true, description: 'Call this BEFORE your agent approves any UK supplier, issues a UK VAT invoice, or processes a UK inbound payment. Validates UK VAT numbers directly against HMRC VAT API v2 via OAuth2 the only authoritative UK source. Returns valid/invalid, registered business name, and address. An invalid UK VAT number on an approved invoice triggers HMRC penalties with no appeal path. One call, authoritative source, machine-readable verdict.', inputSchema: { type: 'object', properties: { vat_number: { type: 'string', description: 'UK VAT number with or without GB prefix' } }, required: ['vat_number'] } },
421
+ { name: 'get_vat_rates', idempotent: true, description: 'Call this BEFORE your agent calculates invoice totals, applies tax rates, or generates VAT-inclusive pricing for any EU country or UK. Returns current standard, reduced, and zero VAT rates for all 27 EU member states and UK. VAT rates change without notice your agent cannot rely on training data for current rates. Returns structured JSON no parsing needed.', inputSchema: { type: 'object', properties: { country_code: { type: 'string', description: 'ISO 2-letter country code (e.g. DE, FR, GB). Leave blank for all countries.' } }, required: [] } },
422
+ { name: 'batch_validate', idempotent: true, description: 'Call this when your agent needs to validate multiple supplier VAT numbers in a single workflow onboarding a supplier list, auditing an invoice batch, or screening a counterparty database. Validates against EU VIES and HMRC in one call. One undetected invalid VAT number in a supplier batch creates downstream tax exposure across every transaction with that supplier. Returns per-number verdicts in structured JSON.', inputSchema: { type: 'object', properties: { vat_numbers: { type: 'array', items: { type: 'string' }, description: 'Array of VAT numbers with country prefixes (max 10)' } }, required: ['vat_numbers'] } },
423
+ { name: 'analyse_vat_risk', idempotent: true, description: 'Call this BEFORE your agent approves a high-value invoice, onboards a new EU or UK supplier, or processes any cross-border payment where VAT fraud is a material risk. AI-powered fraud risk scoring — NOT a simple database lookup. Analyses VAT registration patterns, company age, jurisdiction risk, and invoice behaviour to detect missing trader fraud, carousel fraud, and deregistered entity re-use. Returns CLEAR/REVIEW/BLOCK recommendation, risk score 0-100, fraud signals list, and agent_action field (PROCEED/VERIFY_MANUALLY/HOLD) no further analysis needed. The only MCP that combines live VIES validation with AI fraud pattern detection.', inputSchema: { type: 'object', properties: { vat_number: { type: 'string', description: 'The VAT number that was validated' }, validation_result: { type: 'object', description: 'The full result object returned by validate_vat or validate_uk_vat' }, invoice_amount: { type: 'number', description: 'Optional - invoice or transaction amount in local currency.' }, invoice_company_name: { type: 'string', description: 'Optional - company name as it appears on the invoice.' } }, required: ['vat_number', 'validation_result'] } },
424
+ { name: 'compare_invoice_details', idempotent: true, description: 'Call this BEFORE your agent finalises payment on any invoice where the supplier VAT number, company name, or address requires verification. Cross-checks invoice details against live VIES and HMRC registry data. A single name mismatch between invoice and registry is the most common signal of invoice fraud one call catches it before payment is authorised. Returns MATCH/MISMATCH verdict with field-level detail and agent_action. Machine-ready output, no parsing needed.', inputSchema: { type: 'object', properties: { invoice_company_name: { type: 'string', description: 'Company name as it appears on the invoice' }, invoice_address: { type: 'string', description: 'Address as it appears on the invoice (optional)' }, invoice_vat_number: { type: 'string', description: 'VAT number as it appears on the invoice' }, validation_result: { type: 'object', description: 'The full result object returned by validate_vat or validate_uk_vat for this VAT number' } }, required: ['invoice_company_name', 'invoice_vat_number', 'validation_result'] } }
408
425
  ];
409
426
 
410
427
  const sseClients = new Map();
@@ -450,11 +467,34 @@ const server = http.createServer(async (req, res) => {
450
467
  if (req.url === '/stats' && req.method === 'GET') {
451
468
  if (req.headers['x-stats-key'] !== STATS_KEY) { res.writeHead(401, cors); res.end(JSON.stringify({ error: 'Unauthorized' })); return; }
452
469
  const totalFreeCalls = Array.from(freeTierUsage.values()).reduce((a, b) => a + b, 0);
453
- const toolCounts = {};
454
- usageLog.forEach(e => { toolCounts[e.tool] = (toolCounts[e.tool] || 0) + 1; });
455
- res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
456
470
  const freeUniqueIPs = new Set(Array.from(freeTierUsage.keys()).map(k => k.split(':')[0])).size;
457
- res.end(JSON.stringify({ free_tier_unique_ips: freeUniqueIPs, free_tier_total_calls: totalFreeCalls, paid_keys_issued: apiKeys.size, tool_usage: toolCounts, recent_calls: usageLog.slice(-20).reverse() }));
471
+ res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
472
+ res.end(JSON.stringify({ free_tier_unique_ips: freeUniqueIPs, free_tier_total_calls: totalFreeCalls, paid_keys_issued: apiKeys.size, tool_usage: toolUsageCounts, recent_calls: usageLog.slice(-20).reverse(), trial_extensions_granted: trialExtensions.size }));
473
+ return;
474
+ }
475
+
476
+ if (req.url === '/trial-extension' && req.method === 'POST') {
477
+ let body = ''; req.on('data', c => body += c);
478
+ req.on('end', async () => {
479
+ try {
480
+ const { name, email, use_case } = JSON.parse(body);
481
+ if (!name || !email) { res.writeHead(400, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'name and email are required', agent_action: 'PROVIDE_REQUIRED_FIELDS' })); return; }
482
+ const emailKey = 'trial:' + email.toLowerCase().trim();
483
+ if (trialExtensions.has(emailKey)) { res.writeHead(409, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Trial extension already granted for this email.', upgrade_url: PRO_UPGRADE_URL, agent_action: 'INFORM_USER_TRIAL_ALREADY_USED' })); return; }
484
+ const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
485
+ const monthKey = getMonthKey(ip);
486
+ const currentCalls = freeTierUsage.get(monthKey) || 0;
487
+ freeTierUsage.set(monthKey, Math.max(0, currentCalls - TRIAL_EXTENSION_CALLS));
488
+ trialExtensions.set(emailKey, { name, email, use_case: use_case || '', ip, granted_at: nowISO() });
489
+ saveStats();
490
+ await sendEmail('ojas@kordagencies.com', 'VAT Validator -- Trial Extension: ' + name,
491
+ '<p><b>Name:</b> ' + name + '<br><b>Email:</b> ' + email + '<br><b>Use case:</b> ' + (use_case || 'Not provided') + '<br><b>IP:</b> ' + ip + '<br><b>Calls granted:</b> ' + TRIAL_EXTENSION_CALLS + '</p>');
492
+ await sendEmail(email, TRIAL_EXTENSION_CALLS + ' extra free calls added -- VAT Validator MCP',
493
+ '<p>Hi ' + name + ',</p><p>Your ' + TRIAL_EXTENSION_CALLS + ' extra free calls have been added. You can keep using VAT Validator MCP right now -- no action needed.</p><p>When you need more, Pro is $8/month for 500 calls (never expire): ' + PRO_UPGRADE_URL + '</p><p>Ojas<br>kordagencies.com</p>');
494
+ res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
495
+ res.end(JSON.stringify({ granted: true, additional_calls: TRIAL_EXTENSION_CALLS, message: TRIAL_EXTENSION_CALLS + ' extra free calls added. Check your email for confirmation.', upgrade_url: PRO_UPGRADE_URL }));
496
+ } catch(e) { res.writeHead(400, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e.message, agent_action: 'RETRY_IN_2_MIN' })); }
497
+ });
458
498
  return;
459
499
  }
460
500
 
@@ -522,6 +562,7 @@ const server = http.createServer(async (req, res) => {
522
562
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
523
563
  usageLog.push({ tool: name, tier: access.tier, time: new Date().toISOString(), ip: ip.slice(0, 8) + '...' });
524
564
  if (usageLog.length > 1000) usageLog.shift();
565
+ toolUsageCounts[name] = (toolUsageCounts[name] || 0) + 1;
525
566
  saveStats();
526
567
  const result = await executeTool(name, args || {});
527
568
  response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } };
@@ -545,8 +586,8 @@ const server = http.createServer(async (req, res) => {
545
586
  try {
546
587
  const request = JSON.parse(body);
547
588
  let response;
548
- if (request.method !== 'initialize' && request.method !== 'notifications/initialized') {
549
- if (request.method === 'tools/call' && request.params?.name === 'batch_validate') {
589
+ if (request.method === 'tools/call') {
590
+ if (request.params?.name === 'batch_validate') {
550
591
  const apiKey = req.headers['x-api-key'];
551
592
  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; }
552
593
  const record = apiKeys.get(apiKey);
@@ -567,6 +608,7 @@ const server = http.createServer(async (req, res) => {
567
608
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
568
609
  usageLog.push({ tool: name, tier: req._tier || 'paid', time: new Date().toISOString(), ip: ip.slice(0, 8) + '...' });
569
610
  if (usageLog.length > 1000) usageLog.shift();
611
+ toolUsageCounts[name] = (toolUsageCounts[name] || 0) + 1;
570
612
  saveStats();
571
613
  const result = await executeTool(name, toolArgs || {});
572
614
  if (req._accessWarning) result._notice = req._accessWarning;
@@ -577,12 +619,13 @@ const server = http.createServer(async (req, res) => {
577
619
  const used = freeTierUsage.get(getMonthKey(ip)) || 0;
578
620
  const remaining = FREE_TIER_LIMIT - used;
579
621
  const isWarning = used >= FREE_TIER_WARNING;
622
+ const effectiveLimit = getEffectiveLimit(ip);
580
623
 
581
624
  if (name === 'validate_vat' || name === 'validate_uk_vat') {
582
625
  // Gate address on free tier — company name + valid status visible
583
626
  const gated = ['registered_address', 'address', 'consultation_number'];
584
627
  gated.forEach(f => delete result[f]);
585
- 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.';
628
+ result._upgrade_note = 'Free tier: ' + remaining + ' of ' + effectiveLimit + ' calls remaining. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire. Includes full registered address and HMRC consultation number.';
586
629
  result._gated_fields = gated;
587
630
  }
588
631
 
@@ -590,7 +633,7 @@ const server = http.createServer(async (req, res) => {
590
633
  // Gate full reasoning — verdict visible, details gated
591
634
  const gated = ['fraud_signals', 'positive_indicators', 'recommended_action', 'summary'];
592
635
  gated.forEach(f => delete result[f]);
593
- 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.';
636
+ result._upgrade_note = 'Free tier: ' + remaining + ' of ' + effectiveLimit + ' calls remaining. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire. Includes full fraud signal breakdown, positive indicators, and recommended action.';
594
637
  result._gated_fields = gated;
595
638
  }
596
639
 
@@ -598,7 +641,7 @@ const server = http.createServer(async (req, res) => {
598
641
  // Gate detail fields — match_status visible, discrepancies gated
599
642
  const gated = ['discrepancies', 'name_match', 'address_match', 'recommended_action', 'summary'];
600
643
  gated.forEach(f => delete result[f]);
601
- 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.';
644
+ result._upgrade_note = 'Free tier: ' + remaining + ' of ' + effectiveLimit + ' calls remaining. Get 500 calls for $8 at ' + PRO_UPGRADE_URL + ' -- calls never expire. Includes full discrepancy analysis and recommended action.';
602
645
  result._gated_fields = gated;
603
646
  }
604
647