@cyanheads/eurostat-mcp-server 0.6.3 → 0.6.5

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 (30) hide show
  1. package/AGENTS.md +49 -25
  2. package/CLAUDE.md +49 -25
  3. package/Dockerfile +25 -6
  4. package/README.md +98 -95
  5. package/changelog/0.6.x/0.6.4.md +29 -0
  6. package/changelog/0.6.x/0.6.5.md +26 -0
  7. package/changelog/template.md +9 -26
  8. package/dist/index.js +6 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.d.ts +1 -0
  11. package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.d.ts.map +1 -1
  12. package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js +3 -4
  13. package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.d.ts +2 -0
  15. package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js +4 -4
  17. package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts +6 -0
  19. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js +8 -4
  21. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js.map +1 -1
  22. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts +1 -0
  23. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js +3 -4
  25. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js.map +1 -1
  26. package/dist/services/eurostat-bulk/eurostat-bulk-service.js +1 -1
  27. package/dist/services/eurostat-data/eurostat-data-service.js +1 -1
  28. package/package.json +11 -10
  29. package/server.json +3 -3
  30. package/dist/eurostat-mcp-server.mcpb +0 -0
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
 
11
11
 
12
- [![Version](https://img.shields.io/badge/Version-0.6.3-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/)
12
+ [![Version](https://img.shields.io/badge/Version-0.6.5-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
 
@@ -29,147 +29,135 @@
29
29
 
30
30
  ---
31
31
 
32
- ## Tools
32
+ ## Overview
33
33
 
34
- 6 tools for discovering and querying Eurostat statistical datasets, plus 2 more when the optional dataframe canvas is enabled:
34
+ EU statistics from the Eurostat catalogue — economy, demography, trade, health, and NUTS regional data. Search and browse the catalogue by keyword or theme, inspect dataset dimensions, and query a slice or bulk-download a whole dataset from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
35
+
36
+ ### Tools
37
+
38
+ Two of the eight are listed only when the dataframe canvas is enabled (`CANVAS_PROVIDER_TYPE=duckdb`).
35
39
 
36
40
  | Tool | Description |
37
41
  |:---|:---|
38
42
  | `eurostat_search_datasets` | Search the Eurostat catalogue by keyword — returns codes, descriptions, period coverage, and theme breadcrumbs |
39
43
  | `eurostat_browse_themes` | Navigate the Eurostat theme hierarchy — list root themes or drill into subthemes and datasets |
40
- | `eurostat_get_dataset_info` | Fetch metadata for a dataset: dimensions with sample values, time range, observation count, and last-update date |
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 a deterministic preview of decoded statistical observations with dimension filters, NUTS geo-level, and time-range controls |
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
- | `eurostat_dataframe_describe` | List the tables staged on a dataframe canvas with their row counts and column types — canvas only |
44
+ | `eurostat_get_dataset_info` | Fetch dataset metadata: dimensions with sample values, time range, observation count, and last-update date |
45
+ | `eurostat_get_dimension_values` | List all valid codes for one dataset dimension, with NUTS hierarchy filtering for `geo` |
46
+ | `eurostat_query_dataset` | Fetch a bounded preview of decoded observations with dimension filters, NUTS geo-level, and time-range controls |
47
+ | `eurostat_download_dataset` | Download a whole dataset via the SDMX bulk endpoint and stage every observation on the dataframe canvas |
48
+ | `eurostat_dataframe_describe` | List the tables staged on a dataframe canvas, with row counts and column types — canvas only |
45
49
  | `eurostat_dataframe_query` | Run a read-only SQL SELECT across staged tables — canvas only |
46
50
 
47
- ### `eurostat_search_datasets`
51
+ ### Resources
52
+
53
+ | Resource | Description |
54
+ |:---|:---|
55
+ | `eurostat://dataset/{dataset_code}` | Dataset metadata (dimensions, time range, observation count, last-updated) by URI, for cache-injectable context |
48
56
 
49
- Search the Eurostat dataset catalogue by keyword.
57
+ ## Capability reference
50
58
 
51
- - Tokenized keyword match — whitespace-separated tokens are ANDed case-insensitively across each dataset's label, theme breadcrumb, and code, so word order and theme-named queries resolve without a verbatim label
52
- - Returns code, label, type (dataset/table), period coverage, observation count, and theme breadcrumb
53
- - One row per dataset code — Eurostat files some datasets under several theme branches; matches are deduplicated so `totalMatches` and page slots count unique query targets
54
- - Cursor pagination: `limit` (1–100, default 20) sets the page size, `totalMatches` reports the full count, and passing the returned `nextCursor` back as `cursor` pages through every match over a stable order. Cursors are bound to their originating query and catalogue snapshot — reusing one with a different query, or after the catalogue refreshes, returns `invalid_cursor` instead of a silently shifted page
55
- - `nextStep` hint on each result points at the next tool to call
56
- - Catalogue TOC cached in memory for 12 hours (`EUROSTAT_TOC_CACHE_TTL_MS`), then refreshed on the next call
57
- - Pair with `eurostat_browse_themes` for structured domain exploration when keywords are unclear
59
+ ### `eurostat_search_datasets` <sub>tool</sub>
58
60
 
59
- ---
61
+ - Tokenized keyword match — whitespace-separated tokens are ANDed case-insensitively across each dataset's label, theme breadcrumb, and code, so word order and theme-named queries resolve without a verbatim label match
62
+ - Returns `code`, `label`, `type` (dataset/table), period coverage, observation count, and theme breadcrumb per result
63
+ - One row per dataset code — Eurostat files some datasets under multiple theme branches; matches are deduplicated so `totalMatches` and page slots count unique targets
64
+ - Cursor pagination: `limit` (1–100, default 20) sets page size, `totalMatches` reports the full count, and passing `nextCursor` back as `cursor` pages through every match. A cursor is bound to its originating query and catalogue snapshot — reusing one with a different query, or after the catalogue refreshes, returns `invalid_cursor` instead of a silently shifted page
65
+ - `nextStep` on each result names the next tool to call
66
+ - Catalogue TOC is cached in memory for 12 hours (`EUROSTAT_TOC_CACHE_TTL_MS`), refreshed on the next call past that age
60
67
 
61
- ### `eurostat_browse_themes`
68
+ ---
62
69
 
63
- Navigate the Eurostat theme tree.
70
+ ### `eurostat_browse_themes` <sub>tool</sub>
64
71
 
65
- - Without `theme_code`: returns the top-level themes (Economy and finance, Population, Transport, etc.)
72
+ - Without `theme_code`: returns the top-level theme folders (Economy, Population, Transport, etc.)
66
73
  - With `theme_code`: returns immediate children — subtheme folders and datasets in that branch
67
- - Each entry includes code, label, type (folder/dataset/table), data period, and observation count where available
68
- - Returns a breadcrumb path from root to the current node, plus a `nextStep` hint suited to the level (drill into folders or inspect a dataset)
69
- - One branch per folder code — Eurostat files a few folder codes under several branches; a code resolves to the first one the catalogue lists, which never has fewer children than the branches it shadows, and `otherPlacements` names those so the ambiguity is visible
70
- - Use for structured discovery when you know the domain but not the exact dataset code
74
+ - Each entry carries `code`, `label`, `type` (folder/dataset/table), data period, and observation count where available
75
+ - Returns a breadcrumb `parentPath` from root to the current node, plus a `nextStep` hint suited to the level
76
+ - One branch per folder code — Eurostat files a few folder codes under several branches; a code resolves to the branch listed first in the catalogue (never fewer children than the ones it shadows), and `otherPlacements` names those so the ambiguity is visible
71
77
 
72
78
  ---
73
79
 
74
- ### `eurostat_get_dataset_info`
75
-
76
- Fetch metadata for a Eurostat dataset before querying it.
80
+ ### `eurostat_get_dataset_info` <sub>tool</sub>
77
81
 
78
82
  - Returns all dimensions with their codes, labels, and up to 10 sample values each
79
- - Reports overall time range and total observation count across all periods, each omitted when Eurostat does not report it
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
- - For dimensions with more than 10 values, use `eurostat_get_dimension_values` for the full list
82
- - Provides a link to the ESMS metadata page when available
83
+ - Dimension values reflect the full dataset-available set (including every period for `time`), not just what appears in populated observations
84
+ - Reports overall time range and total observation count, each omitted — not zeroed — when Eurostat does not report it
85
+ - For a dimension with more than 10 values, call `eurostat_get_dimension_values` for the full list
86
+ - `metadataUrl` links to the ESMS metadata page when Eurostat provides one
83
87
 
84
88
  ---
85
89
 
86
- ### `eurostat_get_dimension_values`
87
-
88
- List all valid values for a specific dataset dimension.
90
+ ### `eurostat_get_dimension_values` <sub>tool</sub>
89
91
 
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
- - Prevents silent no-data returns — invalid dimension values in `eurostat_query_dataset` return nothing without error; verify codes here first
92
+ - Returns the complete dataset-available set of codes and labels for any dimension, from the same content constraint `eurostat_get_dataset_info` uses
93
+ - For `geo`, NUTS hierarchy filtering via `geo_level`: `aggregate`, `country` (default), `nuts1`, `nuts2`, `nuts3` — an empty level reports `no_results` rather than implying the dataset lacks data, and pairing `geo_level` with any other dimension is rejected
94
+ - Verify codes here before `eurostat_query_dataset` or `eurostat_download_dataset` — an invalid dimension value returns no data silently from the former and a rejected fault from the latter
93
95
 
94
96
  ---
95
97
 
96
- ### `eurostat_query_dataset`
97
-
98
- Fetch statistical data from a Eurostat dataset.
98
+ ### `eurostat_query_dataset` <sub>tool</sub>
99
99
 
100
- - Accepts dimension filters as a map of `{dimension_code: [value1, value2, ...]}`
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
- - 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
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)
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
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
108
- - Pass `canvas_id` from an earlier response to stage several results side by side and join across them
109
- - Async-response detection — large unfiltered queries return an actionable, non-retryable error with filter guidance rather than silently timing out
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
100
+ - Dimension filters as `{dimension_code: [values]}`; a `geo` filter and `geo_level` (NUTS: `aggregate`/`country`/`nuts1`/`nuts2`/`nuts3`) are mutually exclusive, as are `since_period`/`until_period` and `last_n_periods`; an empty filter array is dropped rather than applied
101
+ - `preview_limit` (1–500, default 50) bounds only the inline prefix of decoded observations — it never changes `obsCount`, `missingObsCount`, `timeRange`, or what gets staged. There is deliberately no cursor or offset; filters and period controls are the only way to shrink the match itself
102
+ - Each observation carries dimension code/label pairs, a nullable `value`, an optional OBS_FLAG `status` (e.g. `p`=provisional, `e`=estimated), and a separate optional `CONF_STATUS` `confStatus` marker — usually why a value is null
103
+ - `truncated` is true only when the match exceeds the 5,000-observation staging threshold, independent of `preview_limit`. With the dataframe canvas enabled, a match above that threshold is staged whole as a SQL table (`canvasId` / `tableName` / `stagedRowCount`) — call `eurostat_dataframe_describe` before `eurostat_dataframe_query`; without a canvas those fields are absent and narrowing the query is the only way to reach the rest
104
+ - `canvas_id` reuses an existing canvas so a result can be joined against earlier ones; an oversized unfiltered query is caught by async-response detection and returned as an actionable, non-retryable error instead of timing out
105
+ - Fetches a slice — for a whole dataset, `eurostat_download_dataset` reads the SDMX bulk endpoint instead, at roughly half the bytes
111
106
 
112
107
  ---
113
108
 
114
- ### `eurostat_download_dataset`
109
+ ### `eurostat_download_dataset` <sub>tool</sub>
115
110
 
116
- Download a whole dataset through the SDMX 2.1 TSV bulk endpoint (`/sdmx/2.1/data/{dataset}?format=TSV`).
111
+ - TSV bulk body runs 48–63% of the JSON-stat bytes `eurostat_query_dataset` reads for the same data — measured across four datasets from 1.1M to 12.8M observations
112
+ - Filters take the same `{dimension_code: [values]}` map, applied server-side; the positional key needs every dimension in the dataset's own order, so a filter naming one the dataset lacks is rejected with the real dimension list rather than sent malformed
113
+ - No `last_n_periods` here — only `since_period` / `until_period` actually shrink the response, since the TSV layout keeps a column per period regardless of selector
114
+ - Byte budget (`EUROSTAT_BULK_MAX_BYTES`, default 50 MiB) is enforced while streaming — Eurostat sends no `Content-Length`, so a transfer stopped mid-flight returns its rows with `budgetExceeded: true` instead of an error
115
+ - Failure modes are typed: an async queue ticket (Eurostat's too-costly-to-serve-inline response) is a non-retryable error; XML SOAP faults map to `not_found` (100), `filter_arity` (140), and `invalid_dimension` (150 — also covers an out-of-coverage period range), each with a recovery hint naming the next tool
116
+ - With the dataframe canvas enabled, every observation is staged as a SQL table (`canvasId` / `tableName` / `stagedRowCount`), streamed row by row — call `eurostat_dataframe_describe` before `eurostat_dataframe_query`. Without a canvas, only `preview_limit` rows (default 50, max 500) survive the call; `rowCount` / `missingCount` / `periodRange` still describe the whole download
117
+
118
+ ---
117
119
 
118
- - The TSV wire format runs 48–63% of the JSON-stat body `eurostat_query_dataset` reads for the same data, because the wide layout writes each dimension key once per row instead of once per observation. Measured across four datasets from 1.1M to 12.8M observations
119
- - Filters take the same `{dimension_code: [value, ...]}` map as `eurostat_query_dataset` and are applied by Eurostat before the body is sent. They become a positional key on the request path, which must carry one position per dimension — the server builds it from the dataset's own dimension order, so a filter naming a dimension the dataset does not have is rejected with the real list rather than sent as a malformed key
120
- - Narrow periods with `since_period` / `until_period`. There is deliberately no "last N periods": the TSV layout keeps a column for every period whichever selector is used, and `lastNObservations` merely blanks the unselected cells — measured at ~3× the equivalent JSON-stat body. `startPeriod` removes the columns
121
- - **Byte budget enforced while streaming.** Eurostat sends the body chunked with no `Content-Length`, so the limit is applied as bytes arrive and the transfer is aborted the moment it is spent — not measured after the fact. A truncated download returns its rows with `budgetExceeded: true` rather than an error, so the work already paid for is not discarded. `EUROSTAT_BULK_MAX_BYTES` sets the ceiling
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
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
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
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`
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
120
+ ### `eurostat_dataframe_describe` <sub>tool</sub>
121
+
122
+ - Lists tables staged on the canvas (from `canvas_id`, returned by `eurostat_query_dataset` or `eurostat_download_dataset`) with row counts, column names, types, and nullability — call before writing SQL, since the two stagers write different dimension columns
123
+ - Also reports the canvas's and each table's `expiresAt`; every call against a canvas slides its lifetime forward (`CANVAS_TTL_MS`, default 24h)
124
+ - Errors `canvas_disabled` when this deployment runs without a canvas, `canvas_not_found` when the ID is unknown or expired
127
125
 
128
126
  ---
129
127
 
130
- ### `eurostat_dataframe_describe` / `eurostat_dataframe_query`
128
+ ### `eurostat_dataframe_query` <sub>tool</sub>
131
129
 
132
- SQL over the results `eurostat_query_dataset` and `eurostat_download_dataset` stage. **Listed only when the dataframe canvas is enabled** (`CANVAS_PROVIDER_TYPE=duckdb`); the server is fully functional without it, and clients never see tools they cannot call.
130
+ - Runs a single read-only `SELECT` against staged tables; statement chaining, non-`SELECT` verbs, and functions that read files or external data are rejected with a typed error
131
+ - `eurostat_query_dataset` tables carry a code column per dimension plus a `_label` companion; `eurostat_download_dataset` tables carry code columns only (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`) with matching codes, so tables from either stager join on dimension codes and `time`
132
+ - A confidential cell reads `obs_flag = NULL` with `conf_status = 'C'` on either table — JSON-stat folds the two into one string (`|C`) that `eurostat_query_dataset` splits before staging
133
+ - Results are bounded by `CANVAS_DEFAULT_ROW_LIMIT` (default 10,000); `truncated: true` means add a `LIMIT`, an aggregate, or a narrower `WHERE`. 64-bit integer results — `COUNT(*)` included — arrive as strings so values outside the JSON number range survive intact
134
+ - The DuckDB binding ships with the server — `CANVAS_PROVIDER_TYPE=duckdb` is the only switch — except the one-click `.mcpb` bundle, which strips native bindings to stay portable; use the npm, Docker, or from-source install for SQL analytics
133
135
 
134
- - `eurostat_dataframe_describe` lists the staged tables with row counts and column names and types — call it before writing SQL
135
- - `eurostat_dataframe_query` runs a single read-only `SELECT`. Statement chaining, non-`SELECT` verbs, and functions that read files or external data are rejected with a typed error
136
- - Staged columns are flat, and the two stagers write different dimension columns — call `eurostat_dataframe_describe` rather than assuming. `eurostat_query_dataset` gives each dimension a code column named after the dimension (`geo`) plus a label companion (`geo_label`); `eurostat_download_dataset` gives code columns only, since 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`
137
- - Tables from the two stagers join on their dimension code columns and `time` — same names, same `VARCHAR` type, `obs_value` `DOUBLE` on both — and their measure columns carry the same codes for the same observation. JSON-stat has no `CONF_STATUS` field and folds the marker into the observation status as `|C`; `eurostat_query_dataset` splits it back out before staging, so a confidential cell reads `obs_flag = NULL` with `conf_status = 'C'` on either table
138
- - The DuckDB binding ships with the server, so `CANVAS_PROVIDER_TYPE=duckdb` is the only switch. The exception is the one-click `.mcpb` bundle, which strips platform-specific native bindings to stay portable — a bundle install cannot run the canvas, so reach for the npm, Docker, or from-source install for SQL analytics
136
+ ---
139
137
 
140
- ## Resource
138
+ ### `eurostat://dataset/{dataset_code}` <sub>resource</sub>
141
139
 
142
- | Type | Name | Description |
143
- |:---|:---|:---|
144
- | Resource | `eurostat://dataset/{dataset_code}` | Dataset metadata (dimensions, time range, obs count, last-updated) accessible by URI for cache-injectable context |
140
+ - Same payload as `eurostat_get_dataset_info`, addressable as a resource URI for cache-injectable context
141
+ - `dataset_code` comes from `eurostat_search_datasets` or `eurostat_browse_themes`
145
142
 
146
143
  ## Features
147
144
 
148
- Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core):
149
-
150
- - Declarative tool definitions — single file per tool, framework handles registration and validation
151
- - Unified error handling across all tools
152
- - Pluggable auth (`none`, `jwt`, `oauth`)
153
- - Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`
154
- - Structured logging with optional OpenTelemetry tracing
155
- - Runs locally (stdio/HTTP) or on Cloudflare Workers from the same codebase
145
+ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
156
146
 
157
147
  Eurostat-specific:
158
148
 
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
160
- - JSON-stat 2.0 stride-based decoder for the Statistics API response format
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
163
- - NUTS hierarchy geo-level filtering across query and dimension-value tools
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
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
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
149
+ - NUTS hierarchy geo-level filtering (`aggregate` / `country` / `nuts1` / `nuts2` / `nuts3`) across `eurostat_query_dataset` and `eurostat_get_dimension_values`
150
+ - OBS_FLAG (provisional, estimated, etc.) and CONF_STATUS (confidentiality) decoded into separate fields from Eurostat's combined encoding, consistent whether the observation came from JSON-stat or the bulk TSV
151
+ - Async-response detection across every data-fetching tool — Eurostat's over-limit HTTP-200 warnings and HTTP-413/SOAP faults are classified into one typed, non-retryable error with filter guidance instead of surfacing as a timeout
152
+ - SDMX 2.1 TSV bulk downloads at roughly half the JSON-stat byte cost, with a mid-transfer byte budget
153
+ - Optional DuckDB dataframe canvas stages a query match above 5,000 observations, or a whole bulk download, as a queryable SQL table
167
154
 
168
155
  Agent-friendly output:
169
156
 
170
- - Discovery workflow: `eurostat_search_datasets` / `eurostat_browse_themes` → `eurostat_get_dataset_info` → `eurostat_get_dimension_values` → `eurostat_query_dataset` for a slice, or `eurostat_download_dataset` for the whole dataset
171
- - Invalid dimension codes in query filters silently return no data from Eurostat — the `eurostat_get_dimension_values` tool prevents this by letting agents verify codes first
172
- - Structured error contracts with typed reasons and recovery hints on all tools
157
+ - Structured error contracts — every declared failure carries a typed `reason` and a `recovery.hint` naming the exact next tool to call, not just an error string
158
+ - Next-step hints — `eurostat_search_datasets` and `eurostat_browse_themes` responses carry a `nextStep` field pointing at the right follow-up call
159
+ - Omitted-vs-unknown fields — counts and period bounds Eurostat doesn't report (`obsCount`, `timeRange.start`/`end`, `lastUpdated`) are omitted from the response rather than defaulted to zero or blank
160
+ - Truncation and staging notices — a response that exceeds an inline cap carries an enrichment notice naming the exact `eurostat_dataframe_describe` → `eurostat_dataframe_query` follow-up
173
161
 
174
162
  ## Getting started
175
163
 
@@ -249,7 +237,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
249
237
 
250
238
  ### Prerequisites
251
239
 
252
- - [Bun v1.3.2](https://bun.sh/) or higher. No API key required — Eurostat's dissemination API is public.
240
+ - [Bun v1.4.0](https://bun.sh/) or higher. No API key required — Eurostat's dissemination API is public.
253
241
 
254
242
  ### Installation
255
243
 
@@ -281,6 +269,7 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
281
269
  | `MCP_HTTP_PORT` | HTTP server port | `3010` |
282
270
  | `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path | `/mcp` |
283
271
  | `MCP_PUBLIC_URL` | Public origin override for TLS-terminating reverse-proxy deployments | none |
272
+ | `MCP_SESSION_MODE` | HTTP session posture: `auto`, `stateful`, or `stateless` (`auto` resolves to `stateful`). The server declares `stateless` in `createApp()`; an explicitly set value overrides that default. | `stateless` |
284
273
  | `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth` | `none` |
285
274
  | `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.) | `info` |
286
275
  | `MCP_GC_PRESSURE_INTERVAL_MS` | Opt-in Bun-only forced-GC pressure loop (ms). Recommended starting point if heap growth is observed: `60000`. | `0` (disabled) |
@@ -297,6 +286,8 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
297
286
  | `CANVAS_DEFAULT_ROW_LIMIT` | Max rows one `eurostat_dataframe_query` returns before reporting `truncated` | `10000` |
298
287
  | `OTEL_ENABLED` | Enable OpenTelemetry | `false` |
299
288
 
289
+ See [`.env.example`](./.env.example) for the full list of optional overrides.
290
+
300
291
  ## Running the server
301
292
 
302
293
  ### Local development
@@ -317,12 +308,23 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
317
308
  ```sh
318
309
  bun run devcheck # Lints, formats, type-checks, and more
319
310
  bun run test # Runs the test suite
311
+ bun run lint:mcp # Validates MCP definitions against spec
320
312
  ```
321
313
 
314
+ ### Docker
315
+
316
+ ```sh
317
+ docker build -t eurostat-mcp-server .
318
+ docker run --rm -p 3010:3010 eurostat-mcp-server
319
+ ```
320
+
321
+ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/eurostat-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
322
+
322
323
  ## Project structure
323
324
 
324
325
  | Directory | Purpose |
325
326
  |:---|:---|
327
+ | `src/index.ts` | `createApp()` entry point — registers tools and resources and inits services. |
326
328
  | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Six tools for discovery and data access, plus two canvas-gated dataframe tools. |
327
329
  | `src/mcp-server/resources` | Resource definitions. Dataset metadata resource. |
328
330
  | `src/services/eurostat-catalogue` | Catalogue service — fetches and parses the Eurostat TOC TXT file; TTL-bounded in-memory cache. |
@@ -338,10 +340,11 @@ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rule
338
340
  - Handlers throw, framework catches — no `try/catch` in tool logic
339
341
  - Use `ctx.log` for logging, `ctx.state` for storage
340
342
  - Register new tools and resources in the `createApp()` arrays
343
+ - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
341
344
 
342
345
  ## Contributing
343
346
 
344
- Issues and pull requests are welcome. Run checks and tests before submitting:
347
+ Issues are welcome. Run checks and tests before submitting:
345
348
 
346
349
  ```sh
347
350
  bun run devcheck
@@ -0,0 +1,29 @@
1
+ ---
2
+ summary: "Adopts mcp-ts-core 0.13.2: the server declares stateless session mode in source, tool argument rejections carry a structured InvalidParams envelope, and the development skill tree moves from skills/ to framework-skills/ so plugin installs no longer receive it."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.6.4 — 2026-09-16
8
+
9
+ ## Added
10
+
11
+ - **`audit:fix`** runs `bun audit fix`, upgrading vulnerable packages to the lowest safe version within existing ranges. First response to a transitive advisory, ahead of `bun update <name>` and `bun dedupe`.
12
+
13
+ ## Changed
14
+
15
+ - **`createApp({ sessionMode: 'stateless' })`** declared in `src/index.ts` — with `MCP_SESSION_MODE` unset, `GET /mcp` server-info now advertises `stateless` (previously the framework's `auto` default resolved to `stateful`), matching the Docker image; an explicit `MCP_SESSION_MODE` still overrides the declaration. `.env.example` sets `MCP_SESSION_MODE=stateless` and documents all three values, and its `MCP_HTTP_HOST` comment now reads `127.0.0.1`.
16
+ - **Argument rejections carry `structuredContent.error`** with code `-32602` alongside the existing `Invalid arguments for tool …` text ([mcp-ts-core#377](https://github.com/cyanheads/mcp-ts-core/issues/377)).
17
+ - **`engines.bun`** raised to `>=1.4.0`; README prerequisites updated to match.
18
+ - **Development skills moved from `skills/` to `framework-skills/`** ([mcp-ts-core#428](https://github.com/cyanheads/mcp-ts-core/issues/428)) — a plugin install no longer receives them. Path references updated in `CLAUDE.md`/`AGENTS.md`, `.mcpbignore`, `.github/CONTRIBUTING.md`, and `docs/tree.md`.
19
+ - Framework skills, `scripts/*.ts`, and `changelog/template.md` re-synced to `@cyanheads/mcp-ts-core` 0.13.2.
20
+ - **Repo metadata refreshed** — `.claude-plugin/plugin.json` gains `$schema`, author email/url, and `keywords`; GitHub issue forms auto-assign to `cyanheads`, allow blank issues, and route framework bugs to mcp-ts-core; `config.yml` links the private security-advisory form.
21
+ - **README restructured** around an Overview, a capability reference per tool and resource, and a Docker section.
22
+
23
+ ## Dependencies
24
+
25
+ - `@cyanheads/mcp-ts-core` `^0.12.3` → `^0.13.2`
26
+ - `zod` `^4.4.3` → `^4.6.4`
27
+ - `@biomejs/biome` (dev) `2.5.9` → `2.5.13`
28
+ - `ignore` (dev) `^7.0.6` → `^7.0.9`
29
+ - `tsc-alias` (dev) `^1.9.2` → `^1.9.5`
@@ -0,0 +1,26 @@
1
+ ---
2
+ summary: "Adopts mcp-ts-core 0.13.6: canvas_id on the DataCanvas tools now enforces the framework's minted-id shape at argument validation, and tool error responses carry the framework's structured reason and recovery details."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.6.5 — 2026-09-21
8
+
9
+ ## Changed
10
+
11
+ - **`canvas_id`** on `eurostat_query_dataset`, `eurostat_download_dataset`, `eurostat_dataframe_describe`, and `eurostat_dataframe_query` now uses mcp-ts-core 0.13.5's `CanvasIdSchema` (`pattern: ^[A-Za-z0-9_-]{10}$`), so a malformed id is rejected at argument validation with `reason: invalid_arguments` instead of reaching the canvas lookup, or being silently ignored on the two staging tools when nothing is staged. Well-formed ids behave as before.
12
+ - **Argument rejections** carry `data.reason: "invalid_arguments"` and a schema-derived `Recovery:` hint (mcp-ts-core 0.13.3).
13
+ - **Tool arguments pass a pre-validation step** (0.13.4): a case-variant key such as `datasetCode` is rewritten to its declared key when it uniquely matches one, the client-added `_meta`, `tool_call_description`, and `toolCallId` keys and any other `_`-prefixed key are dropped, and a `filters` value sent as a JSON-stringified array is parsed back into an array. Any other undeclared key is still rejected.
14
+ - **A DuckDB conversion or out-of-range error** in `eurostat_dataframe_query` SQL now comes back as a `ValidationError` with `reason: "sql_execution_error"`, instead of a `DatabaseError` (0.13.4).
15
+ - **Tool error text** in `content[]` now closes with `(reason <reason>)`, adding ` · not retryable` or ` · retryable` when the error carries a `retryable` flag; `structuredContent` is unchanged (0.13.5).
16
+ - **README documents `MCP_SESSION_MODE`**, and an integration test pins both cases (#43). With `MCP_SESSION_MODE` unset the server runs stateless, the default `createApp()` declares; an explicitly set `MCP_SESSION_MODE` still overrides it.
17
+ - **Repo scaffolding synced** to the current framework template: `.github/workflows/codeql.yml` added, framework skills and `lint-mcp.ts` re-synced, Dockerfile comments and issue-form text updated. The ten error reasons produced below the handler carry mcp-ts-core 0.13.6's `thrownBy: 'service'` lint marker: `canvas_not_found` on all four canvas tools, `missing_table` on `eurostat_dataframe_query`, and `not_found`, `invalid_dimension`, `filter_arity`, `async_queued`, and `upstream_fault` on `eurostat_download_dataset` (lint-only metadata; the advertised error contract is unchanged).
18
+
19
+ ## Dependencies
20
+
21
+ - `@cyanheads/mcp-ts-core` `^0.13.2` → `^0.13.6`
22
+ - `zod` `^4.6.4` → `^4.6.5`
23
+ - `@duckdb/node-api` `^1.5.5-r.4` → `^1.5.5-r.5`
24
+ - `vitest` (dev) `^4.1.11` → `^5.0.1`
25
+ - `@biomejs/biome` (dev) `2.5.13` → `2.5.14`
26
+ - `@types/node` (dev) `26.2.0` → `26.6.2`
@@ -6,8 +6,8 @@
6
6
 
7
7
  # Required. One-line GitHub Release-style headline. 350 character cap — a
8
8
  # ceiling, not a target. Default short and scannable. Don't pad, don't stitch
9
- # unrelated changes with commas/semicolons into an inventory — pick the
10
- # headline, like a tag's theme line. Quotes required: unquoted YAML treats
9
+ # unrelated changes with commas/semicolons into an inventory — pick the one
10
+ # headline the release is about. Quotes required: unquoted YAML treats
11
11
  # `: ` inside the value as a key separator and fails GitHub's strict parser.
12
12
  summary: ""
13
13
 
@@ -117,30 +117,13 @@ security: false
117
117
  in that unrelated item's metadata.
118
118
 
119
119
  TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
120
- via `gh release create --notes-from-tag`. The tag is a derivative of this
121
- changelog entry — a condensed, scannable version, not a copy. Format:
122
-
123
- <theme — omit version number, GitHub prepends it>
124
- ← blank line
125
- <1-2 sentence context: what this release does>
126
- ← blank line
127
- Dependency bumps: ← section header
128
- ← blank line
129
- - `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
130
- ← blank line
131
- Changed: ← only sections with entries
132
- ← blank line
133
- - `format()` output includes `query` in text mode
134
- ← blank line
135
- Added:
136
- ← blank line
137
- - `manifest.json` scaffolded for MCPB bundle support
138
- - Install badges (Claude Desktop, Cursor, VS Code)
139
- ← blank line
140
- <N> tests pass; `bun run devcheck` clean. ← footer
141
-
142
- Never a flat comma-separated string. Always structured markdown with
143
- sections. The tag must scan well as a rendered GitHub Release page.
120
+ via `gh release create --notes-from-tag`. It is a condensed digest of this
121
+ entry, never a copy, and its format is owned by the `release-and-publish`
122
+ skill (step 4, "Create the annotated tag"): a short subject line without the
123
+ version, flat headline bullets — no Keep-a-Changelog section headers, no
124
+ gates line — at most one deps line, issue backlinks, and the changelog link
125
+ last. In release-PR mode the `git-wrapup` skill authors those bullets as the
126
+ PR body's `## Changes` and the tag copies them.
144
127
  -->
145
128
 
146
129
  ## Added
package/dist/index.js CHANGED
@@ -34,6 +34,12 @@ const CANVAS_OFF = {
34
34
  await createApp({
35
35
  name: 'eurostat-mcp-server',
36
36
  title: 'eurostat-mcp-server',
37
+ /**
38
+ * No tool gates on `ctx.requestInput`, so nothing here needs a durable
39
+ * session. Declaring it in source keeps the posture with the code rather than
40
+ * the deployment; `MCP_SESSION_MODE` still wins when it is set.
41
+ */
42
+ sessionMode: 'stateless',
37
43
  tools: [
38
44
  eurostatSearchDatasets,
39
45
  eurostatBrowseThemes,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iEAAiE,CAAC;AAC1G,OAAO,EAAE,oBAAoB,EAAE,MAAM,+DAA+D,CAAC;AACrG,OAAO,EAAE,yBAAyB,EAAE,MAAM,oEAAoE,CAAC;AAC/G,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,uBAAuB,EAAE,MAAM,kEAAkE,CAAC;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,kEAAkE,CAAC;AAC1G,OAAO,EAAE,0BAA0B,EAAE,MAAM,sEAAsE,CAAC;AAClH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+DAA+D,CAAC;AACrG,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,mDAAmD,CAAC;AAC5F,OAAO,EAAE,4BAA4B,EAAE,MAAM,6DAA6D,CAAC;AAC3G,OAAO,EAAE,uBAAuB,EAAE,MAAM,mDAAmD,CAAC;AAE5F;;;;;;;GAOG;AACH,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,KAAK,MAAM,CAAC;AAC5D,MAAM,UAAU,GAAG;IACjB,MAAM,EAAE,qFAAqF;IAC7F,IAAI,EAAE,6BAA6B;CAC3B,CAAC;AAEX,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,qBAAqB;IAC5B,KAAK,EAAE;QACL,sBAAsB;QACtB,oBAAoB;QACpB,sBAAsB;QACtB,0BAA0B;QAC1B,oBAAoB;QACpB,uBAAuB;QACvB,aAAa,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,YAAY,CAAC,yBAAyB,EAAE,UAAU,CAAC;QAC/F,aAAa,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,YAAY,CAAC,sBAAsB,EAAE,UAAU,CAAC;KAC1F;IACD,SAAS,EAAE,CAAC,uBAAuB,CAAC;IACpC,OAAO,EAAE,EAAE;IACX,YAAY,EAAE;;;;iMAIiL;IAC/L,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;IAC/B,KAAK,CAAC,IAAI;QACR,4BAA4B,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACxD,uBAAuB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACnD,uBAAuB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACnD,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iEAAiE,CAAC;AAC1G,OAAO,EAAE,oBAAoB,EAAE,MAAM,+DAA+D,CAAC;AACrG,OAAO,EAAE,yBAAyB,EAAE,MAAM,oEAAoE,CAAC;AAC/G,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,uBAAuB,EAAE,MAAM,kEAAkE,CAAC;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,kEAAkE,CAAC;AAC1G,OAAO,EAAE,0BAA0B,EAAE,MAAM,sEAAsE,CAAC;AAClH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+DAA+D,CAAC;AACrG,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,mDAAmD,CAAC;AAC5F,OAAO,EAAE,4BAA4B,EAAE,MAAM,6DAA6D,CAAC;AAC3G,OAAO,EAAE,uBAAuB,EAAE,MAAM,mDAAmD,CAAC;AAE5F;;;;;;;GAOG;AACH,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,KAAK,MAAM,CAAC;AAC5D,MAAM,UAAU,GAAG;IACjB,MAAM,EAAE,qFAAqF;IAC7F,IAAI,EAAE,6BAA6B;CAC3B,CAAC;AAEX,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,qBAAqB;IAC5B;;;;OAIG;IACH,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE;QACL,sBAAsB;QACtB,oBAAoB;QACpB,sBAAsB;QACtB,0BAA0B;QAC1B,oBAAoB;QACpB,uBAAuB;QACvB,aAAa,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,YAAY,CAAC,yBAAyB,EAAE,UAAU,CAAC;QAC/F,aAAa,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,YAAY,CAAC,sBAAsB,EAAE,UAAU,CAAC;KAC1F;IACD,SAAS,EAAE,CAAC,uBAAuB,CAAC;IACpC,OAAO,EAAE,EAAE;IACX,YAAY,EAAE;;;;iMAIiL;IAC/L,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;IAC/B,KAAK,CAAC,IAAI;QACR,4BAA4B,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACxD,uBAAuB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACnD,uBAAuB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACnD,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;CACF,CAAC,CAAC"}
@@ -34,6 +34,7 @@ export declare const eurostatDataframeDescribe: import("@cyanheads/mcp-ts-core")
34
34
  readonly code: JsonRpcErrorCode.NotFound;
35
35
  readonly when: "The canvas_id is unknown or its lifetime has elapsed.";
36
36
  readonly recovery: "Re-run eurostat_query_dataset to stage the data again and use the canvasId it returns.";
37
+ readonly thrownBy: "service";
37
38
  }], {
38
39
  readonly notice: z.ZodOptional<z.ZodString>;
39
40
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"eurostat-dataframe-describe.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgIpC,CAAC"}
1
+ {"version":3,"file":"eurostat-dataframe-describe.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8HpC,CAAC"}
@@ -3,6 +3,7 @@
3
3
  * @module mcp-server/tools/definitions/eurostat-dataframe-describe.tool
4
4
  */
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
+ import { CanvasIdSchema } from '@cyanheads/mcp-ts-core/canvas';
6
7
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
7
8
  import { acquireCanvas, getCanvas } from '../../../services/canvas-accessor.js';
8
9
  export const eurostatDataframeDescribe = tool('eurostat_dataframe_describe', {
@@ -10,10 +11,7 @@ export const eurostatDataframeDescribe = tool('eurostat_dataframe_describe', {
10
11
  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.',
11
12
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
12
13
  input: z.object({
13
- canvas_id: z
14
- .string()
15
- .min(1)
16
- .describe('Canvas identifier returned as canvasId by eurostat_query_dataset or eurostat_download_dataset. Identifies the workspace holding the staged tables.'),
14
+ canvas_id: CanvasIdSchema.describe('Canvas identifier returned as canvasId by eurostat_query_dataset or eurostat_download_dataset. Identifies the workspace holding the staged tables.'),
17
15
  }),
18
16
  output: z.object({
19
17
  canvasId: z.string().describe('Canvas identifier the tables were read from.'),
@@ -64,6 +62,7 @@ export const eurostatDataframeDescribe = tool('eurostat_dataframe_describe', {
64
62
  code: JsonRpcErrorCode.NotFound,
65
63
  when: 'The canvas_id is unknown or its lifetime has elapsed.',
66
64
  recovery: 'Re-run eurostat_query_dataset to stage the data again and use the canvasId it returns.',
65
+ thrownBy: 'service',
67
66
  },
68
67
  ],
69
68
  async handler(input, ctx) {
@@ -1 +1 @@
1
- {"version":3,"file":"eurostat-dataframe-describe.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"eurostat-dataframe-describe.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAEzE,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC,6BAA6B,EAAE;IAC3E,KAAK,EAAE,8BAA8B;IACrC,WAAW,EACT,g9BAAg9B;IACl9B,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE;IAC/E,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,SAAS,EAAE,cAAc,CAAC,QAAQ,CAChC,oJAAoJ,CACrJ;KACF,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC;QAC7E,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,CACP,mFAAmF,CACpF;QACH,MAAM,EAAE,CAAC;aACN,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;YAC5D,IAAI,EAAE,CAAC;iBACJ,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;iBACvB,QAAQ,CAAC,8CAA8C,CAAC;YAC3D,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;YACtD,SAAS,EAAE,CAAC;iBACT,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,uHAAuH,CACxH;YACH,OAAO,EAAE,CAAC;iBACP,KAAK,CACJ,CAAC;iBACE,MAAM,CAAC;gBACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;gBACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;gBACxE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;aAClE,CAAC;iBACD,QAAQ,CAAC,0BAA0B,CAAC,CACxC;iBACA,QAAQ,CAAC,+BAA+B,CAAC;SAC7C,CAAC;aACD,QAAQ,CAAC,yCAAyC,CAAC,CACvD;aACA,QAAQ,CACP,+GAA+G,CAChH;KACJ,CAAC;IACF,UAAU,EAAE;QACV,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,+EAA+E,CAAC;KAC7F;IAED,MAAM,EAAE;QACN;YACE,MAAM,EAAE,iBAAiB;YACzB,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;YACzC,IAAI,EAAE,mFAAmF;YACzF,SAAS,EAAE,KAAK;YAChB,QAAQ,EACN,uGAAuG;SAC1G;QACD;YACE,MAAM,EAAE,kBAAkB;YAC1B,IAAI,EAAE,gBAAgB,CAAC,QAAQ;YAC/B,IAAI,EAAE,uDAAuD;YAC7D,QAAQ,EACN,wFAAwF;YAC1F,QAAQ,EAAE,SAAS;SACpB;KACF;IAED,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG;QACtB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,CAAC,IAAI,CAAC,iBAAiB,EAAE,SAAS,EAAE,GAAG,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,CAAC;QACnF,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAEzC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAE7F,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,GAAG,CAAC,MAAM,CAAC,MAAM,CACf,UAAU,QAAQ,CAAC,QAAQ,uHAAuH,CACnJ,CAAC;QACJ,CAAC;QAED,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACzB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;gBAC5D,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC7B,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI;iBAC7B,CAAC,CAAC;aACJ,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;QACjB,MAAM,KAAK,GAAa;YACtB,cAAc,MAAM,CAAC,QAAQ,IAAI;YACjC,gBAAgB,MAAM,CAAC,SAAS,kBAAkB,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI;SAC3E,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,qBAAqB,KAAK,CAAC,SAAS,EAAE,CAAC;YAC3F,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;YACnD,KAAK,CAAC,IAAI,CAAC,aAAa,KAAK,CAAC,QAAQ,GAAG,MAAM,EAAE,CAAC,CAAC;YACnD,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBAChC,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,eAAe,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC;YAC1E,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QACD,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpD,CAAC;CACF,CAAC,CAAC"}
@@ -25,10 +25,12 @@ export declare const eurostatDataframeQuery: import("@cyanheads/mcp-ts-core").To
25
25
  readonly code: JsonRpcErrorCode.NotFound;
26
26
  readonly when: "The canvas_id is unknown or its lifetime has elapsed.";
27
27
  readonly recovery: "Re-run eurostat_query_dataset to stage the data again and use the canvasId it returns.";
28
+ readonly thrownBy: "service";
28
29
  }, {
29
30
  readonly reason: "missing_table";
30
31
  readonly code: JsonRpcErrorCode.NotFound;
31
32
  readonly when: "The SQL names a table that is not staged on this canvas, or that has expired.";
32
33
  readonly recovery: "Call eurostat_dataframe_describe for the staged table names, or re-run eurostat_query_dataset to stage them again.";
34
+ readonly thrownBy: "service";
33
35
  }], undefined>;
34
36
  //# sourceMappingURL=eurostat-dataframe-query.tool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"eurostat-dataframe-query.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-query.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;cAiHjC,CAAC"}
1
+ {"version":3,"file":"eurostat-dataframe-query.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-query.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgHjC,CAAC"}
@@ -4,6 +4,7 @@
4
4
  * @module mcp-server/tools/definitions/eurostat-dataframe-query.tool
5
5
  */
6
6
  import { tool, z } from '@cyanheads/mcp-ts-core';
7
+ import { CanvasIdSchema } from '@cyanheads/mcp-ts-core/canvas';
7
8
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
8
9
  import { acquireCanvas, getCanvas } from '../../../services/canvas-accessor.js';
9
10
  export const eurostatDataframeQuery = tool('eurostat_dataframe_query', {
@@ -11,10 +12,7 @@ export const eurostatDataframeQuery = tool('eurostat_dataframe_query', {
11
12
  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.',
12
13
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
13
14
  input: z.object({
14
- canvas_id: z
15
- .string()
16
- .min(1)
17
- .describe('Canvas identifier returned as canvasId by eurostat_query_dataset or eurostat_download_dataset. Identifies the workspace holding the staged tables.'),
15
+ canvas_id: CanvasIdSchema.describe('Canvas identifier returned as canvasId by eurostat_query_dataset or eurostat_download_dataset. Identifies the workspace holding the staged tables.'),
18
16
  sql: z
19
17
  .string()
20
18
  .min(1)
@@ -46,12 +44,14 @@ export const eurostatDataframeQuery = tool('eurostat_dataframe_query', {
46
44
  code: JsonRpcErrorCode.NotFound,
47
45
  when: 'The canvas_id is unknown or its lifetime has elapsed.',
48
46
  recovery: 'Re-run eurostat_query_dataset to stage the data again and use the canvasId it returns.',
47
+ thrownBy: 'service',
49
48
  },
50
49
  {
51
50
  reason: 'missing_table',
52
51
  code: JsonRpcErrorCode.NotFound,
53
52
  when: 'The SQL names a table that is not staged on this canvas, or that has expired.',
54
53
  recovery: 'Call eurostat_dataframe_describe for the staged table names, or re-run eurostat_query_dataset to stage them again.',
54
+ thrownBy: 'service',
55
55
  },
56
56
  ],
57
57
  async handler(input, ctx) {