statbank-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@statbank-mcpHvor mange elbiler blev nyregistreret i 2025?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Spørg Danmarks Statistik
Ask Statistics Denmark a question in plain Danish or English. Get the number, a short explanation, a chart and the exact source.
Demo GIF placeholder:
docs/demo.gif
The problem
Statistics Denmark's StatBank has about 2,300 active tables and a free API. Using it means three things:
knowing the right table among hundreds of similar ones
knowing the variable codes (
OMRÅDE=751,Tid=2025M03,DRIV=20225)knowing which variables can be left out
General-purpose chatbots answer statistics questions from memory: confidently, often with outdated or invented numbers, and with no verifiable source.
This project is an agent that does the lookup properly:
It searches tables, reads the metadata, builds a valid query and fetches the data.
It answers with numbers it actually retrieved, and cites the table ID and the exact API query.
When the data doesn't exist, it says "kunne ikke finde" (could not find) instead of guessing.
It comes with an eval suite that measures this, with numbers in the tables below.
Related MCP server: MCP Server for Danmarks Statistik
Architecture
flowchart LR
U[User] -->|question| W[apps/web<br/>Next.js]
W -->|POST /api/ask<br/>NDJSON stream| A[packages/agent<br/>tool-use loop]
CLI[npm run ask] --> A
A <-->|Messages API| C[(Claude)]
A -->|search / info / data| T[StatbankTools]
D[Claude Desktop /<br/>Claude Code] -->|MCP stdio| M[packages/mcp-server<br/>statbank-mcp]
M --> T
T --> S[StatbankClient<br/>cache · rate limit · retry · Zod]
S -->|HTTPS| API[(api.statbank.dk)]
S -.->|replay| F[(fixtures)]
E[evals] --> A
E -.->|offline retrieval benchmark| TPackage | What it is |
| |
| An agent loop on the Anthropic SDK with a step limit, a global timeout, retries and refusal handling. A final |
| A Next.js app that streams each agent step live and shows the answer, the key values, a chart (recharts) and sources with links and API queries. It has per-IP rate limiting and a question length limit. |
| 36 questions verified against the live API, with a runner, scoring, run comparison and an offline retrieval benchmark. |
Design decisions are recorded in docs/adr:
Running it
You need Node 20 or later (developed on Node 24). The agent needs an Anthropic API key; the MCP server, the tests and demo mode do not.
Demo mode (free, no key)
Without ANTHROPIC_API_KEY, the web app and npm run ask -- --demo run five example questions with a scripted stand-in for Claude (packages/agent/src/demo.ts). Only the tool choices are hard-coded. The agent loop, input validation, answer validation, StatBank calls and numbers are real and fetched live, so this shows the full pipeline at zero cost. It is not a measure of the model. Other questions are rejected until a key is set.
npm install && npm run build
npm run web # click an example question
npm run ask -- "Hvor mange elbiler blev nyregistreret i 2025?" --demonpm install
cp .env.example .env # add ANTHROPIC_API_KEY (never commit it)
npm run build
npm run ask -- "Hvor mange elbiler blev indregistreret i 2025?"
npm run ask -- "How many people live in Aarhus?" --model haiku --json
npm run web # http://localhost:3000npm test # unit tests, no network (mocked fetch and recorded fixtures)
npm run typecheck
npm run eval:retrieval # offline search benchmark, free
npm run eval -- --model haiku # end-to-end eval, prints the cost estimate and asks first
npm run eval:compare # compare saved runsThe key is read only from the environment (ANTHROPIC_API_KEY). All options are listed in .env.example.
Use the MCP server in Claude Desktop or Claude Code
{
"mcpServers": {
"statbank": { "command": "npx", "args": ["-y", "statbank-mcp"] }
}
}claude mcp add statbank -- npx -y statbank-mcpBefore the package is published, point the config at a local build: "command": "node", "args": ["/path/to/spoerg-dst/packages/mcp-server/dist/bin.js"].
Evals
Dataset
36 questions, each verified against the live API by npm run eval:verify. The table ID and the exact query are stored for each one.
Type | Cases | Example |
lookup | 13 | "What was the population of Aarhus municipality on 1 January 2026?" |
time comparison | 8 | "Hvor mange flere elbiler blev nyregistreret i 2025 end i 2024?" |
calculation | 6 | "Hvor stor en andel af de nyregistrerede personbiler i september 2026 var elbiler?" |
ambiguous | 4 | "Hvor mange boede der i København 1. juli 2026?" (municipality or capital region?) |
no data / refusal | 5 | "Hvor mange katte er der i Danmark?", "What is the current share price of Novo Nordisk?" |
Metrics:
accuracy (an expected value within tolerance)
correct table cited
correct refusal
tool calls, tokens, cost (USD) and latency (average and p95)
Every run is saved as JSON in evals/results/.
Results: table search (offline, measured)
Tuning R@1 | Tuning MRR | Holdout R@1 | Holdout MRR | |
Baseline | 0.38 | 0.427 | 0.79 | 0.821 |
After tuning | 0.85 | 0.894 | 1.00 | 1.000 |
The tuning queries and the 14 holdout queries are separate sets; the holdout queries were written before tuning and never used to choose a change. What changed, why, and the tradeoffs are in docs/improvements.md. CI fails if MRR drops below 0.85.
Results: end-to-end agent
Model | Prompt | Accuracy | Correct table | Correct refusal | Avg tool calls | Avg cost | p95 latency |
claude-haiku-5-5 | v1 | pending | |||||
claude-sonnet-5-5 | v1 | pending |
These are not run yet. The project was built without an API key, and numbers are only reported here once they come from real runs. A full run is estimated at about $0.12 on Haiku and $2.30 on Sonnet. See ADR 0004 for how the model will be chosen from these results.
CI
On every push: build, typecheck, unit tests, the retrieval benchmark and a web build.
On pull requests: the 7-case
cisubset on Haiku (about $0.02), if theANTHROPIC_API_KEYsecret is set.
Known limitations
Table search is lexical (stems, synonyms, priors), with no embeddings. A few phrasings still miss, such as "unemployment rate" or "indbyggere Aarhus". A curated list of headline tables biases search toward key figures.
Evals are small. With 36 cases, a difference of one or two cases between models is noise.
Live data is revised. Expected answers can drift;
eval:verifyre-checks them.Rate limiting and caches are in memory and per server instance. That is fine for a demo, not for horizontal scaling.
Answers are only as good as the chosen table. The agent cites its source so a human can check it, but it can still pick a plausible table that is wrong.
Prose quality isn't evaluated. Scoring checks numbers, tables and status only.
Built with AI assistance
This project was built with substantial help from an AI coding assistant (Claude Code). I directed the work:
the architecture, the tool boundaries and the eval design
verifying the expected answers against the API
the decision to report only measured numbers
The AI wrote much of the code under that direction, and the tests, evals and ADRs exist so that the result can be checked rather than trusted.
License
MIT. Data © Danmarks Statistik, used under their open data terms.
This server cannot be deployed
Maintenance
Related MCP Connectors
Statistics Denmark (Danmarks Statistik / Statbank) MCP. Keyless REST API: https://api.statbank.dk/v1
Swedish open data for Claude and other AI assistants - verified, with sources.
Query official statistics through MCP, with periods, places and citable sources in every answer.
Official statistics for AI agents: Eurostat, World Bank, OECD, IMF and WHO data for 150+ countries.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceExposes Denmark's Statistical Bureau API as programmable resources, making it easy to integrate with language models and modern AI applications to enable natural language queries for statistical data.18MIT
- AlicenseNot gradedqualityCmaintenanceEnables natural language access to Denmark's Statistics API (Danmarks Statistik), allowing users to query and analyze Danish statistical data without coding knowledge through AI-powered interactions.MIT
- AlicenseNot gradedqualityCmaintenanceEnables LLMs to search, access, and retrieve official Swedish statistics from Statistics Sweden (SCB), providing access to 1,200+ tables covering demographics, economy, environment, labor market, and education with 75+ years of historical data.9MIT
- AlicenseNot gradedqualityBmaintenanceEnables language models to search and query Statistics Netherlands open data, turning natural-language questions into traceable statistics by discovering tables, resolving codes, and retrieving filtered observations.MIT