Verified n8n node that puts VIES VAT validation and EU rate lookups inside a workflow, so onboarding or invoicing can decide on a real VAT number instead of a
Open-source MCP server that lets Claude Desktop, Cursor and other MCP clients validate EU VAT numbers and look up rates directly in chat.
Stack
Libraries
Services
Key Results

Anyone selling to businesses across the EU has to confirm their customers' VAT numbers are real โ it decides whether an invoice is charged VAT or zero-rated, and getting it wrong is an accounting and audit problem. The official EU checking service (VIES) is awkward to work with, and until recently the only way to reach it was to write software or wire up an API.
Meanwhile, people increasingly work inside AI assistants like Claude Desktop and Cursor. There was no way to ask an assistant "is this EU VAT number valid, and who is the company behind it?" and get an audit-grade answer. vatnode, my SaaS API for EU VAT validation, could answer that question โ but only through code. A new distribution channel was opening up: AI assistants that look for "tool servers" the same way developers look for packages.
The window was narrow. The catalogues where these tools get discovered reward early, polished entries, and third parties had already started using the "vatnode" name on unofficial servers. An official one had to ship before the brand fragmented.
I built and shipped an official vatnode tool server for AI assistants โ end-to-end in a single day. Now a user can ask Claude, Cursor, or another compatible assistant to check an EU VAT number and get back whether it's valid, the company name and address behind it, and, when configured, an audit-grade proof-of-validation reference.
Two decisions made it trustworthy and easy to adopt. First, four of the five tools work with no account and no API key at all โ rate lookups and format checks run entirely offline โ so anyone can try it before committing. Only the live VIES validation needs a key. Second, the entire package is open source under a permissive license. That matters because an AI assistant hands the server an API key, and trust there comes from being able to read the code, not from marketing. Every release is cryptographically signed with a public, auditable record proving it was built from a specific version of the public source, and the build pipeline holds no secrets at all.
Installation is a single command with no global install, and the server is listed in the catalogues where AI assistants discover tools, so it shows up as the official option rather than the unofficial copies.
| Metric | Value |
|---|---|
| Tools exposed | 5 (4 offline / 1 live) |
| npm bundle | 8.5 KB packed, 15 KB unpacked |
| Source | 251 lines TypeScript, single file |
| Dependencies | 3 runtime (SDK, Zod, eu-vat-rates-data) |
| Tests | 13 stdio JSON-RPC smoke tests on Node 24 |
| CI secrets | 0 โ npm Trusted Publishing via OIDC |
| Provenance | Sigstore-signed, publicly auditable per release |
| License | MIT |
Released as [email protected] on 2026-05-20. Companion to the closed-source vatnode SaaS โ the open-source positioning is deliberate: an AI agent receives an API key, and trust there comes from auditability, not marketing copy.
A package on npm is not enough โ AI assistants discover tool servers through catalogues and through the model's own knowledge of which servers exist:
server.json manifest with namespace io.github.vatnode/vatnode-mcp, submitted via mcp-publisher CLI/docs/mcp, case-study blog post at /blog/vat-validation-in-claude-desktop, mention in /guides/vies-api-alternative and /stripe-tax-alternativellms.txt โ section for LLM agents indexing the site, pointing them at the MCP server as the canonical agent-facing entry pointFor the technically curious, here is how the core pieces are built.
The Model Context Protocol stabilized in late 2024, and AI agents (Claude Desktop, Cursor, Cline, Continue, plus ChatGPT Plus/Pro via Custom Connectors) now look for tool servers the same way developers look for npm packages. The server is a single-file TypeScript MCP server, bundled with tsup, published to npm with OIDC-based provenance:
list_eu_vat_rates, get_country_vat_rates, check_vat_format, list_supported_countries run fully offline from the bundled eu-vat-rates-data package; only validate_vat_number calls the vatnode API and needs a keynpx -y vatnode-mcp โ no global install, ~50ms cold start, 15 KB unpackedvatnode-mcp/0.2.0 (+https://vatnode.dev) so MCP traffic is measurable in the backend logsgithub.com/vatnode/vatnode-mcp, every release signed via GitHub Actions OIDCThe description field on each MCP tool is the input prompt the model reads when deciding which tool to call. With five tools whose names overlap (check_vat_format vs validate_vat_number), documentation-style copy makes the LLM pick the wrong one โ users got "format looks valid" when they wanted a real VIES lookup.
// Documentation style โ the model has no "when to use"
description: "Validates an EU VAT number against VIES.";
// Decision-aiding style โ the model sees triggers and constraints
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 โ 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.";Result: the model routes correctly between the offline check_vat_format (cheap, no key) and the live validate_vat_number (paid, audit-grade).
Every release is published from GitHub Actions with no NPM_TOKEN, no long-lived credential. npm verifies the workflow identity via OIDC and writes a provenance record to a public sigstore transparency log โ auditable proof that a given tarball was built from a specific commit in a specific repo.
# .github/workflows/release.yml
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # OIDC token for npm
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
# Node 22 ships npm 10.x โ can sign provenance but cannot
# use the same OIDC token to authenticate the registry PUT.
# Trusted Publishing requires npm >= 11.5.1, so Node 24.
node-version: "24"
registry-url: "https://registry.npmjs.org"
- run: npm install
- run: npm test
- run: npm publish --provenance --access publicThe pitfall that cost three CI iterations: Signed provenance statement succeeds, immediately followed by 404 PUT โฆ or you do not have permission. Diagnosis โ npm version. npm install -g npm@latest on top of Node 22 trips an incompatible promise-retry inside npm itself. Clean fix is Node 24.
The MCP test runner spawns the real bundled binary as a child process, talks JSON-RPC over its stdin/stdout, and asserts on the actual wire protocol โ no SDK mocks, no fake transport.
// test/smoke.mjs
const server = spawn("node", ["dist/index.js"], {
stdio: ["pipe", "pipe", "inherit"],
});
async function rpc(method, params) {
const id = ++requestId;
server.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
return waitForResponse(id);
}
test("validate_vat_number returns auth error without API key", async () => {
const result = await rpc("tools/call", {
name: "validate_vat_number",
arguments: { vatId: "IE6388047V" },
});
assert.equal(result.isError, true);
assert.match(result.content[0].text, /VATNODE_API_KEY/);
});13 cases cover the handshake, every tool's happy path, missing-key errors, malformed input. Runs on every PR via GitHub Actions on Node 24.
AvailableNeed something similar?
I build custom solutions โ from APIs to full products. Let's talk about your project.
Verified n8n node that puts VIES VAT validation and EU rate lookups inside a workflow, so onboarding or invoicing can decide on a real VAT number instead of a
Developer-first SaaS API for EU VAT validation via VIES with Redis caching, change monitoring, and webhook notifications.
Use linkinator to catch broken links before production: local URL rewriting, --silent flag, GitHub Actions integration, and false positive patterns for
How to publish one dataset to npm, PyPI, Go Module, RubyGems, and Packagist automatically with GitHub Actions โ architecture, versioning, and per-ecosystem