@aborruso/ckan-mcp-server 0.4.11 → 0.4.14

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 (42) hide show
  1. package/CLAUDE.md +7 -7
  2. package/LOG.md +66 -0
  3. package/NOTICE.md +77 -0
  4. package/PRD.md +6 -6
  5. package/README.md +48 -20
  6. package/dist/index.js +150 -3
  7. package/dist/worker.js +60 -56
  8. package/examples/langgraph/01_basic_workflow.py +277 -0
  9. package/examples/langgraph/02_data_exploration.py +366 -0
  10. package/examples/langgraph/README.md +719 -0
  11. package/examples/langgraph/metadata_quality.py +299 -0
  12. package/examples/langgraph/requirements.txt +12 -0
  13. package/examples/langgraph/setup.sh +32 -0
  14. package/examples/langgraph/test_setup.py +106 -0
  15. package/openspec/changes/add-ckan-host-allowlist-env/design.md +38 -0
  16. package/openspec/changes/add-ckan-host-allowlist-env/proposal.md +16 -0
  17. package/openspec/changes/add-ckan-host-allowlist-env/specs/ckan-request-allowlist/spec.md +15 -0
  18. package/openspec/changes/add-ckan-host-allowlist-env/specs/cloudflare-deployment/spec.md +11 -0
  19. package/openspec/changes/add-ckan-host-allowlist-env/tasks.md +12 -0
  20. package/openspec/changes/add-escape-text-query/proposal.md +12 -0
  21. package/openspec/changes/add-escape-text-query/specs/ckan-search/spec.md +11 -0
  22. package/openspec/changes/add-escape-text-query/tasks.md +8 -0
  23. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/proposal.md +13 -0
  24. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/specs/mcp-resources/spec.md +38 -0
  25. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/tasks.md +10 -0
  26. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/proposal.md +13 -0
  27. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/specs/repository-metadata/spec.md +14 -0
  28. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/tasks.md +12 -0
  29. package/openspec/specs/ckan-insights/spec.md +5 -1
  30. package/openspec/specs/ckan-search/spec.md +16 -0
  31. package/openspec/specs/mcp-prompts/spec.md +26 -0
  32. package/openspec/specs/mcp-resources/spec.md +30 -4
  33. package/openspec/specs/repository-metadata/spec.md +19 -0
  34. package/package.json +1 -1
  35. package/private/commenti-privati.yaml +14 -0
  36. /package/openspec/changes/{add-mcp-prompts → archive/2026-01-15-add-mcp-prompts}/proposal.md +0 -0
  37. /package/openspec/changes/{add-mcp-prompts → archive/2026-01-15-add-mcp-prompts}/specs/mcp-prompts/spec.md +0 -0
  38. /package/openspec/changes/{add-mcp-prompts → archive/2026-01-15-add-mcp-prompts}/tasks.md +0 -0
  39. /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/proposal.md +0 -0
  40. /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/specs/ckan-insights/spec.md +0 -0
  41. /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/specs/ckan-search/spec.md +0 -0
  42. /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/tasks.md +0 -0
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/LOG.md CHANGED
@@ -1,7 +1,73 @@
1
1
  # LOG
2
2
 
3
+ ## 2026-01-22
4
+
5
+ ### Date Query Auto-Conversion (v0.4.14)
6
+
7
+ - **Feature**: Auto-convert NOW-based date math for `modified` and `issued` fields
8
+ - **Problem**: CKAN Solr supports `NOW-XDAYS` syntax only on `metadata_modified` and `metadata_created` fields
9
+ - **Solution**: New `convertDateMathForUnsupportedFields()` automatically converts queries like `modified:[NOW-30DAYS TO NOW]` to ISO dates `modified:[2025-12-23T... TO 2026-01-22T...]`
10
+ - **Supported fields**: `modified`, `issued` (auto-converted) | `metadata_modified`, `metadata_created` (native NOW support)
11
+ - **Supported units**: DAYS, MONTHS, YEARS (singular and plural forms)
12
+ - **Tests**: +10 unit tests (201 total, all passing)
13
+ - **Documentation**: Updated tool description with NOW syntax limitations and examples
14
+ - **Files**: `src/utils/search.ts`, `src/tools/package.ts`, `tests/unit/search.test.ts`
15
+ - **No breaking changes**: Backward compatible - existing queries work unchanged
16
+
17
+ ## 2026-01-19
18
+
19
+ ### Search Parser Escaping
20
+ - **Fix**: Escape Solr special characters when forcing `text:(...)` queries
21
+ - **Tests**: Added unit coverage for escaping and forced parser output
22
+ - **Files**: `src/utils/search.ts`, `tests/unit/search.test.ts`, `README.md`
23
+
24
+ ## 2026-01-17
25
+
26
+ ### Documentation Alignment
27
+ - **Test counts updated**: README.md (184→190), PRD.md (130→190)
28
+ - **Version updated**: PRD.md version 0.4.7→0.4.12
29
+ - **Date updated**: PRD.md last updated 2026-01-10→2026-01-17
30
+ - **Verification**: All 13 tools, 7 resources, 5 prompts implemented and documented
31
+ - **Files**: `README.md`, `PRD.md`
32
+
33
+ ### GitHub Templates
34
+ - **Issue templates**: Added bug report and feature request YAML forms
35
+ - Bug report: CKAN portal URL, steps, expected/actual, error, Node version
36
+ - Feature request: problem/use case, proposed solution, alternatives
37
+ - Auto-labels: `bug` and `enhancement`
38
+ - **Issue chooser**: Routes questions to Discussions Q&A
39
+ - **PR template**: Description, related issue, test/docs checklist
40
+ - **Files**: `.github/ISSUE_TEMPLATE/{bug_report,feature_request,config}.yml`, `.github/PULL_REQUEST_TEMPLATE.md`
41
+
42
+ ## 2026-01-16
43
+
44
+ ### Demo Video Preparation
45
+ - **Documentation**: Created complete demo video documentation suite in `docs/video/`
46
+ - `demo-script.md` - Commands and technical notes for 4 use cases
47
+ - `demo-expected-results.md` - Actual test results with data samples
48
+ - `demo-recording-guide.md` - Step-by-step recording guide with voiceover scripts
49
+ - `pre-recording-checklist.md` - Practical day-of checklist
50
+ - `demo-fallback-options.md` - Comprehensive fallback strategies for 8 scenarios
51
+ - `demo-timing-report.md` - Performance analysis and timing verification
52
+ - **Testing**: Verified all demo commands working with dati.gov.it
53
+ - Portal overview: 67,614 datasets, top 10 organizations (~5s)
54
+ - Targeted search: 263 Milano transport datasets (~10s)
55
+ - Dataset details: Complete metadata with CSV/JSON resources (~10s)
56
+ - DuckDB analysis: DESCRIBE, SUMMARIZE, SAMPLE all working (~8s total)
57
+ - **Target**: 5-7 minute video demonstrating MCP convenience for Italian open data
58
+ - **Status**: Ready for recording with high confidence level
59
+
3
60
  ## 2026-01-15
4
61
 
62
+ ### Version 0.4.12 - Dataset filter resources
63
+ - **Feature**: Added `ckan://{server}/.../datasets` resource templates for group, organization, tag, and format filters
64
+ - **Fix**: Map `ckan://` hostnames to portal API base URLs (e.g., dati.gov.it → /opendata)
65
+ - **Fix**: Format filtering now matches `res_format` and `distribution_format` (with case variants)
66
+ - **Docs**: Updated README and future ideas with new URI templates
67
+ - **Docs**: Updated `docs/proposta-spunti-datos-gob-es-mcp.md` marking resource templates as completed
68
+ - **Tests**: Added unit tests for dataset filter resource templates
69
+ - **Files**: New `src/resources/dataset-filters.ts`, updates in `src/resources/index.ts`, `src/worker.ts`
70
+
5
71
  ### Version 0.4.11 - Prompt argument coercion
6
72
  - **Fix**: Prompt arguments now coerce numeric strings (e.g., rows) for MCP prompt requests
7
73
  - **Docs**: Updated evaluation notes for 0.4.11
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 (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
- - 130 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**: 130 tests (100% passing)
863
+ - **Test Coverage**: 191 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 191 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 (191 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:
@@ -115,22 +117,29 @@ Then add to `claude_desktop_config.json`:
115
117
  }
116
118
  ```
117
119
 
118
- #### Option 2: Local Installation (Optional)
120
+ #### Option 2: Local Project Installation (Optional)
121
+
122
+ If you want to install the server in a specific project:
119
123
 
120
- If you installed locally (see Installation), use this config:
124
+ ```bash
125
+ cd your-project
126
+ npm install @aborruso/ckan-mcp-server
127
+ ```
128
+
129
+ Then add to `claude_desktop_config.json`:
121
130
 
122
131
  ```json
123
132
  {
124
133
  "mcpServers": {
125
134
  "ckan": {
126
- "command": "node",
127
- "args": ["/absolute/path/to/project/node_modules/@username/ckan-mcp-server/dist/index.js"]
135
+ "command": "npx",
136
+ "args": ["@aborruso/ckan-mcp-server"]
128
137
  }
129
138
  }
130
139
  }
131
140
  ```
132
141
 
133
- 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.
134
143
 
135
144
  #### Option 3: From Source
136
145
 
@@ -147,6 +156,8 @@ If you cloned the repository:
147
156
  }
148
157
  ```
149
158
 
159
+ [A detailed guide](https://github.com/ondata/ckan-mcp-server/discussions/4#discussion-9359684)
160
+
150
161
  #### Option 4: Cloudflare Workers (HTTP transport)
151
162
 
152
163
  Use the public Cloudflare Workers deployment (no local installation required):
@@ -165,6 +176,13 @@ Use the public Cloudflare Workers deployment (no local installation required):
165
176
 
166
177
  **Note**: This uses the public endpoint. You can also deploy your own Workers instance and use that URL instead.
167
178
 
179
+ ### Web Tools Configuration
180
+
181
+ - [ChatGPT web](docs/guide/chatgpt/chatgpt_web.md)
182
+ - [Claude web](docs/guide/claude/claude_web.md)
183
+
184
+ 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.
185
+
168
186
  ## Available Tools
169
187
 
170
188
  ### Search and Discovery
@@ -172,13 +190,13 @@ Use the public Cloudflare Workers deployment (no local installation required):
172
190
  - **ckan_package_search**: Search datasets with Solr queries
173
191
  - **ckan_find_relevant_datasets**: Rank datasets by relevance score
174
192
  - **ckan_package_show**: Complete details of a dataset
175
- - **ckan_package_list**: List all datasets
176
193
  - **ckan_tag_list**: List tags with counts
177
194
 
178
195
  ### Organizations
179
196
 
180
197
  - **ckan_organization_list**: List all organizations
181
198
  - **ckan_organization_show**: Details of an organization
199
+ - **ckan_organization_search**: Search organizations by name
182
200
 
183
201
  ### DataStore
184
202
 
@@ -202,6 +220,10 @@ Direct data access via `ckan://` URI scheme:
202
220
  - `ckan://{server}/dataset/{id}` - Dataset metadata
203
221
  - `ckan://{server}/resource/{id}` - Resource metadata and download URL
204
222
  - `ckan://{server}/organization/{name}` - Organization details
223
+ - `ckan://{server}/group/{name}/datasets` - Datasets by group (theme)
224
+ - `ckan://{server}/organization/{name}/datasets` - Datasets by organization
225
+ - `ckan://{server}/tag/{name}/datasets` - Datasets by tag
226
+ - `ckan://{server}/format/{format}/datasets` - Datasets by resource format (res_format + distribution_format)
205
227
 
206
228
  Examples:
207
229
 
@@ -209,6 +231,10 @@ Examples:
209
231
  ckan://dati.gov.it/dataset/vaccini-covid
210
232
  ckan://demo.ckan.org/resource/abc-123
211
233
  ckan://data.gov/organization/sample-org
234
+ ckan://dati.gov.it/group/ambiente/datasets
235
+ ckan://dati.gov.it/organization/regione-toscana/datasets
236
+ ckan://dati.gov.it/tag/turismo/datasets
237
+ ckan://dati.gov.it/format/csv/datasets
212
238
  ```
213
239
 
214
240
  ## Guided Prompts
@@ -256,6 +282,7 @@ ckan_package_search({
256
282
  rows: 0
257
283
  })
258
284
  ```
285
+ Note: when `query_parser: "text"` is used, Solr special characters in the query are escaped automatically.
259
286
 
260
287
  ### Rank datasets by relevance (natural language: "find most relevant datasets about urban mobility")
261
288
 
@@ -354,15 +381,15 @@ Some of the main compatible portals:
354
381
 
355
382
  ### Portal View URL Templates
356
383
 
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.
384
+ 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
385
 
359
386
  When generating a dataset or organization view link, the server:
360
387
 
361
- - matches the `server_url` against `api_url` and `api_url_aliases` in `src/portals.json`
388
+ - matches the `server_url` against `api_url` and `api_url_aliases` in [`src/portals.json`](src/portals.json)
362
389
  - uses the portal-specific `dataset_view_url` / `organization_view_url` template when available
363
390
  - falls back to the generic defaults (`{server_url}/dataset/{name}` and `{server_url}/organization/{name}`)
364
391
 
365
- You can extend `src/portals.json` by adding new entries under `portals` if a portal uses different web URL patterns.
392
+ You can extend [`src/portals.json`](src/portals.json) by adding new entries under `portals` if a portal uses different web URL patterns.
366
393
 
367
394
  ## Advanced Solr Queries
368
395
 
@@ -539,7 +566,7 @@ ckan-mcp-server/
539
566
  │ └── transport/
540
567
  │ ├── stdio.ts # Stdio transport
541
568
  │ └── http.ts # HTTP transport
542
- ├── tests/ # Test suite (184 tests)
569
+ ├── tests/ # Test suite (191 tests)
543
570
  ├── dist/ # Compiled files (generated)
544
571
  ├── package.json
545
572
  └── README.md
@@ -547,6 +574,8 @@ ckan-mcp-server/
547
574
 
548
575
  ## Development
549
576
 
577
+ This project uses [OpenSpec](https://github.com/Fission-AI/OpenSpec) to manage change proposals and keep specifications aligned with implementation.
578
+
550
579
  ### Testing
551
580
 
552
581
  The project uses **Vitest** for automated testing:
@@ -640,7 +669,9 @@ Contributions are welcome! Please:
640
669
 
641
670
  ## License
642
671
 
643
- MIT License - See LICENSE.txt file for complete details.
672
+ MIT License - See [LICENSE.txt](LICENSE.txt) for complete details.
673
+
674
+ Third-party attributions: See [NOTICE.md](NOTICE.md) for third-party software notices and information.
644
675
 
645
676
  ## Useful Links
646
677
 
@@ -650,10 +681,7 @@ MIT License - See LICENSE.txt file for complete details.
650
681
 
651
682
  ## Support
652
683
 
653
- For issues or questions:
654
-
655
- - Open an issue on GitHub
656
- - Contact onData: https://www.ondata.it/
684
+ For issues or questions, [open an issue on GitHub](https://github.com/ondata/ckan-mcp-server/issues/new/choose).
657
685
 
658
686
  ---
659
687
 
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,38 @@ 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
+ }
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
+ }
153
196
  function resolveSearchQuery(serverUrl, query, parserOverride) {
154
197
  const portalSearchConfig = getPortalSearchConfig(serverUrl);
155
198
  const portalForce = portalSearchConfig.force_text_field ?? false;
@@ -162,7 +205,8 @@ function resolveSearchQuery(serverUrl, query, parserOverride) {
162
205
  const trimmedQuery = query.trim();
163
206
  forceTextField = trimmedQuery !== DEFAULT_SEARCH_QUERY && !isFieldedQuery(trimmedQuery);
164
207
  }
165
- const effectiveQuery = forceTextField ? `text:(${query})` : query;
208
+ let effectiveQuery = forceTextField ? `text:(${escapeSolrQuery(query)})` : query;
209
+ effectiveQuery = convertDateMathForUnsupportedFields(effectiveQuery);
166
210
  return { effectiveQuery, forcedTextField: forceTextField };
167
211
  }
168
212
 
@@ -315,6 +359,9 @@ Query Syntax (parameter q):
315
359
  - NOW/DAY, NOW/MONTH (round down)
316
360
  - Combined: "metadata_modified:[NOW-2MONTHS TO NOW]"
317
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 *]"
318
365
 
319
366
  Field existence:
320
367
  - Exists: "field:*" or "field:[* TO *]"
@@ -333,6 +380,7 @@ Examples:
333
380
  - Proximity: { q: "notes:"open data"~3" }
334
381
  - Date range: { q: "metadata_modified:[2024-01-01T00:00:00Z TO 2024-12-31T23:59:59Z]" }
335
382
  - Date math: { q: "metadata_modified:[NOW-6MONTHS TO *]" }
383
+ - Date math (auto-converted): { q: "modified:[NOW-30DAYS TO NOW]" }
336
384
  - Field exists: { q: "organization:* AND num_resources:[1 TO *]" }
337
385
  - Boosting: { q: "title:climate^2 OR notes:climate" }
338
386
  - Filter org: { fq: "organization:regione-siciliana" }
@@ -2073,7 +2121,7 @@ function parseCkanUri(uri) {
2073
2121
  if (!type || !id) {
2074
2122
  throw new Error("Invalid ckan:// URI: missing type or id");
2075
2123
  }
2076
- const server2 = `https://${hostname}`;
2124
+ const server2 = getPortalApiUrlForHostname(hostname) || `https://${hostname}`;
2077
2125
  return { server: server2, type, id };
2078
2126
  }
2079
2127
 
@@ -2215,11 +2263,110 @@ function registerOrganizationResource(server2) {
2215
2263
  );
2216
2264
  }
2217
2265
 
2266
+ // src/resources/dataset-filters.ts
2267
+ import { ResourceTemplate as ResourceTemplate4 } from "@modelcontextprotocol/sdk/server/mcp.js";
2268
+ var escapeSolrTerm = (value) => value.replace(/["\\]/g, "\\$&");
2269
+ var buildFormatFilter = (rawFormat) => {
2270
+ const format = rawFormat.trim();
2271
+ const variants = [format];
2272
+ const upper = format.toUpperCase();
2273
+ if (upper !== format) {
2274
+ variants.push(upper);
2275
+ }
2276
+ const fields = ["res_format", "distribution_format"];
2277
+ const clauses = fields.flatMap(
2278
+ (field) => variants.map((value) => `${field}:"${escapeSolrTerm(value)}"`)
2279
+ );
2280
+ return `(${clauses.join(" OR ")})`;
2281
+ };
2282
+ var registerDatasetFilterResource = (server2, config) => {
2283
+ server2.registerResource(
2284
+ config.name,
2285
+ new ResourceTemplate4(config.template, { list: void 0 }),
2286
+ {
2287
+ title: config.title,
2288
+ description: config.description,
2289
+ mimeType: "application/json"
2290
+ },
2291
+ async (uri, variables) => {
2292
+ try {
2293
+ const { server: serverUrl } = parseCkanUri(uri);
2294
+ const fq = config.buildFq(variables);
2295
+ const result = await makeCkanRequest(serverUrl, "package_search", {
2296
+ q: "*:*",
2297
+ fq
2298
+ });
2299
+ const content = truncateText(JSON.stringify(result, null, 2));
2300
+ return {
2301
+ contents: [
2302
+ {
2303
+ uri: uri.href,
2304
+ mimeType: "application/json",
2305
+ text: content
2306
+ }
2307
+ ]
2308
+ };
2309
+ } catch (error) {
2310
+ const errorMessage = error instanceof Error ? error.message : String(error);
2311
+ return {
2312
+ contents: [
2313
+ {
2314
+ uri: uri.href,
2315
+ mimeType: "text/plain",
2316
+ text: `Error fetching datasets: ${errorMessage}`
2317
+ }
2318
+ ]
2319
+ };
2320
+ }
2321
+ }
2322
+ );
2323
+ };
2324
+ function registerGroupDatasetsResource(server2) {
2325
+ registerDatasetFilterResource(server2, {
2326
+ name: "ckan-group-datasets",
2327
+ template: "ckan://{server}/group/{name}/datasets",
2328
+ title: "CKAN Group Datasets",
2329
+ description: "List datasets in a CKAN group (theme). URI format: ckan://{server}/group/{name}/datasets",
2330
+ buildFq: (variables) => `groups:"${escapeSolrTerm(variables.name)}"`
2331
+ });
2332
+ }
2333
+ function registerOrganizationDatasetsResource(server2) {
2334
+ registerDatasetFilterResource(server2, {
2335
+ name: "ckan-organization-datasets",
2336
+ template: "ckan://{server}/organization/{name}/datasets",
2337
+ title: "CKAN Organization Datasets",
2338
+ description: "List datasets for a CKAN organization. URI format: ckan://{server}/organization/{name}/datasets",
2339
+ buildFq: (variables) => `organization:"${escapeSolrTerm(variables.name)}"`
2340
+ });
2341
+ }
2342
+ function registerTagDatasetsResource(server2) {
2343
+ registerDatasetFilterResource(server2, {
2344
+ name: "ckan-tag-datasets",
2345
+ template: "ckan://{server}/tag/{name}/datasets",
2346
+ title: "CKAN Tag Datasets",
2347
+ description: "List datasets matching a CKAN tag. URI format: ckan://{server}/tag/{name}/datasets",
2348
+ buildFq: (variables) => `tags:"${escapeSolrTerm(variables.name)}"`
2349
+ });
2350
+ }
2351
+ function registerFormatDatasetsResource(server2) {
2352
+ registerDatasetFilterResource(server2, {
2353
+ name: "ckan-format-datasets",
2354
+ template: "ckan://{server}/format/{format}/datasets",
2355
+ title: "CKAN Format Datasets",
2356
+ description: "List datasets by resource format. URI format: ckan://{server}/format/{format}/datasets",
2357
+ buildFq: (variables) => buildFormatFilter(variables.format)
2358
+ });
2359
+ }
2360
+
2218
2361
  // src/resources/index.ts
2219
2362
  function registerAllResources(server2) {
2220
2363
  registerDatasetResource(server2);
2221
2364
  registerResourceResource(server2);
2222
2365
  registerOrganizationResource(server2);
2366
+ registerGroupDatasetsResource(server2);
2367
+ registerOrganizationDatasetsResource(server2);
2368
+ registerTagDatasetsResource(server2);
2369
+ registerFormatDatasetsResource(server2);
2223
2370
  }
2224
2371
 
2225
2372
  // src/prompts/theme.ts
@@ -2462,7 +2609,7 @@ var registerAllPrompts = (server2) => {
2462
2609
  function createServer() {
2463
2610
  return new McpServer({
2464
2611
  name: "ckan-mcp-server",
2465
- version: "0.4.11"
2612
+ version: "0.4.13"
2466
2613
  });
2467
2614
  }
2468
2615
  function registerAll(server2) {