@cyanheads/eurostat-mcp-server 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/AGENTS.md +386 -0
  2. package/CLAUDE.md +32 -9
  3. package/Dockerfile +5 -5
  4. package/README.md +15 -13
  5. package/changelog/0.1.x/0.1.0.md +20 -0
  6. package/changelog/0.1.x/0.1.1.md +21 -0
  7. package/changelog/0.1.x/0.1.10.md +24 -0
  8. package/changelog/0.1.x/0.1.11.md +15 -0
  9. package/changelog/0.1.x/0.1.12.md +28 -0
  10. package/changelog/0.1.x/0.1.13.md +20 -0
  11. package/changelog/0.1.x/0.1.14.md +38 -0
  12. package/changelog/0.1.x/0.1.15.md +13 -0
  13. package/changelog/0.1.x/0.1.16.md +36 -0
  14. package/changelog/0.1.x/0.1.2.md +17 -0
  15. package/changelog/0.1.x/0.1.3.md +12 -0
  16. package/changelog/0.1.x/0.1.4.md +17 -0
  17. package/changelog/0.1.x/0.1.5.md +11 -0
  18. package/changelog/0.1.x/0.1.6.md +14 -0
  19. package/changelog/0.1.x/0.1.7.md +16 -0
  20. package/changelog/0.1.x/0.1.8.md +19 -0
  21. package/changelog/0.1.x/0.1.9.md +20 -0
  22. package/changelog/0.2.x/0.2.0.md +29 -0
  23. package/changelog/0.3.x/0.3.0.md +17 -0
  24. package/changelog/0.4.x/0.4.0.md +24 -0
  25. package/changelog/0.5.x/0.5.0.md +20 -0
  26. package/changelog/0.6.x/0.6.0.md +20 -0
  27. package/changelog/0.6.x/0.6.1.md +30 -0
  28. package/changelog/0.6.x/0.6.2.md +21 -0
  29. package/changelog/template.md +168 -0
  30. package/dist/mcp-server/resources/definitions/eurostat-dataset.resource.js +2 -2
  31. package/dist/mcp-server/resources/definitions/eurostat-dataset.resource.js.map +1 -1
  32. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts +3 -0
  33. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts.map +1 -1
  34. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js +16 -4
  35. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js.map +1 -1
  36. package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js +2 -2
  37. package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js.map +1 -1
  38. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.d.ts +13 -1
  39. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.d.ts.map +1 -1
  40. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js +24 -3
  41. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js.map +1 -1
  42. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts +4 -3
  43. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts.map +1 -1
  44. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js +40 -21
  45. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js.map +1 -1
  46. package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts.map +1 -1
  47. package/dist/services/eurostat-bulk/eurostat-bulk-service.js +1 -4
  48. package/dist/services/eurostat-bulk/eurostat-bulk-service.js.map +1 -1
  49. package/dist/services/eurostat-catalogue/eurostat-catalogue-service.d.ts.map +1 -1
  50. package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js +3 -6
  51. package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js.map +1 -1
  52. package/dist/services/eurostat-data/eurostat-data-service.d.ts +17 -55
  53. package/dist/services/eurostat-data/eurostat-data-service.d.ts.map +1 -1
  54. package/dist/services/eurostat-data/eurostat-data-service.js +90 -160
  55. package/dist/services/eurostat-data/eurostat-data-service.js.map +1 -1
  56. package/dist/services/eurostat-data/sdmx-metadata.d.ts +16 -0
  57. package/dist/services/eurostat-data/sdmx-metadata.d.ts.map +1 -0
  58. package/dist/services/eurostat-data/sdmx-metadata.js +330 -0
  59. package/dist/services/eurostat-data/sdmx-metadata.js.map +1 -0
  60. package/dist/services/eurostat-data/types.d.ts +21 -19
  61. package/dist/services/eurostat-data/types.d.ts.map +1 -1
  62. package/dist/services/eurostat-data/types.js +3 -3
  63. package/dist/services/eurostat-data/types.js.map +1 -1
  64. package/package.json +20 -12
  65. package/server.json +3 -3
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
 
11
11
 
12
- [![Version](https://img.shields.io/badge/Version-0.6.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/eurostat-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.30.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/eurostat-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/eurostat-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.14-blueviolet.svg?style=flat-square)](https://bun.sh/)
12
+ [![Version](https://img.shields.io/badge/Version-0.6.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/eurostat-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/eurostat-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/eurostat-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
13
13
 
14
14
  </div>
15
15
 
@@ -39,7 +39,7 @@
39
39
  | `eurostat_browse_themes` | Navigate the Eurostat theme hierarchy — list root themes or drill into subthemes and datasets |
40
40
  | `eurostat_get_dataset_info` | Fetch metadata for a dataset: dimensions with sample values, time range, observation count, and last-update date |
41
41
  | `eurostat_get_dimension_values` | List all valid codes for a specific dimension (e.g., all geo codes, all unit codes); supports NUTS hierarchy filtering |
42
- | `eurostat_query_dataset` | Fetch decoded statistical observations with dimension filters, NUTS geo-level, and time-range controls |
42
+ | `eurostat_query_dataset` | Fetch a deterministic preview of decoded statistical observations with dimension filters, NUTS geo-level, and time-range controls |
43
43
  | `eurostat_download_dataset` | Download a whole dataset through the SDMX 2.1 TSV bulk endpoint and stage every observation on the dataframe canvas |
44
44
  | `eurostat_dataframe_describe` | List the tables staged on a dataframe canvas with their row counts and column types — canvas only |
45
45
  | `eurostat_dataframe_query` | Run a read-only SQL SELECT across staged tables — canvas only |
@@ -77,7 +77,7 @@ Fetch metadata for a Eurostat dataset before querying it.
77
77
 
78
78
  - Returns all dimensions with their codes, labels, and up to 10 sample values each
79
79
  - Reports overall time range and total observation count across all periods, each omitted when Eurostat does not report it
80
- - Uses a minimal Statistics API call (most recent period only), plus one bounded follow-up to count the dataset's periods when it has a `time` dimension. If that follow-up fails, the call still returns everything the first request produced, with the `time` dimension's value count omitted rather than reported as 1
80
+ - Combines the dataset-scoped SDMX dataflow descendants with its content constraint, so every dimension — including `time` — is described from the full dataset-available value set rather than from a populated observation slice
81
81
  - For dimensions with more than 10 values, use `eurostat_get_dimension_values` for the full list
82
82
  - Provides a link to the ESMS metadata page when available
83
83
 
@@ -87,8 +87,8 @@ Fetch metadata for a Eurostat dataset before querying it.
87
87
 
88
88
  List all valid values for a specific dataset dimension.
89
89
 
90
- - Retrieves the complete set of valid codes and labels for any dimension (unit, na_item, geo, etc.)
91
- - For the `geo` dimension, supports NUTS hierarchy filtering: `aggregate` (EU/EA totals), `country` (41 states), `nuts1` (127 major regions), `nuts2` (309 basic regions), `nuts3` (1,343 small regions). Pairing it with any other dimension is rejected rather than ignored
90
+ - Retrieves the complete dataset-available set of codes and labels for any dimension (`unit`, `na_item`, `geo`, `time`, etc.) from the same SDMX content constraint used by dataset metadata
91
+ - For the `geo` dimension, supports NUTS hierarchy filtering: `aggregate` (EU/EA totals), `country` (the default when omitted), `nuts1`, `nuts2`, and `nuts3`. The response reports the effective level; an empty level is `no_results`, not a claim that the dataset is missing. Pairing `geo_level` with any other dimension is rejected rather than ignored
92
92
  - Prevents silent no-data returns — invalid dimension values in `eurostat_query_dataset` return nothing without error; verify codes here first
93
93
 
94
94
  ---
@@ -100,10 +100,11 @@ Fetch statistical data from a Eurostat dataset.
100
100
  - Accepts dimension filters as a map of `{dimension_code: [value1, value2, ...]}`
101
101
  - NUTS geo-level filter (`aggregate`, `country`, `nuts1`, `nuts2`, `nuts3`) — mutually exclusive with a non-empty `geo` entry in filters; an empty array is treated as no filter and dropped
102
102
  - Time range via `since_period`/`until_period` (e.g., `"2020"`, `"2023-Q1"`) or `last_n_periods` for the N most recent
103
+ - `preview_limit` controls the deterministic inline prefix (default 50, max 500). It does not change the match, totals, period coverage, or staged rows; filters and period controls reduce the match itself. There is deliberately no cursor or offset
103
104
  - Returns decoded observations with dimension codes and labels, numeric values, an `OBS_FLAG` status (`p` = provisional, `e` = estimated, etc.) and a separate `CONF_STATUS` confidentiality marker (`C` = confidential, usually the reason a value is null)
104
105
  - Reports total observation count, missing value count, and the effective time range of the result, each period bound omitted when neither the observations nor Eurostat report it
105
- - Inline rows are capped at 5,000, applied while decoding so a broad query never builds the rest; `obsCount`, `missingObsCount` and `timeRange` still describe the whole match, and `truncated` flags when the cap bit. Filter the query to shrink what Eurostat sends — the cap bounds the decode, not the transfer
106
- - With the dataframe canvas enabled, a match past the cap is also staged whole as a SQL table and the response returns `canvasId` / `tableName` / `stagedRowCount`; the rows are streamed into the table one at a time from the response body already in memory, so nothing extra is fetched and the match is never materialized as an array. Without a canvas those fields are absent and narrowing the query is the way to the rest
106
+ - `obsCount`, `missingObsCount`, and `timeRange` always describe the full match. `truncated` is independent of `preview_limit` and is true only when the match crosses the 5,000-observation staging threshold
107
+ - With the dataframe canvas enabled, only a match above 5,000 observations is staged whole as a SQL table and the response returns `canvasId` / `tableName` / `stagedRowCount`; matches at or below 5,000 are never staged. The rows stream into the table one at a time from the response body already in memory, so nothing extra is fetched and the match is never materialized as an array. Call `eurostat_dataframe_describe` before `eurostat_dataframe_query`. Without a canvas those fields and tool guidance are absent, and narrowing the query is the way to the rest
107
108
  - Pass `canvas_id` from an earlier response to stage several results side by side and join across them
108
109
  - Async-response detection — large unfiltered queries return an actionable, non-retryable error with filter guidance rather than silently timing out
109
110
  - Fetches a *slice*. When the target is a whole dataset, `eurostat_download_dataset` reads the SDMX bulk endpoint instead, at roughly half the bytes
@@ -121,7 +122,7 @@ Download a whole dataset through the SDMX 2.1 TSV bulk endpoint (`/sdmx/2.1/data
121
122
  - **gzip is sniffed off the stream, not read from headers.** Eurostat compresses large bodies with no `Content-Encoding` header; the only header-level tell is a `.tsv.gz` filename on `Content-Disposition`, and the switch does not track dataset size, so the magic bytes are what decide
122
123
  - **The asynchronous queue envelope is detected explicitly.** When an extraction is too costly to serve inline Eurostat answers HTTP 200 with a SOAP `syncResponse` ticket instead of data; read as TSV that yields a header row of XML and no observations, so it is classified up front as a non-retryable error naming what to narrow
123
124
  - Errors arrive as XML SOAP faults, not JSON: faultcode 100 → `not_found`, 140 → `filter_arity`, 150 → `invalid_dimension` (which also covers a period range outside the dataset's coverage). Each maps to a typed reason with a recovery hint naming the tool to call next
124
- - With the dataframe canvas enabled, every observation is staged as a SQL table and the response returns `canvasId` / `tableName` / `stagedRowCount`; rows stream into the table one at a time, so a multi-million-row download never materializes as an array. Only `preview_limit` rows (default 50, max 500) come back inline, and they are the leading rows of the staged table
125
+ - With the dataframe canvas enabled, every observation is staged as a SQL table and the response returns `canvasId` / `tableName` / `stagedRowCount`; rows stream into the table one at a time, so a multi-million-row download never materializes as an array. Only `preview_limit` rows (default 50, max 500) come back inline, and they are the leading rows of the staged table. Call `eurostat_dataframe_describe` before `eurostat_dataframe_query`
125
126
  - Without a canvas the download still runs so `rowCount`, `missingCount` and `periodRange` describe it, but only the preview is retained — the response says so plainly instead of implying the rest is reachable
126
127
 
127
128
  ---
@@ -157,10 +158,11 @@ Eurostat-specific:
157
158
 
158
159
  - TTL-bounded in-memory cache for the TOC file — reused across all search and browse calls, refreshed on the first call past its 12-hour lifetime, with the last loaded copy served if a refresh fails
159
160
  - JSON-stat 2.0 stride-based decoder for the Statistics API response format
160
- - Async-response detection — Eurostat returns a warning object rather than an error for over-limit queries; the server intercepts it and returns an actionable error with filter guidance
161
+ - Dataset-scoped SDMX metadata parsing — dataflow descendants supply labels, annotations, concepts, and partial codelists; the content constraint supplies the actual available positions, including every period
162
+ - Async-response detection — Eurostat returns over-limit queries as either an HTTP-200 warning object or an HTTP-413 error array; both fail once with actionable filter guidance
161
163
  - NUTS hierarchy geo-level filtering across query and dimension-value tools
162
164
  - Status decoding against both published codelists — the `OBS_FLAG` observation flag (provisional, estimated, definition differs) and the `CONF_STATUS` confidentiality marker, each in its own field. JSON-stat folds the two into one string and SDMX TSV into one cell; both are split on their separator, so a given observation reads the same whichever endpoint served it
163
- - Optional DuckDB dataframe canvas — a query matching more than the inline cap is streamed row by row into a SQL table, reaching the observations the cap drops without a second request to Eurostat
165
+ - Optional DuckDB dataframe canvas — a query matching more than 5,000 observations is streamed row by row into a SQL table, reaching the rows outside the inline preview without a second request to Eurostat
164
166
  - SDMX 2.1 TSV bulk downloads with streaming gzip detection, a mid-transfer byte budget, wide-to-long expansion, and SOAP fault classification — the whole-dataset counterpart to the per-query path
165
167
 
166
168
  Agent-friendly output:
@@ -289,7 +291,7 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
289
291
  | `EUROSTAT_TOC_CACHE_TTL_MS` | Catalogue TOC cache lifetime in ms — the first search or browse call past this age refreshes it | `43200000` (12 hours) |
290
292
  | `EUROSTAT_BULK_TIMEOUT_MS` | HTTP timeout for one `eurostat_download_dataset` transfer in ms — held separate because a bulk body streams for minutes | `120000` (2 minutes) |
291
293
  | `EUROSTAT_BULK_MAX_BYTES` | Byte budget for one bulk download, counted on the decoded TSV and enforced while streaming | `52428800` (50 MiB) |
292
- | `CANVAS_PROVIDER_TYPE` | `duckdb` enables the dataframe canvas: lists the two dataframe tools, lets `eurostat_query_dataset` stage a match past its inline cap, and lets `eurostat_download_dataset` retain a bulk download | `none` |
294
+ | `CANVAS_PROVIDER_TYPE` | `duckdb` enables the dataframe canvas: lists the two dataframe tools, lets `eurostat_query_dataset` stage a match above 5,000 observations, and lets `eurostat_download_dataset` retain a bulk download | `none` |
293
295
  | `CANVAS_TEMP_PATH` | Directory DuckDB writes canvas spill files to. Must be writable by the server process | `<os tmpdir>/mcp-canvas` |
294
296
  | `CANVAS_TTL_MS` | Sliding lifetime of a staged canvas in ms; every call against it extends the window | `86400000` (24 hours) |
295
297
  | `CANVAS_DEFAULT_ROW_LIMIT` | Max rows one `eurostat_dataframe_query` returns before reporting `truncated` | `10000` |
@@ -321,10 +323,10 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
321
323
 
322
324
  | Directory | Purpose |
323
325
  |:---|:---|
324
- | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Five tools for discovery and data access, plus two canvas-gated dataframe tools. |
326
+ | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Six tools for discovery and data access, plus two canvas-gated dataframe tools. |
325
327
  | `src/mcp-server/resources` | Resource definitions. Dataset metadata resource. |
326
328
  | `src/services/eurostat-catalogue` | Catalogue service — fetches and parses the Eurostat TOC TXT file; TTL-bounded in-memory cache. |
327
- | `src/services/eurostat-data` | Data service — Statistics API HTTP client, JSON-stat 2.0 decoder, async-response detection, dataframe row source. |
329
+ | `src/services/eurostat-data` | Data service — dataset-scoped SDMX metadata parser plus Statistics API querying, JSON-stat 2.0 decoding, async-response detection, and dataframe row source. |
328
330
  | `src/services/canvas-accessor.ts` | Module-level accessor for the optional DataCanvas, plus the acquire helper that names the misconfigured path on a permission failure. |
329
331
  | `src/config` | Server-specific environment variable parsing and validation with Zod. |
330
332
  | `tests/` | Unit and integration tests, mirroring the `src/` structure. |
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "Initial release — Eurostat statistical data server with 5 tools and 1 resource"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.0 — 2026-05-23
8
+
9
+ ## Added
10
+
11
+ - **`eurostat_search_datasets`** — keyword search across 8,933 Eurostat datasets using the session-cached TOC TXT file
12
+ - **`eurostat_browse_themes`** — tree navigation of the Eurostat theme hierarchy (11 root themes, hierarchical subthemes)
13
+ - **`eurostat_get_dataset_info`** — dataset metadata including dimensions, valid values, time range, obs count, and last-updated date
14
+ - **`eurostat_get_dimension_values`** — full list of valid values for a specific dataset dimension; supports NUTS geo-level filtering
15
+ - **`eurostat_query_dataset`** — decoded JSON-stat 2.0 observations with dimension filters, NUTS geo-level, and time-range support
16
+ - **`eurostat://dataset/{dataset_code}`** resource — dataset metadata accessible by URI
17
+ - `EurostatCatalogueService` — fetches and parses the TOC TXT file; session-level in-memory cache for search and tree navigation
18
+ - `EurostatDataService` — Statistics API HTTP client with JSON-stat 2.0 stride-based decoder, async-response detection, and retry logic
19
+ - `EUROSTAT_BASE_URL` env var — override API base URL (default: `https://ec.europa.eu/eurostat/api/dissemination`)
20
+ - `EUROSTAT_REQUEST_TIMEOUT_MS` env var — HTTP request timeout in ms (default: `30000`)
@@ -0,0 +1,21 @@
1
+ ---
2
+ summary: "First public release — 5 tools, 1 resource, full Eurostat implementation covering 8,900+ datasets and NUTS regional data"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.1 — 2026-05-23
8
+
9
+ ## Added
10
+
11
+ - **`eurostat_search_datasets`** — keyword search across 8,933 Eurostat datasets using a session-cached TOC TXT file
12
+ - **`eurostat_browse_themes`** — hierarchical navigation of the Eurostat theme tree (11 root themes)
13
+ - **`eurostat_get_dataset_info`** — dataset metadata: dimensions, valid values, time range, obs count, last-updated date
14
+ - **`eurostat_get_dimension_values`** — full value list for a specific dataset dimension; supports NUTS geo-level filtering
15
+ - **`eurostat_query_dataset`** — decoded JSON-stat 2.0 observations with dimension filters, NUTS geo-level, and time-range support
16
+ - **`eurostat://dataset/{dataset_code}`** resource — dataset metadata accessible by URI
17
+ - `EurostatCatalogueService` — fetches and parses the TOC TXT file with session-level in-memory cache
18
+ - `EurostatDataService` — Statistics API HTTP client with JSON-stat 2.0 stride decoder, async-response polling, and retry logic
19
+ - `EUROSTAT_BASE_URL` env var — override API base URL (default: `https://ec.europa.eu/eurostat/api/dissemination`)
20
+ - `EUROSTAT_REQUEST_TIMEOUT_MS` env var — HTTP request timeout in ms (default: `30000`)
21
+ - 37 tests across all tools and the dataset resource
@@ -0,0 +1,24 @@
1
+ ---
2
+ summary: "@cyanheads/mcp-ts-core ^0.9.16 → ^0.9.21 — per-request log context fix, secret-stripping in fetch errors, withRetry fail-fast; skill sync (api-mirror, orchestrations, 8 updated)"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.10 — 2026-06-02
8
+
9
+ ## Changed
10
+
11
+ - **`@cyanheads/mcp-ts-core`** `^0.9.16` → `^0.9.21` — adopts three framework fixes:
12
+ - Per-request logs and traces now carry fresh request + trace/span IDs instead of the frozen boot context (HTTP transport log context fix).
13
+ - **`fetchWithTimeout`** strips query-string secrets (e.g. `?api_key=`) from error messages and logs.
14
+ - **`withRetry`** fails fast on non-retryable errors; `ctx.fail` auto-populates the `retryable` flag.
15
+ - **Skills synced** — 8 updated (`add-tool`, `api-canvas`, `api-context`, `api-linter`, `api-utils`, `design-mcp-server`, `release-and-publish`, `add-service`); new `api-mirror` and `orchestrations` skills added.
16
+ - **`scripts/release-github.ts`** added — `bun run release:github` creates the GitHub Release from the latest annotated tag.
17
+ - **`scripts/devcheck.ts`** updated — consolidated devcheck script.
18
+ - **README client-config keys** renamed to full package name `eurostat-mcp-server` (was `eurostat`).
19
+
20
+ ## Dependencies
21
+
22
+ - `@cyanheads/mcp-ts-core` `^0.9.16` → `^0.9.21`
23
+ - `tsx` `^4.22.3` → `^4.22.4`
24
+ - `vitest` `^4.1.7` → `^4.1.8`
@@ -0,0 +1,15 @@
1
+ ---
2
+ summary: "Populate data.recovery.hint via ctx.fail in all four tool handlers — agents now get actionable next-step guidance on every declared error path"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.11 — 2026-06-04
8
+
9
+ ## Fixed
10
+
11
+ - **`eurostat_browse_themes`** — handler now catches `not_found` from the service and re-throws via `ctx.fail`, populating `data.recovery.hint` with navigation guidance back to root themes ([#10](https://github.com/cyanheads/eurostat-mcp-server/issues/10)).
12
+ - **`eurostat_get_dataset_info`** — handler catches `not_found` and `async_response` and re-throws via `ctx.fail` with contextual recovery hints (search suggestion; retry note for the unexpected async path) ([#10](https://github.com/cyanheads/eurostat-mcp-server/issues/10)).
13
+ - **`eurostat_get_dimension_values`** — handler catches `not_found` and re-throws via `ctx.fail` with a hint pointing to `eurostat_search_datasets` and `eurostat_get_dataset_info` ([#10](https://github.com/cyanheads/eurostat-mcp-server/issues/10)).
14
+ - **`eurostat_query_dataset`** — handler catches all four declared error reasons (`no_results`, `async_response`, `not_found`, `invalid_dimension`) and re-throws via `ctx.fail` with dataset-contextual recovery hints; `conflicting_params` continues to fall through ([#11](https://github.com/cyanheads/eurostat-mcp-server/issues/11)).
15
+ - **Tests** — updated and extended to assert `data.recovery.hint` is populated on every error path; added new test cases for `async_response` in `eurostat_get_dataset_info` and `invalid_dimension` in `eurostat_query_dataset`.
@@ -0,0 +1,28 @@
1
+ ---
2
+ summary: "Adopt @cyanheads/mcp-ts-core ^0.10.6: server identity name/title, Docker HEALTHCHECK + image.version label, bundle cleaner for dependency-shipped agent docs, packaging/antipattern lint additions."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.12 — 2026-06-15
8
+
9
+ ## Added
10
+
11
+ - **Server identity** — `createApp()` now sets `name` and `title` to `eurostat-mcp-server`, pinning the display identity to the unscoped repo name on every client surface ([core #213](https://github.com/cyanheads/mcp-ts-core/issues/213)).
12
+ - **`Dockerfile` `HEALTHCHECK`** — bun-native `fetch` against `/healthz` (slim image ships no curl/wget), plus `org.opencontainers.image.version` stamped from the `APP_VERSION` build arg.
13
+ - **`scripts/clean-mcpb.ts`** — post-pack step that strips dependency-shipped agent docs (`node_modules/**/skills/`, `.claude/`, `.agents/`, `SKILL.md`) from the `.mcpb` bundle; wired into the `bundle` script ([core #230](https://github.com/cyanheads/mcp-ts-core/issues/230)).
14
+
15
+ ## Changed
16
+
17
+ - **`@cyanheads/mcp-ts-core`** `^0.9.21` → `^0.10.6` — env booleans now parse via Zod `stringbool` (rejects values outside `true/false/1/0/yes/no/on/off`); DataCanvas SQL gate fails closed on non-SELECT and denies `pragma_*`/system-catalog table functions; wire-level elicitation + URL-mode elicitation; `outlineOnOverflow()` and `ctx.enrich.truncated()` utilities; `RootsRegistry` and the sampling surface removed. No server code depends on the removed surfaces.
18
+ - **`.mcpbignore`** — root dev-dir patterns anchored with `/` (`/skills/`, `/.agents/`, `/.claude/`) so they no longer also strip nested runtime paths like `node_modules/x/skills/` ([core #172](https://github.com/cyanheads/mcp-ts-core/issues/172), [#207](https://github.com/cyanheads/mcp-ts-core/issues/207)).
19
+ - **`scripts/lint-packaging.ts`** — adds bundle-content guards (unanchored-pattern and critical-runtime-path checks, post-bundle `node_modules` agent-doc scan) and an entrypoint identity check that `name`/`title` equal the unscoped package name ([core #230](https://github.com/cyanheads/mcp-ts-core/issues/230), [#231](https://github.com/cyanheads/mcp-ts-core/issues/231)).
20
+ - **`scripts/check-framework-antipatterns.ts`** — adds the `coerce-boolean-env-flag` rule flagging `z.coerce.boolean()` on env flags (use `z.stringbool()`), and skips comment-line matches.
21
+ - **Skills** — re-synced from the framework; adds the `techniques` skill (outline-on-overflow pattern catalog).
22
+
23
+ ## Dependencies
24
+
25
+ - `@biomejs/biome` `^2.4.16` → `^2.5.0`
26
+ - `@types/node` `^25.9.1` → `^25.9.3`
27
+ - `tsx` `^4.22.3` → `^4.22.4`
28
+ - `vitest` `^4.1.8` → `^4.1.9`
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "Adopt @cyanheads/mcp-ts-core ^0.10.9: DataCanvas describe() binder-error fix, SQL-gate invalid_sql classification, ctx.content collector, two new devcheck guards (dependency specifiers, plugin-manifest correctness)."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.13 — 2026-06-20
8
+
9
+ ## Changed
10
+
11
+ - **`@cyanheads/mcp-ts-core`** `^0.10.6` → `^0.10.9` — `DuckdbProvider.describe({ tableName })` qualifies its `WHERE` filters with the table alias, fixing the DuckDB Binder Error (ambiguous `table_name`) that hit every filtered describe ([core #235](https://github.com/cyanheads/mcp-ts-core/issues/235)); the Canvas SQL gate now classifies a `SELECT`-shaped statement that parses but fails to prepare (mistyped column, unknown function) as `ValidationError` with `data.reason: 'invalid_sql'` and the DuckDB binder detail in `data.binderMessage`, instead of the misleading `non_select_statement` ([core #236](https://github.com/cyanheads/mcp-ts-core/issues/236)); adds `ctx.content` — an always-present collector for image/audio blocks that ride `content[]` only and never enter `structuredContent` ([core #239](https://github.com/cyanheads/mcp-ts-core/issues/239)); corrects `OTEL_SERVICE_NAME` seeding precedence (`createApp({ name })` seeds it only when unset) ([core #233](https://github.com/cyanheads/mcp-ts-core/issues/233)); biome `2.4.16` → `2.5.0` and fresh-scaffold devcheck guards that skip git-dependent checks cleanly when `.git` is absent ([core #243](https://github.com/cyanheads/mcp-ts-core/issues/243)).
12
+ - **`scripts/check-dependency-specifiers.ts`** — new devcheck step (`Dependency Specifiers`, `--no-dep-specifiers`) that hard-fails on floating specifiers (`latest`, `*`, pre-release dist-tags) in `package.json`'s dependency sections and `bun.lock`'s `workspaces` map; catches `bun update --latest` writing a literal `latest` dist-tag into the lock's workspace map, which would re-resolve the dep past the `package.json` range on the next install ([core #246](https://github.com/cyanheads/mcp-ts-core/issues/246)).
13
+ - **`scripts/lint-packaging.ts`** — adds plugin-marketplace manifest checks (check 10) validating `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json`: non-empty descriptions, the unscoped machine name in display fields, and the full scoped name in the `npx -y` install arg (an unscoped arg for a scoped package is a guaranteed install 404) ([core #240](https://github.com/cyanheads/mcp-ts-core/issues/240)).
14
+ - **`devcheck.config.json`** — enables `packaging.pluginManifests`, running the Packaging gate against the plugin manifests now that this server ships them.
15
+ - **Skills** — re-synced from the framework; seven `metadata.version` bumps propagate body drift (`api-auth`, `api-errors`, `api-services`, `api-telemetry`, `field-test`, `report-issue-local`, `tool-defs-analysis`) ([core #238](https://github.com/cyanheads/mcp-ts-core/issues/238)).
16
+
17
+ ## Dependencies
18
+
19
+ - `@cyanheads/mcp-ts-core` `^0.10.6` → `^0.10.9`
20
+ - `@types/node` `^25.9.3` → `^26.0.0`
@@ -0,0 +1,38 @@
1
+ ---
2
+ summary: "Tokenized multi-word search, cursor pagination, and nextStep hints for the discovery tools; multi-root theme browse fix; stale catalogue counts removed; mcp-ts-core ^0.10.9 → ^0.10.14."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.14 — 2026-07-10
8
+
9
+ ## Added
10
+
11
+ - **`eurostat_search_datasets` cursor pagination** — new optional `cursor` input; `limit` is now the page size (previously a hard ceiling on total results). Enrichment adds `nextCursor` (opaque, omitted on the last page) and `truncated` (more matches remain); `totalMatches` now reports the full count across all pages. Built on the framework's `paginateArray` primitive ([#16](https://github.com/cyanheads/eurostat-mcp-server/issues/16)).
12
+ - **`nextStep` output field** on `eurostat_search_datasets` and `eurostat_browse_themes` — a suggested follow-up call (e.g. pointing at `eurostat_get_dataset_info`), rendered as a "Next step" line in `format()` text; omitted when there's no clear follow-up, such as an empty theme folder ([#13](https://github.com/cyanheads/eurostat-mcp-server/issues/13)).
13
+ - **`skills/api-linter` `definition-invalid` rule** — flags a `null`/`undefined` entry in a `tools`/`resources`/`prompts` array passed to `validateDefinitions()` instead of crashing the lint run.
14
+ - **Supply chain** — adds `.gitattributes` (LF normalization, binary-asset markers, `linguist-generated` tags for `bun.lock`/`CHANGELOG.md`/`docs/tree.md`) and `.github/SECURITY.md` (vulnerability reporting policy).
15
+ - **`bunfig.toml` install guard** — `minimumReleaseAge` blocks installing package versions published less than 3 days ago (`@cyanheads/mcp-ts-core` excluded, adopted same-day), with `@socketsecurity/bun-security-scanner` wired as the install-time package scanner; `scripts/devcheck.ts`'s outdated-dependency check now treats a guard-held row (Update equals Current, `*`-flagged) as expected rather than a failure.
16
+
17
+ ## Changed
18
+
19
+ - **Tool descriptions and docs** — dropped the hardcoded "8,933 datasets" and "11 top-level themes" figures from `eurostat_search_datasets`, `eurostat_browse_themes`, the server's `instructions` string, and `docs/design.md`; both counts drift upstream and were already stale ([#19](https://github.com/cyanheads/eurostat-mcp-server/issues/19)).
20
+ - **`@cyanheads/mcp-ts-core`** `^0.10.9` → `^0.10.14` — adopts `ctx.state.getMany(keys)`, the `ctx.elicit.url(message, url)` external-link form, explicit `ctx.enrich` helpers (`.notice()`/`.total()`/`.echo()`/`.truncated()`), and the `ctx.content` non-text block collector (`.image()`/`.audio()`).
21
+ - **Skills** — re-synced from the framework; 12 `metadata.version` bumps propagate body drift (`add-tool`, `api-canvas`, `api-linter`, `api-utils`, `api-workers`, `code-simplifier`, `design-mcp-server`, `git-wrapup`, `orchestrations`, `polish-docs-meta`, `release-and-publish`, `techniques`).
22
+ - **`Dockerfile`** — pins `oven/bun:1.3` → `1.3.14` in both stages, adds BuildKit cache mounts (`--mount=type=cache,target=/root/.bun/install/cache`) around every `bun install`, and adds `--ignore-scripts` to the build-stage install.
23
+
24
+ ## Fixed
25
+
26
+ - **`eurostat_search_datasets` multi-word queries** — the query is now tokenized on whitespace and every token must match (AND), case-insensitively, across a combined `label + themePath + code` haystack, replacing a single substring match against the label alone; queries like "business demography NUTS 3" or "regional economic accounts" now resolve ([#14](https://github.com/cyanheads/eurostat-mcp-server/issues/14)).
27
+ - **`eurostat_browse_themes` root browse** — `findRootChildren` now unions the depth-1 children of every depth-0 TOC folder root instead of only the first, so themes under a second root (e.g. `tb_eu`, `cc` under "Cross cutting topics") are reachable from a root-level browse ([#20](https://github.com/cyanheads/eurostat-mcp-server/issues/20)).
28
+
29
+ ## Dependencies
30
+
31
+ - `@cyanheads/mcp-ts-core` `^0.10.9` → `^0.10.14`
32
+ - `@biomejs/biome` `^2.5.0` → `2.5.2` (pinned exact)
33
+ - `@types/node` `^26.0.0` → `26.1.0` (pinned exact)
34
+ - `tsc-alias` `^1.8.17` → `^1.9.0`
35
+ - `tsx` `^4.22.4` → `^4.23.0`
36
+ - `vitest` `^4.1.9` → `^4.1.10`
37
+ - `@socketsecurity/bun-security-scanner` `^1.1.2` (new)
38
+ - Transitive, via the framework bump — `hono` → `4.12.28`, `js-yaml` → `3.15.0`, `vite`/`esbuild` patched; clears the prior `bun audit` advisories on all three
@@ -0,0 +1,13 @@
1
+ ---
2
+ summary: "content[]/structuredContent parity for query_dataset and browse_themes; get_dimension_values returns the full time range and defaults geo to country; invalid dimension values now surface the declared error contract."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.15 — 2026-07-10
8
+
9
+ ## Fixed
10
+
11
+ - **`eurostat_query_dataset` / `eurostat_browse_themes` content[] parity** — `query_dataset`'s `format()` rendered only the first 200 observations behind a hardcoded cap while `structuredContent` carried up to 5,000; the cap and its "N more not shown" note are removed so both channels carry identical rows. `browse_themes`'s `format()` rendered `hasChildren` only when `true`; the `false` state now renders explicitly as "(no children)" ([#15](https://github.com/cyanheads/eurostat-mcp-server/issues/15)).
12
+ - **`eurostat_get_dimension_values` full time range + geo default** — every non-geo dimension was queried with `lastTimePeriod=1`, truncating `time` to its latest period; `getDimensionValues()` now probes a single period, pins every other dimension to its first value, and leaves `time` unfiltered to enumerate the complete range in one bounded call. `geo` without `geo_level` now defaults to `geoLevel=country` per the tool's documented default. Adds a declared `async_response` error variant (non-retryable) for dimension queries too large to bound this way ([#17](https://github.com/cyanheads/eurostat-mcp-server/issues/17)).
13
+ - **`eurostat_query_dataset` invalid-dimension error classification** — an invalid filter dimension (Eurostat HTTP 400, error id 150) bypassed the declared `invalid_dimension` contract and surfaced the raw `FetchHttpError` with the wrong JSON-RPC code (`-32602` instead of `-32007`). `fetchJson()`'s catch block now re-parses the captured error body for any non-2xx response and routes it through the existing classifier, so `invalid_dimension` and `not_found` are reachable on the live HTTP path, not just the 200-wrapped async-413 warning ([#18](https://github.com/cyanheads/eurostat-mcp-server/issues/18)).
@@ -0,0 +1,36 @@
1
+ ---
2
+ summary: "Catalogue TOC now refreshes on a 12h TTL instead of the process lifetime; search results deduplicated by dataset code; search cursors bound to their query and catalogue snapshot; whitespace-only queries and openWorldHint fixed; mcp-ts-core ^0.10.14 → ^0.11.1."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.16 — 2026-08-03
8
+
9
+ ## Added
10
+
11
+ - **`EUROSTAT_TOC_CACHE_TTL_MS`** — configures how long the catalogue TOC cache stays valid before the next `eurostat_search_datasets`/`eurostat_browse_themes` call refreshes it; defaults to `43200000` (12 hours), matching Eurostat's twice-daily update cadence ([#23](https://github.com/cyanheads/eurostat-mcp-server/issues/23)).
12
+ - **`.mcpb` bundle** — the clean step now also strips platform-specific `@duckdb/node-bindings-*` native binaries, keeping the bundle portable across platforms and under the registry size cap.
13
+
14
+ ## Changed
15
+
16
+ - **`@cyanheads/mcp-ts-core`** `^0.10.14` → `^0.11.1` — adopts the `fetchWithTimeout` error-data field rename (`responseBody` → `body`) and the new `expectedStatuses` option in `eurostat-data-service.ts`.
17
+ - **Catalogue TOC caching** — the in-memory TOC cache now expires after `EUROSTAT_TOC_CACHE_TTL_MS` instead of living for the process lifetime; concurrent callers past the TTL share one in-flight refresh, and a failed refresh serves the last loaded cache and holds the next attempt off for 60 seconds ([#23](https://github.com/cyanheads/eurostat-mcp-server/issues/23)).
18
+ - **`eurostat_search_datasets` result set** — matches are deduplicated by dataset code before pagination, so a code Eurostat files under multiple TOC branches is counted and returned once, keeping its first matching placement as the canonical `themePath` ([#26](https://github.com/cyanheads/eurostat-mcp-server/issues/26)).
19
+ - **`eurostat_search_datasets` / `eurostat_browse_themes`** now advertise `annotations.openWorldHint: true`, matching their live Catalogue API dependency ([#29](https://github.com/cyanheads/eurostat-mcp-server/issues/29)).
20
+ - Skills re-synced from the framework (12 files).
21
+
22
+ ## Fixed
23
+
24
+ - **`eurostat_search_datasets` whitespace-only query** — now rejected at the input schema (a `\S` pattern on `query`); the service also treats an empty token list as zero matches rather than matching the full catalogue ([#24](https://github.com/cyanheads/eurostat-mcp-server/issues/24)).
25
+ - **`eurostat_search_datasets` cross-query cursors** — a cursor is now bound to the normalized query and the TOC snapshot that produced it. A cursor presented with a different query, a malformed cursor, or one issued against a since-refreshed catalogue is rejected as the new `invalid_cursor` contract instead of silently paging a different result set ([#28](https://github.com/cyanheads/eurostat-mcp-server/issues/28), [#23](https://github.com/cyanheads/eurostat-mcp-server/issues/23)).
26
+
27
+ ## Dependencies
28
+
29
+ - `@cyanheads/mcp-ts-core` `^0.10.14` → `^0.11.1`
30
+ - `@biomejs/biome` `2.5.2` → `2.5.6` (pinned exact)
31
+ - `@types/node` `26.1.0` → `26.1.2` (pinned exact)
32
+ - `typescript` `^6.0.3` → `^7.0.2`
33
+ - `tsc-alias` `^1.9.0` → `^1.9.1`
34
+ - `tsx` `^4.23.0` → `^4.23.1`
35
+ - `ignore` `^7.0.5` → `^7.0.6`
36
+ - Transitive, via the framework bump — `hono` → `4.12.33`, `@modelcontextprotocol/sdk` → `1.30.0`, `jose` → `6.2.6`
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "Bug fixes: depth-encoding whitespace in browse_themes, timeRange computed from observations, not_found error contract, description cleanup"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.2 — 2026-05-24
8
+
9
+ ## Changed
10
+
11
+ - **`eurostat_search_datasets`**, **`eurostat_get_dataset_info`**, **`eurostat_get_dimension_values`** — tool descriptions trimmed; removed implementation-detail sentences that don't help callers
12
+
13
+ ## Fixed
14
+
15
+ - **`eurostat_browse_themes`** — `unquote()` now preserves leading spaces when stripping quotes, fixing the depth-encoding whitespace that encodes NUTS hierarchy indentation
16
+ - **`eurostat_get_dataset_info`**, **`eurostat_query_dataset`** — `timeRange.start/end` computed from actual time dimension codes in the response; annotation-based fallback used only when no time dimension is present
17
+ - **`eurostat_get_dataset_info`**, **`eurostat_query_dataset`** — 404 responses from the Statistics API are now caught via `McpError` and re-thrown as `notFound` with an actionable message; previously surfaced as raw `FetchHttpError`
@@ -0,0 +1,12 @@
1
+ ---
2
+ summary: "Sync tagline across README, package.json, server.json, and manifest.json"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.3 — 2026-05-23
8
+
9
+ ## Changed
10
+
11
+ - **Tagline** — canonical description synced across all surfaces: README header, `package.json`, `server.json`, and `manifest.json`. Full form: "Search and query 8,933 Eurostat datasets — EU economy, demography, trade, health, and NUTS regional data via MCP. STDIO or Streamable HTTP." Condensed form (server.json, manifest.json): "Search and query 8,933 Eurostat datasets — EU economy, demography, trade, and NUTS data."
12
+ - **`manifest.json`** — version updated from `0.1.1` to `0.1.3` (was two versions behind)
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "Metadata alignment: Dockerfile restored to oven/bun:1.3, package.json scripts/fields, manifest.json fields, server.json env vars, CLAUDE.md + AGENTS.md, vitest.config.ts simplified"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.4 — 2026-05-23
8
+
9
+ ## Changed
10
+
11
+ - **`Dockerfile`** — build stage restored to `oven/bun:1.3` (reverts the `node:24-slim` workaround); LABEL `title` and `description` aligned to the canonical package tagline.
12
+ - **`package.json`** — scripts updated to `bun run scripts/...` form; `start:stdio` / `start:http` use `bun ./dist/index.js`; `engines.bun` bumped to `>=1.3.2`; `files[]` trimmed to exclude `changelog/` and `AGENTS.md`; stale `start` script removed.
13
+ - **`manifest.json`** — `description` aligned to full canonical tagline; `repository`, `homepage`, and `license` fields added.
14
+ - **`server.json`** — `MCP_PUBLIC_URL` env var added to the HTTP package block.
15
+ - **`CLAUDE.md` / `AGENTS.md`** — synced to current framework template (header renamed "Agent Protocol", commands table updated to `bun run`, skills table current).
16
+ - **`vitest.config.ts`** — simplified to single `include` pattern; multi-project scaffold removed.
17
+ - **`devcheck.config.json`** — `tsx` added to depcheck ignores (used as shebang in `scripts/devcheck.ts` and `scripts/lint-mcp.ts`).
@@ -0,0 +1,11 @@
1
+ ---
2
+ summary: "Added hosted server endpoint: remotes block in server.json pointing to https://eurostat.caseyjhand.com/mcp"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.5 — 2026-05-23
8
+
9
+ ## Added
10
+
11
+ - **`server.json`** — `remotes` array with the hosted Streamable HTTP endpoint (`https://eurostat.caseyjhand.com/mcp`), enabling MCP registry clients to discover the public server directly.
@@ -0,0 +1,14 @@
1
+ ---
2
+ summary: "Four field-test bug fixes: obs cap (355MB crash), conflicting param validation, notFound reason field, dataStart/dataEnd whitespace"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.6 — 2026-05-24
8
+
9
+ ## Fixed
10
+
11
+ - **`eurostat_query_dataset`** — unfiltered queries that return > 5,000 observations now cap at 5,000 rows and set `truncated: true` on the result, preventing the ~355MB crash seen with broad dataset requests ([#4](https://github.com/cyanheads/eurostat-mcp-server/issues/4)).
12
+ - **`eurostat_query_dataset`** — `since_period`/`until_period` combined with `last_n_periods` now raises a `conflicting_params` error instead of silently ignoring the date range ([#5](https://github.com/cyanheads/eurostat-mcp-server/issues/5)).
13
+ - **`eurostat_browse_themes`** — `notFound` error now includes `data.reason: "not_found"` so agents can branch on the error code ([#6](https://github.com/cyanheads/eurostat-mcp-server/issues/6)).
14
+ - **TOC parser** — `dataStart`/`dataEnd` values are trimmed before the falsy check, so entries that carry only whitespace no longer surface as `" "` in folder results ([#7](https://github.com/cyanheads/eurostat-mcp-server/issues/7)).
@@ -0,0 +1,16 @@
1
+ ---
2
+ summary: "Code simplification, error code corrections (InvalidParams → ValidationError), mcp-ts-core ^0.9.7 → ^0.9.9, skills synced"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.7 — 2026-05-24
8
+
9
+ ## Changed
10
+
11
+ - **`mcp-ts-core`** ^0.9.7 → ^0.9.9 (`@hono/node-server` ^2.0.3 → ^2.0.4; fuzz pre-parse fix; `fast-check` moved to optional peer dep; `git-wrapup` skill added; "Close the loop on issues" rule in CLAUDE.md/AGENTS.md).
12
+ - **Error codes** — 4 sites corrected from `InvalidParams` to `ValidationError`: `invalid_dimension` and `conflicting_params` (×2) error contracts in `eurostat-query-dataset.tool.ts`, and the generic HTTP 400 branch in `eurostat-data-service.ts`. Imports updated: `invalidParams` → `validationError`.
13
+ - **`indexesWhere` helper** — consolidates 3 repeated `entries.map((_, i) => i).filter(...)` patterns in `eurostat-catalogue-service.ts` into a single private method.
14
+ - **Code simplification** — `Number.isNaN` over global `isNaN` (2 sites); dot notation over bracket access (4 sites); `?.trim() || undefined` optional-chaining form for `themeCode`; removed spurious `async` on resource handler; `biome-ignore` suppression replaces stale `eslint-disable` comments; destructure-after-length-guard replaces repeated `cols[N]!` non-null assertions in TOC parser.
15
+ - **Skills** — `git-wrapup` v1.0 added; `release-and-publish` v2.5 → v2.6.
16
+ - **CLAUDE.md / AGENTS.md** — "Close the loop on issues" rule; framework version reference updated.
@@ -0,0 +1,19 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.9.9 → ^0.9.13: 413 body cap, session-init gate, quieter expected-error logs, GET /mcp keywords; skill sync, plugin metadata scaffolding"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.8 — 2026-05-28
8
+
9
+ ## Changed
10
+
11
+ - **`@cyanheads/mcp-ts-core`** `^0.9.9` → `^0.9.13` — picks up four framework releases:
12
+ - **`MCP_HTTP_MAX_BODY_BYTES`** — configurable inbound request-body cap (default 1 MiB); oversized requests rejected with HTTP 413 before parsing ([#157](https://github.com/cyanheads/mcp-ts-core/issues/157))
13
+ - **HTTP session-init gate** — stateful HTTP mode now rejects requests without `Mcp-Session-Id` with HTTP 400, preventing uninit session minting ([#154](https://github.com/cyanheads/mcp-ts-core/issues/154))
14
+ - **Expected client error log levels** — 401/403/400/404 errors use `logger.warning` instead of the full error-handler pipeline, reducing stack-trace noise in logs ([#158](https://github.com/cyanheads/mcp-ts-core/issues/158))
15
+ - **`GET /mcp` keywords** — `package.json` `keywords` now surfaced on the status JSON response ([#165](https://github.com/cyanheads/mcp-ts-core/issues/165))
16
+ - **`landing.requireAuth: false`** — explicitly opts the landing page into public inventory (keeps current behavior against the 0.9.13 `jwt`/`oauth` default-auth change; this server uses `MCP_AUTH_MODE=none` but the explicit opt-in future-proofs the config).
17
+ - **`@biomejs/biome`** `^2.4.15` → `^2.4.16`
18
+ - **Skills synced** to mcp-ts-core 0.9.13 — `api-canvas`, `api-config`, `design-mcp-server`, `git-wrapup`, `polish-docs-meta`, `release-and-publish`, `report-issue-framework`; `migrate-mcp-ts-template` removed; `code-simplifier` added.
19
+ - **Plugin metadata scaffolded** — `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json` + `mcp.json` added.
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "Enrichment adoption — search/browse/query tools surface query echoes, result totals, and empty-result guidance via typed enrichment block; mcp-ts-core ^0.9.13 → ^0.9.16"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.9 — 2026-05-30
8
+
9
+ ## Added
10
+
11
+ - **`enrichment` block** on `eurostat_search_datasets`, `eurostat_browse_themes`, and `eurostat_query_dataset` — surfaces agent-facing metadata in a typed `enrichment` block reaching both `structuredContent` JSON and the `content[]` markdown trailer, so both Claude Code and Claude Desktop clients receive the same signal.
12
+ - `eurostat_search_datasets`: `query` (echoed search terms), `totalMatches` (true count before limit)
13
+ - `eurostat_browse_themes`: `itemCount` (items returned), `themeCode` (folder browsed, when set)
14
+ - `eurostat_query_dataset`: `appliedFilters` (dimension filters, geo level, time range), `notice` (truncation guidance when the 5,000-row cap is hit)
15
+
16
+ ## Changed
17
+
18
+ - **`eurostat_search_datasets` `format()`** — text header changed from `N total matches (showing N)` to `Showing N results`; the true result count now surfaces in the `enrichment` block.
19
+ - **`eurostat_search_datasets` `structuredContent`** — `totalMatches` moved from the `output` object to `enrichment`; value and presence preserved in both channels, key path changed (`result.totalMatches` → `result.enrichment.totalMatches`).
20
+ - **`@cyanheads/mcp-ts-core`** `^0.9.13` → `^0.9.16` — picks up enrichment builder, `ctx.enrich()` / `ctx.enrich.notice()`, enrichment schema support, and skill sync.
@@ -0,0 +1,29 @@
1
+ ---
2
+ summary: "Ten output fields become optional across three schemas — absent annotations are omitted, not fabricated as 0/\"\" (#30, #33); dataset_info's time valuesCount reflects real coverage (#21); its async_response is non-retryable (#31); dimension_values rejects geo_level on a non-geo dimension (#25); query_dataset normalizes empty filter arrays (#22)."
3
+ breaking: true
4
+ security: false
5
+ ---
6
+
7
+ # 0.2.0 — 2026-08-04
8
+
9
+ ## Breaking
10
+
11
+ - **Ten output fields move from required to optional, across three independently declared schemas**, because Eurostat's annotation-derived metadata is no longer fabricated as `0` / `""` when absent — it is omitted. A consumer reading any of these fields unconditionally will break:
12
+ - `eurostat_get_dataset_info` output: `timeRange.start`, `timeRange.end`, `obsCount`, `lastUpdated`.
13
+ - `eurostat://dataset/{dataset_code}` resource output: `timeRange.start`, `timeRange.end`, `obsCount`, `lastUpdated`.
14
+ - `eurostat_query_dataset` output: `timeRange.start`, `timeRange.end`.
15
+
16
+ ([#30](https://github.com/cyanheads/eurostat-mcp-server/issues/30), [#33](https://github.com/cyanheads/eurostat-mcp-server/issues/33))
17
+
18
+ ## Changed
19
+
20
+ - **`eurostat_get_dataset_info` `dimensions[].valuesCount` for `time`** now reflects the dataset's real period count instead of the single-period artifact of the `lastTimePeriod=1` metadata query. Costs one bounded extra upstream request (pinning every other dimension, unfiltered on `time`), and only when the dataset has a `time` dimension. ([#21](https://github.com/cyanheads/eurostat-mcp-server/issues/21))
21
+ - **`eurostat_get_dataset_info`'s `async_response` error is no longer `retryable`.** The tool exposes no filters to narrow, so an identical retry produces the identical oversized response. The recovery hint now points to `eurostat_search_datasets`/`eurostat_browse_themes` for catalogue-level coverage or `eurostat_get_dimension_values` for one dimension at a time. ([#31](https://github.com/cyanheads/eurostat-mcp-server/issues/31))
22
+ - **`eurostat_query_dataset` `filters` drops zero-length arrays** before building the request, the `geo`/`geo_level` conflict check, and the `appliedFilters` echoed back in both `structuredContent` and `ctx.enrich`. `{"geo": []}` now neither restricts the query nor conflicts with `geo_level`. `appliedFilters` (`Record<string, string[]>`) is added to the tool's output as the filters actually sent upstream. ([#22](https://github.com/cyanheads/eurostat-mcp-server/issues/22))
23
+
24
+ ## Fixed
25
+
26
+ - **`eurostat_get_dimension_values` rejects `geo_level` paired with any dimension other than `geo`** as a new `conflicting_params` error, instead of silently accepting and ignoring it. ([#25](https://github.com/cyanheads/eurostat-mcp-server/issues/25))
27
+ - **Absent Eurostat annotations are omitted rather than fabricated.** `eurostat_get_dataset_info` and the `eurostat://dataset/{dataset_code}` resource no longer default a missing `OBS_COUNT` to `0` or a missing period bound to `""`; `format()` renders each as "not reported by Eurostat" instead of a blank or zero value. ([#30](https://github.com/cyanheads/eurostat-mcp-server/issues/30))
28
+ - **`eurostat_query_dataset`'s `timeRange` has the same fabricated-empty-bounds defect fixed**, found during verification of this release: a period bound absent from both the returned observations and the dataset-wide annotations is now omitted rather than reported as `""`. ([#33](https://github.com/cyanheads/eurostat-mcp-server/issues/33))
29
+ - **`docs/design.md` and `README.md` corrected** — stale error-code labels (`InvalidParams` → `ValidationError`), the single-request description of `get_dataset_info`, the mutual-exclusion description for `geo`/`geo_level`, and a quoted `async_response` recovery hint that no longer exists.
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "`get_dataset_info` output and the dataset resource narrow `dimensions[].valuesCount`/`sampleValues` to optional — a failed time-enumeration request now returns partial metadata instead of failing (#34); `query_dataset`'s row cap moves into the decoder (#27); duplicate `browse_themes` folder codes resolve to their first placement (#32)."
3
+ breaking: true
4
+ security: false
5
+ ---
6
+
7
+ # 0.3.0 — 2026-08-04
8
+
9
+ ## Breaking
10
+
11
+ - **`dimensions[].valuesCount` and `dimensions[].sampleValues` move from required to optional**, on both `eurostat_get_dataset_info`'s output and the independently declared `eurostat://dataset/{dataset_code}` resource output. They're omitted — not filled from the one-period slice — when the `time` dimension's enumeration follow-up request fails. A consumer reading either field unconditionally will break. ([#34](https://github.com/cyanheads/eurostat-mcp-server/issues/34))
12
+
13
+ ## Fixed
14
+
15
+ - **`eurostat_get_dataset_info` no longer discards metadata it already retrieved when the follow-up `time`-enumeration request fails.** It now catches that failure and returns everything the primary request produced, with `time`'s `valuesCount`/`sampleValues` omitted — the schema change above. A failure on the primary request still fails the call. This is a regression introduced in 0.2.0: readers on 0.1.x are unaffected; readers on 0.2.0 should upgrade. ([#34](https://github.com/cyanheads/eurostat-mcp-server/issues/34))
16
+ - **`eurostat_query_dataset`'s 5,000-row cap now applies during decoding** instead of after the full match has been decoded into observation objects. `obsCount`, `missingObsCount`, and `timeRange` are computed from a separate scan of the response's `value`/`status` cell keys, so they keep describing the whole match regardless of the cap. This removes a million-object intermediate allocation on a broad, unfiltered query — it is not a speed improvement: on the reported case, network transfer is ~93% of the round trip, `JSON.parse` ~1.3%, and decoding ~5%; Eurostat serving the full payload is what dominates, not the decode loop. ([#27](https://github.com/cyanheads/eurostat-mcp-server/issues/27))
17
+ - **`eurostat_browse_themes` resolves a folder code filed under more than one TOC branch to its first placement instead of its last.** A new optional `otherPlacements` field on the tool output names the branches not taken, so an ambiguous code is visible as such rather than silently resolved. ([#32](https://github.com/cyanheads/eurostat-mcp-server/issues/32))
@@ -0,0 +1,24 @@
1
+ ---
2
+ summary: "eurostat_query_dataset stages a match past its 5,000-row inline cap onto an optional DuckDB dataframe canvas; two new tools, eurostat_dataframe_describe and eurostat_dataframe_query, read it (#8)."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.4.0 — 2026-08-04
8
+
9
+ ## Added
10
+
11
+ - **`eurostat_dataframe_describe` and `eurostat_dataframe_query`** — SQL access to a dataframe canvas. `describe` lists staged tables with row counts and column types; `query` runs a single read-only `SELECT`, rejecting statement chaining, non-`SELECT` verbs, and functions that read files or external data. Both are registered only when `CANVAS_PROVIDER_TYPE=duckdb`; otherwise they're wrapped in `disabledTool()` — invisible to `tools/list` but still named on the landing page and server card. ([#8](https://github.com/cyanheads/eurostat-mcp-server/issues/8))
12
+ - **`eurostat_query_dataset` stages a capped match onto the canvas.** When a match exceeds the 5,000-row inline cap and a canvas is configured, the whole match is written to a SQL table and the response gains `canvasId`, `tableName`, and `stagedRowCount`. Staging is skipped when the result fits inline. Passing a prior response's `canvasId` back as the new optional `canvas_id` input stages the next result onto the same canvas, so two datasets can be joined. ([#8](https://github.com/cyanheads/eurostat-mcp-server/issues/8))
13
+ - **Canvas env vars** — `CANVAS_PROVIDER_TYPE`, `CANVAS_TEMP_PATH`, `CANVAS_TTL_MS`, `CANVAS_DEFAULT_ROW_LIMIT`, documented in `.env.example`, `server.json`, and `manifest.json`. Off by default (`none`); the `@duckdb/node-api` binding ships in every install and image, so enabling the canvas needs only the one variable.
14
+
15
+ ## Changed
16
+
17
+ - **`eurostat_query_dataset`'s row source is a lazy generator** over the response's populated cells, shared by the capped inline decode and the canvas spill. The inline rows are a prefix of the staged table by construction — both walks pull from the same generator — and staging costs no additional request to Eurostat: the full response body is already in memory before decoding starts.
18
+ - **Staged columns are flat and declared, not inferred.** Each dimension contributes a `<dim>` code column and a `<dim>_label` column; the observation contributes `obs_value`, `obs_flag`, `obs_flag_label`. The schema is declared up front because DuckDB's inference reads only the leading rows, which would type an all-integer `obs_value` prefix as `BIGINT` and return it as a string.
19
+ - **Dockerfile** — the non-root `bun` user now owns `/var/lib/eurostat-mcp-server/canvas-tmp` and `/var/lib/eurostat-mcp-server/canvas-exports`, mapped to `CANVAS_TEMP_PATH` and `CANVAS_EXPORT_PATH`; the prior image had no writable path for either. A canvas-acquire failure caused by an unwritable scratch directory is now re-thrown naming `CANVAS_TEMP_PATH` instead of surfacing a bare `EACCES`.
20
+ - **`.mcpb` bundle** — `scripts/clean-mcpb.ts`'s DuckDB-stripping comment updated to describe the binding as a runtime dependency gated on `CANVAS_PROVIDER_TYPE`, not an optional peer. A bundle install still cannot run the canvas — native bindings are stripped from the bundle to keep it portable and under the registry size cap — so SQL analytics needs the npm, Docker, or from-source install.
21
+
22
+ ## Dependencies
23
+
24
+ - `@duckdb/node-api` `^1.5.5-r.3` added as a runtime dependency (powers the DataCanvas).