@cyanheads/eurostat-mcp-server 0.10.1 → 0.11.1

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 (66) hide show
  1. package/AGENTS.md +15 -4
  2. package/CLAUDE.md +15 -4
  3. package/Dockerfile +4 -0
  4. package/README.md +41 -102
  5. package/changelog/0.11.x/0.11.0.md +33 -0
  6. package/changelog/0.11.x/0.11.1.md +23 -0
  7. package/dist/config/server-config.js +1 -1
  8. package/dist/config/server-config.js.map +1 -1
  9. package/dist/index.js +4 -9
  10. package/dist/index.js.map +1 -1
  11. package/dist/mcp-server/tools/clamp-read-limit.d.ts +1 -1
  12. package/dist/mcp-server/tools/clamp-read-limit.d.ts.map +1 -1
  13. package/dist/mcp-server/tools/clamp-read-limit.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.d.ts.map +1 -1
  15. package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.js +6 -5
  16. package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.js.map +1 -1
  17. package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js +9 -56
  19. package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js.map +1 -1
  20. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts +6 -6
  21. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js +39 -18
  23. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.d.ts +71 -2
  25. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.d.ts.map +1 -1
  26. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js +50 -57
  27. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js.map +1 -1
  28. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts +6 -4
  29. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts.map +1 -1
  30. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js +108 -37
  31. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js.map +1 -1
  32. package/dist/mcp-server/tools/hoist-dimensions.d.ts +18 -0
  33. package/dist/mcp-server/tools/hoist-dimensions.d.ts.map +1 -0
  34. package/dist/mcp-server/tools/hoist-dimensions.js +29 -0
  35. package/dist/mcp-server/tools/hoist-dimensions.js.map +1 -0
  36. package/dist/mcp-server/tools/narrowing-advice.d.ts +8 -7
  37. package/dist/mcp-server/tools/narrowing-advice.d.ts.map +1 -1
  38. package/dist/mcp-server/tools/narrowing-advice.js +16 -9
  39. package/dist/mcp-server/tools/narrowing-advice.js.map +1 -1
  40. package/dist/mcp-server/tools/value-list-paging.d.ts +1 -29
  41. package/dist/mcp-server/tools/value-list-paging.d.ts.map +1 -1
  42. package/dist/mcp-server/tools/value-list-paging.js +1 -47
  43. package/dist/mcp-server/tools/value-list-paging.js.map +1 -1
  44. package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts.map +1 -1
  45. package/dist/services/eurostat-bulk/eurostat-bulk-service.js +31 -5
  46. package/dist/services/eurostat-bulk/eurostat-bulk-service.js.map +1 -1
  47. package/dist/services/eurostat-catalogue/eurostat-catalogue-service.d.ts +1 -0
  48. package/dist/services/eurostat-catalogue/eurostat-catalogue-service.d.ts.map +1 -1
  49. package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js +14 -3
  50. package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js.map +1 -1
  51. package/dist/services/eurostat-data/eurostat-data-service.d.ts +5 -1
  52. package/dist/services/eurostat-data/eurostat-data-service.d.ts.map +1 -1
  53. package/dist/services/eurostat-data/eurostat-data-service.js +66 -26
  54. package/dist/services/eurostat-data/eurostat-data-service.js.map +1 -1
  55. package/dist/services/eurostat-data/types.d.ts +9 -10
  56. package/dist/services/eurostat-data/types.d.ts.map +1 -1
  57. package/dist/services/eurostat-data/types.js +2 -2
  58. package/dist/services/eurostat-data/types.js.map +1 -1
  59. package/dist/services/eurostat-periods.d.ts +107 -1
  60. package/dist/services/eurostat-periods.d.ts.map +1 -1
  61. package/dist/services/eurostat-periods.js +182 -7
  62. package/dist/services/eurostat-periods.js.map +1 -1
  63. package/package.json +8 -7
  64. package/server.json +9 -9
  65. package/skills/eurostat-guide/SKILL.md +60 -0
  66. package/skills/eurostat-guide/references/sql-analysis.md +40 -0
package/AGENTS.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** @cyanheads/eurostat-mcp-server
4
- **Version:** 0.10.1
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`
4
+ **Version:** 0.11.1
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` `^2.3.1`
7
+ **MCP SDK:** `@modelcontextprotocol/server` `^2.4.0`
8
8
  **Zod:** `^4.6.7`
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -266,7 +266,16 @@ src/
266
266
 
267
267
  ## Skills
268
268
 
269
- Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
269
+ Two skill trees live at the project root, written for different readers:
270
+
271
+ | Tree | For | Written by |
272
+ |:-----|:----|:-----------|
273
+ | `framework-skills/` | The agent developing this server — framework skills (`metadata.audience: external`) plus any development-time skills of the server's own | `init` and the `maintenance` sync (framework skills); you (your own) |
274
+ | `skills/` | The agents that install the server and call its tools — tool chains, identifier sources, gates | You, deliberately, with the `add-skill` skill. Absent by default; usually none is needed |
275
+
276
+ Development skills are modular instructions. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. Their directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand them to every agent that installs it.
277
+
278
+ A published skill in root `skills/` reaches plugin installs and the `.mcpb` bundle as-is, ships in the npm package and the Docker image, and reaches MCP clients when the server opts in with `createApp({ skills: 'skills' })`. `lint:packaging` validates the tree.
270
279
 
271
280
  **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
272
281
 
@@ -280,6 +289,7 @@ Available skills:
280
289
  | `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
281
290
  | `add-resource` | Scaffold a new resource definition |
282
291
  | `add-prompt` | Scaffold a new prompt definition |
292
+ | `add-skill` | Author a skill the server publishes in root `skills/` for the agents that use it — when one earns its place, structure, validation, delivery |
283
293
  | `add-service` | Scaffold a new service integration |
284
294
  | `add-test` | Scaffold test file for a tool, resource, or service |
285
295
  | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
@@ -411,6 +421,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
411
421
  - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
412
422
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
413
423
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
424
+ - [ ] `plugin.json` + `mcp.json` (Agent Plugins 1.0.0) populated — unscoped server name, package metadata, and one stdio server running `npx -y <full package name>`. Root `mcp.json` carries only fixed env values; user-supplied variables remain in `.codex-plugin/mcp.json` `env_vars`, forwarded through `.codex-plugin/plugin.json` `"mcpServers": "./.codex-plugin/mcp.json"`.
414
425
  - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
415
426
  - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
416
427
  - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never write `"KEY": ""` into `env`
package/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** @cyanheads/eurostat-mcp-server
4
- **Version:** 0.10.1
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`
4
+ **Version:** 0.11.1
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` `^2.3.1`
7
+ **MCP SDK:** `@modelcontextprotocol/server` `^2.4.0`
8
8
  **Zod:** `^4.6.7`
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -266,7 +266,16 @@ src/
266
266
 
267
267
  ## Skills
268
268
 
269
- Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
269
+ Two skill trees live at the project root, written for different readers:
270
+
271
+ | Tree | For | Written by |
272
+ |:-----|:----|:-----------|
273
+ | `framework-skills/` | The agent developing this server — framework skills (`metadata.audience: external`) plus any development-time skills of the server's own | `init` and the `maintenance` sync (framework skills); you (your own) |
274
+ | `skills/` | The agents that install the server and call its tools — tool chains, identifier sources, gates | You, deliberately, with the `add-skill` skill. Absent by default; usually none is needed |
275
+
276
+ Development skills are modular instructions. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. Their directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand them to every agent that installs it.
277
+
278
+ A published skill in root `skills/` reaches plugin installs and the `.mcpb` bundle as-is, ships in the npm package and the Docker image, and reaches MCP clients when the server opts in with `createApp({ skills: 'skills' })`. `lint:packaging` validates the tree.
270
279
 
271
280
  **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
272
281
 
@@ -280,6 +289,7 @@ Available skills:
280
289
  | `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
281
290
  | `add-resource` | Scaffold a new resource definition |
282
291
  | `add-prompt` | Scaffold a new prompt definition |
292
+ | `add-skill` | Author a skill the server publishes in root `skills/` for the agents that use it — when one earns its place, structure, validation, delivery |
283
293
  | `add-service` | Scaffold a new service integration |
284
294
  | `add-test` | Scaffold test file for a tool, resource, or service |
285
295
  | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
@@ -411,6 +421,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
411
421
  - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
412
422
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
413
423
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
424
+ - [ ] `plugin.json` + `mcp.json` (Agent Plugins 1.0.0) populated — unscoped server name, package metadata, and one stdio server running `npx -y <full package name>`. Root `mcp.json` carries only fixed env values; user-supplied variables remain in `.codex-plugin/mcp.json` `env_vars`, forwarded through `.codex-plugin/plugin.json` `"mcpServers": "./.codex-plugin/mcp.json"`.
414
425
  - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
415
426
  - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
416
427
  - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never write `"KEY": ""` into `env`
package/Dockerfile CHANGED
@@ -29,6 +29,9 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \
29
29
  # Copy the rest of the source code
30
30
  COPY . .
31
31
 
32
+ # Published agent skills are read from the application root at runtime.
33
+ RUN mkdir -p skills
34
+
32
35
  # Build the application
33
36
  RUN bun run build
34
37
 
@@ -140,6 +143,7 @@ COPY --from=deps /usr/src/app/node_modules ./node_modules
140
143
 
141
144
  # Copy the compiled application code from the build stage
142
145
  COPY --from=build /usr/src/app/dist ./dist
146
+ COPY --from=build /usr/src/app/skills ./skills
143
147
 
144
148
  # The 'oven/bun' image already provides a non-root user named 'bun'.
145
149
  # We will use this existing user for enhanced security.
package/README.md CHANGED
@@ -1,13 +1,13 @@
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>6 Tools (up to 9 with the dataframe canvas) • 1 Resource</div>
4
+ <div>6 Tools (up to 9 with the dataframe canvas) • 2 Resources • 1 Skill</div>
5
5
  </p>
6
6
  </div>
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.10.1-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.3.1-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.4-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.11.1-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.4.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.4-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -52,9 +52,14 @@ The three `eurostat_dataframe_*` tools need the dataframe canvas, which is on by
52
52
  | Resource | Description |
53
53
  |:---|:---|
54
54
  | `eurostat://dataset/{dataset_code}` | Dataset metadata (dimensions, time range, observation count, last-updated) by URI |
55
+ | `skill://eurostat-guide/SKILL.md` | Statistical comparison gates, identifier provenance, and SQL analysis workflows |
55
56
 
56
57
  The same metadata is available through `eurostat_get_dataset_info` for tool-only clients.
57
58
 
59
+ ### Agent skill
60
+
61
+ [eurostat-guide](skills/eurostat-guide/SKILL.md) covers country and regional comparisons, Comext aggregation, missing-data interpretation, and SQL joins over staged observations. It ships with plugin, npm, Docker, and MCPB installs and is served over MCP at `skill://eurostat-guide/SKILL.md`; supporting SQL examples are included under the skill's `references/` directory.
62
+
58
63
  ## Capability reference
59
64
 
60
65
  ### `eurostat_search_datasets` <sub>tool</sub>
@@ -67,7 +72,7 @@ The same metadata is available through `eurostat_get_dataset_info` for tool-only
67
72
 
68
73
  ### `eurostat_browse_themes` <sub>tool</sub>
69
74
 
70
- - Omit `theme_code` for the top-level theme folders; pass a folder code for its immediate children. The Comext collections sit under `ext_go_detail` (detailed trade, in `ext_go`) and `prom` (PRODCOM, in `icts`)
75
+ - Omit `theme_code` for the top-level theme folders; pass a folder code for its immediate children. Codes resolve exact-first, then case-insensitively. The Comext collections sit under `ext_go_detail` (detailed trade, in `ext_go`) and `prom` (PRODCOM, in `icts`)
71
76
  - Items carry `code`, `label`, `type` (`folder` / `dataset` / `table`), and `hasChildren`, with a `parentPath` breadcrumb and `otherPlacements` for a folder filed in several branches; fails as `not_found` for an unknown code, `not_a_folder` for a dataset or table code, or a retryable `upstream_fault` when no catalogue is loaded and Eurostat's table of contents is unreachable
72
77
  - If the Comext dataflow list cannot be loaded, `ext_go` and `icts` carry `comextListMissing` and a `notice`; a catalogue that could not be refreshed carries `catalogueLoadedAt`
73
78
 
@@ -82,23 +87,27 @@ The same metadata is available through `eurostat_get_dataset_info` for tool-only
82
87
 
83
88
  ### `eurostat_get_dimension_values` <sub>tool</sub>
84
89
 
85
- - `dataset_code` plus `dimension`; for `geo`, `geo_level` picks a NUTS level (`aggregate`, `country` by default, `nuts1`, `nuts2`, `nuts3`). `filter` keeps values whose code and label contain every word given, ignoring case and accents (`palma frankfurt` finds `DE_EDDF_ES_LEPA`), counted in `matchCount`
86
- - Long lists come back in pages of up to 64,000 bytes (or an optional `limit`), with `totalCount` for every value; a page with more after it carries `truncated`, `nextCursor` (pass back as `cursor`), and a `notice`. Fails as `not_found`, `no_results`, `conflicting_params`, `invalid_cursor`, `upstream_fault` (retryable when the endpoint is down), or `canvas_not_found`
90
+ - `dataset_code` plus `dimension`, resolved exact-first then case-insensitively (`TIME_PERIOD` aliases `time`); returned value codes and labels stay unchanged. For `geo`, `geo_level` picks a NUTS level (`aggregate`, `country` by default, `nuts1`, `nuts2`, `nuts3`). `filter` keeps values whose code and label contain every word given, ignoring case and accents (`palma frankfurt` finds `DE_EDDF_ES_LEPA`), counted in `matchCount`
91
+ - `limit` defaults to 25 and clamps to 1,000 with a notice. Pages contain that many values, fewer at the end, with `totalCount` for the full list; a page with more after it carries `truncated`, `nextCursor` (pass back as `cursor`), and a `notice`. Continuations retain the effective count and ignore a new `limit`; older cursors above 1,000 normalize to 1,000 with disclosure. Fails as `not_found`, `no_results`, `conflicting_params`, `invalid_cursor`, `upstream_fault` (retryable when the endpoint is down), or `canvas_not_found`
87
92
  - `canvas_id` stages every value on that canvas as a `code` / `label` table (`canvasId` / `tableName` / `stagedRowCount`) that labels a download's code columns through a join; it never starts a canvas of its own
88
93
 
89
94
  ---
90
95
 
91
96
  ### `eurostat_query_dataset` <sub>tool</sub>
92
97
 
93
- - `dataset_code` plus `filters` (`{dimension_code: [values]}`), `geo_level`, and either `since_period` / `until_period` (`2020`, `2020-Q1`, `2020-01`, down to `YYYY-Wnn` and `YYYY-Dnnn`) or `last_n_periods`; `preview_limit` is a positive integer (default 50), clamped to 500 with a notice; `lang` is `EN`, `FR`, or `DE`
98
+ - `dataset_code` plus `filters` (`{dimension_code: [values]}`), `geo_level`, and either `since_period` / `until_period` (`2020`, `2020-Q1`, `2020-01`, down to `YYYY-Wnn` and `YYYY-Dnnn`) or `last_n_periods`; `preview_limit` is a positive integer (default 25), clamped to 500 with a notice; `lang` is `EN`, `FR`, or `DE`
99
+ - `stage: true` stages the whole match on an available canvas, regardless of size; reuse `canvas_id` or omit it to create one. Without it, automatic staging starts above 5,000 observations. `truncated` still describes that threshold; `tableName` identifies a staged result. Canvas-disabled deployments disclose an explicit staging request as unavailable
100
+ - `last_n_periods` counts dataset time positions, which can include periods with no populated cells. A notice names gaps across the full match (up to the newest 24, with an omitted count); increase N or replace it with `until_period` to reach earlier observations
101
+ - Eurostat keeps only periods wholly inside `since_period`–`until_period`, per frequency. A notice names a period a bound falls inside and the bound that includes it. A one-day range goes out as `time=<date>`, because Eurostat ignores one-day bounds
94
102
  - Each observation carries code/label pairs per dimension, a nullable `value`, `status` (OBS_FLAG), and `confStatus` (CONF_STATUS); `obsCount`, `missingObsCount`, and `timeRange` cover the whole match, `truncated` is true above 5,000 observations, and `unmatchedValues` names filter values that matched nothing
95
- - Fails as `not_found`, `no_results` (naming the unmatched values and the periods with no value), `invalid_dimension`, `invalid_period`, `conflicting_params`, a non-retryable `async_response` for a query too large to serve, `canvas_not_found`, or a retryable `upstream_fault` when the Statistics API is down
103
+ - Fails as `not_found`, `no_results` (naming the unmatched values, the periods with no value, why a range selected no period, or `geo_level` as the likely cause when Eurostat's reply says nothing more), `invalid_dimension`, `invalid_period`, `conflicting_params`, a non-retryable `async_response` for a query too large to serve, `canvas_not_found`, or a retryable `upstream_fault` when the Statistics API is down
96
104
 
97
105
  ---
98
106
 
99
107
  ### `eurostat_download_dataset` <sub>tool</sub>
100
108
 
101
- - `dataset_code` plus the same `filters` map and `since_period` / `until_period` (no `last_n_periods`: the TSV keeps a column per period unless a range drops it); `preview_limit` is a positive integer (default 50), clamped to 500 with a notice
109
+ - `dataset_code` plus the same `filters` map and `since_period` / `until_period` (no `last_n_periods`: the TSV keeps a column per period unless a range drops it); `preview_limit` is a positive integer (default 25), clamped to 500 with a notice
110
+ - The wholly-inside rule and its notice apply here too. A one-day range requests that day and the next, and the later day's column is dropped before anything is counted or staged
102
111
  - Returns `rowCount` (echoed as `totalCount`), `missingCount`, `periodRange`, and `bytesRead` for the whole download; fails as `not_found`, `invalid_dimension`, `invalid_period`, `filter_arity`, a non-retryable `async_queued` or `extraction_too_big`, `no_results`, `upstream_fault` (retryable when the SDMX endpoint is down), or `canvas_not_found`
103
112
  - The `EUROSTAT_BULK_MAX_BYTES` budget (default 50 MiB decoded) retains complete observations within the permitted prefix and returns `budgetExceeded: true`. A budget too small for a complete header and usable observation fails as `byte_budget_exceeded`, with the configured limit and narrowing guidance. `bytesRead` counts accepted decoded bytes, never more than the budget; incomplete trailing rows are discarded.
104
113
 
@@ -114,7 +123,7 @@ The same metadata is available through `eurostat_get_dataset_info` for tool-only
114
123
  ### `eurostat_dataframe_query` <sub>tool</sub>
115
124
 
116
125
  - One read-only `SELECT` per call; chained statements, other verbs, and functions that read files or external data are rejected
117
- - Returns `columns`, `rows`, `rowCount`, and required boolean `truncated`. Whole rows fit within `CANVAS_DEFAULT_ROW_LIMIT` (default 10,000) and 64,000 bytes independently for JSON-serialized `structuredContent` and `content[]`, including enrichment. At least one complete row is retained even if oversized. Cells render as lossless JSON, including nesting and quoted strings; 64-bit integers arrive as strings. Fails as `missing_table`, `canvas_not_found`, or `canvas_disabled`
126
+ - Returns `columns`, `rows`, `rowCount`, and required boolean `truncated`. Every row supplied by the canvas is returned intact, up to `CANVAS_DEFAULT_ROW_LIMIT` (default 10,000). Cells render as lossless JSON, including nesting and quoted strings; 64-bit integers arrive as strings. Fails as `missing_table`, `canvas_not_found`, or `canvas_disabled`
118
127
  - A cut sets `truncated: true` and adds a notice. Page an unchanged deterministic SELECT with a total `ORDER BY` (tie-breakers required), `LIMIT` and `OFFSET`; advance by actual `rowCount`. Restart an initially unordered result sorted at offset zero. Volatile SQL and changing or expired tables cannot provide stable paging
119
128
 
120
129
  ---
@@ -141,7 +150,7 @@ Eurostat-specific:
141
150
  - The Comext host's `DS-*` collections (detailed trade by CN8, HS, SITC, BEC and CPA, and PRODCOM) through the same tools. Filter every request, `freq` included, since trade flows mix annual and monthly series; aggregates such as `TOTAL`, `EU27_2020`, and `EXT_EU27_2020` double-count when summed with their members. PRODCOM text values arrive in `valueText` / `obs_value_text`
142
151
  - NUTS geo-level filtering (`aggregate` / `country` / `nuts1` / `nuts2` / `nuts3`), and OBS_FLAG and CONF_STATUS decoded into separate fields
143
152
  - Too-large-to-serve responses come back as non-retryable `async_response`, `extraction_too_big`, or `async_queued` errors that name the dimensions left to filter
144
- - A query match above 5,000 observations, or any bulk download, is staged whole as a SQL table (`canvasId` / `tableName` / `stagedRowCount`) on the DuckDB dataframe canvas, which is on by default. The `.mcpb` bundle strips the native DuckDB binding and runs with `CANVAS_PROVIDER_TYPE=none`, so SQL analytics need the npm, Docker, or from-source install
153
+ - A query with `stage: true`, a match above 5,000 observations, or any bulk download is staged as a SQL table (`canvasId` / `tableName` / `stagedRowCount`) on the DuckDB dataframe canvas, which is on by default. The `.mcpb` bundle strips the native DuckDB binding and runs with `CANVAS_PROVIDER_TYPE=none`, so SQL analytics need the npm, Docker, or from-source install
145
154
 
146
155
  Agent-friendly output:
147
156
 
@@ -152,110 +161,32 @@ Agent-friendly output:
152
161
 
153
162
  ## Getting started
154
163
 
155
- ### Public Hosted Instance
164
+ Connect to the hosted instance:
156
165
 
157
- A public instance is available at `https://eurostat.caseyjhand.com/mcp` — no installation required. Point any MCP client at it via Streamable HTTP:
158
-
159
- ```json
160
- {
161
- "mcpServers": {
162
- "eurostat-mcp-server": {
163
- "type": "streamable-http",
164
- "url": "https://eurostat.caseyjhand.com/mcp"
165
- }
166
- }
167
- }
166
+ ```sh
167
+ claude mcp add --transport http eurostat-mcp-server https://eurostat.caseyjhand.com/mcp
168
168
  ```
169
169
 
170
- ### Self-Hosted / Local
171
-
172
- Add the following to your MCP client configuration file.
170
+ Or run it locally:
173
171
 
174
- ```json
175
- {
176
- "mcpServers": {
177
- "eurostat-mcp-server": {
178
- "type": "stdio",
179
- "command": "bunx",
180
- "args": ["@cyanheads/eurostat-mcp-server@latest"],
181
- "env": {
182
- "MCP_TRANSPORT_TYPE": "stdio",
183
- "MCP_LOG_LEVEL": "info"
184
- }
185
- }
186
- }
187
- }
172
+ ```sh
173
+ claude mcp add eurostat-mcp-server -- npx -y @cyanheads/eurostat-mcp-server
188
174
  ```
189
175
 
190
- Or with npx (no Bun required):
176
+ For other clients, add this to the MCP config file:
191
177
 
192
178
  ```json
193
179
  {
194
180
  "mcpServers": {
195
181
  "eurostat-mcp-server": {
196
- "type": "stdio",
197
182
  "command": "npx",
198
- "args": ["-y", "@cyanheads/eurostat-mcp-server@latest"],
199
- "env": {
200
- "MCP_TRANSPORT_TYPE": "stdio",
201
- "MCP_LOG_LEVEL": "info"
202
- }
203
- }
204
- }
205
- }
206
- ```
207
-
208
- Or with Docker:
209
-
210
- ```json
211
- {
212
- "mcpServers": {
213
- "eurostat-mcp-server": {
214
- "type": "stdio",
215
- "command": "docker",
216
- "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/eurostat-mcp-server:latest"]
183
+ "args": ["-y", "@cyanheads/eurostat-mcp-server"]
217
184
  }
218
185
  }
219
186
  }
220
187
  ```
221
188
 
222
- For Streamable HTTP, set the transport and start the server:
223
-
224
- ```sh
225
- MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
226
- # Server listens at http://localhost:3010/mcp
227
- ```
228
-
229
- ### Prerequisites
230
-
231
- - [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
232
- - No API key: Eurostat's dissemination API and its Comext host are public.
233
-
234
- ### Installation
235
-
236
- 1. **Clone the repository:**
237
-
238
- ```sh
239
- git clone https://github.com/cyanheads/eurostat-mcp-server.git
240
- ```
241
-
242
- 2. **Navigate into the directory:**
243
-
244
- ```sh
245
- cd eurostat-mcp-server
246
- ```
247
-
248
- 3. **Install dependencies:**
249
-
250
- ```sh
251
- bun install
252
- ```
253
-
254
- 4. **Configure environment (optional, every setting has a default):**
255
-
256
- ```sh
257
- cp .env.example .env
258
- ```
189
+ No API key is required. For Docker or a source build, see [Running the server](#running-the-server).
259
190
 
260
191
  ## Configuration
261
192
 
@@ -270,8 +201,8 @@ cp .env.example .env
270
201
  | `EUROSTAT_BULK_MAX_BYTES` | Byte budget for one bulk download. | `52428800` (50 MiB) |
271
202
  | `CANVAS_PROVIDER_TYPE` | Dataframe canvas backend; `none` disables the canvas and its tools. The `.mcpb` bundle sets `none`. | `duckdb` |
272
203
  | `CANVAS_TTL_MS` | Sliding lifetime of a staged canvas, in ms. | `86400000` (24 h) |
273
- | `CANVAS_DEFAULT_ROW_LIMIT` | Max rows one `eurostat_dataframe_query` returns before reporting `truncated`; a separate 64,000-byte budget applies to each serialized response surface, retaining at least one complete row. | `10000` |
274
- | `EUROSTAT_CLAMP_LIMITS` | Clamp positive integer preview/search limits to 500/100 with a notice. `false` restores schemas rejecting over-cap inputs; zero, negatives, fractions and nonnumeric inputs are always rejected. Each clamp logs once and increments `eurostat.limit_clamps`, labelled by tool. | `true` |
204
+ | `CANVAS_DEFAULT_ROW_LIMIT` | Max complete rows one `eurostat_dataframe_query` returns before reporting `truncated`. | `10000` |
205
+ | `EUROSTAT_CLAMP_LIMITS` | Clamp positive integer preview/search/dimension limits to 500/100/1,000 with a notice. `false` restores schemas rejecting over-cap inputs; zero, negatives, fractions and nonnumeric inputs are always rejected. Each clamp logs once and increments `eurostat.limit_clamps`, labelled by tool. | `true` |
275
206
  | `EUROSTAT_DATAFRAME_DROP_ENABLED` | `true` lists `eurostat_dataframe_drop`; no effect with `CANVAS_PROVIDER_TYPE=none`. | `false` |
276
207
  | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
277
208
  | `MCP_HTTP_PORT` | HTTP server port. | `3010` |
@@ -289,6 +220,14 @@ See [`.env.example`](./.env.example) for the full list of optional overrides.
289
220
 
290
221
  ### Local development
291
222
 
223
+ Requires [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
224
+
225
+ ```sh
226
+ git clone https://github.com/cyanheads/eurostat-mcp-server.git
227
+ cd eurostat-mcp-server && bun install
228
+ cp .env.example .env # optional; every setting has a default
229
+ ```
230
+
292
231
  - **Build and run the production version**:
293
232
 
294
233
  ```sh
@@ -311,11 +250,10 @@ See [`.env.example`](./.env.example) for the full list of optional overrides.
311
250
  ### Docker
312
251
 
313
252
  ```sh
314
- docker build -t eurostat-mcp-server .
315
- docker run --rm -p 3010:3010 eurostat-mcp-server
253
+ docker run --rm -p 3010:3010 ghcr.io/cyanheads/eurostat-mcp-server:latest
316
254
  ```
317
255
 
318
- 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.
256
+ The image defaults to HTTP transport, stateless session mode, and logs to `/var/log/eurostat-mcp-server`. For stdio, use `docker run -i --rm -e MCP_TRANSPORT_TYPE=stdio ghcr.io/cyanheads/eurostat-mcp-server:latest`. Build locally with `docker build -t eurostat-mcp-server .`; OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
319
257
 
320
258
  ## Project structure
321
259
 
@@ -332,6 +270,7 @@ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `
332
270
  | `src/services/canvas-accessor.ts` | Accessor for the optional DataCanvas and the acquire helper canvas-touching tools share. |
333
271
  | `src/services/shared-load.ts` | Lets concurrent callers await one shared download, so one caller's cancellation releases only that caller. |
334
272
  | `src/config` | Server-specific environment variable parsing and validation with Zod. |
273
+ | `skills/` | Published eurostat-guide and SQL reference, served over MCP and included in installation packages. |
335
274
  | `tests/` | Unit and integration tests, mirroring the `src/` structure. |
336
275
 
337
276
  ## Development guide
@@ -0,0 +1,33 @@
1
+ ---
2
+ summary: "Complete Eurostat results and guided statistical analysis."
3
+ breaking: true
4
+ security: false
5
+ ---
6
+
7
+ # 0.11.0 — 2026-10-26
8
+
9
+ ## Added
10
+
11
+ - **`eurostat_query_dataset`** accepts `stage: true` to retain every matched observation on a canvas at any size, with an unavailable-staging notice when the canvas is disabled ([#80](https://github.com/cyanheads/eurostat-mcp-server/issues/80)).
12
+
13
+ ## Changed
14
+
15
+ - Published Eurostat analysis guide as MCP skill resources.
16
+ - **Dimension pages and SQL results** use count limits and complete rows instead of response byte cuts; dimension pages default to 25, cap at 1,000, and retain cursor continuation ([#82](https://github.com/cyanheads/eurostat-mcp-server/issues/82)).
17
+ - **`EUROSTAT_CLAMP_LIMITS=false`** now rejects dimension `limit` above 1,000. Request at most 1,000 and follow `nextCursor`, or enable clamping; default-on clamping remains permissive.
18
+ - **SQL column references** named `current_schema` must now be qualified, such as `d.current_schema`; unqualified references are rejected by the adopted SQL gate.
19
+ - **Query and download previews** default to 25 observations; `last_n_periods` advertises no maximum.
20
+ - **Installation and development** include Agent Plugins manifests, published guide delivery across npm, Docker and MCPB, synchronized framework checks, and shorter connection instructions.
21
+
22
+ ## Fixed
23
+
24
+ - **`last_n_periods`** success notices disclose selected time positions with no populated cells across the full match and explain how to reach earlier observations ([#79](https://github.com/cyanheads/eurostat-mcp-server/issues/79)).
25
+ - **Dimension and theme identifiers** resolve exact matches before case-insensitive matches; `TIME_PERIOD` aliases `time`, and dimension misses name the available codes ([#81](https://github.com/cyanheads/eurostat-mcp-server/issues/81)).
26
+
27
+ ## Dependencies
28
+
29
+ - `@cyanheads/mcp-ts-core` ^0.13.15 → ^0.13.18.
30
+ - `@duckdb/node-api` ^1.5.6-r.1 → ^2.0.0-r.1.
31
+ - `@biomejs/biome` 2.5.15 → 2.6.3.
32
+ - `ignore` ^7.0.13 → ^7.0.14.
33
+ - `tsc-alias` ^1.9.8 → ^1.9.9.
@@ -0,0 +1,23 @@
1
+ ---
2
+ summary: "Period bounds that fall inside a period now say so, one-day ranges select that day instead of the whole series, a query whose geo_level selects nothing names it as the likely cause, the dimension-values hint offers only levels the dataset lists, and result rows stop repeating single-valued dimensions."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.11.1 — 2026-10-31
8
+
9
+ ## Changed
10
+
11
+ - **`since_period` / `until_period`** on `eurostat_query_dataset` and `eurostat_download_dataset`: a success names each period a bound falls inside, which Eurostat excludes because it keeps only periods wholly inside a range, and the bound that includes it. The download names only frequencies its TSV header shows. When both bounds cut one period, the query notice uses a plural form naming both. The descriptions state the rule in place of "mapped onto the dataset's own" ([#84](https://github.com/cyanheads/eurostat-mcp-server/issues/84)).
12
+ - **`content[]`** of both tools lists each dimension holding one value across the rows shown once above the rows; `urb_cpop1` at `preview_limit: 500` went from 43,856 to 13,016 bytes ([#86](https://github.com/cyanheads/eurostat-mcp-server/issues/86)).
13
+ - **`eurostat_get_dimension_values`** `no_results` for an empty `geo_level` offers only the levels the dataset's `geo` codes carry, and offers omitting `geo_level` only when `country` is among them ([#87](https://github.com/cyanheads/eurostat-mcp-server/issues/87)).
14
+ - **`eurostat_query_dataset`** `no_results` for a query that set `geo_level` names it as the likely cause when Eurostat's reply says nothing more, lists the dimensions of a dataset without `geo`, and recommends dropping `geo_level` ([#85](https://github.com/cyanheads/eurostat-mcp-server/issues/85)).
15
+ - **Recovery hints** that read the dataset structure do so under a fixed 5 s deadline across retries: the query's `no_results` and `async_response`, and the download's `extraction_too_big` when no filter has read it already. `async_response` and `extraction_too_big` hints previously read under `EUROSTAT_REQUEST_TIMEOUT_MS` ([#85](https://github.com/cyanheads/eurostat-mcp-server/issues/85)).
16
+ - **`skills/eurostat-guide`** 1.0 → 1.1.
17
+
18
+ ## Fixed
19
+
20
+ - **One-day ranges** select that day: the query sends `time=<date>`, and the download requests the day and the next, dropping the later column before anything is counted or staged. Both used to return the whole series. The download's `appliedQuery.url` shows an `endPeriod` a day past `untilPeriod` ([#88](https://github.com/cyanheads/eurostat-mcp-server/issues/88)).
21
+ - **Rejected forms:** a one-day range whose day holds no published period, or on a dataset with no period that short, used to return the whole series and now fails. The query fails as `no_results`; the download fails as `invalid_dimension` (fault 150) or `no_results`, depending on Eurostat's reply ([#88](https://github.com/cyanheads/eurostat-mcp-server/issues/88)).
22
+ - **`eurostat_query_dataset` `no_results` for a range that selects no period** is diagnosed as outside the coverage, holding no whole period (naming the bound or bounds that select one), or holding no published period (pointing to `eurostat_get_dimension_values` with `dimension: "time"`). Previously every case read as outside the coverage. A `filters.time` that selects nothing, with no bounds set, keeps its outside-coverage message ([#84](https://github.com/cyanheads/eurostat-mcp-server/issues/84)).
23
+ - **Download fault 150** splits on `allowed: TIME_PERIOD: [<>]=`: a range that selects no period reports `invalid_dimension` with a range message that omits Eurostat's clause, and a filter value spelling that clause stays a filter fault. It previously read as a rejected filter value, with a range outside the coverage as the one alternative ([#84](https://github.com/cyanheads/eurostat-mcp-server/issues/84)).
@@ -8,7 +8,7 @@ const ServerConfigSchema = z.object({
8
8
  clampLimits: z
9
9
  .stringbool()
10
10
  .default(true)
11
- .describe('Clamp positive read limits to preview 500 and search 100 with a notice. False restores rejecting maximum schemas.'),
11
+ .describe('Clamp positive read limits to preview 500, search 100 and dimension values 1,000 with a notice. False restores rejecting maximum schemas.'),
12
12
  baseUrl: z
13
13
  .string()
14
14
  .url()
@@ -1 +1 @@
1
- {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,WAAW,EAAE,CAAC;SACX,UAAU,EAAE;SACZ,OAAO,CAAC,IAAI,CAAC;SACb,QAAQ,CACP,mHAAmH,CACpH;IACH,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,iDAAiD,CAAC;SAC1D,QAAQ,CAAC,uBAAuB,CAAC;IACpC,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,wDAAwD,CAAC;SACjE,QAAQ,CACP,mMAAmM,CACpM;IACH,gBAAgB,EAAE,CAAC,CAAC,MAAM;SACvB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CACP,4GAA4G,CAC7G;IACH,iBAAiB,EAAE,CAAC,CAAC,MAAM;SACxB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,OAAO,CAAC;SAChB,QAAQ,CACP,wUAAwU,CACzU;IACH,aAAa,EAAE,CAAC,CAAC,MAAM;SACpB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,UAAU,CAAC;SACnB,QAAQ,CACP,qLAAqL,CACtL;IACH,aAAa,EAAE,CAAC,CAAC,MAAM;SACpB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,OAAO,CAAC;SAChB,QAAQ,CACP,iQAAiQ,CAClQ;IACH,YAAY,EAAE,CAAC,CAAC,MAAM;SACnB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,UAAU,CAAC;SACnB,QAAQ,CACP,0QAA0Q,CAC3Q;IACH,oBAAoB,EAAE,CAAC;SACpB,UAAU,EAAE;SACZ,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CACP,sOAAsO,CACvO;CACJ,CAAC,CAAC;AAEH,IAAI,OAAuD,CAAC;AAE5D,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,WAAW,EAAE,uBAAuB;QACpC,OAAO,EAAE,mBAAmB;QAC5B,aAAa,EAAE,0BAA0B;QACzC,gBAAgB,EAAE,6BAA6B;QAC/C,iBAAiB,EAAE,8BAA8B;QACjD,aAAa,EAAE,2BAA2B;QAC1C,aAAa,EAAE,0BAA0B;QACzC,YAAY,EAAE,yBAAyB;QACvC,oBAAoB,EAAE,iCAAiC;KACxD,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,WAAW,EAAE,CAAC;SACX,UAAU,EAAE;SACZ,OAAO,CAAC,IAAI,CAAC;SACb,QAAQ,CACP,2IAA2I,CAC5I;IACH,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,iDAAiD,CAAC;SAC1D,QAAQ,CAAC,uBAAuB,CAAC;IACpC,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,wDAAwD,CAAC;SACjE,QAAQ,CACP,mMAAmM,CACpM;IACH,gBAAgB,EAAE,CAAC,CAAC,MAAM;SACvB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CACP,4GAA4G,CAC7G;IACH,iBAAiB,EAAE,CAAC,CAAC,MAAM;SACxB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,OAAO,CAAC;SAChB,QAAQ,CACP,wUAAwU,CACzU;IACH,aAAa,EAAE,CAAC,CAAC,MAAM;SACpB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,UAAU,CAAC;SACnB,QAAQ,CACP,qLAAqL,CACtL;IACH,aAAa,EAAE,CAAC,CAAC,MAAM;SACpB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,OAAO,CAAC;SAChB,QAAQ,CACP,iQAAiQ,CAClQ;IACH,YAAY,EAAE,CAAC,CAAC,MAAM;SACnB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,UAAU,CAAC;SACnB,QAAQ,CACP,0QAA0Q,CAC3Q;IACH,oBAAoB,EAAE,CAAC;SACpB,UAAU,EAAE;SACZ,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CACP,sOAAsO,CACvO;CACJ,CAAC,CAAC;AAEH,IAAI,OAAuD,CAAC;AAE5D,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,WAAW,EAAE,uBAAuB;QACpC,OAAO,EAAE,mBAAmB;QAC5B,aAAa,EAAE,0BAA0B;QACzC,gBAAgB,EAAE,6BAA6B;QAC/C,iBAAiB,EAAE,8BAA8B;QACjD,aAAa,EAAE,2BAA2B;QAC1C,aAAa,EAAE,0BAA0B;QACzC,YAAY,EAAE,yBAAyB;QACvC,oBAAoB,EAAE,iCAAiC;KACxD,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import { eurostatDataframeDrop } from './mcp-server/tools/definitions/eurostat-d
13
13
  import { eurostatDataframeQuery } from './mcp-server/tools/definitions/eurostat-dataframe-query.tool.js';
14
14
  import { createEurostatDownloadDataset } from './mcp-server/tools/definitions/eurostat-download-dataset.tool.js';
15
15
  import { eurostatGetDatasetInfo } from './mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js';
16
- import { eurostatGetDimensionValues } from './mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js';
16
+ import { createEurostatGetDimensionValues } from './mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js';
17
17
  import { createEurostatQueryDataset } from './mcp-server/tools/definitions/eurostat-query-dataset.tool.js';
18
18
  import { createEurostatSearchDatasets } from './mcp-server/tools/definitions/eurostat-search-datasets.tool.js';
19
19
  import { setCanvas } from './services/canvas-accessor.js';
@@ -98,7 +98,7 @@ await createApp({
98
98
  createEurostatSearchDatasets(clampLimits),
99
99
  eurostatBrowseThemes,
100
100
  eurostatGetDatasetInfo,
101
- eurostatGetDimensionValues,
101
+ createEurostatGetDimensionValues(clampLimits),
102
102
  createEurostatQueryDataset(clampLimits),
103
103
  createEurostatDownloadDataset(clampLimits),
104
104
  canvasEnabled ? eurostatDataframeDescribe : disabledTool(eurostatDataframeDescribe, CANVAS_OFF),
@@ -107,13 +107,8 @@ await createApp({
107
107
  ],
108
108
  resources: [eurostatDatasetResource],
109
109
  prompts: [],
110
- instructions: `Eurostat MCP server — EU statistical data across the Eurostat catalogue.
111
- 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.
112
- 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.
113
- The catalogue covers two hosts. Most datasets come from the Eurostat dissemination table of contents; the DS-* codes — detailed trade by CN8, HS, SITC, BEC and CPA, and PRODCOM — come from the Comext host, and every tool routes a DS-* code there by its prefix. Comext collections are large: filter every request (freq included, since the trade flows mix annual and monthly series), and expect eurostat_get_dimension_values to page a long list such as the 37,069 CN8 product codes — pass filter with words of the code or label to find one value, or follow nextCursor. A collection on neither host, such as the legacy DS-056120, is not disseminated.
114
- Two paths past eurostat_query_dataset's 5,000-observation inline cap. For a slice of a dataset, keep using eurostat_query_dataset: when the match is larger than the cap and this deployment runs a dataframe canvas, the whole match is also staged as a SQL table named in the response. For a whole dataset, use eurostat_download_dataset, which reads the SDMX bulk endpoint instead — about half the bytes — and stages every observation the same way, bounded by a byte budget it reports back. Read either staged table with eurostat_dataframe_query (eurostat_dataframe_describe lists the table and column names); the two stage different column sets, since the bulk endpoint carries dimension codes with no labels. To label a download's codes, pass its canvasId to eurostat_get_dimension_values as canvas_id: the dimension's values are staged beside it as a code/label table to join on.
115
- Staged tables expire with their canvas. eurostat_dataframe_drop removes one sooner, leaving the canvas and its other tables in place; it is opt-in, listed only where the deployment also sets EUROSTAT_DATAFRAME_DROP_ENABLED=true.
116
- The dataframe tools are not listed when the canvas is off. There, narrowing the filters is what brings a capped match into reach, and eurostat_download_dataset retains only its inline preview.`,
110
+ skills: 'skills',
111
+ instructions: 'Eurostat statistical data, including Comext DS-* collections; no API key required. Resolve dataset codes with eurostat_search_datasets or eurostat_browse_themes, inspect dimensions with eurostat_get_dataset_info, and resolve their values with eurostat_get_dimension_values before fetching observations. Filter large requests, including frequency on Comext, and keep units and aggregation levels comparable. Previews and incomplete bulk transfers cannot support complete totals; inspect notices and flags before concluding data is absent. When a response includes tableName, pass its canvasId as canvas_id to eurostat_dataframe_describe before eurostat_dataframe_query; reuse that canvas for joins. Staging and dataframe tools depend on the deployment canvas. Read eurostat-guide for statistical interpretation and SQL workflows.',
117
112
  landing: { requireAuth: false },
118
113
  setup(core) {
119
114
  // Parsed here so a bad value fails startup with the configuration banner, not every call.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type { Context } from '@cyanheads/mcp-ts-core';
6
6
  /** Returns the effective limit and records one clamp, labelled only by tool. */
7
- export declare function clampReadLimit(requested: number, maximum: number, field: 'preview_limit' | 'limit', toolName: 'eurostat_query_dataset' | 'eurostat_download_dataset' | 'eurostat_search_datasets', ctx: Pick<Context, 'log'>): {
7
+ export declare function clampReadLimit(requested: number, maximum: number, field: 'preview_limit' | 'limit', toolName: 'eurostat_query_dataset' | 'eurostat_download_dataset' | 'eurostat_search_datasets' | 'eurostat_get_dimension_values', ctx: Pick<Context, 'log'>): {
8
8
  value: number;
9
9
  notice?: string;
10
10
  };
@@ -1 +1 @@
1
- {"version":3,"file":"clamp-read-limit.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/clamp-read-limit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAStD,gFAAgF;AAChF,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,GAAG,OAAO,EAChC,QAAQ,EAAE,wBAAwB,GAAG,2BAA2B,GAAG,0BAA0B,EAC7F,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,GACxB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQpC"}
1
+ {"version":3,"file":"clamp-read-limit.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/clamp-read-limit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAStD,gFAAgF;AAChF,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,GAAG,OAAO,EAChC,QAAQ,EACJ,wBAAwB,GACxB,2BAA2B,GAC3B,0BAA0B,GAC1B,+BAA+B,EACnC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,GACxB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"clamp-read-limit.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/clamp-read-limit.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,MAAM,MAAM,GAAG,aAAa,CAC1B,uBAAuB,EACvB,gDAAgD,EAChD,UAAU,CACX,CAAC;AAEF,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAC5B,SAAiB,EACjB,OAAe,EACf,KAAgC,EAChC,QAA6F,EAC7F,GAAyB;IAEzB,IAAI,SAAS,IAAI,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACtD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3F,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClC,OAAO;QACL,KAAK,EAAE,OAAO;QACd,MAAM,EAAE,GAAG,KAAK,cAAc,SAAS,aAAa,OAAO,0BAA0B;KACtF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"clamp-read-limit.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/clamp-read-limit.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,MAAM,MAAM,GAAG,aAAa,CAC1B,uBAAuB,EACvB,gDAAgD,EAChD,UAAU,CACX,CAAC;AAEF,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAC5B,SAAiB,EACjB,OAAe,EACf,KAAgC,EAChC,QAImC,EACnC,GAAyB;IAEzB,IAAI,SAAS,IAAI,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACtD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3F,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClC,OAAO;QACL,KAAK,EAAE,OAAO;QACd,MAAM,EAAE,GAAG,KAAK,cAAc,SAAS,aAAa,OAAO,0BAA0B;KACtF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"eurostat-browse-themes.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-browse-themes.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;AAIhF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiO/B,CAAC"}
1
+ {"version":3,"file":"eurostat-browse-themes.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-browse-themes.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;AAIhF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuO/B,CAAC"}
@@ -14,7 +14,7 @@ export const eurostatBrowseThemes = tool('eurostat_browse_themes', {
14
14
  theme_code: z
15
15
  .string()
16
16
  .optional()
17
- .describe('Folder code to expand (e.g., "economy", "reg"). Omit to list the top-level theme folders.'),
17
+ .describe('Folder code to expand (e.g., "economy", "reg"). Exact codes take precedence, then case-insensitive matching applies. Omit to list the top-level theme folders.'),
18
18
  }),
19
19
  output: z.object({
20
20
  items: z
@@ -122,11 +122,12 @@ export const eurostatBrowseThemes = tool('eurostat_browse_themes', {
122
122
  });
123
123
  }
124
124
  if (data?.reason === 'not_a_folder') {
125
+ const canonicalThemeCode = data.themeCode;
125
126
  throw ctx.fail('not_a_folder', err.message, {
126
- themeCode,
127
+ themeCode: canonicalThemeCode,
127
128
  entryType: data.entryType,
128
129
  recovery: {
129
- hint: `Pass "${themeCode}" as dataset_code to eurostat_get_dataset_info to inspect its dimensions, then eurostat_query_dataset to retrieve data.`,
130
+ hint: `Pass "${canonicalThemeCode}" as dataset_code to eurostat_get_dataset_info to inspect its dimensions, then eurostat_query_dataset to retrieve data.`,
130
131
  },
131
132
  });
132
133
  }
@@ -140,11 +141,11 @@ export const eurostatBrowseThemes = tool('eurostat_browse_themes', {
140
141
  }
141
142
  throw err;
142
143
  }
143
- const { comextListMissing, catalogueLoadedAt, ...listing } = browsed;
144
+ const { comextListMissing, catalogueLoadedAt, themeCode: canonicalThemeCode, ...listing } = browsed;
144
145
  ctx.log.info('Theme browse complete', { themeCode, itemCount: listing.items.length });
145
146
  ctx.enrich({
146
147
  itemCount: listing.items.length,
147
- ...(themeCode && { themeCode }),
148
+ ...(canonicalThemeCode && { themeCode: canonicalThemeCode }),
148
149
  ...(comextListMissing && { comextListMissing }),
149
150
  ...(catalogueLoadedAt && { catalogueLoadedAt }),
150
151
  });