Skip to content

Data.bs.ch MCP ​

Finds and queries datasets of the open government data (OGD) portal data.bs.ch.

Endpointhttps://mcp.data.bs.ch/mcp
API referencemcp.data.bs.ch/docs
CodeDCC-BS/mcp-data-bs
Imageghcr.io/dcc-bs/mcp-data-bs
Data sourceExplore API v2.1 of the portal, https://<domain>/api/explore/v2.1

data.bs.ch runs on Huwise, formerly Opendatasoft. Every portal on that platform has the same API path, so the server is not tied to Basel-Stadt. DATA_PORTAL_DOMAIN picks the portal, and the DCC deployment sets it to data.bs.ch.

Tools ​

ToolWhat it does
get_datasetsSearches the catalog. semantic mode (default) ranks by meaning through Huwise's vector_similarity endpoint and finds synonyms and other languages. lexical mode matches the exact words. refine filters by facet, next_offset pages
get_datasetMetadata of one dataset: title, URL, modified date, publisher and the fields with name, type, label, unit and whether they can be sorted or faceted
get_recordsRows of a dataset with ODSQL select, where, group_by, order_by and limit. The result carries dataset_id, url, title and modified, so it can be cited on its own
get_facetsValid filter values for the catalog: publisher, keyword, theme, features, modified, language
export_dataset_urlDownload link for an export, optionally filtered. Formats: csv, json, geojson, xlsx, shp, parquet, gpx, kml, rdfxml, jsonld, turtle

A typical call sequence:

text
get_datasets(search="Luftqualität Messwerte")
get_dataset(dataset_id="100113")
get_records(dataset_id="100113", where="pm25 > 10", order_by="time DESC", limit=100)
export_dataset_url(dataset_id="100113", format="csv", where="sensornr=240")

Things a model gets wrong ​

These came up in the deep research evaluation, and the tools and the agent's hints were adjusted for them.

  • Field names. Models guess field names from the dataset title. get_dataset lists the real ones, so call it before get_records. A wrong name comes back as the portal's ODSQL error, which the model can act on.
  • Year fields are often text. where jahr >= 2016 fails with a type error on many datasets. Use group_by=jahr, order_by="jahr desc" and limit, or where jahr in ("2015","2024").
  • Big results. get_records caps its output at about 12,000 characters. When it cuts rows it sets truncated: true and a hint that suggests select, group_by, a smaller limit or next_offset.

get_records looks up the title and modified date in the catalog and keeps them in memory for one hour, so a records call costs at most one extra request.

Configuration ​

VariableDefaultMeaning
DATA_PORTAL_DOMAINnone, requiredPortal domain, for example data.bs.ch
MCP_ALLOWED_HOSTSunset, protection offAllowed Host headers, see Hosting
PORT8000HTTP port in the container

The server reads the environment first, then a .env next to main.py.

Connecting a client ​

Hosted, for Open WebUI, ChatGPT and other HTTP clients: add https://mcp.data.bs.ch/mcp as an MCP tool server. No key is needed.

Locally over stdio, for example in the Antigravity CLI:

bash
agy mcp add --env DATA_PORTAL_DOMAIN=data.bs.ch databs -- \
  uvx --from git+https://github.com/DCC-BS/mcp-data-bs data-bs-mcp

Or in claude_desktop_config.json and ~/.cursor/mcp.json:

json
{
  "mcpServers": {
    "data-bs": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/DCC-BS/mcp-data-bs", "data-bs-mcp"],
      "env": { "DATA_PORTAL_DOMAIN": "data.bs.ch" }
    }
  }
}

Developed with ❤️ by the DCC. Documentation released under the MIT License.