eu-vat
eu-vat
The standard VAT rate in every EU member state on any date since 1 January 2016, as small libraries with no dependencies that work offline.
Language | Package | Install | Status |
Node and the browser (TypeScript) |
| ||
Python 3.9+ |
| ||
Java 11+ | Maven or Gradle, see java/ | ||
AI assistants (MCP server) |
|
import { getStandardRate } from '@duty27/eu-vat';
getStandardRate('DE', '2020-07-01'); // 16 (Germany's temporary cut)from duty27_eu_vat import get_standard_rate
get_standard_rate("DE", "2020-07-01") # Decimal('16')EuVat.getStandardRate("DE", "2020-07-01"); // 16All three give identical answers (so does the MCP server) and are built from the same dataset, which Duty27 publishes at https://duty27.com/vat-rates/history, as CSV and JSON at https://duty27.com/data/eu-standard-vat-rates.json. Every rate change there cites the law or tax-authority notice that made it, and the 2016 starting rates cite the European Commission's rate table; the citations are on that page and in the CSV and JSON, not in the packages. It covers standard rates only; reduced rates are not included. It looks rates up; it does not calculate tax. This is information, not tax advice.
For deciding what to charge (B2B reverse charge, the EU €10,000 threshold, VIES checks) and keeping the proof (archive and OSS reports), see the Duty27 API, which is free to try.
Taking just the one you need
Most people never need this repository: install the package from its registry. To read or work on one library, each folder is self-contained: it has its own README, licences, build file and tests, and needs nothing from the rest of the repo. To fetch only one folder instead of the whole repository:
git clone --filter=blob:none --no-checkout https://github.com/duty27/eu-vat.git
cd eu-vat
git sparse-checkout set python # or node, or java
git checkout mainRelated MCP server: mcp-europe-business
How they stay identical
One file, data/eu-standard-vat-rates.json, is the source. ./build.sh turns it into each
language's data module and a shared set of expected answers (test-vectors.csv, worked out by a plain scan of the
data, not by any library), then builds and tests all three libraries and the MCP server (which bundles the Node library's data), and finishes by
checking that every one of them carries the same source hash.
./build.sh # regenerate the data, then build and test node, python, java and the MCP server
./build.sh java # one language (or node, python, mcp)
./build.sh refresh # fetch the published rates and update the snapshot
./build.sh check # fail if the published rates changed, or a generated file was edited by handLicence
Code: MIT (LICENSE). Data: CC BY 4.0 (DATA-LICENSE.md), credit
Rate data: Duty27 (https://duty27.com/vat-rates/history), CC BY 4.0.
Available Tools
4 toolsget_rate_historyEvery standard VAT rate a country has hadARead-onlyIdempotent
List every standard VAT rate one EU member state has had since 2016-01-01, with the first day each one applied. Use this for "how has the VAT rate in Ireland changed?". Standard VAT rates only (not reduced rates), from 2016-01-01. Data checked against the published rates on 2026-10-04; a change made after that date appears in a newer release.
| Name | Required | Description | Default |
|---|---|---|---|
| country | Yes | EU member state code such as "DE" or "FR" (case does not matter). Greece is "EL"; "GR" is accepted too. |
Output Schema
| Name | Required | Description |
|---|---|---|
| country | Yes | |
| windows | Yes | |
| dataAsOf | Yes | |
| attribution | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish the safe read-only, idempotent, closed-world profile, so the bar is lower. The description adds genuinely useful behavior beyond that: data freshness ('checked against the published rates on 2026-10-04; a change made after that date appears in a newer release') and the 2016-01-01 start boundary. It says nothing about ordering or size of the returned series, though the series nature implies coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and the example query, and every sentence carries information. The scope clause is slightly redundant, restating 'since 2016-01-01' and the standard-rates-only restriction that the opening sentence already conveys.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return structure needn't be described. Together with the schema, annotations, and freshness note, the description gives an agent everything needed to call this correctly. Only the overlap with the list_rate_changes sibling is left unresolved.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%: the single country parameter is fully documented in the schema, including the EL/GR special case. The description adds only the constraint that it is one EU member state, which the schema's type and the tool's framing already imply. Baseline 3 is correct here.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb+resource+scope: 'List every standard VAT rate one EU member state has had since 2016-01-01, with the first day each one applied.' It tells the agent this is the historical series for a single country, which separates it from get_standard_rate (current rate). It does not, however, distinguish itself from the similarly-named sibling list_rate_changes, which an agent could confuse with this tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides a concrete trigger example ("how has the VAT rate in Ireland changed?") and an exclusion (standard rates only, not reduced rates). No sibling tool is named as an alternative, so the agent must infer when get_standard_rate or list_rate_changes is the better choice.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_standard_rateEU standard VAT rate on a dateARead-onlyIdempotent
Look up the standard VAT rate in force in one EU member state on a given day (any day since 2016-01-01). Use this for "what was the VAT rate in Germany on 1 July 2020?" or "what is the VAT rate in Finland today?". The day a rate changes is the first day of the new rate. Standard VAT rates only (not reduced rates), from 2016-01-01. Data checked against the published rates on 2026-10-04; a change made after that date appears in a newer release.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | Day as YYYY-MM-DD, for example "2020-07-01". Optional: today (UTC) when omitted. | |
| country | Yes | EU member state code such as "DE" or "FR" (case does not matter). Greece is "EL"; "GR" is accepted too. |
Output Schema
| Name | Required | Description |
|---|---|---|
| date | Yes | |
| rate | Yes | |
| country | Yes | |
| dataAsOf | Yes | |
| attribution | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish read-only, idempotent, non-destructive and closed-world behavior, so the safety profile is covered. The description adds genuinely new context the annotations cannot carry: the data vintage (checked 2026-10-04) and the boundary rule that the changeover day belongs to the new rate. It does not say what happens for a non-EU or unknown country code.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and scope are front-loaded, followed by worked examples and then caveats, so an agent can stop reading early. The two example questions are useful for routing but slightly redundant with each other, and the release-vintage sentence could be tightened.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation here. Combined with annotations covering the safety profile and the schema covering parameters, the description supplies the remaining essentials: coverage window, changeover-day semantics, and data freshness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented including the today-UTC default and the 'EL'/'GR' code detail. The description adds one real constraint beyond the schema: the lower bound of 2016-01-01 on the date, which is not encoded anywhere in the input schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (look up) plus resource (standard VAT rate) plus scope (one EU member state, one day), which clearly separates it from the list-oriented siblings. It does not explicitly name get_rate_history or list_rate_changes, so an agent must infer the boundary from the 'on a given day' framing rather than being told.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives two concrete example questions that map directly to invocation, plus the key exclusions: standard rates only, not reduced rates, and nothing before 2016-01-01. It stops short of explicitly naming which sibling to use for historical series or rate-change listings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_countriesThe 27 EU member states and their codesARead-onlyIdempotent
List the 27 EU member states with the codes the other tools expect. Greece is "EL", the code the European Union uses ("GR" is also accepted). Use this to find a code. The United Kingdom and other non-EU countries are not covered.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| dataAsOf | Yes | |
| countries | Yes | |
| attribution | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so the safety profile is covered. The description adds genuine domain behavior beyond the structured data: the Greece 'EL' vs 'GR' alias convention that the other tools expect, which is exactly the kind of quirk that prevents a failed downstream call.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, zero filler. The scope and the primary use case lead, and the code-format caveat and exclusion follow in priority order.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no narrative. With scope, purpose, and the one non-obvious code convention all covered, an agent has everything needed to call and interpret this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate; the 4 baseline applies. The description's discussion of country codes is response content, not parameter semantics, so no further credit is warranted.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the 27 EU member states with the codes the other tools expect') and immediately bounds the scope. The title and description together make it unmistakable that this is a reference/lookup tool for EU country codes, not a rate tool like its siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says to use it 'to find a code', which ties it directly to the downstream tools that require those codes, and it names the exclusion ('The United Kingdom and other non-EU countries are not covered'). Both the when and the when-not are stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_rate_changesStandard VAT rate changes across the EUARead-onlyIdempotent
List the changes of the standard VAT rate across the EU, newest first, optionally for one country and/or since a date. Use this for "which EU countries changed their VAT rate this year?". Each change gives the old rate, the new rate, and the first day of the new rate. Standard VAT rates only (not reduced rates), from 2016-01-01. Data checked against the published rates on 2026-10-04; a change made after that date appears in a newer release.
| Name | Required | Description | Default |
|---|---|---|---|
| since | No | Only changes on or after this day, YYYY-MM-DD. Omit for all of them. | |
| country | No | Only this member state (for example "RO"). Omit for all 27. |
Output Schema
| Name | Required | Description |
|---|---|---|
| changes | Yes | |
| dataAsOf | Yes | |
| attribution | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnly, idempotent, non-destructive), but the description adds genuine behavioral context: newest-first ordering, the per-change payload (old rate, new rate, first day of new rate), the 2016-01-01 coverage floor, and a data-freshness cutoff of 2026-10-04. This goes beyond what the annotations convey, though it does not discuss pagination or result size.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads what the tool does, then usage, then output fields and data caveats — every sentence carries information. It is slightly dense, with the data-checked date and release caveat adding length, but nothing is pure filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema exists, the description need not explain return values, yet it still names them. Combined with scope, ordering, coverage window, and freshness, an agent has everything needed to call and interpret this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description restates the two filters ('optionally for one country and/or since a date') without adding format or edge-case detail beyond the schema's 'YYYY-MM-DD' and country-code notes.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the changes of the standard VAT rate across the EU') with scope, ordering, and the exact fields returned. It is clearly distinguishable from siblings like get_standard_rate and get_rate_history, which return a rate or a full series rather than a change log.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides a concrete trigger question ('which EU countries changed their VAT rate this year?') and a scoping exclusion ('Standard VAT rates only (not reduced rates), from 2016-01-01'). It does not explicitly name a sibling as the alternative for adjacent questions, so it stops short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
get_rate_history - First observed
get_standard_rate - First observed
list_countries - First observed
list_rate_changes
TDQS
Scored across 4 tools
Each tool has a clearly stated distinct purpose: point-in-time lookup, per-country history, country code list, and cross-country change feed. There is mild overlap between get_rate_history and list_rate_changes (both surface rate changes for a country), but the descriptions make the intended use cases distinguishable.
All four tools follow a consistent verb_noun snake_case pattern (get_standard_rate, get_rate_history, list_countries, list_rate_changes). No deviations in convention or style.
Four tools is well-scoped for a narrow domain (EU standard VAT rates). Each tool earns its place with no redundancy or bloat.
The surface covers lookup, full history, cross-country change monitoring, and the country code reference needed to use the other tools. Reduced rates are explicitly out of scope, so within the stated domain there are no dead ends.
Maintenance
Related MCP Connectors
European govt data, cited: rates, VAT, tax, wages, holidays, FX. 35 countries; Germany free.
SLA'd EU VAT validation on VIES. Honest 3-state result, never guesses. 100 free lookups.
Utility data for AI agents: IBAN, EU holidays, VAT rates, time zones, ECB FX. Pay per call.
EU/UK VAT compliance for AI agents: number validation, rate lookups, reverse-charge checks.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables real-time validation of EU VAT numbers using the official VIES service. Supports all 27 EU member states with automatic country code detection and provides company information for valid VAT numbers.41MIT
- FlicenseAqualityDmaintenanceEuropean business compliance suite for AI agents — 28 tools covering tax ID validation (PT, ES, FR, DE, IT, UK, NL), IBAN verification, EU VAT rates, invoice requirements, e-invoicing rules, payment terms, labor calendar helpers, VAT breakdown calculations and invoice schema validation for 18+ European countries.28-
- AlicenseBqualityDmaintenanceEnables AI assistants to answer tax compliance questions (VAT, sales tax, GST) and validate EU VAT numbers in real time via the VIES registry.211 npmMIT
- AlicenseNot gradedqualityDmaintenanceSLA'd EU VAT number validation for AI agents via VIES, with caching and circuit-breaking to handle flaky upstream, returning valid/invalid/unavailable responses.MIT