io.github.cyanheads/eurostat-mcp-server
Search and query the Eurostat catalogue — EU economy, demography, trade, and NUTS regional data.
What can you do with io.github.cyanheads/eurostat-mcp-server?
- The Statistics API (JSON-stat 2.0) for slices, the SDMX 2.1 TSV bulk endpoint for whole datasets at roughly half the bytes, and the catalogue TOC cached in memory for 12 hours
- The Comext host's
DS-*collections (detailed trade by CN8, HS, SITC, BEC and CPA, and PRODCOM) through the same tools. Filter every request,freqincluded, since trade flows mix annual and monthly series; aggregates such asTOTAL,EU27_2020, and `EX... - NUTS geo-level filtering (
aggregate/country/nuts1/nuts2/nuts3), and OBS_FLAG and CONF_STATUS decoded into separate fields - Too-large-to-serve responses come back as non-retryable
async_response,extraction_too_big, orasync_queuederrors that name the dimensions left to filter - A query match above 5,000 observations, or any bulk download, is staged whole as a SQL table (
canvasId/tableName/stagedRowCount) on the DuckDB dataframe canvas, which is on by default. The.mcpbbundle strips the native DuckDB binding and runs w... - Typed error contracts: every declared failure carries a
reasonand arecovery.hintnaming the next tool to call - Next-step hints:
eurostat_search_datasetsandeurostat_browse_themesreturn anextSteppointing at the follow-up call - Unknown stays unknown: counts and period bounds Eurostat doesn't report (
obsCount,timeRange.start/end,lastUpdated) are omitted rather than zeroed
Install
Add this to your client's MCP configuration (Claude Desktop: claude_desktop_config.json; Cursor: .cursor/mcp.json; VS Code and others use the same "mcpServers" shape).
claude mcp add --transport http eurostat-mcp-server 'https://eurostat.caseyjhand.com/mcp'
{
"mcpServers": {
"eurostat-mcp-server": {
"type": "http",
"url": "https://eurostat.caseyjhand.com/mcp"
}
}
}
@cyanheads/eurostat-mcp-serverclaude mcp add eurostat-mcp-server -e 'EUROSTAT_CLAMP_LIMITS=true' -e 'MCP_LOG_LEVEL=info' -e 'EUROSTAT_BASE_URL=https://ec.europa.eu/eurostat/api/dissemination' -e 'EUROSTAT_COMEXT_BASE_URL=https://ec.europa.eu/eurostat/api/comext/dissemination' -e 'EUROSTAT_REQUEST_TIMEOUT_MS=30000' -e 'EUROSTAT_METADATA_TIMEOUT_MS=120000' -e 'EUROSTAT_TOC_CACHE_TTL_MS=43200000' -e 'EUROSTAT_BULK_TIMEOUT_MS=120000' -e 'EUROSTAT_BULK_MAX_BYTES=52428800' -e 'CANVAS_PROVIDER_TYPE=duckdb' -e 'CANVAS_TEMP_PATH=<CANVAS_TEMP_PATH>' -e 'CANVAS_TTL_MS=86400000' -e 'CANVAS_DEFAULT_ROW_LIMIT=10000' -e 'EUROSTAT_DATAFRAME_DROP_ENABLED=false' -- npx -y @cyanheads/eurostat-mcp-server@0.11.1
{
"mcpServers": {
"eurostat-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/eurostat-mcp-server@0.11.1"
],
"env": {
"EUROSTAT_CLAMP_LIMITS": "true",
"MCP_LOG_LEVEL": "info",
"EUROSTAT_BASE_URL": "https://ec.europa.eu/eurostat/api/dissemination",
"EUROSTAT_COMEXT_BASE_URL": "https://ec.europa.eu/eurostat/api/comext/dissemination",
"EUROSTAT_REQUEST_TIMEOUT_MS": "30000",
"EUROSTAT_METADATA_TIMEOUT_MS": "120000",
"EUROSTAT_TOC_CACHE_TTL_MS": "43200000",
"EUROSTAT_BULK_TIMEOUT_MS": "120000",
"EUROSTAT_BULK_MAX_BYTES": "52428800",
"CANVAS_PROVIDER_TYPE": "duckdb",
"CANVAS_TEMP_PATH": "<CANVAS_TEMP_PATH>",
"CANVAS_TTL_MS": "86400000",
"CANVAS_DEFAULT_ROW_LIMIT": "10000",
"EUROSTAT_DATAFRAME_DROP_ENABLED": "false"
}
}
}
}
Environment variables
EUROSTAT_CLAMP_LIMITSClamp positive integer preview/search/dimension limits to 500/100/1000 with a notice. False restores schemas rejecting over-cap inputs.MCP_LOG_LEVELSets the minimum log level for output (e.g., 'debug', 'info', 'warn').EUROSTAT_BASE_URLEurostat API base URL. Override when using a mirror or proxy.EUROSTAT_COMEXT_BASE_URLBase URL of the Comext dissemination host, which serves every DS-* dataset code (detailed trade and PRODCOM). Override when using a mirror or proxy.EUROSTAT_REQUEST_TIMEOUT_MSTotal allowance for one Eurostat request across retries, backoff and body consumption, in milliseconds.EUROSTAT_METADATA_TIMEOUT_MSTotal allowance for one dataset's SDMX dataflow structure across retries, backoff and body consumption, in milliseconds. Defaults to 120000; separate from EUROSTAT_REQUEST_TIMEOUT_MS for large structures.EUROSTAT_TOC_CACHE_TTL_MSHow long a fetched catalogue TOC stays usable before the next catalogue call refreshes it, in milliseconds. Defaults to 12 hours.EUROSTAT_BULK_TIMEOUT_MSTotal allowance for one eurostat_download_dataset transfer across request retries, backoff and streaming body consumption, in milliseconds. Defaults to 120000.EUROSTAT_BULK_MAX_BYTESByte budget for one eurostat_download_dataset transfer, counted on the decoded TSV rather than on the wire. Eurostat sends the body chunked with no Content-Length, so the budget is enforced while streaming and the transfer is aborted when it is reached. Defaults to 50 MiB.CANVAS_PROVIDER_TYPEDataframe canvas backend. The default, 'duckdb', lists eurostat_dataframe_describe and eurostat_dataframe_query, stages query matches with stage=true or above 5,000 observations, and retains bulk downloads beyond their previews. Set to 'none' to disable the canvas and those tools. The DuckDB binding ships with the server.CANVAS_TEMP_PATHParent directory for DuckDB canvas spill files: a private 'mcp-canvas-XXXXXX' directory is created inside it, so it must be writable by the server process. Defaults to the OS temp directory.CANVAS_TTL_MSSliding lifetime of a staged dataframe canvas in milliseconds; every call against it extends the window. Defaults to 24 hours.CANVAS_DEFAULT_ROW_LIMITMaximum complete rows a single eurostat_dataframe_query returns before reporting truncated.EUROSTAT_DATAFRAME_DROP_ENABLEDSet to 'true' to list eurostat_dataframe_drop, which removes one staged table from a dataframe canvas and leaves its other tables in place. Off by default because a drop cannot be undone; staged tables expire with their canvas either way. Has no effect with CANVAS_PROVIDER_TYPE=none.
@cyanheads/eurostat-mcp-serverclaude mcp add eurostat-mcp-server -e 'MCP_TRANSPORT_TYPE=<MCP_TRANSPORT_TYPE>' -e 'EUROSTAT_CLAMP_LIMITS=true' -e 'MCP_HTTP_HOST=127.0.0.1' -e 'MCP_HTTP_PORT=3010' -e 'MCP_HTTP_ENDPOINT_PATH=/mcp' -e 'MCP_PUBLIC_URL=<MCP_PUBLIC_URL>' -e 'MCP_AUTH_MODE=none' -e 'MCP_LOG_LEVEL=info' -e 'EUROSTAT_BASE_URL=https://ec.europa.eu/eurostat/api/dissemination' -e 'EUROSTAT_COMEXT_BASE_URL=https://ec.europa.eu/eurostat/api/comext/dissemination' -e 'EUROSTAT_REQUEST_TIMEOUT_MS=30000' -e 'EUROSTAT_METADATA_TIMEOUT_MS=120000' -e 'EUROSTAT_TOC_CACHE_TTL_MS=43200000' -e 'EUROSTAT_BULK_TIMEOUT_MS=120000' -e 'EUROSTAT_BULK_MAX_BYTES=52428800' -e 'CANVAS_PROVIDER_TYPE=duckdb' -e 'CANVAS_TEMP_PATH=<CANVAS_TEMP_PATH>' -e 'CANVAS_TTL_MS=86400000' -e 'CANVAS_DEFAULT_ROW_LIMIT=10000' -e 'EUROSTAT_DATAFRAME_DROP_ENABLED=false' -- npx -y @cyanheads/eurostat-mcp-server@0.11.1
{
"mcpServers": {
"eurostat-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/eurostat-mcp-server@0.11.1"
],
"env": {
"MCP_TRANSPORT_TYPE": "<MCP_TRANSPORT_TYPE>",
"EUROSTAT_CLAMP_LIMITS": "true",
"MCP_HTTP_HOST": "127.0.0.1",
"MCP_HTTP_PORT": "3010",
"MCP_HTTP_ENDPOINT_PATH": "/mcp",
"MCP_PUBLIC_URL": "<MCP_PUBLIC_URL>",
"MCP_AUTH_MODE": "none",
"MCP_LOG_LEVEL": "info",
"EUROSTAT_BASE_URL": "https://ec.europa.eu/eurostat/api/dissemination",
"EUROSTAT_COMEXT_BASE_URL": "https://ec.europa.eu/eurostat/api/comext/dissemination",
"EUROSTAT_REQUEST_TIMEOUT_MS": "30000",
"EUROSTAT_METADATA_TIMEOUT_MS": "120000",
"EUROSTAT_TOC_CACHE_TTL_MS": "43200000",
"EUROSTAT_BULK_TIMEOUT_MS": "120000",
"EUROSTAT_BULK_MAX_BYTES": "52428800",
"CANVAS_PROVIDER_TYPE": "duckdb",
"CANVAS_TEMP_PATH": "<CANVAS_TEMP_PATH>",
"CANVAS_TTL_MS": "86400000",
"CANVAS_DEFAULT_ROW_LIMIT": "10000",
"EUROSTAT_DATAFRAME_DROP_ENABLED": "false"
}
}
}
}
Environment variables
MCP_TRANSPORT_TYPESelects the HTTP transport.EUROSTAT_CLAMP_LIMITSClamp positive integer preview/search/dimension limits to 500/100/1000 with a notice. False restores schemas rejecting over-cap inputs.MCP_HTTP_HOSTThe hostname for the HTTP server.MCP_HTTP_PORTThe port to run the HTTP server on.MCP_HTTP_ENDPOINT_PATHThe endpoint path for the MCP server.MCP_PUBLIC_URLPublic origin override for deployments behind a TLS-terminating reverse proxy (e.g. https://mcp.example.com).MCP_AUTH_MODEAuthentication mode to use: 'none', 'jwt', or 'oauth'.MCP_LOG_LEVELSets the minimum log level for output (e.g., 'debug', 'info', 'warn').EUROSTAT_BASE_URLEurostat API base URL. Override when using a mirror or proxy.EUROSTAT_COMEXT_BASE_URLBase URL of the Comext dissemination host, which serves every DS-* dataset code (detailed trade and PRODCOM). Override when using a mirror or proxy.EUROSTAT_REQUEST_TIMEOUT_MSTotal allowance for one Eurostat request across retries, backoff and body consumption, in milliseconds.EUROSTAT_METADATA_TIMEOUT_MSTotal allowance for one dataset's SDMX dataflow structure across retries, backoff and body consumption, in milliseconds. Defaults to 120000; separate from EUROSTAT_REQUEST_TIMEOUT_MS for large structures.EUROSTAT_TOC_CACHE_TTL_MSHow long a fetched catalogue TOC stays usable before the next catalogue call refreshes it, in milliseconds. Defaults to 12 hours.EUROSTAT_BULK_TIMEOUT_MSTotal allowance for one eurostat_download_dataset transfer across request retries, backoff and streaming body consumption, in milliseconds. Defaults to 120000.EUROSTAT_BULK_MAX_BYTESByte budget for one eurostat_download_dataset transfer, counted on the decoded TSV rather than on the wire. Eurostat sends the body chunked with no Content-Length, so the budget is enforced while streaming and the transfer is aborted when it is reached. Defaults to 50 MiB.CANVAS_PROVIDER_TYPEDataframe canvas backend. The default, 'duckdb', lists eurostat_dataframe_describe and eurostat_dataframe_query, stages query matches with stage=true or above 5,000 observations, and retains bulk downloads beyond their previews. Set to 'none' to disable the canvas and those tools. The DuckDB binding ships with the server.CANVAS_TEMP_PATHParent directory for DuckDB canvas spill files: a private 'mcp-canvas-XXXXXX' directory is created inside it, so it must be writable by the server process. Defaults to the OS temp directory.CANVAS_TTL_MSSliding lifetime of a staged dataframe canvas in milliseconds; every call against it extends the window. Defaults to 24 hours.CANVAS_DEFAULT_ROW_LIMITMaximum complete rows a single eurostat_dataframe_query returns before reporting truncated.EUROSTAT_DATAFRAME_DROP_ENABLEDSet to 'true' to list eurostat_dataframe_drop, which removes one staged table from a dataframe canvas and leaves its other tables in place. Off by default because a drop cannot be undone; staged tables expire with their canvas either way. Has no effect with CANVAS_PROVIDER_TYPE=none.
Documentation
From the project's README on GitHub, refreshed weekly. Links open on GitHub.
Contents
@cyanheads/eurostat-mcp-server
Search and query the Eurostat catalogue — EU economy, demography, trade, health, and NUTS regional data via MCP. STDIO or Streamable HTTP.
6 Tools (up to 9 with the dataframe canvas) • 1 Resource
Public Hosted Server: https://eurostat.caseyjhand.com/mcp
Overview
EU statistics from Eurostat: economy, demography, trade, health, and NUTS regional data, including detailed Comext trade and PRODCOM industrial production. Search or browse the catalogue, inspect a dataset's dimensions, then query a slice or bulk-download the whole thing.
Tools
The three eurostat_dataframe_* tools need the dataframe canvas, which is on by default and off with CANVAS_PROVIDER_TYPE=none; eurostat_dataframe_drop also needs EUROSTAT_DATAFRAME_DROP_ENABLED=true.
| Tool | Description |
|---|---|
eurostat_search_datasets | Search the Eurostat catalogue by keyword — returns codes, descriptions, period coverage, and theme breadcrumbs |
eurostat_browse_themes | Navigate the Eurostat theme hierarchy — list root themes or drill into subthemes and datasets |
eurostat_get_dataset_info | Fetch dataset metadata: dimensions with sample values, time range, observation count, and last-update date |
eurostat_get_dimension_values | List all valid codes for one dataset dimension, paged, with a code/label word filter and NUTS hierarchy filtering for geo |
eurostat_query_dataset | Fetch a bounded preview of decoded observations with dimension filters, NUTS geo-level, and time-range controls |
eurostat_download_dataset | Download a whole dataset via the SDMX bulk endpoint and stage every observation on the dataframe canvas |
eurostat_dataframe_describe | List the tables staged on a dataframe canvas, with row counts and column types |
eurostat_dataframe_query | Run a read-only SQL SELECT across staged tables |
eurostat_dataframe_drop | Remove one staged table from a dataframe canvas, leaving its other tables in place (opt-in) |
Resources
| Resource | Description |
|---|---|
eurostat://dataset/{dataset_code} | Dataset metadata (dimensions, time range, observation count, last-updated) by URI |
The same metadata is available through eurostat_get_dataset_info for tool-only clients.
Capability reference
eurostat_search_datasets tool
querywords are ANDed case-insensitively across each dataset's label, theme breadcrumb, and code, ignoring spacing beside punctuation (Rev.2findsRev. 2);limitis a positive integer (default 20), clamped to 100 with a notice. Page by passingnextCursorback ascursor; cursor calls ignorelimit- Results carry
code,label,type(dataset/table), period coverage,obsCount,lastUpdated, andthemePath, withtotalMatchesacross every page; fails asno_match(naming the words whose removal finds matches),invalid_cursor, or a retryableupstream_faultwhen no catalogue is loaded and Eurostat's table of contents is unreachable - If the Comext dataflow list cannot be loaded, results carry
comextListMissingand anotice; a catalogue that could not be refreshed carriescatalogueLoadedAt
eurostat_browse_themes tool
- Omit
theme_codefor the top-level theme folders; pass a folder code for its immediate children. The Comext collections sit underext_go_detail(detailed trade, inext_go) andprom(PRODCOM, inicts) - Items carry
code,label,type(folder/dataset/table), andhasChildren, with aparentPathbreadcrumb andotherPlacementsfor a folder filed in several branches; fails asnot_foundfor an unknown code,not_a_folderfor a dataset or table code, or a retryableupstream_faultwhen no catalogue is loaded and Eurostat's table of contents is unreachable - If the Comext dataflow list cannot be loaded,
ext_goandictscarrycomextListMissingand anotice; a catalogue that could not be refreshed carriescatalogueLoadedAt
eurostat_get_dataset_info tool
- One
dataset_code; returns every dimension withvaluesCountand up to 10sampleValues, plustimeRange,obsCount,lastUpdated, and the ESMSmetadataUrl - Fails as
not_foundfor an unknown dataset, orupstream_faultwhen Eurostat returns a structure the server cannot read (retryable when the endpoint is down)
eurostat_get_dimension_values tool
dataset_codeplusdimension; forgeo,geo_levelpicks a NUTS level (aggregate,countryby default,nuts1,nuts2,nuts3).filterkeeps values whose code and label contain every word given, ignoring case and accents (palma frankfurtfindsDE_EDDF_ES_LEPA), counted inmatchCount- Long lists come back in pages of up to 64,000 bytes (or an optional
limit), withtotalCountfor every value; a page with more after it carriestruncated,nextCursor(pass back ascursor), and anotice. Fails asnot_found,no_results,conflicting_params,invalid_cursor,upstream_fault(retryable when the endpoint is down), orcanvas_not_found canvas_idstages every value on that canvas as acode/labeltable (canvasId/tableName/stagedRowCount) that labels a download's code columns through a join; it never starts a canvas of its own
eurostat_query_dataset tool
dataset_codeplusfilters({dimension_code: [values]}),geo_level, and eithersince_period/until_period(2020,2020-Q1,2020-01, down toYYYY-WnnandYYYY-Dnnn) orlast_n_periods;preview_limitis a positive integer (default 50), clamped to 500 with a notice;langisEN,FR, orDE- Each observation carries code/label pairs per dimension, a nullable
value,status(OBS_FLAG), andconfStatus(CONF_STATUS);obsCount,missingObsCount, andtimeRangecover the whole match,truncatedis true above 5,000 observations, andunmatchedValuesnames filter values that matched nothing - Fails as
not_found,no_results(naming the unmatched values and the periods with no value),invalid_dimension,invalid_period,conflicting_params, a non-retryableasync_responsefor a query too large to serve,canvas_not_found, or a retryableupstream_faultwhen the Statistics API is down
eurostat_download_dataset tool
dataset_codeplus the samefiltersmap andsince_period/until_period(nolast_n_periods: the TSV keeps a column per period unless a range drops it);preview_limitis a positive integer (default 50), clamped to 500 with a notice- Returns
rowCount(echoed astotalCount),missingCount,periodRange, andbytesReadfor the whole download; fails asnot_found,invalid_dimension,invalid_period,filter_arity, a non-retryableasync_queuedorextraction_too_big,no_results,upstream_fault(retryable when the SDMX endpoint is down), orcanvas_not_found - The
EUROSTAT_BULK_MAX_BYTESbudget (default 50 MiB decoded) retains complete observations within the permitted prefix and returnsbudgetExceeded: true. A budget too small for a complete header and usable observation fails asbyte_budget_exceeded, with the configured limit and narrowing guidance.bytesReadcounts accepted decoded bytes, never more than the budget; incomplete trailing rows are discarded.
eurostat_dataframe_describe tool
canvas_idfrom a staging response; lists each table'sname,rowCount, typed columns, andexpiresAt(slidingCANVAS_TTL_MS, default 24h); fails ascanvas_not_foundorcanvas_disabled- Columns depend on the staging tool:
eurostat_query_datasetwrites a code and a_labelcolumn per dimension,eurostat_download_datasetcodes only plustime(both share theobs_*andconf_status*measures), andeurostat_get_dimension_valueswritescodeandlabel
eurostat_dataframe_query tool
- One read-only
SELECTper call; chained statements, other verbs, and functions that read files or external data are rejected - Returns
columns,rows,rowCount, and required booleantruncated. Whole rows fit withinCANVAS_DEFAULT_ROW_LIMIT(default 10,000) and 64,000 bytes independently for JSON-serializedstructuredContentandcontent[], including enrichment. At least one complete row is retained even if oversized. Cells render as lossless JSON, including nesting and quoted strings; 64-bit integers arrive as strings. Fails asmissing_table,canvas_not_found, orcanvas_disabled - A cut sets
truncated: trueand adds a notice. Page an unchanged deterministic SELECT with a totalORDER BY(tie-breakers required),LIMITandOFFSET; advance by actualrowCount. Restart an initially unordered result sorted at offset zero. Volatile SQL and changing or expired tables cannot provide stable paging
eurostat_dataframe_drop tool
canvas_idplustable_name, exactly aseurostat_dataframe_describelists it; removes that one table and leaves the canvas and its other tables in place. Listed only whenEUROSTAT_DATAFRAME_DROP_ENABLED=trueis set beside the canvas- Returns
canvasId,tableName,dropped, andexpiresAt;droppedisfalse, with anotice, when nothing by that name was staged, so repeating a drop is safe. Fails ascanvas_not_found,identifier_reserved, orcanvas_disabled
eurostat://dataset/{dataset_code} resource
- Same payload as
eurostat_get_dataset_info, returned asapplication/json; a failed read carries the samereason, andretryableon an outage dataset_codecomes fromeurostat_search_datasetsoreurostat_browse_themes
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Eurostat-specific:
- The Statistics API (JSON-stat 2.0) for slices, the SDMX 2.1 TSV bulk endpoint for whole datasets at roughly half the bytes, and the catalogue TOC cached in memory for 12 hours
- The Comext host's
DS-*collections (detailed trade by CN8, HS, SITC, BEC and CPA, and PRODCOM) through the same tools. Filter every request,freqincluded, since trade flows mix annual and monthly series; aggregates such asTOTAL,EU27_2020, andEXT_EU27_2020double-count when summed with their members. PRODCOM text values arrive invalueText/obs_value_text - NUTS geo-level filtering (
aggregate/country/nuts1/nuts2/nuts3), and OBS_FLAG and CONF_STATUS decoded into separate fields - Too-large-to-serve responses come back as non-retryable
async_response,extraction_too_big, orasync_queuederrors that name the dimensions left to filter - A query match above 5,000 observations, or any bulk download, is staged whole as a SQL table (
canvasId/tableName/stagedRowCount) on the DuckDB dataframe canvas, which is on by default. The.mcpbbundle strips the native DuckDB binding and runs withCANVAS_PROVIDER_TYPE=none, so SQL analytics need the npm, Docker, or from-source install
Agent-friendly output:
- Typed error contracts: every declared failure carries a
reasonand arecovery.hintnaming the next tool to call - Next-step hints:
eurostat_search_datasetsandeurostat_browse_themesreturn anextSteppointing at the follow-up call - Unknown stays unknown: counts and period bounds Eurostat doesn't report (
obsCount,timeRange.start/end,lastUpdated) are omitted rather than zeroed - Effective-query echo:
appliedFiltersoneurostat_query_datasetandappliedQuery(with the SDMXurl) oneurostat_download_dataset, plus anoticewhen the preview omits rows
Getting started
Public Hosted Instance
A public instance is available at https://eurostat.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"eurostat-mcp-server": {
"type": "streamable-http",
"url": "https://eurostat.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"eurostat-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/eurostat-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"eurostat-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/eurostat-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"eurostat-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/eurostat-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- No API key: Eurostat's dissemination API and its Comext host are public.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/eurostat-mcp-server.git
- Navigate into the directory:
cd eurostat-mcp-server
- Install dependencies:
bun install
- Configure environment (optional, every setting has a default):
cp .env.example .env
Configuration
| Variable | Description | Default |
|---|---|---|
EUROSTAT_BASE_URL | Eurostat API base URL. | https://ec.europa.eu/eurostat/api/dissemination |
EUROSTAT_COMEXT_BASE_URL | Comext dissemination host, which serves every DS-* dataset code (detailed trade and PRODCOM). | https://ec.europa.eu/eurostat/api/comext/dissemination |
EUROSTAT_REQUEST_TIMEOUT_MS | Total allowance for one request, including retries, backoff and body reads, in ms. | 30000 |
EUROSTAT_METADATA_TIMEOUT_MS | Total allowance for one dataset's SDMX dataflow structure, including retries, backoff and body reads, in ms. | 120000 (2 min) |
EUROSTAT_TOC_CACHE_TTL_MS | Catalogue TOC cache lifetime, in ms. | 43200000 (12 h) |
EUROSTAT_BULK_TIMEOUT_MS | Total allowance for one bulk transfer, including request retries, backoff and streaming, in ms. | 120000 (2 min) |
EUROSTAT_BULK_MAX_BYTES | Byte budget for one bulk download. | 52428800 (50 MiB) |
CANVAS_PROVIDER_TYPE | Dataframe canvas backend; none disables the canvas and its tools. The .mcpb bundle sets none. | duckdb |
CANVAS_TTL_MS | Sliding lifetime of a staged canvas, in ms. | 86400000 (24 h) |
CANVAS_DEFAULT_ROW_LIMIT | Max rows one eurostat_dataframe_query returns before reporting truncated; a separate 64,000-byte budget applies to each serialized response surface, retaining at least one complete row. | 10000 |
EUROSTAT_CLAMP_LIMITS | Clamp positive integer preview/search limits to 500/100 with a notice. false restores schemas rejecting over-cap inputs; zero, negatives, fractions and nonnumeric inputs are always rejected. Each clamp logs once and increments eurostat.limit_clamps, labelled by tool. | true |
EUROSTAT_DATAFRAME_DROP_ENABLED | true lists eurostat_dataframe_drop; no effect with CANVAS_PROVIDER_TYPE=none. | false |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. The server declares stateless; a set value overrides it. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). The Docker image sets info. | debug |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | false |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
- Build and run the production version:
# One-time build bun run rebuild # Run the built server bun run start:http # or bun run start:stdio - Run checks and tests:
bun run devcheck # Lints, formats, type-checks, and more bun run test # Runs the test suite bun run lint:mcp # Validates MCP definitions against spec
Docker
docker build -t eurostat-mcp-server .
docker run --rm -p 3010:3010 eurostat-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/eurostat-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools and the resource, inits services, gates the dataframe tools on the canvas and the drop tool on its own flag. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Six discovery and data tools, plus three canvas-gated dataframe tools, one of them opt-in. |
src/mcp-server/resources | Resource definitions. Dataset metadata resource. |
src/services/eurostat-catalogue | Catalogue service — fetches and parses the Eurostat TOC, merges the Comext dataflow list into it, with a TTL-bounded in-memory cache. |
src/services/eurostat-data | Statistics API service — SDMX metadata parsing with a per-dataset cache, JSON-stat 2.0 decoding, async-response detection, canvas row source. |
src/services/eurostat-bulk | SDMX 2.1 TSV bulk service — streaming download, byte budget, SOAP fault mapping, canvas row source. |
src/services/eurostat-codelists.ts | OBS_FLAG and CONF_STATUS codelists, the measure column names both stagers write, and the text-value decoder. |
src/services/eurostat-hosts.ts | Which host serves a dataset code: DS-* to the Comext host, everything else to the main one. |
src/services/canvas-accessor.ts | Accessor for the optional DataCanvas and the acquire helper canvas-touching tools share. |
src/services/shared-load.ts | Lets concurrent callers await one shared download, so one caller's cancellation releases only that caller. |
src/config | Server-specific environment variable parsing and validation with Zod. |
tests/ | Unit and integration tests, mirroring the src/ structure. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor logging,ctx.statefor storage - Register new tools and resources in the
createApp()arrays insrc/index.ts - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.
Frequently asked questions
What is io.github.cyanheads/eurostat-mcp-server?
Search and query the Eurostat catalogue — EU economy, demography, trade, and NUTS regional data.
How do I install io.github.cyanheads/eurostat-mcp-server?
It runs as a remote server. In Claude Code: claude mcp add --transport http eurostat-mcp-server 'https://eurostat.caseyjhand.com/mcp'. For other clients, paste the JSON configuration from this page into their MCP settings.
Does io.github.cyanheads/eurostat-mcp-server need an API key?
Its registry entry lists no required key or secret. Some features may still need an account with the service it connects to.
Is io.github.cyanheads/eurostat-mcp-server open source?
Yes. Its code is on GitHub under the Apache-2.0 license.
Who maintains io.github.cyanheads/eurostat-mcp-server?
It is published by cyanheads. AI.tn lists it but does not maintain or endorse it.
From the official MCP Registry. Listing is not an endorsement: review a server's code and permissions before giving it access to your data.