{"name":"io.github.cyanheads/eurostat-mcp-server","slug":"cyanheads-eurostat-mcp-server","title":null,"description":"Search and query the Eurostat catalogue — EU economy, demography, trade, and NUTS regional data.","url":"https://mcp.market/server/cyanheads-eurostat-mcp-server","rating":null,"grade":"A","score":86,"certified":false,"status":"active","category":"search","tags":["search"],"presence":{"score":41,"stars":6,"forks":0,"downloads_week":421,"last_push_at":"2026-09-16T09:43:47.000Z","license":"Apache-2.0"},"uptime":null,"claimed":false,"transport":"mixed","callable_via_gateway":true,"default_price_micros":0,"repository":"https://github.com/cyanheads/eurostat-mcp-server","website":null,"version":"0.6.4","remotes":[{"type":"streamable-http","url":"https://eurostat.caseyjhand.com/mcp"}],"packages":[{"registryType":"npm","registryBaseUrl":"https://registry.npmjs.org","identifier":"@cyanheads/eurostat-mcp-server","version":"0.6.4","runtimeHint":"bun","transport":{"type":"stdio"},"packageArguments":[{"value":"run","type":"positional"},{"value":"start:stdio","type":"positional"}],"environmentVariables":[{"description":"Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').","format":"string","default":"info","name":"MCP_LOG_LEVEL"},{"description":"Eurostat API base URL. Override when using a mirror or proxy.","format":"string","default":"https://ec.europa.eu/eurostat/api/dissemination","name":"EUROSTAT_BASE_URL"},{"description":"HTTP request timeout in milliseconds for Eurostat API calls.","format":"string","default":"30000","name":"EUROSTAT_REQUEST_TIMEOUT_MS"},{"description":"How long a fetched catalogue TOC stays usable before the next catalogue call refreshes it, in milliseconds. Defaults to 12 hours.","format":"string","default":"43200000","name":"EUROSTAT_TOC_CACHE_TTL_MS"},{"description":"HTTP timeout for one eurostat_download_dataset transfer, in milliseconds. Held separate from EUROSTAT_REQUEST_TIMEOUT_MS because a bulk body streams for minutes where a metadata call answers in seconds. Defaults to 2 minutes.","format":"string","default":"120000","name":"EUROSTAT_BULK_TIMEOUT_MS"},{"description":"Byte budget for one eurostat_download_dataset transfer, counted on the decoded TSV rather than on the wire. Eurostat sends the body chunked with no Content-Length, so the budget is enforced while streaming and the transfer is aborted when it is reached. Defaults to 50 MiB.","format":"string","default":"52428800","name":"EUROSTAT_BULK_MAX_BYTES"},{"description":"Set to 'duckdb' to enable the dataframe canvas, which lists the eurostat_dataframe_describe and eurostat_dataframe_query tools, lets eurostat_query_dataset stage a match larger than its 5,000-row inline cap as a SQL table, and lets eurostat_download_dataset retain a whole bulk download instead of only its inline preview. The DuckDB binding ships with the server, so this variable is the only switch.","format":"string","default":"none","name":"CANVAS_PROVIDER_TYPE"},{"description":"Directory DuckDB writes canvas spill files to. Must be writable by the server process. Defaults to a 'mcp-canvas' directory under the OS temp directory.","format":"string","name":"CANVAS_TEMP_PATH"},{"description":"Sliding lifetime of a staged dataframe canvas in milliseconds; every call against it extends the window. Defaults to 24 hours.","format":"string","default":"86400000","name":"CANVAS_TTL_MS"},{"description":"Maximum rows a single eurostat_dataframe_query returns before the result is reported as truncated.","format":"string","default":"10000","name":"CANVAS_DEFAULT_ROW_LIMIT"}]},{"registryType":"npm","registryBaseUrl":"https://registry.npmjs.org","identifier":"@cyanheads/eurostat-mcp-server","version":"0.6.4","runtimeHint":"bun","transport":{"type":"streamable-http","url":"http://localhost:3010/mcp"},"packageArguments":[{"value":"run","type":"positional"},{"value":"start:http","type":"positional"}],"environmentVariables":[{"description":"The hostname for the HTTP server.","format":"string","default":"127.0.0.1","name":"MCP_HTTP_HOST"},{"description":"The port to run the HTTP server on.","format":"string","default":"3010","name":"MCP_HTTP_PORT"},{"description":"The endpoint path for the MCP server.","format":"string","default":"/mcp","name":"MCP_HTTP_ENDPOINT_PATH"},{"description":"Public origin override for deployments behind a TLS-terminating reverse proxy (e.g. https://mcp.example.com).","format":"string","name":"MCP_PUBLIC_URL"},{"description":"Authentication mode to use: 'none', 'jwt', or 'oauth'.","format":"string","default":"none","name":"MCP_AUTH_MODE"},{"description":"Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').","format":"string","default":"info","name":"MCP_LOG_LEVEL"},{"description":"Eurostat API base URL. Override when using a mirror or proxy.","format":"string","default":"https://ec.europa.eu/eurostat/api/dissemination","name":"EUROSTAT_BASE_URL"},{"description":"HTTP request timeout in milliseconds for Eurostat API calls.","format":"string","default":"30000","name":"EUROSTAT_REQUEST_TIMEOUT_MS"},{"description":"How long a fetched catalogue TOC stays usable before the next catalogue call refreshes it, in milliseconds. Defaults to 12 hours.","format":"string","default":"43200000","name":"EUROSTAT_TOC_CACHE_TTL_MS"},{"description":"HTTP timeout for one eurostat_download_dataset transfer, in milliseconds. Held separate from EUROSTAT_REQUEST_TIMEOUT_MS because a bulk body streams for minutes where a metadata call answers in seconds. Defaults to 2 minutes.","format":"string","default":"120000","name":"EUROSTAT_BULK_TIMEOUT_MS"},{"description":"Byte budget for one eurostat_download_dataset transfer, counted on the decoded TSV rather than on the wire. Eurostat sends the body chunked with no Content-Length, so the budget is enforced while streaming and the transfer is aborted when it is reached. Defaults to 50 MiB.","format":"string","default":"52428800","name":"EUROSTAT_BULK_MAX_BYTES"},{"description":"Set to 'duckdb' to enable the dataframe canvas, which lists the eurostat_dataframe_describe and eurostat_dataframe_query tools, lets eurostat_query_dataset stage a match larger than its 5,000-row inline cap as a SQL table, and lets eurostat_download_dataset retain a whole bulk download instead of only its inline preview. The DuckDB binding ships with the server, so this variable is the only switch.","format":"string","default":"none","name":"CANVAS_PROVIDER_TYPE"},{"description":"Directory DuckDB writes canvas spill files to. Must be writable by the server process. Defaults to a 'mcp-canvas' directory under the OS temp directory.","format":"string","name":"CANVAS_TEMP_PATH"},{"description":"Sliding lifetime of a staged dataframe canvas in milliseconds; every call against it extends the window. Defaults to 24 hours.","format":"string","default":"86400000","name":"CANVAS_TTL_MS"},{"description":"Maximum rows a single eurostat_dataframe_query returns before the result is reported as truncated.","format":"string","default":"10000","name":"CANVAS_DEFAULT_ROW_LIMIT"}]}],"tools":[{"name":"eurostat_browse_themes","description":"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"theme_code":{"description":"Folder code to expand (e.g., \"economy\", \"reg\"). Omit to list the top-level theme folders.","type":"string"}},"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"eurostat_dataframe_describe","description":"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. Every observation column is flat, but the two stagers write different dimension columns, so read the columns reported here rather than assuming: 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 tables from the two stagers join on dimension codes and time and compare like with like.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"canvas_id":{"type":"string","minLength":1,"description":"Canvas identifier returned as canvasId by eurostat_query_dataset or eurostat_download_dataset. Identifies the workspace holding the staged tables."}},"required":["canvas_id"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"eurostat_dataframe_query","description":"Run a read-only SQL SELECT against tables staged on a Eurostat dataframe canvas — the way to reach observations past the 5,000-row inline cap of eurostat_query_dataset and past the inline preview of a eurostat_download_dataset bulk download, and to aggregate, group, or join across staged tables without re-fetching from Eurostat. Call eurostat_dataframe_describe first for the table and column names, which differ between the two stagers. Only a single SELECT statement runs: statement chaining, non-SELECT verbs, and functions that read files or external data are rejected. Columns are flat — every dimension is a code column named after the dimension, the measure is obs_value, the observation flag is obs_flag / obs_flag_label and the confidentiality marker is conf_status / conf_status_label; a \"_label\" companion per dimension exists only on tables eurostat_query_dataset staged. Both stagers write the same five measure columns with the same codes, so join their tables on dimension codes and time and compare obs_flag or conf_status across them directly.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"canvas_id":{"type":"string","minLength":1,"description":"Canvas identifier returned as canvasId by eurostat_query_dataset or eurostat_download_dataset. Identifies the workspace holding the staged tables."},"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."}},"required":["canvas_id","sql"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"eurostat_download_dataset","description":"Download a Eurostat dataset in bulk through the SDMX 2.1 TSV endpoint and stage every observation 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 observation. 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 byte budget enforced while streaming: when it is spent the download stops and budgetExceeded is set, leaving a prefix of the dataset rather than an error. 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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"dataset_code":{"type":"string","minLength":1,"description":"Dataset code (e.g., \"nama_10_gdp\"). Required."},"filters":{"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. 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.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}},"since_period":{"description":"Start of the period range (e.g., \"2020\", \"2023-Q1\", \"2024-01\"), sent as startPeriod. The most effective way to shrink a bulk response: it removes period columns from the TSV rather than blanking their cells.","type":"string"},"until_period":{"description":"End of the period range (e.g., \"2024\"), sent as endPeriod. Omit for data through the latest available period.","type":"string"},"preview_limit":{"default":50,"description":"How many observations to echo inline, from the start of the download. Caps at 500. The full download is on the canvas table when one was staged; this is orientation, not the result set.","type":"integer","minimum":1,"maximum":500},"canvas_id":{"description":"Reuse an existing dataframe canvas so this download lands beside earlier results and can be joined against them. Pass a canvasId from a previous response; omit to start a fresh canvas. Ignored on deployments without a dataframe canvas.","type":"string"}},"required":["dataset_code"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"eurostat_get_dataset_info","description":"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.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"dataset_code":{"type":"string","minLength":1,"description":"Dataset code (e.g., \"nama_10_gdp\"). Use eurostat_search_datasets or eurostat_browse_themes to find codes."}},"required":["dataset_code"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"eurostat_get_dimension_values","description":"List all valid values for a specific dimension in a Eurostat dataset (e.g., all unit codes for nama_10_gdp, all geo codes for a regional dataset). Use this when eurostat_get_dataset_info returns more values than the 10-item sample, or to confirm exact codes before querying. For the \"geo\" dimension, use geo_level to filter by NUTS hierarchy (country, nuts1, nuts2, nuts3). Invalid dimension_value codes silently return no data from eurostat_query_dataset, and are rejected by Eurostat as a fault on eurostat_download_dataset; use this tool to verify codes first.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"dataset_code":{"type":"string","minLength":1,"description":"Dataset code (e.g., \"nama_10_gdp\")."},"dimension":{"type":"string","minLength":1,"description":"Dimension code to retrieve values for (e.g., \"unit\", \"na_item\", \"geo\"). Use eurostat_get_dataset_info to see available dimensions."},"geo_level":{"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).","type":"string","enum":["aggregate","country","nuts1","nuts2","nuts3"]}},"required":["dataset_code","dimension"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"eurostat_query_dataset","description":"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 periods without knowing the end date. Matches above 5,000 observations are staged whole when this deployment runs a dataframe canvas: call eurostat_dataframe_describe first, then eurostat_dataframe_query. Matches at or below 5,000 are never staged. 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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"dataset_code":{"type":"string","minLength":1,"description":"Dataset code (e.g., \"nama_10_gdp\"). Required."},"filters":{"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. Do not include \"geo\" here if using geo_level. Invalid dimension values silently return no data — verify with eurostat_get_dimension_values first.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}},"geo_level":{"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).","type":"string","enum":["aggregate","country","nuts1","nuts2","nuts3"]},"since_period":{"description":"Start of time range (e.g., \"2020\", \"2023-Q1\", \"2024-01\"). Mutually exclusive with last_n_periods.","type":"string"},"until_period":{"description":"End of time range (e.g., \"2024\"). Omit for data through the latest available period. Mutually exclusive with last_n_periods.","type":"string"},"last_n_periods":{"description":"Return only the N most recent periods. Mutually exclusive with since_period and until_period.","type":"integer","minimum":1,"maximum":9007199254740991},"preview_limit":{"default":50,"description":"How many matched observations to return inline, from the deterministic start of the JSON-stat cell order. Default 50; maximum 500. This changes only the inline prefix: it does not reduce obsCount, missingObsCount, timeRange, the upstream response, or the rows staged when the match exceeds 5,000. Use filters or period controls to reduce the match itself.","type":"integer","minimum":1,"maximum":500},"lang":{"default":"EN","description":"Language for labels in the response. Default is \"EN\". Options: \"EN\", \"FR\", \"DE\".","type":"string","enum":["EN","FR","DE"]},"canvas_id":{"description":"Reuse an existing dataframe canvas, so a result staged by this call lands beside earlier ones and can be joined against them. Pass a canvasId from a previous response; omit to start a fresh canvas. Ignored on deployments without a dataframe canvas and when the match is at or below 5,000 observations.","type":"string"}},"required":["dataset_code"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"eurostat_search_datasets","description":"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"query":{"type":"string","minLength":1,"pattern":"\\S","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. Word order does not matter, so \"business demography NUTS 3\" or \"regional economic accounts\" resolve without naming a label verbatim."},"limit":{"default":20,"description":"Page size — maximum datasets returned per page (1–100). Default is 20. To retrieve matches beyond one page, pass the returned nextCursor back as cursor; the page size is fixed by this first call.","type":"integer","minimum":1,"maximum":100},"cursor":{"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.","type":"string"}},"required":["query"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}}],"scan":{"score":86,"grade":"A","scanned_at":"2026-09-19T10:14:37.695Z","report":{"scannerVersion":"0.1.3","scannedAt":"2026-09-19T10:14:37.662Z","components":{"code":{"score":25,"max":25,"notes":["42 source files scanned","42 source files scanned"]},"reliability":{"score":20,"max":20,"notes":["remote reachable in 328ms"]},"poisoning":{"score":15,"max":15,"notes":["8 tool descriptions checked"]},"auth":{"score":3,"max":15,"notes":["open endpoint exposes 2 write-action tools with no auth"]},"maintenance":{"score":15,"max":15,"notes":["last push 3 days ago"]},"identity":{"score":8,"max":10,"notes":["registry namespace matches repository owner","GitHub account older than a year"]}},"findings":[{"id":"auth.open-write","severity":"high","component":"auth","title":"Write-action tools reachable without authentication"}],"inputs":{"probes":[{"url":"https://eurostat.caseyjhand.com/mcp","reachable":true,"authRequired":false,"latencyMs":328,"serverInfo":{"name":"eurostat-mcp-server","version":"0.6.4"}}],"packages":[{"registryType":"npm","identifier":"@cyanheads/eurostat-mcp-server","version":"0.6.4","found":true,"license":"Apache-2.0","hasInstallScripts":false,"dependencyCount":4,"publishedAt":"2026-09-16T09:38:06.583Z","repositoryUrl":"git+https://github.com/cyanheads/eurostat-mcp-server.git","weeklyDownloads":421},{"registryType":"npm","identifier":"@cyanheads/eurostat-mcp-server","version":"0.6.4","found":true,"license":"Apache-2.0","hasInstallScripts":false,"dependencyCount":4,"publishedAt":"2026-09-16T09:38:06.583Z","repositoryUrl":"git+https://github.com/cyanheads/eurostat-mcp-server.git"}],"repo":{"found":true,"owner":"cyanheads","repo":"eurostat-mcp-server","archived":false,"pushedAt":"2026-09-16T09:43:47Z","stars":6,"forks":0,"openIssues":7,"ownerType":"User","ownerAvatarUrl":"https://avatars.githubusercontent.com/u/10339515?v=4","ownerCreatedAt":"2014-12-29T13:01:12Z","license":"Apache-2.0"},"icon":{"url":"https://avatars.githubusercontent.com/u/10339515?v=4&s=128","source":"github"},"presence":{"stars":6,"forks":0,"downloadsWeek":421,"license":"Apache-2.0","lastPushAt":"2026-09-16T09:43:47.000Z","score":41}}}},"grade_history":[],"reviews":[]}