@aborruso/ckan-mcp-server 0.4.16 → 0.4.18

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 (87) hide show
  1. package/LOG.md +78 -0
  2. package/README.md +104 -34
  3. package/dist/index.js +161 -45
  4. package/dist/worker.js +42 -42
  5. package/package.json +12 -1
  6. package/.devin/wiki.json +0 -273
  7. package/CLAUDE.md +0 -398
  8. package/PRD.md +0 -999
  9. package/REFACTORING.md +0 -238
  10. package/examples/langgraph/01_basic_workflow.py +0 -277
  11. package/examples/langgraph/02_data_exploration.py +0 -366
  12. package/examples/langgraph/README.md +0 -719
  13. package/examples/langgraph/metadata_quality.py +0 -299
  14. package/examples/langgraph/requirements.txt +0 -12
  15. package/examples/langgraph/setup.sh +0 -32
  16. package/examples/langgraph/test_setup.py +0 -106
  17. package/openspec/AGENTS.md +0 -456
  18. package/openspec/changes/add-ckan-analyze-dataset-structure/proposal.md +0 -17
  19. package/openspec/changes/add-ckan-analyze-dataset-structure/specs/ckan-insights/spec.md +0 -7
  20. package/openspec/changes/add-ckan-analyze-dataset-structure/tasks.md +0 -6
  21. package/openspec/changes/add-ckan-analyze-dataset-updates/proposal.md +0 -17
  22. package/openspec/changes/add-ckan-analyze-dataset-updates/specs/ckan-insights/spec.md +0 -7
  23. package/openspec/changes/add-ckan-analyze-dataset-updates/tasks.md +0 -6
  24. package/openspec/changes/add-ckan-audit-tool/proposal.md +0 -17
  25. package/openspec/changes/add-ckan-audit-tool/specs/ckan-insights/spec.md +0 -7
  26. package/openspec/changes/add-ckan-audit-tool/tasks.md +0 -6
  27. package/openspec/changes/add-ckan-dataset-insights/proposal.md +0 -17
  28. package/openspec/changes/add-ckan-dataset-insights/specs/ckan-insights/spec.md +0 -7
  29. package/openspec/changes/add-ckan-dataset-insights/tasks.md +0 -6
  30. package/openspec/changes/add-ckan-host-allowlist-env/design.md +0 -38
  31. package/openspec/changes/add-ckan-host-allowlist-env/proposal.md +0 -16
  32. package/openspec/changes/add-ckan-host-allowlist-env/specs/ckan-request-allowlist/spec.md +0 -15
  33. package/openspec/changes/add-ckan-host-allowlist-env/specs/cloudflare-deployment/spec.md +0 -11
  34. package/openspec/changes/add-ckan-host-allowlist-env/tasks.md +0 -12
  35. package/openspec/changes/add-escape-text-query/proposal.md +0 -12
  36. package/openspec/changes/add-escape-text-query/specs/ckan-search/spec.md +0 -11
  37. package/openspec/changes/add-escape-text-query/tasks.md +0 -8
  38. package/openspec/changes/add-mqa-quality-tool/proposal.md +0 -21
  39. package/openspec/changes/add-mqa-quality-tool/specs/ckan-quality/spec.md +0 -71
  40. package/openspec/changes/add-mqa-quality-tool/tasks.md +0 -29
  41. package/openspec/changes/archive/2026-01-08-add-mcp-resources/design.md +0 -115
  42. package/openspec/changes/archive/2026-01-08-add-mcp-resources/proposal.md +0 -52
  43. package/openspec/changes/archive/2026-01-08-add-mcp-resources/specs/mcp-resources/spec.md +0 -92
  44. package/openspec/changes/archive/2026-01-08-add-mcp-resources/tasks.md +0 -56
  45. package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/design.md +0 -355
  46. package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/proposal.md +0 -161
  47. package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/tasks.md +0 -162
  48. package/openspec/changes/archive/2026-01-08-translate-project-to-english/proposal.md +0 -115
  49. package/openspec/changes/archive/2026-01-08-translate-project-to-english/specs/documentation-language/spec.md +0 -32
  50. package/openspec/changes/archive/2026-01-08-translate-project-to-english/tasks.md +0 -115
  51. package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/proposal.md +0 -17
  52. package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/specs/ckan-insights/spec.md +0 -7
  53. package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/tasks.md +0 -6
  54. package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/design.md +0 -734
  55. package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/proposal.md +0 -183
  56. package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/specs/cloudflare-deployment/spec.md +0 -389
  57. package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/tasks.md +0 -519
  58. package/openspec/changes/archive/2026-01-15-add-mcp-prompts/proposal.md +0 -13
  59. package/openspec/changes/archive/2026-01-15-add-mcp-prompts/specs/mcp-prompts/spec.md +0 -22
  60. package/openspec/changes/archive/2026-01-15-add-mcp-prompts/tasks.md +0 -10
  61. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/proposal.md +0 -13
  62. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/specs/mcp-resources/spec.md +0 -38
  63. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/tasks.md +0 -10
  64. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/proposal.md +0 -13
  65. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/specs/repository-metadata/spec.md +0 -14
  66. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/tasks.md +0 -12
  67. package/openspec/changes/archive/2026-01-19-update-search-parser-config/proposal.md +0 -13
  68. package/openspec/changes/archive/2026-01-19-update-search-parser-config/specs/ckan-insights/spec.md +0 -11
  69. package/openspec/changes/archive/2026-01-19-update-search-parser-config/specs/ckan-search/spec.md +0 -11
  70. package/openspec/changes/archive/2026-01-19-update-search-parser-config/tasks.md +0 -6
  71. package/openspec/changes/archive/add-automated-tests/design.md +0 -324
  72. package/openspec/changes/archive/add-automated-tests/proposal.md +0 -167
  73. package/openspec/changes/archive/add-automated-tests/specs/automated-testing/spec.md +0 -143
  74. package/openspec/changes/archive/add-automated-tests/tasks.md +0 -132
  75. package/openspec/project.md +0 -115
  76. package/openspec/specs/ckan-insights/spec.md +0 -23
  77. package/openspec/specs/ckan-search/spec.md +0 -16
  78. package/openspec/specs/cloudflare-deployment/spec.md +0 -344
  79. package/openspec/specs/documentation-language/spec.md +0 -32
  80. package/openspec/specs/mcp-prompts/spec.md +0 -26
  81. package/openspec/specs/mcp-resources/spec.md +0 -120
  82. package/openspec/specs/repository-metadata/spec.md +0 -19
  83. package/private/commenti-privati.yaml +0 -14
  84. package/testo.md +0 -12
  85. package/web-gui/PRD.md +0 -158
  86. package/web-gui/public/index.html +0 -883
  87. package/wrangler.toml +0 -6
package/LOG.md CHANGED
@@ -1,7 +1,85 @@
1
1
  # LOG
2
2
 
3
+ ## 2026-01-26
4
+
5
+ ### Release v0.4.18
6
+
7
+ - MQA quality output now includes dimension score breakdown with ✅/⚠️ indicators
8
+ - Metrics endpoint link added for direct score inspection
9
+ - Non-max dimension(s) highlighted for quick diagnosis
10
+ - Files: `src/tools/quality.ts`, `tests/integration/quality.test.ts`, `tests/fixtures/responses/mqa-metrics-success.json`
11
+
12
+ ### MQA Quality Metrics - Readable dimension scores
13
+ - **Feature**: Add dimension score breakdown with ✅/⚠️ indicators and non-max dimensions
14
+ - **Source**: Fetch metrics JSON-LD from data.europa.eu for scoring details
15
+ - **Output**: Markdown and JSON include metrics endpoint link and derived scores
16
+ - **Files**: `src/tools/quality.ts`, `tests/integration/quality.test.ts`, `tests/fixtures/responses/mqa-metrics-success.json`
17
+
18
+ ### Website - Fix color contrast issues
19
+ - **Fix**: Added custom color definitions to `tailwind.config.mjs`
20
+ - **Colors**: navy (#0A1628), data-blue (#0066CC), teal (#0D9488), coral (#F97316), amber (#F59E0B), cream (#FFFEF9)
21
+ - **Impact**: CTA section and all custom-colored elements now render correctly with proper contrast
22
+ - **Before**: Custom Tailwind classes (bg-navy, text-data-blue, etc.) were ignored, causing transparent backgrounds and unreadable text
23
+ - **After**: All colors apply correctly, navy CTA section has proper dark background with white/gray text
24
+
25
+ ## 2026-01-25
26
+
27
+ ### Website - Landing Page
28
+ - **Website**: Created production-ready landing page in `website/` directory
29
+ - **Stack**: Astro v5 + React + Tailwind CSS + TypeScript strict mode
30
+ - **Deployment**: GitHub Actions workflow for automatic GitHub Pages deployment
31
+ - **URL**: https://ondata.github.io/ckan-mcp-server/
32
+ - **Content**:
33
+ - Hero section with value proposition for open data researchers
34
+ - Features section (6 key capabilities)
35
+ - Quick start with copy-paste configs (Claude Desktop, VS Code, Cloudflare Workers, global npm)
36
+ - Use cases (researchers, data scientists, students, journalists, etc.)
37
+ - Supported portals showcase (dati.gov.it, data.gov, data.europa.eu, etc.)
38
+ - SEO optimized (meta tags, Open Graph, sitemap)
39
+ - Responsive design (mobile-first, accessible WCAG AA)
40
+ - **Assets**:
41
+ - SVG favicon with network graph icon
42
+ - manifest.json for PWA support
43
+ - robots.txt and sitemap
44
+ - Script for PNG favicon generation (`generate-favicons.sh`)
45
+ - **Files**:
46
+ - `website/src/pages/index.astro` (main landing page)
47
+ - `website/src/layouts/Layout.astro` (SEO layout)
48
+ - `website/src/components/Footer.astro`
49
+ - `website/public/favicon.svg`, `manifest.json`, `robots.txt`
50
+ - `.github/workflows/deploy-website.yml` (deployment automation)
51
+ - `website/README.md` (documentation)
52
+ - **Build**: 396 packages, builds successfully in ~1s
53
+ - **Deployment trigger**: Push to main branch with changes in `website/` directory
54
+
55
+ ### Documentation - MCP Inspector
56
+ - **README**: Added "Exploring the Server" section before "Manual Testing"
57
+ - **Tool**: MCP Inspector for interactive server exploration
58
+ - **Usage**: `npx @modelcontextprotocol/inspector node dist/index.js`
59
+ - **Features**: Browse tools/resources, test calls with auto-complete, real-time responses, debug errors
60
+ - **Impact**: Developers can quickly explore and test server without manual JSON-RPC
61
+
3
62
  ## 2026-01-23
4
63
 
64
+ ### Release v0.4.17
65
+
66
+ - Published to npm: `@aborruso/ckan-mcp-server@0.4.17`
67
+ - Aligned with GitHub tag `v0.4.17`
68
+
69
+ ### MQA Quality Metrics - Identifier normalization and disambiguation
70
+
71
+ - **Fix**: Normalize identifiers for data.europa.eu lookups (lowercase, collapse hyphens)
72
+ - **Fix**: Retry with disambiguation suffixes (`~~1`, `~~2`) when base identifier 404s
73
+ - **Result**: MQA quality now matches portal IDs for datasets like Beinasco (with `~~1`)
74
+ - **Improved errors**: clearer message when identifier is not aligned
75
+ - **Files modified**: `src/tools/quality.ts`, `tests/integration/quality.test.ts`
76
+ - **Deployed**: Cloudflare Workers v0.4.17
77
+
78
+ ### Release v0.4.16
79
+
80
+ - Published to npm: `@aborruso/ckan-mcp-server@0.4.16`
81
+ - Aligned with GitHub tag `v0.4.16`
82
+
5
83
  ### MQA Quality Metrics - Fix identifier format
6
84
 
7
85
  - **Bug fix**: Identifier transformation for data.europa.eu API compatibility
package/README.md CHANGED
@@ -67,7 +67,7 @@ Deploy on your own server using Node.js:
67
67
  TRANSPORT=http PORT=3000 npm start
68
68
  ```
69
69
 
70
- ### Option 3: Cloudflare Workers ⭐ NEW
70
+ ### Option 3: Cloudflare Workers
71
71
 
72
72
  **Best for**: Global access, zero infrastructure, free hosting
73
73
 
@@ -83,84 +83,128 @@ Use the public Workers endpoint (no local install required):
83
83
  }
84
84
  ```
85
85
 
86
- **NOTE**: This service uses the Cloudflare Workers free tier which has a limit of 100,000 requests per month.
86
+ **NOTE**: This service uses the Cloudflare Workers free tier which has a limit of 100,000 requests per month. **ITS OPERATION IS THEREFORE NOT GUARANTEED**. With local installation, you will not have any problems.
87
87
 
88
88
  Want your own deployment? See [DEPLOYMENT.md](docs/DEPLOYMENT.md).
89
89
 
90
- ## Guides
90
+ ## MCP Client Configuration
91
91
 
92
- ### Claude Desktop Configuration
92
+ This server works with any MCP-compatible client. Below are configuration examples for popular clients, organized by category. Using `@aborruso/ckan-mcp-server@latest` ensures you always get the latest version.
93
93
 
94
- Configuration file location:
94
+ ### CLI Tools
95
95
 
96
- - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
97
- - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
98
- - **Linux**: `~/.config/Claude/claude_desktop_config.json`
96
+ #### Codex
99
97
 
100
- #### Option 1: Global Installation (Recommended)
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "ckan": {
102
+ "command": "npx",
103
+ "args": ["@aborruso/ckan-mcp-server@latest"]
104
+ }
105
+ }
106
+ }
107
+ ```
101
108
 
102
- Install globally to use across all projects:
109
+ #### Copilot CLI
103
110
 
104
111
  ```bash
105
- npm install -g @aborruso/ckan-mcp-server
112
+ copilot mcp add ckan npx @aborruso/ckan-mcp-server@latest
113
+ ```
114
+
115
+ #### Gemini CLI
116
+
117
+ ```bash
118
+ gemini mcp add ckan npx @aborruso/ckan-mcp-server@latest
106
119
  ```
107
120
 
108
- Then add to `claude_desktop_config.json`:
121
+ ### IDEs & Code Editors
122
+
123
+ #### Copilot / VS Code
124
+
125
+ Add to VS Code settings (`.vscode/settings.json` or User Settings):
109
126
 
110
127
  ```json
111
128
  {
112
129
  "mcpServers": {
113
130
  "ckan": {
114
- "command": "ckan-mcp-server"
131
+ "command": "npx",
132
+ "args": ["@aborruso/ckan-mcp-server@latest"]
115
133
  }
116
134
  }
117
135
  }
118
136
  ```
119
137
 
120
- #### Option 2: Local Project Installation (Optional)
138
+ #### Cursor
121
139
 
122
- If you want to install the server in a specific project:
140
+ Add to Cursor MCP settings:
123
141
 
124
- ```bash
125
- cd your-project
126
- npm install @aborruso/ckan-mcp-server
142
+ ```json
143
+ {
144
+ "mcpServers": {
145
+ "ckan": {
146
+ "command": "npx",
147
+ "args": ["@aborruso/ckan-mcp-server@latest"]
148
+ }
149
+ }
150
+ }
127
151
  ```
128
152
 
129
- Then add to `claude_desktop_config.json`:
153
+ #### OpenCode
154
+
155
+ Add to OpenCode configuration:
130
156
 
131
157
  ```json
132
158
  {
133
159
  "mcpServers": {
134
160
  "ckan": {
135
161
  "command": "npx",
136
- "args": ["@aborruso/ckan-mcp-server"]
162
+ "args": ["@aborruso/ckan-mcp-server@latest"]
137
163
  }
138
164
  }
139
165
  }
140
166
  ```
141
167
 
142
- **Note**: `npx` will use the locally installed package in `node_modules`. Make sure to install the package first.
168
+ ### Desktop Applications
143
169
 
144
- #### Option 3: From Source
170
+ #### Claude Desktop
145
171
 
146
- If you cloned the repository:
172
+ Configuration file location:
173
+
174
+ - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
175
+ - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
176
+ - **Linux**: `~/.config/Claude/claude_desktop_config.json`
177
+
178
+ **Using npx (recommended)**:
147
179
 
148
180
  ```json
149
181
  {
150
182
  "mcpServers": {
151
183
  "ckan": {
152
- "command": "node",
153
- "args": ["/absolute/path/to/ckan-mcp-server/dist/index.js"]
184
+ "command": "npx",
185
+ "args": ["@aborruso/ckan-mcp-server@latest"]
154
186
  }
155
187
  }
156
188
  }
157
189
  ```
158
190
 
159
- [A detailed guide](https://github.com/ondata/ckan-mcp-server/discussions/4#discussion-9359684)
191
+ **Using global installation**:
192
+
193
+ ```bash
194
+ npm install -g @aborruso/ckan-mcp-server
195
+ ```
160
196
 
161
- #### Option 4: Cloudflare Workers (HTTP transport)
197
+ ```json
198
+ {
199
+ "mcpServers": {
200
+ "ckan": {
201
+ "command": "ckan-mcp-server"
202
+ }
203
+ }
204
+ }
205
+ ```
162
206
 
163
- Use the public Cloudflare Workers deployment (no local installation required):
207
+ **Using Cloudflare Workers (HTTP)**:
164
208
 
165
209
  ```json
166
210
  {
@@ -172,16 +216,21 @@ Use the public Cloudflare Workers deployment (no local installation required):
172
216
  }
173
217
  ```
174
218
 
175
- **NOTE**: This service uses the Cloudflare Workers free tier which has a limit of 100,000 requests per month.
219
+ **NOTE**: The Cloudflare Workers endpoint uses the free tier (100,000 requests/month limit).
176
220
 
177
- **Note**: This uses the public endpoint. You can also deploy your own Workers instance and use that URL instead.
221
+ [Detailed guide](https://github.com/ondata/ckan-mcp-server/discussions/4#discussion-9359684)
178
222
 
179
- ### Web Tools Configuration
223
+ ### Web Tools
180
224
 
181
- - [ChatGPT web](docs/guide/chatgpt/chatgpt_web.md)
182
- - [Claude web](docs/guide/claude/claude_web.md)
225
+ #### ChatGPT
183
226
 
184
- These guides are based on a public demo server, which has a limit of 100,000 calls per month. For reliable usage, it is recommended to install the CKAN MCP Server on your own machine.
227
+ See [ChatGPT web guide](docs/guide/chatgpt/chatgpt_web.md)
228
+
229
+ #### Claude
230
+
231
+ See [Claude web guide](docs/guide/claude/claude_web.md)
232
+
233
+ **Note**: Web tools use a public demo server with 100,000 calls/month limit. For reliable usage, install the server locally.
185
234
 
186
235
  ## Available Tools
187
236
 
@@ -617,6 +666,27 @@ npm run build
617
666
  npm run watch
618
667
  ```
619
668
 
669
+ ### Exploring the Server
670
+
671
+ If you want to explore and test the server interactively, use the MCP Inspector:
672
+
673
+ ```bash
674
+ # Install MCP Inspector globally (one-time setup)
675
+ npm install -g @modelcontextprotocol/inspector
676
+
677
+ # Build the server
678
+ npm run build
679
+
680
+ # Launch Inspector with your server
681
+ npx @modelcontextprotocol/inspector node dist/index.js
682
+ ```
683
+
684
+ This opens a web interface (usually at `http://localhost:5173`) where you can:
685
+ - Browse all registered tools and resources
686
+ - Test tool calls with auto-complete for parameters
687
+ - See real-time responses in both JSON and rendered format
688
+ - Debug errors with detailed stack traces
689
+
620
690
  ### Manual Testing
621
691
 
622
692
  ```bash
package/dist/index.js CHANGED
@@ -2111,91 +2111,207 @@ var ALLOWED_SERVER_PATTERNS = [
2111
2111
  function isValidMqaServer(serverUrl) {
2112
2112
  return ALLOWED_SERVER_PATTERNS.some((pattern) => pattern.test(serverUrl));
2113
2113
  }
2114
+ function normalizeMqaIdentifier(identifier) {
2115
+ return identifier.trim().replace(/:/g, "-").replace(/-+/g, "-").toLowerCase();
2116
+ }
2117
+ function buildMqaIdCandidates(identifier) {
2118
+ const base = normalizeMqaIdentifier(identifier);
2119
+ if (!base) {
2120
+ return [];
2121
+ }
2122
+ const candidates = [base];
2123
+ if (!base.includes("~~")) {
2124
+ candidates.push(`${base}~~1`, `${base}~~2`);
2125
+ }
2126
+ return candidates;
2127
+ }
2114
2128
  async function getMqaQuality(serverUrl, datasetId) {
2115
2129
  const dataset = await makeCkanRequest(
2116
2130
  serverUrl,
2117
2131
  "package_show",
2118
2132
  { id: datasetId }
2119
2133
  );
2120
- const europeanId = (dataset.identifier || dataset.name).replace(/:/g, "-");
2121
- const mqaUrl = `${MQA_API_BASE}/${europeanId}`;
2122
- try {
2123
- const response = await axios2.get(mqaUrl, {
2124
- timeout: 3e4,
2125
- headers: {
2126
- "User-Agent": "CKAN-MCP-Server/1.0"
2134
+ const baseIdentifier = dataset.identifier || dataset.name;
2135
+ const candidates = buildMqaIdCandidates(baseIdentifier);
2136
+ if (candidates.length === 0) {
2137
+ throw new Error("Dataset identifier is empty; cannot query MQA API");
2138
+ }
2139
+ for (const europeanId of candidates) {
2140
+ const mqaUrl = `${MQA_API_BASE}/${europeanId}`;
2141
+ try {
2142
+ const response = await axios2.get(mqaUrl, {
2143
+ timeout: 3e4,
2144
+ headers: {
2145
+ "User-Agent": "CKAN-MCP-Server/1.0"
2146
+ }
2147
+ });
2148
+ return response.data;
2149
+ } catch (error) {
2150
+ if (axios2.isAxiosError(error)) {
2151
+ if (error.response?.status === 404) {
2152
+ continue;
2153
+ }
2154
+ throw new Error(`MQA API error: ${error.message}`);
2127
2155
  }
2128
- });
2129
- return response.data;
2130
- } catch (error) {
2131
- if (axios2.isAxiosError(error)) {
2132
- if (error.response?.status === 404) {
2133
- throw new Error(`Quality metrics not found for dataset '${europeanId}' on data.europa.eu`);
2156
+ throw error;
2157
+ }
2158
+ }
2159
+ throw new Error(
2160
+ `Quality metrics not found or identifier not aligned on data.europa.eu. Tried: ${candidates.join(", ")}. Check the dataset quality page on data.europa.eu to confirm the identifier (it may include a '~~1' suffix) or verify alignment on dati.gov.it (quality may be marked as 'Non disponibile o identificativo non allineato').`
2161
+ );
2162
+ }
2163
+ function findSectionMetric(section, key) {
2164
+ if (!Array.isArray(section)) {
2165
+ return void 0;
2166
+ }
2167
+ for (const item of section) {
2168
+ if (item && typeof item === "object" && key in item) {
2169
+ return item[key];
2170
+ }
2171
+ }
2172
+ return void 0;
2173
+ }
2174
+ function metricArrayIsAvailable(metric) {
2175
+ if (!Array.isArray(metric)) {
2176
+ return void 0;
2177
+ }
2178
+ const byName = /* @__PURE__ */ new Map();
2179
+ for (const entry of metric) {
2180
+ if (!entry || typeof entry !== "object") {
2181
+ continue;
2182
+ }
2183
+ const name = entry.name;
2184
+ const percentage = entry.percentage;
2185
+ if (typeof name === "string" && typeof percentage === "number") {
2186
+ byName.set(name.toLowerCase(), percentage);
2187
+ }
2188
+ }
2189
+ if (byName.has("yes")) {
2190
+ return (byName.get("yes") || 0) > 0;
2191
+ }
2192
+ if (byName.size > 0) {
2193
+ for (const [name, percentage] of byName.entries()) {
2194
+ if (name.startsWith("2") && percentage > 0) {
2195
+ return true;
2134
2196
  }
2135
- throw new Error(`MQA API error: ${error.message}`);
2136
2197
  }
2137
- throw error;
2198
+ return false;
2199
+ }
2200
+ return void 0;
2201
+ }
2202
+ function metricBoolean(section, key) {
2203
+ const metric = findSectionMetric(section, key);
2204
+ if (typeof metric === "boolean") {
2205
+ return metric;
2206
+ }
2207
+ return void 0;
2208
+ }
2209
+ function metricAvailability(section, availabilityKey, statusKey) {
2210
+ const availabilityMetric = findSectionMetric(section, availabilityKey);
2211
+ const availability = metricArrayIsAvailable(availabilityMetric);
2212
+ if (availability !== void 0) {
2213
+ return { available: availability };
2138
2214
  }
2215
+ if (statusKey) {
2216
+ const statusMetric = findSectionMetric(section, statusKey);
2217
+ const statusAvailable = metricArrayIsAvailable(statusMetric);
2218
+ if (statusAvailable !== void 0) {
2219
+ return { available: statusAvailable };
2220
+ }
2221
+ }
2222
+ return void 0;
2223
+ }
2224
+ function normalizeQualityData(data) {
2225
+ const resultEntry = data?.result?.results?.[0];
2226
+ if (!resultEntry || typeof resultEntry !== "object") {
2227
+ return data;
2228
+ }
2229
+ return {
2230
+ id: resultEntry.info?.["dataset-id"],
2231
+ info: { score: resultEntry.info?.score },
2232
+ accessibility: {
2233
+ accessUrl: metricAvailability(resultEntry.accessibility, "accessUrlAvailability", "accessUrlStatusCode"),
2234
+ downloadUrl: metricAvailability(resultEntry.accessibility, "downloadUrlAvailability", "downloadUrlStatusCode")
2235
+ },
2236
+ reusability: {
2237
+ licence: metricAvailability(resultEntry.reusability, "licenceAvailability"),
2238
+ contactPoint: metricBoolean(resultEntry.reusability, "contactPointAvailability") !== void 0 ? { available: metricBoolean(resultEntry.reusability, "contactPointAvailability") } : void 0,
2239
+ publisher: metricBoolean(resultEntry.reusability, "publisherAvailability") !== void 0 ? { available: metricBoolean(resultEntry.reusability, "publisherAvailability") } : void 0
2240
+ },
2241
+ interoperability: {
2242
+ format: metricAvailability(resultEntry.interoperability, "formatAvailability"),
2243
+ mediaType: metricAvailability(resultEntry.interoperability, "mediaTypeAvailability")
2244
+ },
2245
+ findability: {
2246
+ keyword: metricBoolean(resultEntry.findability, "keywordAvailability") !== void 0 ? { available: metricBoolean(resultEntry.findability, "keywordAvailability") } : void 0,
2247
+ category: metricBoolean(resultEntry.findability, "categoryAvailability") !== void 0 ? { available: metricBoolean(resultEntry.findability, "categoryAvailability") } : void 0,
2248
+ spatial: metricBoolean(resultEntry.findability, "spatialAvailability") !== void 0 ? { available: metricBoolean(resultEntry.findability, "spatialAvailability") } : void 0,
2249
+ temporal: metricBoolean(resultEntry.findability, "temporalAvailability") !== void 0 ? { available: metricBoolean(resultEntry.findability, "temporalAvailability") } : void 0
2250
+ }
2251
+ };
2139
2252
  }
2140
2253
  function formatQualityMarkdown(data, datasetId) {
2254
+ const normalized = normalizeQualityData(data);
2141
2255
  const lines = [];
2142
2256
  lines.push(`# Quality Metrics for Dataset: ${datasetId}`);
2143
2257
  lines.push("");
2144
- if (data.info?.score !== void 0) {
2145
- lines.push(`**Overall Score**: ${data.info.score}/405`);
2258
+ if (normalized.info?.score !== void 0) {
2259
+ lines.push(`**Overall Score**: ${normalized.info.score}/405`);
2146
2260
  lines.push("");
2147
2261
  }
2148
- if (data.accessibility) {
2262
+ if (normalized.accessibility) {
2149
2263
  lines.push("## Accessibility");
2150
- if (data.accessibility.accessUrl !== void 0) {
2151
- lines.push(`- Access URL: ${data.accessibility.accessUrl.available ? "\u2713" : "\u2717"} Available`);
2264
+ if (normalized.accessibility.accessUrl !== void 0) {
2265
+ lines.push(`- Access URL: ${normalized.accessibility.accessUrl.available ? "\u2713" : "\u2717"} Available`);
2152
2266
  }
2153
- if (data.accessibility.downloadUrl !== void 0) {
2154
- lines.push(`- Download URL: ${data.accessibility.downloadUrl.available ? "\u2713" : "\u2717"} Available`);
2267
+ if (normalized.accessibility.downloadUrl !== void 0) {
2268
+ lines.push(`- Download URL: ${normalized.accessibility.downloadUrl.available ? "\u2713" : "\u2717"} Available`);
2155
2269
  }
2156
2270
  lines.push("");
2157
2271
  }
2158
- if (data.reusability) {
2272
+ if (normalized.reusability) {
2159
2273
  lines.push("## Reusability");
2160
- if (data.reusability.licence !== void 0) {
2161
- lines.push(`- License: ${data.reusability.licence.available ? "\u2713" : "\u2717"} Available`);
2274
+ if (normalized.reusability.licence !== void 0) {
2275
+ lines.push(`- License: ${normalized.reusability.licence.available ? "\u2713" : "\u2717"} Available`);
2162
2276
  }
2163
- if (data.reusability.contactPoint !== void 0) {
2164
- lines.push(`- Contact Point: ${data.reusability.contactPoint.available ? "\u2713" : "\u2717"} Available`);
2277
+ if (normalized.reusability.contactPoint !== void 0) {
2278
+ lines.push(`- Contact Point: ${normalized.reusability.contactPoint.available ? "\u2713" : "\u2717"} Available`);
2165
2279
  }
2166
- if (data.reusability.publisher !== void 0) {
2167
- lines.push(`- Publisher: ${data.reusability.publisher.available ? "\u2713" : "\u2717"} Available`);
2280
+ if (normalized.reusability.publisher !== void 0) {
2281
+ lines.push(`- Publisher: ${normalized.reusability.publisher.available ? "\u2713" : "\u2717"} Available`);
2168
2282
  }
2169
2283
  lines.push("");
2170
2284
  }
2171
- if (data.interoperability) {
2285
+ if (normalized.interoperability) {
2172
2286
  lines.push("## Interoperability");
2173
- if (data.interoperability.format !== void 0) {
2174
- lines.push(`- Format: ${data.interoperability.format.available ? "\u2713" : "\u2717"} Available`);
2287
+ if (normalized.interoperability.format !== void 0) {
2288
+ lines.push(`- Format: ${normalized.interoperability.format.available ? "\u2713" : "\u2717"} Available`);
2175
2289
  }
2176
- if (data.interoperability.mediaType !== void 0) {
2177
- lines.push(`- Media Type: ${data.interoperability.mediaType.available ? "\u2713" : "\u2717"} Available`);
2290
+ if (normalized.interoperability.mediaType !== void 0) {
2291
+ lines.push(`- Media Type: ${normalized.interoperability.mediaType.available ? "\u2713" : "\u2717"} Available`);
2178
2292
  }
2179
2293
  lines.push("");
2180
2294
  }
2181
- if (data.findability) {
2295
+ if (normalized.findability) {
2182
2296
  lines.push("## Findability");
2183
- if (data.findability.keyword !== void 0) {
2184
- lines.push(`- Keywords: ${data.findability.keyword.available ? "\u2713" : "\u2717"} Available`);
2297
+ if (normalized.findability.keyword !== void 0) {
2298
+ lines.push(`- Keywords: ${normalized.findability.keyword.available ? "\u2713" : "\u2717"} Available`);
2185
2299
  }
2186
- if (data.findability.category !== void 0) {
2187
- lines.push(`- Category: ${data.findability.category.available ? "\u2713" : "\u2717"} Available`);
2300
+ if (normalized.findability.category !== void 0) {
2301
+ lines.push(`- Category: ${normalized.findability.category.available ? "\u2713" : "\u2717"} Available`);
2188
2302
  }
2189
- if (data.findability.spatial !== void 0) {
2190
- lines.push(`- Spatial: ${data.findability.spatial.available ? "\u2713" : "\u2717"} Available`);
2303
+ if (normalized.findability.spatial !== void 0) {
2304
+ lines.push(`- Spatial: ${normalized.findability.spatial.available ? "\u2713" : "\u2717"} Available`);
2191
2305
  }
2192
- if (data.findability.temporal !== void 0) {
2193
- lines.push(`- Temporal: ${data.findability.temporal.available ? "\u2713" : "\u2717"} Available`);
2306
+ if (normalized.findability.temporal !== void 0) {
2307
+ lines.push(`- Temporal: ${normalized.findability.temporal.available ? "\u2713" : "\u2717"} Available`);
2194
2308
  }
2195
2309
  lines.push("");
2196
2310
  }
2197
2311
  lines.push("---");
2198
- lines.push(`Source: ${MQA_API_BASE}/${data.id || datasetId}`);
2312
+ const portalId = normalized.id || datasetId;
2313
+ lines.push(`Portal: https://data.europa.eu/data/datasets/${portalId}/quality?locale=it`);
2314
+ lines.push(`Source: ${MQA_API_BASE}/${portalId}`);
2199
2315
  return lines.join("\n");
2200
2316
  }
2201
2317
  function registerQualityTools(server2) {
@@ -2749,7 +2865,7 @@ var registerAllPrompts = (server2) => {
2749
2865
  function createServer() {
2750
2866
  return new McpServer({
2751
2867
  name: "ckan-mcp-server",
2752
- version: "0.4.16"
2868
+ version: "0.4.17"
2753
2869
  });
2754
2870
  }
2755
2871
  function registerAll(server2) {