MCP server for interacting with CKAN open data portals
npm install @aborruso/ckan-mcp-server


MCP (Model Context Protocol) server for interacting with CKAN-based open data portals.
- โ
Support for any CKAN server (dati.gov.it, data.gov, demo.ckan.org, etc.)
- ๐ Advanced search with Solr syntax
- ๐ DataStore queries for tabular data analysis
- ๐ข Organization and group exploration
- ๐ฆ Complete dataset and resource metadata
- ๐จ Output in Markdown or JSON format
- โก Pagination and faceting support
- ๐ MCP Resource Templates for direct data access
- ๐งญ Guided MCP prompts for common workflows
- ๐ก๏ธ Browser-like headers to avoid WAF blocks
- ๐งช Test suite with 214 tests (100% passing)
๐ If you want to dive deeper, the AI-generated DeepWiki is very well done.
---
> ## ๐ Recommended: Install Locally
>
> ``bash`
> npm install -g @aborruso/ckan-mcp-server
>
>
> Benefits:
> - โ
No request limits - unlimited queries
> - โ
Faster - no network latency
> - โ
Always available - no shared quota
> - โ
Free - open source, no costs
>
> The Cloudflare Workers endpoint is only for quick testing (100k requests/month shared globally across all users).
---
Install via npm:
`bash`
npm install -g @aborruso/ckan-mcp-server
That's it! The server will be available as ckan-mcp-server command or via npx @aborruso/ckan-mcp-server.
For quick testing without installation, you can use the public Cloudflare Workers endpoint:
``
https://ckan-mcp-server.andy-pr.workers.dev/mcp
โ ๏ธ Warning: This is a demo instance with 100,000 requests/month shared globally across all users. Not recommended for production use. Install locally for reliable service.
This server works with any MCP-compatible client. Below are configuration examples for popular clients, organized by category.
Recommended: Use @aborruso/ckan-mcp-server@latest to always get the latest version.
#### Codex
`json`
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
#### Copilot CLI
`bash`
copilot mcp add ckan npx @aborruso/ckan-mcp-server@latest
#### Gemini CLI
`bash`
gemini mcp add ckan npx @aborruso/ckan-mcp-server@latest
#### Copilot / VS Code
Add to VS Code settings (.vscode/settings.json or User Settings):
`json`
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
#### Cursor
Add to Cursor MCP settings:
`json`
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
#### OpenCode
Add to OpenCode configuration:
`json`
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
#### Claude Desktop
Configuration file location:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json
- Windows: ~/.config/Claude/claude_desktop_config.json
- Linux:
Using npx (recommended):
`json`
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
Using global installation:
`bash`
npm install -g @aborruso/ckan-mcp-server
`json`
{
"mcpServers": {
"ckan": {
"command": "ckan-mcp-server"
}
}
}
For testing only - Cloudflare Workers endpoint:
`json`
{
"mcpServers": {
"ckan": {
"url": "https://ckan-mcp-server.andy-pr.workers.dev/mcp"
}
}
}
โ ๏ธ Warning: Demo instance with 100,000 requests/month shared globally across all users. Not reliable for production use.
#### ChatGPT
#### Claude
See Claude web guide
โ ๏ธ Note: Web tools use a demo server with 100,000 requests/month shared globally across all users. For reliable usage, install the server locally (see Installation section above).
- ckan_package_search: Search datasets with Solr queries
- ckan_find_relevant_datasets: Rank datasets by relevance score
- ckan_package_show: Complete details of a dataset
- ckan_tag_list: List tags with counts
- ckan_organization_list: List all organizations
- ckan_organization_show: Details of an organization
- ckan_organization_search: Search organizations by name
- ckan_datastore_search: Query tabular data
- ckan_datastore_search_sql: SQL queries on DataStore
- ckan_group_list: List groups
- ckan_group_show: Show group details
- ckan_group_search: Search groups by name
- ckan_get_mqa_quality: Get MQA quality score and metrics for dati.gov.it datasets (accessibility, reusability, interoperability, findability)
- ckan_get_mqa_quality_details: Get detailed MQA quality reasons and failing flags for dati.gov.it datasets
- ckan_status_show: Verify server status
Direct data access via ckan:// URI scheme:
- ckan://{server}/dataset/{id} - Dataset metadatackan://{server}/resource/{id}
- - Resource metadata and download URLckan://{server}/organization/{name}
- - Organization detailsckan://{server}/group/{name}/datasets
- - Datasets by group (theme)ckan://{server}/organization/{name}/datasets
- - Datasets by organizationckan://{server}/tag/{name}/datasets
- - Datasets by tagckan://{server}/format/{format}/datasets
- - Datasets by resource format (res_format + distribution_format)
Examples:
``
ckan://dati.gov.it/dataset/vaccini-covid
ckan://demo.ckan.org/resource/abc-123
ckan://data.gov/organization/sample-org
ckan://dati.gov.it/group/ambiente/datasets
ckan://dati.gov.it/organization/regione-toscana/datasets
ckan://dati.gov.it/tag/turismo/datasets
ckan://dati.gov.it/format/csv/datasets
Prompt templates that guide users through common CKAN workflows:
- ckan-search-by-theme: Find a theme/group and list datasets under it
- ckan-search-by-organization: Discover an organization and list its datasets
- ckan-search-by-format: Find datasets by resource format (CSV/JSON/etc.)
- ckan-recent-datasets: List recently updated datasets
- ckan-analyze-dataset: Inspect dataset metadata and explore DataStore resources
Example (retrieve a prompt by name with args):
`json`
{
"name": "ckan-search-by-theme",
"arguments": {
"server_url": "https://www.dati.gov.it/opendata",
"theme": "ambiente",
"rows": 10
}
}
`typescript`
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
q: "popolazione",
rows: 20
})
`typescript`
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
q: "hotel OR alberghi OR \"strutture ricettive\" OR ospitalitร OR ricettivitร ",
query_parser: "text",
rows: 0
})query_parser: "text"
Note: when is used, Solr special characters in the query are escaped automatically.
`typescript`
ckan_find_relevant_datasets({
server_url: "https://www.dati.gov.it/opendata",
query: "mobilitร urbana",
limit: 5
})
`typescript`
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
fq: "organization:regione-siciliana",
sort: "metadata_modified desc"
})
`typescript`
// Find all organizations containing "salute" in the name
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
q: "organization:salute",
rows: 0,
facet_field: ["organization"],
facet_limit: 100
})
`typescript`
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
facet_field: ["organization", "tags", "res_format"],
rows: 0
})
`typescript`
ckan_tag_list({
server_url: "https://www.dati.gov.it/opendata",
tag_query: "salute",
limit: 25
})
`typescript`
ckan_group_search({
server_url: "https://www.dati.gov.it/opendata",
pattern: "ambiente"
})
`typescript`
ckan_datastore_search({
server_url: "https://www.dati.gov.it/opendata",
resource_id: "abc-123-def",
filters: { "regione": "Sicilia", "anno": 2023 },
sort: "popolazione desc",
limit: 50
})
`typescript`
ckan_datastore_search_sql({
server_url: "https://demo.ckan.org",
sql: "SELECT Country, COUNT(*) AS total FROM \"abc-123-def\" GROUP BY Country ORDER BY total DESC LIMIT 10"
})
Verified portals with public API access:
- ๐ฎ๐น https://www.dati.gov.it/opendata - Italian National Open Data Portal (CKAN 2.10.3)
- ๐บ๐ธ https://catalog.data.gov - United States Open Data (CKAN 2.11.4)
- ๐จ๐ฆ https://open.canada.ca/data - Canada Open Government (CKAN 2.10.8)
- ๐ฆ๐บ https://data.gov.au - Australian Government Open Data (CKAN 2.11.4)
- ๐ฌ๐ง https://data.gov.uk - United Kingdom Open Data
- And 500+ more portals worldwide
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.
When generating a dataset or organization view link, the server:
- matches the server_url against api_url and api_url_aliases in src/portals.jsondataset_view_url
- uses the portal-specific / organization_view_url template when available{server_url}/dataset/{name}
- falls back to the generic defaults ( and {server_url}/organization/{name})
You can extend src/portals.json by adding new entries under portals if a portal uses different web URL patterns.
CKAN uses Apache Solr for search. Examples:
`Basic search
q: "popolazione"
$3
These real-world examples demonstrate powerful Solr query combinations tested on the Italian open data portal (dati.gov.it):
#### 1. Fuzzy Search + Date Math + Boosting (natural language: "find healthcare datasets modified in last 6 months")
Find healthcare datasets (tolerating spelling errors) modified in the last 6 months, prioritizing title matches:
`typescript
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
q: "(title:sanitร ~2^3 OR title:salute~2^3 OR notes:sanitร ~1) AND metadata_modified:[NOW-6MONTHS TO *]",
sort: "score desc, metadata_modified desc",
rows: 30
})
`Techniques used:
-
sanitร ~2 - Fuzzy search with edit distance 2 (finds "sanita", "sanitรก", minor typos)
- ^3 - Boosts title matches 3x higher in relevance scoring
- NOW-6MONTHS - Dynamic date math for rolling time windows
- Combined boolean logic with multiple field searchesResults: 871 datasets including hospital units, healthcare organizations, medical services
#### 2. Proximity Search + Complex Boolean (natural language: "find air pollution datasets excluding water")
Environmental datasets where "inquinamento" and "aria" (air pollution) appear close together, excluding water-related datasets:
`typescript
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
q: "(notes:\"inquinamento aria\"~5 OR title:\"qualitร aria\"~3) AND NOT (title:acqua OR title:mare)",
facet_field: ["organization", "res_format"],
rows: 25
})
`Techniques used:
-
"inquinamento aria"~5 - Proximity search (words within 5 positions)
- ~3 - Tighter proximity for title matches
- NOT (title:acqua OR title:mare) - Exclude water/sea datasets
- Faceting for statistical breakdownResults: 306 datasets, primarily air quality monitoring from Milan (44) and Palermo (161), formats: XML (150), CSV (124), JSON (76)
#### 3. Wildcard + Field Existence + Range Queries (natural language: "regional datasets with many resources from last year")
Regional datasets with at least 5 resources, published in the last year:
`typescript
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
q: "organization:regione AND num_resources:[5 TO ] AND metadata_created:[NOW-1YEAR TO ] AND res_format:",
sort: "num_resources desc, metadata_modified desc",
facet_field: ["organization"],
rows: 40
})
`Techniques used:
-
regione* - Wildcard matches all regional organizations
- [5 TO *] - Inclusive range (5 or more resources)
- res_format:* - Field existence check (has at least one resource format)
- NOW-1YEAR - Rolling 12-month windowResults: 5,318 datasets, top contributors: Lombardy (3,012), Tuscany (1,151), Puglia (460)
#### 4. Date Ranges + Exclusive Bounds (natural language: "ISTAT datasets with 10-50 resources from specific period")
ISTAT datasets with moderate resource count (10-50), modified in specific date range:
`typescript
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
q: "(title:istat OR organization:istat) AND num_resources:{9 TO 51} AND metadata_modified:[2025-07-01T00:00:00Z TO 2025-12-31T23:59:59Z]",
sort: "metadata_modified desc",
facet_field: ["res_format", "tags"],
rows: 30
})
`Techniques used:
-
{9 TO 51} - Exclusive bounds (10-50 resources, excluding 9 and 51)
- [2025-07-01T00:00:00Z TO 2025-12-31T23:59:59Z] - Explicit date range
- Combined organization wildcard with title search
- Multiple facets for content analysisNote: This specific query returned 0 results due to the narrow time window, demonstrating how precise constraints work.
$3
Boolean Operators:
AND, OR, NOT, +required, -excluded
Wildcards: * (multiple chars), ? (single char) - Note: left truncation not supported
Fuzzy: ~N (edit distance), e.g., health~2
Proximity: "phrase"~N (words within N positions)
Boosting: ^N (relevance multiplier), e.g., title:water^2
Ranges:- Inclusive:
[a TO b], e.g., num_resources:[5 TO 10]
- Exclusive: {a TO b}, e.g., num_resources:{0 TO 100}
- Open-ended: [2024-01-01T00:00:00Z TO *]Date Math:
NOW, NOW-1YEAR, NOW-6MONTHS, NOW-7DAYS, NOW/DAY
Field Existence: field: (field exists), NOT field: (field missing)Project Structure
`
ckan-mcp-server/
โโโ src/
โ โโโ index.ts # Entry point
โ โโโ server.ts # MCP server setup
โ โโโ worker.ts # Cloudflare Workers entry
โ โโโ types.ts # Types & schemas
โ โโโ utils/
โ โ โโโ http.ts # CKAN API client
โ โ โโโ formatting.ts # Output formatting
โ โ โโโ url-generator.ts
โ โโโ tools/
โ โ โโโ package.ts # Package search/show
โ โ โโโ organization.ts # Organization tools
โ โ โโโ datastore.ts # DataStore queries
โ โ โโโ status.ts # Server status
โ โ โโโ tag.ts # Tag tools
โ โ โโโ group.ts # Group tools
โ โโโ resources/ # MCP Resource Templates
โ โ โโโ index.ts
โ โ โโโ uri.ts # URI parsing
โ โ โโโ dataset.ts
โ โ โโโ resource.ts
โ โ โโโ organization.ts
โ โโโ prompts/ # MCP Guided Prompts
โ โ โโโ index.ts
โ โ โโโ theme.ts
โ โ โโโ organization.ts
โ โ โโโ format.ts
โ โ โโโ recent.ts
โ โ โโโ dataset-analysis.ts
โ โโโ transport/
โ โโโ stdio.ts # Stdio transport
โ โโโ http.ts # HTTP transport
โโโ tests/ # Test suite (212 tests)
โโโ dist/ # Compiled files (generated)
โโโ package.json
โโโ README.md
`Development
This project uses OpenSpec to manage change proposals and keep specifications aligned with implementation.
$3
The project uses Vitest for automated testing:
`bash
Run all tests
npm testRun tests in watch mode
npm run test:watchRun tests with coverage report
npm run test:coverage
`Current test coverage: ~39% (utils: 98%, tools: 15-20%).
Test suite includes:
- Unit tests for utility functions (formatting, HTTP, URI parsing, URL generation)
- Integration tests for MCP tools with mocked CKAN API responses
- Mock fixtures for CKAN API success and error scenarios
Coverage is higher for utility modules and lower for tool handlers.
See
tests/README.md for detailed testing guidelines.$3
The project uses esbuild for ultra-fast compilation (~4ms):
`bash
Build with esbuild (default)
npm run buildWatch mode for development
npm run watch
`$3
If you want to explore and test the server interactively, use the MCP Inspector:
`bash
Install MCP Inspector globally (one-time setup)
npm install -g @modelcontextprotocol/inspectorBuild the server
npm run buildLaunch Inspector with your server
npx @modelcontextprotocol/inspector node dist/index.js
`This opens a web interface (usually at
http://localhost:5173) where you can:
- Browse all registered tools and resources
- Test tool calls with auto-complete for parameters
- See real-time responses in both JSON and rendered format
- Debug errors with detailed stack traces$3
`bash
Start server in HTTP mode
TRANSPORT=http PORT=3000 npm startIn another terminal, test available tools
curl -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
`Troubleshooting
$3
Important: The correct URL for the Italian portal is
https://www.dati.gov.it/opendata (not https://dati.gov.it).$3
`
Error: Server not found: https://esempio.gov.it
`Solution: Verify the URL is correct and the server is online. Use
ckan_status_show to verify.$3
`typescript
// Use a more generic query
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
q: ":"
})// Check contents with faceting
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
facet_field: ["tags", "organization"],
rows: 0
})
`Contributing
Contributions are welcome! Please:
1. Fork the project
2. Create a branch for the feature (
git checkout -b feature/AmazingFeature)
3. Commit your changes (git commit -m 'Add some AmazingFeature')
4. Push to the branch (git push origin feature/AmazingFeature)
5. Open a Pull RequestLicense
MIT License - See LICENSE.txt for complete details.
Third-party attributions: See NOTICE.md for third-party software notices and information.
Useful Links
- CKAN: https://ckan.org/
- CKAN API Documentation: https://docs.ckan.org/en/latest/api/
- MCP Protocol: https://modelcontextprotocol.io/
Date fields (source vs aggregator)
CKAN portals can be source catalogs or harvesting aggregators.
-
issued / modified: publisher content dates (best for "created/updated" when present)
- metadata_created / metadata_modified: CKAN record timestamps (publish time on source portals,
harvest time on aggregators)For "recent content" queries, prefer
issued with a fallback to metadata_created
when issued is missing (see the content_recent helper in ckan_package_search`).For issues or questions, open an issue on GitHub.
---
Created with โค๏ธ by onData for the open data community