@cyanheads/eurostat-mcp-server 0.3.0 → 0.4.0
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/CLAUDE.md +1 -1
- package/Dockerfile +16 -3
- package/README.md +27 -6
- package/dist/index.js +23 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.d.ts +40 -0
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js +113 -0
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.d.ts +34 -0
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js +98 -0
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts +9 -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 +72 -10
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js.map +1 -1
- package/dist/services/canvas-accessor.d.ts +26 -0
- package/dist/services/canvas-accessor.d.ts.map +1 -0
- package/dist/services/canvas-accessor.js +57 -0
- package/dist/services/canvas-accessor.js.map +1 -0
- package/dist/services/eurostat-data/eurostat-data-service.d.ts +53 -8
- package/dist/services/eurostat-data/eurostat-data-service.d.ts.map +1 -1
- package/dist/services/eurostat-data/eurostat-data-service.js +90 -13
- package/dist/services/eurostat-data/eurostat-data-service.js.map +1 -1
- package/dist/services/eurostat-data/types.d.ts +30 -0
- package/dist/services/eurostat-data/types.d.ts.map +1 -1
- package/dist/services/eurostat-data/types.js +4 -0
- package/dist/services/eurostat-data/types.js.map +1 -1
- package/package.json +2 -1
- package/server.json +58 -4
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Agent Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** @cyanheads/eurostat-mcp-server
|
|
4
|
-
**Version:** 0.
|
|
4
|
+
**Version:** 0.4.0
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.1`
|
|
6
6
|
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
7
7
|
**Zod:** `^4.4.3`
|
package/Dockerfile
CHANGED
|
@@ -47,7 +47,10 @@ LABEL org.opencontainers.image.source="https://github.com/cyanheads/eurostat-mcp
|
|
|
47
47
|
COPY package.json bun.lock ./
|
|
48
48
|
|
|
49
49
|
# Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
|
|
50
|
-
# that are not needed in the final production image.
|
|
50
|
+
# that are not needed in the final production image. This installs every direct
|
|
51
|
+
# runtime dependency, including the @duckdb/node-api binding that backs DataCanvas —
|
|
52
|
+
# the canvas is gated at runtime by CANVAS_PROVIDER_TYPE, not at build time, so the
|
|
53
|
+
# image always carries the binding and a deployment turns the feature on with an env var.
|
|
51
54
|
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
52
55
|
bun install --production --frozen-lockfile --ignore-scripts
|
|
53
56
|
|
|
@@ -75,8 +78,16 @@ COPY --from=build /usr/src/app/dist ./dist
|
|
|
75
78
|
# The 'oven/bun' image already provides a non-root user named 'bun'.
|
|
76
79
|
# We will use this existing user for enhanced security.
|
|
77
80
|
|
|
78
|
-
# Create and set permissions for the log
|
|
79
|
-
|
|
81
|
+
# Create and set permissions for the log and canvas directories, assigning ownership
|
|
82
|
+
# to the 'bun' user. DuckDB writes spill files under CANVAS_TEMP_PATH and the framework
|
|
83
|
+
# sandboxes path exports to CANVAS_EXPORT_PATH. The export root is the one that breaks
|
|
84
|
+
# by default: it resolves under the working directory /usr/src/app, which this non-root
|
|
85
|
+
# user does not own. Both are pointed at owned directories below so neither depends on
|
|
86
|
+
# the image's writable-by-accident paths.
|
|
87
|
+
RUN mkdir -p /var/log/eurostat-mcp-server \
|
|
88
|
+
/var/lib/eurostat-mcp-server/canvas-tmp \
|
|
89
|
+
/var/lib/eurostat-mcp-server/canvas-exports \
|
|
90
|
+
&& chown -R bun:bun /var/log/eurostat-mcp-server /var/lib/eurostat-mcp-server
|
|
80
91
|
|
|
81
92
|
# Switch to the non-root user
|
|
82
93
|
USER bun
|
|
@@ -94,6 +105,8 @@ ENV MCP_SESSION_MODE="stateless"
|
|
|
94
105
|
ENV MCP_LOG_LEVEL="info"
|
|
95
106
|
ENV LOGS_DIR="/var/log/eurostat-mcp-server"
|
|
96
107
|
ENV MCP_FORCE_CONSOLE_LOGGING="true"
|
|
108
|
+
ENV CANVAS_TEMP_PATH="/var/lib/eurostat-mcp-server/canvas-tmp"
|
|
109
|
+
ENV CANVAS_EXPORT_PATH="/var/lib/eurostat-mcp-server/canvas-exports"
|
|
97
110
|
|
|
98
111
|
# Expose the port the server listens on
|
|
99
112
|
EXPOSE ${MCP_HTTP_PORT}
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>@cyanheads/eurostat-mcp-server</h1>
|
|
3
3
|
<p><b>Search and query the Eurostat catalogue — EU economy, demography, trade, health, and NUTS regional data via MCP. STDIO or Streamable HTTP.</b>
|
|
4
|
-
<div>5 Tools • 1 Resource</div>
|
|
4
|
+
<div>5 Tools (7 with the dataframe canvas) • 1 Resource</div>
|
|
5
5
|
</p>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
@@ -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
|
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
## Tools
|
|
33
33
|
|
|
34
|
-
5 tools for discovering and querying Eurostat statistical datasets:
|
|
34
|
+
5 tools for discovering and querying Eurostat statistical datasets, plus 2 more when the optional dataframe canvas is enabled:
|
|
35
35
|
|
|
36
36
|
| Tool | Description |
|
|
37
37
|
|:---|:---|
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
| `eurostat_get_dataset_info` | Fetch metadata for a dataset: dimensions with sample values, time range, observation count, and last-update date |
|
|
41
41
|
| `eurostat_get_dimension_values` | List all valid codes for a specific dimension (e.g., all geo codes, all unit codes); supports NUTS hierarchy filtering |
|
|
42
42
|
| `eurostat_query_dataset` | Fetch decoded statistical observations with dimension filters, NUTS geo-level, and time-range controls |
|
|
43
|
+
| `eurostat_dataframe_describe` | List the tables staged on a dataframe canvas with their row counts and column types — canvas only |
|
|
44
|
+
| `eurostat_dataframe_query` | Run a read-only SQL SELECT across staged tables — canvas only |
|
|
43
45
|
|
|
44
46
|
### `eurostat_search_datasets`
|
|
45
47
|
|
|
@@ -99,9 +101,22 @@ Fetch statistical data from a Eurostat dataset.
|
|
|
99
101
|
- Time range via `since_period`/`until_period` (e.g., `"2020"`, `"2023-Q1"`) or `last_n_periods` for the N most recent
|
|
100
102
|
- Returns decoded observations with dimension codes and labels, numeric values, and status flags (`p` = provisional, `e` = estimated, etc.)
|
|
101
103
|
- 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
|
|
102
|
-
-
|
|
104
|
+
- Inline rows are capped at 5,000, applied while decoding so a broad query never builds the rest; `obsCount`, `missingObsCount` and `timeRange` still describe the whole match, and `truncated` flags when the cap bit. Filter the query to shrink what Eurostat sends — the cap bounds the decode, not the transfer
|
|
105
|
+
- With the dataframe canvas enabled, a match past the cap is also staged whole as a SQL table and the response returns `canvasId` / `tableName` / `stagedRowCount`; the rows are streamed into the table one at a time from the response body already in memory, so nothing extra is fetched and the match is never materialized as an array. Without a canvas those fields are absent and narrowing the query is the way to the rest
|
|
106
|
+
- Pass `canvas_id` from an earlier response to stage several results side by side and join across them
|
|
103
107
|
- Async-response detection — large unfiltered queries return an actionable, non-retryable error with filter guidance rather than silently timing out
|
|
104
108
|
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### `eurostat_dataframe_describe` / `eurostat_dataframe_query`
|
|
112
|
+
|
|
113
|
+
SQL over the results `eurostat_query_dataset` stages. **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.
|
|
114
|
+
|
|
115
|
+
- `eurostat_dataframe_describe` lists the staged tables with row counts and column names and types — call it before writing SQL
|
|
116
|
+
- `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
|
|
117
|
+
- Staged columns are flat: each dimension contributes a code column named after the dimension (`geo`) and a label column beside it (`geo_label`), followed by `obs_value`, `obs_flag`, and `obs_flag_label`
|
|
118
|
+
- 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
|
|
119
|
+
|
|
105
120
|
## Resource
|
|
106
121
|
|
|
107
122
|
| Type | Name | Description |
|
|
@@ -126,6 +141,7 @@ Eurostat-specific:
|
|
|
126
141
|
- Async-response detection — Eurostat returns a warning object rather than an error for over-limit queries; the server intercepts it and returns an actionable error with filter guidance
|
|
127
142
|
- NUTS hierarchy geo-level filtering across query and dimension-value tools
|
|
128
143
|
- Status flag decoding (provisional, estimated, definition differs, etc.)
|
|
144
|
+
- Optional DuckDB dataframe canvas — a query matching more than the inline cap is streamed row by row into a SQL table, reaching the observations the cap drops without a second request to Eurostat
|
|
129
145
|
|
|
130
146
|
Agent-friendly output:
|
|
131
147
|
|
|
@@ -251,6 +267,10 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
|
|
|
251
267
|
| `EUROSTAT_BASE_URL` | Eurostat API base URL | `https://ec.europa.eu/eurostat/api/dissemination` |
|
|
252
268
|
| `EUROSTAT_REQUEST_TIMEOUT_MS` | HTTP request timeout in ms | `30000` |
|
|
253
269
|
| `EUROSTAT_TOC_CACHE_TTL_MS` | Catalogue TOC cache lifetime in ms — the first search or browse call past this age refreshes it | `43200000` (12 hours) |
|
|
270
|
+
| `CANVAS_PROVIDER_TYPE` | `duckdb` enables the dataframe canvas: lists the two dataframe tools and lets `eurostat_query_dataset` stage a match past its inline cap | `none` |
|
|
271
|
+
| `CANVAS_TEMP_PATH` | Directory DuckDB writes canvas spill files to. Must be writable by the server process | `<os tmpdir>/mcp-canvas` |
|
|
272
|
+
| `CANVAS_TTL_MS` | Sliding lifetime of a staged canvas in ms; every call against it extends the window | `86400000` (24 hours) |
|
|
273
|
+
| `CANVAS_DEFAULT_ROW_LIMIT` | Max rows one `eurostat_dataframe_query` returns before reporting `truncated` | `10000` |
|
|
254
274
|
| `OTEL_ENABLED` | Enable OpenTelemetry | `false` |
|
|
255
275
|
|
|
256
276
|
## Running the server
|
|
@@ -279,10 +299,11 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
|
|
|
279
299
|
|
|
280
300
|
| Directory | Purpose |
|
|
281
301
|
|:---|:---|
|
|
282
|
-
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Five tools for discovery and data access. |
|
|
302
|
+
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Five tools for discovery and data access, plus two canvas-gated dataframe tools. |
|
|
283
303
|
| `src/mcp-server/resources` | Resource definitions. Dataset metadata resource. |
|
|
284
304
|
| `src/services/eurostat-catalogue` | Catalogue service — fetches and parses the Eurostat TOC TXT file; TTL-bounded in-memory cache. |
|
|
285
|
-
| `src/services/eurostat-data` | Data service — Statistics API HTTP client, JSON-stat 2.0 decoder, async-response detection. |
|
|
305
|
+
| `src/services/eurostat-data` | Data service — Statistics API HTTP client, JSON-stat 2.0 decoder, async-response detection, dataframe row source. |
|
|
306
|
+
| `src/services/canvas-accessor.ts` | Module-level accessor for the optional DataCanvas, plus the acquire helper that names the misconfigured path on a permission failure. |
|
|
286
307
|
| `src/config` | Server-specific environment variable parsing and validation with Zod. |
|
|
287
308
|
| `tests/` | Unit and integration tests, mirroring the `src/` structure. |
|
|
288
309
|
|
package/dist/index.js
CHANGED
|
@@ -3,15 +3,32 @@
|
|
|
3
3
|
* @fileoverview eurostat-mcp-server MCP server entry point.
|
|
4
4
|
* @module index
|
|
5
5
|
*/
|
|
6
|
-
import { createApp } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { createApp, disabledTool } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { config } from '@cyanheads/mcp-ts-core/config';
|
|
7
8
|
import { eurostatDatasetResource } from './mcp-server/resources/definitions/eurostat-dataset.resource.js';
|
|
8
9
|
import { eurostatBrowseThemes } from './mcp-server/tools/definitions/eurostat-browse-themes.tool.js';
|
|
10
|
+
import { eurostatDataframeDescribe } from './mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js';
|
|
11
|
+
import { eurostatDataframeQuery } from './mcp-server/tools/definitions/eurostat-dataframe-query.tool.js';
|
|
9
12
|
import { eurostatGetDatasetInfo } from './mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js';
|
|
10
13
|
import { eurostatGetDimensionValues } from './mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js';
|
|
11
14
|
import { eurostatQueryDataset } from './mcp-server/tools/definitions/eurostat-query-dataset.tool.js';
|
|
12
15
|
import { eurostatSearchDatasets } from './mcp-server/tools/definitions/eurostat-search-datasets.tool.js';
|
|
16
|
+
import { setCanvas } from './services/canvas-accessor.js';
|
|
13
17
|
import { initEurostatCatalogueService } from './services/eurostat-catalogue/eurostat-catalogue-service.js';
|
|
14
18
|
import { initEurostatDataService } from './services/eurostat-data/eurostat-data-service.js';
|
|
19
|
+
/**
|
|
20
|
+
* The dataframe tools address tables on a canvas, so without one they have
|
|
21
|
+
* nothing to talk to. `disabledTool` keeps them off `tools/list` — clients
|
|
22
|
+
* cannot call them — while leaving them visible on the landing page and the
|
|
23
|
+
* server card with the variable that turns them on, so an operator can see the
|
|
24
|
+
* capability exists rather than wondering why the README documents a tool the
|
|
25
|
+
* server does not advertise.
|
|
26
|
+
*/
|
|
27
|
+
const canvasEnabled = config.canvas.providerType !== 'none';
|
|
28
|
+
const CANVAS_OFF = {
|
|
29
|
+
reason: 'This deployment runs without a dataframe canvas, so there are no tables to address.',
|
|
30
|
+
hint: 'CANVAS_PROVIDER_TYPE=duckdb',
|
|
31
|
+
};
|
|
15
32
|
await createApp({
|
|
16
33
|
name: 'eurostat-mcp-server',
|
|
17
34
|
title: 'eurostat-mcp-server',
|
|
@@ -21,16 +38,20 @@ await createApp({
|
|
|
21
38
|
eurostatGetDatasetInfo,
|
|
22
39
|
eurostatGetDimensionValues,
|
|
23
40
|
eurostatQueryDataset,
|
|
41
|
+
canvasEnabled ? eurostatDataframeDescribe : disabledTool(eurostatDataframeDescribe, CANVAS_OFF),
|
|
42
|
+
canvasEnabled ? eurostatDataframeQuery : disabledTool(eurostatDataframeQuery, CANVAS_OFF),
|
|
24
43
|
],
|
|
25
44
|
resources: [eurostatDatasetResource],
|
|
26
45
|
prompts: [],
|
|
27
46
|
instructions: `Eurostat MCP server — EU statistical data across the Eurostat catalogue.
|
|
28
47
|
Workflow: eurostat_search_datasets or eurostat_browse_themes to find a dataset code → eurostat_get_dataset_info to see dimensions → eurostat_get_dimension_values to list valid filter values → eurostat_query_dataset to fetch observations.
|
|
29
|
-
Apply dimension filters to
|
|
48
|
+
Apply dimension filters to keep queries small: an unfiltered query on a large dataset is slow and can return an async response error instead of data.
|
|
49
|
+
eurostat_query_dataset returns at most 5,000 observations inline. When the match is larger and this deployment runs a dataframe canvas, the whole match is also staged as a SQL table named in the response — read it with eurostat_dataframe_query (eurostat_dataframe_describe lists the table and column names). The dataframe tools are not listed when the canvas is off; there, narrowing the filters is what brings the rest of a capped match into reach.`,
|
|
30
50
|
landing: { requireAuth: false },
|
|
31
51
|
setup(core) {
|
|
32
52
|
initEurostatCatalogueService(core.config, core.storage);
|
|
33
53
|
initEurostatDataService(core.config, core.storage);
|
|
54
|
+
setCanvas(core.canvas);
|
|
34
55
|
},
|
|
35
56
|
});
|
|
36
57
|
//# sourceMappingURL=index.js.map
|
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,MAAM,wBAAwB,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,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,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,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;;;kcAGkb;IAChc,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,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tool for listing the tables staged on a Eurostat DataCanvas.
|
|
3
|
+
* @module mcp-server/tools/definitions/eurostat-dataframe-describe.tool
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
export declare const eurostatDataframeDescribe: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
|
+
canvas_id: z.ZodString;
|
|
9
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
10
|
+
canvasId: z.ZodString;
|
|
11
|
+
expiresAt: z.ZodString;
|
|
12
|
+
tables: z.ZodArray<z.ZodObject<{
|
|
13
|
+
name: z.ZodString;
|
|
14
|
+
kind: z.ZodEnum<{
|
|
15
|
+
table: "table";
|
|
16
|
+
view: "view";
|
|
17
|
+
}>;
|
|
18
|
+
rowCount: z.ZodNumber;
|
|
19
|
+
expiresAt: z.ZodOptional<z.ZodString>;
|
|
20
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
21
|
+
name: z.ZodString;
|
|
22
|
+
type: z.ZodString;
|
|
23
|
+
nullable: z.ZodBoolean;
|
|
24
|
+
}, z.core.$strip>>;
|
|
25
|
+
}, z.core.$strip>>;
|
|
26
|
+
}, z.core.$strip>, readonly [{
|
|
27
|
+
readonly reason: "canvas_disabled";
|
|
28
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
29
|
+
readonly when: "This deployment runs without a dataframe canvas, so there is nothing to describe.";
|
|
30
|
+
readonly retryable: false;
|
|
31
|
+
readonly recovery: "Query the data inline with eurostat_query_dataset, narrowing dimension filters until the result fits.";
|
|
32
|
+
}, {
|
|
33
|
+
readonly reason: "canvas_not_found";
|
|
34
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
35
|
+
readonly when: "The canvas_id is unknown or its lifetime has elapsed.";
|
|
36
|
+
readonly recovery: "Re-run eurostat_query_dataset to stage the data again and use the canvasId it returns.";
|
|
37
|
+
}], {
|
|
38
|
+
readonly notice: z.ZodOptional<z.ZodString>;
|
|
39
|
+
}>;
|
|
40
|
+
//# sourceMappingURL=eurostat-dataframe-describe.tool.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tool for listing the tables staged on a Eurostat DataCanvas.
|
|
3
|
+
* @module mcp-server/tools/definitions/eurostat-dataframe-describe.tool
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { acquireCanvas, getCanvas } from '../../../services/canvas-accessor.js';
|
|
8
|
+
export const eurostatDataframeDescribe = tool('eurostat_dataframe_describe', {
|
|
9
|
+
title: 'Describe Eurostat Dataframes',
|
|
10
|
+
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 response that reported a staged table. Every observation column is flat: each dimension contributes a code column named after the dimension (e.g. "geo") and a label column beside it (e.g. "geo_label"), alongside obs_value, obs_flag, and obs_flag_label.',
|
|
11
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
12
|
+
input: z.object({
|
|
13
|
+
canvas_id: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.describe('Canvas identifier returned by eurostat_query_dataset as canvasId. Identifies the workspace holding the staged tables.'),
|
|
17
|
+
}),
|
|
18
|
+
output: z.object({
|
|
19
|
+
canvasId: z.string().describe('Canvas identifier the tables were read from.'),
|
|
20
|
+
expiresAt: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe('ISO 8601 timestamp when the canvas expires. Every call on it slides this forward.'),
|
|
23
|
+
tables: z
|
|
24
|
+
.array(z
|
|
25
|
+
.object({
|
|
26
|
+
name: z.string().describe('Table name to reference in SQL.'),
|
|
27
|
+
kind: z
|
|
28
|
+
.enum(['table', 'view'])
|
|
29
|
+
.describe('Whether the entry is a base table or a view.'),
|
|
30
|
+
rowCount: z.number().describe('Rows the table holds.'),
|
|
31
|
+
expiresAt: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('ISO 8601 expiry of this table specifically. Omitted when the table ages with the canvas rather than on its own clock.'),
|
|
35
|
+
columns: z
|
|
36
|
+
.array(z
|
|
37
|
+
.object({
|
|
38
|
+
name: z.string().describe('Column name.'),
|
|
39
|
+
type: z.string().describe('SQL column type (e.g. "VARCHAR", "DOUBLE").'),
|
|
40
|
+
nullable: z.boolean().describe('Whether the column admits NULL.'),
|
|
41
|
+
})
|
|
42
|
+
.describe('One column of the table.'))
|
|
43
|
+
.describe('Columns in declaration order.'),
|
|
44
|
+
})
|
|
45
|
+
.describe('One table or view staged on the canvas.'))
|
|
46
|
+
.describe('Tables staged on this canvas. Empty when nothing has been staged yet, or when every staged table has expired.'),
|
|
47
|
+
}),
|
|
48
|
+
enrichment: {
|
|
49
|
+
notice: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('Guidance when the canvas holds no tables. Omitted when it holds at least one.'),
|
|
53
|
+
},
|
|
54
|
+
errors: [
|
|
55
|
+
{
|
|
56
|
+
reason: 'canvas_disabled',
|
|
57
|
+
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
58
|
+
when: 'This deployment runs without a dataframe canvas, so there is nothing to describe.',
|
|
59
|
+
retryable: false,
|
|
60
|
+
recovery: 'Query the data inline with eurostat_query_dataset, narrowing dimension filters until the result fits.',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
reason: 'canvas_not_found',
|
|
64
|
+
code: JsonRpcErrorCode.NotFound,
|
|
65
|
+
when: 'The canvas_id is unknown or its lifetime has elapsed.',
|
|
66
|
+
recovery: 'Re-run eurostat_query_dataset to stage the data again and use the canvasId it returns.',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
async handler(input, ctx) {
|
|
70
|
+
const canvas = getCanvas();
|
|
71
|
+
if (!canvas) {
|
|
72
|
+
throw ctx.fail('canvas_disabled', undefined, ctx.recoveryFor('canvas_disabled'));
|
|
73
|
+
}
|
|
74
|
+
const instance = await acquireCanvas(canvas, input.canvas_id, ctx);
|
|
75
|
+
const tables = await instance.describe();
|
|
76
|
+
ctx.log.info('Described canvas', { canvasId: instance.canvasId, tableCount: tables.length });
|
|
77
|
+
if (tables.length === 0) {
|
|
78
|
+
ctx.enrich.notice(`Canvas ${instance.canvasId} holds no tables. Staged tables expire on their own schedule — re-run eurostat_query_dataset to stage the data again.`);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
canvasId: instance.canvasId,
|
|
82
|
+
expiresAt: instance.expiresAt,
|
|
83
|
+
tables: tables.map((t) => ({
|
|
84
|
+
name: t.name,
|
|
85
|
+
kind: t.kind,
|
|
86
|
+
rowCount: t.rowCount,
|
|
87
|
+
...(t.expiresAt !== undefined && { expiresAt: t.expiresAt }),
|
|
88
|
+
columns: t.columns.map((c) => ({
|
|
89
|
+
name: c.name,
|
|
90
|
+
type: c.type,
|
|
91
|
+
nullable: c.nullable ?? true,
|
|
92
|
+
})),
|
|
93
|
+
})),
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
format: (result) => {
|
|
97
|
+
const lines = [
|
|
98
|
+
`# Canvas \`${result.canvasId}\``,
|
|
99
|
+
`**Expires:** ${result.expiresAt} | **Tables:** ${result.tables.length}\n`,
|
|
100
|
+
];
|
|
101
|
+
for (const table of result.tables) {
|
|
102
|
+
const expiry = table.expiresAt === undefined ? '' : ` | **expiresAt:** ${table.expiresAt}`;
|
|
103
|
+
lines.push(`## \`${table.name}\` (${table.kind})`);
|
|
104
|
+
lines.push(`**Rows:** ${table.rowCount}${expiry}`);
|
|
105
|
+
for (const col of table.columns) {
|
|
106
|
+
lines.push(`- \`${col.name}\` ${col.type} (nullable: ${col.nullable})`);
|
|
107
|
+
}
|
|
108
|
+
lines.push('');
|
|
109
|
+
}
|
|
110
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=eurostat-dataframe-describe.tool.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tool for running read-only SQL against tables staged on a
|
|
3
|
+
* Eurostat DataCanvas.
|
|
4
|
+
* @module mcp-server/tools/definitions/eurostat-dataframe-query.tool
|
|
5
|
+
*/
|
|
6
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
export declare const eurostatDataframeQuery: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
9
|
+
canvas_id: z.ZodString;
|
|
10
|
+
sql: z.ZodString;
|
|
11
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
12
|
+
canvasId: z.ZodString;
|
|
13
|
+
columns: z.ZodArray<z.ZodString>;
|
|
14
|
+
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
15
|
+
rowCount: z.ZodNumber;
|
|
16
|
+
truncated: z.ZodBoolean;
|
|
17
|
+
}, z.core.$strip>, readonly [{
|
|
18
|
+
readonly reason: "canvas_disabled";
|
|
19
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
20
|
+
readonly when: "This deployment runs without a dataframe canvas, so there is nothing to query.";
|
|
21
|
+
readonly retryable: false;
|
|
22
|
+
readonly recovery: "Query the data inline with eurostat_query_dataset, narrowing dimension filters until the result fits.";
|
|
23
|
+
}, {
|
|
24
|
+
readonly reason: "canvas_not_found";
|
|
25
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
26
|
+
readonly when: "The canvas_id is unknown or its lifetime has elapsed.";
|
|
27
|
+
readonly recovery: "Re-run eurostat_query_dataset to stage the data again and use the canvasId it returns.";
|
|
28
|
+
}, {
|
|
29
|
+
readonly reason: "missing_table";
|
|
30
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
31
|
+
readonly when: "The SQL names a table that is not staged on this canvas, or that has expired.";
|
|
32
|
+
readonly recovery: "Call eurostat_dataframe_describe for the staged table names, or re-run eurostat_query_dataset to stage them again.";
|
|
33
|
+
}], undefined>;
|
|
34
|
+
//# sourceMappingURL=eurostat-dataframe-query.tool.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tool for running read-only SQL against tables staged on a
|
|
3
|
+
* Eurostat DataCanvas.
|
|
4
|
+
* @module mcp-server/tools/definitions/eurostat-dataframe-query.tool
|
|
5
|
+
*/
|
|
6
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
import { acquireCanvas, getCanvas } from '../../../services/canvas-accessor.js';
|
|
9
|
+
export const eurostatDataframeQuery = tool('eurostat_dataframe_query', {
|
|
10
|
+
title: 'Query Eurostat Dataframes',
|
|
11
|
+
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 to aggregate, group, or join across staged tables without re-fetching from Eurostat. Call eurostat_dataframe_describe first for the table and column names. Only a single SELECT statement runs: statement chaining, non-SELECT verbs, and functions that read files or external data are rejected. Columns are flat — each dimension appears as a code column named after the dimension plus a "_label" companion, with the measure in obs_value and the status flag in obs_flag / obs_flag_label.',
|
|
12
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
13
|
+
input: z.object({
|
|
14
|
+
canvas_id: z
|
|
15
|
+
.string()
|
|
16
|
+
.min(1)
|
|
17
|
+
.describe('Canvas identifier returned by eurostat_query_dataset as canvasId. Identifies the workspace holding the staged tables.'),
|
|
18
|
+
sql: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.describe("A single read-only SELECT statement. Reference tables by the names eurostat_dataframe_describe reports. Example: SELECT geo, geo_label, AVG(obs_value) AS mean FROM df_a1b2c3d4 WHERE time >= '2020' GROUP BY geo, geo_label ORDER BY mean DESC."),
|
|
22
|
+
}),
|
|
23
|
+
output: z.object({
|
|
24
|
+
canvasId: z.string().describe('Canvas identifier the query ran against.'),
|
|
25
|
+
columns: z.array(z.string()).describe('Column names in projection order.'),
|
|
26
|
+
rows: z
|
|
27
|
+
.array(z.record(z.string(), z.unknown()))
|
|
28
|
+
.describe('Result rows, each keyed by column name. Bounded by the canvas row limit. 64-bit integer results — COUNT(*) among them — arrive as strings so values outside the JSON number range survive intact; cast to DOUBLE in the SQL if a number is wanted.'),
|
|
29
|
+
rowCount: z
|
|
30
|
+
.number()
|
|
31
|
+
.describe('Rows materialized into this response. Equals the full result size unless truncated is true.'),
|
|
32
|
+
truncated: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.describe('True when the result exceeded the canvas row limit and was cut short. Add a LIMIT, an aggregate, or a narrower WHERE clause to see the rest.'),
|
|
35
|
+
}),
|
|
36
|
+
errors: [
|
|
37
|
+
{
|
|
38
|
+
reason: 'canvas_disabled',
|
|
39
|
+
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
40
|
+
when: 'This deployment runs without a dataframe canvas, so there is nothing to query.',
|
|
41
|
+
retryable: false,
|
|
42
|
+
recovery: 'Query the data inline with eurostat_query_dataset, narrowing dimension filters until the result fits.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
reason: 'canvas_not_found',
|
|
46
|
+
code: JsonRpcErrorCode.NotFound,
|
|
47
|
+
when: 'The canvas_id is unknown or its lifetime has elapsed.',
|
|
48
|
+
recovery: 'Re-run eurostat_query_dataset to stage the data again and use the canvasId it returns.',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
reason: 'missing_table',
|
|
52
|
+
code: JsonRpcErrorCode.NotFound,
|
|
53
|
+
when: 'The SQL names a table that is not staged on this canvas, or that has expired.',
|
|
54
|
+
recovery: 'Call eurostat_dataframe_describe for the staged table names, or re-run eurostat_query_dataset to stage them again.',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
async handler(input, ctx) {
|
|
58
|
+
const canvas = getCanvas();
|
|
59
|
+
if (!canvas) {
|
|
60
|
+
throw ctx.fail('canvas_disabled', undefined, ctx.recoveryFor('canvas_disabled'));
|
|
61
|
+
}
|
|
62
|
+
const instance = await acquireCanvas(canvas, input.canvas_id, ctx);
|
|
63
|
+
/**
|
|
64
|
+
* The SQL is not sanitized here on purpose. The canvas gate rejects
|
|
65
|
+
* anything that is not a single SELECT — statement count, statement type,
|
|
66
|
+
* an EXPLAIN-plan operator allowlist, and a table-function deny-list — and
|
|
67
|
+
* a rejection there is a typed ValidationError carrying the reason. A
|
|
68
|
+
* second, weaker string filter in front of it would only shadow those
|
|
69
|
+
* reasons with a vaguer message.
|
|
70
|
+
*/
|
|
71
|
+
const result = await instance.query(input.sql, { signal: ctx.signal });
|
|
72
|
+
ctx.log.info('Canvas query complete', {
|
|
73
|
+
canvasId: instance.canvasId,
|
|
74
|
+
rowCount: result.rowCount,
|
|
75
|
+
truncated: result.truncated === true,
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
canvasId: instance.canvasId,
|
|
79
|
+
columns: result.columns,
|
|
80
|
+
rows: result.rows,
|
|
81
|
+
rowCount: result.rowCount,
|
|
82
|
+
truncated: result.truncated === true,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
format: (result) => {
|
|
86
|
+
const lines = [
|
|
87
|
+
`# Query result (canvas \`${result.canvasId}\`)`,
|
|
88
|
+
`**Rows:** ${result.rowCount} | **Truncated:** ${result.truncated}${result.truncated ? ' — add a LIMIT, an aggregate, or a narrower WHERE clause' : ''}`,
|
|
89
|
+
`**Columns:** ${result.columns.join(', ')}\n`,
|
|
90
|
+
];
|
|
91
|
+
// Every row renders, so content[] carries the same set structuredContent does.
|
|
92
|
+
for (const row of result.rows) {
|
|
93
|
+
lines.push(result.columns.map((col) => `${col}=${row[col] === null ? 'NULL' : row[col]}`).join(' | '));
|
|
94
|
+
}
|
|
95
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
//# sourceMappingURL=eurostat-dataframe-query.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eurostat-dataframe-query.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-dataframe-query.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,6 +22,7 @@ export declare const eurostatQueryDataset: import("@cyanheads/mcp-ts-core").Tool
|
|
|
22
22
|
EN: "EN";
|
|
23
23
|
FR: "FR";
|
|
24
24
|
}>>;
|
|
25
|
+
canvas_id: z.ZodOptional<z.ZodString>;
|
|
25
26
|
}, z.core.$strip>, z.ZodObject<{
|
|
26
27
|
datasetCode: z.ZodString;
|
|
27
28
|
datasetLabel: z.ZodString;
|
|
@@ -36,6 +37,9 @@ export declare const eurostatQueryDataset: import("@cyanheads/mcp-ts-core").Tool
|
|
|
36
37
|
}, z.core.$strip>>;
|
|
37
38
|
obsCount: z.ZodNumber;
|
|
38
39
|
truncated: z.ZodBoolean;
|
|
40
|
+
canvasId: z.ZodOptional<z.ZodString>;
|
|
41
|
+
tableName: z.ZodOptional<z.ZodString>;
|
|
42
|
+
stagedRowCount: z.ZodOptional<z.ZodNumber>;
|
|
39
43
|
timeRange: z.ZodObject<{
|
|
40
44
|
start: z.ZodOptional<z.ZodString>;
|
|
41
45
|
end: z.ZodOptional<z.ZodString>;
|
|
@@ -67,6 +71,11 @@ export declare const eurostatQueryDataset: import("@cyanheads/mcp-ts-core").Tool
|
|
|
67
71
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
68
72
|
readonly when: "Mutually exclusive parameters were combined: \"geo\" filter + geo_level, or since_period/until_period + last_n_periods.";
|
|
69
73
|
readonly recovery: "Use \"geo\" or geo_level (not both); use since_period/until_period or last_n_periods (not both).";
|
|
74
|
+
}, {
|
|
75
|
+
readonly reason: "canvas_not_found";
|
|
76
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
77
|
+
readonly when: "A canvas_id was supplied for staging but is unknown or its lifetime has elapsed.";
|
|
78
|
+
readonly recovery: "Omit canvas_id so the response starts a fresh canvas and returns its canvasId.";
|
|
70
79
|
}], {
|
|
71
80
|
readonly appliedFilters: z.ZodObject<{
|
|
72
81
|
filters: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eurostat-query-dataset.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-query-dataset.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"eurostat-query-dataset.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-query-dataset.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;AAahF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6X/B,CAAC"}
|