data-bs-mcp
This MCP server enables discovery, inspection, querying, and export of open datasets from any Huwise/Opendatasoft portal (e.g., data.bs.ch).
Search datasets using semantic (default) or lexical full-text modes, with pagination, refine/exclude filters, ordering, and timezone.
Retrieve detailed dataset metadata: field definitions, schema, publisher info, and record count.
Query actual records with ODSQL where clauses, select/group_by/order_by, limit/offset, and other filters.
Get available facet values (publisher, keyword, theme, features, modified, language) to refine searches.
Generate export/download URLs in formats like CSV, JSON, GeoJSON, XLSX, Parquet, SHP, KML, and more.
Run locally via stdio for MCP clients or as streamable HTTP for ChatGPT/OpenWebUI, or use as a no-server skill.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@data-bs-mcp search for air quality datasets in Basel"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
data-bs-mcp
MCP server for any Huwise/Opendatasoft data portal — query open datasets from data.bs.ch (and other portals on the same platform) through the Explore 2.1 API.
It runs in two modes:
stdio — for local MCP clients (opencode, Cursor, Claude Desktop).
streamable HTTP — hosted as a container, so it can be wired into ChatGPT connectors and OpenWebUI. Also ships a skill as a no-server alternative.
Installation
The toolchain (uv only; Python version comes from pyproject.toml) and the task
runner are managed by mise. Enter the project and trust
the config once:
mise trust
mise install # provisions uv, runs the postinstall hookThen set up the virtual environment and dependencies:
mise run install # alias: i — uv sync --lockedThe postinstall hook runs install automatically on tool provisioning.
Related MCP server: mcp-data-strasbourg
Tasks
All standard DCC task names are available via mise run <task>; see
mise.toml. In Docker the install task syncs --no-dev (driven by
DOCKER_BUILD=1).
Task | Alias | Description |
|
| Create venv and install deps ( |
|
| MCP server over stdio (local clients) |
|
| Streamable HTTP on |
|
| Verify lockfile, format, and lint (ruff) |
|
| Run the unit test suite (pytest) |
Selecting a catalog
The catalog is chosen by whoever runs the server. Configuration is read from the
DATA_PORTAL_DOMAIN environment variable first, else a local .env file next to
main.py. Copy the example and fill it in:
cp .env.example .env# .env
DATA_PORTAL_DOMAIN=data.bs.ch
MCP_ALLOWED_HOSTS=mcp.bs.ch:* # optional, see HostingThe API base URL is built as https://<domain>/api/explore/v2.1. All
Huwise/Opendatasoft portals share this path, so changing the domain targets a
different portal.
Hosting (streamable HTTP)
Deploy as a container. The image is built against the DCC shared base image
(ghcr.io/dcc-bs/dcc-docker-images/mise:13-slim); see Dockerfile.
The MCP endpoint is mounted at /mcp (streamable HTTP).
docker build -f Dockerfile -t mcp-data-bs .
docker run --rm -p 8000:8000 -e DATA_PORTAL_DOMAIN=data.bs.ch mcp-data-bsHealthcheck: GET /healthz -> {"status":"ok"}.
API reference
The HTTP server exposes an OpenAPI description of the MCP endpoint with a rendered Swagger UI:
GET /docs— interactive reference (Swagger UI). Each tool appears as atools/callrequest schema with a working example you can execute against the server via Try it out.GET /openapi.json— the raw OpenAPI 3.1 spec.
The spec is generated from the live MCP tool registry at request time, so new
tools appear automatically. It covers all endpoints (POST /mcp, GET /healthz,
GET /). Responses are text/event-stream; each data: frame carries a
JSON-RPC message. The server is stateless — requests need no initialize
handshake and no session id.
Client compatibility: POST requests with incomplete Accept headers (e.g. only
application/json or */*) are rewritten to
application/json, text/event-stream by a middleware before the transport
validates them, so plain JSON-RPC clients work unmodified. GET / returns a
JSON status page (server, transport, endpoint links) instead of holding an SSE
connection open — clients expecting a traditional SSE handshake get an
immediate, meaningful response.
Remote hosting requires
MCP_ALLOWED_HOSTS. The MCP HTTP endpoint has DNS-rebinding protection on by default and, without config, accepts only localhostHostheaders — a remote deploy would get421 Invalid Host header. When the server is reachable via a public hostname, list it (comma-separated, port-wildcard allowed):MCP_ALLOWED_HOSTS="mcp.bs.ch:*". When unset, protection is disabled so anyHostheader is accepted.
Docker Compose
compose.yml pulls the published GHCR image, sets the domain and allowed
hostname(s), and healthchecks: docker compose up -d.
Publishing (CI)
This repo uses the DCC reusable workflows (ci-workflows):
.github/workflows/ci.ymlrunsmise run checkandmise run test:unitautomatically on push/PR (tasks absent frommise.tomlare skipped)..github/workflows/publish.yml(manualworkflow_dispatch) builds and pushes to GHCR usingpublish-docker.yml@v2. Bump the version inpyproject.toml, then dispatch to tag the image<version>+latest.
Connecting clients
The snippets below are self-contained: stdio clients launch the server from
GitHub via uvx (needs uv installed), HTTP
clients point at the hosted deployment. Set DATA_PORTAL_DOMAIN to another
portal domain to target a different catalog.
Antigravity CLI (agy) — one command:
agy mcp add --env DATA_PORTAL_DOMAIN=data.bs.ch databs -- \
uvx --from git+https://github.com/DCC-BS/mcp-data-bs data-bs-mcpClaude Desktop / Cursor — add to claude_desktop_config.json
(Claude Desktop → Settings → Developer → Edit Config) or ~/.cursor/mcp.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" }
}
}
}OpenWebUI / ChatGPT — point at the streamable HTTP endpoint
https://mcp.data.bs.ch/mcp (no auth):
OpenWebUI: Settings → Tools → add
https://mcp.data.bs.ch/mcpChatGPT (developer-mode connector): Settings → Connectors → Create → add
https://mcp.data.bs.ch/mcp; all five tools are exposed and usable, and the interactive API reference lives athttps://mcp.data.bs.ch/docs.
Other clients
opencode: add to OpenCode config:
{ "mcpServers": { "data-bs": { "command": "uv", "args": ["--directory", "/ABSOLUTE/PATH/TO/data-bs-mcp", "run", "main.py"] } } }uvx (anywhere): set
DATA_PORTAL_DOMAINin your environment first (the.envis no longer committed).uvxruns the same code as a local checkout.DATA_PORTAL_DOMAIN=data.bs.ch \ uvx --from git+https://github.com/DCC-BS/mcp-data-bs data-bs-mcp
Skills (no MCP needed)
Prefer not to run a server? Install the agent skill instead — it teaches an
agent to hit the public REST API directly with plain HTTP (curl/httpx). See
skills/data-bs/SKILL.md; copy or symlink it into your agent's skills directory
(e.g. ~/.agents/skills/data-bs).
Tools
get_datasets
Search and list available datasets.
Two search modes:
semantic(default): ranks the catalog by meaning using thevector_similarityexplore endpoint from Huwise. Best for natural-language / conceptual queries. Matches synonyms and other languages.lexical: classic full-text match on the exact terms.
# semantic (default) — natural language, ranked by relevance
get_datasets(search="air quality measurements")
# lexical — exact full-text match
get_datasets(search="luft", search_mode="lexical")
# combine with facet filters
get_datasets(search="bevölkerung", refine="publisher:Statistisches Amt")get_datasets also returns next_offset for paging (null when there is no next page).
get_dataset
Get detailed metadata for a specific dataset (fields with name, type, label, short description, unit, sortable/facet flags; publisher; url, title, modified).
get_dataset(dataset_id="100113")get_records
Query records from a dataset with ODSQL filtering. The result is citable on its own: dataset_id, url, title, modified (the dataset's Stand; title and modified come from a cached catalog lookup and are omitted if it fails), plus total_count, next_offset (null on the last page) and results. Output is capped at about 12,000 characters; if rows were cut, truncated: true and a hint say so (use select, group_by, a smaller limit or next_offset).
get_records(dataset_id="100113", where="pm25 > 10", limit=100, order_by="time DESC")get_facets
Get available facet values for filtering. Without facet, every facet is returned with at most 30 values and total_values; with a name, up to 100.
get_facets(facet="publisher") # Options: publisher, keyword, theme, features, modified, languageexport_dataset_url
Get download URL for dataset export. Returns an object {download_url, format, dataset_id, url, title, modified} (older versions returned the URL as a plain string).
export_dataset_url(dataset_id="100113", format="csv", where="sensornr=240")Formats: csv, json, geojson, xlsx, shp, parquet, gpx, kml, rdfxml, jsonld, turtle
Debug
npx @modelcontextprotocol/inspector uv run main.pyAvailable Tools
5 toolsexport_dataset_urlGet Export URLA
Generate a download URL for exporting a dataset in various formats (CSV, JSON, GeoJSON, XLSX, Shapefile, Parquet, etc.). Use this when you need to download or share dataset exports.
Inputs: dataset_id (required), where, format. Returns: result
get_datasetGet Dataset MetadataA
Get detailed metadata for a specific dataset including field definitions, schema, publisher info, and record count. Use this to understand a dataset's structure before querying records.
Inputs: dataset_id (required).
get_datasetsSearch DatasetsA
Search and list available open datasets from data.bs.ch. Two modes: 'semantic' (default) ranks the catalog by meaning using natural-language queries (handles synonyms and other languages); 'lexical' does a classic full-text match on the exact terms. Use semantic for conceptual discovery, lexical for precise term/name lookups.
Inputs: limit, offset, refine, search, +5 more.
get_facetsGet Facet ValuesB
Get available filter values for categorizing datasets. Useful for discovering publishers, keywords, themes, or other facets to refine dataset searches.
Inputs: facet.
get_recordsQuery Dataset RecordsA
Query and filter records from a dataset using ODSQL syntax. Use this to retrieve actual data from a dataset with optional WHERE clauses, ordering, and pagination.
Inputs: dataset_id (required), lang, limit, where, +8 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 5 tools
Each tool targets a distinct stage: search (get_datasets), metadata (get_dataset), facets (get_facets), records (get_records), and export (export_dataset_url). Minor potential confusion exists between get_datasets and get_facets for discovery, but descriptions clarify their different purposes.
All tools follow a verb_noun pattern, with four using 'get_' and one using 'export_', both being clear verbs. The naming is consistent and predictable across the set.
Five tools is well-scoped for a data portal: search, metadata, facets, records, and export cover the core read workflows without unnecessary bloat.
The set covers the full read lifecycle: discover datasets, understand their structure, query records, and export data. No obvious missing operations for a public read-only open data portal.
Maintenance
Related MCP Connectors
This MCP server provides seamless access to Malaysia's government open data, including datasets, w…
data.overheid.nl MCP — the Netherlands national open-data portal (CKAN API).
MCP server for the Seline Analytics API
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server to query French Open Data from data.gouv.frMIT
- AlicenseNot gradedqualityBmaintenanceMCP server for accessing Strasbourg Open Data (data.strasbourg.eu). Allows searching datasets, retrieving metadata, and querying records with ODSQL.109 npmMIT
- AlicenseAqualityDmaintenanceAn MCP server to find and fetch Norwegian open government data from data.norge.no. Enables search, metadata retrieval, and data download.54MIT
- AlicenseNot gradedqualityBmaintenanceMCP server for the French open-data catalogue data.gouv.fr, enabling dataset search and retrieval, organization lookup, and reuse discovery via natural language queries.101 npmMIT