vatnode-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +124 -0
  2. package/dist/index.js +990 -0
  3. package/package.json +52 -0
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # vatnode-mcp
2
+
3
+ [![npm version](https://img.shields.io/npm/v/vatnode-mcp)](https://www.npmjs.com/package/vatnode-mcp)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ Official [Model Context Protocol](https://modelcontextprotocol.io) server for **[vatnode](https://vatnode.dev)** — VAT validation and EU tax data for AI agents.
7
+
8
+ Lets AI assistants (Claude Desktop, Cursor, ChatGPT, Continue, Cline, …) look up VAT rates, check VAT number formats, and validate VAT IDs against the EU VIES service without leaving the chat.
9
+
10
+ - **Free, offline** — VAT rates and format checks for 45 European countries, no account needed
11
+ - **Live validation** — verify EU VAT numbers against VIES, get the registered company + audit-grade consultation number (requires a free [vatnode](https://vatnode.dev) API key)
12
+ - **Five focused tools** — well-described for accurate agent tool selection
13
+ - Pure stdio, zero hosted dependencies, runs locally via `npx`
14
+
15
+ ---
16
+
17
+ ## Tools
18
+
19
+ | Tool | Free | Description |
20
+ |---|---|---|
21
+ | `get_country_vat_rates` | ✅ | Standard / reduced / super-reduced / parking rates + VAT number format for a country |
22
+ | `list_eu_vat_rates` | ✅ | All 27 EU member states (plus XI for Northern Ireland) at once |
23
+ | `check_vat_format` | ✅ | Offline syntactic check of a VAT number against the country regex |
24
+ | `list_supported_countries` | ✅ | All 45 supported countries and which ones support full VIES validation |
25
+ | `validate_vat_number` | 🔑 | Live VIES validation — returns validity, company name, address, registration date, and optional consultation number for audit proof |
26
+
27
+ Free tools work fully offline — data is bundled via [`eu-vat-rates-data`](https://www.npmjs.com/package/eu-vat-rates-data) and updated daily from the European Commission TEDB.
28
+
29
+ `validate_vat_number` requires a vatnode API key. The free tier includes a monthly request quota — [get one in 30 seconds](https://vatnode.dev).
30
+
31
+ ---
32
+
33
+ ## Install
34
+
35
+ ### Claude Desktop
36
+
37
+ Add to your `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`):
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "vatnode": {
43
+ "command": "npx",
44
+ "args": ["-y", "vatnode-mcp"],
45
+ "env": {
46
+ "VATNODE_API_KEY": "vat_live_..."
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Restart Claude Desktop. The `vatnode` tools will appear in the tool picker.
54
+
55
+ You can omit `VATNODE_API_KEY` if you only need the free tools (rates, format checks).
56
+
57
+ ### Cursor
58
+
59
+ Settings → MCP → Add new server:
60
+
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "vatnode": {
65
+ "command": "npx",
66
+ "args": ["-y", "vatnode-mcp"],
67
+ "env": { "VATNODE_API_KEY": "vat_live_..." }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### ChatGPT (custom connectors / Apps SDK)
74
+
75
+ Configure as an stdio MCP server with the same `npx -y vatnode-mcp` command. See the [Apps SDK docs](https://platform.openai.com/docs).
76
+
77
+ ### Continue / Cline / other clients
78
+
79
+ Any MCP-compatible client can connect — point it at `npx -y vatnode-mcp` and (optionally) pass `VATNODE_API_KEY` via environment.
80
+
81
+ ---
82
+
83
+ ## Get an API key
84
+
85
+ `validate_vat_number` requires a [vatnode](https://vatnode.dev) account. The platform also offers things the MCP doesn't expose:
86
+
87
+ - Webhooks for VAT status changes (monitor a customer's VAT continuously)
88
+ - Bulk validation
89
+ - National-database fallback when VIES is down
90
+ - VIES consultation numbers (audit-grade proof of validation)
91
+ - Per-key rate limiting + dashboard analytics
92
+
93
+ [**Sign up free →**](https://vatnode.dev)
94
+
95
+ ---
96
+
97
+ ## Example session
98
+
99
+ > **You:** What's the VAT rate in Finland and Germany?
100
+ >
101
+ > *(Agent calls `get_country_vat_rates` for FI and DE — free, no key.)*
102
+ >
103
+ > **Agent:** Finland's standard VAT is 25.5%, Germany's is 19%. Finland has reduced rates of 14% and 10%; Germany has 7%.
104
+
105
+ > **You:** Is IE6388047V a valid VAT?
106
+ >
107
+ > *(Agent calls `validate_vat_number` — requires API key.)*
108
+ >
109
+ > **Agent:** Yes, it's valid. Registered to **GOOGLE IRELAND LIMITED** at Gordon House, Barrow Street, Dublin 4.
110
+
111
+ ---
112
+
113
+ ## Configuration
114
+
115
+ | Env var | Required | Default | Purpose |
116
+ |---|---|---|---|
117
+ | `VATNODE_API_KEY` | for `validate_vat_number` | — | API key from https://vatnode.dev |
118
+ | `VATNODE_API_URL` | no | `https://api.vatnode.dev` | Override the API base (self-hosting / staging) |
119
+
120
+ ---
121
+
122
+ ## License
123
+
124
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,990 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z as z2 } from "zod";
7
+
8
+ // ../../packages/eu-vat-rates/dist/index.js
9
+ var eu_vat_rates_default = {
10
+ version: "2026-05-19",
11
+ source: "European Commission TEDB",
12
+ rates: {
13
+ AD: {
14
+ country: "Andorra",
15
+ currency: "EUR",
16
+ eu_member: false,
17
+ vat_name: "Impost General Indirecte",
18
+ vat_abbr: "IGI",
19
+ standard: 4.5,
20
+ reduced: [
21
+ 1,
22
+ 2.5
23
+ ],
24
+ super_reduced: null,
25
+ parking: null,
26
+ format: "1 letter + 6 digits + 1 letter",
27
+ pattern: "^[A-Z]\\d{6}[A-Z]$"
28
+ },
29
+ AL: {
30
+ country: "Albania",
31
+ currency: "ALL",
32
+ eu_member: false,
33
+ vat_name: "Tatimi mbi vler\xEBn e shtuar",
34
+ vat_abbr: "TVSH",
35
+ standard: 20,
36
+ reduced: [
37
+ 6,
38
+ 10
39
+ ],
40
+ super_reduced: null,
41
+ parking: null,
42
+ format: "1 letter + 8 digits + 1 letter",
43
+ pattern: "^[A-Z]\\d{8}[A-Z]$"
44
+ },
45
+ AT: {
46
+ country: "Austria",
47
+ currency: "EUR",
48
+ eu_member: true,
49
+ vat_name: "Umsatzsteuer",
50
+ vat_abbr: "USt",
51
+ standard: 20,
52
+ reduced: [
53
+ 10,
54
+ 13,
55
+ 19
56
+ ],
57
+ super_reduced: null,
58
+ parking: null,
59
+ format: "ATU + 8 digits",
60
+ pattern: "^ATU\\d{8}$"
61
+ },
62
+ BA: {
63
+ country: "Bosnia and Herzegovina",
64
+ currency: "BAM",
65
+ eu_member: false,
66
+ vat_name: "Porez na dodanu vrijednost",
67
+ vat_abbr: "PDV",
68
+ standard: 17,
69
+ reduced: [],
70
+ super_reduced: null,
71
+ parking: null,
72
+ format: "12 digits",
73
+ pattern: "^\\d{12}$"
74
+ },
75
+ BE: {
76
+ country: "Belgium",
77
+ currency: "EUR",
78
+ eu_member: true,
79
+ vat_name: "Belasting over de toegevoegde waarde",
80
+ vat_abbr: "BTW",
81
+ standard: 21,
82
+ reduced: [
83
+ 6,
84
+ 12
85
+ ],
86
+ super_reduced: null,
87
+ parking: 12,
88
+ format: "BE + 0/1 + 9 digits",
89
+ pattern: "^BE[01]\\d{9}$"
90
+ },
91
+ BG: {
92
+ country: "Bulgaria",
93
+ currency: "EUR",
94
+ eu_member: true,
95
+ vat_name: "\u0414\u0430\u043D\u044A\u043A \u0432\u044A\u0440\u0445\u0443 \u0434\u043E\u0431\u0430\u0432\u0435\u043D\u0430\u0442\u0430 \u0441\u0442\u043E\u0439\u043D\u043E\u0441\u0442",
96
+ vat_abbr: "\u0414\u0414\u0421",
97
+ standard: 20,
98
+ reduced: [
99
+ 9
100
+ ],
101
+ super_reduced: null,
102
+ parking: null,
103
+ format: "BG + 9\u201310 digits",
104
+ pattern: "^BG\\d{9,10}$"
105
+ },
106
+ CH: {
107
+ country: "Switzerland",
108
+ currency: "CHF",
109
+ eu_member: false,
110
+ vat_name: "Mehrwertsteuer",
111
+ vat_abbr: "MWST",
112
+ standard: 8.1,
113
+ reduced: [
114
+ 2.6,
115
+ 3.8
116
+ ],
117
+ super_reduced: null,
118
+ parking: null,
119
+ format: "CHE-NNN.NNN.NNN (+ MWST/TVA/IVA)",
120
+ pattern: "^CHE-?\\d{3}\\.?\\d{3}\\.?\\d{3}[ ]?(MWST|TVA|IVA)?$"
121
+ },
122
+ CY: {
123
+ country: "Cyprus",
124
+ currency: "EUR",
125
+ eu_member: true,
126
+ vat_name: "\u03A6\u03CC\u03C1\u03BF\u03C2 \u03A0\u03C1\u03BF\u03C3\u03C4\u03B9\u03B8\u03AD\u03BC\u03B5\u03BD\u03B7\u03C2 \u0391\u03BE\u03AF\u03B1\u03C2",
127
+ vat_abbr: "\u03A6\u03A0\u0391",
128
+ standard: 19,
129
+ reduced: [
130
+ 5,
131
+ 9
132
+ ],
133
+ super_reduced: 3,
134
+ parking: null,
135
+ format: "CY + 8 digits + 1 letter",
136
+ pattern: "^CY\\d{8}[A-Z]$"
137
+ },
138
+ CZ: {
139
+ country: "Czech Republic",
140
+ currency: "CZK",
141
+ eu_member: true,
142
+ vat_name: "Da\u0148 z p\u0159idan\xE9 hodnoty",
143
+ vat_abbr: "DPH",
144
+ standard: 21,
145
+ reduced: [
146
+ 12
147
+ ],
148
+ super_reduced: null,
149
+ parking: null,
150
+ format: "CZ + 8\u201310 digits",
151
+ pattern: "^CZ\\d{8,10}$"
152
+ },
153
+ DE: {
154
+ country: "Germany",
155
+ currency: "EUR",
156
+ eu_member: true,
157
+ vat_name: "Mehrwertsteuer",
158
+ vat_abbr: "MwSt",
159
+ standard: 19,
160
+ reduced: [
161
+ 7
162
+ ],
163
+ super_reduced: null,
164
+ parking: null,
165
+ format: "DE + 9 digits",
166
+ pattern: "^DE\\d{9}$"
167
+ },
168
+ DK: {
169
+ country: "Denmark",
170
+ currency: "DKK",
171
+ eu_member: true,
172
+ vat_name: "Moms",
173
+ vat_abbr: "moms",
174
+ standard: 25,
175
+ reduced: [],
176
+ super_reduced: null,
177
+ parking: null,
178
+ format: "DK + 8 digits",
179
+ pattern: "^DK\\d{8}$"
180
+ },
181
+ EE: {
182
+ country: "Estonia",
183
+ currency: "EUR",
184
+ eu_member: true,
185
+ vat_name: "K\xE4ibemaks",
186
+ vat_abbr: "km",
187
+ standard: 24,
188
+ reduced: [
189
+ 9,
190
+ 13
191
+ ],
192
+ super_reduced: null,
193
+ parking: null,
194
+ format: "EE + 9 digits",
195
+ pattern: "^EE\\d{9}$"
196
+ },
197
+ ES: {
198
+ country: "Spain",
199
+ currency: "EUR",
200
+ eu_member: true,
201
+ vat_name: "Impuesto sobre el Valor A\xF1adido",
202
+ vat_abbr: "IVA",
203
+ standard: 21,
204
+ reduced: [
205
+ 10
206
+ ],
207
+ super_reduced: 4,
208
+ parking: null,
209
+ format: "ES + letter/digit + 7 digits + letter/digit",
210
+ pattern: "^ES[A-Z0-9]\\d{7}[A-Z0-9]$"
211
+ },
212
+ FI: {
213
+ country: "Finland",
214
+ currency: "EUR",
215
+ eu_member: true,
216
+ vat_name: "Arvonlis\xE4vero",
217
+ vat_abbr: "ALV",
218
+ standard: 25.5,
219
+ reduced: [
220
+ 10,
221
+ 13.5
222
+ ],
223
+ super_reduced: null,
224
+ parking: null,
225
+ format: "FI + 8 digits",
226
+ pattern: "^FI\\d{8}$"
227
+ },
228
+ FR: {
229
+ country: "France",
230
+ currency: "EUR",
231
+ eu_member: true,
232
+ vat_name: "Taxe sur la valeur ajout\xE9e",
233
+ vat_abbr: "TVA",
234
+ standard: 20,
235
+ reduced: [
236
+ 0.9,
237
+ 1.05,
238
+ 5.5,
239
+ 8.5,
240
+ 10,
241
+ 13
242
+ ],
243
+ super_reduced: 2.1,
244
+ parking: null,
245
+ format: "FR + 2 alphanumeric + 9 digits",
246
+ pattern: "^FR[A-HJ-NP-Z0-9]{2}\\d{9}$"
247
+ },
248
+ GB: {
249
+ country: "United Kingdom",
250
+ currency: "GBP",
251
+ eu_member: false,
252
+ vat_name: "Value Added Tax",
253
+ vat_abbr: "VAT",
254
+ standard: 20,
255
+ reduced: [
256
+ 5
257
+ ],
258
+ super_reduced: null,
259
+ parking: null,
260
+ format: "GB + 9 digits, 12 digits, or GD/HA + 3 digits",
261
+ pattern: "^GB(\\d{9}|\\d{12}|GD\\d{3}|HA\\d{3})$"
262
+ },
263
+ GE: {
264
+ country: "Georgia",
265
+ currency: "GEL",
266
+ eu_member: false,
267
+ vat_name: "\u10D3\u10D0\u10DB\u10D0\u10E2\u10D4\u10D1\u10E3\u10DA\u10D8 \u10E6\u10D8\u10E0\u10D4\u10D1\u10E3\u10DA\u10D4\u10D1\u10D8\u10E1 \u10D2\u10D0\u10D3\u10D0\u10E1\u10D0\u10EE\u10D0\u10D3\u10D8",
268
+ vat_abbr: "\u10D3\u10E6\u10D2",
269
+ standard: 18,
270
+ reduced: [],
271
+ super_reduced: null,
272
+ parking: null,
273
+ format: "9 digits",
274
+ pattern: "^\\d{9}$"
275
+ },
276
+ GR: {
277
+ country: "Greece",
278
+ currency: "EUR",
279
+ eu_member: true,
280
+ vat_name: "\u03A6\u03CC\u03C1\u03BF\u03C2 \u03A0\u03C1\u03BF\u03C3\u03C4\u03B9\u03B8\u03AD\u03BC\u03B5\u03BD\u03B7\u03C2 \u0391\u03BE\u03AF\u03B1\u03C2",
281
+ vat_abbr: "\u03A6\u03A0\u0391",
282
+ standard: 24,
283
+ reduced: [
284
+ 6,
285
+ 13,
286
+ 17
287
+ ],
288
+ super_reduced: 4,
289
+ parking: 13,
290
+ format: "EL + 9 digits",
291
+ pattern: "^EL\\d{9}$"
292
+ },
293
+ HR: {
294
+ country: "Croatia",
295
+ currency: "EUR",
296
+ eu_member: true,
297
+ vat_name: "Porez na dodanu vrijednost",
298
+ vat_abbr: "PDV",
299
+ standard: 25,
300
+ reduced: [
301
+ 5,
302
+ 13
303
+ ],
304
+ super_reduced: null,
305
+ parking: null,
306
+ format: "HR + 11 digits",
307
+ pattern: "^HR\\d{11}$"
308
+ },
309
+ HU: {
310
+ country: "Hungary",
311
+ currency: "HUF",
312
+ eu_member: true,
313
+ vat_name: "\xC1ltal\xE1nos forgalmi ad\xF3",
314
+ vat_abbr: "\xC1FA",
315
+ standard: 27,
316
+ reduced: [
317
+ 5,
318
+ 18
319
+ ],
320
+ super_reduced: null,
321
+ parking: null,
322
+ format: "HU + 8 digits",
323
+ pattern: "^HU\\d{8}$"
324
+ },
325
+ IE: {
326
+ country: "Ireland",
327
+ currency: "EUR",
328
+ eu_member: true,
329
+ vat_name: "Value Added Tax",
330
+ vat_abbr: "VAT",
331
+ standard: 23,
332
+ reduced: [
333
+ 9,
334
+ 13.5
335
+ ],
336
+ super_reduced: null,
337
+ parking: null,
338
+ format: "IE + 7 digits + 1\u20132 letters",
339
+ pattern: "^IE\\d{7}[A-W][A-IW]?$|^IE\\d[A-Z+*]\\d{5}[A-W]$"
340
+ },
341
+ IS: {
342
+ country: "Iceland",
343
+ currency: "ISK",
344
+ eu_member: false,
345
+ vat_name: "Vir\xF0isaukaskattur",
346
+ vat_abbr: "VSK",
347
+ standard: 24,
348
+ reduced: [
349
+ 11
350
+ ],
351
+ super_reduced: null,
352
+ parking: null,
353
+ format: "5\u20136 digits",
354
+ pattern: "^\\d{5,6}$"
355
+ },
356
+ IT: {
357
+ country: "Italy",
358
+ currency: "EUR",
359
+ eu_member: true,
360
+ vat_name: "Imposta sul valore aggiunto",
361
+ vat_abbr: "IVA",
362
+ standard: 22,
363
+ reduced: [
364
+ 5,
365
+ 10
366
+ ],
367
+ super_reduced: 4,
368
+ parking: null,
369
+ format: "IT + 11 digits",
370
+ pattern: "^IT\\d{11}$"
371
+ },
372
+ LI: {
373
+ country: "Liechtenstein",
374
+ currency: "CHF",
375
+ eu_member: false,
376
+ vat_name: "Mehrwertsteuer",
377
+ vat_abbr: "MWST",
378
+ standard: 8.1,
379
+ reduced: [
380
+ 2.6,
381
+ 3.8
382
+ ],
383
+ super_reduced: null,
384
+ parking: null,
385
+ format: "5 digits",
386
+ pattern: "^\\d{5}$"
387
+ },
388
+ LT: {
389
+ country: "Lithuania",
390
+ currency: "EUR",
391
+ eu_member: true,
392
+ vat_name: "Prid\u0117tin\u0117s vert\u0117s mokestis",
393
+ vat_abbr: "PVM",
394
+ standard: 21,
395
+ reduced: [
396
+ 5,
397
+ 12
398
+ ],
399
+ super_reduced: null,
400
+ parking: null,
401
+ format: "LT + 9 or 12 digits",
402
+ pattern: "^LT(\\d{9}|\\d{12})$"
403
+ },
404
+ LU: {
405
+ country: "Luxembourg",
406
+ currency: "EUR",
407
+ eu_member: true,
408
+ vat_name: "Taxe sur la valeur ajout\xE9e",
409
+ vat_abbr: "TVA",
410
+ standard: 17,
411
+ reduced: [
412
+ 8,
413
+ 14
414
+ ],
415
+ super_reduced: 3,
416
+ parking: 14,
417
+ format: "LU + 8 digits",
418
+ pattern: "^LU\\d{8}$"
419
+ },
420
+ LV: {
421
+ country: "Latvia",
422
+ currency: "EUR",
423
+ eu_member: true,
424
+ vat_name: "Pievienot\u0101s v\u0113rt\u012Bbas nodoklis",
425
+ vat_abbr: "PVN",
426
+ standard: 21,
427
+ reduced: [
428
+ 5,
429
+ 12
430
+ ],
431
+ super_reduced: null,
432
+ parking: null,
433
+ format: "LV + 11 digits",
434
+ pattern: "^LV\\d{11}$"
435
+ },
436
+ MC: {
437
+ country: "Monaco",
438
+ currency: "EUR",
439
+ eu_member: false,
440
+ vat_name: "Taxe sur la valeur ajout\xE9e",
441
+ vat_abbr: "TVA",
442
+ standard: 20,
443
+ reduced: [
444
+ 5.5,
445
+ 10
446
+ ],
447
+ super_reduced: 2.1,
448
+ parking: null,
449
+ format: "FR + 2 alphanumeric + 9 digits",
450
+ pattern: "^FR[A-HJ-NP-Z0-9]{2}\\d{9}$"
451
+ },
452
+ MD: {
453
+ country: "Moldova",
454
+ currency: "MDL",
455
+ eu_member: false,
456
+ vat_name: "Taxa pe valoarea ad\u0103ugat\u0103",
457
+ vat_abbr: "TVA",
458
+ standard: 20,
459
+ reduced: [
460
+ 8
461
+ ],
462
+ super_reduced: null,
463
+ parking: null,
464
+ format: "7 digits",
465
+ pattern: "^\\d{7}$"
466
+ },
467
+ ME: {
468
+ country: "Montenegro",
469
+ currency: "EUR",
470
+ eu_member: false,
471
+ vat_name: "Porez na dodatu vrijednost",
472
+ vat_abbr: "PDV",
473
+ standard: 21,
474
+ reduced: [
475
+ 7,
476
+ 15
477
+ ],
478
+ super_reduced: null,
479
+ parking: null,
480
+ format: "8 digits",
481
+ pattern: "^\\d{8}$"
482
+ },
483
+ MK: {
484
+ country: "North Macedonia",
485
+ currency: "MKD",
486
+ eu_member: false,
487
+ vat_name: "\u0414\u0430\u043D\u043E\u043A \u043D\u0430 \u0434\u043E\u0434\u0430\u0434\u0435\u043D\u0430 \u0432\u0440\u0435\u0434\u043D\u043E\u0441\u0442",
488
+ vat_abbr: "\u0414\u0414\u0412",
489
+ standard: 18,
490
+ reduced: [
491
+ 5,
492
+ 10
493
+ ],
494
+ super_reduced: null,
495
+ parking: null,
496
+ format: "MK + 13 digits",
497
+ pattern: "^MK\\d{13}$"
498
+ },
499
+ MT: {
500
+ country: "Malta",
501
+ currency: "EUR",
502
+ eu_member: true,
503
+ vat_name: "Taxxa tal-Valur Mi\u017Cjud",
504
+ vat_abbr: "VAT",
505
+ standard: 18,
506
+ reduced: [
507
+ 5,
508
+ 7
509
+ ],
510
+ super_reduced: null,
511
+ parking: 12,
512
+ format: "MT + 8 digits",
513
+ pattern: "^MT\\d{8}$"
514
+ },
515
+ NL: {
516
+ country: "Netherlands",
517
+ currency: "EUR",
518
+ eu_member: true,
519
+ vat_name: "Belasting over de toegevoegde waarde",
520
+ vat_abbr: "btw",
521
+ standard: 21,
522
+ reduced: [
523
+ 9
524
+ ],
525
+ super_reduced: null,
526
+ parking: null,
527
+ format: "NL + 9 digits + B + 2 digits",
528
+ pattern: "^NL\\d{9}B\\d{2}$"
529
+ },
530
+ NO: {
531
+ country: "Norway",
532
+ currency: "NOK",
533
+ eu_member: false,
534
+ vat_name: "Merverdiavgift",
535
+ vat_abbr: "MVA",
536
+ standard: 25,
537
+ reduced: [
538
+ 12,
539
+ 15
540
+ ],
541
+ super_reduced: null,
542
+ parking: null,
543
+ format: "9 digits + MVA",
544
+ pattern: "^\\d{9}MVA$"
545
+ },
546
+ PL: {
547
+ country: "Poland",
548
+ currency: "PLN",
549
+ eu_member: true,
550
+ vat_name: "Podatek od towar\xF3w i us\u0142ug",
551
+ vat_abbr: "VAT",
552
+ standard: 23,
553
+ reduced: [
554
+ 5,
555
+ 8
556
+ ],
557
+ super_reduced: 8,
558
+ parking: null,
559
+ format: "PL + 10 digits",
560
+ pattern: "^PL\\d{10}$"
561
+ },
562
+ PT: {
563
+ country: "Portugal",
564
+ currency: "EUR",
565
+ eu_member: true,
566
+ vat_name: "Imposto sobre o Valor Acrescentado",
567
+ vat_abbr: "IVA",
568
+ standard: 23,
569
+ reduced: [
570
+ 6,
571
+ 13,
572
+ 16,
573
+ 22
574
+ ],
575
+ super_reduced: 6,
576
+ parking: 13,
577
+ format: "PT + 9 digits",
578
+ pattern: "^PT\\d{9}$"
579
+ },
580
+ RO: {
581
+ country: "Romania",
582
+ currency: "RON",
583
+ eu_member: true,
584
+ vat_name: "Taxa pe valoarea ad\u0103ugat\u0103",
585
+ vat_abbr: "TVA",
586
+ standard: 21,
587
+ reduced: [
588
+ 11
589
+ ],
590
+ super_reduced: null,
591
+ parking: null,
592
+ format: "RO + 2\u201310 digits",
593
+ pattern: "^RO\\d{2,10}$"
594
+ },
595
+ RS: {
596
+ country: "Serbia",
597
+ currency: "RSD",
598
+ eu_member: false,
599
+ vat_name: "Porez na dodatu vrednost",
600
+ vat_abbr: "PDV",
601
+ standard: 20,
602
+ reduced: [
603
+ 10
604
+ ],
605
+ super_reduced: null,
606
+ parking: null,
607
+ format: "9 digits",
608
+ pattern: "^\\d{9}$"
609
+ },
610
+ SE: {
611
+ country: "Sweden",
612
+ currency: "SEK",
613
+ eu_member: true,
614
+ vat_name: "Merv\xE4rdesskatt",
615
+ vat_abbr: "moms",
616
+ standard: 25,
617
+ reduced: [
618
+ 6,
619
+ 12
620
+ ],
621
+ super_reduced: null,
622
+ parking: null,
623
+ format: "SE + 10 digits + 01",
624
+ pattern: "^SE\\d{10}01$"
625
+ },
626
+ SI: {
627
+ country: "Slovenia",
628
+ currency: "EUR",
629
+ eu_member: true,
630
+ vat_name: "Davek na dodano vrednost",
631
+ vat_abbr: "DDV",
632
+ standard: 22,
633
+ reduced: [
634
+ 5,
635
+ 9.5
636
+ ],
637
+ super_reduced: null,
638
+ parking: null,
639
+ format: "SI + 8 digits",
640
+ pattern: "^SI\\d{8}$"
641
+ },
642
+ SK: {
643
+ country: "Slovakia",
644
+ currency: "EUR",
645
+ eu_member: true,
646
+ vat_name: "Da\u0148 z pridanej hodnoty",
647
+ vat_abbr: "DPH",
648
+ standard: 23,
649
+ reduced: [
650
+ 5,
651
+ 19
652
+ ],
653
+ super_reduced: null,
654
+ parking: null,
655
+ format: "SK + 10 digits",
656
+ pattern: "^SK\\d{10}$"
657
+ },
658
+ TR: {
659
+ country: "Turkey",
660
+ currency: "TRY",
661
+ eu_member: false,
662
+ vat_name: "Katma De\u011Fer Vergisi",
663
+ vat_abbr: "KDV",
664
+ standard: 20,
665
+ reduced: [
666
+ 1,
667
+ 10
668
+ ],
669
+ super_reduced: null,
670
+ parking: null,
671
+ format: "10 digits",
672
+ pattern: "^\\d{10}$"
673
+ },
674
+ UA: {
675
+ country: "Ukraine",
676
+ currency: "UAH",
677
+ eu_member: false,
678
+ vat_name: "\u041F\u043E\u0434\u0430\u0442\u043E\u043A \u043D\u0430 \u0434\u043E\u0434\u0430\u043D\u0443 \u0432\u0430\u0440\u0442\u0456\u0441\u0442\u044C",
679
+ vat_abbr: "\u041F\u0414\u0412",
680
+ standard: 20,
681
+ reduced: [
682
+ 7,
683
+ 14
684
+ ],
685
+ super_reduced: null,
686
+ parking: null,
687
+ format: "9 digits",
688
+ pattern: "^\\d{9}$"
689
+ },
690
+ XI: {
691
+ country: "Northern Ireland",
692
+ currency: "GBP",
693
+ eu_member: false,
694
+ vat_name: "Value Added Tax",
695
+ vat_abbr: "VAT",
696
+ standard: 20,
697
+ reduced: [
698
+ 5
699
+ ],
700
+ super_reduced: null,
701
+ parking: null,
702
+ format: "XI + 9 digits, 12 digits, or GD/HA + 3 digits",
703
+ pattern: "^XI(\\d{9}|\\d{12}|(GD|HA)\\d{3})$"
704
+ },
705
+ XK: {
706
+ country: "Kosovo",
707
+ currency: "EUR",
708
+ eu_member: false,
709
+ vat_name: "Tatimi mbi Vler\xEBn e Shtuar",
710
+ vat_abbr: "TVSH",
711
+ standard: 18,
712
+ reduced: [
713
+ 8
714
+ ],
715
+ super_reduced: null,
716
+ parking: null,
717
+ format: "9 digits",
718
+ pattern: "^\\d{9}$"
719
+ }
720
+ }
721
+ };
722
+ var dataset = eu_vat_rates_default;
723
+ function getRate(code) {
724
+ return dataset.rates[code];
725
+ }
726
+ function getAllRates() {
727
+ return dataset.rates;
728
+ }
729
+ var dataVersion = dataset.version;
730
+
731
+ // ../../packages/shared/src/validators/index.ts
732
+ import { z } from "zod";
733
+ var EU_COUNTRY_CODES = [
734
+ "AT",
735
+ "BE",
736
+ "BG",
737
+ "HR",
738
+ "CY",
739
+ "CZ",
740
+ "DK",
741
+ "EE",
742
+ "FI",
743
+ "FR",
744
+ "DE",
745
+ "GR",
746
+ "HU",
747
+ "IE",
748
+ "IT",
749
+ "LV",
750
+ "LT",
751
+ "LU",
752
+ "MT",
753
+ "NL",
754
+ "PL",
755
+ "PT",
756
+ "RO",
757
+ "SK",
758
+ "SI",
759
+ "ES",
760
+ "SE",
761
+ "XI"
762
+ ];
763
+ var _rates = getAllRates();
764
+ var VAT_PATTERNS = {};
765
+ for (const code of EU_COUNTRY_CODES) {
766
+ const entry = _rates[code];
767
+ if (entry?.pattern) {
768
+ VAT_PATTERNS[code] = new RegExp(entry.pattern);
769
+ }
770
+ }
771
+ function normalizeVatId(vatId) {
772
+ const upper = vatId.trim().toUpperCase().replace(/[\s\-.]/g, "");
773
+ return upper.startsWith("GR") ? "EL" + upper.slice(2) : upper;
774
+ }
775
+ function extractCountryCode(vatId) {
776
+ const normalized = normalizeVatId(vatId);
777
+ if (normalized.startsWith("EL")) return "GR";
778
+ const code = normalized.slice(0, 2);
779
+ return EU_COUNTRY_CODES.includes(code) ? code : null;
780
+ }
781
+ function validateVatIdFormat(vatId) {
782
+ const normalized = normalizeVatId(vatId);
783
+ if (normalized.length < 4) {
784
+ return { valid: false, error: "VAT ID too short" };
785
+ }
786
+ const countryCode = extractCountryCode(normalized);
787
+ if (!countryCode) {
788
+ return { valid: false, error: "Invalid country code" };
789
+ }
790
+ const pattern = VAT_PATTERNS[countryCode];
791
+ if (pattern && !pattern.test(normalized)) {
792
+ return { valid: false, error: `Invalid VAT ID format for ${countryCode}` };
793
+ }
794
+ return { valid: true };
795
+ }
796
+ var vatIdSchema = z.string().min(4).max(20).transform(normalizeVatId);
797
+ var countryCodeSchema = z.enum(EU_COUNTRY_CODES);
798
+ var createSubscriptionSchema = z.object({
799
+ vatId: vatIdSchema,
800
+ webhookId: z.string().uuid().optional(),
801
+ emailEnabled: z.boolean().default(false)
802
+ });
803
+ var updateSubscriptionSchema = z.object({
804
+ status: z.enum(["active", "paused"]).optional(),
805
+ webhookId: z.string().uuid().nullable().optional(),
806
+ emailEnabled: z.boolean().optional()
807
+ });
808
+ var createWebhookSchema = z.object({
809
+ url: z.string().url()
810
+ });
811
+ var paginationSchema = z.object({
812
+ page: z.coerce.number().int().positive().default(1),
813
+ limit: z.coerce.number().int().min(1).max(100).default(20)
814
+ });
815
+
816
+ // src/index.ts
817
+ var VERSION = "0.1.0";
818
+ var API_BASE = process.env.VATNODE_API_URL ?? "https://api.vatnode.dev";
819
+ var API_KEY = process.env.VATNODE_API_KEY;
820
+ var USER_AGENT = `vatnode-mcp/${VERSION} (+https://vatnode.dev)`;
821
+ var server = new McpServer({ name: "vatnode", version: VERSION });
822
+ var ok = (data) => ({
823
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
824
+ structuredContent: data
825
+ });
826
+ var err = (message, hint) => ({
827
+ isError: true,
828
+ content: [
829
+ {
830
+ type: "text",
831
+ text: hint ? `${message}
832
+
833
+ Hint: ${hint}` : message
834
+ }
835
+ ]
836
+ });
837
+ function buildRate(countryCode) {
838
+ const r = getRate(countryCode);
839
+ if (!r) return null;
840
+ return {
841
+ countryCode,
842
+ countryName: r.country,
843
+ vatName: r.vat_name,
844
+ vatAbbr: r.vat_abbr,
845
+ currency: r.currency,
846
+ standardRate: r.standard,
847
+ reducedRates: [...r.reduced].sort((a, b) => b - a),
848
+ superReducedRate: r.super_reduced,
849
+ parkingRate: r.parking,
850
+ vatNumberFormat: r.format ?? null,
851
+ vatNumberPattern: r.pattern ?? null,
852
+ updatedAt: dataVersion
853
+ };
854
+ }
855
+ server.registerTool(
856
+ "list_eu_vat_rates",
857
+ {
858
+ title: "List EU VAT rates",
859
+ description: 'Returns current VAT rates for all 27 EU member states (plus XI for Northern Ireland). Use when the user asks for an overview, a comparison across countries, or "all EU VAT rates". For a single country prefer get_country_vat_rates. Data is sourced from the EU Commission TEDB and updated daily. Free, no API key required.',
860
+ inputSchema: {}
861
+ },
862
+ async () => {
863
+ const rates = EU_COUNTRY_CODES.map((c) => buildRate(c)).filter(Boolean);
864
+ return ok({ rates, count: rates.length, updatedAt: dataVersion });
865
+ }
866
+ );
867
+ server.registerTool(
868
+ "get_country_vat_rates",
869
+ {
870
+ title: "Get VAT rates for a country",
871
+ description: 'Returns the standard, reduced, super-reduced and parking VAT rates for a single European country, plus the VAT number format and regex. Accepts ISO 3166-1 alpha-2 codes (DE, FR, IT, \u2026); also covers non-EU European jurisdictions where available (NO, CH, GB, UA, TR, \u2026). Use when the user asks "what is the VAT rate in X" or needs the VAT number format for a country. Free, no API key required.',
872
+ inputSchema: {
873
+ countryCode: z2.string().length(2).describe('ISO 3166-1 alpha-2 country code, e.g. "DE", "FR", "IT".')
874
+ }
875
+ },
876
+ async ({ countryCode }) => {
877
+ const rate = buildRate(countryCode.toUpperCase());
878
+ if (!rate) return err(`No VAT data for country code "${countryCode}".`);
879
+ return ok(rate);
880
+ }
881
+ );
882
+ server.registerTool(
883
+ "check_vat_format",
884
+ {
885
+ title: "Check VAT number format",
886
+ description: "Performs an offline syntactic check of a VAT number against the country-specific regex pattern. Does NOT verify the VAT with VIES \u2014 use validate_vat_number for that. Use when the user wants a quick sanity check on the shape of a VAT ID without burning a quota call. Free, no API key required.",
887
+ inputSchema: {
888
+ vatId: z2.string().min(3).describe(
889
+ 'A VAT number, with or without spaces/dashes, with or without country prefix (e.g. "DE123456789", "IE 6388047 V").'
890
+ )
891
+ }
892
+ },
893
+ async ({ vatId }) => {
894
+ const normalized = normalizeVatId(vatId);
895
+ const country = extractCountryCode(normalized);
896
+ const result = validateVatIdFormat(normalized);
897
+ return ok({
898
+ input: vatId,
899
+ normalized,
900
+ countryCode: country,
901
+ validFormat: result.valid,
902
+ error: result.error ?? null
903
+ });
904
+ }
905
+ );
906
+ server.registerTool(
907
+ "list_supported_countries",
908
+ {
909
+ title: "List supported countries",
910
+ description: "Returns the list of countries the vatnode MCP server has VAT data for. Distinguishes EU member states (eligible for full VIES validation) from other European jurisdictions (rate lookup only).",
911
+ inputSchema: {}
912
+ },
913
+ async () => {
914
+ const all = getAllRates();
915
+ const euSet = new Set(EU_COUNTRY_CODES);
916
+ const countries = Object.entries(all).map(([code, r]) => ({
917
+ countryCode: code,
918
+ countryName: r.country,
919
+ isEUMember: euSet.has(code),
920
+ viesValidationSupported: euSet.has(code)
921
+ })).sort((a, b) => a.countryCode.localeCompare(b.countryCode));
922
+ return ok({
923
+ countries,
924
+ count: countries.length,
925
+ euCount: countries.filter((c) => c.isEUMember).length,
926
+ updatedAt: dataVersion
927
+ });
928
+ }
929
+ );
930
+ async function callVatnodeApi(path, init) {
931
+ if (!API_KEY) {
932
+ return {
933
+ ok: false,
934
+ error: err(
935
+ "This tool requires a vatnode API key.",
936
+ "Set the VATNODE_API_KEY environment variable in your MCP client config. Get a free key at https://vatnode.dev (free tier includes a monthly request quota)."
937
+ )
938
+ };
939
+ }
940
+ const res = await fetch(`${API_BASE}${path}`, {
941
+ ...init,
942
+ headers: {
943
+ Authorization: `Bearer ${API_KEY}`,
944
+ "User-Agent": USER_AGENT,
945
+ Accept: "application/json",
946
+ ...init?.headers ?? {}
947
+ }
948
+ });
949
+ const body = await res.json().catch(() => ({}));
950
+ if (!res.ok) {
951
+ const code = body.error?.code ?? `HTTP_${res.status}`;
952
+ const message = body.error?.message ?? `vatnode API returned ${res.status}`;
953
+ return { ok: false, error: err(`${code}: ${message}`) };
954
+ }
955
+ return { ok: true, data: body };
956
+ }
957
+ server.registerTool(
958
+ "validate_vat_number",
959
+ {
960
+ title: "Validate EU VAT number",
961
+ description: "Verifies an EU VAT number against the official VIES service and returns validity, company name, address, registration date and other metadata. When the requester (your own VAT) is configured on the vatnode account, also returns a VIES consultation number \u2014 audit-grade proof of validation. Use whenever the user wants to confirm a VAT is real, look up the company behind a VAT, or needs evidence for accounting/compliance. Requires a vatnode API key (free tier available). Only EU-27 + XI (Northern Ireland) are supported by VIES.",
962
+ inputSchema: {
963
+ vatId: z2.string().min(3).describe(
964
+ 'EU VAT number, with country prefix. Spaces and dashes are stripped. Examples: "DE123456789", "IE6388047V", "FR12345678901".'
965
+ ),
966
+ requesterCountryCode: z2.string().length(2).optional().describe(
967
+ "Optional: 2-letter country code of the party doing the check. Together with requesterVatNumber, asks VIES to issue an audit consultation number."
968
+ ),
969
+ requesterVatNumber: z2.string().min(1).optional().describe(
970
+ "Optional: VAT number of the party doing the check. Pair with requesterCountryCode to get a consultation number."
971
+ )
972
+ }
973
+ },
974
+ async ({ vatId, requesterCountryCode, requesterVatNumber }) => {
975
+ const normalized = normalizeVatId(vatId);
976
+ const params = new URLSearchParams();
977
+ if (requesterCountryCode && requesterVatNumber) {
978
+ params.set("requesterCountryCode", requesterCountryCode.toUpperCase());
979
+ params.set("requesterVatNumber", requesterVatNumber);
980
+ }
981
+ const qs = params.toString();
982
+ const result = await callVatnodeApi(
983
+ `/v1/vat/${encodeURIComponent(normalized)}${qs ? `?${qs}` : ""}`
984
+ );
985
+ if (!result.ok) return result.error;
986
+ return ok(result.data);
987
+ }
988
+ );
989
+ var transport = new StdioServerTransport();
990
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "vatnode-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Official Model Context Protocol (MCP) server for vatnode — EU VAT validation, rates, and format checks for AI agents (Claude, Cursor, ChatGPT).",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "vat",
9
+ "vies",
10
+ "eu-vat",
11
+ "tax",
12
+ "claude",
13
+ "anthropic",
14
+ "vatnode"
15
+ ],
16
+ "license": "MIT",
17
+ "author": "vatnode",
18
+ "homepage": "https://vatnode.dev",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/vatnode/vatnode.git",
22
+ "directory": "apps/mcp"
23
+ },
24
+ "type": "module",
25
+ "bin": {
26
+ "vatnode-mcp": "./dist/index.js"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ],
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.0.4",
34
+ "zod": "^3.24.0",
35
+ "@vatnode/shared": "0.1.0",
36
+ "eu-vat-rates-data": "2026.3.27"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.10.0",
40
+ "tsup": "^8.3.0",
41
+ "tsx": "^4.19.0",
42
+ "typescript": "^5.7.0"
43
+ },
44
+ "scripts": {
45
+ "dev": "tsx watch src/index.ts",
46
+ "build": "tsup",
47
+ "start": "node dist/index.js",
48
+ "lint": "eslint src/",
49
+ "typecheck": "tsc --noEmit",
50
+ "test": "pnpm build && node test/smoke.mjs"
51
+ }
52
+ }