eurostat-mcp-server
An MCP server that allows agents to search, browse, and query the Eurostat catalogue for EU economic, demographic, trade, and regional data. It supports keyword searches, theme navigation, dataset metadata retrieval, and data querying or downloading via SDMX.
Endpoint and access
No sign-in to connect and list tools. Individual tools may still need a vendor account, credentials, or permissions; check the documented prerequisites.
https://eurostat.caseyjhand.com/mcpWhat its tools do
8 tools, grouped by the hints the server advertises. Hints are self-declared, not verified behavior.
Checks and signals
Each line is separate evidence, not a trust score.
- Passed Endpoint answered the live check ·
- Availability 1 of 1 checks passed in 30 days
What you can do with it
- Search the Eurostat catalogue by keyword to find dataset codes
- Browse the Eurostat theme hierarchy to discover datasets
- Fetch metadata for a specific Eurostat dataset
- Query a slice of a dataset or download the whole dataset
Source and maintenance
- Maintenance checked
Protocol and capabilities
- Server
- eurostat-mcp-server 0.11.2
- MCP protocol negotiated in check
- 2025-11-25
- Tools
- 8 advertised
- Resources
- 1 advertised
- Resource templates
- 2 advertised
- Prompts
- 0 advertised
{
"tools": {
"listChanged": true
},
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"extensions": {
"io.modelcontextprotocol/skills": {
"directoryRead": true
}
}
}Server-provided instructions
Untrusted server output shown for inspection. It is not Koskamo guidance and is excluded from knowledge search.
Tools
8 advertised
eurostat_search_datasets — Search Eurostat Datasets
Search the Eurostat catalogue by keyword. Returns matching datasets with codes, descriptions, period coverage, and theme breadcrumbs. Use this to discover dataset codes before calling eurostat_get_dataset_info, then eurostat_query_dataset for a slice of a dataset or eurostat_download_dataset for the whole of one. Results are limited to datasets and predefined tables — folders are excluded. The catalogue joins two sources: the dissemination table of contents, and the Comext host's dataflow list, which adds the DS-* collections — detailed trade by CN8, HS, SITC, BEC and CPA, and PRODCOM — filed under "International trade in goods - detailed data (Comext)" and "Statistics on the production of manufactured goods (PRODCOM)". Comext entries report a last-update date but no period coverage or observation count. A collection on neither list, such as the legacy PRODCOM DS-056120, is not disseminated and cannot be reached through this server.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"query"
],
"properties": {
"limit": {
"type": "number",
"default": 20,
"minimum": 1,
"multipleOf": 1,
"description": "Page size — maximum datasets returned per page. Default 20; positive integers above 100 are clamped to 100 with a notice. To retrieve matches beyond one page, pass the returned nextCursor back as cursor; the page size is fixed by this first call and limit is ignored on cursor calls."
},
"query": {
"type": "string",
"pattern": "\\S",
"minLength": 1,
"description": "Search terms — at least one non-whitespace token is required. Split on whitespace into tokens; every token must match (AND), case-insensitively, somewhere across the dataset label, theme breadcrumb, or code. Spacing beside punctuation in the catalogue is ignored, so \"Rev.2\" also finds \"Rev. 2\" and \"2015=100\" finds \"2015 = 100\"; a token keeps its own punctuation, as in \"R&D\", \"non-EU\", or \"DS-045409\". Word order does not matter, so \"business demography NUTS 3\" or \"regional economic accounts\" resolve without naming a label verbatim. When nothing matches, the error names each word whose removal alone would find matches or, when no single removal would and both catalogue sources were searched, the words that match no dataset label, theme breadcrumb, or code."
},
"cursor": {
"type": "string",
"description": "Opaque pagination cursor from a previous call's nextCursor. Omit for the first page; pass it back — with the same query — to fetch the next page of matches over a stable order. A cursor is bound to the query that produced it and to the catalogue snapshot in effect at that time, so reusing one with a different query, or after the catalogue refreshes, is rejected rather than silently paging a different result set."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"datasets",
"query",
"totalMatches",
"truncated"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"no_match",
"invalid_cursor",
"upstream_fault"
],
"description": "Machine-readable failure mode. Declared by this tool: `no_match`: No datasets matched the query string — including a query naming a DS-* code that neither the dissemination table of contents nor the Comext dataflow list carries, which is a collection Eurostat does not disseminate, and any query while the Comext dataflow list cannot be loaded and only the table of contents is searched. `invalid_cursor`: The cursor is malformed, came from a different query, or came from a catalogue snapshot that has since refreshed. `upstream_fault`: The server had no catalogue loaded yet, and Eurostat's table of contents answered HTTP 501, which is not retried; or it answered another HTTP 5xx or could not be reached on every retry, which the error marks retryable. Once a catalogue is loaded, a failed refresh serves that copy instead. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"query": {
"type": "string",
"description": "Search terms as submitted."
},
"notice": {
"type": "string",
"description": "Names each part of the catalogue this answer is missing — the Comext collections, or updates since a refresh failed — and what to do about it. Omitted when the whole catalogue answered."
},
"datasets": {
"type": "array",
"items": {
"type": "object",
"required": [
"code",
"label",
"type",
"themePath"
],
"properties": {
"code": {
"type": "string",
"description": "Dataset code (e.g., \"nama_10_gdp\"). Use this in eurostat_get_dataset_info, eurostat_query_dataset, and eurostat_download_dataset."
},
"type": {
"enum": [
"dataset",
"table"
],
"type": "string",
"description": "Entry type: \"dataset\" for standard datasets, \"table\" for predefined tables."
},
"label": {
"type": "string",
"description": "Human-readable dataset title."
},
"dataEnd": {
"type": "string",
"description": "Most recent data period available (e.g., \"2025\"). Omitted when not reported by Eurostat."
},
"obsCount": {
"type": "number",
"description": "Approximate number of observations. Omitted when not reported by Eurostat."
},
"dataStart": {
"type": "string",
"description": "Earliest data period available (e.g., \"1975\"). Omitted when not reported by Eurostat."
},
"themePath": {
"type": "array",
"items": {
"type": "string"
},
"description": "Breadcrumb path from root theme to this dataset (e.g., [\"Database by themes\", \"Economy and finance\"]). Eurostat files some datasets under several branches; this is the first branch that matched the query. Empty for top-level entries."
},
"lastUpdated": {
"type": "string",
"description": "Date of last data update (e.g., \"22.05.2026\"). Omitted when not reported."
}
},
"description": "A matched dataset entry.",
"additionalProperties": false
},
"description": "Matching datasets for the current page, up to the requested limit."
},
"nextStep": {
"type": "string",
"description": "Suggested next action based on these results. Populated when there is a clear follow-up call."
},
"truncated": {
"type": "boolean",
"description": "True when more matches remain beyond this page — pass nextCursor as cursor to fetch them."
},
"nextCursor": {
"type": "string",
"description": "Opaque cursor for the next page of matches. Pass it back as cursor with the same query; it stops working once the catalogue refreshes. Omitted on the last page."
},
"totalMatches": {
"type": "number",
"description": "Total distinct dataset codes matching the query across all pages, before the page limit."
},
"catalogueLoadedAt": {
"type": "string",
"description": "When the catalogue that answered was loaded (ISO 8601). Present only when a refresh failed and that older copy is still in service; omitted otherwise."
},
"comextListMissing": {
"type": "boolean",
"description": "True when the Comext dataflow list could not be loaded, so the DS-* collections (detailed trade and PRODCOM) were not searched and totalMatches counts the dissemination table of contents alone. Omitted when both sources were searched."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}eurostat_browse_themes — Browse Eurostat Theme Hierarchy
Navigate the Eurostat theme tree. Without theme_code returns the top-level theme folders (Economy, Population, Transport, etc.) — the practical starting points. With a theme_code returns its immediate children: subtheme folders and datasets in that branch. Use this for structured discovery when you know the domain but not the dataset code, or to drill down from a broad topic to a specific dataset. Pair with eurostat_search_datasets for keyword-based discovery. The tree is the dissemination table of contents plus the Comext host's DS-* collections: detailed trade under ext_go_detail (inside ext_go, "International trade in goods") and PRODCOM under prom (inside icts, "Industry, trade and services"). A collection on neither, such as the legacy PRODCOM DS-056120, is not disseminated and appears nowhere in the tree.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"theme_code": {
"type": "string",
"description": "Folder code to expand (e.g., \"economy\", \"reg\"). Exact codes take precedence, then case-insensitive matching applies. Omit to list the top-level theme folders."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"items",
"parentPath",
"itemCount"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"not_found",
"not_a_folder",
"upstream_fault"
],
"description": "Machine-readable failure mode. Declared by this tool: `not_found`: The provided theme_code does not exist in the TOC — or is a Comext folder (ext_go_detail, prom, comext) or DS-* code while the Comext dataflow list cannot be loaded. `not_a_folder`: The provided theme_code identifies a dataset or table entry instead of a folder. `upstream_fault`: The server had no catalogue loaded yet, and Eurostat's table of contents answered HTTP 501, which is not retried; or it answered another HTTP 5xx or could not be reached on every retry, which the error marks retryable. Once a catalogue is loaded, a failed refresh serves that copy instead. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"items": {
"type": "array",
"items": {
"type": "object",
"required": [
"code",
"label",
"type",
"hasChildren"
],
"properties": {
"code": {
"type": "string",
"description": "Theme or dataset code. Pass to theme_code to drill into a folder, or use as dataset_code for data tools."
},
"type": {
"enum": [
"folder",
"dataset",
"table"
],
"type": "string",
"description": "Item type: \"folder\" has sub-items; \"dataset\"/\"table\" can be queried for data."
},
"label": {
"type": "string",
"description": "Human-readable name."
},
"dataEnd": {
"type": "string",
"description": "Most recent data period. Omitted for folders and when not reported."
},
"obsCount": {
"type": "number",
"description": "Approximate observation count. Omitted for folders and when not reported."
},
"dataStart": {
"type": "string",
"description": "Earliest data period (e.g., \"1995\"). Omitted for folders and when not reported."
},
"hasChildren": {
"type": "boolean",
"description": "True when this folder contains sub-items. Only meaningful for type \"folder\"."
}
},
"description": "A theme folder, dataset, or table entry.",
"additionalProperties": false
},
"description": "Immediate children of the requested theme, or the root themes if theme_code was omitted."
},
"notice": {
"type": "string",
"description": "Names each part of the catalogue this listing is missing — the Comext collections, or updates since a refresh failed — and what to do about it. Omitted when the whole catalogue answered."
},
"nextStep": {
"type": "string",
"description": "Suggested next action based on these results. Populated when there is a clear follow-up call."
},
"itemCount": {
"type": "number",
"description": "Number of items returned in this level."
},
"themeCode": {
"type": "string",
"description": "Folder code that was browsed, or omitted for root."
},
"parentPath": {
"type": "array",
"items": {
"type": "string"
},
"description": "Breadcrumb from root to the requested theme (e.g., [\"Database by themes\", \"Economy and finance\"]). Empty when browsing root."
},
"otherPlacements": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "string"
},
"description": "Breadcrumb from root to one other folder carrying the requested code."
},
"description": "Breadcrumbs of the other branches that file this same theme_code. Eurostat lists a few folder codes in more than one branch; the items above come from the first one the catalogue lists, which never has fewer children than the branches named here but can list different ones. theme_code takes a bare code, so those branches cannot be addressed directly — browse down to them from the root instead. Omitted when the code has a single placement — the normal case."
},
"catalogueLoadedAt": {
"type": "string",
"description": "When the catalogue that answered was loaded (ISO 8601). Present only when a refresh failed and that older copy is still in service; omitted otherwise."
},
"comextListMissing": {
"type": "boolean",
"description": "True on a browse of ext_go or icts when the Comext dataflow list could not be loaded, so the DS-* collection folder filed there is not listed. Omitted otherwise."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}eurostat_get_dataset_info — Get Eurostat Dataset Info
Fetch metadata for a Eurostat dataset: dimensions with valid values, time range, observation count, and last-update date. Call this before eurostat_query_dataset or eurostat_download_dataset to discover what dimension codes are valid (unit, na_item, geo, etc.); eurostat_download_dataset builds its positional filter key from this dimension list, so a filter naming a dimension absent here is rejected outright. Returns up to 10 sample values per dimension for orientation; use eurostat_get_dimension_values to list the full set for large dimensions. A DS-* code (detailed trade and PRODCOM, in any case) is read from the Comext dissemination host, which reports no period coverage or observation count, so timeRange and obsCount come back unreported; the first call on a large Comext collection downloads its full structure (23 MB for DS-045409) and takes longer, and repeat calls within the hour reuse it.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"dataset_code"
],
"properties": {
"dataset_code": {
"type": "string",
"minLength": 1,
"description": "Dataset code (e.g., \"nama_10_gdp\", or \"DS-045409\" for a Comext collection). Use eurostat_search_datasets or eurostat_browse_themes to find codes."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"code",
"label",
"dimensions",
"timeRange"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"code": {
"type": "string",
"description": "Dataset code as provided."
},
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"not_found",
"upstream_fault"
],
"description": "Machine-readable failure mode. Declared by this tool: `not_found`: The dataset code does not exist or is not available for dissemination. `upstream_fault`: Eurostat returned a dataset structure or content constraint this server cannot read — malformed, truncated, not XML, or missing the requested dataset's dataflow; the structure endpoint answered HTTP 501, which is not retried; or it answered another HTTP 5xx or could not be reached on every retry, which the error marks retryable. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"label": {
"type": "string",
"description": "Human-readable dataset title."
},
"obsCount": {
"type": "number",
"description": "Total number of observations in the full dataset (all periods). Omitted when Eurostat does not report it — an omitted count is unknown, not zero."
},
"timeRange": {
"type": "object",
"properties": {
"end": {
"type": "string",
"description": "Most recent available period (e.g., \"2024\"). Omitted when Eurostat does not report it."
},
"start": {
"type": "string",
"description": "Earliest available period (e.g., \"1975\"). Omitted when Eurostat does not report it."
}
},
"description": "Overall data coverage period for this dataset. Each bound is omitted when Eurostat does not report it — an omitted bound is unknown, not empty.",
"additionalProperties": false
},
"dimensions": {
"type": "array",
"items": {
"type": "object",
"required": [
"code",
"label"
],
"properties": {
"code": {
"type": "string",
"description": "Dimension code (e.g., \"unit\", \"geo\", \"na_item\"). Use these as filter keys in eurostat_query_dataset and eurostat_download_dataset."
},
"label": {
"type": "string",
"description": "Human-readable dimension name (e.g., \"Unit of measure\")."
},
"valuesCount": {
"type": "number",
"description": "Number of dataset-available values in this dimension, taken from the dataset content constraint. For \"time\" this is the full period count. Omitted only when Eurostat does not supply a measurable value set."
},
"sampleValues": {
"type": "array",
"items": {
"type": "object",
"required": [
"code",
"label"
],
"properties": {
"code": {
"type": "string",
"description": "Dimension value code."
},
"label": {
"type": "string",
"description": "Human-readable label for this value."
}
},
"description": "A dimension value code and label pair.",
"additionalProperties": false
},
"description": "First 10 dataset-available values for orientation. Use eurostat_get_dimension_values for the full constrained list. Omitted alongside valuesCount when Eurostat does not supply a measurable value set."
}
},
"description": "A dataset dimension with its valid values.",
"additionalProperties": false
},
"description": "All dimensions of the dataset with their valid codes and labels."
},
"lastUpdated": {
"type": "string",
"description": "ISO 8601 timestamp of the most recent data update. Omitted when Eurostat does not report it."
},
"metadataUrl": {
"type": "string",
"description": "URL to the ESMS HTML metadata page for this dataset. Omitted when not provided by Eurostat."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}eurostat_get_dimension_values — Get Eurostat Dimension Values
List the valid values for a dimension in a Eurostat dataset, preserving value codes and labels exactly. Use this when eurostat_get_dataset_info returns more values than its sample, or to confirm exact filter codes before querying. Dimension identifiers match exactly first, then case-insensitively; TIME_PERIOD aliases time. For geo, use geo_level to select the NUTS hierarchy (country, nuts1, nuts2, nuts3). To find codes in a long list, pass filter: words its code or label contains. Pages default to 25 values, maximum 1,000; pass nextCursor back as cursor for the next page with the same dataset, dimension, geo level and filter. Canonical and alias dimension spellings resume the same list. Pass canvas_id on the first call to also stage every value as a two-column code/label table, regardless of filter or limit. Inspect that table with eurostat_dataframe_describe, then search or join it with eurostat_dataframe_query.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"dataset_code",
"dimension"
],
"properties": {
"limit": {
"type": "number",
"default": 25,
"minimum": 1,
"multipleOf": 1,
"description": "Most values per page, default 25, maximum 1,000. Larger positive integers clamp with a notice when EUROSTAT_CLAMP_LIMITS is enabled. Fixed by the first call: with cursor, the cursor carries that limit and this field is ignored."
},
"cursor": {
"type": "string",
"description": "Opaque nextCursor from the previous page; omit for the first page. It resumes after that page's last code and is bound to dataset_code, dimension, geo_level and filter, so send those unchanged. Cannot be combined with canvas_id on a deployment with a dataframe canvas."
},
"filter": {
"type": "string",
"maxLength": 500,
"description": "Words to find, up to 500 characters: keeps only values whose code and label, joined by a space, contain every whitespace-separated word as a substring — in any order, ignoring case and accents (\"palma frankfurt\" finds DE_EDDF_ES_LEPA, \"zurich\" finds Zürich). Punctuation is part of a word, and spellings are not transliterated (\"münchen\" does not find MUENCHEN). Runs over the whole list at the effective geo_level before paging and keeps Eurostat's order, with no ranking; matchCount counts the matches."
},
"canvas_id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{10}$",
"description": "Also stage the values on this dataframe canvas as a table with two columns, code and label, holding every value the dimension lists at the geo_level requested (country when it is omitted), whatever filter and limit are. Pass the canvasId a eurostat_download_dataset or eurostat_query_dataset response returned, then search the staged table with SQL or join it to a download on the dimension column (e.g., d.geo = g.code). Only on a call without cursor; omit to return the values inline only: this tool never starts a canvas. Ignored on deployments without a dataframe canvas."
},
"dimension": {
"type": "string",
"minLength": 1,
"description": "Dimension code to retrieve values for (e.g., \"unit\", \"na_item\", \"geo\"). Exact match first, then case-insensitive; TIME_PERIOD aliases time. Use eurostat_get_dataset_info to see available dimensions."
},
"geo_level": {
"enum": [
"aggregate",
"country",
"nuts1",
"nuts2",
"nuts3"
],
"type": "string",
"description": "NUTS hierarchy level filter — applies only when dimension is \"geo\"; passing it with any other dimension is rejected. Options: \"aggregate\" (EU/EA codes), \"country\" (2-letter codes, default), \"nuts1\" (3-char), \"nuts2\" (4-char), \"nuts3\" (5-char)."
},
"dataset_code": {
"type": "string",
"minLength": 1,
"description": "Dataset code (e.g., \"nama_10_gdp\", or \"DS-045409\" for a Comext collection)."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"dimensionCode",
"dimensionLabel",
"values",
"totalCount"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"not_found",
"no_results",
"conflicting_params",
"invalid_cursor",
"upstream_fault",
"canvas_not_found"
],
"description": "Machine-readable failure mode. Declared by this tool: `not_found`: The dataset code or dimension code does not exist. `no_results`: The dataset has no geo values at the effective geo_level. `conflicting_params`: geo_level was combined with a dimension other than \"geo\", where it has no effect; or, on a deployment with a dataframe canvas, canvas_id was combined with cursor, which would stage the list again on every page. `invalid_cursor`: The cursor is malformed, was issued for a different dataset_code, dimension, geo_level or filter, or resumes after a code the list no longer holds. `upstream_fault`: Eurostat returned a dataset structure or content constraint this server cannot read — malformed, truncated, not XML, or missing the requested dataset's dataflow; the structure endpoint answered HTTP 501, which is not retried; or it answered another HTTP 5xx or could not be reached on every retry, which the error marks retryable. `canvas_not_found`: A canvas_id was supplied for staging but is unknown or its lifetime has elapsed. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"notice": {
"type": "string",
"description": "Count-limit clamp disclosure, continuation routes (nextCursor, filter, canvas_id), or an explanation of a filter that matched nothing."
},
"values": {
"type": "array",
"items": {
"type": "object",
"required": [
"code",
"label"
],
"properties": {
"code": {
"type": "string",
"description": "Dimension value code. Use these as filter values in eurostat_query_dataset and eurostat_download_dataset."
},
"label": {
"type": "string",
"description": "Human-readable label for this value."
}
},
"description": "A dimension value code and label pair.",
"additionalProperties": false
},
"description": "This page of dataset-available values, in Eurostat's order — for geo, the subset at geoLevel; with filter, only the matches. A page holds the effective limit (default 25, maximum 1,000), fewer only at the end; truncated and nextCursor are set when more follow."
},
"canvasId": {
"type": "string",
"description": "Dataframe canvas the values were staged on — the canvas_id supplied. Omitted when nothing was staged: canvas_id was omitted, this deployment runs without a dataframe canvas, or the dimension lists no values."
},
"geoLevel": {
"enum": [
"aggregate",
"country",
"nuts1",
"nuts2",
"nuts3"
],
"type": "string",
"description": "Effective NUTS hierarchy level for the geo value set. Present only for the geo dimension; country is reported when geo_level was omitted."
},
"tableName": {
"type": "string",
"description": "Canvas table holding every value as two VARCHAR columns, code and label. Call eurostat_dataframe_describe with canvasId first to confirm it, then join it in eurostat_dataframe_query. Omitted alongside canvasId."
},
"truncated": {
"type": "boolean",
"description": "True when more values follow this page — pass nextCursor as cursor to fetch them. Omitted on the last page."
},
"matchCount": {
"type": "number",
"description": "Values matching filter across the whole list, before paging. Present only when filter was passed."
},
"nextCursor": {
"type": "string",
"description": "Opaque cursor for the next page. Pass it back as cursor with dataset_code, dimension, geo_level and filter unchanged; it resumes after this page's last code. Omitted on the last page."
},
"totalCount": {
"type": "number",
"description": "Number of distinct values the dimension lists, after geoLevel filtering for geo — every one, whatever filter and paging apply."
},
"dimensionCode": {
"type": "string",
"description": "The dimension code that was queried."
},
"dimensionLabel": {
"type": "string",
"description": "Human-readable dimension name."
},
"stagedRowCount": {
"type": "number",
"description": "Rows written to the canvas table. Equals totalCount. Omitted alongside tableName."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}eurostat_query_dataset — Query Eurostat Dataset
Fetch statistical data from a Eurostat dataset with dimension filters. Returns a deterministic inline prefix of decoded observations with dimension codes and labels, numeric values, an OBS_FLAG status (e.g., "p" = provisional, "e" = estimated) and a separate CONF_STATUS confidentiality marker (e.g., "C" = confidential, which is usually why a value is null). preview_limit controls only that prefix; filters and period controls reduce the matched result itself. Call eurostat_get_dataset_info first to discover valid dimension codes and values. Apply filters to keep the result set manageable — large unfiltered queries may trigger an async response error. Use filters.geo for specific country/region codes, or geo_level for NUTS hierarchy filtering (mutually exclusive). Use last_n_periods for the N most recent dataset time positions without knowing the end date; a notice names selected periods with no populated cells for the slice. Set stage=true to stage the whole match at any size on a canvas-enabled deployment; matches above 5,000 observations stage automatically. Call eurostat_dataframe_describe first, then eurostat_dataframe_query. When the target is a whole dataset rather than a slice, eurostat_download_dataset reads the SDMX bulk endpoint instead and is the cheaper route.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"dataset_code"
],
"properties": {
"lang": {
"enum": [
"EN",
"FR",
"DE"
],
"type": "string",
"default": "EN",
"description": "Language for labels in the response. Default is \"EN\". Options: \"EN\", \"FR\", \"DE\"."
},
"stage": {
"type": "boolean",
"default": false,
"description": "Stage every matched observation on a dataframe canvas, regardless of preview length or match size. Default false preserves automatic staging only above 5,000 observations. Ignored with an unavailable-staging notice on deployments without a canvas."
},
"filters": {
"type": "object",
"default": {},
"description": "Dimension filters as a map of dimension code → array of valid values. Example: {\"unit\": [\"CP_MEUR\"], \"na_item\": [\"B1GQ\"], \"geo\": [\"DE\", \"FR\"]}. An empty array is treated as no filter for that dimension and is dropped from the request, and so is a blank or whitespace-only value ([\"\"] is no filter, [\"\", \"DE\"] is [\"DE\"]). Dimension codes match in any case (\"GEO\" is geo). Keys are dimensions only: one naming a request control (lang, format, geoLevel, sinceTimePeriod, untilTimePeriod, lastTimePeriod, in any case) is rejected as invalid_dimension — use the lang, geo_level, since_period, until_period and last_n_periods inputs. Do not include \"geo\" here if using geo_level, nor \"time\"/\"time_period\" if using since_period, until_period or last_n_periods. A value that matches nothing contributes no rows rather than an error; the response names it in unmatchedValues, or in the no_results error when nothing matched at all. eurostat_get_dimension_values lists the valid values.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"canvas_id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{10}$",
"description": "Reuse an existing dataframe canvas, so a result staged by this call lands beside earlier ones and can be joined against them. Pass the canvasId a previous eurostat_query_dataset or eurostat_download_dataset response returned; omit to start a fresh canvas. Used only when staging: stage=true or a match above 5,000 observations, on a canvas-enabled deployment."
},
"geo_level": {
"enum": [
"aggregate",
"country",
"nuts1",
"nuts2",
"nuts3"
],
"type": "string",
"description": "Filter by NUTS hierarchy level. Mutually exclusive with a \"geo\" key in filters. Options: \"aggregate\" (EU/EA totals), \"country\" (41 member/candidate states), \"nuts1\" (127 major regions), \"nuts2\" (309 basic regions), \"nuts3\" (1,343 small regions)."
},
"dataset_code": {
"type": "string",
"minLength": 1,
"description": "Dataset code (e.g., \"nama_10_gdp\"). Required. A DS-* code — Comext detailed trade or PRODCOM, in any case — is served by the Comext host: filter freq on its trade flows, which mix annual and monthly series, and note that product, reporter and partner carry aggregates (TOTAL, EU27_2020) that double-count when summed with their members."
},
"since_period": {
"type": "string",
"description": "Start of the time range, inclusive. Accepted forms: YYYY, YYYY-MM, YYYY-MM-DD, YYYY-Qn (1–4), YYYY-Sn (1–2), YYYY-Tn (1–3), YYYY-Mnn (01–12), YYYY-Wnn (a week the year has, up to 53) or YYYY-Dnnn (a day the year has, up to 366) (e.g., \"2020\", \"2023-Q1\", \"2024-01\"). Extra leading zeros after the letter are dropped (\"2020-Q01\" is sent as \"2020-Q1\"), a day of the year is sent as three digits (\"2026-D1\" as \"2026-D001\"), and YYYY-A1 is sent as YYYY. Any form works on any dataset, read as a span of days, but Eurostat keeps only periods wholly inside the range, per frequency: \"2022-06\" on annual data excludes 2022, which starts before the bound, and a notice names that period when the response shows its frequency. A range whose first and last day are the same day is sent as that one date. A malformed or non-existent period (e.g., \"2020-13\") is rejected as invalid_period. Mutually exclusive with last_n_periods."
},
"until_period": {
"type": "string",
"description": "End of the time range, inclusive (e.g., \"2024\"), in the same forms as since_period. Omit for data through the latest available period. A bound that falls inside a period excludes it here too: \"2023-06\" on annual data excludes 2023, which ends after the bound. The range must hold at least one day: a since_period that starts after until_period ends is rejected as invalid_period, and bounds of different frequencies are compared by day. Mutually exclusive with last_n_periods."
},
"preview_limit": {
"type": "number",
"default": 25,
"minimum": 1,
"multipleOf": 1,
"description": "How many matched observations to return inline, from the deterministic start of the JSON-stat cell order. Default 25; positive integers above 500 are clamped to 500 with a notice. This changes only the inline prefix: it does not reduce obsCount, missingObsCount, timeRange, the upstream response, or staged rows. Use filters or period controls to reduce the match itself."
},
"last_n_periods": {
"type": "number",
"minimum": 1,
"multipleOf": 1,
"description": "Return only the N most recent dataset time positions. Selected periods without populated cells for this slice are disclosed in a notice on successful queries. N counts back from the dataset's latest period, not from the latest period published for this slice, so a slice that lags can come back empty — increase N, or replace last_n_periods with until_period ending at a period the slice has published. Mutually exclusive with since_period and until_period."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"datasetCode",
"datasetLabel",
"dimensionsUsed",
"observations",
"obsCount",
"truncated",
"timeRange",
"missingObsCount",
"absentCellCount",
"appliedFilters"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"not_found",
"no_results",
"invalid_period",
"async_response",
"invalid_dimension",
"conflicting_params",
"canvas_not_found",
"upstream_fault"
],
"description": "Machine-readable failure mode. Declared by this tool: `not_found`: The dataset code does not exist (HTTP 404 carrying Eurostat error id 100). `no_results`: The query matched no observation cells — including Eurostat HTTP-200 error id 100. The dataset is valid, but no cell carries a value or a status flag for that filter combination and period range. When Eurostat returns the empty table, the error names the filter values that matched nothing (data.unmatchedValues), the selected periods that carry no value (data.matchedPeriods, the newest 24, with data.matchedPeriodCount counting all of them), and, for a range that selected no period, whether it misses the coverage, holds no whole period, or holds no published one. When error id 100 answers a query that set geo_level, the error names geo_level as the likely cause, and lists the dataset's dimensions when it has no \"geo\" dimension. `invalid_period`: since_period or until_period is not a period literal, or names a month, quarter, semester, trimester, week or day that does not exist, or since_period starts after until_period ends. Checked before any request; a period Eurostat itself rejects maps here too. `async_response`: Eurostat returned an async warning or an HTTP-413 error array — the query matched too many observations, including an EXTRACTION_TOO_BIG refusal past Eurostat's 5,000,000-row limit. `invalid_dimension`: A dimension code in filters does not exist in this dataset (HTTP 400, Eurostat error id 150), or a filter key names a request control (lang, format, geoLevel, sinceTimePeriod, untilTimePeriod, lastTimePeriod, in any case) rather than a dimension — rejected before any request, naming the input that sets it. `conflicting_params`: Mutually exclusive parameters were combined: \"geo\" filter + geo_level, since_period/until_period + last_n_periods, or a \"time\"/\"time_period\" filter + since_period, until_period or last_n_periods. Checked before any request; Eurostat's own refusal of two time selections maps here too. `canvas_not_found`: A canvas_id was supplied for staging but is unknown or its lifetime has elapsed. `upstream_fault`: The Statistics API answered HTTP 5xx or could not be reached on every attempt of the retry loop. The error is marked retryable, except for HTTP 501, which is marked not retryable. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"notice": {
"type": "string",
"description": "Composed guidance for a period a bound falls inside, which Eurostat excludes, unmatched filter values, selected last_n_periods positions with no populated cells, absent cells (periods a series lacks, with how many series lack any), inline previews, staging and unavailable explicit staging. Period gaps list up to the newest 24 periods with an omitted count; staged matches give the describe-then-query sequence. Omitted when none of these disclosures applies."
},
"canvasId": {
"type": "string",
"description": "Dataframe canvas holding the staged result. Pass to eurostat_dataframe_describe, eurostat_dataframe_query, or a later eurostat_query_dataset call. Omitted when nothing was staged."
},
"obsCount": {
"type": "number",
"description": "Total number of observations matched (before any cap)."
},
"tableName": {
"type": "string",
"description": "Canvas table holding every matched observation in flat form — one code column per dimension plus a \"_label\" companion, then obs_value, obs_flag, obs_flag_label, conf_status, conf_status_label. A DS-* table also carries obs_value_text, the valueText of each row. Call eurostat_dataframe_describe with canvasId first to confirm the table and columns, then eurostat_dataframe_query. Omitted when nothing was staged: stage was false with at most 5,000 observations, or this deployment runs without a dataframe canvas."
},
"timeRange": {
"type": "object",
"properties": {
"end": {
"type": "string",
"description": "Most recent period matched. Omitted when the match carries no time dimension and Eurostat reports no overall period."
},
"start": {
"type": "string",
"description": "Earliest period matched. Omitted when the match carries no time dimension and Eurostat reports no overall period."
}
},
"description": "Time coverage of everything matched — the same set obsCount counts, so it can reach periods absent from observations when truncated is true. Each bound is omitted when neither the match nor Eurostat report it — an omitted bound is unknown, not empty.",
"additionalProperties": false
},
"truncated": {
"type": "boolean",
"description": "True only when the match exceeded the 5,000-observation staging threshold. Independent of preview_limit: observations can be a shorter prefix while truncated is false. When tableName is set, call eurostat_dataframe_describe first and then eurostat_dataframe_query; when it is absent, use filters or period controls to reduce the match."
},
"datasetCode": {
"type": "string",
"description": "Dataset code as provided."
},
"datasetLabel": {
"type": "string",
"description": "Human-readable dataset title."
},
"observations": {
"type": "array",
"items": {
"type": "object",
"required": [
"dimensions",
"value"
],
"properties": {
"value": {
"type": [
"number",
"null"
],
"description": "Numeric observation value, or null when Eurostat reports none — unavailable in the source data, withheld (confStatus says so), or published as text (valueText holds it)."
},
"status": {
"type": "object",
"required": [
"code",
"label"
],
"properties": {
"code": {
"type": "string",
"description": "OBS_FLAG code (e.g., \"p\", \"e\", \"d\")."
},
"label": {
"type": "string",
"description": "Status description (e.g., \"provisional\", \"estimated\", \"definition differs\")."
}
},
"description": "Eurostat OBS_FLAG for this observation. Omitted for unflagged observations, and never carries a confidentiality code — that arrives in confStatus.",
"additionalProperties": false
},
"valueText": {
"type": "string",
"description": "A value Eurostat published as text rather than a number, verbatim — PRODCOM (DS-*) flag and unit indicators such as QNTUNIT publish a unit like \"KG\" — with value null. PRODCOM's \":C\" is decoded to confStatus \"C\" instead. Omitted for numeric and missing values."
},
"confStatus": {
"type": "object",
"required": [
"code",
"label"
],
"properties": {
"code": {
"type": "string",
"description": "CONF_STATUS code: \"C\", \"N\", or \"P\"."
},
"label": {
"type": "string",
"description": "Confidentiality description (e.g., \"confidential\", \"not for publication\")."
}
},
"description": "Eurostat CONF_STATUS for this observation — a different codelist from status. Present when Eurostat restricts the cell, which is usually why value is null. Omitted otherwise.",
"additionalProperties": false
},
"dimensions": {
"type": "object",
"properties": {},
"description": "Map of dimension code → {code, label}. One entry per dimension in dimensionsUsed, keyed by dimension code (e.g., {\"geo\": {\"code\": \"DE\", \"label\": \"Germany\"}, \"time\": {\"code\": \"2023\", \"label\": \"2023\"}}).",
"additionalProperties": {}
}
},
"description": "A single decoded observation with dimension values, numeric value, and the optional OBS_FLAG and CONF_STATUS markers.",
"additionalProperties": false
},
"description": "The first preview_limit decoded observations in deterministic JSON-stat cell order — the leading combinations of the dataset dimensions, neither a sample nor necessarily the most recent periods. This prefix is independent of the 5,000-observation staging threshold. When tableName is set, the table holds every matched row; otherwise use filters or a period range to reduce the match itself."
},
"appliedFilters": {
"type": "object",
"required": [
"filters"
],
"properties": {
"filters": {
"type": "object",
"description": "Dimension filters actually sent to Eurostat. Blank values, and dimensions left with no value (empty arrays included), are dropped and do not appear here.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"geoLevel": {
"type": "string",
"description": "NUTS geo level filter applied, if any."
},
"sincePeriod": {
"type": "string",
"description": "Start of time range applied, if any."
},
"untilPeriod": {
"type": "string",
"description": "End of time range applied, if any."
},
"lastNPeriods": {
"type": "number",
"description": "Last N periods filter applied, if any."
}
},
"description": "Effective query parameters applied to the Eurostat API.",
"additionalProperties": false
},
"dimensionsUsed": {
"type": "array",
"items": {
"type": "string"
},
"description": "Ordered list of dimension codes present in the response (e.g., [\"freq\", \"unit\", \"na_item\", \"geo\", \"time\"])."
},
"stagedRowCount": {
"type": "number",
"description": "Rows written to the canvas table. Matches obsCount. Omitted alongside tableName when nothing was staged."
},
"absentCellCount": {
"type": "number",
"description": "Periods the matched series lack: for each series (one combination of the non-time dimensions) with at least one cell in the match, the periods of a frequency it carries in which Eurostat publishes no cell for it, with no value and no flag. Absent cells are not observations, so obsCount, missingObsCount, observations and staged rows leave them out. A series lacks no period of a frequency it does not carry: an annual series beside a monthly one lacks no months. 0 when every series has a cell in every period of its frequencies."
},
"missingObsCount": {
"type": "number",
"description": "Number of matched observations carrying no numeric value, counted across everything matched rather than only the returned rows. Covers both unavailable and withheld cells — a slice can be wholly confidential, so this equalling obsCount does not mean the data is absent. A missing observation is a cell Eurostat publishes without a number (with a flag, a confidentiality marker, or text); a period of a frequency the series carries in which it has no cell at all is an absent cell, counted in absentCellCount instead."
},
"unmatchedValues": {
"type": "object",
"description": "Filter values that matched nothing in the dataset, keyed by dimension code and spelled as sent (matching ignores case). The observations cover only the values that did match. Omitted when every filter value matched.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}eurostat_download_dataset — Download Eurostat Dataset
Download a Eurostat dataset in bulk through the SDMX 2.1 TSV endpoint and stage it as a SQL table on the dataframe canvas — the route to a whole dataset, where eurostat_query_dataset is the route to a slice of one. The TSV wire format is roughly half the bytes of the JSON-stat body eurostat_query_dataset reads, so it reaches datasets that would otherwise time out, and it is expanded here into one row per non-blank TSV cell, including the unflagged ":" cells that mark a period a series lacks or a period of a frequency it has no observation of. Filters take the same dimension-code map eurostat_query_dataset uses and are applied server-side by Eurostat; call eurostat_get_dataset_info first for the dimension codes and eurostat_get_dimension_values for their values. Narrow with since_period/until_period rather than asking for the most recent N periods — the TSV layout keeps a column for every period whichever is requested, so a period range is what actually shrinks the response. Transfers are bounded by a decoded-byte budget enforced while streaming: complete rows within the allowed prefix are retained with budgetExceeded set; a budget too small for a usable row fails as byte_budget_exceeded with narrowing guidance. Only preview_limit rows come back inline. When a table is staged, call eurostat_dataframe_describe first to confirm its columns, then eurostat_dataframe_query; without a canvas, rows past the preview are not retained.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"dataset_code"
],
"properties": {
"filters": {
"type": "object",
"default": {},
"description": "Dimension filters as a map of dimension code → array of accepted values, applied by Eurostat before the body is sent. Example: {\"unit\": [\"CP_MEUR\"], \"na_item\": [\"B1G\"], \"geo\": [\"DE\", \"FR\"]}. Omit a dimension or pass an empty array to accept every value for it; a blank or whitespace-only value is likewise unset and dropped ([\"\"] accepts every value, [\"\", \"DE\"] is [\"DE\"]). Dimension codes match in any case (\"GEO\" is geo). Do not put \"time\" here — use since_period/until_period. Naming a dimension the dataset does not have is rejected with the dataset's dimension list rather than silently ignored.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"canvas_id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{10}$",
"description": "Reuse an existing dataframe canvas so this download lands beside earlier results and can be joined against them. Pass the canvasId a previous eurostat_download_dataset or eurostat_query_dataset response returned; omit to start a fresh canvas. Ignored on deployments without a dataframe canvas. A download that yields no row (a header-only table) fails as no_results and leaves the canvas untouched."
},
"dataset_code": {
"type": "string",
"minLength": 1,
"description": "Dataset code (e.g., \"nama_10_gdp\"). Required. A DS-* code — Comext detailed trade or PRODCOM, in any case — is served by the Comext host, which refuses an unfiltered download of its large collections as extraction_too_big: filter it, including freq on the trade flows, which mix annual and monthly series."
},
"since_period": {
"type": "string",
"description": "Start of the period range, inclusive, sent as startPeriod. Accepted forms: YYYY, YYYY-MM, YYYY-MM-DD, YYYY-Qn (1–4), YYYY-Sn (1–2), YYYY-Tn (1–3), YYYY-Mnn (01–12), YYYY-Wnn (a week the year has, up to 53) or YYYY-Dnnn (a day the year has, up to 366) (e.g., \"2020\", \"2023-Q1\", \"2024-01\"). Extra leading zeros after the letter are dropped (\"2020-W001\" is sent as \"2020-W01\"), a day of the year is sent as three digits (\"2026-D1\" as \"2026-D001\"), and YYYY-A1 is sent as YYYY. Any form works on any dataset, read as a span of days, but Eurostat keeps only periods wholly inside the range, per frequency: \"2022-06\" on annual data excludes 2022, which starts before the bound, and a notice names that period when the TSV header shows its frequency. A range whose first and last day are the same day is requested as that day and the next, and the next day is dropped before anything is counted or staged. A malformed or non-existent period (e.g., \"2020-13\") is rejected as invalid_period. The most effective way to shrink a bulk response: it removes period columns from the TSV rather than blanking their cells."
},
"until_period": {
"type": "string",
"description": "End of the period range, inclusive (e.g., \"2024\"), sent as endPeriod, in the same forms as since_period. Omit for data through the latest available period. A bound that falls inside a period excludes it here too: \"2023-06\" on annual data excludes 2023, which ends after the bound. The range must hold at least one day: a since_period that starts after until_period ends is rejected as invalid_period, and bounds of different frequencies are compared by day."
},
"preview_limit": {
"type": "number",
"default": 25,
"minimum": 1,
"multipleOf": 1,
"description": "How many rows to echo inline, from the start of the download. Default 25; positive integers above 500 are clamped to 500 with a notice. The full download is on the canvas table when one was staged; this is orientation, not the result set."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"datasetCode",
"dimensionsUsed",
"rowCount",
"missingCount",
"absentCellCount",
"periodRange",
"bytesRead",
"compressed",
"budgetExceeded",
"observations",
"totalCount",
"appliedQuery"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"byte_budget_exceeded",
"not_found",
"invalid_dimension",
"filter_arity",
"invalid_period",
"async_queued",
"no_results",
"extraction_too_big",
"upstream_fault",
"canvas_not_found"
],
"description": "Machine-readable failure mode. Declared by this tool: `byte_budget_exceeded`: The decoded TSV byte budget was exhausted before a complete header and usable row could be read. `not_found`: The dataset code is not available for dissemination (HTTP 404, SDMX faultcode 100). `invalid_dimension`: A filter names a dimension the dataset does not have, or a value Eurostat rejects, or the period range selects no period of the dataset (SDMX faultcode 150): it misses the coverage, or holds no whole period. The fault covers all of these; its TIME_PERIOD clause tells the period range apart. `filter_arity`: Eurostat rejected the positional dimension key because it carried the wrong number of positions (SDMX faultcode 140, INVALID_QUERY_NB_FILTERS), meaning the dataset structure has changed since the metadata call. `invalid_period`: since_period or until_period is not a period literal, or names a month, quarter, semester, trimester, week or day that does not exist, or since_period starts after until_period ends. Checked before any request — the bulk endpoint would otherwise roll an out-of-range period into a neighbouring one, and answer an inverted range with the whole series — and SDMX faultcode 140 TIME_PERIOD_FILTER_SPEC_INVALID maps here too. `async_queued`: Eurostat answered HTTP 200 with a SOAP syncResponse queue ticket (status SUBMITTED) instead of data, because the extraction was too costly to serve synchronously. `no_results`: The download completed but yielded no row: a header-only table, or a one-day reply whose only cells fall on the next day. Nothing is staged, and no canvas is created or touched. A table whose only rows are unflagged \":\" cells succeeds with those rows, with periodRange omitted. `extraction_too_big`: Eurostat refused the extraction as too large (SDMX faultcode 413, HTTP 413): past its 5,000,000-row extraction limit, or an unfiltered download of a large DS-* Comext collection, which Eurostat serves only filtered. Covers every dataset, on either host. `upstream_fault`: The SDMX data endpoint returned a fault this server does not model, or a body that is not a TSV table; for a filtered download, the structure endpoint returned a structure this server cannot build a filter key from; the data endpoint — or, for a filtered download, the structure endpoint — answered HTTP 501, which is not retried; or it answered another HTTP 5xx or could not be reached on every retry, which the error marks retryable. `canvas_not_found`: A canvas_id was supplied for staging but is unknown or its lifetime has elapsed. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"notice": {
"type": "string",
"description": "Guidance on every download: the staged table with the required eurostat_dataframe_describe then eurostat_dataframe_query sequence (or, without a canvas, what was returned inline and what was discarded), preceded by a period a bound falls inside when the TSV header shows that frequency, by byte-budget disclosure when the budget stopped the transfer, by what the unflagged \":\" rows are when a series lacks periods or the slice mixes frequencies, and by the inline-preview length when preview_limit returns fewer rows than were downloaded."
},
"canvasId": {
"type": "string",
"description": "Dataframe canvas holding the staged download. Pass to eurostat_dataframe_describe, eurostat_dataframe_query, or a later staging call. Omitted when nothing was staged."
},
"rowCount": {
"type": "number",
"description": "Rows expanded from the download, one per non-blank cell. A row carrying a value, a flag, a status or a text value is an observation, a flagged \":\" (a value Eurostat reports as unavailable) included. A row holding an unflagged \":\" — obs_value null with no flag, status or text — is not: it is a period its series lacks (counted in absentCellCount when the series has an observation of that frequency) or a period of a frequency its series has no observation of."
},
"bytesRead": {
"type": "number",
"description": "Decoded TSV bytes accepted for parsing, after decompression when applicable; never more than EUROSTAT_BULK_MAX_BYTES, including an incomplete trailing line that is discarded."
},
"tableName": {
"type": "string",
"description": "Canvas table holding every downloaded row. Call eurostat_dataframe_describe with canvasId first to confirm the table and columns, then eurostat_dataframe_query. Omitted when this deployment runs without a dataframe canvas, in which case only the inline preview survives the call."
},
"compressed": {
"type": "boolean",
"description": "True when Eurostat sent the body gzip-compressed. It does so without a Content-Encoding header on large responses, so this reports what the stream actually carried."
},
"totalCount": {
"type": "number",
"description": "Rows the download produced — equal to rowCount, unflagged \":\" rows included: a period their series lacks (absentCellCount) or a period of a frequency their series has no observation of. The inline observations array holds only the first preview_limit of them; budgetExceeded, not this count, says whether the download is the whole dataset."
},
"datasetCode": {
"type": "string",
"description": "Dataset code as provided."
},
"periodRange": {
"type": "object",
"properties": {
"end": {
"type": "string",
"description": "Latest period of a row carrying an observation, read the same way as start. Omitted, with the other bound, when no row carries an observation."
},
"start": {
"type": "string",
"description": "Earliest period of a row carrying an observation — a value, a flag, a status or a text value; an unflagged \":\" row sets no bound. Omitted, with the other bound, when no row carries an observation."
}
},
"description": "Period coverage of the observations actually downloaded. Narrower than the dataset when budgetExceeded is true or a period range was applied.",
"additionalProperties": false
},
"appliedQuery": {
"type": "object",
"required": [
"filters",
"url"
],
"properties": {
"url": {
"type": "string",
"description": "The SDMX request URL, so the download can be reproduced. A one-day range goes out as that day and the next, so its endPeriod is a day past untilPeriod."
},
"filters": {
"type": "object",
"description": "Dimension filters sent to Eurostat. Blank values and dimensions left with no value are dropped.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"sincePeriod": {
"type": "string",
"description": "since_period applied, if any."
},
"untilPeriod": {
"type": "string",
"description": "until_period applied, if any."
}
},
"description": "The bulk request as the server built it.",
"additionalProperties": false
},
"missingCount": {
"type": "number",
"description": "Rows carrying no numeric value (obs_value is null): observations published without a number — a flagged \":\" or a text value — and every unflagged \":\" row, a period its series lacks or a period of a frequency its series has no observation of."
},
"observations": {
"type": "array",
"items": {
"type": "object",
"description": "One downloaded row: one column per dimension holding its code, \"time\" for the period, then obs_value, obs_flag, obs_flag_label, conf_status, conf_status_label. A label column is null when Eurostat publishes no label for that code. Rows of a DS-* dataset also carry obs_value_text after obs_value: a value Eurostat published as text, such as a PRODCOM quantity unit (\"KG\"), kept verbatim with obs_value null; a PRODCOM \":C\" arrives as conf_status \"C\" instead.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": [
"string",
"number",
"null"
]
}
},
"description": "The first preview_limit rows of the download, in the order Eurostat streamed them. A prefix of the staged table, not a sample."
},
"budgetExceeded": {
"type": "boolean",
"description": "True when the byte budget stopped the transfer before the dataset ended, making the rows a prefix rather than the whole thing. Narrow with filters or a period range, or raise EUROSTAT_BULK_MAX_BYTES."
},
"dimensionsUsed": {
"type": "array",
"items": {
"type": "string"
},
"description": "Dimension codes carried by the downloaded rows, in the order Eurostat keys them (e.g., [\"freq\", \"unit\", \"na_item\", \"geo\"]). Read from the TSV header, so it reflects the response rather than metadata. The period lives in the separate \"time\" column."
},
"stagedRowCount": {
"type": "number",
"description": "Rows written to the canvas table. Matches rowCount. Omitted alongside tableName."
},
"absentCellCount": {
"type": "number",
"description": "Unflagged \":\" rows — obs_value null with no flag, status or text — at a period of a frequency the same series carries in another downloaded row: periods that series lacks, where Eurostat publishes no observation. Unflagged rows at the periods of a frequency the series has no observation of (the padding of a mixed-frequency slice, or a series that is \":\" throughout) are left out, and a flagged \":\" or a text value is an observation, never counted. Part of rowCount and missingCount, and counted over the rows downloaded, so a budget-cut download counts its prefix."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true
}eurostat_dataframe_describe — Describe Eurostat Dataframes
List the tables staged on a Eurostat dataframe canvas, with their row counts and column names and types. Call this before eurostat_dataframe_query to learn the table and column names to write SQL against. The canvas_id comes from a eurostat_query_dataset or eurostat_download_dataset response that reported a staged table. Three tools stage tables, and they write different columns, so read the columns reported here rather than assuming. The two observation stagers keep every column flat: eurostat_query_dataset gives each dimension a code column named after the dimension (e.g. "geo") plus a label companion (e.g. "geo_label"); eurostat_download_dataset gives code columns only — the bulk endpoint carries no labels — plus a "time" column. Both write the same five measure columns — obs_value, obs_flag, obs_flag_label, conf_status, conf_status_label — carrying the same codes for the same observation, so their tables join on dimension codes and time and compare like with like. A table staged from a DS-* dataset (Comext detailed trade, PRODCOM) adds obs_value_text, holding a value Eurostat published as text, such as a PRODCOM quantity unit. eurostat_get_dimension_values, given a canvas_id, stages one dimension's value list as two columns, code and label, which labels a download's code column through a join (e.g. d.geo = g.code).
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"canvas_id"
],
"properties": {
"canvas_id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{10}$",
"description": "Canvas identifier returned as canvasId by eurostat_query_dataset, eurostat_download_dataset, or eurostat_get_dimension_values. Identifies the workspace holding the staged tables."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"canvasId",
"expiresAt",
"tables"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"canvas_disabled",
"canvas_not_found"
],
"description": "Machine-readable failure mode. Declared by this tool: `canvas_disabled`: This deployment runs without a dataframe canvas, so there is nothing to describe. `canvas_not_found`: The canvas_id is unknown or its lifetime has elapsed. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"notice": {
"type": "string",
"description": "Guidance when the canvas holds no tables. Omitted when it holds at least one."
},
"tables": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"kind",
"rowCount",
"columns"
],
"properties": {
"kind": {
"enum": [
"table",
"view"
],
"type": "string",
"description": "Whether the entry is a base table or a view."
},
"name": {
"type": "string",
"description": "Table name to reference in SQL."
},
"columns": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"type",
"nullable"
],
"properties": {
"name": {
"type": "string",
"description": "Column name."
},
"type": {
"type": "string",
"description": "SQL column type (e.g. \"VARCHAR\", \"DOUBLE\")."
},
"nullable": {
"type": "boolean",
"description": "Whether the column admits NULL."
}
},
"description": "One column of the table.",
"additionalProperties": false
},
"description": "Columns in declaration order."
},
"rowCount": {
"type": "number",
"description": "Rows the table holds."
},
"expiresAt": {
"type": "string",
"description": "ISO 8601 expiry of this table specifically. Omitted when the table ages with the canvas rather than on its own clock."
}
},
"description": "One table or view staged on the canvas.",
"additionalProperties": false
},
"description": "Tables staged on this canvas. Empty when nothing has been staged yet, or when every staged table has expired."
},
"canvasId": {
"type": "string",
"description": "Canvas identifier the tables were read from."
},
"expiresAt": {
"type": "string",
"description": "ISO 8601 timestamp when the canvas expires. Every call on it slides this forward."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true
}eurostat_dataframe_query — Query Eurostat Dataframes
Run a read-only SQL SELECT against tables staged on a Eurostat dataframe canvas, reaching observations beyond an inline preview and aggregating or joining tables without re-fetching Eurostat. Call eurostat_dataframe_describe first for table and column names. Only a single SELECT runs: statement chaining, non-SELECT verbs, and functions reading files or external data are rejected. Columns are flat: dimension codes, obs_value, obs_flag / obs_flag_label, and conf_status / conf_status_label. Tables eurostat_query_dataset staged also carry dimension _label companions; DS-* tables also carry obs_value_text for published text values. Join observation tables on dimension codes and time. A staged dimension value list has code and label columns; join it to an observation dimension code to add labels. Returns every complete row the canvas supplies, bounded only by the canvas row limit; nested cells render as lossless JSON. A row-limit cut sets truncated and adds a notice: page the same deterministic SELECT over unchanged tables with a total ORDER BY and LIMIT/OFFSET, advancing by actual rowCount. Restart an unordered first page sorted at offset zero; tied keys need tie-breakers. Volatile SQL and changing or expired tables cannot provide stable paging.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"canvas_id",
"sql"
],
"properties": {
"sql": {
"type": "string",
"minLength": 1,
"description": "A single read-only SELECT statement. Reference tables by the names eurostat_dataframe_describe reports. Example: SELECT geo, geo_label, AVG(obs_value) AS mean FROM df_a1b2c3d4 WHERE time >= '2020' GROUP BY geo, geo_label ORDER BY mean DESC."
},
"canvas_id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{10}$",
"description": "Canvas identifier returned as canvasId by eurostat_query_dataset, eurostat_download_dataset, or eurostat_get_dimension_values. Identifies the workspace holding the staged tables."
}
},
"additionalProperties": false
}{
"type": "object",
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"canvasId",
"columns",
"rows",
"rowCount",
"truncated"
]
},
{
"required": [
"error"
]
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"rows": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"description": "Complete result rows, keyed by column name, bounded by the canvas row limit. Nested cells render as JSON; 64-bit integers arrive as strings so they survive intact. Cast to DOUBLE in SQL if a number is wanted."
},
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"examples": [
"canvas_disabled",
"canvas_not_found",
"missing_table"
],
"description": "Machine-readable failure mode. Declared by this tool: `canvas_disabled`: This deployment runs without a dataframe canvas, so there is nothing to query. `canvas_not_found`: The canvas_id is unknown or its lifetime has elapsed. `missing_table`: The SQL names a table that is not staged on this canvas, or that has expired. Other values are possible when a failure originates below the handler."
},
"recovery": {
"type": "object",
"required": [
"hint"
],
"properties": {
"hint": {
"type": "string"
}
},
"description": "Actionable next step for the caller.",
"additionalProperties": {}
},
"retryable": {
"type": "boolean",
"description": "Whether retrying may succeed."
}
},
"additionalProperties": {}
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
}
},
"description": "Present when the call failed. Absent on success.",
"additionalProperties": {}
},
"notice": {
"type": "string",
"description": "Whole-row cut disclosure and deterministic SQL paging guidance. Omitted when no rows were omitted."
},
"columns": {
"type": "array",
"items": {
"type": "string"
},
"description": "Column names in projection order."
},
"canvasId": {
"type": "string",
"description": "Canvas identifier the query ran against."
},
"rowCount": {
"type": "number",
"description": "Complete rows returned after the canvas row limit. Advance SQL OFFSET by this actual count."
},
"truncated": {
"type": "boolean",
"description": "True when the canvas row limit omitted rows. Follow notice for SQL paging."
}
},
"additionalProperties": false
}{
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true
}Prompts
0 advertised
Resources
1 advertised
eurostat-guide
Use with eurostat_* tools to compare countries or regions, analyse trade, resolve statistical codes, and query staged observations with SQL. Apply coverage and comparability checks before ranking, aggregating, or reporting missing data.
skill://eurostat-guide/SKILL.mdeurostat-dataset-info — eurostat-dataset-info
Dataset metadata for a Eurostat dataset — dimensions, time range, observation count, and last-updated date. Equivalent to eurostat_get_dataset_info but accessible as a resource URI for cache-injectable context. A DS-* code (detailed trade and PRODCOM) is read from the Comext dissemination host, which reports no time range or observation count.
eurostat://dataset/{dataset_code}io.modelcontextprotocol/skills
skill://{+path}90-day uptime
A lightweight connection check about every six hours, sampled on hosts with many listings. It shows the server answered, not that its tools work.
- 2026-11-07: 1 of 1 checks answered
30-day availability
Aggregated from read-only remote checks; this is not a tool-execution or security guarantee.
- Successful checks
- 1 of 1 (100%)
- Average latency
- 755 ms
- 95th percentile latency
- 755 ms
Maintenance history
Public check and source-refresh events. Editorial and ownership records remain private.
- · probe · passed · 755 ms · metadata changed · 8 tool(s), 1 resource(s), 2 resource template(s), 0 prompt(s)
Connect from your client
Each snippet registers the same endpoint; pick the client you use.
Claude Code
claude mcp add --transport http eurostat-mcp-server https://eurostat.caseyjhand.com/mcpCodex
codex mcp add eurostat-mcp-server --url https://eurostat.caseyjhand.com/mcpCursor
{
"mcpServers": {
"eurostat-mcp-server": {
"url": "https://eurostat.caseyjhand.com/mcp"
}
}
}Cline
{
"mcpServers": {
"eurostat-mcp-server": {
"type": "streamableHttp",
"url": "https://eurostat.caseyjhand.com/mcp",
"disabled": false
}
}
}VS Code
{
"servers": {
"eurostat-mcp-server": {
"type": "http",
"url": "https://eurostat.caseyjhand.com/mcp"
}
}
}Gemini CLI
gemini mcp add --transport http eurostat-mcp-server https://eurostat.caseyjhand.com/mcpClaude Desktop
https://eurostat.caseyjhand.com/mcpWindsurf
{
"mcpServers": {
"eurostat-mcp-server": {
"serverUrl": "https://eurostat.caseyjhand.com/mcp"
}
}
}Let your agent find its tools
Find servers like eurostat-mcp-server from your agent. Add Koskamo once to search the index for endpoints, access requirements, and client setup. Public discovery needs no key.