I14y Mcp

io.github.malkreide由 GitHub 帳號 malkreide 發布v0.4.0更新於 Oct 24, 2026

MCP server for I14Y — Switzerland's national open-data metadata catalogue (DCAT-AP-CH)

概覽

AI 產生的概覽

讓助理檢索瑞士 I14Y 國家詮釋資料目錄,查出某個資料集由誰發布、透過哪個介面提供、依何種授權。

可以做什麼
針對 I14Y 互通平台(由聯邦統計局維護的瑞士國家詮釋資料目錄)提供唯讀工具。工具涵蓋目錄全文檢索、分頁資料集清冊、含散布方式、下載網址與授權的完整資料集紀錄、已登錄資料服務與公共服務清冊、調和概念與代碼清單、附瑞士 UID 的發布機關、參與目錄,以及 API 可達性檢查。結果由 DCAT-AP-CH 對應為扁平紀錄,並附上來源與溯源資訊。
適用情境
當助理需要先找到瑞士公共資料來源、再查詢資料時使用:確認某個主題的發布機關、介面或下載網址以及授權。它是目錄與連接層,不是資料倉儲,回答的是誰發布什麼,而不是資料內容本身。
執行需求
以 stdio 本機程序執行,透過 uvx i14y-mcp 啟動,需要 Python 與 uv;文件中提供了 Claude Desktop 設定範例。也可用 streamable HTTP 或 SSE 部署,涉及 I14Y_MCP_TRANSPORT、HOST、PORT、I14Y_MCP_ALLOWED_HOSTS 與 I14Y_MCP_CORS_ORIGINS。上游 API 以 HTTPS 呼叫,無需驗證,未宣告任何憑證。
安裝前請注意
所有工具皆為唯讀,未暴露寫入操作。公開 HTTP 部署時,I14Y_MCP_ALLOWED_HOSTS 必須填寫純主機名稱;若在非回送位址綁定下未設定,Host 檢查會完全關閉,日誌會輸出 dns_rebinding_protection_off。未設定 I14Y_MCP_CORS_ORIGINS 時不允許任何瀏覽器來源。授權依散布方式而異,許多要求標示出處或限制商業使用,重複使用前請先閱讀 licence 欄位。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 I14y Mcp,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

Part of the Swiss Public Data MCP Portfolio — a collection of open-source MCP servers connecting AI agents to Swiss public and open data. This is a private project. It is not affiliated with, endorsed by, or operated on behalf of any employer or public authority.

i14y-mcp

[License: MIT] [Python 3.10+] [MCP] [Data: I14Y]

MCP server for the I14Y interoperability platform — Switzerland's national metadata catalogue.

🇩🇪 Deutsche Version


Why this server exists

The other servers in this portfolio answer «what does the data say?». This one answers the question that comes first: «who publishes data on this topic, through which interface, under which licence?»

I14Y is the national data catalogue maintained by the Federal Statistical Office. It describes datasets, registered APIs, public services and harmonised concepts from the Confederation, cantons and communes, using the DCAT-AP-CH profile (eCH-0200).

Mnemonic: «Catalogue before shelf.» Without a catalogue, an agent has to already know a data source exists. With one, it can find it.


🎯 Anchor Demo Query

«Which authority publishes data on special needs education, through which interface is it available, and under which licence?»

search_catalog(query="Sonderpädagogik")  → «Statistik der Sonderpädagogik» — Federal Statistical Office (BFS), theme: Bildung
get_dataset(dataset_id=...)  → 2 distributions, licence: «Opendata BY ASK — attribution required,    commercial use only with permission from the data supplier»  → contact: [email protected]

Two tool calls turn a vague topic into a named authority, a download URL and a licence you can act on — get_dataset aggregates the distributions, licences and contact point into one record.

Demo

[Demo: Claude using search_catalog and get_dataset]


Architecture

                 ┌──────────────────────────────┐                 │      MCP Host (Claude)       │                 └───────────────┬──────────────┘                                 │ stdio | streamable-http                 ┌───────────────▼──────────────┐                 │          i14y-mcp            │                 │  ┌────────────────────────┐  │                 │  │ server.py  (13 tools)  │  │                 │  ├────────────────────────┤  │                 │  │ mappers.py             │  │  DCAT → flat, one language                 │  ├────────────────────────┤  │                 │  │ models.py  (Pydantic)  │  │  source + provenance envelope                 │  ├────────────────────────┤  │                 │  │ client.py              │  │  retry 2s/4s/8s, no-retry 4xx                 │  └────────────────────────┘  │                 └───────────────┬──────────────┘                                 │ HTTPS, no auth                 ┌───────────────▼──────────────┐                 │  api.i14y.admin.ch/api       │                 │  datasets · dataservices ·   │                 │  concepts · publicservices · │                 │  catalogs · agents · search  │                 └──────────────────────────────┘

Architecture decision

This server uses Architecture A (live API only).

Rationale (verified live on 2026-07-21):

  • All read endpoints respond without authentication and paginate correctly.
  • No bulk download of catalogue metadata is offered, and none is needed.
  • Error responses follow RFC 7807, so failure modes are distinguishable.

Consequences:

  • Every HTTP call retries transient failures with 2 s / 4 s / 8 s backoff.
  • search_catalog caps results client-side because the upstream ignores paging.
  • api_status always returns an evaluable state instead of empty records.

Full probe report: docs/probe-i14y.md.

Project phase

This server is in Phase 1 (read-only) of the portfolio's «Read-only First» phase architecture: all tools are read-only, there is no authentication and no personal data. See docs/roadmap.md for the phase model and the prerequisites for any future write capability.


Tools

ToolPurpose
search_catalogFree-text search across the catalogue. Entry point.
list_datasetsPaginated dataset register (complete, unlike search).
get_datasetFull metadata record for one dataset.
get_dataset_distributionsDownload URLs, formats and licences.
list_data_servicesRegister of official Swiss APIs with endpoint URLs.
get_data_serviceFull record for one registered interface.
list_public_servicesAdministrative services for citizens.
list_conceptsHarmonised concepts and code lists.
get_conceptOne concept definition.
search_codelist_entriesIndividual codes of a code list.
list_publishersPublishing bodies, with Swiss UID.
list_catalogsContributing catalogues.
api_statusReachability check with graceful degradation.

All tools are annotated readOnlyHint: true. Write operations exist in the upstream API but are deliberately not exposed.

MCP primitives

This server exposes Tools only — no Resources, no Prompts. That is a deliberate choice, not an omission: I14Y is queried by free-text search and by opaque UUIDs, so there is no small, stable set of addressable URIs that would map cleanly onto MCP Resources, and the server ships no opinionated prompt templates. Every tool is read-only and idempotent; if a future stable entry point emerges (e.g. a fixed theme list) it is a candidate for a Resource.


MCP Protocol Version

This server speaks two protocol eras over the same endpoint. The client's first request on a connection decides which one applies; a later claim from the other era is refused.

EraRevisionWho reaches it
initialize handshake2024-11-05 … 2025-11-25What today's clients speak. The server answers with the revision asked for, or with the 2025-11-25 ceiling when the request asks for something newer.
Per-request envelope2026-07-28A request carrying the 2026-07-28 _meta envelope opens a modern connection.

Both revisions are pinned in tests/test_protocol_version.py and asserted against the installed SDK, so a Dependabot bump of mcp cannot move either one silently. The handshake ceiling is measured against a live initialize through the assembled ASGI stack, not read off a constant name.

Note that the SDK's LATEST_PROTOCOL_VERSION is an alias for the modern era, not for the handshake era — pinning against it alone would leave the era that current clients actually negotiate free to drift.

What the server carries on the modern revision

2026-07-28 moves discovery off the initialize handshake onto server/discover plus a per-request _meta envelope, and gives every cacheable result a freshness hint. What this server puts on those surfaces is measured in tests/test_spec_2026_07_28.py through the assembled stack — and, for stdio, through a real subprocess — rather than read back off the configuration:

SurfaceWhat this server answers
serverInfo, stamped under every modern responsename, display title, description, website, and the installed distribution version — the same version the outbound User-Agent carries
server/discover → instructionshow to sequence the tools, plus the two properties of I14Y that no single tool description shows
ttlMs / cacheScope300 s, public, on all five cacheable methods this server answers
tools[].titlea display name per tool, so a client shows «Search a concept's code list» rather than search_codelist_entries

The freshness hint is not cosmetic. With none set, the SDK answers ttlMs: 0, cacheScope: private — «already stale, never share» — for directories that are fixed at import and cannot change while the process runs.

Both transports serve the modern revision: HTTP through the streamable-HTTP session manager, and stdio — the default, and what uvx i14y-mcp starts — through the same dual-era loop. Neither is evidence for the other, so both are measured.

One thing this server advertises but does not use. On 2026-07-28 the listChanged flags and resources.subscribe derive solely from whether subscriptions/listen is served, and the SDK wires that handler unconditionally. server/discover therefore reports listChanged: true for a tool list that is fixed at import, and a subscribe capability over an empty resource list; no change notification is ever sent. It is not switchable through the public API — only a wholesale replacement of the server/discover handler could do it, which would be a second truth about our own capabilities — so it is pinned by a test instead of papered over.

Update policy. When the gate fails, do not edit the constant blindly: read the spec changelog between the two revisions, verify the server still behaves, then move the constant, this section, README.de.md and CHANGELOG.md together.


Installation

bash
uvx i14y-mcp

Or from source:

bash
git clone https://github.com/malkreide/i14y-mcpcd i14y-mcppip install -e ".[dev]"

Claude Desktop

json
{  "mcpServers": {    "i14y": {      "command": "uvx",      "args": ["i14y-mcp"]    }  }}

Remote deployment (Render, Railway)

bash
I14Y_MCP_TRANSPORT=streamable-http HOST=0.0.0.0 PORT=8000 i14y-mcp

I14Y_MCP_TRANSPORT accepts stdio (the default of the entry point itself), streamable-http (http is a synonym) or sse. The HTTP transports bind to HOST, which defaults to 127.0.0.1 (loopback); set HOST=0.0.0.0 to expose the port on a PaaS (the container image already does both).

Connector URL: https://<host>/mcp

A Claude.ai custom connector speaks Streamable HTTP, and that transport serves exactly one path: /mcp. sse is the older transport and serves /sse and /messages instead — measured through the assembled app, POST /mcp answers 404 there and 200 under Streamable HTTP. The container image therefore defaults to streamable-http; a connector pointed at an SSE deployment gets a 404 and never completes a handshake.

I14Y_MCP_ALLOWED_HOSTS — required for a public deployment

A comma-separated list of the hostnames the server is reached under — hostnames only, no scheme and no port:

bash
I14Y_MCP_ALLOWED_HOSTS=i14y-mcp.up.railway.app,mcp.example.ch

The value is compared literally against the incoming Host header, so https://i14y-mcp.up.railway.app or a trailing :443 matches nothing and every request fails with HTTP 421 Invalid Host header. Behind TLS the browser sends the bare hostname; a non-standard port needs the SDK's only wildcard form, host:*. Loopback stays reachable either way, so container health checks are unaffected.

Leaving it unset on a non-loopback bind does not fall back to something safe. The server cannot guess the name it will be addressed by, and a guessed list would reject every real request, so it switches the Host check off entirely and says so in the log:

dns_rebinding_protection_off
railway.json

railway.json pins the one build setting the deployment cannot get wrong on its own:

json
{ "build": { "builder": "DOCKERFILE", "dockerfilePath": "Dockerfile" } }

With any other builder Railway never looks at the Dockerfile. It derives a start command itself, I14Y_MCP_TRANSPORT is then set nowhere, and main() falls into the stdio branch — the container runs, never opens a port, and the only trace is a failing health check. tests/test_entrypoint.py fails the day that setting changes.

Two things the file deliberately does not carry:

  • No environment variables. Railway's schema has no key for them at any level, so I14Y_MCP_ALLOWED_HOSTS and I14Y_MCP_TRANSPORT belong in the service variables. The trap is that the schema validates values but waves invented keys through: a "variables": { … } block passes validation, your editor stays quiet, and Railway ignores it. A test rejects such a key.
  • No healthcheckPath. Measured through the assembled app, no path answers a GET with 2xx: / and /health are 404, /mcp is 400 without a session and 421 under a foreign Host. A health check pointed at any of them would mark the deployment unhealthy and roll it back. The Dockerfile's TCP check does the right thing instead. Should the server ever grow a real health route, the key may be set — a test then requires that the path actually answers.

CORS exposes the Mcp-Session-Id header so browser MCP clients keep their session. Which browser origins may call the server comes from I14Y_MCP_CORS_ORIGINS, a comma-separated list — unset means no browser client is permitted at all, which is the default. * is still accepted and logs a warning. stdio and other non-browser clients are unaffected either way.

Docker

bash
docker compose up --build      # Streamable HTTP on http://localhost:8000/mcp

The image is a hardened multi-stage build: it runs as a non-root user, ships no build tools, and needs no secrets (the API is unauthenticated). See Dockerfile and compose.yaml.


Join keys

I14Y is a connector layer. Two identifiers make it composable with the rest of the portfolio:

KeyFieldJoins to
Swiss UIDPublisher.uidregister-mcp (Zefix)
Endpoint URLDataServiceSummary.endpoint_urlsany portfolio server wrapping that API

Known limitations

Verified live on 2026-07-21.

  1. The search index covers roughly half the register. search_catalog returns at most 1013 records; list_datasets reaches about 2003. Use list_datasets when completeness matters.
  2. Search returns Datasets only. Filtering by types=["Concept"] or types=["DataService"] yields zero results even though those entities exist. Use list_concepts and list_data_services instead.
  3. The upstream ignores paging on search. The full result set is always returned; this server caps it at 200 records and sets truncated: true.
  4. Licences vary per distribution, not per dataset. Most carry «Opendata BY ASK», which requires attribution and restricts commercial use. Always read the licence field before reuse.
  5. Some metadata fields are simply empty. Frequency, temporal coverage and distribution format are optional and frequently unset by publishers. This is a data-quality property of the catalogue, not a bug in this server.
  6. Not every entry with an endpoint has a URL. Entries labelled only «OpenAPI Spezifikation» without a URI are surfaced as (no URI) <label> rather than dropped.

Testing

bash
PYTHONPATH=src pytest tests/ -m "not live"   # offline, used in CIPYTHONPATH=src pytest tests/ -m "live"       # hits the real APIPYTHONPATH=src pytest tests/                 # everythingpython -m ruff check src tests

The live tests are not decoration: fundstück 4 in the probe report — keywords nesting their language object under label — was caught by a live test after the unit tests were already green.


Contributing

See CONTRIBUTING.md for the ground rules (read-only, one egress host, no secrets) and the local dev loop. Maintainers: PUBLISHING.md covers the PyPI / MCP Registry release process.


Security

See SECURITY.md for the security posture and how to report a vulnerability.


License

MIT License — see LICENSE. The catalogue data remains subject to the terms declared by each publisher.


Author

Hayal Oezkan · github.com/malkreide


Credits & related projects

Licence: MIT. The catalogue data remains subject to the terms declared by each publisher.


MCP Registry

Ownership marker used by the MCP Registry to link this PyPI package to the GitHub namespace:

mcp-name: io.github.malkreide/i14y-mcp

來源:README.md,提交 b364841

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

2
  1. v0.4.0最新Sep 19, 2026
  2. v0.3.2Sep 16, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分