# GovData MCP
A Model Context Protocol (MCP) server that lets AI agents **search, explore, and fetch open datasets from official government data portals**. One unified tool interface across the United States, United Kingdom, Canada, and Australia — with per-portal quirks (API migrations, auth walls, bilingual metadata, stale DataStore flags) absorbed behind a common adapter layer.
Built for the typical agent workflow: *"Is there a government dataset for X?"* → find candidates → inspect the files → preview rows or grab a download link and move on.
### Key Features
- **Four national portals, one interface.** US (data.gov v4 / DCAT), UK (data.gov.uk / CKAN), Canada (open.canada.ca / CKAN), Australia (data.gov.au / CKAN + DataStore).
- **Token-frugal by design.** Descriptions are HTML-stripped and truncated, search results are compact summaries, and large files are never inlined by accident.
- **Row-level preview.** Uses CKAN DataStore where available (Australia), and transparently falls back to reading the head of CSV/TSV files everywhere else.
- **Agent-friendly failure modes.** Errors are returned as actionable text ("use `get_download_link` instead", "retry with `dataset_id`", "available portals: …") — never raw tracebacks.
- **Extensible registry.** Adding another CKAN-based portal is a single config entry; non-CKAN portals plug in via a small adapter class.
- **Dual transport.** `stdio` for local clients (Claude Desktop etc.), stateless `streamable-http` for serverless deployment (Google Cloud Run).
### Supported Portals
| Code | Portal | API | Search | Explore | Row Preview | Notes |
|------|--------|-----|:------:|:-------:|:-----------:|-------|
| `us` | [data.gov](https://catalog.data.gov) | data.gov v4 (DCAT) | ✅ | ✅ | CSV fallback | Requires free [api.data.gov](https://api.data.gov/signup/) key |
| `uk` | [data.gov.uk](https://www.data.gov.uk) | CKAN | ✅ | ✅ | CSV fallback | Resource lookups need the parent `dataset_id` |
| `ca` | [open.canada.ca](https://open.canada.ca/en/open-data) | CKAN | ✅ | ✅ | CSV fallback | Bilingual titles normalized to English |
| `au` | [data.gov.au](https://data.gov.au) | CKAN + DataStore | ✅ | ✅ | ✅ DataStore | Full row queries with text filtering |
### Requirements
- Python 3.11+
- An [api.data.gov](https://api.data.gov/signup/) API key for the US portal (free; other portals need no credentials)
### Installation
```bash
git clone https://github.com/YOUR_ORG/Gov-Stat-MCP-Server.git
cd Gov-Stat-MCP-Server
# pip
pip install -e ".[dev]"
# or conda
conda env create -f environment.yml
conda activate opendata-mcp
```
Copy `.env.example` to `.env` and set your key:
```bash
DATAGOV_API_KEY=your-api-data-gov-key
```
### Getting Started
**Standard config** for most MCP clients (stdio transport):
```json
{
"mcpServers": {
"govdata": {
"command": "govdata-mcp",
"env": {
"DATAGOV_API_KEY": "your-api-data-gov-key"
}
}
}
}
```
Claude Code
```bash
claude mcp add govdata -e DATAGOV_API_KEY=your-key -- govdata-mcp
```
Claude Desktop
Follow the MCP install [guide](https://modelcontextprotocol.io/quickstart/user) and use the standard config above. Note: `govdata-mcp` must be on the PATH Claude Desktop uses — provide an absolute path to the executable inside your virtualenv/conda env if needed, e.g. `/path/to/envs/mcp-env/bin/govdata-mcp`.
Cursor / Windsurf / other JSON-config clients
Use the standard config above in the client's MCP settings file.
HTTP transport (remote / long-running server)
Run the server in HTTP mode:
```bash
MCP_TRANSPORT=http govdata-mcp
```
Then point your client at the endpoint:
```json
{
"mcpServers": {
"govdata": {
"url": "http://localhost:8080/mcp"
}
}
}
```
### Tools
The intended agent flow: `list_portals` → `search_datasets` → `get_dataset` → `preview_resource` / `fetch_resource` / `get_download_link`.
Discovery
- **list_portals**
- Description: List available government open-data portals and their capabilities.
- Parameters: None
- Read-only: **true**
- **search_datasets**
- Description: Full-text search for datasets on a portal. Returns compact summaries with dataset ids.
- Parameters:
- `portal` (string): Portal code — `us`, `uk`, `ca`, `au`
- `query` (string): Search terms, e.g. `"air quality monitoring"`
- `limit` (number, optional): Max results (default 10, max 50)
- `org` (string, optional): Publisher/organization slug filter (CKAN portals)
- `format` (string, optional): Resource format filter, e.g. `"CSV"` (CKAN portals)
- Read-only: **true**
Exploration
- **get_dataset**
- Description: Full dataset metadata — organization, license, tags, description, and the list of downloadable resources with their ids, formats, and sizes.
- Parameters:
- `portal` (string): Portal code
- `dataset_id` (string): Dataset id/slug from `search_datasets`
- Read-only: **true**
- **preview_resource**
- Description: Preview rows from a tabular resource. Tries the portal DataStore first (row counts + text filtering); falls back to reading the head of CSV/TSV files. Non-tabular formats can't be previewed.
- Parameters:
- `portal` (string): Portal code
- `resource_id` (string): Resource id from `get_dataset`
- `rows` (number, optional): Rows to return (default 20, max 100)
- `query` (string, optional): Full-text row filter (DataStore-backed resources only)
- `dataset_id` (string, optional): Parent dataset id — recommended; required on `uk`
- Read-only: **true**
Retrieval
- **fetch_resource**
- Description: Download a small text resource (CSV/JSON/XML/…) and return its content inline, truncated at a size cap on a clean line boundary. Binary content is refused with a pointer to the download URL.
- Parameters:
- `portal` (string): Portal code
- `resource_id` (string): Resource id from `get_dataset`
- `max_kb` (number, optional): Max kilobytes to inline (default 256, cap 512)
- `dataset_id` (string, optional): Parent dataset id — recommended; required on `uk`
- Read-only: **true**
- **get_download_link**
- Description: Return the direct download URL, format, and size for a resource so the agent can proceed on its own. Works for any format, including large/binary files.
- Parameters:
- `portal` (string): Portal code
- `resource_id` (string): Resource id from `get_dataset`
- `dataset_id` (string, optional): Parent dataset id — recommended; required on `uk`
- Read-only: **true**
### Configuration
All settings are environment variables (or a local `.env` file):
| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_TRANSPORT` | `stdio` | `stdio` (local clients) or `http` (streamable-http server) |
| `PORT` | `8080` | Listen port for HTTP transport (injected automatically on Cloud Run) |
| `DATAGOV_API_KEY` | — | api.data.gov key for the US portal; falls back to `DEMO_KEY` (~30 req/hr) |
| `LOG_LEVEL` | `INFO` | Logging verbosity (always written to stderr) |
| `HTTP_TIMEOUT_SECONDS` | `30` | Timeout for outbound portal requests |
| `HTTP_MAX_RETRIES` | `2` | Retries on transient portal errors (5xx / transport), exponential backoff |
| `SEARCH_DEFAULT_LIMIT` | `10` | Default number of search results |
| `NOTES_TRUNCATE_CHARS` | `200` | Description truncation length in search summaries |
| `FETCH_MAX_BYTES` | `524288` | Hard cap for inlined resource content |
### Docker
```bash
make docker-build # build image
make docker-run # run on :8080, exactly as Cloud Run would
```
The image runs the HTTP transport in stateless mode and is ready for serverless platforms (Google Cloud Run deployment script included under `deploy/`).
### Development
```bash
make run # stdio mode
make run-http # HTTP mode on :8080
make inspect # MCP Inspector interactive UI (requires Node.js)
make test # offline unit tests
make test-live # live end-to-end tests against all four real portals
make lint # ruff check + format check
```
The live suite (`pytest -m live`) verifies the full search → explore → preview → fetch chain per country plus portal-specific behavior (Canada's bilingual normalization, UK's dataset-scoped resource lookup, Australia's DataStore paths). Run it before deploying — government portals change without notice.
### Architecture
```text
src/govdata_mcp/
├── server.py # FastMCP instance + tool registration
├── config.py # env-driven settings
├── ckan/ # generic async CKAN client, models, typed errors
├── portals/
│ ├── registry.py # portal registry — single source of truth
│ ├── base.py # Portal config + default (vanilla CKAN) adapter
│ ├── us.py # data.gov v4 DCAT adapter (auth, cursor paging)
│ ├── uk.py # dataset-scoped resource lookup
│ └── ca.py # bilingual metadata normalization
├── tools/ # MCP tools: search, explore, fetch
└── utils/ # formatting (token economy), CSV-head preview
```
**Adding a portal:** for a standard CKAN portal, add one `Portal(...)` entry to `portals/registry.py` — done. For portals with quirks, subclass `PortalAdapter` and override the narrow hooks (`normalize_dataset`, `build_search_params`) or, for non-CKAN APIs, the operation methods themselves (see `us.py` for a full custom adapter).
### TODO / Roadmap
- [ ] **🇨🇳 China portal support** — no unified national open-data API exists; planned as a custom adapter targeting National Bureau of Statistics data (via an existing stats package) with graceful degradation.
- [ ] **🇯🇵 Japan portal support** — e-Gov Data Portal ([data.e-gov.go.jp](https://data.e-gov.go.jp)) is CKAN-compatible and should slot into the existing registry; the richer e-Stat statistics API (app-ID auth, Japanese-language metadata) planned as a dedicated adapter.
- [ ] Offline unit test suite with recorded portal fixtures (respx) for CI
- [ ] Google Cloud Run deployment walkthrough (service is container-ready; script in `deploy/`)
- [ ] US v4 publisher/format search filters (pending API documentation)
- [ ] More CKAN portals (New Zealand [data.govt.nz](https://data.govt.nz) is a near-free addition)
- [ ] Content-type sniffing to rescue mislabeled resource formats
### Security Notes
- This server is **read-only** toward the portals — no tool can create, modify, or delete anything.
- `fetch_resource` and `preview_resource` download from URLs contained in portal metadata; content is size-capped and binary-checked before being returned to the model, but treat fetched content as untrusted input.
- The server itself has no authentication. For remote deployment, put it behind an authenticating proxy or platform-level auth (e.g. Cloud Run with `--no-allow-unauthenticated`).
- Keep `DATAGOV_API_KEY` in `.env` / secret manager — never commit it.
### License
MIT