A proof of concept in Sovereign AI — agentic, MCP-connected tooling that grounds its answers in primary Canadian sources instead of model memory, for trade data sovereignty — built by an importer rather than a software vendor, because the tool it needed did not exist.
Every charge that decides a Canadian landed cost is already public: the CBSA tariff schedule, surtax orders in the Canada Gazette, quota utilisation reports, the SIMA anti-dumping measures in force. None of it was assembled in one place, and the calculators that do exist mostly model United States trade policy — because the US is what every default in the stack assumes, right down to the ten-digit tariff code a supplier hands you. DAITK is the Canadian side of that.
Eight pollers against primary government sources, rebuilt nightly — two of them read CBSA's CARM API, listed as two rows below since either can fail independently even though they share one authority. Each is public and free, and each answers only part of the question — a landed cost only exists where they intersect.
Every answer carries source_current_to, the date its sources were last
consolidated, so freshness travels with the figure rather than being taken on faith. The live list of
what is currently tracked, named the way the government names it, is always at
/status.
There is no separate "web version" quietly carrying different numbers from the API — the lookup form on the homepage, the JSON endpoint, and the MCP tool a model calls all read the same mirror, rebuilt on the schedule above. An answer inside Claude is the answer that belongs in a costing sheet.
GET /duty?hs=&from=,
drops into a costing sheet or an ERP./mcp —
plain-language questions, answered from the same mirror.The site, the API and the MCP server are one Cloudflare Worker, reading from Cloudflare KV — edge compute serving a read-only mirror, with no server to manage and no cold start worth mentioning. A separate process does the actual reading: a Python sync job running as a Cloudflare Container on a cron trigger, scaling to zero between runs. It runs the eight pollers above, rebuilds the mirror, and pushes the result to KV; the container's own disk is ephemeral, so its working state round-trips through R2 between runs.
The two halves are deliberately decoupled. The Worker never talks to a government API directly
— it only ever reads its own mirror — so a slow or unavailable source upstream cannot make
the live site slow or unavailable. At worst, the mirror ages, and /status and
/health/data say so.
The MCP server speaks both eras of the Model Context Protocol on the same endpoint at the same time: the older, handshake-based dialect most existing clients still use, and the stateless 2026-07-28 revision announced weeks after this project began, which replaces the handshake with per-request negotiation and drops a real answer to any protocol version this server does not actually support, rather than claiming one. Nothing already configured against this server had to change for the newer dialect to start working.
Sitting inside a model's own reasoning rather than a browser tab raises two problems a website never has to solve:
cite and notes[0] — naming the source and the date it was read, so a
model has no reason to construct its own claim about where a figure came from.expires_at, twenty-four hours out, with instructions to re-query rather than
repeat a figure past it.The harder engineering problem was not reading eight feeds — it was resisting the urge to turn incomplete data into a confident-looking number. A few of the resulting rules:
sales_tax.included is always false, stated outright rather than left
absent, because GST/HST/QST/PST depend on the province of entry and cannot be derived from an HS
code and an origin — a missing field reads as zero, and zero is usually wrong.unlabeled_treatment_codes rather than silently omitted, so nothing downstream mistakes
the gap for "no preferential rate."total_if_applies rather than a bare "+50%", because a reader cannot reliably
chain percentages from prose, and a worst case stated in isolation gets read as an addition to a
budget rather than a new total.possible_relief worth asking a broker about, instead of requiring the caller to already
know a specific provision exists.No signup, no API key, no rate-limited tier held back for later. The value of this project, for now, is being useful enough that people find it, use it, and occasionally tell us what it got wrong.