vat-validator-mcp 1.4.11 → 1.4.13

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,138 +1,43 @@
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
- ## Harness Integration
26
-
27
- ### Claude Code / Claude Desktop (.mcp.json)
28
- ```json
29
- {
30
- "mcpServers": {
31
- "vat-validator": {
32
- "type": "http",
33
- "url": "https://vat-validator-mcp-production.up.railway.app"
34
- }
35
- }
36
- }
37
- ```
38
-
39
- ### LangChain (Python)
40
- ```python
41
- from langchain_mcp_adapters.client import MultiServerMCPClient
42
- client = MultiServerMCPClient({
43
- "vat-validator": {
44
- "url": "https://vat-validator-mcp-production.up.railway.app",
45
- "transport": "http"
46
- }
47
- })
48
- tools = await client.get_tools()
49
- ```
50
-
51
- ### OpenAI Agents SDK (Python)
52
- ```python
53
- from agents import Agent, HostedMCPTool
54
- agent = Agent(
55
- name="Assistant",
56
- tools=[HostedMCPTool(tool_config={
57
- "type": "mcp",
58
- "server_label": "vat-validator",
59
- "server_url": "https://vat-validator-mcp-production.up.railway.app",
60
- "require_approval": "never"
61
- })]
62
- )
63
- ```
19
+ Claude and other LLMs cannot reliably check live VAT registration
20
+ status from training data. This tool calls the authoritative
21
+ sources directly:
64
22
 
65
- ### LangGraph
66
- Same as LangChain above langchain-mcp-adapters works with LangGraph natively.
67
-
68
- ## Why Use This
69
-
70
- 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.
71
-
72
- 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
73
26
 
74
27
  ## Tools
75
28
 
76
- ### `validate_vat`
77
- 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.
78
-
79
- - EU (all 27 member states) via EU VIES (ec.europa.eu/taxation_customs/vies)
80
- - UK (GB prefix) via UK HMRC (api.service.hmrc.gov.uk)
81
- - Australia (AU prefix or 11-digit ABN) via Australian ABR (abr.business.gov.au)
82
-
83
- ```json
84
- { "vat_number": "DE811128135" }
85
- ```
86
-
87
- ### `validate_uk_vat`
88
- 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.
89
-
90
- ```json
91
- { "vat_number": "GB123456789" }
92
- ```
93
-
94
- ### `get_vat_rates`
95
- Current VAT rates for all 27 EU member states, UK, and Australia. Use before generating any cross-border invoice or quote.
96
-
97
- ```json
98
- { "country_code": "DE" }
99
- ```
100
-
101
- ### `batch_validate` *(Paid only)*
102
- 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.
103
-
104
- ```json
105
- { "vat_numbers": ["DE811128135", "GB123456789", "FR12345678901"] }
106
- ```
107
-
108
- ### `analyse_vat_risk` *(AI-powered — NOT a database lookup)*
109
- 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.
110
-
111
- ```json
112
- {
113
- "vat_number": "DE811128135",
114
- "validation_result": { "valid": true, "company_name": null, "country": "DE" },
115
- "invoice_amount": 50000,
116
- "invoice_company_name": "Deutsche Test GmbH"
117
- }
118
- ```
119
-
120
- ### `compare_invoice_details` *(AI-powered — NOT a database lookup)*
121
- 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.
122
-
123
- ```json
124
- {
125
- "invoice_company_name": "Deutsche Test GmbH",
126
- "invoice_vat_number": "DE811128135",
127
- "invoice_address": "Musterstrasse 1, Berlin",
128
- "validation_result": { "valid": true, "company_name": null, "country": "DE" }
129
- }
130
- ```
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 |
131
37
 
132
38
  ## Add to Your Agent
133
39
 
134
- ### Claude Code / Claude Desktop (.mcp.json)
135
- ```json
40
+ **Claude Code** add to .mcp.json:
136
41
  {
137
42
  "mcpServers": {
138
43
  "vat-validator": {
@@ -141,114 +46,38 @@ AI comparison of invoice details against official registry records. Flags discre
141
46
  }
142
47
  }
143
48
  }
144
- ```
145
49
 
146
- ### LangChain / LangGraph (Python)
147
- ```python
148
- from langchain_mcp_adapters.client import MultiServerMCPClient
149
- client = MultiServerMCPClient({
150
- "vat-validator": {
151
- "url": "https://vat-validator-mcp-production.up.railway.app/sse",
152
- "transport": "sse"
153
- }
154
- })
155
- tools = await client.get_tools()
156
- ```
50
+ **LangChain:**
51
+ from langchain_mcp import MCPClient
52
+ client = MCPClient(
53
+ "https://vat-validator-mcp-production.up.railway.app/sse"
54
+ )
157
55
 
158
- ### OpenAI Agents SDK (Python)
159
- ```python
56
+ **OpenAI Agents SDK:**
160
57
  from agents.mcp import MCPServerSse
161
58
  mcp_server = MCPServerSse(
162
- params={"url": "https://vat-validator-mcp-production.up.railway.app/sse"}
59
+ params={
60
+ "url": "https://vat-validator-mcp-production.up.railway.app/sse"
61
+ }
163
62
  )
164
- ```
165
-
166
- ## Example Responses
167
-
168
- **validate_vat:**
169
- ```json
170
- {
171
- "valid": true,
172
- "vat_number": "DE811128135",
173
- "country": "DE",
174
- "company_name": null,
175
- "source": "VIES",
176
- "source_url": "ec.europa.eu/taxation_customs/vies",
177
- "checked_at": "2026-04-09T06:17:00Z"
178
- }
179
- ```
180
-
181
- **analyse_vat_risk:**
182
- ```json
183
- {
184
- "recommendation": "REVIEW",
185
- "risk_level": "MEDIUM",
186
- "risk_score": 65,
187
- "fraud_signals": ["Company name not available in registry despite valid VAT number", "Unable to verify invoice company name against registry data"],
188
- "positive_indicators": ["VAT number validates as authentic in German registry"],
189
- "recommended_action": "Request additional company documentation before processing payment.",
190
- "summary": "Valid VAT number but missing registry information prevents full verification."
191
- }
192
- ```
193
-
194
- ## Recommended Workflows
195
-
196
- **Invoice processing (3 calls):**
197
- 1. `validate_vat` — confirm VAT number is real and active
198
- 2. `compare_invoice_details` — AI checks invoice name/address against registry
199
- 3. `analyse_vat_risk` — AI fraud risk assessment with CLEAR/REVIEW/BLOCK
200
- Only proceed with payment if recommendation is CLEAR.
201
-
202
- **Supplier onboarding (2 calls):**
203
- 1. `validate_vat` — confirm registration
204
- 2. `analyse_vat_risk` — AI fraud signal check
205
-
206
- **Monthly vendor audit (1 call):**
207
- - `batch_validate` — re-validate all active suppliers. Registrations can lapse.
208
-
209
- ## Data Sources
210
-
211
- | Tool | Data Source | Update Frequency |
212
- |---|---|---|
213
- | validate_vat (EU) | EU VIES (ec.europa.eu/taxation_customs/vies) | Real-time |
214
- | validate_vat (UK) | UK HMRC (api.service.hmrc.gov.uk) | Real-time |
215
- | validate_vat (AU) | Australian ABR (abr.business.gov.au) | Real-time |
216
- | analyse_vat_risk | Registry data + Claude AI analysis | Real-time |
217
- | compare_invoice_details | Registry data + Claude AI analysis | Real-time |
218
-
219
- Every response includes `source_url` and `checked_at` so agents can verify exactly where data came from and when.
220
-
221
- ## Supported Jurisdictions
222
-
223
- **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
224
-
225
- **UK:** GB prefix via HMRC
226
-
227
- **Australia:** AU prefix or 11-digit ABN via ABR
228
63
 
229
64
  ## Pricing
230
65
 
231
- | Plan | Validations | Price |
66
+ | Tier | Calls | Price |
232
67
  |---|---|---|
233
- | Free | 20/month | No API key required |
234
- | Starter | 500-call bundle | $8 |
235
- | Pro | 2,000-call bundle | $28 |
236
-
237
- Upgrade at **[kordagencies.com](https://kordagencies.com)**
238
-
239
- ## Reliability
240
-
241
- - Uptime monitored every 5 minutes via UptimeRobot
242
- - Version history documented in [CHANGELOG.md](CHANGELOG.md)
243
- - Health endpoint: `GET /health`
244
- - Note: EU VIES experiences periodic downtime errors include explanation and retry guidance
245
-
246
- ## Legal
247
-
248
- 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)
249
-
250
- ## Connect
251
-
252
- - Website: [kordagencies.com](https://kordagencies.com)
253
- - Smithery: [smithery.ai/server/OjasKord/vat-validator-mcp](https://smithery.ai/server/OjasKord/vat-validator-mcp)
254
- - 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/glama.json CHANGED
@@ -1,19 +1,45 @@
1
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",
2
+ "$schema": "https://glama.ai/mcp/servers/schema.json",
3
+ "name": "VAT Validator MCP",
4
+ "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.",
5
+ "license": "MIT",
6
+ "categories": [
7
+ "finance",
8
+ "government-data",
9
+ "legal-and-compliance"
10
+ ],
11
+ "remote": {
12
+ "transport": "sse",
13
+ "url": "https://vat-validator-mcp-production.up.railway.app/sse"
14
+ },
8
15
  "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)" }
16
+ {
17
+ "name": "validate_vat",
18
+ "description": "Validates EU VAT numbers against EU VIES (all 27 member states) and AU ABR in real time. Returns valid/invalid, registered company name, address."
19
+ },
20
+ {
21
+ "name": "validate_uk_vat",
22
+ "description": "Validates UK VAT numbers against HMRC VAT API v2 via OAuth2. Returns valid/invalid, registered business name, address."
23
+ },
24
+ {
25
+ "name": "get_vat_rates",
26
+ "description": "Returns current standard, reduced, and zero VAT rates for all 27 EU member states and UK."
27
+ },
28
+ {
29
+ "name": "batch_validate",
30
+ "description": "Validates multiple VAT numbers against EU VIES and HMRC in one call. Returns per-number verdicts in structured JSON."
31
+ },
32
+ {
33
+ "name": "analyse_vat_risk",
34
+ "description": "AI-powered VAT fraud risk scoring. Detects missing trader fraud, carousel fraud, deregistered entity re-use. Returns CLEAR/REVIEW/BLOCK recommendation, risk score 0-100, fraud signals, agent_action (PROCEED/VERIFY_MANUALLY/HOLD)."
35
+ },
36
+ {
37
+ "name": "compare_invoice_details",
38
+ "description": "Cross-checks invoice VAT details against live VIES and HMRC registry data. Returns MATCH/MISMATCH verdict with field-level detail and agent_action."
39
+ }
13
40
  ],
14
- "pricing": {
15
- "free": "20 validations/month, no API key",
16
- "pro": "$99/month — 5,000 validations/month",
17
- "enterprise": "$299/month — unlimited + batch"
41
+ "links": {
42
+ "homepage": "https://kordagencies.com",
43
+ "npm": "https://www.npmjs.com/package/vat-validator-mcp"
18
44
  }
19
45
  }
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.11",
4
+ "version": "1.4.13",
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": {
@@ -40,5 +40,8 @@
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=18.0.0"
43
+ },
44
+ "dependencies": {
45
+ "stripe": "^22.1.1"
43
46
  }
44
47
  }
package/railway ADDED
File without changes
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.8",
6
+ "version": "1.4.13",
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.8",
16
+ "version": "1.4.13",
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 validation via EU VIES (27 member states), UK HMRC, and AU ABR. Call before invoice approval, supplier onboarding, or cross-border payment. Returns valid/invalid, company name, and CLEAR/REVIEW/BLOCK fraud assessment."
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
@@ -2,26 +2,28 @@ const http = require('http');
2
2
  const https = require('https');
3
3
  const crypto = require('crypto');
4
4
  const fs = require('fs');
5
+ const Stripe = require('stripe');
6
+ const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
5
7
 
6
8
  const PERSIST_FILE = '/tmp/vat_stats.json';
7
- const API_KEYS_FILE = '/tmp/vat_apikeys.json';
8
- const VERSION = '1.4.11';
9
- const PRO_UPGRADE_URL = 'https://buy.stripe.com/28EeVceUB06N1ty3teebu0l';
10
- const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/00w14m7s96vb1ty5Bmebu0m';
9
+ const VERSION = '1.4.13';
11
10
  const RESEND_API_KEY = process.env.RESEND_API_KEY || '';
12
11
  const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || '';
13
12
  const PORT = process.env.PORT || 3000;
14
13
  const STATS_KEY = process.env.STATS_KEY || 'ojas2026';
14
+ const REDIS_PREFIX = 'vat';
15
+ const FREE_TIER_LIMIT = 50;
16
+ const METERED_SUBSCRIBE_URL = 'https://vat-validator-mcp-production.up.railway.app/subscribe';
17
+ const BUNDLE_500_URL = 'https://buy.stripe.com/28EeVceUB06N1ty3teebu0l';
18
+ const BUNDLE_2000_URL = 'https://buy.stripe.com/00w14m7s96vb1ty5Bmebu0m';
15
19
 
16
20
  const freeTierUsage = new Map();
17
21
  const usageLog = [];
18
22
  const toolUsageCounts = {};
19
23
  const trialExtensions = new Map();
20
- const FREE_TIER_LIMIT = 20;
21
- const FREE_TIER_WARNING = 16;
24
+ const FREE_TIER_WARNING = 40;
22
25
  const TRIAL_EXTENSION_CALLS = 10;
23
26
  const apiKeys = new Map();
24
- const PLAN_LIMITS = { pro: 5000, enterprise: Infinity };
25
27
 
26
28
  function saveStats() {
27
29
  try {
@@ -56,27 +58,98 @@ function getEffectiveLimit(ip) {
56
58
  return FREE_TIER_LIMIT;
57
59
  }
58
60
 
59
- function saveApiKeys() {
60
- try { fs.writeFileSync(API_KEYS_FILE, JSON.stringify(Array.from(apiKeys.entries()))); } catch(e) { console.error('API keys save error:', e.message); }
61
+ const UPSTASH_URL = process.env.UPSTASH_REDIS_REST_URL;
62
+ const UPSTASH_TOKEN = process.env.UPSTASH_REDIS_REST_TOKEN;
63
+
64
+ async function redisGet(key) {
65
+ try {
66
+ const res = await fetch(
67
+ `${UPSTASH_URL}/get/${encodeURIComponent(key)}`,
68
+ { headers: { Authorization: `Bearer ${UPSTASH_TOKEN}` } }
69
+ );
70
+ const data = await res.json();
71
+ if (!data.result) return null;
72
+ return JSON.parse(data.result);
73
+ } catch(e) { return null; }
61
74
  }
62
75
 
63
- function loadApiKeys() {
76
+ async function redisSet(key, value) {
64
77
  try {
65
- if (fs.existsSync(API_KEYS_FILE)) {
66
- const entries = JSON.parse(fs.readFileSync(API_KEYS_FILE, 'utf8'));
67
- entries.forEach(([k, v]) => apiKeys.set(k, v));
68
- console.log('API keys loaded: ' + apiKeys.size + ' keys');
78
+ await fetch(
79
+ `${UPSTASH_URL}/set/${encodeURIComponent(key)}`,
80
+ {
81
+ method: 'POST',
82
+ headers: {
83
+ Authorization: `Bearer ${UPSTASH_TOKEN}`,
84
+ 'Content-Type': 'application/json'
85
+ },
86
+ body: JSON.stringify({ value: JSON.stringify(value) })
87
+ }
88
+ );
89
+ } catch(e) {}
90
+ }
91
+
92
+ async function redisKeys(pattern) {
93
+ try {
94
+ const res = await fetch(
95
+ `${UPSTASH_URL}/keys/${encodeURIComponent(pattern)}`,
96
+ { headers: { Authorization: `Bearer ${UPSTASH_TOKEN}` } }
97
+ );
98
+ const data = await res.json();
99
+ return data.result || [];
100
+ } catch(e) { return []; }
101
+ }
102
+
103
+ async function saveKeyToRedis(apiKey, record, prefix) {
104
+ await redisSet(`${prefix}:key:${apiKey}`, record);
105
+ }
106
+
107
+ async function loadApiKeysFromRedis(prefix) {
108
+ const keys = await redisKeys(`${prefix}:key:*`);
109
+ for (const redisKey of keys) {
110
+ const record = await redisGet(redisKey);
111
+ if (record) {
112
+ const apiKey = redisKey.replace(`${prefix}:key:`, '');
113
+ apiKeys.set(apiKey, record);
69
114
  }
70
- } catch(e) { console.error('API keys load error:', e.message); }
115
+ }
116
+ console.log(`Loaded ${apiKeys.size} API keys from Redis`);
71
117
  }
72
118
 
73
119
  function generateApiKey() { return 'vat_' + crypto.randomBytes(24).toString('hex'); }
74
- function getPlanFromProduct(name) {
75
- if (!name) return 'pro';
76
- return name.toLowerCase().includes('enterprise') ? 'enterprise' : 'pro';
120
+ function getPlanFromProduct(productName) {
121
+ if (!productName) return 'bundle_500';
122
+ const n = productName.toLowerCase();
123
+ if (n.includes('metered') || n.includes('pay as you go') || n === 'metered') return 'metered';
124
+ if (n.includes('2000') || n.includes('2,000') || n.includes('enterprise')) return 'bundle_2000';
125
+ return 'bundle_500';
77
126
  }
78
127
  function nowISO() { return new Date().toISOString(); }
79
128
 
129
+ function checkAndResetPeriod(record) {
130
+ const thirtyDays = 30 * 24 * 60 * 60 * 1000;
131
+ if (Date.now() - record.periodStart > thirtyDays) {
132
+ record.calls = 0;
133
+ record.periodStart = Date.now();
134
+ return true;
135
+ }
136
+ return false;
137
+ }
138
+
139
+ async function reportMeteredUsage(customerId, eventName) {
140
+ try {
141
+ await stripe.billing.meterEvents.create({
142
+ event_name: eventName,
143
+ payload: {
144
+ stripe_customer_id: customerId,
145
+ value: '1'
146
+ }
147
+ });
148
+ } catch(e) {
149
+ console.error('Stripe metered usage report failed:', e.message);
150
+ }
151
+ }
152
+
80
153
  async function sendEmail(to, subject, html) {
81
154
  return new Promise((resolve) => {
82
155
  const body = JSON.stringify({ from: 'VAT Validator MCP <ojas@kordagencies.com>', to: [to], subject, html });
@@ -90,10 +163,10 @@ async function sendEmail(to, subject, html) {
90
163
  }
91
164
 
92
165
  async function sendApiKeyEmail(email, apiKey, plan) {
93
- const planLabel = plan === 'enterprise' ? 'Enterprise' : 'Pro';
94
- const limit = plan === 'enterprise' ? 'Unlimited' : '5,000';
95
- 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><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;text-transform:uppercase;margin-bottom:8px">Your API Key</div><div style="color:#00E5C3;font-size:14px;word-break:break-all">' + 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;text-transform:uppercase;margin-bottom:8px">MCP Config</div><div style="color:#86EFAC;font-size:12px">{"vat-validator":{"url":"https://vat-validator-mcp-production.up.railway.app","headers":{"x-api-key":"' + 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:#E8EDF5;font-size:13px">Plan: ' + planLabel + ' | Validations: ' + limit + '/month</div></div><div style="background:#0D1219;border-radius:6px;padding:16px;margin-bottom:24px;font-size:11px;color:#5A6478;line-height:1.7">Results are informational only. Verify with a qualified tax advisor. Liability capped at 3 months fees. Full terms: kordagencies.com/terms.html</div><p style="color:#5A6478;font-size:12px">Questions? ojas@kordagencies.com</p></div></body></html>';
96
- return sendEmail(email, 'Your VAT Validator MCP ' + planLabel + ' API Key', html);
166
+ const planLabel = plan === 'metered' ? 'Pay-as-you-go' : plan === 'bundle_2000' ? 'Bundle 2000' : 'Bundle 500';
167
+ const limitNote = plan === 'metered' ? 'Pay only for what you use — billed monthly' : plan === 'bundle_2000' ? '2,000 calls included' : '500 calls included';
168
+ 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 + '</div><h1 style="font-size:24px;font-weight:700;margin-bottom:8px;color:#FFFFFF">Your API key is ready.</h1><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;text-transform:uppercase;margin-bottom:8px">Your API Key</div><div style="color:#00E5C3;font-size:14px;word-break:break-all">' + 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;text-transform:uppercase;margin-bottom:8px">MCP Config</div><div style="color:#86EFAC;font-size:12px">{"vat-validator":{"url":"https://vat-validator-mcp-production.up.railway.app","headers":{"x-api-key":"' + 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:#E8EDF5;font-size:13px">Plan: ' + planLabel + '<br>' + limitNote + '</div></div><div style="background:#0D1219;border-radius:6px;padding:16px;margin-bottom:24px;font-size:11px;color:#5A6478;line-height:1.7">Results are informational only. Verify with a qualified tax advisor. Liability capped at 3 months fees. Full terms: kordagencies.com/terms.html</div><p style="color:#5A6478;font-size:12px">Questions? ojas@kordagencies.com</p></div></body></html>';
169
+ return sendEmail(email, 'Your VAT Validator MCP API Key ' + planLabel, html);
97
170
  }
98
171
 
99
172
  async function callClaude(prompt) {
@@ -352,19 +425,69 @@ function checkAccess(req) {
352
425
  const apiKey = req.headers['x-api-key'];
353
426
  if (apiKey) {
354
427
  const record = apiKeys.get(apiKey);
355
- if (!record) return { allowed: false, reason: 'Invalid API key. Get yours at kordagencies.com', tier: 'invalid' };
356
- if (record.limit !== Infinity && record.calls >= record.limit) return { allowed: false, reason: 'Monthly limit of ' + record.limit + ' validations reached. Upgrade at kordagencies.com', tier: 'limit_reached' };
428
+ if (!record) {
429
+ return { allowed: false, error: 'Invalid API key' };
430
+ }
431
+
432
+ const wasReset = checkAndResetPeriod(record);
433
+ if (wasReset) {
434
+ saveKeyToRedis(apiKey, record, REDIS_PREFIX).catch(() => {});
435
+ }
436
+
437
+ if (record.plan === 'metered') {
438
+ record.calls++;
439
+ saveKeyToRedis(apiKey, record, REDIS_PREFIX).catch(() => {});
440
+ return {
441
+ allowed: true,
442
+ paid: true,
443
+ plan: 'metered',
444
+ stripeCustomerId: record.stripeCustomerId
445
+ };
446
+ }
447
+
448
+ if (record.calls >= record.limit) {
449
+ return {
450
+ allowed: false,
451
+ error: `Bundle exhausted. You have used all ${record.limit} calls in this bundle. Purchase another bundle or switch to pay-as-you-go.`,
452
+ subscribe_url: METERED_SUBSCRIBE_URL,
453
+ bundle_500_url: BUNDLE_500_URL,
454
+ bundle_2000_url: BUNDLE_2000_URL,
455
+ agent_action: 'PAUSE_AND_NOTIFY_USER'
456
+ };
457
+ }
458
+
357
459
  record.calls++;
358
- return { allowed: true, tier: record.plan, record };
460
+ saveKeyToRedis(apiKey, record, REDIS_PREFIX).catch(() => {});
461
+ return { allowed: true, paid: true, plan: record.plan };
359
462
  }
360
463
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
361
464
  const monthKey = getMonthKey(ip);
362
465
  const calls = freeTierUsage.get(monthKey) || 0;
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' };
466
+ if (calls >= FREE_TIER_LIMIT) return {
467
+ allowed: false,
468
+ error: 'Free tier limit of 50 calls/month reached.',
469
+ options: {
470
+ pay_as_you_go: {
471
+ description: 'No commitment. Pay only for what you use. Billed monthly at end of period.',
472
+ pricing: {
473
+ vat_query: '$0.010 per query'
474
+ },
475
+ subscribe_url: METERED_SUBSCRIBE_URL
476
+ },
477
+ bundle: {
478
+ description: 'Buy a fixed call bundle. No subscription.',
479
+ options: [
480
+ { calls: 500, price: '$8', url: BUNDLE_500_URL },
481
+ { calls: 2000, price: '$28', url: BUNDLE_2000_URL }
482
+ ]
483
+ }
484
+ },
485
+ agent_action: 'PAUSE_AND_NOTIFY_USER'
486
+ };
364
487
  freeTierUsage.set(monthKey, calls + 1);
365
488
  saveStats();
366
489
  const remaining = FREE_TIER_LIMIT - calls - 1;
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;
490
+ const warningMsg = remaining < 10 ? remaining + ' free validations remaining this month. Get 500 calls for $8 at ' + BUNDLE_500_URL + ' -- calls never expire.' : null;
368
491
  return { allowed: true, tier: 'free', remaining, warning: warningMsg };
369
492
  }
370
493
 
@@ -400,28 +523,47 @@ async function handleStripeWebhook(body, sig) {
400
523
  const event = JSON.parse(body);
401
524
  if (event.type === 'checkout.session.completed') {
402
525
  const session = event.data.object;
403
- const email = session.customer_email || session.customer_details?.email;
404
- const plan = getPlanFromProduct(session.metadata?.product_name || '');
405
- if (email) {
406
- const apiKey = generateApiKey();
407
- apiKeys.set(apiKey, { email, plan, createdAt: new Date().toISOString(), calls: 0, limit: PLAN_LIMITS[plan] });
408
- saveApiKeys();
409
- await sendApiKeyEmail(email, apiKey, plan);
410
- console.log('[vat] API key created for ' + email + ' (' + plan + ')');
411
- return { success: true, email, plan };
526
+ const plan = getPlanFromProduct(session.metadata?.product_name);
527
+ const apiKey = generateApiKey();
528
+ const limit = plan === 'metered' ? null : plan === 'bundle_2000' ? 2000 : 500;
529
+ const record = {
530
+ email: session.customer_details?.email || 'unknown',
531
+ plan,
532
+ calls: 0,
533
+ periodStart: Date.now(),
534
+ limit,
535
+ stripeCustomerId: session.customer || null,
536
+ createdAt: Date.now()
537
+ };
538
+ apiKeys.set(apiKey, record);
539
+ await saveKeyToRedis(apiKey, record, REDIS_PREFIX);
540
+ await sendApiKeyEmail(record.email, apiKey, plan);
541
+ console.log('[vat] API key created for ' + record.email + ' (' + plan + ')');
542
+ return { success: true, email: record.email, plan };
543
+ }
544
+ if (event.type === 'customer.subscription.created') {
545
+ const sub = event.data.object;
546
+ const customerId = sub.customer;
547
+ for (const [key, record] of apiKeys.entries()) {
548
+ if (record.stripeCustomerId === customerId && !record.subscriptionId) {
549
+ record.subscriptionId = sub.id;
550
+ await saveKeyToRedis(key, record, REDIS_PREFIX);
551
+ break;
552
+ }
412
553
  }
554
+ return { received: true, type: event.type };
413
555
  }
414
556
  return { received: true, type: event.type };
415
557
  } catch(e) { console.error('[vat] Webhook error:', e.message); return { error: e.message, status: 400 }; }
416
558
  }
417
559
 
418
560
  const tools = [
419
- { 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'] } },
420
- { 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'] } },
421
- { 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: [] } },
422
- { 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'] } },
423
- { 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'] } },
424
- { 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'] } }
561
+ { 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'] } },
562
+ { 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'] } },
563
+ { 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: [] } },
564
+ { 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'] } },
565
+ { 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'] } },
566
+ { 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'] } }
425
567
  ];
426
568
 
427
569
  const sseClients = new Map();
@@ -431,7 +573,7 @@ const server = http.createServer(async (req, res) => {
431
573
 
432
574
  if (req.url === '/health' && (req.method === 'GET' || req.method === 'HEAD')) {
433
575
  res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
434
- res.end(JSON.stringify({ status: 'ok', version: VERSION, service: 'vat-validator-mcp', free_tier: 'no API key required for first 20 calls/month', paid_keys_issued: apiKeys.size }));
576
+ res.end(JSON.stringify({ status: 'ok', version: VERSION, service: 'vat-validator-mcp', free_tier: 'no API key required for first ' + FREE_TIER_LIMIT + ' calls/month', paid_keys_issued: apiKeys.size }));
435
577
  return;
436
578
  }
437
579
 
@@ -480,7 +622,7 @@ const server = http.createServer(async (req, res) => {
480
622
  const { name, email, use_case } = JSON.parse(body);
481
623
  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
624
  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; }
625
+ if (trialExtensions.has(emailKey)) { res.writeHead(409, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Trial extension already granted for this email.', bundle_url: BUNDLE_500_URL, agent_action: 'INFORM_USER_TRIAL_ALREADY_USED' })); return; }
484
626
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
485
627
  const monthKey = getMonthKey(ip);
486
628
  const currentCalls = freeTierUsage.get(monthKey) || 0;
@@ -490,9 +632,9 @@ const server = http.createServer(async (req, res) => {
490
632
  await sendEmail('ojas@kordagencies.com', 'VAT Validator -- Trial Extension: ' + name,
491
633
  '<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
634
  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>');
635
+ '<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, get 500 calls for $8: ' + BUNDLE_500_URL + '</p><p>Ojas<br>kordagencies.com</p>');
494
636
  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 }));
637
+ res.end(JSON.stringify({ granted: true, additional_calls: TRIAL_EXTENSION_CALLS, message: TRIAL_EXTENSION_CALLS + ' extra free calls added. Check your email for confirmation.', bundle_url: BUNDLE_500_URL }));
496
638
  } catch(e) { res.writeHead(400, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e.message, agent_action: 'RETRY_IN_2_MIN' })); }
497
639
  });
498
640
  return;
@@ -556,15 +698,18 @@ const server = http.createServer(async (req, res) => {
556
698
  } else if (request.method === 'tools/call') {
557
699
  const access = checkAccess(req);
558
700
  if (!access.allowed) {
559
- 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.' } };
701
+ response = { jsonrpc: '2.0', id: request.id, error: { code: -32000, message: access.error || 'Access denied', data: access, agent_action: 'PAUSE_AND_NOTIFY_USER' } };
560
702
  } else {
561
703
  const { name, arguments: args } = request.params;
562
704
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
563
- usageLog.push({ tool: name, tier: access.tier, time: new Date().toISOString(), ip: ip.slice(0, 8) + '...' });
705
+ usageLog.push({ tool: name, tier: access.tier || access.plan || 'paid', time: new Date().toISOString(), ip: ip.slice(0, 8) + '...' });
564
706
  if (usageLog.length > 1000) usageLog.shift();
565
707
  toolUsageCounts[name] = (toolUsageCounts[name] || 0) + 1;
566
708
  saveStats();
567
709
  const result = await executeTool(name, args || {});
710
+ if (access.plan === 'metered' && access.stripeCustomerId) {
711
+ reportMeteredUsage(access.stripeCustomerId, 'vat_query').catch(() => {});
712
+ }
568
713
  response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } };
569
714
  }
570
715
  } else {
@@ -587,16 +732,15 @@ const server = http.createServer(async (req, res) => {
587
732
  const request = JSON.parse(body);
588
733
  let response;
589
734
  if (request.method === 'tools/call') {
590
- if (request.params?.name === 'batch_validate') {
591
- const apiKey = req.headers['x-api-key'];
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; }
593
- const record = apiKeys.get(apiKey);
594
- 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; }
595
- } else {
596
- const access = checkAccess(req);
597
- 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; }
598
- req._accessWarning = access.warning; req._tier = access.tier;
735
+ const access = checkAccess(req);
736
+ if (!access.allowed) {
737
+ res.writeHead(429, { ...cors, 'Content-Type': 'application/json' });
738
+ res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: -32000, message: access.error || 'Access denied', data: access, agent_action: 'PAUSE_AND_NOTIFY_USER' } }));
739
+ return;
599
740
  }
741
+ req._accessWarning = access.warning;
742
+ req._tier = access.tier;
743
+ req._accessResult = access;
600
744
  }
601
745
  if (request.method === 'initialize') { response = { jsonrpc: '2.0', id: request.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {}, resources: {}, prompts: {} }, serverInfo: { name: 'vat-validator-mcp', version: VERSION, description: 'Every accounts-payable pipeline reaches a moment where an agent must validate a VAT registration or approve an invoice without being able to reason its way to a reliable answer. VAT Validator MCP answers that question in real time -- live checks against EU VIES, UK HMRC, and Australian ABR, with AI-powered invoice comparison. An agent acting on stale VAT data has no defence against a tax authority. Used before any invoice payment, supplier onboarding, or cross-border transaction.' } } };
602
746
  } else if (request.method === 'notifications/initialized') { res.writeHead(204, cors); res.end(); return;
@@ -606,46 +750,46 @@ const server = http.createServer(async (req, res) => {
606
750
  } else if (request.method === 'tools/call') {
607
751
  const { name, arguments: toolArgs } = request.params;
608
752
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
609
- usageLog.push({ tool: name, tier: req._tier || 'paid', time: new Date().toISOString(), ip: ip.slice(0, 8) + '...' });
753
+ usageLog.push({ tool: name, tier: req._tier || req._accessResult?.plan || 'paid', time: new Date().toISOString(), ip: ip.slice(0, 8) + '...' });
610
754
  if (usageLog.length > 1000) usageLog.shift();
611
755
  toolUsageCounts[name] = (toolUsageCounts[name] || 0) + 1;
612
756
  saveStats();
613
757
  const result = await executeTool(name, toolArgs || {});
614
758
  if (req._accessWarning) result._notice = req._accessWarning;
615
759
 
760
+ if (req._accessResult && req._accessResult.plan === 'metered' && req._accessResult.stripeCustomerId) {
761
+ reportMeteredUsage(req._accessResult.stripeCustomerId, 'vat_query').catch(() => {});
762
+ }
763
+
616
764
  // Partial response for free tier
617
765
  if (req._tier === 'free' && !result.error) {
618
- const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
619
766
  const used = freeTierUsage.get(getMonthKey(ip)) || 0;
620
767
  const remaining = FREE_TIER_LIMIT - used;
621
768
  const isWarning = used >= FREE_TIER_WARNING;
622
769
  const effectiveLimit = getEffectiveLimit(ip);
623
770
 
624
771
  if (name === 'validate_vat' || name === 'validate_uk_vat') {
625
- // Gate address on free tier — company name + valid status visible
626
772
  const gated = ['registered_address', 'address', 'consultation_number'];
627
773
  gated.forEach(f => delete result[f]);
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.';
774
+ result._upgrade_note = 'Free tier: ' + remaining + ' of ' + effectiveLimit + ' calls remaining. Get 500 calls for $8 at ' + BUNDLE_500_URL + ' -- calls never expire. Includes full registered address and HMRC consultation number.';
629
775
  result._gated_fields = gated;
630
776
  }
631
777
 
632
778
  if (name === 'analyse_vat_risk') {
633
- // Gate full reasoning — verdict visible, details gated
634
779
  const gated = ['fraud_signals', 'positive_indicators', 'recommended_action', 'summary'];
635
780
  gated.forEach(f => delete result[f]);
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.';
781
+ result._upgrade_note = 'Free tier: ' + remaining + ' of ' + effectiveLimit + ' calls remaining. Get 500 calls for $8 at ' + BUNDLE_500_URL + ' -- calls never expire. Includes full fraud signal breakdown, positive indicators, and recommended action.';
637
782
  result._gated_fields = gated;
638
783
  }
639
784
 
640
785
  if (name === 'compare_invoice_details') {
641
- // Gate detail fields — match_status visible, discrepancies gated
642
786
  const gated = ['discrepancies', 'name_match', 'address_match', 'recommended_action', 'summary'];
643
787
  gated.forEach(f => delete result[f]);
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.';
788
+ result._upgrade_note = 'Free tier: ' + remaining + ' of ' + effectiveLimit + ' calls remaining. Get 500 calls for $8 at ' + BUNDLE_500_URL + ' -- calls never expire. Includes full discrepancy analysis and recommended action.';
645
789
  result._gated_fields = gated;
646
790
  }
647
791
 
648
- 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.';
792
+ if (isWarning) result._notice = 'Warning: only ' + remaining + ' free call' + (remaining === 1 ? '' : 's') + ' left this month. Get 500 calls for $8 at ' + BUNDLE_500_URL + ' -- calls never expire.';
649
793
  }
650
794
 
651
795
  response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } };
@@ -657,7 +801,57 @@ const server = http.createServer(async (req, res) => {
657
801
  return;
658
802
  }
659
803
 
660
- 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; }
804
+ 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: '50 calls/month, no API key required', description: 'VAT validation + AI fraud detection. EU VIES, UK HMRC, Australian ABN.', subscribe_url: METERED_SUBSCRIBE_URL, bundle_500_url: BUNDLE_500_URL, bundle_2000_url: BUNDLE_2000_URL })); return; }
805
+
806
+ if (req.url === '/subscribe' && req.method === 'GET') {
807
+ try {
808
+ const session = await stripe.checkout.sessions.create({
809
+ mode: 'subscription',
810
+ line_items: [
811
+ { price: 'price_1TUkxWD6WvRe6sn3eFTaokqx' }
812
+ ],
813
+ success_url: 'https://vat-validator-mcp-production.up.railway.app/subscribed',
814
+ cancel_url: 'https://kordagencies.com/vat-validator.html',
815
+ metadata: { product_name: 'metered' }
816
+ });
817
+ res.writeHead(302, { Location: session.url });
818
+ res.end();
819
+ } catch(e) {
820
+ res.writeHead(500, { ...cors, 'Content-Type': 'application/json' });
821
+ res.end(JSON.stringify({ error: 'Could not create checkout session', details: e.message }));
822
+ }
823
+ return;
824
+ }
825
+
826
+ if (req.url === '/subscribed' && req.method === 'GET') {
827
+ res.writeHead(200, { 'Content-Type': 'text/html' });
828
+ res.end(`<!DOCTYPE html>
829
+ <html>
830
+ <head>
831
+ <meta charset="UTF-8">
832
+ <title>Subscription confirmed</title>
833
+ <style>
834
+ body{background:#070910;color:#00E5C3;
835
+ font-family:'DM Mono',monospace;padding:3rem;
836
+ max-width:600px;margin:0 auto}
837
+ h2{font-weight:400;margin-bottom:1rem}
838
+ p{color:#8895AA;font-size:13px;line-height:1.6;
839
+ margin-bottom:0.8rem}
840
+ a{color:#00E5C3}
841
+ </style>
842
+ </head>
843
+ <body>
844
+ <h2>Subscription confirmed.</h2>
845
+ <p>Your API key will arrive by email within 60 seconds.</p>
846
+ <p>Add it to your agent config as the
847
+ <span style="color:#fff">x-api-key</span> header.</p>
848
+ <p>Full documentation at
849
+ <a href="https://kordagencies.com">kordagencies.com</a></p>
850
+ </body>
851
+ </html>`);
852
+ return;
853
+ }
854
+
661
855
  res.writeHead(404, cors); res.end(JSON.stringify({ error: 'Not found' }));
662
856
  });
663
857
 
@@ -702,12 +896,14 @@ function setupStdio() {
702
896
 
703
897
  setupStdio();
704
898
 
705
- server.listen(PORT, () => {
899
+ server.listen(PORT, async () => {
706
900
  loadStats();
707
- loadApiKeys();
901
+ await loadApiKeysFromRedis('vat');
708
902
  console.log('VAT Validator MCP v' + VERSION + ' running on port ' + PORT);
709
903
  console.log('Free tier: ' + FREE_TIER_LIMIT + ' calls/IP/month, no API key required');
710
904
  console.log('Resend: ' + (RESEND_API_KEY ? 'configured' : 'MISSING'));
711
905
  console.log('Anthropic: ' + (ANTHROPIC_API_KEY ? 'configured' : 'MISSING'));
712
906
  console.log('ABR GUID: ' + (process.env.ABR_GUID ? 'custom GUID set' : 'using fallback demo GUID — set ABR_GUID env var'));
907
+ console.log('Upstash Redis: ' + (UPSTASH_URL ? 'configured' : 'MISSING - set UPSTASH_REDIS_REST_URL'));
908
+ console.log('Stripe: ' + (process.env.STRIPE_SECRET_KEY ? 'configured' : 'MISSING - set STRIPE_SECRET_KEY'));
713
909
  });