@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.
- package/AGENTS.md +49 -25
- package/CLAUDE.md +49 -25
- package/Dockerfile +25 -6
- package/README.md +98 -95
- package/changelog/0.6.x/0.6.4.md +29 -0
- package/changelog/0.6.x/0.6.5.md +26 -0
- package/changelog/template.md +9 -26
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js +3 -4
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js +4 -4
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts +6 -0
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js +8 -4
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js +3 -4
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js.map +1 -1
- package/dist/services/eurostat-bulk/eurostat-bulk-service.js +1 -1
- package/dist/services/eurostat-data/eurostat-data-service.js +1 -1
- package/package.json +11 -10
- package/server.json +3 -3
- package/dist/eurostat-mcp-server.mcpb +0 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/eurostat-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/eurostat-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -29,147 +29,135 @@
|
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## Overview
|
|
33
33
|
|
|
34
|
-
|
|
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
|
|
41
|
-
| `eurostat_get_dimension_values` | List all valid codes for
|
|
42
|
-
| `eurostat_query_dataset` | Fetch a
|
|
43
|
-
| `eurostat_download_dataset` | Download a whole dataset
|
|
44
|
-
| `eurostat_dataframe_describe` | List the tables staged on a dataframe canvas with
|
|
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
|
-
###
|
|
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
|
-
|
|
57
|
+
## Capability reference
|
|
50
58
|
|
|
51
|
-
|
|
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
|
-
|
|
68
|
+
---
|
|
62
69
|
|
|
63
|
-
|
|
70
|
+
### `eurostat_browse_themes` <sub>tool</sub>
|
|
64
71
|
|
|
65
|
-
- Without `theme_code`: returns the top-level
|
|
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
|
|
68
|
-
- Returns a breadcrumb
|
|
69
|
-
- One branch per folder code — Eurostat files a few folder codes under several branches; a code resolves to the first
|
|
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
|
-
-
|
|
80
|
-
-
|
|
81
|
-
- For
|
|
82
|
-
-
|
|
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
|
-
-
|
|
91
|
-
- For
|
|
92
|
-
-
|
|
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
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
- `
|
|
104
|
-
-
|
|
105
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
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
|
|
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
|
-
### `
|
|
128
|
+
### `eurostat_dataframe_query` <sub>tool</sub>
|
|
131
129
|
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
+
### `eurostat://dataset/{dataset_code}` <sub>resource</sub>
|
|
141
139
|
|
|
142
|
-
|
|
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
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
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
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
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.
|
|
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
|
|
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`
|
package/changelog/template.md
CHANGED
|
@@ -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
|
|
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`.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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;
|
|
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:
|
|
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":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,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,
|
|
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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"}
|
|
@@ -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;
|
|
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:
|
|
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) {
|