@aborruso/ckan-mcp-server 0.4.11 → 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.
- package/LOG.md +52 -0
- package/NOTICE.md +77 -0
- package/PRD.md +6 -6
- package/README.md +35 -14
- package/dist/index.js +120 -3
- package/dist/worker.js +56 -56
- 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/LOG.md
CHANGED
|
@@ -1,7 +1,59 @@
|
|
|
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
|
+
|
|
5
57
|
### Version 0.4.11 - Prompt argument coercion
|
|
6
58
|
- **Fix**: Prompt arguments now coerce numeric strings (e.g., rows) for MCP prompt requests
|
|
7
59
|
- **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 (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
|
-
-
|
|
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**:
|
|
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
|
[](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 186 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 (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
|
|
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
|
|
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
|
|
@@ -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.
|
|
2582
|
+
version: "0.4.13"
|
|
2466
2583
|
});
|
|
2467
2584
|
}
|
|
2468
2585
|
function registerAll(server2) {
|