@aborruso/ckan-mcp-server 0.4.10 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LOG.md +57 -0
  2. package/NOTICE.md +77 -0
  3. package/PRD.md +6 -6
  4. package/README.md +35 -14
  5. package/dist/index.js +124 -7
  6. package/dist/worker.js +56 -56
  7. package/openspec/changes/add-ckan-host-allowlist-env/design.md +38 -0
  8. package/openspec/changes/add-ckan-host-allowlist-env/proposal.md +16 -0
  9. package/openspec/changes/add-ckan-host-allowlist-env/specs/ckan-request-allowlist/spec.md +15 -0
  10. package/openspec/changes/add-ckan-host-allowlist-env/specs/cloudflare-deployment/spec.md +11 -0
  11. package/openspec/changes/add-ckan-host-allowlist-env/tasks.md +12 -0
  12. package/openspec/changes/add-escape-text-query/proposal.md +12 -0
  13. package/openspec/changes/add-escape-text-query/specs/ckan-search/spec.md +11 -0
  14. package/openspec/changes/add-escape-text-query/tasks.md +8 -0
  15. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/proposal.md +13 -0
  16. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/specs/mcp-resources/spec.md +38 -0
  17. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/tasks.md +10 -0
  18. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/proposal.md +13 -0
  19. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/specs/repository-metadata/spec.md +14 -0
  20. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/tasks.md +12 -0
  21. package/openspec/specs/ckan-insights/spec.md +5 -1
  22. package/openspec/specs/ckan-search/spec.md +16 -0
  23. package/openspec/specs/mcp-prompts/spec.md +26 -0
  24. package/openspec/specs/mcp-resources/spec.md +30 -4
  25. package/openspec/specs/repository-metadata/spec.md +19 -0
  26. package/package.json +1 -1
  27. package/private/commenti-privati.yaml +14 -0
  28. /package/openspec/changes/{add-mcp-prompts → archive/2026-01-15-add-mcp-prompts}/proposal.md +0 -0
  29. /package/openspec/changes/{add-mcp-prompts → archive/2026-01-15-add-mcp-prompts}/specs/mcp-prompts/spec.md +0 -0
  30. /package/openspec/changes/{add-mcp-prompts → archive/2026-01-15-add-mcp-prompts}/tasks.md +0 -0
  31. /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/proposal.md +0 -0
  32. /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/specs/ckan-insights/spec.md +0 -0
  33. /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/specs/ckan-search/spec.md +0 -0
  34. /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/tasks.md +0 -0
package/LOG.md CHANGED
@@ -1,7 +1,64 @@
1
1
  # LOG
2
2
 
3
+ ## 2026-01-19
4
+
5
+ ### Search Parser Escaping
6
+ - **Fix**: Escape Solr special characters when forcing `text:(...)` queries
7
+ - **Tests**: Added unit coverage for escaping and forced parser output
8
+ - **Files**: `src/utils/search.ts`, `tests/unit/search.test.ts`, `README.md`
9
+
10
+ ## 2026-01-17
11
+
12
+ ### Documentation Alignment
13
+ - **Test counts updated**: README.md (184→190), PRD.md (130→190)
14
+ - **Version updated**: PRD.md version 0.4.7→0.4.12
15
+ - **Date updated**: PRD.md last updated 2026-01-10→2026-01-17
16
+ - **Verification**: All 13 tools, 7 resources, 5 prompts implemented and documented
17
+ - **Files**: `README.md`, `PRD.md`
18
+
19
+ ### GitHub Templates
20
+ - **Issue templates**: Added bug report and feature request YAML forms
21
+ - Bug report: CKAN portal URL, steps, expected/actual, error, Node version
22
+ - Feature request: problem/use case, proposed solution, alternatives
23
+ - Auto-labels: `bug` and `enhancement`
24
+ - **Issue chooser**: Routes questions to Discussions Q&A
25
+ - **PR template**: Description, related issue, test/docs checklist
26
+ - **Files**: `.github/ISSUE_TEMPLATE/{bug_report,feature_request,config}.yml`, `.github/PULL_REQUEST_TEMPLATE.md`
27
+
28
+ ## 2026-01-16
29
+
30
+ ### Demo Video Preparation
31
+ - **Documentation**: Created complete demo video documentation suite in `docs/video/`
32
+ - `demo-script.md` - Commands and technical notes for 4 use cases
33
+ - `demo-expected-results.md` - Actual test results with data samples
34
+ - `demo-recording-guide.md` - Step-by-step recording guide with voiceover scripts
35
+ - `pre-recording-checklist.md` - Practical day-of checklist
36
+ - `demo-fallback-options.md` - Comprehensive fallback strategies for 8 scenarios
37
+ - `demo-timing-report.md` - Performance analysis and timing verification
38
+ - **Testing**: Verified all demo commands working with dati.gov.it
39
+ - Portal overview: 67,614 datasets, top 10 organizations (~5s)
40
+ - Targeted search: 263 Milano transport datasets (~10s)
41
+ - Dataset details: Complete metadata with CSV/JSON resources (~10s)
42
+ - DuckDB analysis: DESCRIBE, SUMMARIZE, SAMPLE all working (~8s total)
43
+ - **Target**: 5-7 minute video demonstrating MCP convenience for Italian open data
44
+ - **Status**: Ready for recording with high confidence level
45
+
3
46
  ## 2026-01-15
4
47
 
48
+ ### Version 0.4.12 - Dataset filter resources
49
+ - **Feature**: Added `ckan://{server}/.../datasets` resource templates for group, organization, tag, and format filters
50
+ - **Fix**: Map `ckan://` hostnames to portal API base URLs (e.g., dati.gov.it → /opendata)
51
+ - **Fix**: Format filtering now matches `res_format` and `distribution_format` (with case variants)
52
+ - **Docs**: Updated README and future ideas with new URI templates
53
+ - **Docs**: Updated `docs/proposta-spunti-datos-gob-es-mcp.md` marking resource templates as completed
54
+ - **Tests**: Added unit tests for dataset filter resource templates
55
+ - **Files**: New `src/resources/dataset-filters.ts`, updates in `src/resources/index.ts`, `src/worker.ts`
56
+
57
+ ### Version 0.4.11 - Prompt argument coercion
58
+ - **Fix**: Prompt arguments now coerce numeric strings (e.g., rows) for MCP prompt requests
59
+ - **Docs**: Updated evaluation notes for 0.4.11
60
+ - **No breaking changes**: Prompt names and outputs unchanged
61
+
5
62
  ### Version 0.4.10 - Guided MCP prompts
6
63
  - **Feature**: Added 5 guided MCP prompts (theme, organization, format, recent datasets, dataset analysis)
7
64
  - **Docs**: README and new `docs/prompts.md` updated with usage examples
package/NOTICE.md ADDED
@@ -0,0 +1,77 @@
1
+ # Third-Party Software Notices
2
+
3
+ This software incorporates material from third parties. The following sets forth attribution notices for third-party software that may be included in this software.
4
+
5
+ ## Production Dependencies
6
+
7
+ ### @modelcontextprotocol/sdk
8
+ - **License**: MIT License
9
+ - **Copyright**: Copyright (c) 2024 Anthropic, PBC
10
+ - **Repository**: https://github.com/modelcontextprotocol/typescript-sdk
11
+ - **Description**: Official TypeScript SDK for the Model Context Protocol (MCP)
12
+
13
+ ### axios
14
+ - **License**: MIT License
15
+ - **Copyright**: Copyright (c) 2014-present Matt Zabriskie & Collaborators
16
+ - **Repository**: https://github.com/axios/axios
17
+ - **Description**: Promise-based HTTP client for the browser and node.js
18
+
19
+ ### express
20
+ - **License**: MIT License
21
+ - **Copyright**: Copyright (c) 2009-2014 TJ Holowaychuk <tj@vision-media.ca>
22
+ - **Copyright**: Copyright (c) 2013-2014 Roman Shtylman <shtylman+expressjs@gmail.com>
23
+ - **Copyright**: Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
24
+ - **Repository**: https://github.com/expressjs/express
25
+ - **Description**: Fast, unopinionated, minimalist web framework for Node.js
26
+
27
+ ### zod
28
+ - **License**: MIT License
29
+ - **Copyright**: Copyright (c) 2025 Colin McDonnell
30
+ - **Repository**: https://github.com/colinhacks/zod
31
+ - **Description**: TypeScript-first schema validation with static type inference
32
+
33
+ ## Development Dependencies (Build Tools)
34
+
35
+ ### TypeScript
36
+ - **License**: Apache License 2.0
37
+ - **Copyright**: Copyright (c) Microsoft Corporation
38
+ - **Repository**: https://github.com/microsoft/TypeScript
39
+ - **Description**: TypeScript language compiler
40
+
41
+ ### esbuild
42
+ - **License**: MIT License
43
+ - **Copyright**: Copyright (c) 2020 Evan Wallace
44
+ - **Repository**: https://github.com/evanw/esbuild
45
+ - **Description**: An extremely fast JavaScript bundler
46
+
47
+ ### Vitest
48
+ - **License**: MIT License
49
+ - **Copyright**: Copyright (c) 2021-Present Anthony Fu, Matías Capeletto
50
+ - **Repository**: https://github.com/vitest-dev/vitest
51
+ - **Description**: Next generation testing framework
52
+
53
+ ### Wrangler
54
+ - **License**: MIT OR Apache-2.0 (dual license)
55
+ - **Copyright**: Copyright (c) 2017-2022 Cloudflare, Inc.
56
+ - **Repository**: https://github.com/cloudflare/workers-sdk
57
+ - **Description**: Cloudflare Workers CLI and deployment tool
58
+
59
+ ---
60
+
61
+ ## License Information
62
+
63
+ All production dependencies use permissive open-source licenses (MIT, ISC, BSD) that are fully compatible with this project's MIT License.
64
+
65
+ Full license texts for all dependencies are available in the respective `node_modules` directories and on their respective repository pages.
66
+
67
+ ## Compliance
68
+
69
+ This project complies with all license obligations of its dependencies:
70
+ - Attribution notices are preserved
71
+ - Original copyright statements are maintained
72
+ - License files are distributed with the software
73
+ - No modifications have been made to third-party code without proper attribution
74
+
75
+ ---
76
+
77
+ For questions about licensing, please contact: Andrea Borruso <aborruso@gmail.com>
package/PRD.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## CKAN MCP Server
4
4
 
5
- **Version**: 0.4.7
6
- **Last Updated**: 2026-01-10
7
- **Author**: onData
5
+ **Version**: 0.4.12
6
+ **Last Updated**: 2026-01-17
7
+ **Author**: onData
8
8
  **Status**: Production
9
9
 
10
10
  ---
@@ -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 (130 tests, 100% passing)
217
+ - `vitest@^4.0.16` - Test runner (190 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
- - 130 unit and integration tests (100% passing)
828
+ - 190 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**: 130 tests (100% passing)
863
+ - **Test Coverage**: 190 tests (100% passing)
864
864
 
865
865
  ### 11.2 Distribution Metrics
866
866
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
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)
2
+ [![GitHub](https://img.shields.io/badge/github-ondata%2Fckan--mcp--server-blue?logo=github)](https://github.com/ondata/ckan-mcp-server)
3
+ [![deepwiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ondata/ckan-mcp-server)
4
4
 
5
5
  # CKAN MCP Server
6
6
 
@@ -17,11 +17,11 @@ 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 184 tests (100% passing)
20
+ - 🧪 Test suite with 186 tests (100% passing)
21
21
 
22
22
  ---
23
23
 
24
- 👉 If you want to dive deeper, the [**AI-generated DeepWiki**](https://deepwiki.com/aborruso/ckan-mcp-server) is very well done.
24
+ 👉 If you want to dive deeper, the [**AI-generated DeepWiki**](https://deepwiki.com/ondata/ckan-mcp-server) is very well done.
25
25
 
26
26
  ---
27
27
 
@@ -45,7 +45,7 @@ npm install
45
45
  # Build with esbuild (fast, ~4ms)
46
46
  npm run build
47
47
 
48
- # Run tests (184 tests)
48
+ # Run tests (186 tests)
49
49
  npm test
50
50
  ```
51
51
 
@@ -87,6 +87,8 @@ Use the public Workers endpoint (no local install required):
87
87
 
88
88
  Want your own deployment? See [DEPLOYMENT.md](docs/DEPLOYMENT.md).
89
89
 
90
+ ## Guides
91
+
90
92
  ### Claude Desktop Configuration
91
93
 
92
94
  Configuration file location:
@@ -147,6 +149,8 @@ If you cloned the repository:
147
149
  }
148
150
  ```
149
151
 
152
+ [A detailed guide](https://github.com/ondata/ckan-mcp-server/discussions/4#discussion-9359684)
153
+
150
154
  #### Option 4: Cloudflare Workers (HTTP transport)
151
155
 
152
156
  Use the public Cloudflare Workers deployment (no local installation required):
@@ -165,6 +169,13 @@ Use the public Cloudflare Workers deployment (no local installation required):
165
169
 
166
170
  **Note**: This uses the public endpoint. You can also deploy your own Workers instance and use that URL instead.
167
171
 
172
+ ### Web Tools Configuration
173
+
174
+ - [ChatGPT web](docs/guide/chatgpt/chatgpt_web.md)
175
+ - [Claude web](docs/guide/claude/claude_web.md)
176
+
177
+ These guides are based on a public demo server, which has a limit of 100,000 calls per month. For reliable usage, it is recommended to install the CKAN MCP Server on your own machine.
178
+
168
179
  ## Available Tools
169
180
 
170
181
  ### Search and Discovery
@@ -172,13 +183,13 @@ Use the public Cloudflare Workers deployment (no local installation required):
172
183
  - **ckan_package_search**: Search datasets with Solr queries
173
184
  - **ckan_find_relevant_datasets**: Rank datasets by relevance score
174
185
  - **ckan_package_show**: Complete details of a dataset
175
- - **ckan_package_list**: List all datasets
176
186
  - **ckan_tag_list**: List tags with counts
177
187
 
178
188
  ### Organizations
179
189
 
180
190
  - **ckan_organization_list**: List all organizations
181
191
  - **ckan_organization_show**: Details of an organization
192
+ - **ckan_organization_search**: Search organizations by name
182
193
 
183
194
  ### DataStore
184
195
 
@@ -202,6 +213,10 @@ Direct data access via `ckan://` URI scheme:
202
213
  - `ckan://{server}/dataset/{id}` - Dataset metadata
203
214
  - `ckan://{server}/resource/{id}` - Resource metadata and download URL
204
215
  - `ckan://{server}/organization/{name}` - Organization details
216
+ - `ckan://{server}/group/{name}/datasets` - Datasets by group (theme)
217
+ - `ckan://{server}/organization/{name}/datasets` - Datasets by organization
218
+ - `ckan://{server}/tag/{name}/datasets` - Datasets by tag
219
+ - `ckan://{server}/format/{format}/datasets` - Datasets by resource format (res_format + distribution_format)
205
220
 
206
221
  Examples:
207
222
 
@@ -209,6 +224,10 @@ Examples:
209
224
  ckan://dati.gov.it/dataset/vaccini-covid
210
225
  ckan://demo.ckan.org/resource/abc-123
211
226
  ckan://data.gov/organization/sample-org
227
+ ckan://dati.gov.it/group/ambiente/datasets
228
+ ckan://dati.gov.it/organization/regione-toscana/datasets
229
+ ckan://dati.gov.it/tag/turismo/datasets
230
+ ckan://dati.gov.it/format/csv/datasets
212
231
  ```
213
232
 
214
233
  ## Guided Prompts
@@ -256,6 +275,7 @@ ckan_package_search({
256
275
  rows: 0
257
276
  })
258
277
  ```
278
+ Note: when `query_parser: "text"` is used, Solr special characters in the query are escaped automatically.
259
279
 
260
280
  ### Rank datasets by relevance (natural language: "find most relevant datasets about urban mobility")
261
281
 
@@ -354,15 +374,15 @@ Some of the main compatible portals:
354
374
 
355
375
  ### Portal View URL Templates
356
376
 
357
- Some CKAN portals expose non-standard web URLs for viewing datasets or organizations. To support those cases, this project ships with `src/portals.json`, which maps known portal API URLs (and aliases) to custom view URL templates.
377
+ Some CKAN portals expose non-standard web URLs for viewing datasets or organizations. To support those cases, this project ships with [`src/portals.json`](src/portals.json), which maps known portal API URLs (and aliases) to custom view URL templates.
358
378
 
359
379
  When generating a dataset or organization view link, the server:
360
380
 
361
- - matches the `server_url` against `api_url` and `api_url_aliases` in `src/portals.json`
381
+ - matches the `server_url` against `api_url` and `api_url_aliases` in [`src/portals.json`](src/portals.json)
362
382
  - uses the portal-specific `dataset_view_url` / `organization_view_url` template when available
363
383
  - falls back to the generic defaults (`{server_url}/dataset/{name}` and `{server_url}/organization/{name}`)
364
384
 
365
- You can extend `src/portals.json` by adding new entries under `portals` if a portal uses different web URL patterns.
385
+ You can extend [`src/portals.json`](src/portals.json) by adding new entries under `portals` if a portal uses different web URL patterns.
366
386
 
367
387
  ## Advanced Solr Queries
368
388
 
@@ -547,6 +567,8 @@ ckan-mcp-server/
547
567
 
548
568
  ## Development
549
569
 
570
+ This project uses [OpenSpec](https://github.com/Fission-AI/OpenSpec) to manage change proposals and keep specifications aligned with implementation.
571
+
550
572
  ### Testing
551
573
 
552
574
  The project uses **Vitest** for automated testing:
@@ -640,7 +662,9 @@ Contributions are welcome! Please:
640
662
 
641
663
  ## License
642
664
 
643
- MIT License - See LICENSE.txt file for complete details.
665
+ MIT License - See [LICENSE.txt](LICENSE.txt) for complete details.
666
+
667
+ Third-party attributions: See [NOTICE.md](NOTICE.md) for third-party software notices and information.
644
668
 
645
669
  ## Useful Links
646
670
 
@@ -650,10 +674,7 @@ MIT License - See LICENSE.txt file for complete details.
650
674
 
651
675
  ## Support
652
676
 
653
- For issues or questions:
654
-
655
- - Open an issue on GitHub
656
- - Contact onData: https://www.ondata.it/
677
+ For issues or questions, [open an issue on GitHub](https://github.com/ondata/ckan-mcp-server/issues/new/choose).
657
678
 
658
679
  ---
659
680
 
package/dist/index.js CHANGED
@@ -110,6 +110,13 @@ var portals_default = {
110
110
  function normalizeUrl(url) {
111
111
  return url.replace(/\/$/, "");
112
112
  }
113
+ function extractHostname(url) {
114
+ try {
115
+ return new URL(url).hostname;
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
113
120
  function getPortalConfig(serverUrl) {
114
121
  const cleanServerUrl = normalizeUrl(serverUrl);
115
122
  const portal = portals_default.portals.find((p) => {
@@ -129,6 +136,13 @@ function getPortalSearchConfig(serverUrl) {
129
136
  function normalizePortalUrl(serverUrl) {
130
137
  return normalizeUrl(serverUrl);
131
138
  }
139
+ function getPortalApiUrlForHostname(hostname) {
140
+ const portal = portals_default.portals.find((p) => {
141
+ const urls = [p.api_url, ...p.api_url_aliases || []];
142
+ return urls.some((url) => extractHostname(url) === hostname);
143
+ });
144
+ return portal ? normalizeUrl(portal.api_url) : null;
145
+ }
132
146
 
133
147
  // src/utils/url-generator.ts
134
148
  function getDatasetViewUrl(serverUrl, pkg) {
@@ -147,9 +161,13 @@ function getOrganizationViewUrl(serverUrl, org) {
147
161
  // src/utils/search.ts
148
162
  var DEFAULT_SEARCH_QUERY = "*:*";
149
163
  var FIELD_QUERY_PATTERN = /\b[a-zA-Z_][\w-]*:/;
164
+ var SOLR_SPECIAL_CHARS = /[+\-!(){}[\]^"~*?:\\/|&]/g;
150
165
  function isFieldedQuery(query) {
151
166
  return FIELD_QUERY_PATTERN.test(query);
152
167
  }
168
+ function escapeSolrQuery(query) {
169
+ return query.replace(SOLR_SPECIAL_CHARS, "\\$&");
170
+ }
153
171
  function resolveSearchQuery(serverUrl, query, parserOverride) {
154
172
  const portalSearchConfig = getPortalSearchConfig(serverUrl);
155
173
  const portalForce = portalSearchConfig.force_text_field ?? false;
@@ -162,7 +180,7 @@ function resolveSearchQuery(serverUrl, query, parserOverride) {
162
180
  const trimmedQuery = query.trim();
163
181
  forceTextField = trimmedQuery !== DEFAULT_SEARCH_QUERY && !isFieldedQuery(trimmedQuery);
164
182
  }
165
- const effectiveQuery = forceTextField ? `text:(${query})` : query;
183
+ const effectiveQuery = forceTextField ? `text:(${escapeSolrQuery(query)})` : query;
166
184
  return { effectiveQuery, forcedTextField: forceTextField };
167
185
  }
168
186
 
@@ -2073,7 +2091,7 @@ function parseCkanUri(uri) {
2073
2091
  if (!type || !id) {
2074
2092
  throw new Error("Invalid ckan:// URI: missing type or id");
2075
2093
  }
2076
- const server2 = `https://${hostname}`;
2094
+ const server2 = getPortalApiUrlForHostname(hostname) || `https://${hostname}`;
2077
2095
  return { server: server2, type, id };
2078
2096
  }
2079
2097
 
@@ -2215,11 +2233,110 @@ function registerOrganizationResource(server2) {
2215
2233
  );
2216
2234
  }
2217
2235
 
2236
+ // src/resources/dataset-filters.ts
2237
+ import { ResourceTemplate as ResourceTemplate4 } from "@modelcontextprotocol/sdk/server/mcp.js";
2238
+ var escapeSolrTerm = (value) => value.replace(/["\\]/g, "\\$&");
2239
+ var buildFormatFilter = (rawFormat) => {
2240
+ const format = rawFormat.trim();
2241
+ const variants = [format];
2242
+ const upper = format.toUpperCase();
2243
+ if (upper !== format) {
2244
+ variants.push(upper);
2245
+ }
2246
+ const fields = ["res_format", "distribution_format"];
2247
+ const clauses = fields.flatMap(
2248
+ (field) => variants.map((value) => `${field}:"${escapeSolrTerm(value)}"`)
2249
+ );
2250
+ return `(${clauses.join(" OR ")})`;
2251
+ };
2252
+ var registerDatasetFilterResource = (server2, config) => {
2253
+ server2.registerResource(
2254
+ config.name,
2255
+ new ResourceTemplate4(config.template, { list: void 0 }),
2256
+ {
2257
+ title: config.title,
2258
+ description: config.description,
2259
+ mimeType: "application/json"
2260
+ },
2261
+ async (uri, variables) => {
2262
+ try {
2263
+ const { server: serverUrl } = parseCkanUri(uri);
2264
+ const fq = config.buildFq(variables);
2265
+ const result = await makeCkanRequest(serverUrl, "package_search", {
2266
+ q: "*:*",
2267
+ fq
2268
+ });
2269
+ const content = truncateText(JSON.stringify(result, null, 2));
2270
+ return {
2271
+ contents: [
2272
+ {
2273
+ uri: uri.href,
2274
+ mimeType: "application/json",
2275
+ text: content
2276
+ }
2277
+ ]
2278
+ };
2279
+ } catch (error) {
2280
+ const errorMessage = error instanceof Error ? error.message : String(error);
2281
+ return {
2282
+ contents: [
2283
+ {
2284
+ uri: uri.href,
2285
+ mimeType: "text/plain",
2286
+ text: `Error fetching datasets: ${errorMessage}`
2287
+ }
2288
+ ]
2289
+ };
2290
+ }
2291
+ }
2292
+ );
2293
+ };
2294
+ function registerGroupDatasetsResource(server2) {
2295
+ registerDatasetFilterResource(server2, {
2296
+ name: "ckan-group-datasets",
2297
+ template: "ckan://{server}/group/{name}/datasets",
2298
+ title: "CKAN Group Datasets",
2299
+ description: "List datasets in a CKAN group (theme). URI format: ckan://{server}/group/{name}/datasets",
2300
+ buildFq: (variables) => `groups:"${escapeSolrTerm(variables.name)}"`
2301
+ });
2302
+ }
2303
+ function registerOrganizationDatasetsResource(server2) {
2304
+ registerDatasetFilterResource(server2, {
2305
+ name: "ckan-organization-datasets",
2306
+ template: "ckan://{server}/organization/{name}/datasets",
2307
+ title: "CKAN Organization Datasets",
2308
+ description: "List datasets for a CKAN organization. URI format: ckan://{server}/organization/{name}/datasets",
2309
+ buildFq: (variables) => `organization:"${escapeSolrTerm(variables.name)}"`
2310
+ });
2311
+ }
2312
+ function registerTagDatasetsResource(server2) {
2313
+ registerDatasetFilterResource(server2, {
2314
+ name: "ckan-tag-datasets",
2315
+ template: "ckan://{server}/tag/{name}/datasets",
2316
+ title: "CKAN Tag Datasets",
2317
+ description: "List datasets matching a CKAN tag. URI format: ckan://{server}/tag/{name}/datasets",
2318
+ buildFq: (variables) => `tags:"${escapeSolrTerm(variables.name)}"`
2319
+ });
2320
+ }
2321
+ function registerFormatDatasetsResource(server2) {
2322
+ registerDatasetFilterResource(server2, {
2323
+ name: "ckan-format-datasets",
2324
+ template: "ckan://{server}/format/{format}/datasets",
2325
+ title: "CKAN Format Datasets",
2326
+ description: "List datasets by resource format. URI format: ckan://{server}/format/{format}/datasets",
2327
+ buildFq: (variables) => buildFormatFilter(variables.format)
2328
+ });
2329
+ }
2330
+
2218
2331
  // src/resources/index.ts
2219
2332
  function registerAllResources(server2) {
2220
2333
  registerDatasetResource(server2);
2221
2334
  registerResourceResource(server2);
2222
2335
  registerOrganizationResource(server2);
2336
+ registerGroupDatasetsResource(server2);
2337
+ registerOrganizationDatasetsResource(server2);
2338
+ registerTagDatasetsResource(server2);
2339
+ registerFormatDatasetsResource(server2);
2223
2340
  }
2224
2341
 
2225
2342
  // src/prompts/theme.ts
@@ -2282,7 +2399,7 @@ var registerThemePrompt = (server2) => {
2282
2399
  argsSchema: {
2283
2400
  server_url: z8.string().url().describe("Base URL of the CKAN server"),
2284
2401
  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")
2402
+ rows: z8.coerce.number().int().positive().default(10).describe("Max results to return")
2286
2403
  }
2287
2404
  },
2288
2405
  async ({ server_url, theme, rows }) => createTextPrompt(buildThemePromptText(server_url, theme, rows))
@@ -2323,7 +2440,7 @@ var registerOrganizationPrompt = (server2) => {
2323
2440
  argsSchema: {
2324
2441
  server_url: z9.string().url().describe("Base URL of the CKAN server"),
2325
2442
  organization: z9.string().min(1).describe("Organization name or keyword"),
2326
- rows: z9.number().int().positive().default(10).describe("Max results to return")
2443
+ rows: z9.coerce.number().int().positive().default(10).describe("Max results to return")
2327
2444
  }
2328
2445
  },
2329
2446
  async ({ server_url, organization, rows }) => createTextPrompt(buildOrganizationPromptText(server_url, organization, rows))
@@ -2354,7 +2471,7 @@ var registerFormatPrompt = (server2) => {
2354
2471
  argsSchema: {
2355
2472
  server_url: z10.string().url().describe("Base URL of the CKAN server"),
2356
2473
  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")
2474
+ rows: z10.coerce.number().int().positive().default(10).describe("Max results to return")
2358
2475
  }
2359
2476
  },
2360
2477
  async ({ server_url, format, rows }) => createTextPrompt(buildFormatPromptText(server_url, format, rows))
@@ -2391,7 +2508,7 @@ var registerRecentPrompt = (server2) => {
2391
2508
  description: "Guided prompt to list recently updated datasets on a CKAN portal.",
2392
2509
  argsSchema: {
2393
2510
  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")
2511
+ rows: z11.coerce.number().int().positive().default(10).describe("Max results to return")
2395
2512
  }
2396
2513
  },
2397
2514
  async ({ server_url, rows }) => createTextPrompt(buildRecentPromptText(server_url, rows))
@@ -2462,7 +2579,7 @@ var registerAllPrompts = (server2) => {
2462
2579
  function createServer() {
2463
2580
  return new McpServer({
2464
2581
  name: "ckan-mcp-server",
2465
- version: "0.4.10"
2582
+ version: "0.4.13"
2466
2583
  });
2467
2584
  }
2468
2585
  function registerAll(server2) {