CKAN MCP Server
MCP (Model Context Protocol) server for interacting with CKAN-based open data portals.
Features
- ✅ 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.
💡 Local installation available for unlimited access and better performance:
npm install -g @aborruso/ckan-mcp-serverThe Cloudflare Workers endpoint has 100k requests/day shared quota - sufficient for most users, but local installation is recommended for heavy usage.
Installation
Install via npm:
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.
Quick Testing with Workers (optional)
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.
MCP Client Configuration
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.
CLI Tools
Codex
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
Copilot CLI
copilot mcp add ckan npx @aborruso/ckan-mcp-server@latest
Gemini CLI
gemini mcp add ckan npx @aborruso/ckan-mcp-server@latest
IDEs & Code Editors
Copilot / VS Code
Add to VS Code settings (.vscode/settings.json or User Settings):
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
Cursor
Add to Cursor MCP settings:
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
OpenCode
Add to OpenCode configuration:
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
Desktop Applications
Claude Desktop
Configuration file location:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Using npx (recommended):
{
"mcpServers": {
"ckan": {
"command": "npx",
"args": ["@aborruso/ckan-mcp-server@latest"]
}
}
}
Using global installation:
npm install -g @aborruso/ckan-mcp-server
{
"mcpServers": {
"ckan": {
"command": "ckan-mcp-server"
}
}
}
For testing only - Cloudflare Workers endpoint:
{
"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.
Claude Desktop on Windows reading from a local MCP server installed on WSL2:
{
"mcpServers": {
"ckan": {
"command": "wsl.exe",
"args": [
"-e",
"/usr/local/bin/node",
"/home/username/projects/ckan-mcp-server/dist/index.js"
]
}
}
}
This requires the server to be built (npm run build) inside the WSL2 environment before use.
Web Tools
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).
Available Tools
Search and Discovery
- 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
Organizations
- ckan_organization_list: List all organizations
- ckan_organization_show: Details of an organization
- ckan_organization_search: Search organizations by name
DataStore
- ckan_datastore_search: Query tabular data
- ckan_datastore_search_sql: SQL queries on DataStore
Groups
- ckan_group_list: List groups
- ckan_group_show: Show group details
- ckan_group_search: Search groups by name
Quality Metrics
- 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
Utilities
- ckan_status_show: Verify server status
MCP Resource Templates
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
Guided Prompts
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):
{
"name": "ckan-search-by-theme",
"arguments": {
"server_url": "https://www.dati.gov.it/opendata",
"theme": "ambiente",
"rows": 10
}
}
Usage Examples
Search datasets on dati.gov.it (natural language: "search for population datasets")
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
q: "popolazione",
rows: 20
})
Force text-field parser for long OR queries (natural language: "find hotel or accommodation datasets")
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
})
Note: when query_parser: "text" is used, Solr special characters in the query are escaped automatically.
Rank datasets by relevance (natural language: "find most relevant datasets about urban mobility")
ckan_find_relevant_datasets({
server_url: "https://www.dati.gov.it/opendata",
query: "mobilità urbana",
limit: 5
})
Filter by organization (natural language: "show recent datasets from Sicilian Region")
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
fq: "organization:regione-siciliana",
sort: "metadata_modified desc"
})
Search organizations with wildcard (natural language: "find all organizations with health/salute in name")
// 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
})
Get statistics with faceting (natural language: "show statistics by organization, tags and format")
ckan_package_search({
server_url: "https://www.dati.gov.it/opendata",
facet_field: ["organization", "tags", "res_format"],
rows: 0
})
List tags (natural language: "show top tags about health")
ckan_tag_list({
server_url: "https://www.dati.gov.it/opendata",
tag_query: "salute",
limit: 25
})
Search groups (natural language: "find groups about environment")
ckan_group_search({
server_url: "https://www.dati.gov.it/opendata",
pattern: "ambiente"
})
DataStore Query (natural language: "query tabular data filtering by region and year")
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
})
DataStore SQL Query (natural language: "count records by country with SQL")
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"
})
Supported CKAN Portals
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
Portal View URL Templates
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_urlagainstapi_urlandapi_url_aliasesinsrc/portals.json - uses the portal-specific
dataset_view_url/organization_view_urltemplate when available - falls back to the generic defaults (
{server_url}/dataset/{name}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.
Advanced Solr Queries
CKAN uses Apache Solr for search. Examples:
# Basic search
q: "popolazione"
# Field search
q: "title:popolazione"
q: "notes:sanità"
# Boolean operators
q: "popolazione AND sicilia"
q: "popolazione OR abitanti"
q: "popolazione NOT censimento"
# Filters (fq)
fq: "organization:comune-palermo"
fq: "tags:sanità"
fq: "res_format:CSV"
# Wildcard
q: "popolaz*"
# Date range
fq: "metadata_modified:[2023-01-01T00:00:00Z TO *]"
Advanced Query Examples
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:
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 scoringNOW-6MONTHS- Dynamic date math for rolling time windows- Combined boolean logic with multiple field searches
Results: 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:
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 matchesNOT (title:acqua OR title:mare)- Exclude water/sea datasets- Faceting for statistical breakdown
Results: 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:
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 window
Results: 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:
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 analysis
Note: This specific query returned 0 results due to the narrow time window, demonstrating how precise constraints work.
Solr Query Syntax Reference
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.
Testing
The project uses Vitest for automated testing:
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run 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.
Build
The project uses esbuild for ultra-fast compilation (~4ms):
# Build with esbuild (default)
npm run build
# Watch mode for development
npm run watch
Exploring the Server
If you want to explore and test the server interactively, use the MCP Inspector:
# Install MCP Inspector globally (one-time setup)
npm install -g @modelcontextprotocol/inspector
# Build the server
npm run build
# Launch 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
Manual Testing
# Start server in HTTP mode
TRANSPORT=http PORT=3000 npm start
# In 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
dati.gov.it URL
Important: The correct URL for the Italian portal is https://www.dati.gov.it/opendata (not https://dati.gov.it).
Connection error
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.
No results
// 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:
- Fork the project
- Create a branch for the feature (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License
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).
Support
For issues or questions, open an issue on GitHub.
Created with ❤️ by onData for the open data community