@aborruso/ckan-mcp-server 0.4.13 → 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/CLAUDE.md CHANGED
@@ -41,7 +41,7 @@ The server exposes MCP tools for:
41
41
  # Build project (uses esbuild - fast and lightweight)
42
42
  npm run build
43
43
 
44
- # Run test suite (179 tests - unit + integration)
44
+ # Run test suite (191 tests - unit + integration)
45
45
  npm test
46
46
 
47
47
  # Watch mode for tests during development
@@ -73,7 +73,7 @@ npm run deploy # Deploy to Cloudflare Workers
73
73
  The project uses **esbuild** for compilation and **vitest** for testing:
74
74
 
75
75
  - **Build**: Ultra-fast builds (milliseconds instead of minutes)
76
- - **Tests**: 179 tests (unit + integration) with 100% success rate
76
+ - **Tests**: 191 tests (unit + integration) with 100% success rate
77
77
  - **Coverage**: ~39% overall (utils: 98%, tools: 15-20%) - available via vitest with v8 coverage engine
78
78
 
79
79
  The `build:tsc` script is available as a fallback but can cause memory issues in some environments (particularly WSL). Always use `npm run build` which uses esbuild.
@@ -88,12 +88,12 @@ The project has a comprehensive test suite using **Vitest**:
88
88
  tests/
89
89
  ├── unit/
90
90
  │ ├── formatting.test.ts # Utility functions (19 tests)
91
- │ ├── http.test.ts # HTTP client (6 tests)
91
+ │ ├── http.test.ts # HTTP client (11 tests)
92
92
  │ └── uri.test.ts # URI parsing (11 tests)
93
93
  ├── integration/
94
- │ ├── package.test.ts # Package tools (29 tests)
94
+ │ ├── package.test.ts # Package tools (31 tests)
95
95
  │ ├── organization.test.ts # Organization tools (6 tests)
96
- │ ├── datastore.test.ts # DataStore tools (17 tests)
96
+ │ ├── datastore.test.ts # DataStore tools (19 tests)
97
97
  │ ├── resources.test.ts # MCP Resources (11 tests)
98
98
  │ └── status.test.ts # Status tools (2 tests)
99
99
  └── fixtures/
@@ -101,7 +101,7 @@ tests/
101
101
  └── errors/ # Error scenario mocks
102
102
  ```
103
103
 
104
- **Test Coverage**: 179 tests total (85 unit + 94 integration)
104
+ **Test Coverage**: 191 tests total (117 unit + 74 integration)
105
105
 
106
106
  When making changes:
107
107
  1. Run tests before committing: `npm test`
@@ -341,7 +341,7 @@ To test with Claude Desktop, add the MCP configuration to the config file.
341
341
  - Direct data access for datasets, resources, organizations
342
342
 
343
343
  **v0.2.0 (2026-01-08)**: Comprehensive test suite
344
- - 179 tests (unit + integration)
344
+ - 191 tests (unit + integration)
345
345
  - ~39% code coverage (utils well-tested, tools improving)
346
346
 
347
347
  **v0.1.0 (2026-01-08)**: Modular refactoring
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,50 @@
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
+
34
+ ## 2026-01-22
35
+
36
+ ### Date Query Auto-Conversion (v0.4.14)
37
+
38
+ - **Feature**: Auto-convert NOW-based date math for `modified` and `issued` fields
39
+ - **Problem**: CKAN Solr supports `NOW-XDAYS` syntax only on `metadata_modified` and `metadata_created` fields
40
+ - **Solution**: New `convertDateMathForUnsupportedFields()` automatically converts queries like `modified:[NOW-30DAYS TO NOW]` to ISO dates `modified:[2025-12-23T... TO 2026-01-22T...]`
41
+ - **Supported fields**: `modified`, `issued` (auto-converted) | `metadata_modified`, `metadata_created` (native NOW support)
42
+ - **Supported units**: DAYS, MONTHS, YEARS (singular and plural forms)
43
+ - **Tests**: +10 unit tests (201 total, all passing)
44
+ - **Documentation**: Updated tool description with NOW syntax limitations and examples
45
+ - **Files**: `src/utils/search.ts`, `src/tools/package.ts`, `tests/unit/search.test.ts`
46
+ - **No breaking changes**: Backward compatible - existing queries work unchanged
47
+
3
48
  ## 2026-01-19
4
49
 
5
50
  ### Search Parser Escaping
package/PRD.md CHANGED
@@ -214,7 +214,7 @@ An MCP server that exposes tools to interact with CKAN API v3, enabling AI agent
214
214
  - `wrangler@^4.58.0` - Cloudflare Workers CLI
215
215
 
216
216
  **Test Framework**:
217
- - `vitest@^4.0.16` - Test runner (190 tests, 100% passing)
217
+ - `vitest@^4.0.16` - Test runner (191 tests, 100% passing)
218
218
 
219
219
  ### 4.2 Architecture Diagram
220
220
 
@@ -825,7 +825,7 @@ ckan_package_search({
825
825
  ### 10.3 Testing & Quality
826
826
 
827
827
  ✅ **Current State**:
828
- - 190 unit and integration tests (100% passing)
828
+ - 191 unit and integration tests (100% passing)
829
829
  - vitest test runner
830
830
  - Coverage for all 13 tools
831
831
  - Fixtures for offline testing
@@ -860,7 +860,7 @@ ckan_package_search({
860
860
  - **Memory Usage**: < 50MB runtime (Node.js), Workers limits apply
861
861
  - **Response Time**: < 30s (CKAN API timeout), < 10s (Workers)
862
862
  - **Cold Start**: < 60ms (Cloudflare Workers)
863
- - **Test Coverage**: 190 tests (100% passing)
863
+ - **Test Coverage**: 191 tests (100% passing)
864
864
 
865
865
  ### 11.2 Distribution Metrics
866
866
 
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 186 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 (186 tests)
48
+ # Run tests (212 tests)
49
49
  npm test
50
50
  ```
51
51
 
@@ -117,22 +117,29 @@ Then add to `claude_desktop_config.json`:
117
117
  }
118
118
  ```
119
119
 
120
- #### Option 2: Local Installation (Optional)
120
+ #### Option 2: Local Project Installation (Optional)
121
121
 
122
- If you installed locally (see Installation), use this config:
122
+ If you want to install the server in a specific project:
123
+
124
+ ```bash
125
+ cd your-project
126
+ npm install @aborruso/ckan-mcp-server
127
+ ```
128
+
129
+ Then add to `claude_desktop_config.json`:
123
130
 
124
131
  ```json
125
132
  {
126
133
  "mcpServers": {
127
134
  "ckan": {
128
- "command": "node",
129
- "args": ["/absolute/path/to/project/node_modules/@username/ckan-mcp-server/dist/index.js"]
135
+ "command": "npx",
136
+ "args": ["@aborruso/ckan-mcp-server"]
130
137
  }
131
138
  }
132
139
  }
133
140
  ```
134
141
 
135
- Replace `/absolute/path/to/project` with your actual project path.
142
+ **Note**: `npx` will use the locally installed package in `node_modules`. Make sure to install the package first.
136
143
 
137
144
  #### Option 3: From Source
138
145
 
@@ -202,6 +209,10 @@ These guides are based on a public demo server, which has a limit of 100,000 cal
202
209
  - **ckan_group_show**: Show group details
203
210
  - **ckan_group_search**: Search groups by name
204
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
+
205
216
  ### Utilities
206
217
 
207
218
  - **ckan_status_show**: Verify server status
@@ -559,7 +570,7 @@ ckan-mcp-server/
559
570
  │ └── transport/
560
571
  │ ├── stdio.ts # Stdio transport
561
572
  │ └── http.ts # HTTP transport
562
- ├── tests/ # Test suite (184 tests)
573
+ ├── tests/ # Test suite (212 tests)
563
574
  ├── dist/ # Compiled files (generated)
564
575
  ├── package.json
565
576
  └── README.md
package/dist/index.js CHANGED
@@ -168,6 +168,31 @@ function isFieldedQuery(query) {
168
168
  function escapeSolrQuery(query) {
169
169
  return query.replace(SOLR_SPECIAL_CHARS, "\\$&");
170
170
  }
171
+ function convertDateMathForUnsupportedFields(query) {
172
+ const now = /* @__PURE__ */ new Date();
173
+ const nowIso = now.toISOString();
174
+ const pattern = /\b(?!metadata_)(modified|issued):\[NOW-(\d+)(DAYS?|MONTHS?|YEARS?)\s+TO\s+NOW\]/gi;
175
+ return query.replace(pattern, (match, field, amount, unit) => {
176
+ const amountNum = parseInt(amount, 10);
177
+ const startDate = new Date(now);
178
+ const normalizedUnit = unit.toLowerCase().replace(/s$/, "");
179
+ switch (normalizedUnit) {
180
+ case "day":
181
+ startDate.setDate(startDate.getDate() - amountNum);
182
+ break;
183
+ case "month":
184
+ startDate.setMonth(startDate.getMonth() - amountNum);
185
+ break;
186
+ case "year":
187
+ startDate.setFullYear(startDate.getFullYear() - amountNum);
188
+ break;
189
+ default:
190
+ return match;
191
+ }
192
+ const startIso = startDate.toISOString();
193
+ return `${field}:[${startIso} TO ${nowIso}]`;
194
+ });
195
+ }
171
196
  function resolveSearchQuery(serverUrl, query, parserOverride) {
172
197
  const portalSearchConfig = getPortalSearchConfig(serverUrl);
173
198
  const portalForce = portalSearchConfig.force_text_field ?? false;
@@ -180,7 +205,8 @@ function resolveSearchQuery(serverUrl, query, parserOverride) {
180
205
  const trimmedQuery = query.trim();
181
206
  forceTextField = trimmedQuery !== DEFAULT_SEARCH_QUERY && !isFieldedQuery(trimmedQuery);
182
207
  }
183
- const effectiveQuery = forceTextField ? `text:(${escapeSolrQuery(query)})` : query;
208
+ let effectiveQuery = forceTextField ? `text:(${escapeSolrQuery(query)})` : query;
209
+ effectiveQuery = convertDateMathForUnsupportedFields(effectiveQuery);
184
210
  return { effectiveQuery, forcedTextField: forceTextField };
185
211
  }
186
212
 
@@ -333,6 +359,9 @@ Query Syntax (parameter q):
333
359
  - NOW/DAY, NOW/MONTH (round down)
334
360
  - Combined: "metadata_modified:[NOW-2MONTHS TO NOW]"
335
361
  - Example: "metadata_created:[NOW-1YEAR TO *]"
362
+ - IMPORTANT: NOW syntax works on metadata_modified and metadata_created fields
363
+ - For 'modified' and 'issued' fields, NOW syntax is auto-converted to ISO dates
364
+ - Manual ISO dates always work: "modified:[2026-01-15T00:00:00Z TO *]"
336
365
 
337
366
  Field existence:
338
367
  - Exists: "field:*" or "field:[* TO *]"
@@ -351,6 +380,7 @@ Examples:
351
380
  - Proximity: { q: "notes:"open data"~3" }
352
381
  - Date range: { q: "metadata_modified:[2024-01-01T00:00:00Z TO 2024-12-31T23:59:59Z]" }
353
382
  - Date math: { q: "metadata_modified:[NOW-6MONTHS TO *]" }
383
+ - Date math (auto-converted): { q: "modified:[NOW-30DAYS TO NOW]" }
354
384
  - Field exists: { q: "organization:* AND num_resources:[1 TO *]" }
355
385
  - Boosting: { q: "title:climate^2 OR notes:climate" }
356
386
  - Filter org: { fq: "organization:regione-siciliana" }
@@ -2071,6 +2101,146 @@ Returns:
2071
2101
  );
2072
2102
  }
2073
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
+
2074
2244
  // src/resources/dataset.ts
2075
2245
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2076
2246
 
@@ -2340,7 +2510,7 @@ function registerAllResources(server2) {
2340
2510
  }
2341
2511
 
2342
2512
  // src/prompts/theme.ts
2343
- import { z as z8 } from "zod";
2513
+ import { z as z9 } from "zod";
2344
2514
 
2345
2515
  // src/prompts/types.ts
2346
2516
  var createTextPrompt = (text) => ({
@@ -2397,9 +2567,9 @@ var registerThemePrompt = (server2) => {
2397
2567
  title: "Search datasets by theme",
2398
2568
  description: "Guided prompt to discover a theme and search datasets under it.",
2399
2569
  argsSchema: {
2400
- server_url: z8.string().url().describe("Base URL of the CKAN server"),
2401
- theme: z8.string().min(1).describe("Theme or group name to search"),
2402
- 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")
2403
2573
  }
2404
2574
  },
2405
2575
  async ({ server_url, theme, rows }) => createTextPrompt(buildThemePromptText(server_url, theme, rows))
@@ -2407,7 +2577,7 @@ var registerThemePrompt = (server2) => {
2407
2577
  };
2408
2578
 
2409
2579
  // src/prompts/organization.ts
2410
- import { z as z9 } from "zod";
2580
+ import { z as z10 } from "zod";
2411
2581
  var ORGANIZATION_PROMPT_NAME = "ckan-search-by-organization";
2412
2582
  var buildOrganizationPromptText = (serverUrl, organization, rows) => `# Guided search: datasets by organization
2413
2583
 
@@ -2438,9 +2608,9 @@ var registerOrganizationPrompt = (server2) => {
2438
2608
  title: "Search datasets by organization",
2439
2609
  description: "Guided prompt to find a publisher and list its datasets.",
2440
2610
  argsSchema: {
2441
- server_url: z9.string().url().describe("Base URL of the CKAN server"),
2442
- organization: z9.string().min(1).describe("Organization name or keyword"),
2443
- 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")
2444
2614
  }
2445
2615
  },
2446
2616
  async ({ server_url, organization, rows }) => createTextPrompt(buildOrganizationPromptText(server_url, organization, rows))
@@ -2448,7 +2618,7 @@ var registerOrganizationPrompt = (server2) => {
2448
2618
  };
2449
2619
 
2450
2620
  // src/prompts/format.ts
2451
- import { z as z10 } from "zod";
2621
+ import { z as z11 } from "zod";
2452
2622
  var FORMAT_PROMPT_NAME = "ckan-search-by-format";
2453
2623
  var buildFormatPromptText = (serverUrl, format, rows) => `# Guided search: datasets by resource format
2454
2624
 
@@ -2469,9 +2639,9 @@ var registerFormatPrompt = (server2) => {
2469
2639
  title: "Search datasets by resource format",
2470
2640
  description: "Guided prompt to find datasets with a given resource format.",
2471
2641
  argsSchema: {
2472
- server_url: z10.string().url().describe("Base URL of the CKAN server"),
2473
- format: z10.string().min(1).describe("Resource format (e.g., CSV, JSON)"),
2474
- 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")
2475
2645
  }
2476
2646
  },
2477
2647
  async ({ server_url, format, rows }) => createTextPrompt(buildFormatPromptText(server_url, format, rows))
@@ -2479,7 +2649,7 @@ var registerFormatPrompt = (server2) => {
2479
2649
  };
2480
2650
 
2481
2651
  // src/prompts/recent.ts
2482
- import { z as z11 } from "zod";
2652
+ import { z as z12 } from "zod";
2483
2653
  var RECENT_PROMPT_NAME = "ckan-recent-datasets";
2484
2654
  var buildRecentPromptText = (serverUrl, rows) => `# Guided search: recent datasets
2485
2655
 
@@ -2507,8 +2677,8 @@ var registerRecentPrompt = (server2) => {
2507
2677
  title: "Find recently updated datasets",
2508
2678
  description: "Guided prompt to list recently updated datasets on a CKAN portal.",
2509
2679
  argsSchema: {
2510
- server_url: z11.string().url().describe("Base URL of the CKAN server"),
2511
- 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")
2512
2682
  }
2513
2683
  },
2514
2684
  async ({ server_url, rows }) => createTextPrompt(buildRecentPromptText(server_url, rows))
@@ -2516,7 +2686,7 @@ var registerRecentPrompt = (server2) => {
2516
2686
  };
2517
2687
 
2518
2688
  // src/prompts/dataset-analysis.ts
2519
- import { z as z12 } from "zod";
2689
+ import { z as z13 } from "zod";
2520
2690
  var DATASET_ANALYSIS_PROMPT_NAME = "ckan-analyze-dataset";
2521
2691
  var buildDatasetAnalysisPromptText = (serverUrl, id) => `# Guided analysis: dataset
2522
2692
 
@@ -2558,8 +2728,8 @@ var registerDatasetAnalysisPrompt = (server2) => {
2558
2728
  title: "Analyze a dataset",
2559
2729
  description: "Guided prompt to inspect dataset metadata and explore DataStore tables.",
2560
2730
  argsSchema: {
2561
- server_url: z12.string().url().describe("Base URL of the CKAN server"),
2562
- 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)")
2563
2733
  }
2564
2734
  },
2565
2735
  async ({ server_url, id }) => createTextPrompt(buildDatasetAnalysisPromptText(server_url, id))
@@ -2579,7 +2749,7 @@ var registerAllPrompts = (server2) => {
2579
2749
  function createServer() {
2580
2750
  return new McpServer({
2581
2751
  name: "ckan-mcp-server",
2582
- version: "0.4.13"
2752
+ version: "0.4.15"
2583
2753
  });
2584
2754
  }
2585
2755
  function registerAll(server2) {
@@ -2589,6 +2759,7 @@ function registerAll(server2) {
2589
2759
  registerStatusTools(server2);
2590
2760
  registerTagTools(server2);
2591
2761
  registerGroupTools(server2);
2762
+ registerQualityTools(server2);
2592
2763
  registerAllResources(server2);
2593
2764
  registerAllPrompts(server2);
2594
2765
  }