DEV Community

viraj geeth
viraj geeth

Posted on

Your coding agent is quoting exchange rates from its training data

Try this in any AI coding tool:

Write a function that converts USD to EUR.

There is a good chance you get something like this back:

const USD_TO_EUR = 0.92;

export function usdToEur(amount: number) {
  return amount * USD_TO_EUR;
}
Enter fullscreen mode Exit fullscreen mode

It compiles. It passes review. It is wrong, and it will stay wrong, silently, for as long as the code lives.

Why this is worse than a normal hallucination

Most model mistakes are visibly wrong. You run the code, it throws, you fix it. A stale exchange rate is different in three ways:

  1. It looks right. 0.92 is a plausible USD/EUR rate. It was probably even correct at some point during pretraining. Nothing in the diff says "this number has an expiry date."
  2. It has no failure mode. The function returns a number forever. There is no exception to catch, no log line, no alert.
  3. It is money. If that function prices a checkout, every transaction is off by whatever the market has done since the training cutoff. A percent or two, compounding across every order, in the direction you didn't choose.

And the same reflex shows up in the conversational case. Ask an agent "what's the USD to EUR rate?" and it will often just answer — no fetch, no date, no caveat — because producing a fluent number is what it does.

The fix isn't a better prompt

You can tell the model "always fetch live rates" in your CLAUDE.md and it will comply, in that repo, until the instruction falls out of context. What actually fixes it is making the correct behaviour the path of least resistance: give the agent a tool that returns a real rate, and a skill that fires on the intent rather than waiting to be asked.

That is what this plugin is:

/plugin marketplace add AllRates-Today/claude-code-plugin
/plugin install allratestoday@allratestoday
Enter fullscreen mode Exit fullscreen mode

No API key needed. That part matters more than it sounds — read on.

What it installs

Two skills. Skills load themselves when a task matches their description, which is the difference between a tool you have to remember and one that shows up on its own:

  • exchange-rates — fires on currency conversion, FX, localized pricing, historical series. Leads with "never quote a rate from memory", then covers which rate the task needs, caching by pair, money in minor units, and what happens when the fetch fails.
  • official-rates — fires on invoices, VAT, customs, statutory accounting. More on why that is a separate skill below.

Someone typing "show prices in the visitor's currency" gets the first one loaded without ever having heard of us. That is the whole point.

Five slash commands: /rate USD EUR, /convert 1500 USD to EUR, /official-rate ecb USD EUR 2026-01-15, /fx-history USD EUR 30d, and /add-currency-support, which reads your project — its language, HTTP conventions, secrets pattern, existing cache layer — and wires rates in the way that project already does things. It also greps for hardcoded rates while it's in there.

Two MCP servers, so the model can fetch rates directly rather than shelling out:

The mistake that costs more than a stale rate

There are two different numbers people call "the exchange rate", and using the wrong one is the expensive bug.

A mid-market rate is the live interbank midpoint. It moves continuously. It is the right number for a checkout, a price display, a converter.

An official rate is what a named institution published — the ECB reference rate for a given day, the HMRC monthly rate, the Bank of Japan rate in force. It is fixed once published, carries the publisher's own date, and is what an auditor checks against.

Compliance rules essentially never say "use the current market rate". They name a publisher and a date. And the two routinely differ by a percent or more, so a VAT return computed from a market rate produces numbers nobody can reconcile.

An LLM will not make this distinction unprompted — both are "the exchange rate" as far as the language goes. So it's built into the plugin: the official-rates skill fires on tax and invoicing intent, and /convert stops and redirects you if you mention an invoice.

Why keyless mattered more than we expected

The first version of both MCP servers did the responsible-looking thing: no API key, refuse to start, print instructions.

Then I installed the plugin as a new user would, and ran claude mcp list:

plugin:allratestoday:allratestoday: ✘ Failed to connect — CONNECTION_CLOSED
plugin:allratestoday:allratestoday-central-bank: ✘ Failed to connect — CONNECTION_CLOSED
Enter fullscreen mode Exit fullscreen mode

An MCP server that exits doesn't degrade — it takes the host client's whole config down with it, at exactly the moment someone is deciding whether this is worth the trouble. "Fail fast with a clear message" is good advice for a CLI and terrible advice for a subprocess whose stderr nobody reads.

Both servers now degrade instead. With no key:

  • get_exchange_rate answers from the official ECB daily reference table — about 30 major currencies, labelled with its publication date so the model never passes a daily fixing off as a live quote
  • list_currencies returns all 160+ codes
  • get_official_rates returns the latest published table for any of the 100+ sources
  • the metered tools return one sentence explaining the free tier, instead of a dead process

That path reads endpoints that are already open and edge-cached, so it costs nothing to give away:

curl "https://allratestoday.com/api/open/central-bank/ecb?source=USD&target=EUR"
Enter fullscreen mode Exit fullscreen mode
{
  "bank": "ecb",
  "rate_date": "2026-08-28",
  "source": "USD",
  "target": "EUR",
  "rate": 0.8588851671,
  "rate_type": "reference",
  "derived": true,
  "method": "inverse"
}
Enter fullscreen mode Exit fullscreen mode

A free API key adds real-time mid-market rates across 160+ currencies, historical series, rates on past dates, and publication calendars. But nothing is gated behind a signup form before you can tell whether the thing works.

If you'd rather not install anything

The general lesson stands on its own: an agent should never produce a rate it did not fetch. Put this in your CLAUDE.md and you get most of the value:

Never state or hardcode an exchange rate from memory — fetch it.
For tax, invoices or customs, use the rate the relevant authority
published on the relevant date, not the current market rate.
Enter fullscreen mode Exit fullscreen mode

And if you want the endpoint without any of the rest, that curl above needs no key and no account.


Plugin source: github.com/AllRates-Today/claude-code-plugin · API docs: allratestoday.com/docs · for agents: llms.txt

Top comments (1)

Collapse
 
jo-do profile image
Jo Do

The 0.92 constant is the perfect hallucination: it compiles. Worse than a wrong answer because it has the shape of a right one - typed, tested, reviewable - and its wrongness only pays out over months. The general form: any fact with a clock (rates, prices, versions, quotas) that the model writes as a literal is a lie scheduled for later. My rule for agent output: constants that change in the world are never allowed to be constants in the code. If it moves, it gets fetched, and the fetch timestamp stays next to the value.