mcp-i14y
by fgouzi
README.md
# mcp-i14y
[](https://www.python.org)
[](LICENSE)
[](https://modelcontextprotocol.io)
**An MCP server that connects AI assistants to the [Swiss I14Y Interoperability Platform](https://www.i14y.admin.ch).**
Ask Claude, Gemini, or GitHub Copilot to explore Swiss government datasets, APIs, codelists, and public services in natural language — powered by the [Model Context Protocol](https://modelcontextprotocol.io).
> Inspired by the French [datagouv-mcp](https://github.com/datagouv/datagouv-mcp) project, adapted for the Swiss I14Y platform.
---
## What is I14Y?
The **I14Y Interoperability Platform** (operated by the Swiss Federal Statistical Office) is Switzerland's national catalog for government data. It standardises and publishes metadata for:
- **Datasets** — data published by federal and cantonal bodies
- **Data Services** — APIs exposing government data
- **Concepts** — reusable data definitions, codelists, and data dictionaries
- **Public Services** — administrative services offered to citizens and businesses
---
## Features
| Tool | Description |
|---|---|
| `list_datasets` | List datasets with filters (publisher, status, level) |
| `get_dataset` | Get full metadata for a dataset |
| `get_dataset_structure` | Export dataset schema (JSON-LD, Turtle, RDF) |
| `list_dataservices` | List data services (APIs) with filters |
| `get_dataservice` | Get full metadata for a data service |
| `list_concepts` | List concepts (codelists, dictionaries) with filters |
| `get_concept` | Get concept details, optionally with codelist entries |
| `get_concept_codelist` | Export all codelist entries (JSON or CSV) |
| `list_publicservices` | List public services with filters |
| `get_publicservice` | Get full metadata for a public service |
| `get_catalog` | Export a catalog in DCAT-AP format (Turtle or RDF/XML) |
| `list_mappingtables` | List mapping tables (source → target codelist correspondences) |
| `get_mappingtable` | Get full metadata for a mapping table |
| `get_mappingtable_relations` | Export all mapping relations (value correspondences) as JSON or CSV |
| `catalog_search` | Full-text search across all resource types (server-side, via CORE API) |
| `get_dataset_by_identifier` | Get a dataset by its short identifier (not UUID) |
| `check_dataset_has_structure` | Check whether a dataset has a structural model defined |
| `get_dataset_model_graph` | Get the dataset schema as a schema graph (nodes/edges) |
| `get_dataservice_by_identifier` | Get a data service by its short identifier |
| `get_publicservice_by_identifier` | Get a public service by its short identifier |
| `get_publicservice_relations` | Get related public services |
| `get_concept_by_identifier` | Get concept(s) by short identifier (e.g. "HGDE_KT") |
| `get_codelist_entries` | Paginated codelist entries with full annotations |
| `get_codelist_entry_by_code` | Look up a single codelist entry by code value |
| `get_codelist_entries_children` | Navigate hierarchical codelists (children of a code) |
| `search_codelist_entries` | Search entries within a specific codelist |
| `list_catalogs` | List all DCAT catalogs |
| `get_catalog_records` | Get records (resources) from a catalog |
| `get_catalog_themes` | Get themes used in a catalog (DCAT-AP alignment) |
| `list_agents` | List all publishing organisations (with identifiers for filtering) |
| `get_agent` | Get full metadata for a publishing organisation |
| `list_vocabularies` | List all controlled vocabularies (themes, licenses, formats…) |
| `get_vocabulary` | Get all entries of a controlled vocabulary for RDF/DCAT-AP use |
| `get_distribution_content` | Fetch the actual content of a distribution file (CSV, JSON, XML…) |
---
## Quick Start
### Option 1: Docker Compose (recommended)
```bash
git clone https://github.com/fgouzi/mcp-i14y.git
cd mcp-i14y
cp .env.example .env
docker compose up -d
```
The server will be available at `http://localhost:8400/mcp`.
### Option 2: Local setup with uv
```bash
git clone https://github.com/fgouzi/mcp-i14y.git
cd mcp-i14y
uv sync
uv run python main.py
```
### Verify
```bash
curl http://localhost:8400/health
# → {"status":"ok","platform":"i14y","version":"0.1.0"}
```
---
## Connect to your AI assistant
The MCP endpoint is `http://localhost:8400/mcp` (Streamable HTTP / JSON-RPC).
### Claude Desktop
Add to `claude_desktop_config.json`:
> macOS/Linux: `~/.claude/claude_desktop_config.json` · Windows: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"i14y": {
"type": "http",
"url": "http://localhost:8400/mcp"
}
}
}
```
### Claude Code (CLI)
```bash
/mcp add i14y http://localhost:8400/mcp
```
### GitHub Copilot (VS Code)
Requires **VS Code 1.112+** with the GitHub Copilot Chat extension.
A `.vscode/mcp.json` file is already included in this repository. After starting the server:
1. Open the repo in VS Code — a **"Start"** button appears at the top of `.vscode/mcp.json`
2. Click **Start** (or run `MCP: List Servers` from the Command Palette)
3. Open Copilot Chat (`Ctrl+Shift+I` / `Cmd+Shift+I`) and switch to **Agent** mode
4. Click the **Tools** icon to confirm the i14y tools are available
To configure manually in another project, create `.vscode/mcp.json`:
```json
{
"servers": {
"i14y": {
"type": "http",
"url": "http://localhost:8400/mcp"
}
}
}
```
### Mistral Le Chat
1. Open [Le Chat](https://chat.mistral.ai) → side panel → **Intelligence** → **Connectors**
2. Click **+ Add Connector** → **Custom MCP Connector**
3. Fill in:
- **Connector name**: `i14y`
- **Connection server URL**: `http://localhost:8400/mcp`
- **Authentication**: None
4. Click **Connect**
5. Enable it in any conversation via the **Tools icon** (four squares) below the chat input
### Mistral Vibe (CLI agent)
Add to your `config.toml`:
```toml
[[mcp_servers]]
name = "i14y"
transport = "streamable-http"
url = "http://localhost:8400/mcp"
```
Tools are then available as `i14y_list_datasets`, `i14y_get_concept`, etc.
### MCP Inspector (testing)
```bash
npx @modelcontextprotocol/inspector http://localhost:8400/mcp
```
---
## Available Tools
### Datasets
#### `list_datasets`
List datasets from the I14Y platform with optional filters.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `publisher_identifier` | string | — | Filter by publisher organisation ID |
| `registration_status` | string | — | `Initial`, `Candidate`, `Recorded`, `Qualified`, `Standard`, `PreferredStandard`, `Superseded`, `Retired` |
| `publication_level` | string | — | `Internal` or `Public` |
| `access_rights` | string | — | Access restriction code |
| `dataset_identifier` | string | — | Filter by dataset identifier |
| `page` | integer | `1` | Page number |
| `page_size` | integer | `25` | Results per page |
#### `get_dataset`
| Parameter | Type | Description |
|---|---|---|
| `dataset_id` | string | Dataset unique identifier |
#### `get_dataset_structure`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `dataset_id` | string | — | Dataset unique identifier |
| `format` | string | `JsonLd` | `JsonLd`, `Ttl`, or `Rdf` |
---
### Data Services
#### `list_dataservices`
Same filter parameters as `list_datasets` (with `dataservice_identifier` instead of `dataset_identifier`).
#### `get_dataservice`
| Parameter | Type | Description |
|---|---|---|
| `dataservice_id` | string | Data service unique identifier |
---
### Concepts
#### `list_concepts`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `publisher_identifier` | string | — | Filter by publisher |
| `concept_identifier` | string | — | Filter by concept identifier |
| `version` | string | — | Filter by version |
| `registration_status` | string | — | Registration status filter |
| `publication_level` | string | — | `Internal` or `Public` |
| `page` | integer | `1` | Page number |
| `page_size` | integer | `25` | Results per page |
#### `get_concept`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `concept_id` | string | — | Concept unique identifier |
| `include_codelist_entries` | boolean | `false` | Embed codelist entries in response |
#### `get_concept_codelist`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `concept_id` | string | — | Concept unique identifier |
| `format` | string | `json` | `json` or `csv` |
---
### Public Services
#### `list_publicservices`
Same filter parameters as `list_datasets` (with `publicservice_identifier`).
#### `get_publicservice`
| Parameter | Type | Description |
|---|---|---|
| `publicservice_id` | string | Public service unique identifier |
---
### Catalogs
#### `get_catalog`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `catalog_id` | string | — | Catalog unique identifier |
| `format` | string | `ttl` | `ttl` (Turtle) or `rdf` (RDF/XML) |
---
### Mapping Tables
A mapping table defines a correspondence between two codelists (source → target), enabling semantic alignment across classification systems (e.g. old canton codes → new codes, Swiss codes → European standards).
#### `list_mappingtables`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `publisher_identifier` | string | — | Filter by publisher organisation ID |
| `mappingtable_identifier` | string | — | Filter by mapping table identifier |
| `version` | string | — | Filter by version string |
| `registration_status` | string | — | `Initial`, `Candidate`, `Recorded`, `Qualified`, `Standard`, `PreferredStandard`, `Superseded`, `Retired` |
| `publication_level` | string | — | `Internal` or `Public` |
| `page` | integer | `1` | Page number |
| `page_size` | integer | `25` | Results per page |
#### `get_mappingtable`
| Parameter | Type | Description |
|---|---|---|
| `mappingtable_id` | string | Mapping table unique identifier (UUID) |
#### `get_mappingtable_relations`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `mappingtable_id` | string | — | Mapping table unique identifier (UUID) |
| `format` | string | `Json` | `Json` or `Csv` |
---
### Search
Since the I14Y API has no full-text search endpoint, these tools fetch pages progressively and rank results client-side.
#### `catalog_search`
Full-text search across all I14Y resource types (server-side, via CORE API).
| Parameter | Type | Default | Description |
|---|---|---|---|
| `query` | string | — | Free-text search query (any language) |
| `types` | list | — | Filter by type(s): `Dataset`, `DataService`, `PublicService`, `Concept`, `MappingTable` |
| `publishers` | list | — | Filter by publisher identifier(s), e.g. `["CH1"]` |
| `statuses` | list | — | Filter by registration status(es) |
| `page` | integer | `1` | Page number |
| `page_size` | integer | `25` | Results per page |
---
### Concepts (CORE)
#### `get_concept_by_identifier`
| Parameter | Type | Description |
|---|---|---|
| `identifier` | string | Short identifier string (e.g. `"HGDE_KT"`, `"CL_NOGA"`) |
#### `get_codelist_entries`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `concept_id` | string | — | Concept UUID |
| `page` | integer | `1` | Page number |
| `page_size` | integer | `100` | Results per page |
#### `get_codelist_entry_by_code`
| Parameter | Type | Description |
|---|---|---|
| `concept_id` | string | Concept UUID |
| `code` | string | Code value to look up (e.g. `"1"`, `"CH"`) |
#### `get_codelist_entries_children`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `concept_id` | string | — | Concept UUID |
| `parent_code` | string | — | Parent code whose children to retrieve |
| `page` | integer | `1` | Page number |
| `page_size` | integer | `100` | Results per page |
#### `search_codelist_entries`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `concept_id` | string | — | Concept UUID |
| `query` | string | — | Search term |
| `language` | string | `fr` | Language for label matching: `fr`, `de`, `it`, `en` |
| `page` | integer | `1` | Page number |
| `page_size` | integer | `25` | Results per page |
---
### Datasets (CORE)
#### `get_dataset_by_identifier`
| Parameter | Type | Description |
|---|---|---|
| `identifier` | string | Dataset short identifier (e.g. `"px-x-0602010000_109"`) |
#### `check_dataset_has_structure`
| Parameter | Type | Description |
|---|---|---|
| `dataset_id` | string | Dataset UUID |
Returns `true` if a structural model exists, `false` otherwise. Use this before calling `get_dataset_structure()` to filter datasets that have a documented schema.
#### `get_dataset_model_graph`
| Parameter | Type | Description |
|---|---|---|
| `dataset_id` | string | Dataset UUID |
Returns the dataset schema as a graph (nodes/edges) — suited for programmatic processing. Complements `get_dataset_structure()` which returns RDF/JSON-LD.
---
### Data Services (CORE)
#### `get_dataservice_by_identifier`
| Parameter | Type | Description |
|---|---|---|
| `identifier` | string | Data service short identifier |
---
### Public Services (CORE)
#### `get_publicservice_by_identifier`
| Parameter | Type | Description |
|---|---|---|
| `identifier` | string | Public service short identifier |
#### `get_publicservice_relations`
| Parameter | Type | Description |
|---|---|---|
| `publicservice_id` | string | Public service UUID |
---
### Catalogs (CORE)
#### `list_catalogs`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `page` | integer | `1` | Page number |
| `page_size` | integer | `25` | Results per page |
#### `get_catalog_records`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `catalog_id` | string | — | Catalog UUID |
| `page` | integer | `1` | Page number |
| `page_size` | integer | `25` | Results per page |
#### `get_catalog_themes`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `catalog_id` | string | — | Catalog UUID |
| `page` | integer | `1` | Page number |
| `page_size` | integer | `100` | Results per page |
---
### Agents
#### `list_agents`
| Parameter | Type | Default | Description |
|---|---|---|---|
| `page` | integer | `1` | Page number |
| `page_size` | integer | `25` | Results per page |
#### `get_agent`
| Parameter | Type | Description |
|---|---|---|
| `agent_id` | string | Agent UUID |
---
### Vocabularies
Controlled vocabularies define valid values for DCAT-AP metadata fields (themes, access rights, licenses, media types, etc.).
#### `list_vocabularies`
No parameters. Returns all available vocabulary configurations with their identifiers.
#### `get_vocabulary`
| Parameter | Type | Description |
|---|---|---|
| `identifier` | string | Vocabulary identifier (e.g. `"Concept_DATASET_THEME"`, `"VOCAB_EU_FREQUENCY"`) |
---
### Distributions
#### `get_distribution_content`
Fetch the actual data content of a DCAT distribution file.
Typical workflow:
1. Call `get_dataset(dataset_id)` → find `distributions[n].downloadUrl.uri`
2. Call `get_distribution_content(url)` → returns the file content
Supported formats: JSON, CSV, XML, RDF/Turtle, GeoJSON, plain text.
Binary formats (PDF, ZIP, Excel, images) are rejected with an error message.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `download_url` | string | — | The `downloadUrl.uri` value from a distribution object |
| `max_kb` | integer | `200` | Maximum content size in KB — larger files are truncated with a warning |
---
## Example Prompts
Once connected, try asking your LLM assistant:
- *"List all public datasets published by the Federal Statistical Office."*
- *"What data services are available with status Standard?"*
- *"Show me the codelist for Swiss canton codes."*
- *"Get the full details for dataset with ID `abc-123`."*
- *"Export the catalog of the OFS in Turtle format."*
- *"What public administrative services are registered on I14Y?"*
- *"Search for datasets about employment by canton."*
- *"Find the concept codelist for 'Secteur économique'."*
- *"List all mapping tables that link old canton codes to new ones."*
- *"Export the mapping relations for mapping table `xyz-456` as CSV."*
- *"Find the dataset with identifier `px-x-0602010000_109`."*
- *"Get the valid values for DCAT themes (data subjects)."*
- *"Which organisations publish data on I14Y?"*
- *"Get all child codes of canton 1 in the commune hierarchy."*
---
## Development
### Setup
```bash
git clone https://github.com/fgouzi/mcp-i14y.git
cd mcp-i14y
uv sync
cp .env.example .env
```
### Run tests
```bash
# Unit tests only (no network required)
uv run pytest
# Including integration tests (hits live I14Y test API)
uv run pytest -m integration
```
### Lint & format
```bash
uv run ruff check --fix .
uv run ruff format .
```
### Install pre-commit hooks
```bash
uv run pre-commit install
```
---
## Configuration
Copy `.env.example` to `.env` and adjust:
| Variable | Default | Description |
|---|---|---|
| `MCP_HOST` | `0.0.0.0` | Bind address (`127.0.0.1` for local-only) |
| `MCP_PORT` | `8400` | Server port |
| `I14Y_API_ENV` | `prod` | `prod` or `test` |
| `LOG_LEVEL` | `INFO` | Logging level |
| `SENTRY_DSN` | _(empty)_ | Optional Sentry DSN |
---
## License
MIT — see [LICENSE](LICENSE).
---
## Credits
- Inspired by [datagouv-mcp](https://github.com/datagouv/datagouv-mcp) by the [data.gouv.fr](https://www.data.gouv.fr) team
- Built on [FastMCP](https://github.com/jlowin/fastmcp) and the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- Data provided by the [Swiss Federal Statistical Office (OFS/BFS)](https://www.bfs.admin.ch) via [I14Y](https://www.i14y.admin.ch)
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues