@aborruso/ckan-mcp-server 0.4.14 โ†’ 0.4.15

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/EXAMPLES.md CHANGED
@@ -151,6 +151,24 @@ ckan_package_search({
151
151
  })
152
152
  ```
153
153
 
154
+ ### Get MQA quality metrics for a dataset
155
+ ```typescript
156
+ ckan_get_mqa_quality({
157
+ server_url: "https://www.dati.gov.it/opendata",
158
+ dataset_id: "332be8b7-89b9-4dfe-a252-7fccd3efda76",
159
+ response_format: "markdown"
160
+ })
161
+ ```
162
+
163
+ Returns quality score and detailed metrics from data.europa.eu MQA (Metadata Quality Assurance) system:
164
+ - Overall score (max 405 points)
165
+ - Accessibility (URL status, download availability)
166
+ - Reusability (license, contact point, publisher)
167
+ - Interoperability (format, media type)
168
+ - Findability (keywords, category, spatial/temporal coverage)
169
+
170
+ **Note**: Only works with dati.gov.it datasets. Uses the `identifier` field (or falls back to `name`) to query the European MQA API.
171
+
154
172
  ## USA Examples - data.gov
155
173
 
156
174
  ### Search government datasets
package/LOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # LOG
2
2
 
3
+ ## 2026-01-23
4
+
5
+ ### MQA Quality Metrics Tool
6
+
7
+ - **Feature**: Added `ckan_get_mqa_quality` tool for retrieving quality metrics from data.europa.eu MQA API
8
+ - **Scope**: Only works with dati.gov.it datasets (server validation enforced)
9
+ - **Data source**: Queries https://data.europa.eu/api/mqa/cache/datasets/{identifier}
10
+ - **Identifier logic**: Uses `identifier` field from CKAN metadata, falls back to `name` if identifier is empty
11
+ - **Metrics returned**:
12
+ - Overall score (max 405 points)
13
+ - Accessibility (URL status, download availability)
14
+ - Reusability (license, contact point, publisher)
15
+ - Interoperability (format, media type)
16
+ - Findability (keywords, category, spatial/temporal coverage)
17
+ - **Output formats**: Markdown (default, human-readable) or JSON (structured data)
18
+ - **Error handling**: Dataset not found, MQA API unavailable, invalid server URL
19
+ - **Tests**: +11 integration tests (212 total, all passing)
20
+ - Server validation (www/non-www dati.gov.it URLs)
21
+ - Quality retrieval with identifier
22
+ - Fallback to name field
23
+ - Error scenarios (404, network errors)
24
+ - Markdown formatting (complete/partial data, availability indicators)
25
+ - **Documentation**: README.md (new Quality Metrics section), EXAMPLES.md (usage example with expected metrics)
26
+ - **Files**:
27
+ - `src/tools/quality.ts` (new, 194 lines)
28
+ - `src/server.ts` (register quality tools)
29
+ - `tests/integration/quality.test.ts` (new, 11 tests)
30
+ - `tests/fixtures/responses/mqa-quality-success.json` (new)
31
+ - `tests/fixtures/responses/package-show-{with,without}-identifier.json` (new)
32
+ - **OpenSpec**: Proposal in `openspec/changes/add-mqa-quality-tool/` (4 requirements, 11 scenarios)
33
+
3
34
  ## 2026-01-22
4
35
 
5
36
  ### Date Query Auto-Conversion (v0.4.14)
package/README.md CHANGED
@@ -17,7 +17,7 @@ MCP (Model Context Protocol) server for interacting with CKAN-based open data po
17
17
  - โšก Pagination and faceting support
18
18
  - ๐Ÿ“„ MCP Resource Templates for direct data access
19
19
  - ๐Ÿงญ Guided MCP prompts for common workflows
20
- - ๐Ÿงช Test suite with 191 tests (100% passing)
20
+ - ๐Ÿงช Test suite with 212 tests (100% passing)
21
21
 
22
22
  ---
23
23
 
@@ -45,7 +45,7 @@ npm install
45
45
  # Build with esbuild (fast, ~4ms)
46
46
  npm run build
47
47
 
48
- # Run tests (191 tests)
48
+ # Run tests (212 tests)
49
49
  npm test
50
50
  ```
51
51
 
@@ -209,6 +209,10 @@ These guides are based on a public demo server, which has a limit of 100,000 cal
209
209
  - **ckan_group_show**: Show group details
210
210
  - **ckan_group_search**: Search groups by name
211
211
 
212
+ ### Quality Metrics
213
+
214
+ - **ckan_get_mqa_quality**: Get MQA quality score and metrics for dati.gov.it datasets (accessibility, reusability, interoperability, findability)
215
+
212
216
  ### Utilities
213
217
 
214
218
  - **ckan_status_show**: Verify server status
@@ -566,7 +570,7 @@ ckan-mcp-server/
566
570
  โ”‚ โ””โ”€โ”€ transport/
567
571
  โ”‚ โ”œโ”€โ”€ stdio.ts # Stdio transport
568
572
  โ”‚ โ””โ”€โ”€ http.ts # HTTP transport
569
- โ”œโ”€โ”€ tests/ # Test suite (191 tests)
573
+ โ”œโ”€โ”€ tests/ # Test suite (212 tests)
570
574
  โ”œโ”€โ”€ dist/ # Compiled files (generated)
571
575
  โ”œโ”€โ”€ package.json
572
576
  โ””โ”€โ”€ README.md
package/dist/index.js CHANGED
@@ -2101,6 +2101,146 @@ Returns:
2101
2101
  );
2102
2102
  }
2103
2103
 
2104
+ // src/tools/quality.ts
2105
+ import { z as z8 } from "zod";
2106
+ import axios2 from "axios";
2107
+ var MQA_API_BASE = "https://data.europa.eu/api/mqa/cache/datasets";
2108
+ var ALLOWED_SERVER_PATTERNS = [
2109
+ /^https?:\/\/(www\.)?dati\.gov\.it/i
2110
+ ];
2111
+ function isValidMqaServer(serverUrl) {
2112
+ return ALLOWED_SERVER_PATTERNS.some((pattern) => pattern.test(serverUrl));
2113
+ }
2114
+ async function getMqaQuality(serverUrl, datasetId) {
2115
+ const dataset = await makeCkanRequest(
2116
+ serverUrl,
2117
+ "package_show",
2118
+ { id: datasetId }
2119
+ );
2120
+ const europeanId = dataset.identifier || dataset.name;
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"
2127
+ }
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`);
2134
+ }
2135
+ throw new Error(`MQA API error: ${error.message}`);
2136
+ }
2137
+ throw error;
2138
+ }
2139
+ }
2140
+ function formatQualityMarkdown(data, datasetId) {
2141
+ const lines = [];
2142
+ lines.push(`# Quality Metrics for Dataset: ${datasetId}`);
2143
+ lines.push("");
2144
+ if (data.info?.score !== void 0) {
2145
+ lines.push(`**Overall Score**: ${data.info.score}/405`);
2146
+ lines.push("");
2147
+ }
2148
+ if (data.accessibility) {
2149
+ lines.push("## Accessibility");
2150
+ if (data.accessibility.accessUrl !== void 0) {
2151
+ lines.push(`- Access URL: ${data.accessibility.accessUrl.available ? "\u2713" : "\u2717"} Available`);
2152
+ }
2153
+ if (data.accessibility.downloadUrl !== void 0) {
2154
+ lines.push(`- Download URL: ${data.accessibility.downloadUrl.available ? "\u2713" : "\u2717"} Available`);
2155
+ }
2156
+ lines.push("");
2157
+ }
2158
+ if (data.reusability) {
2159
+ lines.push("## Reusability");
2160
+ if (data.reusability.licence !== void 0) {
2161
+ lines.push(`- License: ${data.reusability.licence.available ? "\u2713" : "\u2717"} Available`);
2162
+ }
2163
+ if (data.reusability.contactPoint !== void 0) {
2164
+ lines.push(`- Contact Point: ${data.reusability.contactPoint.available ? "\u2713" : "\u2717"} Available`);
2165
+ }
2166
+ if (data.reusability.publisher !== void 0) {
2167
+ lines.push(`- Publisher: ${data.reusability.publisher.available ? "\u2713" : "\u2717"} Available`);
2168
+ }
2169
+ lines.push("");
2170
+ }
2171
+ if (data.interoperability) {
2172
+ lines.push("## Interoperability");
2173
+ if (data.interoperability.format !== void 0) {
2174
+ lines.push(`- Format: ${data.interoperability.format.available ? "\u2713" : "\u2717"} Available`);
2175
+ }
2176
+ if (data.interoperability.mediaType !== void 0) {
2177
+ lines.push(`- Media Type: ${data.interoperability.mediaType.available ? "\u2713" : "\u2717"} Available`);
2178
+ }
2179
+ lines.push("");
2180
+ }
2181
+ if (data.findability) {
2182
+ lines.push("## Findability");
2183
+ if (data.findability.keyword !== void 0) {
2184
+ lines.push(`- Keywords: ${data.findability.keyword.available ? "\u2713" : "\u2717"} Available`);
2185
+ }
2186
+ if (data.findability.category !== void 0) {
2187
+ lines.push(`- Category: ${data.findability.category.available ? "\u2713" : "\u2717"} Available`);
2188
+ }
2189
+ if (data.findability.spatial !== void 0) {
2190
+ lines.push(`- Spatial: ${data.findability.spatial.available ? "\u2713" : "\u2717"} Available`);
2191
+ }
2192
+ if (data.findability.temporal !== void 0) {
2193
+ lines.push(`- Temporal: ${data.findability.temporal.available ? "\u2713" : "\u2717"} Available`);
2194
+ }
2195
+ lines.push("");
2196
+ }
2197
+ lines.push("---");
2198
+ lines.push(`Source: ${MQA_API_BASE}/${data.id || datasetId}`);
2199
+ return lines.join("\n");
2200
+ }
2201
+ function registerQualityTools(server2) {
2202
+ server2.tool(
2203
+ "ckan_get_mqa_quality",
2204
+ "Get MQA (Metadata Quality Assurance) quality metrics for a dataset on dati.gov.it. Returns quality score and detailed metrics (accessibility, reusability, interoperability, findability) from data.europa.eu. Only works with dati.gov.it server.",
2205
+ {
2206
+ server_url: z8.string().url().describe("Base URL of dati.gov.it (e.g., https://www.dati.gov.it/opendata)"),
2207
+ dataset_id: z8.string().describe("Dataset ID or name"),
2208
+ response_format: ResponseFormatSchema.optional()
2209
+ },
2210
+ async ({ server_url, dataset_id, response_format }) => {
2211
+ if (!isValidMqaServer(server_url)) {
2212
+ return {
2213
+ content: [{
2214
+ type: "text",
2215
+ text: `Error: MQA quality metrics are only available for dati.gov.it datasets. Provided server: ${server_url}
2216
+
2217
+ The MQA (Metadata Quality Assurance) system is operated by data.europa.eu and only evaluates datasets from Italian open data portal.`
2218
+ }]
2219
+ };
2220
+ }
2221
+ try {
2222
+ const qualityData = await getMqaQuality(server_url, dataset_id);
2223
+ const format = response_format || "markdown" /* MARKDOWN */;
2224
+ const output = format === "json" /* JSON */ ? JSON.stringify(qualityData, null, 2) : formatQualityMarkdown(qualityData, dataset_id);
2225
+ return {
2226
+ content: [{
2227
+ type: "text",
2228
+ text: output
2229
+ }]
2230
+ };
2231
+ } catch (error) {
2232
+ const errorMessage = error instanceof Error ? error.message : String(error);
2233
+ return {
2234
+ content: [{
2235
+ type: "text",
2236
+ text: `Error retrieving quality metrics: ${errorMessage}`
2237
+ }]
2238
+ };
2239
+ }
2240
+ }
2241
+ );
2242
+ }
2243
+
2104
2244
  // src/resources/dataset.ts
2105
2245
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2106
2246
 
@@ -2370,7 +2510,7 @@ function registerAllResources(server2) {
2370
2510
  }
2371
2511
 
2372
2512
  // src/prompts/theme.ts
2373
- import { z as z8 } from "zod";
2513
+ import { z as z9 } from "zod";
2374
2514
 
2375
2515
  // src/prompts/types.ts
2376
2516
  var createTextPrompt = (text) => ({
@@ -2427,9 +2567,9 @@ var registerThemePrompt = (server2) => {
2427
2567
  title: "Search datasets by theme",
2428
2568
  description: "Guided prompt to discover a theme and search datasets under it.",
2429
2569
  argsSchema: {
2430
- server_url: z8.string().url().describe("Base URL of the CKAN server"),
2431
- theme: z8.string().min(1).describe("Theme or group name to search"),
2432
- rows: z8.coerce.number().int().positive().default(10).describe("Max results to return")
2570
+ server_url: z9.string().url().describe("Base URL of the CKAN server"),
2571
+ theme: z9.string().min(1).describe("Theme or group name to search"),
2572
+ rows: z9.coerce.number().int().positive().default(10).describe("Max results to return")
2433
2573
  }
2434
2574
  },
2435
2575
  async ({ server_url, theme, rows }) => createTextPrompt(buildThemePromptText(server_url, theme, rows))
@@ -2437,7 +2577,7 @@ var registerThemePrompt = (server2) => {
2437
2577
  };
2438
2578
 
2439
2579
  // src/prompts/organization.ts
2440
- import { z as z9 } from "zod";
2580
+ import { z as z10 } from "zod";
2441
2581
  var ORGANIZATION_PROMPT_NAME = "ckan-search-by-organization";
2442
2582
  var buildOrganizationPromptText = (serverUrl, organization, rows) => `# Guided search: datasets by organization
2443
2583
 
@@ -2468,9 +2608,9 @@ var registerOrganizationPrompt = (server2) => {
2468
2608
  title: "Search datasets by organization",
2469
2609
  description: "Guided prompt to find a publisher and list its datasets.",
2470
2610
  argsSchema: {
2471
- server_url: z9.string().url().describe("Base URL of the CKAN server"),
2472
- organization: z9.string().min(1).describe("Organization name or keyword"),
2473
- rows: z9.coerce.number().int().positive().default(10).describe("Max results to return")
2611
+ server_url: z10.string().url().describe("Base URL of the CKAN server"),
2612
+ organization: z10.string().min(1).describe("Organization name or keyword"),
2613
+ rows: z10.coerce.number().int().positive().default(10).describe("Max results to return")
2474
2614
  }
2475
2615
  },
2476
2616
  async ({ server_url, organization, rows }) => createTextPrompt(buildOrganizationPromptText(server_url, organization, rows))
@@ -2478,7 +2618,7 @@ var registerOrganizationPrompt = (server2) => {
2478
2618
  };
2479
2619
 
2480
2620
  // src/prompts/format.ts
2481
- import { z as z10 } from "zod";
2621
+ import { z as z11 } from "zod";
2482
2622
  var FORMAT_PROMPT_NAME = "ckan-search-by-format";
2483
2623
  var buildFormatPromptText = (serverUrl, format, rows) => `# Guided search: datasets by resource format
2484
2624
 
@@ -2499,9 +2639,9 @@ var registerFormatPrompt = (server2) => {
2499
2639
  title: "Search datasets by resource format",
2500
2640
  description: "Guided prompt to find datasets with a given resource format.",
2501
2641
  argsSchema: {
2502
- server_url: z10.string().url().describe("Base URL of the CKAN server"),
2503
- format: z10.string().min(1).describe("Resource format (e.g., CSV, JSON)"),
2504
- rows: z10.coerce.number().int().positive().default(10).describe("Max results to return")
2642
+ server_url: z11.string().url().describe("Base URL of the CKAN server"),
2643
+ format: z11.string().min(1).describe("Resource format (e.g., CSV, JSON)"),
2644
+ rows: z11.coerce.number().int().positive().default(10).describe("Max results to return")
2505
2645
  }
2506
2646
  },
2507
2647
  async ({ server_url, format, rows }) => createTextPrompt(buildFormatPromptText(server_url, format, rows))
@@ -2509,7 +2649,7 @@ var registerFormatPrompt = (server2) => {
2509
2649
  };
2510
2650
 
2511
2651
  // src/prompts/recent.ts
2512
- import { z as z11 } from "zod";
2652
+ import { z as z12 } from "zod";
2513
2653
  var RECENT_PROMPT_NAME = "ckan-recent-datasets";
2514
2654
  var buildRecentPromptText = (serverUrl, rows) => `# Guided search: recent datasets
2515
2655
 
@@ -2537,8 +2677,8 @@ var registerRecentPrompt = (server2) => {
2537
2677
  title: "Find recently updated datasets",
2538
2678
  description: "Guided prompt to list recently updated datasets on a CKAN portal.",
2539
2679
  argsSchema: {
2540
- server_url: z11.string().url().describe("Base URL of the CKAN server"),
2541
- rows: z11.coerce.number().int().positive().default(10).describe("Max results to return")
2680
+ server_url: z12.string().url().describe("Base URL of the CKAN server"),
2681
+ rows: z12.coerce.number().int().positive().default(10).describe("Max results to return")
2542
2682
  }
2543
2683
  },
2544
2684
  async ({ server_url, rows }) => createTextPrompt(buildRecentPromptText(server_url, rows))
@@ -2546,7 +2686,7 @@ var registerRecentPrompt = (server2) => {
2546
2686
  };
2547
2687
 
2548
2688
  // src/prompts/dataset-analysis.ts
2549
- import { z as z12 } from "zod";
2689
+ import { z as z13 } from "zod";
2550
2690
  var DATASET_ANALYSIS_PROMPT_NAME = "ckan-analyze-dataset";
2551
2691
  var buildDatasetAnalysisPromptText = (serverUrl, id) => `# Guided analysis: dataset
2552
2692
 
@@ -2588,8 +2728,8 @@ var registerDatasetAnalysisPrompt = (server2) => {
2588
2728
  title: "Analyze a dataset",
2589
2729
  description: "Guided prompt to inspect dataset metadata and explore DataStore tables.",
2590
2730
  argsSchema: {
2591
- server_url: z12.string().url().describe("Base URL of the CKAN server"),
2592
- id: z12.string().min(1).describe("Dataset id or name (CKAN package id)")
2731
+ server_url: z13.string().url().describe("Base URL of the CKAN server"),
2732
+ id: z13.string().min(1).describe("Dataset id or name (CKAN package id)")
2593
2733
  }
2594
2734
  },
2595
2735
  async ({ server_url, id }) => createTextPrompt(buildDatasetAnalysisPromptText(server_url, id))
@@ -2609,7 +2749,7 @@ var registerAllPrompts = (server2) => {
2609
2749
  function createServer() {
2610
2750
  return new McpServer({
2611
2751
  name: "ckan-mcp-server",
2612
- version: "0.4.13"
2752
+ version: "0.4.15"
2613
2753
  });
2614
2754
  }
2615
2755
  function registerAll(server2) {
@@ -2619,6 +2759,7 @@ function registerAll(server2) {
2619
2759
  registerStatusTools(server2);
2620
2760
  registerTagTools(server2);
2621
2761
  registerGroupTools(server2);
2762
+ registerQualityTools(server2);
2622
2763
  registerAllResources(server2);
2623
2764
  registerAllPrompts(server2);
2624
2765
  }
@@ -0,0 +1,21 @@
1
+ # Change: Add MQA Quality Score Tool for dati.gov.it
2
+
3
+ ## Why
4
+ Datasets on dati.gov.it display quality scores (Eccellente, Buono, etc.) calculated by data.europa.eu's MQA (Metadata Quality Assurance) system. Currently there's no way to access these quality metrics through the MCP server, limiting users' ability to evaluate dataset quality programmatically.
5
+
6
+ ## What Changes
7
+ - Add `ckan_get_mqa_quality` tool for retrieving quality metrics from data.europa.eu
8
+ - Tool works only with dati.gov.it server (validated at runtime)
9
+ - Fetches dataset identifier from CKAN, then queries MQA API
10
+ - Returns quality score and detailed metrics (accessibility, reusability, interoperability, findability)
11
+ - Supports both markdown and JSON output formats
12
+
13
+ ## Impact
14
+ - Affected specs: New capability `ckan-quality`
15
+ - Affected code:
16
+ - New file: `src/tools/quality.ts` (tool handler)
17
+ - New file: `tests/integration/quality.test.ts` (tests with mocked responses)
18
+ - New file: `tests/fixtures/responses/mqa-quality.json` (mock data)
19
+ - Modified: `src/server.ts` (register new tool)
20
+ - Modified: `README.md` (document new tool)
21
+ - Modified: `EXAMPLES.md` (add usage examples)
@@ -0,0 +1,71 @@
1
+ ## ADDED Requirements
2
+
3
+ ### Requirement: MQA Quality Score Retrieval
4
+ The system SHALL provide a tool to retrieve MQA (Metadata Quality Assurance) quality metrics from data.europa.eu for datasets published on dati.gov.it.
5
+
6
+ #### Scenario: Successful quality score retrieval
7
+ - **GIVEN** a valid dataset ID from dati.gov.it
8
+ - **WHEN** user requests quality metrics
9
+ - **THEN** system SHALL fetch identifier field from CKAN package_show
10
+ - **AND** system SHALL query data.europa.eu MQA API
11
+ - **AND** system SHALL return quality score and detailed metrics (accessibility, reusability, interoperability, findability)
12
+
13
+ #### Scenario: Identifier fallback to name
14
+ - **GIVEN** a dataset with empty identifier field
15
+ - **WHEN** user requests quality metrics
16
+ - **THEN** system SHALL use the name field as fallback identifier for MQA API query
17
+
18
+ #### Scenario: Dataset not found
19
+ - **GIVEN** an invalid or non-existent dataset ID
20
+ - **WHEN** user requests quality metrics
21
+ - **THEN** system SHALL return clear error message indicating dataset not found
22
+
23
+ #### Scenario: MQA API unavailable
24
+ - **GIVEN** data.europa.eu MQA API is unavailable or returns error
25
+ - **WHEN** user requests quality metrics
26
+ - **THEN** system SHALL return clear error message indicating MQA service unavailability
27
+
28
+ ### Requirement: Server Validation
29
+ The system SHALL restrict MQA quality queries to dati.gov.it server only.
30
+
31
+ #### Scenario: Valid dati.gov.it server
32
+ - **GIVEN** server_url is "https://www.dati.gov.it/opendata" or "https://dati.gov.it/opendata"
33
+ - **WHEN** user requests quality metrics
34
+ - **THEN** system SHALL proceed with MQA query
35
+
36
+ #### Scenario: Invalid server URL
37
+ - **GIVEN** server_url is not dati.gov.it (e.g., "https://catalog.data.gov")
38
+ - **WHEN** user requests quality metrics
39
+ - **THEN** system SHALL reject request with error message explaining MQA is only available for dati.gov.it
40
+
41
+ ### Requirement: Output Formats
42
+ The system SHALL support both markdown and JSON output formats for quality metrics.
43
+
44
+ #### Scenario: Markdown format (default)
45
+ - **GIVEN** user does not specify response_format or specifies "markdown"
46
+ - **WHEN** quality metrics are retrieved
47
+ - **THEN** system SHALL return human-readable markdown with:
48
+ - Overall quality score
49
+ - Breakdown by dimension (accessibility, reusability, interoperability, findability)
50
+ - Key findings and recommendations
51
+
52
+ #### Scenario: JSON format
53
+ - **GIVEN** user specifies response_format as "json"
54
+ - **WHEN** quality metrics are retrieved
55
+ - **THEN** system SHALL return complete MQA API response as structured JSON
56
+
57
+ ### Requirement: Tool Parameters
58
+ The system SHALL accept the following parameters for the MQA quality tool:
59
+ - server_url (required): Base URL of dati.gov.it portal
60
+ - dataset_id (required): Dataset ID or name
61
+ - response_format (optional): "markdown" (default) or "json"
62
+
63
+ #### Scenario: Minimal parameters
64
+ - **GIVEN** user provides only server_url and dataset_id
65
+ - **WHEN** tool is invoked
66
+ - **THEN** system SHALL use default markdown format
67
+
68
+ #### Scenario: All parameters specified
69
+ - **GIVEN** user provides server_url, dataset_id, and response_format
70
+ - **WHEN** tool is invoked
71
+ - **THEN** system SHALL use specified format for output
@@ -0,0 +1,29 @@
1
+ # Implementation Tasks
2
+
3
+ ## 1. Core Implementation
4
+ - [x] 1.1 Create `src/tools/quality.ts` with `ckan_get_mqa_quality` tool handler
5
+ - [x] 1.2 Implement server URL validation (dati.gov.it only)
6
+ - [x] 1.3 Add CKAN package_show call to extract identifier field
7
+ - [x] 1.4 Add MQA API client (https://data.europa.eu/api/mqa/cache/datasets/{id})
8
+ - [x] 1.5 Implement markdown and JSON formatters for quality metrics
9
+ - [x] 1.6 Register tool in `src/server.ts`
10
+
11
+ ## 2. Testing
12
+ - [x] 2.1 Create mock fixtures for CKAN package_show response
13
+ - [x] 2.2 Create mock fixtures for MQA API response
14
+ - [x] 2.3 Write integration tests for successful quality retrieval
15
+ - [x] 2.4 Write tests for error scenarios (invalid server, dataset not found, MQA API unavailable)
16
+ - [x] 2.5 Write tests for fallback from identifier to name field
17
+ - [x] 2.6 Verify test coverage matches project standards
18
+
19
+ ## 3. Documentation
20
+ - [x] 3.1 Add tool description to README.md
21
+ - [x] 3.2 Add usage examples to EXAMPLES.md
22
+ - [x] 3.3 Document server restriction (dati.gov.it only)
23
+ - [x] 3.4 Document quality metrics structure (score, accessibility, reusability, interoperability, findability)
24
+
25
+ ## 4. Validation
26
+ - [x] 4.1 Run full test suite (npm test) - 212 tests passing
27
+ - [x] 4.2 Test manually with real dati.gov.it dataset
28
+ - [x] 4.3 Verify error handling for non-dati.gov.it servers
29
+ - [x] 4.4 Build project successfully (npm run build)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aborruso/ckan-mcp-server",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
4
4
  "description": "MCP server for interacting with CKAN open data portals",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",