Skip to main content
Glama
cyanheads

eurostat-mcp-server

by cyanheads

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

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

skill://eurostat-guide/SKILL.md

Statistical comparison gates, identifier provenance, and SQL analysis workflows

The same metadata is available through eurostat_get_dataset_info for tool-only clients.

Agent skill

eurostat-guide covers country and regional comparisons, Comext aggregation, missing-data interpretation, and SQL joins over staged observations. It ships with plugin, npm, Docker, and MCPB installs and is served over MCP at skill://eurostat-guide/SKILL.md; supporting SQL examples are included under the skill's references/ directory.

Related MCP server: eurostat-mcp

Capability reference

eurostat_search_datasets tool

  • query words are ANDed case-insensitively across each dataset's label, theme breadcrumb, and code, ignoring spacing beside punctuation (Rev.2 finds Rev. 2); limit is a positive integer (default 20), clamped to 100 with a notice. Page by passing nextCursor back as cursor; cursor calls ignore limit

  • Results carry code, label, type (dataset / table), period coverage, obsCount, lastUpdated, and themePath, with totalMatches across every page; fails as no_match (naming the words whose removal finds matches), invalid_cursor, or a retryable upstream_fault when no catalogue is loaded and Eurostat's table of contents is unreachable

  • If the Comext dataflow list cannot be loaded, results carry comextListMissing and a notice; a catalogue that could not be refreshed carries catalogueLoadedAt


eurostat_browse_themes tool

  • Omit theme_code for the top-level theme folders; pass a folder code for its immediate children. Codes resolve exact-first, then case-insensitively. The Comext collections sit under ext_go_detail (detailed trade, in ext_go) and prom (PRODCOM, in icts)

  • Items carry code, label, type (folder / dataset / table), and hasChildren, with a parentPath breadcrumb and otherPlacements for a folder filed in several branches; fails as not_found for an unknown code, not_a_folder for a dataset or table code, or a retryable upstream_fault when no catalogue is loaded and Eurostat's table of contents is unreachable

  • If the Comext dataflow list cannot be loaded, ext_go and icts carry comextListMissing and a notice; a catalogue that could not be refreshed carries catalogueLoadedAt


eurostat_get_dataset_info tool

  • One dataset_code; returns every dimension with valuesCount and up to 10 sampleValues, plus timeRange, obsCount, lastUpdated, and the ESMS metadataUrl

  • Fails as not_found for an unknown dataset, or upstream_fault when Eurostat returns a structure the server cannot read (retryable when the endpoint is down)


eurostat_get_dimension_values tool

  • dataset_code plus dimension, resolved exact-first then case-insensitively (TIME_PERIOD aliases time); returned value codes and labels stay unchanged. For geo, geo_level picks a NUTS level (aggregate, country by default, nuts1, nuts2, nuts3). filter keeps values whose code and label contain every word given, ignoring case and accents (palma frankfurt finds DE_EDDF_ES_LEPA), counted in matchCount

  • limit defaults to 25 and clamps to 1,000 with a notice. Pages contain that many values, fewer at the end, with totalCount for the full list; a page with more after it carries truncated, nextCursor (pass back as cursor), and a notice. Continuations retain the effective count and ignore a new limit; older cursors above 1,000 normalize to 1,000 with disclosure. Fails as not_found, no_results, conflicting_params, invalid_cursor, upstream_fault (retryable when the endpoint is down), or canvas_not_found

  • canvas_id stages every value on that canvas as a code / label table (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_code plus filters ({dimension_code: [values]}), geo_level, and either since_period / until_period (2020, 2020-Q1, 2020-01, down to YYYY-Wnn and YYYY-Dnnn) or last_n_periods; preview_limit is a positive integer (default 25), clamped to 500 with a notice; lang is EN, FR, or DE

  • stage: true stages the whole match on an available canvas, regardless of size; reuse canvas_id or omit it to create one. Without it, automatic staging starts above 5,000 observations. truncated still describes that threshold; tableName identifies a staged result. Canvas-disabled deployments disclose an explicit staging request as unavailable

  • last_n_periods counts dataset time positions, which can include periods with no populated cells. A notice names gaps across the full match (up to the newest 24, with an omitted count); increase N or replace it with until_period to reach earlier observations

  • Eurostat keeps only periods wholly inside since_period–until_period, per frequency. A notice names a period a bound falls inside and the bound that includes it. A one-day range goes out as time=<date>, because Eurostat ignores one-day bounds

  • Each observation carries code/label pairs per dimension, a nullable value, status (OBS_FLAG), and confStatus (CONF_STATUS); obsCount, missingObsCount, and timeRange cover the whole match, truncated is true above 5,000 observations, and unmatchedValues names filter values that matched nothing

  • Fails as not_found, no_results (naming the unmatched values, the periods with no value, why a range selected no period, or geo_level as the likely cause when Eurostat's reply says nothing more), invalid_dimension, invalid_period, conflicting_params, a non-retryable async_response for a query too large to serve, canvas_not_found, or a retryable upstream_fault when the Statistics API is down


eurostat_download_dataset tool

  • dataset_code plus the same filters map and since_period / until_period (no last_n_periods: the TSV keeps a column per period unless a range drops it); preview_limit is a positive integer (default 25), clamped to 500 with a notice

  • The wholly-inside rule and its notice apply here too. A one-day range requests that day and the next, and the later day's column is dropped before anything is counted or staged

  • Returns rowCount (echoed as totalCount), missingCount, periodRange, and bytesRead for the whole download; fails as not_found, invalid_dimension, invalid_period, filter_arity, a non-retryable async_queued or extraction_too_big, no_results, upstream_fault (retryable when the SDMX endpoint is down), or canvas_not_found

  • The EUROSTAT_BULK_MAX_BYTES budget (default 50 MiB decoded) retains complete observations within the permitted prefix and returns budgetExceeded: true. A budget too small for a complete header and usable observation fails as byte_budget_exceeded, with the configured limit and narrowing guidance. bytesRead counts accepted decoded bytes, never more than the budget; incomplete trailing rows are discarded.


eurostat_dataframe_describe tool

  • canvas_id from a staging response; lists each table's name, rowCount, typed columns, and expiresAt (sliding CANVAS_TTL_MS, default 24h); fails as canvas_not_found or canvas_disabled

  • Columns depend on the staging tool: eurostat_query_dataset writes a code and a _label column per dimension, eurostat_download_dataset codes only plus time (both share the obs_* and conf_status* measures), and eurostat_get_dimension_values writes code and label


eurostat_dataframe_query tool

  • One read-only SELECT per call; chained statements, other verbs, and functions that read files or external data are rejected

  • Returns columns, rows, rowCount, and required boolean truncated. Every row supplied by the canvas is returned intact, up to CANVAS_DEFAULT_ROW_LIMIT (default 10,000). Cells render as lossless JSON, including nesting and quoted strings; 64-bit integers arrive as strings. Fails as missing_table, canvas_not_found, or canvas_disabled

  • A cut sets truncated: true and adds a notice. Page an unchanged deterministic SELECT with a total ORDER BY (tie-breakers required), LIMIT and OFFSET; advance by actual rowCount. 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_id plus table_name, exactly as eurostat_dataframe_describe lists it; removes that one table and leaves the canvas and its other tables in place. Listed only when EUROSTAT_DATAFRAME_DROP_ENABLED=true is set beside the canvas

  • Returns canvasId, tableName, dropped, and expiresAt; dropped is false, with a notice, when nothing by that name was staged, so repeating a drop is safe. Fails as canvas_not_found, identifier_reserved, or canvas_disabled


eurostat://dataset/{dataset_code} resource

  • Same payload as eurostat_get_dataset_info, returned as application/json; a failed read carries the same reason, and retryable on an outage

  • dataset_code comes from eurostat_search_datasets or eurostat_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, freq included, since trade flows mix annual and monthly series; aggregates such as TOTAL, EU27_2020, and EXT_EU27_2020 double-count when summed with their members. PRODCOM text values arrive in valueText / 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, or async_queued errors that name the dimensions left to filter

  • A query with stage: true, a match above 5,000 observations, or any bulk download is staged as a SQL table (canvasId / tableName / stagedRowCount) on the DuckDB dataframe canvas, which is on by default. The .mcpb bundle strips the native DuckDB binding and runs with CANVAS_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 reason and a recovery.hint naming the next tool to call

  • Next-step hints: eurostat_search_datasets and eurostat_browse_themes return a nextStep pointing 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: appliedFilters on eurostat_query_dataset and appliedQuery (with the SDMX url) on eurostat_download_dataset, plus a notice when the preview omits rows

Getting started

Connect to the hosted instance:

claude mcp add --transport http eurostat-mcp-server https://eurostat.caseyjhand.com/mcp

Or run it locally:

claude mcp add eurostat-mcp-server -- npx -y @cyanheads/eurostat-mcp-server

For other clients, add this to the MCP config file:

{
  "mcpServers": {
    "eurostat-mcp-server": {
      "command": "npx",
      "args": ["-y", "@cyanheads/eurostat-mcp-server"]
    }
  }
}

No API key is required. For Docker or a source build, see Running the server.

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 complete rows one eurostat_dataframe_query returns before reporting truncated.

10000

EUROSTAT_CLAMP_LIMITS

Clamp positive integer preview/search/dimension limits to 500/100/1,000 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

Requires Bun v1.4.0 or higher (or Node.js v24+).

git clone https://github.com/cyanheads/eurostat-mcp-server.git
cd eurostat-mcp-server && bun install
cp .env.example .env # optional; every setting has a default
  • 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 run --rm -p 3010:3010 ghcr.io/cyanheads/eurostat-mcp-server:latest

The image defaults to HTTP transport, stateless session mode, and logs to /var/log/eurostat-mcp-server. For stdio, use docker run -i --rm -e MCP_TRANSPORT_TYPE=stdio ghcr.io/cyanheads/eurostat-mcp-server:latest. Build locally with docker build -t 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.

skills/

Published eurostat-guide and SQL reference, served over MCP and included in installation packages.

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/catch in tool logic

  • Use ctx.log for logging, ctx.state for storage

  • Register new tools and resources in the createApp() arrays in src/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.

Related MCP Connectors

Related MCP Servers