Spain Open Data MCP
Provides business counts from OpenStreetMap, using a monthly snapshot, to support market sizing and area comparisons in Spain. Planned to be cross-checked against INE's company directory (DIRCE).
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., "@Spain Open Data MCPWhich districts of Valladolid have the highest household income?"
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.
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: early development (milestone 1 of 4). Population, households and income from INE work today, down to census tract. Business counts (OpenStreetMap), the national open-data catalogue (datos.gob.es), market sizing, price bands and charts are next. See the roadmap.
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 does income in Sevilla compare with the province, Andalucía and Spain?"
"Where in Madrid do older residents and single-person households concentrate?"
Related MCP server: mcp-ine-es
Tools
Tool | What it returns |
| INE code and level for a place name: autonomous community, province, municipality, district or census tract. Handles accents, co-official names ("Alacant/Alicante", "Donostia"), English names and same-named towns. |
| Population by sex and five-year age band, 5-year trend, household size and type. Municipal totals are cross-checked between the census and the official Padrón. Optional district or tract breakdown. |
| Net and gross income per person and per household, median per consumption unit, inequality (Gini, P80/P20), income sources, trend, and benchmarks against province, region and Spain. Optional district or tract ranking. |
All tools are read-only. Each result has the same shape:
data the figures
sources publisher, dataset, dataset id, URL, licence, data period, retrieval date
checks suppressed | missing | stale | thin_sample | estimate | cross_check | scope, with severity
confidence high | medium | low: the weakest input sets itExample (abridged) for income("Valladolid", sub_areas="district"):
Household income: Valladolid (municipality, INE code 47186), 2023
Net income per person €16,356, per household €37,466
Inequality: Gini 30.5, P80/P20 2.6
Net income per person by year: 2019 €14,099 ... 2023 €16,356 (+16.0%)
Benchmark Valladolid (province): net per person €15,590; Valladolid is +4.9%
Benchmark España (country): net per person €15,036; Valladolid is +8.8%
Districts: 12 total. Net income per person across them: min €12,884, median €18,466, max €27,922
...
Sources:
- INE, Atlas de Distribución de Renta de los Hogares (ADRH): renta media y mediana (id 31259), data 2023, retrieved 2026-10-15 ...
Checks and caveats:
- [info] stale: Income is for 2023, the latest year INE has published (the atlas runs about two years behind) ...
Confidence: highHow 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.
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.
Install
Requires uv. No API keys are needed.
Claude Code
claude mcp add spain-open-data -- uvx --from git+https://github.com/digvijaysingh-ai/spain-open-data-mcp spain-open-data-mcpClaude Desktop: add this to claude_desktop_config.json and restart Claude.
{
"mcpServers": {
"spain-open-data": {
"command": "uvx",
"args": ["--from", "git+https://github.com/digvijaysingh-ai/spain-open-data-mcp", "spain-open-data-mcp"]
}
}
}A PyPI release (uvx spain-open-data-mcp) and a hosted URL for use as a custom connector in
Claude are planned for milestone 4.
Settings
Variable | Default | Purpose |
|
| On-disk cache of upstream responses |
|
| Seconds per upstream request |
| unset |
|
Architecture
src/spain_open_data_mcp/
server.py MCP tools, resource and error handling
tools/ one module per tool: fetch, check, render
sources/ine/ INE Tempus API client and table definitions
places/ place resolver and INE codes
core/ cached HTTP client, response envelope, errors
data/ bundled municipality list and INE table map (built by scripts/build_reference.py)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.
INE table ids are not hard-coded in the tools.
scripts/build_reference.pyrebuilds the map when INE publishes new tables, for example each October's income atlas.
Development
uv sync
uv run pytest # offline tests against recorded INE responses
uv run ruff check . && uv run ruff format --check . && uv run pyright
uv run spain-open-data-mcp # stdio
uv run spain-open-data-mcp http # Streamable HTTP on 127.0.0.1:8000/mcpRefresh the recorded test fixtures with uv run python tests/fixtures/record_ine.py.
Roadmap
Skeleton (in progress): place resolver,
demographics,income, CI.Supply and catalogue: 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; rental reference index and crime statistics 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, MCP Registry, 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".
Available Tools
3 toolsdemographicsPopulation and householdsARead-onlyIdempotent
Population, sex, five-year age bands, 5-year trend, and household profile for a place in Spain.
Municipality, province and CCAA: census population by age band (latest 1 January),
cross-checked against the official Padrón figure. Municipality, district and tract:
household size, single-person households, mean age, share under 18 and 65+ from INE's
income atlas. Use sub_areas to compare a city's districts or tracts, e.g. to find
where young families or older residents concentrate.
| Name | Required | Description | Default |
|---|---|---|---|
| place | Yes | Place name or INE code: municipality ('Valladolid' or '47186'), province ('41'; a bare name like 'Sevilla' means the city), autonomous community ('Cataluña' or 'CA09'), district ('4718603') or census tract ('4718603012'). 'Name, Province' picks between same-named towns. | |
| sub_areas | No | For a municipality, also list its 'district's or census 'tract's. Default 'none'. | none |
Output Schema
| Name | Required | Description |
|---|---|---|
| data | Yes | |
| checks | No | |
| sources | Yes | |
| confidence | Yes | Lowest confidence across inputs: high, medium or low. Never state a conclusion more strongly than this. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive, open-world behavior, so the bar is lower. The description still adds real context: the underlying sources (census cross-checked against the official Padrón, INE income atlas) and the vintage ('latest 1 January'), which tells the agent how fresh and authoritative the data is.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the returned data and free of filler; the level-by-level breakdown is dense but each clause carries information. It is slightly heavy for the small 2-parameter surface but not padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return formatting need not be explained, and the description covers data content, sources, granularity, and a comparison workflow. The only omission is any hint about ambiguous-name handling or relation to resolve_place, which the schema partially mitigates with the 'Name, Province' convention.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. The description goes beyond the schema by mapping each geographic level to the data it yields (census age bands at municipality/province/CCAA vs. household metrics at district/tract) and by illustrating sub_areas' comparison purpose, adding meaning the enum values alone do not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description enumerates exactly what is returned (population, sex, five-year age bands, 5-year trend, household profile) and constrains it to a place in Spain, so the resource is unambiguous. It does not explicitly name or contrast with its siblings (resolve_place, income), so it falls short of full sibling differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives a concrete use case for sub_areas ('compare a city's districts or tracts... to find where young families or older residents concentrate') and explains which data is available at which geographic level. It does not state when to prefer resolve_place for name disambiguation or when income would be the better tool, so usage context is clear but not exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
incomeHousehold incomeARead-onlyIdempotent
Household income for a place in Spain from INE's income atlas, down to census tract.
Returns net and gross income per person and per household, mean and median per consumption unit, inequality (Gini, P80/P20), income sources (wages, pensions, benefits), a trend, and benchmarks against the province, region and Spain. Use it for purchasing power, price positioning and choosing between districts. Figures are euros of the data year; the atlas runs about two years behind.
| Name | Required | Description | Default |
|---|---|---|---|
| place | Yes | Place name or INE code: municipality ('Valladolid' or '47186'), province ('41'; a bare name like 'Sevilla' means the city), autonomous community ('Cataluña' or 'CA09'), district ('4718603') or census tract ('4718603012'). 'Name, Province' picks between same-named towns. | |
| years | No | Years of history for the trend (max 9). | |
| rank_by | No | Metric used to rank sub-areas. | net_income_per_person |
| sub_areas | No | For a municipality, rank its 'district's or census 'tract's. Default 'none'. | none |
Output Schema
| Name | Required | Description |
|---|---|---|
| data | Yes | |
| checks | No | |
| sources | Yes | |
| confidence | Yes | Lowest confidence across inputs: high, medium or low. Never state a conclusion more strongly than this. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, openWorld), so the description is free to add the genuinely useful operational facts: figures are in euros of the data year and the atlas lags about two years. That data-freshness caveat is exactly the kind of context annotations cannot express. Auth/rate-limit behavior is not covered, keeping it below 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the resource and scope, then use cases and the freshness caveat. The middle sentence enumerates return values fairly exhaustively, which is partly redundant given an output schema exists, but there is no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema, full parameter documentation and rich annotations, the only missing pieces an agent would need are units and data vintage, both of which are supplied. Nothing required to call the tool correctly is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including enum values and the 'Name, Province' disambiguation rule, so the schema carries the parameter burden. The description adds only indirect hints (census tract, trend, ranking districts) and no syntax or default guidance beyond that. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific resource (INE household income for a place in Spain) and its full granularity, down to census tract, plus the exact metric families returned. An agent can distinguish this from demographics (population) and resolve_place (name resolution) without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit use cases are given: purchasing power, price positioning, choosing between districts. There are no stated exclusions or named alternatives (e.g. when to prefer demographics or to resolve_place first), so it stops short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
resolve_placeResolve a Spanish placeARead-onlyIdempotent
Find the INE code and level (CCAA, province, municipality, district, tract) for a place in Spain.
Use this first when a question names a place. Returns the best match first, with
alternatives (e.g. 'Valencia' is both a city and a province). Handles accents, Basque,
Catalan and Galician names, and common English names (Seville, Catalonia).
Neighbourhood names are not supported; use districts or tracts from `demographics`
or `income` with `sub_areas`.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Place name or INE code, e.g. 'A Coruña', 'Mérida, Badajoz', '08'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| data | Yes | |
| checks | No | |
| sources | Yes | |
| confidence | Yes | Lowest confidence across inputs: high, medium or low. Never state a conclusion more strongly than this. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, closed-world), so extra credit goes to the description's added behavior: best match returned first with alternatives for ambiguous names, multilingual/accents handling, and the neighbourhood limitation. Return format is delegated to the existing output schema, so 4 rather than 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the outcome, followed by the highest-priority usage rule, then edge cases. Every sentence carries distinct information with no padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter read tool with full schema coverage, complete annotations, and an output schema, the description covers ambiguity, scope limits, and language handling. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the param description already documents that 'query' accepts a place name or an INE code with examples. The description reinforces ambiguity handling ('Valencia' is both city and province) but adds no syntax or format detail beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (find/resolve) and resource (INE code and level for a Spanish place), and enumerates the exact levels returned (CCAA, province, municipality, district, tract). An agent can distinguish it from the sibling data tools without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says 'Use this first when a question names a place', and routes unsupported work elsewhere: 'Neighbourhood names are not supported; use districts or tracts from `demographics` or `income` with `sub_areas`.' This names both when-to-use and when-not with a concrete alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
demographics - First observed
income - First observed
resolve_place
TDQS
Scored across 3 tools
Each tool targets a clearly distinct concern: resolve_place does code/level lookup, demographics returns population/household data, and income returns economic data. Descriptions explicitly explain when to use each and how they chain, leaving little room for misselection.
Names mix conventions: resolve_place is a verb_noun pattern while demographics and income are bare nouns. It is still readable and each name is self-explanatory, but there is no single predictable pattern.
Three tools is on the thin side for a server branded as covering Spanish open data broadly, though it is coherent for a focused socioeconomic surface. Each tool earns its place but the set feels lean.
The resolve_place → demographics/income chain gives a complete workflow for place-based socioeconomic queries, with sub_areas supporting drill-down. Gaps exist for other open-data domains (housing, employment, health), but the stated socioeconomic purpose is well covered.
Maintenance
Related MCP Connectors
Europe's population layer: demographics, spending and market data for every place, down to 1 km².
Data Commons MCP — harmonised public statistics for places, from Google Data Commons (REST v2).
Portuguese address autocomplete, postal codes, list checks; typo-tolerant, municipality-first; free.
Macroeconomic and other official data from 170+ publishers, resolved from natural language with provenance.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables querying and analyzing over 90,000 public datasets from the Spanish Government Open Data Portal (datos.gob.es) using natural language, with tools for search, filtering, metadata access, and SPARQL queries.105MIT
- AlicenseNot gradedqualityBmaintenanceMCP server providing access to Spain's National Statistics Institute (INE) Tempus3 JSON API, enabling querying of Spanish statistical data through natural language.22 npmMIT
- AlicenseNot gradedqualityBmaintenanceProvides access to official U.S. Census Bureau data, including decennial census, American Community Survey, economic census, population estimates, and housing characteristics, across geographic levels from nation to block group. Enables natural-language queries and direct tool calls for demographic and housing analysis.245 npmMIT
- FlicenseNot gradedqualityBmaintenanceEnables querying demographic, spending, and market data for every place in Europe, offering tools to analyze markets, describe areas, reach audiences, compare and rank places, sample personas, resolve locations, and run structured population queries—all as privacy-protected aggregates.-