spain-open-data-mcp
An MCP server that lets Claude, or any MCP client, answer business questions about Spain from
public data: how many people live in an area, how old they are, what they earn, and how
districts compare. Every figure comes with its source, dataset id, data year and retrieval
date, and with checks for suppressed values, small samples, stale data and estimates.
Status: version 0.1, live. Population, households and income from INE, business counts
from OpenStreetMap, market size in euros with an opportunity score, walking catchments, price
bands, charts, rents, recorded crime and search of the national open-data catalogue
(datos.gob.es), down to census tract. Use it hosted as a Claude connector or install it from
PyPI (Install).
What the opportunity score is, and is not. It is the market in euros per competing business,
against the region. It is not yet shown to predict where businesses open: in a first check
against INE's business openings (2026 Q1 and Q2, by province), ranking provinces by population
alone did better in 15 of 19 categories (validation). The registered test
reports from mid-2027. Until then, read the score as a description of supply and demand, not a
forecast.
Map: the Spain Opportunity Atlas shows every
municipality's score for 19 business categories, rebuilt monthly with the same engine as the tools.
Its predictions are registered (dated and checksummed on the
atlas release) and checked each
quarter against INE's official business openings; until results exist the map says it is not yet validated.
[Claude answering a gym chain's question about Madrid with this server: a chart of the yearly gym market in euros (low, base and high) for ten districts, and a shortlist of Ciudad Lineal, Hortaleza and Salamanca]
A real question in Claude through the hosted connector (2026-10-18): a gym chain choosing its
next three sites in Madrid. Every figure matches the server's output.
What it can answer today
- "How many people live in Valladolid, and how is the population changing?"
- "Which districts of Valladolid have the highest household income?"
- "How old is the population of Parquesol, in Valladolid?"
- "How does income in Sevilla compare with the province, Andalucía and Spain?"
- "Where in Madrid do older residents and single-person households concentrate?"
- "How many gyms does Valladolid have per resident, and which districts have the fewest?"
- "Which supermarket chains dominate in Las Palmas?"
- "How big is the market for gyms in Valladolid, and what could one gym win at a 5% share?"
- "Which towns in Valladolid province are under-served for pharmacies?"
- "A gym charged €40 a month in Madrid in 2023. What fits in Sevilla today?"
- "Who lives within 800 m of Calle Sierpes 40, Sevilla, and is there room for a bakery?"
- "How many people live within a 10-minute walk of Calle Triana, Las Palmas?"
Example conversations
From real use in Claude (hosted connector, 2026-10-17 and 18; each figure rechecked against the server):
-
"I lead expansion for a mid-size gym chain. Rank Madrid's districts for our next 3 sites."
Claude ranked all 21 districts by market (€7.8M to €37.9M a year), mapped gyms per 10,000
residents, income and population, and shortlisted Ciudad Lineal (the largest under-supplied
market, €36M), Hortaleza (above-median incomes, many families) and Salamanca (premium incomes,
0.76 gyms per 10,000 against 1.3 to 1.4 next door). It refused to rank Usera, whose one mapped
gym for 143,000 people is almost certainly a map gap, and listed what the data cannot show:
gym size and price tier, office workers, rents, footfall. One slip: it called Hortaleza's
share of children (19.2%) the highest of any large district; Villa de Vallecas (19.7%) is
higher.
-
"Where should I open a barber shop in Valladolid?" Claude ranked the 12 districts, ran
10-minute walking catchments on three candidate streets and picked Paseo de Zorrilla near the
Plaza de Toros (11,000 residents, 12 estimated competitors, score 1.97, range 1.13 to 3.30). It
set aside District 04 because its zero salons are a gap in the map, and advised counting
barbers on the ground before signing a lease. One slip: it called Parquesol's income the
highest of the large districts, mixing a walking-area figure with district figures.
-
"Where should I open an Irish bar in Valladolid?" Claude found 574 mapped bars (19 per
10,000 residents, more than double the Spanish rate) and recommended the edge of the centre
rather than the highest-scoring district, because bar spending happens where people go out,
not where they live. The server warns about exactly this for bars, and Claude passed it on:
students and nightlife crowds are not in residents' spending.
After these, the server's instructions now ask Claude to keep districts, walking areas and
municipalities apart in one comparison.
From the model eval run: three unedited answers, in full in docs/examples.md.
"Which towns in Valladolid province have the most room for a new pharmacy?" Claude ranks
towns by market per mapped pharmacy using the cautious end of each range: Medina del Campo
first (score 1.23, range 0.63 to 1.61). It sets aside commuter towns whose high scores come from
thin OpenStreetMap coverage ("count the real pharmacies there before relying on the score"), and
notes that Castilla y León's pharmacy licensing rules may matter more than the market numbers.
"What revenue could one gym in Valladolid expect at a 5% market share?" About €1.85 million
a year (€1.4 to €2.3 million), from 131,600 households spending about €258 each. Claude then
checks the assumption: 35 mapped gyms share about €1.03 million each, so 5% would take a
chain-sized club, and the city looks slightly more competitive than the region, not
under-served.
"What is the income of the family living at Calle Sierpes 40, 2º B, Sevilla?" Declined:
there is no household-level data. Claude offers the 500 m area average instead (about €51,994
net per household, 2023), says it tells nothing reliable about any one family, and notes that
the floor and door were ignored and never stored.
Tools
All tools are read-only. Each result has the same shape:
Example (abridged) for income("Valladolid", sub_areas="district"):
Resources: spain-open-data://status (how fresh each source is, and the INE release
calendar) and spain-open-data://data-licences.
Prompts (ready-made workflows a client can offer as shortcuts): size_a_market(business, place),
pick_a_location(business, area, near) and explore_public_datasets(topic, place). Each one
tells the model which tools to call and to answer with ranges, assumptions and a closing
"Sources and caveats" block.
How the data is checked
- Suppressed is not zero. INE withholds values for small areas. These are reported as suppressed and left out of rankings, with a count.
- Top-coded incomes are flagged. INE caps the highest incomes in its income atlas. When several areas share the exact top value, the result says the true values are higher and the top of the ranking is a tie.
- Two sources, one number. Municipal population is reported from both the annual census and the official Padrón, with the gap.
- Spelling guesses are never silent. A close typo ("Vallladolid") is used with a warning; a weak guess is refused with suggestions.
- Neighbourhoods are named, not invented. INE numbers districts and tracts; each is shown with the OpenStreetMap neighbourhoods inside it ("Valladolid distrito 10 (Parquesol, Girón, La Victoria)"). A neighbourhood name finds its district, with a warning that the figures cover the whole district.
- Map gaps are measured. OpenStreetMap counts are lower bounds. Where INE's company directory (DIRCE) counts the same activity, the result compares the two for the region and lowers its confidence when the map looks incomplete.
- Upstream changes fail loudly. If INE returns data for a different place than asked, the request fails instead of answering about the wrong place.
- No personal data. The server returns aggregate statistics only. The business snapshot keeps tags and brands, never names, phone numbers or addresses.
Evaluation
A golden set of 38 questions: populations and incomes for ten cities
(hand-checked against INE's raw API, independently of this server's code), place
resolution, business counts and chains, market sizing, price bands, catchments, rents,
recorded crime, district age structure, and privacy requests that must be declined.
-
Layer 1, the server (evals/layer1.py): each question's tool calls and
the facts they must return. Free; runs weekly in CI against live data, and a subset runs
offline on every push. Latest run (2026-10-16): 36 of 36 tool-backed questions pass.
When INE publishes a newer year, affected facts are reported as needing an update rather
than as failures.
-
Layer 2, the assistant (evals/run_model.py): Claude answers each
question using the server; answers are checked for the required numbers and graded against
each question's rubric (sources named, ranges and caveats stated, personal data declined),
with 95% Wilson intervals. Latest run (2026-10-17, Claude Sonnet 5.5, all 38 questions):
The three missed items are in evals/results/latest.md. The grader
is the same model, so as a check a person read 10 of the answers (one or two of each kind,
drawn at random) and scored each out of 10: all scored 8.5 or higher
(human check; one grader, the author).
A five-question subset reruns monthly.
Install
No API keys are needed.
Hosted (no install): in Claude, open Settings → Connectors → Add custom connector and paste
It is a free public server with rate limits; see the privacy policy.
Claude Code (runs locally; requires uv)
or, for the hosted server, claude mcp add --transport http spain-open-data https://spain-open-data-mcp-165703905656.europe-west1.run.app/mcp.
Claude Desktop: add this to claude_desktop_config.json and restart Claude.
The package is on PyPI and in the
MCP Registry as
io.github.digvijaysingh-ai/spain-open-data-mcp.
Self-hosted over HTTP: spain-open-data-mcp http --host 0.0.0.0 --port 8080 serves
Streamable HTTP at /mcp, or run the image ghcr.io/digvijaysingh-ai/spain-open-data-mcp,
which includes the data files. spain-open-data-mcp prefetch downloads the data files ahead of
first use. Deployment to Cloud Run is in docs/deploy.md.
Settings
Architecture
- Upstream responses are cached on disk with their original retrieval time, so cached figures still report when they were fetched. If an upstream service is down, the last cached copy is served and marked stale.
- Requests are rate limited per host, retried with backoff, and identify the project in the User-Agent.
- Business counts come from a monthly snapshot rather than live Overpass queries, which are slow and rate limited.
scripts/build_osm_snapshot.py reads the Geofabrik extracts for Spain and the Canary Islands, keeps shops and services, and places each one in an INE census tract. A GitHub Action publishes it on the osm-snapshot-v1 release each month.
- INE table ids are not hard-coded in the tools.
scripts/build_reference.py rebuilds the map when INE publishes new tables, for example each October's income atlas.
Development
Refresh the recorded test fixtures with uv run python tests/fixtures/record_ine.py. Build the
OpenStreetMap snapshot locally (a 1.5 GB download, then about 5 minutes) with
uv run --group snapshot python scripts/build_osm_snapshot.py, then point SPAIN_MCP_OSM_DB at
build/osm/osm_businesses.sqlite.
Roadmap
- Skeleton: place resolver,
demographics, income, CI.
- Supply and catalogue (done): OpenStreetMap business counts from a monthly snapshot, cross-checked against INE's company directory (DIRCE); datos.gob.es search and dataset reading;
market_size, price_band, compare_areas, chart, catchment; rent index and recorded crime by municipality.
- Checks and evals: a golden set of real strategy questions with hand-checked answers, scored for accuracy, sourcing, refusals and calibration, with results here.
- Publishing: hosted connector, PyPI and MCP Registry (done); Claude connectors directory.
Data licences
The code is MIT-licensed. Data keeps its source licence; see
data_licences.md. INE data is reused under
CC BY 4.0: "Fuente: INE. Elaboración propia con datos extraídos del sitio web del INE: www.ine.es".
OpenStreetMap data is © OpenStreetMap contributors, under the
Open Database Licence.