@aborruso/ckan-mcp-server 0.4.8 โ†’ 0.4.10

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/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # CKAN MCP Server
2
-
3
1
  [![npm version](https://img.shields.io/npm/v/@aborruso/ckan-mcp-server)](https://www.npmjs.com/package/@aborruso/ckan-mcp-server)
2
+ [![GitHub](https://img.shields.io/badge/github-aborruso%2Fckan--mcp--server-blue?logo=github)](https://github.com/aborruso/ckan-mcp-server)
3
+ [![deepwiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/aborruso/ckan-mcp-server)
4
+
5
+ # CKAN MCP Server
4
6
 
5
7
  MCP (Model Context Protocol) server for interacting with CKAN-based open data portals.
6
8
 
@@ -14,7 +16,14 @@ MCP (Model Context Protocol) server for interacting with CKAN-based open data po
14
16
  - ๐ŸŽจ Output in Markdown or JSON format
15
17
  - โšก Pagination and faceting support
16
18
  - ๐Ÿ“„ MCP Resource Templates for direct data access
17
- - ๐Ÿงช Comprehensive test suite (120 tests, 100% passing)
19
+ - ๐Ÿงญ Guided MCP prompts for common workflows
20
+ - ๐Ÿงช Test suite with 184 tests (100% passing)
21
+
22
+ ---
23
+
24
+ ๐Ÿ‘‰ If you want to dive deeper, the [**AI-generated DeepWiki**](https://deepwiki.com/aborruso/ckan-mcp-server) is very well done.
25
+
26
+ ---
18
27
 
19
28
  ## Installation
20
29
 
@@ -36,7 +45,7 @@ npm install
36
45
  # Build with esbuild (fast, ~4ms)
37
46
  npm run build
38
47
 
39
- # Run tests (120 tests)
48
+ # Run tests (184 tests)
40
49
  npm test
41
50
  ```
42
51
 
@@ -202,9 +211,32 @@ ckan://demo.ckan.org/resource/abc-123
202
211
  ckan://data.gov/organization/sample-org
203
212
  ```
204
213
 
214
+ ## Guided Prompts
215
+
216
+ Prompt templates that guide users through common CKAN workflows:
217
+
218
+ - **ckan-search-by-theme**: Find a theme/group and list datasets under it
219
+ - **ckan-search-by-organization**: Discover an organization and list its datasets
220
+ - **ckan-search-by-format**: Find datasets by resource format (CSV/JSON/etc.)
221
+ - **ckan-recent-datasets**: List recently updated datasets
222
+ - **ckan-analyze-dataset**: Inspect dataset metadata and explore DataStore resources
223
+
224
+ Example (retrieve a prompt by name with args):
225
+
226
+ ```json
227
+ {
228
+ "name": "ckan-search-by-theme",
229
+ "arguments": {
230
+ "server_url": "https://www.dati.gov.it/opendata",
231
+ "theme": "ambiente",
232
+ "rows": 10
233
+ }
234
+ }
235
+ ```
236
+
205
237
  ## Usage Examples
206
238
 
207
- ### Search datasets on dati.gov.it
239
+ ### Search datasets on dati.gov.it (natural language: "search for population datasets")
208
240
 
209
241
  ```typescript
210
242
  ckan_package_search({
@@ -214,7 +246,7 @@ ckan_package_search({
214
246
  })
215
247
  ```
216
248
 
217
- ### Force text-field parser for long OR queries
249
+ ### Force text-field parser for long OR queries (natural language: "find hotel or accommodation datasets")
218
250
 
219
251
  ```typescript
220
252
  ckan_package_search({
@@ -225,7 +257,7 @@ ckan_package_search({
225
257
  })
226
258
  ```
227
259
 
228
- ### Rank datasets by relevance
260
+ ### Rank datasets by relevance (natural language: "find most relevant datasets about urban mobility")
229
261
 
230
262
  ```typescript
231
263
  ckan_find_relevant_datasets({
@@ -235,7 +267,7 @@ ckan_find_relevant_datasets({
235
267
  })
236
268
  ```
237
269
 
238
- ### Filter by organization
270
+ ### Filter by organization (natural language: "show recent datasets from Sicilian Region")
239
271
 
240
272
  ```typescript
241
273
  ckan_package_search({
@@ -245,7 +277,7 @@ ckan_package_search({
245
277
  })
246
278
  ```
247
279
 
248
- ### Search organizations with wildcard
280
+ ### Search organizations with wildcard (natural language: "find all organizations with health/salute in name")
249
281
 
250
282
  ```typescript
251
283
  // Find all organizations containing "salute" in the name
@@ -258,7 +290,7 @@ ckan_package_search({
258
290
  })
259
291
  ```
260
292
 
261
- ### Get statistics with faceting
293
+ ### Get statistics with faceting (natural language: "show statistics by organization, tags and format")
262
294
 
263
295
  ```typescript
264
296
  ckan_package_search({
@@ -287,7 +319,7 @@ ckan_group_search({
287
319
  })
288
320
  ```
289
321
 
290
- ### DataStore Query
322
+ ### DataStore Query (natural language: "query tabular data filtering by region and year")
291
323
 
292
324
  ```typescript
293
325
  ckan_datastore_search({
@@ -299,7 +331,7 @@ ckan_datastore_search({
299
331
  })
300
332
  ```
301
333
 
302
- ### DataStore SQL Query
334
+ ### DataStore SQL Query (natural language: "count records by country with SQL")
303
335
 
304
336
  ```typescript
305
337
  ckan_datastore_search_sql({
@@ -365,7 +397,7 @@ fq: "metadata_modified:[2023-01-01T00:00:00Z TO *]"
365
397
 
366
398
  These real-world examples demonstrate powerful Solr query combinations tested on the Italian open data portal (dati.gov.it):
367
399
 
368
- #### 1. Fuzzy Search + Date Math + Boosting
400
+ #### 1. Fuzzy Search + Date Math + Boosting (natural language: "find healthcare datasets modified in last 6 months")
369
401
 
370
402
  Find healthcare datasets (tolerating spelling errors) modified in the last 6 months, prioritizing title matches:
371
403
 
@@ -387,7 +419,7 @@ ckan_package_search({
387
419
 
388
420
  **Results**: 871 datasets including hospital units, healthcare organizations, medical services
389
421
 
390
- #### 2. Proximity Search + Complex Boolean
422
+ #### 2. Proximity Search + Complex Boolean (natural language: "find air pollution datasets excluding water")
391
423
 
392
424
  Environmental datasets where "inquinamento" and "aria" (air pollution) appear close together, excluding water-related datasets:
393
425
 
@@ -409,7 +441,7 @@ ckan_package_search({
409
441
 
410
442
  **Results**: 306 datasets, primarily air quality monitoring from Milan (44) and Palermo (161), formats: XML (150), CSV (124), JSON (76)
411
443
 
412
- #### 3. Wildcard + Field Existence + Range Queries
444
+ #### 3. Wildcard + Field Existence + Range Queries (natural language: "regional datasets with many resources from last year")
413
445
 
414
446
  Regional datasets with at least 5 resources, published in the last year:
415
447
 
@@ -432,7 +464,7 @@ ckan_package_search({
432
464
 
433
465
  **Results**: 5,318 datasets, top contributors: Lombardy (3,012), Tuscany (1,151), Puglia (460)
434
466
 
435
- #### 4. Date Ranges + Exclusive Bounds
467
+ #### 4. Date Ranges + Exclusive Bounds (natural language: "ISTAT datasets with 10-50 resources from specific period")
436
468
 
437
469
  ISTAT datasets with moderate resource count (10-50), modified in specific date range:
438
470
 
@@ -497,10 +529,17 @@ ckan-mcp-server/
497
529
  โ”‚ โ”‚ โ”œโ”€โ”€ dataset.ts
498
530
  โ”‚ โ”‚ โ”œโ”€โ”€ resource.ts
499
531
  โ”‚ โ”‚ โ””โ”€โ”€ organization.ts
532
+ โ”‚ โ”œโ”€โ”€ prompts/ # MCP Guided Prompts
533
+ โ”‚ โ”‚ โ”œโ”€โ”€ index.ts
534
+ โ”‚ โ”‚ โ”œโ”€โ”€ theme.ts
535
+ โ”‚ โ”‚ โ”œโ”€โ”€ organization.ts
536
+ โ”‚ โ”‚ โ”œโ”€โ”€ format.ts
537
+ โ”‚ โ”‚ โ”œโ”€โ”€ recent.ts
538
+ โ”‚ โ”‚ โ””โ”€โ”€ dataset-analysis.ts
500
539
  โ”‚ โ””โ”€โ”€ transport/
501
540
  โ”‚ โ”œโ”€โ”€ stdio.ts # Stdio transport
502
541
  โ”‚ โ””โ”€โ”€ http.ts # HTTP transport
503
- โ”œโ”€โ”€ tests/ # Test suite (120 tests)
542
+ โ”œโ”€โ”€ tests/ # Test suite (184 tests)
504
543
  โ”œโ”€โ”€ dist/ # Compiled files (generated)
505
544
  โ”œโ”€โ”€ package.json
506
545
  โ””โ”€โ”€ README.md
@@ -523,12 +562,14 @@ npm run test:watch
523
562
  npm run test:coverage
524
563
  ```
525
564
 
526
- Test coverage target is 80%. Current test suite includes:
565
+ Current test coverage: ~39% (utils: 98%, tools: 15-20%).
527
566
 
528
- - Unit tests for utility functions (formatting, HTTP)
567
+ Test suite includes:
568
+ - Unit tests for utility functions (formatting, HTTP, URI parsing, URL generation)
529
569
  - Integration tests for MCP tools with mocked CKAN API responses
530
570
  - Mock fixtures for CKAN API success and error scenarios
531
571
 
572
+ Coverage is higher for utility modules and lower for tool handlers.
532
573
  See `tests/README.md` for detailed testing guidelines.
533
574
 
534
575
  ### Build
package/dist/index.js CHANGED
@@ -2222,11 +2222,247 @@ function registerAllResources(server2) {
2222
2222
  registerOrganizationResource(server2);
2223
2223
  }
2224
2224
 
2225
+ // src/prompts/theme.ts
2226
+ import { z as z8 } from "zod";
2227
+
2228
+ // src/prompts/types.ts
2229
+ var createTextPrompt = (text) => ({
2230
+ messages: [
2231
+ {
2232
+ role: "user",
2233
+ content: {
2234
+ type: "text",
2235
+ text
2236
+ }
2237
+ }
2238
+ ]
2239
+ });
2240
+
2241
+ // src/prompts/theme.ts
2242
+ var THEME_PROMPT_NAME = "ckan-search-by-theme";
2243
+ var buildThemePromptText = (serverUrl, theme, rows) => `# Guided search: datasets by theme
2244
+
2245
+ ## Step 1: Find the theme/group
2246
+ Use \`ckan_group_search\` to locate the group that matches the theme.
2247
+
2248
+ Example:
2249
+
2250
+ ckan_group_search({
2251
+ server_url: "${serverUrl}",
2252
+ pattern: "${theme}"
2253
+ })
2254
+
2255
+ ## Step 2: Search datasets in that group
2256
+ Use \`ckan_package_search\` with a group filter.
2257
+
2258
+ Example:
2259
+
2260
+ ckan_package_search({
2261
+ server_url: "${serverUrl}",
2262
+ fq: "groups:<group_name>",
2263
+ sort: "metadata_modified desc",
2264
+ rows: ${rows}
2265
+ })
2266
+
2267
+ ## If no group matches
2268
+ Fallback to a tag-based search using facets:
2269
+
2270
+ ckan_package_search({
2271
+ server_url: "${serverUrl}",
2272
+ q: "tags:${theme}",
2273
+ sort: "metadata_modified desc",
2274
+ rows: ${rows}
2275
+ })`;
2276
+ var registerThemePrompt = (server2) => {
2277
+ server2.registerPrompt(
2278
+ THEME_PROMPT_NAME,
2279
+ {
2280
+ title: "Search datasets by theme",
2281
+ description: "Guided prompt to discover a theme and search datasets under it.",
2282
+ argsSchema: {
2283
+ server_url: z8.string().url().describe("Base URL of the CKAN server"),
2284
+ theme: z8.string().min(1).describe("Theme or group name to search"),
2285
+ rows: z8.number().int().positive().default(10).describe("Max results to return")
2286
+ }
2287
+ },
2288
+ async ({ server_url, theme, rows }) => createTextPrompt(buildThemePromptText(server_url, theme, rows))
2289
+ );
2290
+ };
2291
+
2292
+ // src/prompts/organization.ts
2293
+ import { z as z9 } from "zod";
2294
+ var ORGANIZATION_PROMPT_NAME = "ckan-search-by-organization";
2295
+ var buildOrganizationPromptText = (serverUrl, organization, rows) => `# Guided search: datasets by organization
2296
+
2297
+ ## Step 1: Discover matching organizations
2298
+ Use \`ckan_package_search\` with an organization wildcard and facets:
2299
+
2300
+ ckan_package_search({
2301
+ server_url: "${serverUrl}",
2302
+ q: "organization:*${organization}*",
2303
+ rows: 0,
2304
+ facet_field: ["organization"],
2305
+ facet_limit: 50
2306
+ })
2307
+
2308
+ ## Step 2: Search datasets for the chosen organization
2309
+ Use \`ckan_package_search\` with a filter query:
2310
+
2311
+ ckan_package_search({
2312
+ server_url: "${serverUrl}",
2313
+ fq: "organization:<org-id>",
2314
+ sort: "metadata_modified desc",
2315
+ rows: ${rows}
2316
+ })`;
2317
+ var registerOrganizationPrompt = (server2) => {
2318
+ server2.registerPrompt(
2319
+ ORGANIZATION_PROMPT_NAME,
2320
+ {
2321
+ title: "Search datasets by organization",
2322
+ description: "Guided prompt to find a publisher and list its datasets.",
2323
+ argsSchema: {
2324
+ server_url: z9.string().url().describe("Base URL of the CKAN server"),
2325
+ organization: z9.string().min(1).describe("Organization name or keyword"),
2326
+ rows: z9.number().int().positive().default(10).describe("Max results to return")
2327
+ }
2328
+ },
2329
+ async ({ server_url, organization, rows }) => createTextPrompt(buildOrganizationPromptText(server_url, organization, rows))
2330
+ );
2331
+ };
2332
+
2333
+ // src/prompts/format.ts
2334
+ import { z as z10 } from "zod";
2335
+ var FORMAT_PROMPT_NAME = "ckan-search-by-format";
2336
+ var buildFormatPromptText = (serverUrl, format, rows) => `# Guided search: datasets by resource format
2337
+
2338
+ Use \`ckan_package_search\` with a format filter:
2339
+
2340
+ ckan_package_search({
2341
+ server_url: "${serverUrl}",
2342
+ fq: "res_format:${format}",
2343
+ sort: "metadata_modified desc",
2344
+ rows: ${rows}
2345
+ })
2346
+
2347
+ Tip: try uppercase (CSV/JSON) or common variants if results are sparse.`;
2348
+ var registerFormatPrompt = (server2) => {
2349
+ server2.registerPrompt(
2350
+ FORMAT_PROMPT_NAME,
2351
+ {
2352
+ title: "Search datasets by resource format",
2353
+ description: "Guided prompt to find datasets with a given resource format.",
2354
+ argsSchema: {
2355
+ server_url: z10.string().url().describe("Base URL of the CKAN server"),
2356
+ format: z10.string().min(1).describe("Resource format (e.g., CSV, JSON)"),
2357
+ rows: z10.number().int().positive().default(10).describe("Max results to return")
2358
+ }
2359
+ },
2360
+ async ({ server_url, format, rows }) => createTextPrompt(buildFormatPromptText(server_url, format, rows))
2361
+ );
2362
+ };
2363
+
2364
+ // src/prompts/recent.ts
2365
+ import { z as z11 } from "zod";
2366
+ var RECENT_PROMPT_NAME = "ckan-recent-datasets";
2367
+ var buildRecentPromptText = (serverUrl, rows) => `# Guided search: recent datasets
2368
+
2369
+ Use \`ckan_package_search\` sorted by modification date:
2370
+
2371
+ ckan_package_search({
2372
+ server_url: "${serverUrl}",
2373
+ q: "*:*",
2374
+ sort: "metadata_modified desc",
2375
+ rows: ${rows}
2376
+ })
2377
+
2378
+ Optional: add a date filter for the last N days:
2379
+
2380
+ ckan_package_search({
2381
+ server_url: "${serverUrl}",
2382
+ q: "metadata_modified:[NOW-30DAYS TO *]",
2383
+ sort: "metadata_modified desc",
2384
+ rows: ${rows}
2385
+ })`;
2386
+ var registerRecentPrompt = (server2) => {
2387
+ server2.registerPrompt(
2388
+ RECENT_PROMPT_NAME,
2389
+ {
2390
+ title: "Find recently updated datasets",
2391
+ description: "Guided prompt to list recently updated datasets on a CKAN portal.",
2392
+ argsSchema: {
2393
+ server_url: z11.string().url().describe("Base URL of the CKAN server"),
2394
+ rows: z11.number().int().positive().default(10).describe("Max results to return")
2395
+ }
2396
+ },
2397
+ async ({ server_url, rows }) => createTextPrompt(buildRecentPromptText(server_url, rows))
2398
+ );
2399
+ };
2400
+
2401
+ // src/prompts/dataset-analysis.ts
2402
+ import { z as z12 } from "zod";
2403
+ var DATASET_ANALYSIS_PROMPT_NAME = "ckan-analyze-dataset";
2404
+ var buildDatasetAnalysisPromptText = (serverUrl, id) => `# Guided analysis: dataset
2405
+
2406
+ ## Step 1: Get dataset metadata
2407
+ Use \`ckan_package_show\` to load full metadata and resources:
2408
+
2409
+ ckan_package_show({
2410
+ server_url: "${serverUrl}",
2411
+ id: "${id}"
2412
+ })
2413
+
2414
+ ## Step 2: Inspect resources
2415
+ For each resource, use \`ckan_resource_show\` to confirm fields like format, url, and datastore availability:
2416
+
2417
+ ckan_resource_show({
2418
+ server_url: "${serverUrl}",
2419
+ id: "<resource-id>"
2420
+ })
2421
+
2422
+ ## Step 3: Explore DataStore (if available)
2423
+ If a resource has \`datastore_active=true\`, use:
2424
+
2425
+ ckan_datastore_search({
2426
+ server_url: "${serverUrl}",
2427
+ resource_id: "<resource-id>",
2428
+ limit: 10
2429
+ })
2430
+
2431
+ For aggregates, use SQL:
2432
+
2433
+ ckan_datastore_search_sql({
2434
+ server_url: "${serverUrl}",
2435
+ sql: "SELECT * FROM "<resource-id>" LIMIT 10"
2436
+ })`;
2437
+ var registerDatasetAnalysisPrompt = (server2) => {
2438
+ server2.registerPrompt(
2439
+ DATASET_ANALYSIS_PROMPT_NAME,
2440
+ {
2441
+ title: "Analyze a dataset",
2442
+ description: "Guided prompt to inspect dataset metadata and explore DataStore tables.",
2443
+ argsSchema: {
2444
+ server_url: z12.string().url().describe("Base URL of the CKAN server"),
2445
+ id: z12.string().min(1).describe("Dataset id or name (CKAN package id)")
2446
+ }
2447
+ },
2448
+ async ({ server_url, id }) => createTextPrompt(buildDatasetAnalysisPromptText(server_url, id))
2449
+ );
2450
+ };
2451
+
2452
+ // src/prompts/index.ts
2453
+ var registerAllPrompts = (server2) => {
2454
+ registerThemePrompt(server2);
2455
+ registerOrganizationPrompt(server2);
2456
+ registerFormatPrompt(server2);
2457
+ registerRecentPrompt(server2);
2458
+ registerDatasetAnalysisPrompt(server2);
2459
+ };
2460
+
2225
2461
  // src/server.ts
2226
2462
  function createServer() {
2227
2463
  return new McpServer({
2228
2464
  name: "ckan-mcp-server",
2229
- version: "0.4.8"
2465
+ version: "0.4.10"
2230
2466
  });
2231
2467
  }
2232
2468
  function registerAll(server2) {
@@ -2237,6 +2473,7 @@ function registerAll(server2) {
2237
2473
  registerTagTools(server2);
2238
2474
  registerGroupTools(server2);
2239
2475
  registerAllResources(server2);
2476
+ registerAllPrompts(server2);
2240
2477
  }
2241
2478
 
2242
2479
  // src/transport/stdio.ts