@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.
- package/LOG.md +78 -0
- package/README.md +104 -34
- package/dist/index.js +161 -45
- package/dist/worker.js +42 -42
- package/package.json +12 -1
- package/.devin/wiki.json +0 -273
- package/CLAUDE.md +0 -398
- package/PRD.md +0 -999
- package/REFACTORING.md +0 -238
- package/examples/langgraph/01_basic_workflow.py +0 -277
- package/examples/langgraph/02_data_exploration.py +0 -366
- package/examples/langgraph/README.md +0 -719
- package/examples/langgraph/metadata_quality.py +0 -299
- package/examples/langgraph/requirements.txt +0 -12
- package/examples/langgraph/setup.sh +0 -32
- package/examples/langgraph/test_setup.py +0 -106
- package/openspec/AGENTS.md +0 -456
- package/openspec/changes/add-ckan-analyze-dataset-structure/proposal.md +0 -17
- package/openspec/changes/add-ckan-analyze-dataset-structure/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/add-ckan-analyze-dataset-structure/tasks.md +0 -6
- package/openspec/changes/add-ckan-analyze-dataset-updates/proposal.md +0 -17
- package/openspec/changes/add-ckan-analyze-dataset-updates/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/add-ckan-analyze-dataset-updates/tasks.md +0 -6
- package/openspec/changes/add-ckan-audit-tool/proposal.md +0 -17
- package/openspec/changes/add-ckan-audit-tool/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/add-ckan-audit-tool/tasks.md +0 -6
- package/openspec/changes/add-ckan-dataset-insights/proposal.md +0 -17
- package/openspec/changes/add-ckan-dataset-insights/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/add-ckan-dataset-insights/tasks.md +0 -6
- package/openspec/changes/add-ckan-host-allowlist-env/design.md +0 -38
- package/openspec/changes/add-ckan-host-allowlist-env/proposal.md +0 -16
- package/openspec/changes/add-ckan-host-allowlist-env/specs/ckan-request-allowlist/spec.md +0 -15
- package/openspec/changes/add-ckan-host-allowlist-env/specs/cloudflare-deployment/spec.md +0 -11
- package/openspec/changes/add-ckan-host-allowlist-env/tasks.md +0 -12
- package/openspec/changes/add-escape-text-query/proposal.md +0 -12
- package/openspec/changes/add-escape-text-query/specs/ckan-search/spec.md +0 -11
- package/openspec/changes/add-escape-text-query/tasks.md +0 -8
- package/openspec/changes/add-mqa-quality-tool/proposal.md +0 -21
- package/openspec/changes/add-mqa-quality-tool/specs/ckan-quality/spec.md +0 -71
- package/openspec/changes/add-mqa-quality-tool/tasks.md +0 -29
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/design.md +0 -115
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/proposal.md +0 -52
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/specs/mcp-resources/spec.md +0 -92
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/tasks.md +0 -56
- package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/design.md +0 -355
- package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/proposal.md +0 -161
- package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/tasks.md +0 -162
- package/openspec/changes/archive/2026-01-08-translate-project-to-english/proposal.md +0 -115
- package/openspec/changes/archive/2026-01-08-translate-project-to-english/specs/documentation-language/spec.md +0 -32
- package/openspec/changes/archive/2026-01-08-translate-project-to-english/tasks.md +0 -115
- package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/proposal.md +0 -17
- package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/tasks.md +0 -6
- package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/design.md +0 -734
- package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/proposal.md +0 -183
- package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/specs/cloudflare-deployment/spec.md +0 -389
- package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/tasks.md +0 -519
- package/openspec/changes/archive/2026-01-15-add-mcp-prompts/proposal.md +0 -13
- package/openspec/changes/archive/2026-01-15-add-mcp-prompts/specs/mcp-prompts/spec.md +0 -22
- package/openspec/changes/archive/2026-01-15-add-mcp-prompts/tasks.md +0 -10
- package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/proposal.md +0 -13
- package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/specs/mcp-resources/spec.md +0 -38
- package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/tasks.md +0 -10
- package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/proposal.md +0 -13
- package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/specs/repository-metadata/spec.md +0 -14
- package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/tasks.md +0 -12
- package/openspec/changes/archive/2026-01-19-update-search-parser-config/proposal.md +0 -13
- package/openspec/changes/archive/2026-01-19-update-search-parser-config/specs/ckan-insights/spec.md +0 -11
- package/openspec/changes/archive/2026-01-19-update-search-parser-config/specs/ckan-search/spec.md +0 -11
- package/openspec/changes/archive/2026-01-19-update-search-parser-config/tasks.md +0 -6
- package/openspec/changes/archive/add-automated-tests/design.md +0 -324
- package/openspec/changes/archive/add-automated-tests/proposal.md +0 -167
- package/openspec/changes/archive/add-automated-tests/specs/automated-testing/spec.md +0 -143
- package/openspec/changes/archive/add-automated-tests/tasks.md +0 -132
- package/openspec/project.md +0 -115
- package/openspec/specs/ckan-insights/spec.md +0 -23
- package/openspec/specs/ckan-search/spec.md +0 -16
- package/openspec/specs/cloudflare-deployment/spec.md +0 -344
- package/openspec/specs/documentation-language/spec.md +0 -32
- package/openspec/specs/mcp-prompts/spec.md +0 -26
- package/openspec/specs/mcp-resources/spec.md +0 -120
- package/openspec/specs/repository-metadata/spec.md +0 -19
- package/private/commenti-privati.yaml +0 -14
- package/testo.md +0 -12
- package/web-gui/PRD.md +0 -158
- package/web-gui/public/index.html +0 -883
- 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
|
|
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
|
-
##
|
|
90
|
+
## MCP Client Configuration
|
|
91
91
|
|
|
92
|
-
|
|
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
|
-
|
|
94
|
+
### CLI Tools
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
98
|
-
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
|
96
|
+
#### Codex
|
|
99
97
|
|
|
100
|
-
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"ckan": {
|
|
102
|
+
"command": "npx",
|
|
103
|
+
"args": ["@aborruso/ckan-mcp-server@latest"]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
101
108
|
|
|
102
|
-
|
|
109
|
+
#### Copilot CLI
|
|
103
110
|
|
|
104
111
|
```bash
|
|
105
|
-
|
|
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
|
-
|
|
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": "
|
|
131
|
+
"command": "npx",
|
|
132
|
+
"args": ["@aborruso/ckan-mcp-server@latest"]
|
|
115
133
|
}
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
136
|
```
|
|
119
137
|
|
|
120
|
-
####
|
|
138
|
+
#### Cursor
|
|
121
139
|
|
|
122
|
-
|
|
140
|
+
Add to Cursor MCP settings:
|
|
123
141
|
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
168
|
+
### Desktop Applications
|
|
143
169
|
|
|
144
|
-
####
|
|
170
|
+
#### Claude Desktop
|
|
145
171
|
|
|
146
|
-
|
|
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": "
|
|
153
|
-
"args": ["/
|
|
184
|
+
"command": "npx",
|
|
185
|
+
"args": ["@aborruso/ckan-mcp-server@latest"]
|
|
154
186
|
}
|
|
155
187
|
}
|
|
156
188
|
}
|
|
157
189
|
```
|
|
158
190
|
|
|
159
|
-
|
|
191
|
+
**Using global installation**:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npm install -g @aborruso/ckan-mcp-server
|
|
195
|
+
```
|
|
160
196
|
|
|
161
|
-
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"mcpServers": {
|
|
200
|
+
"ckan": {
|
|
201
|
+
"command": "ckan-mcp-server"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
162
206
|
|
|
163
|
-
|
|
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**:
|
|
219
|
+
**NOTE**: The Cloudflare Workers endpoint uses the free tier (100,000 requests/month limit).
|
|
176
220
|
|
|
177
|
-
|
|
221
|
+
[Detailed guide](https://github.com/ondata/ckan-mcp-server/discussions/4#discussion-9359684)
|
|
178
222
|
|
|
179
|
-
### Web Tools
|
|
223
|
+
### Web Tools
|
|
180
224
|
|
|
181
|
-
|
|
182
|
-
- [Claude web](docs/guide/claude/claude_web.md)
|
|
225
|
+
#### ChatGPT
|
|
183
226
|
|
|
184
|
-
|
|
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
|
|
2121
|
-
const
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
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
|
-
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
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
|
-
|
|
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 (
|
|
2145
|
-
lines.push(`**Overall Score**: ${
|
|
2258
|
+
if (normalized.info?.score !== void 0) {
|
|
2259
|
+
lines.push(`**Overall Score**: ${normalized.info.score}/405`);
|
|
2146
2260
|
lines.push("");
|
|
2147
2261
|
}
|
|
2148
|
-
if (
|
|
2262
|
+
if (normalized.accessibility) {
|
|
2149
2263
|
lines.push("## Accessibility");
|
|
2150
|
-
if (
|
|
2151
|
-
lines.push(`- Access URL: ${
|
|
2264
|
+
if (normalized.accessibility.accessUrl !== void 0) {
|
|
2265
|
+
lines.push(`- Access URL: ${normalized.accessibility.accessUrl.available ? "\u2713" : "\u2717"} Available`);
|
|
2152
2266
|
}
|
|
2153
|
-
if (
|
|
2154
|
-
lines.push(`- Download URL: ${
|
|
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 (
|
|
2272
|
+
if (normalized.reusability) {
|
|
2159
2273
|
lines.push("## Reusability");
|
|
2160
|
-
if (
|
|
2161
|
-
lines.push(`- License: ${
|
|
2274
|
+
if (normalized.reusability.licence !== void 0) {
|
|
2275
|
+
lines.push(`- License: ${normalized.reusability.licence.available ? "\u2713" : "\u2717"} Available`);
|
|
2162
2276
|
}
|
|
2163
|
-
if (
|
|
2164
|
-
lines.push(`- Contact Point: ${
|
|
2277
|
+
if (normalized.reusability.contactPoint !== void 0) {
|
|
2278
|
+
lines.push(`- Contact Point: ${normalized.reusability.contactPoint.available ? "\u2713" : "\u2717"} Available`);
|
|
2165
2279
|
}
|
|
2166
|
-
if (
|
|
2167
|
-
lines.push(`- Publisher: ${
|
|
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 (
|
|
2285
|
+
if (normalized.interoperability) {
|
|
2172
2286
|
lines.push("## Interoperability");
|
|
2173
|
-
if (
|
|
2174
|
-
lines.push(`- Format: ${
|
|
2287
|
+
if (normalized.interoperability.format !== void 0) {
|
|
2288
|
+
lines.push(`- Format: ${normalized.interoperability.format.available ? "\u2713" : "\u2717"} Available`);
|
|
2175
2289
|
}
|
|
2176
|
-
if (
|
|
2177
|
-
lines.push(`- Media Type: ${
|
|
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 (
|
|
2295
|
+
if (normalized.findability) {
|
|
2182
2296
|
lines.push("## Findability");
|
|
2183
|
-
if (
|
|
2184
|
-
lines.push(`- Keywords: ${
|
|
2297
|
+
if (normalized.findability.keyword !== void 0) {
|
|
2298
|
+
lines.push(`- Keywords: ${normalized.findability.keyword.available ? "\u2713" : "\u2717"} Available`);
|
|
2185
2299
|
}
|
|
2186
|
-
if (
|
|
2187
|
-
lines.push(`- Category: ${
|
|
2300
|
+
if (normalized.findability.category !== void 0) {
|
|
2301
|
+
lines.push(`- Category: ${normalized.findability.category.available ? "\u2713" : "\u2717"} Available`);
|
|
2188
2302
|
}
|
|
2189
|
-
if (
|
|
2190
|
-
lines.push(`- Spatial: ${
|
|
2303
|
+
if (normalized.findability.spatial !== void 0) {
|
|
2304
|
+
lines.push(`- Spatial: ${normalized.findability.spatial.available ? "\u2713" : "\u2717"} Available`);
|
|
2191
2305
|
}
|
|
2192
|
-
if (
|
|
2193
|
-
lines.push(`- Temporal: ${
|
|
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
|
-
|
|
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.
|
|
2868
|
+
version: "0.4.17"
|
|
2753
2869
|
});
|
|
2754
2870
|
}
|
|
2755
2871
|
function registerAll(server2) {
|