@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.
- package/CLAUDE.md +7 -7
- package/LOG.md +66 -0
- package/NOTICE.md +77 -0
- package/PRD.md +6 -6
- package/README.md +48 -20
- package/dist/index.js +150 -3
- package/dist/worker.js +60 -56
- package/examples/langgraph/01_basic_workflow.py +277 -0
- package/examples/langgraph/02_data_exploration.py +366 -0
- package/examples/langgraph/README.md +719 -0
- package/examples/langgraph/metadata_quality.py +299 -0
- package/examples/langgraph/requirements.txt +12 -0
- package/examples/langgraph/setup.sh +32 -0
- package/examples/langgraph/test_setup.py +106 -0
- package/openspec/changes/add-ckan-host-allowlist-env/design.md +38 -0
- package/openspec/changes/add-ckan-host-allowlist-env/proposal.md +16 -0
- package/openspec/changes/add-ckan-host-allowlist-env/specs/ckan-request-allowlist/spec.md +15 -0
- package/openspec/changes/add-ckan-host-allowlist-env/specs/cloudflare-deployment/spec.md +11 -0
- package/openspec/changes/add-ckan-host-allowlist-env/tasks.md +12 -0
- package/openspec/changes/add-escape-text-query/proposal.md +12 -0
- package/openspec/changes/add-escape-text-query/specs/ckan-search/spec.md +11 -0
- package/openspec/changes/add-escape-text-query/tasks.md +8 -0
- package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/proposal.md +13 -0
- package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/specs/mcp-resources/spec.md +38 -0
- package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/tasks.md +10 -0
- package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/proposal.md +13 -0
- package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/specs/repository-metadata/spec.md +14 -0
- package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/tasks.md +12 -0
- package/openspec/specs/ckan-insights/spec.md +5 -1
- package/openspec/specs/ckan-search/spec.md +16 -0
- package/openspec/specs/mcp-prompts/spec.md +26 -0
- package/openspec/specs/mcp-resources/spec.md +30 -4
- package/openspec/specs/repository-metadata/spec.md +19 -0
- package/package.json +1 -1
- package/private/commenti-privati.yaml +14 -0
- /package/openspec/changes/{add-mcp-prompts → archive/2026-01-15-add-mcp-prompts}/proposal.md +0 -0
- /package/openspec/changes/{add-mcp-prompts → archive/2026-01-15-add-mcp-prompts}/specs/mcp-prompts/spec.md +0 -0
- /package/openspec/changes/{add-mcp-prompts → archive/2026-01-15-add-mcp-prompts}/tasks.md +0 -0
- /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/proposal.md +0 -0
- /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/specs/ckan-insights/spec.md +0 -0
- /package/openspec/changes/{update-search-parser-config → archive/2026-01-19-update-search-parser-config}/specs/ckan-search/spec.md +0 -0
- /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 (
|
|
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**:
|
|
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 (
|
|
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 (
|
|
94
|
+
│ ├── package.test.ts # Package tools (31 tests)
|
|
95
95
|
│ ├── organization.test.ts # Organization tools (6 tests)
|
|
96
|
-
│ ├── datastore.test.ts # DataStore tools (
|
|
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**:
|
|
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
|
-
-
|
|
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.
|
|
6
|
-
**Last Updated**: 2026-01-
|
|
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 (
|
|
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
|
-
-
|
|
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**:
|
|
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
|
[](https://www.npmjs.com/package/@aborruso/ckan-mcp-server)
|
|
2
|
-
[](https://deepwiki.com/
|
|
2
|
+
[](https://github.com/ondata/ckan-mcp-server)
|
|
3
|
+
[](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
|
|
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/
|
|
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 (
|
|
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
|
-
|
|
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": "
|
|
127
|
-
"args": ["/
|
|
135
|
+
"command": "npx",
|
|
136
|
+
"args": ["@aborruso/ckan-mcp-server"]
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
140
|
```
|
|
132
141
|
|
|
133
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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.
|
|
2612
|
+
version: "0.4.13"
|
|
2466
2613
|
});
|
|
2467
2614
|
}
|
|
2468
2615
|
function registerAll(server2) {
|