lindas-mcp について
lindas-mcpは、スイス行政機関のリンクトデータグラフLINDASを照会するMCPサーバーです。統計データキューブを扱うエージェント向けに、無制限のSPARQLではなく制御されたツールを通じて、構造とコード値をラベルに変換した観測値を提供します。
主な機能
- 01統計データキューブの検索
- 02次元、測定値、ライセンスの確認
- 03コード値をラベルに変換した観測値の照会
- 04データを公開する連邦機関の一覧
- 05自治体名、URI、BFS番号の解決
- 06制限付きSPARQLクエリの実行
活用シーン
- 01テーマに関連するデータキューブを検索
- 02人が読めるラベル付きで観測値を確認
- 03キューブの次元とライセンスを確認
- 04データ結合に使う自治体識別子を解決
MCP 対応
MCPクライアントに、LINDASデータキューブの検索・照会、自治体情報の解決、制限付きSPARQLを行う7つの読み取り専用ツールを公開します。生のSPARQLは、上限付きの上級者向けツールからのみ利用できます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
Swiss Public Data MCP Portfolio の一部です — AI エージェントをスイスの公共データおよびオープンデータに接続する、オープンソースの MCP サーバーのコレクションです。 これは個人プロジェクトです。雇用主や公的機関とは一切関係なく、承認を受けたものでも、これらに代わって運営されているものでもありません。
lindas-mcp
スイス行政機関のリンクトデータ・ナレッジグラフ、LINDAS 用の MCP サーバーです。
🇩🇪 ドイツ語版
LINDAS とは
LINDAS(Linked Data Service)は、連邦公文書館が運用するスイス連邦の SPARQL ナレッジ グラフです。表形式ではなく、RDF トリプルとしてデータを公開しています。連邦 機関が提供する約 2000 件の統計 データキューブ(cube.link)に加え、 visualize.admin.ch を支える地理情報とリンクされたデータも含まれています。
覚え方:「I14Y は図書館の目録、LINDAS は図書館そのものです。」 i14y-mcp はデータセットの存在を教えてくれます。 LINDAS はデータを保持し、すべてを一度に横断してクエリできます。
このサーバーは、生の SPARQL を公開するのではなく、ガード付きのツールを通じて LINDAS を利用します。 LINDAS のストアでは、精密なクエリは適切に処理されますが、範囲の広いクエリはタイムアウトするためです。
🎯 デモ用の代表的なクエリ
「現在適用されている森林火災の危険度はどのレベルで、誰が公表しており、 どのライセンスが適用されていますか?」
search_cubes(query="waldbrand")
→ «Waldbrandgefahr» — BAFU, published
get_cube_structure(cube_uri=...)
→ dimensions: Warnregion (key), Gefahrenstufe (measure)
→ licence: fedlex.data.admin.ch/eli/cc/1984/... (a Fedlex URI!)
query_cube_observations(cube_uri=...)
→ Warnregion: "Dorneck / Thierstein (SO)", Gefahrenstufe: "grosse Gefahr"
コードは数値の 4 ではなく、「大きな危険」のようなラベルで返されます。また、ライセンスは
Fedlex の URI であり、Fedlex MCP で解決できます。
デモ
2 段階のアクセスパターン
LINDAS のキューブは自己記述型ですが、値はコード化されています。サーバーは読み取る際に 次の 2 段階で処理します。
- 構造 — キューブの SHACL シェイプです。ディメンション(フィルター可能な軸)、 メジャー(数値)、コードリストを持つディメンションを示します。
- データ — 観測値です。構造を使って、コード化された値を人が読めるラベルに 解決します。
覚え方:「LINDAS が使うのは地名ではなく郵便番号です。」 観測値には 地域が
1805と記録されていますが、サーバーが「Alpennordhang」に変換します。
どちらの段階も query_cube_observations の内部で実行されます。 このツールは
構造を自ら取得するため、呼び出し側が読み取り可能な行を得るために、あらかじめ
get_cube_structure を呼び出す必要はありません。呼び出すと、キューブのメタデータと
ディメンションのクエリが繰り返されるだけです。get_cube_structure は、キューブに
何が含まれるか(ディメンション名、キーかメジャーか、ライセンス)を調べたり、
キューブに対する run_sparql クエリを作成したりするためのツールです。各ディメンションに
コードリストがあるかどうかだけを返し、その項目は返さないため、コードの解読にも使えません。
アーキテクチャ
┌──────────────────────────────┐
│ MCP Host (Claude) │
└───────────────┬──────────────┘
│ stdio | streamable-http
┌───────────────▼──────────────┐
│ lindas-mcp │
│ ┌────────────────────────┐ │
│ │ server.py (7 tools) │ │ talks only to cube.py
│ ├────────────────────────┤ │
│ │ lindas/cube.py │ │ ← vocabulary guardrail,
│ │ │ │ two-phase access,
│ │ │ │ code→label resolution
│ ├────────────────────────┤ │
│ │ lindas/queries.py │ │ SPARQL templates,
│ │ │ │ all anchored on a class
│ ├────────────────────────┤ │
│ │ lindas/client.py │ │ raw SPARQL over HTTP,
│ │ │ │ knows nothing of cubes
│ └────────────────────────┘ │
└───────────────┬──────────────┘
│ HTTPS, no auth
┌───────────────▼──────────────┐
│ lindas.admin.ch/query │
│ SPARQL 1.1 · ~2000 cubes │
└──────────────────────────────┘
lindas/ パッケージは、変更せずに他の LINDAS 対応サーバーへ移植できるよう、
意図的に階層化されています。client.py が扱うのは HTTP と SPARQL のみ、
cube.py が扱うのは cube.link の語彙のみで、ツールが扱うのは cube.py のみです。
ガード付きの run_sparql というエスケープハッチを通さない限り、生の SPARQL が
エージェントに渡ることはありません。
アーキテクチャの決定
アーキテクチャ A(ライブ SPARQL のみ)。厳格な語彙ガードレールを設けます。
2026-07-21 にライブ環境で検証済み:
- エンドポイントは安定しており、認証は不要です。不正なクエリに対して、診断情報を含むクリーンな HTTP 400 を返します。
- 無差別スキャン(ストア全体に対する
SELECT *、COUNT(*))は 60~90 秒でタイムアウトします。同じ質問でも?x a cube:Cubeを条件に加えると、約 2 秒で回答が返ります。
これらを踏まえ、ツールには次の制約を組み込んでいます:
- すべてのクエリテンプレートは、既知のクラスを条件に含めています。範囲無制限のスキャンは行いません。
- 2 フェーズのアクセスを強制し、エージェントに生のコードは表示しません。
run_sparqlは 500 行、30 秒を上限とし、高度な操作として設定しています。- クライアントのタイムアウトは 45 秒に設定し、ストア側の 60~90 秒の中断より前に動作します。
調査の完全なレポート: docs/probe-lindas.md。
ツール
| ツール | 目的 |
|---|---|
search_cubes | トピックからキューブを検索します。エントリーポイントです。バージョンの重複を排除します。 |
get_cube_structure | フェーズ 1:ディメンション、メジャー、ライセンスを取得します。 |
query_cube_observations | フェーズ 2:コードをラベルに解決したデータポイントを取得します。 |
list_publishers | キューブを公開している連邦機関を、件数とともに一覧表示します。 |
resolve_municipality | 名称 ↔ URI ↔ BFS 番号を解決します。ポートフォリオの結合キーです。 |
run_sparql | 高度な操作向けのエスケープハッチです。上限とガードを設けています。 |
api_status | キューブ数を含む到達可能性チェックです。 |
すべてのツールには readOnlyHint: true が注釈されています。
search_cubes の結果の読み方
returned は返された件数であり、存在する件数を示すものではありません。回答がどの程度完全かは、次の 2 つのフィールドで確認できます:
| フィールド | 意味 |
|---|---|
truncated | false — 一致したものはすべて cubes に含まれています。true — さらに結果があるか、またはサーバーがそれ以上ないと判断できませんでした。 |
total_matched | returned と同じ単位で利用可能な場合は正確な合計数を示し、比較可能な数値がない場合は null です。 |
結論を出す前に、必ず truncated を確認してください。 true の場合、結果が完全だと報告してはいけません。また、returned を根拠に「X に関するキューブは N 個あります」と回答してはいけません。次の順序で範囲を広げてください:limit を増やす(最大 100)。100 でも切り捨てられる場合は、list_publishers の creator_uri で絞り込み、連邦機関を 1 つずつ指定します。バージョン履歴が実際に必要な場合に限り、latest_only=False を使用してください。これは上限を回避するのではなく、結果の範囲を広げます。
total_matched が null になるのは、想定より多いかもしれませんが、意図した動作です。 latest_only=true(デフォルト)では、LINDAS が低コストで返せる件数はキューブの バージョン 数である一方、ツールが返すのはバージョンを統合したキューブです。2026-09-20 にドイツ語ラベルで測定した結果、«wald» は公開済みのバージョン 127 件に一致し、論理キューブ 35 件に統合されます。「energie」は 33 件から 13 件に統合されます。returned が 20 のときに total_matched が 127 だと、実際には最大でも 15 件しか見つかる可能性がないのに、キューブが 107 件不足しているという誤解を招きます。そのため、このフィールドには不正確な数値ではなく、正しい値である null が設定されます。この場合も truncated は信頼できるため、こちらを基準に判断してください。
サーバーが一致するすべての行を取得できている場合(必要数より 1 行多く取得するため、通常はこちらに該当します)、両方の分岐で total_matched は正確です。また、truncated は取得上限ではなく、その合計数から導出されます。
インストール
uvx lindas-mcp
Claude Desktop
{
"mcpServers": {
"lindas": {
"command": "uvx",
"args": ["lindas-mcp"]
}
}
}
リモートデプロイ
LINDAS_MCP_TRANSPORT=streamable-http PORT=8000 lindas-mcp
LINDAS_MCP_TRANSPORT は stdio(デフォルト)、streamable-http、または sse を受け付けます。パスを決めるのはトランスポートです:streamable-http は /mcp を、sse は /sse を提供します。それ以外の値はすべて stdio にフォールバックし、ポートは一切開きません。コンテナでは、ヘルスチェックの失敗としてのみ表面化します。
どちらの HTTP トランスポートも HOST にバインドします。デフォルトは 127.0.0.1 です。公開するには HOST=0.0.0.0 を明示的に設定してください(リバースプロキシの背後に限ります)。
LOG_LEVEL で JSON 形式の stderr ログの詳細度を調整できます。
リモートコネクターとしてホストする
コネクター URL は https://<host>/mcp です。 パスは変更できません。
トランスポートによって決まり、現在のクライアントが想定しているのは
streamable-http です。sse とその /sse パスは、仕様上すでに置き換えられたトランスポートです。
削除されたわけではなく、今も利用できますが、新しいコネクターの接続先には
指定しないでください。
ホスト型デプロイ(Railway、Fly、任意のリバースプロキシの背後にあるコンテナー)には 3 つの変数が必要です。そのうち 3 つ目は、設定しなくても明確なエラーが起きないため、 誤った設定を引き継いでしまいがちな変数です。
| 変数 | ホスト型の値 | 未設定の場合 |
|---|---|---|
LINDAS_MCP_TRANSPORT | streamable-http | stdio にフォールバックします。プロセスは起動しますが、ポートを開かないため、ヘルスチェックが失敗する形でしか問題が表面化しません。コンテナーイメージにはすでに設定されています。 |
HOST | 0.0.0.0 | ループバックインターフェースにのみバインドされるため、公開されたポートにアクセスしても何も応答しません。イメージでは意図的に設定されています(SEC-016)。 |
LINDAS_MCP_ALLOWED_HOSTS | 公開ホスト名 | Host の検証が完全に無効になります。詳細は以下をご覧ください。 |
LINDAS_MCP_ALLOWED_HOSTS は、スキームとポートを含めないホスト名のカンマ区切りリストです。例は lindas-mcp.example.ch,alias.example.ch であり、
https://lindas-mcp.example.ch:443 ではありません。値は受信した Host ヘッダーと
文字列どおりに照合されます。TLS をポート 443 で使用している場合、このヘッダーにポートは
含まれません。ループバック形式は自動で追加されるため、コンテナーのヘルスチェックは
引き続き動作します。
設定を誤ると、正反対の 2 つの問題が起きます。
- ループバック以外にバインドしている状態で未設定の場合:保護が完全に無効になります。 その状況ではホスト名を導き出せません。サーバーには、このプロセスが認識していないサービス名または公開 DNS 名でアクセスされます。推測したリストを設定すると、実際のリクエストがすべて 421 で応答されてしまいます。そのため、許可リストは一切設定されず、
Hostヘッダーもチェックされません。これは SDK 自体のデフォルト動作です。唯一の兆候は、起動時の警告dns_rebinding_protection_offです。 - 誤った名前を設定した場合:実際のリクエストはすべて HTTP 421 になります。 照合は完全一致で、ポートも厳密に一致します。
mcp.example.chはHost: mcp.example.ch:8443には一致しません。プロキシがデフォルト以外のポートを転送する場合は、両方の形式を指定してください。
ALLOWED_ORIGINS は別の設定で、ブラウザークライアントだけに関係します。
これは CORS オリジンのカンマ区切りリストです。未設定の場合、ブラウザークライアントは一切許可されません。これがデフォルトです。ブラウザーではないクライアントは
Origin を送信しないため、影響を受けません。* も引き続き受け付けられ、警告がログに記録されます。
ブラウザーにもサービスを提供する場合に知っておくべき点として、
LINDAS_MCP_ALLOWED_HOSTS から導出されるオリジンは http:// 形式です。そのため、
https:// のブラウザーオリジンは ALLOWED_ORIGINS に自分で指定する必要があります。
Docker
docker compose up --build # binds 0.0.0.0 inside the container, publishes :8000
このイメージは、非 root ユーザーとして読み取り専用で実行され、リソース制限と
TCP ヘルスチェックが設定されています(Dockerfile と
compose.yaml を参照)。
結合キー
LINDAS はコネクターレイヤーであり、2 つの識別子によって、ポートフォリオ内の 他のツールと連携できます。
| キー | 場所 | 結合先 |
|---|---|---|
| BFS の自治体番号 | resolve_municipality → bfs_number | swiss-statistics-mcp、zurich-opendata-mcp |
| Fedlex URI | cube の licence フィールド | fedlex-mcp |
Fedlex との連携は、意外に気づかれにくい点です。多くの cube では、ライセンスとして
法的根拠の URI(fedlex.data.admin.ch/eli/cc/...)が指定されています。そのため、データ
ポイントから、それを規定する法律へ直接たどることができます。
既知の制限事項
2026-07-21 にライブ環境で検証済みです。
- 広範な SPARQL クエリはタイムアウトします。 ストアは、起点のないスキャンを 60~90 秒で中断します。
ガード付きツールはこれを回避します。
run_sparqlは警告を表示し、実行時間に上限を設けます。 - 観測値はコード化されています。 次元の値はラベルではなく URI です。サーバーは各次元のコードリストを使って解決しますが、コード化された次元ごとに追加のクエリが 1 回必要です。これを省略するには
resolve_labels=Falseを設定します。 - 任意の値で観測値をサーバー側フィルタリングすることはできません。 LINDAS には、キューブ内の次元値で観測値をフィルタリングする低コストな方法がないため、
query_cube_observationsは先頭から N 件の観測値を読み込みます。分析上のスライスにはrun_sparqlを使用してください。 - ライセンスはキューブごとに異なり、
dcterms:licenseとして宣言されています。多くの場合、単純な名称ではなく Fedlex の URI です。必ずlicenceフィールドを提示してください。 - バージョンの処理はヒューリスティックです。
search_cubesはキューブ URI からバージョン接尾辞を取り除いて重複を排除し、公開済みキューブのうちschema:versionが最も高いものを残します。通常と異なる URI 形式では適切に統合されないことがあります。すべてのバージョンを確認するにはlatest_only=Falseを使用してください。同じヒューリスティックが原因でtotal_matchedがnullになる場合があります。これは Python で URI 形式を推測するもので、それを表現できる低コストな SPARQL カウントはありません。そのため、クエリで同じ推測を繰り返すと、ずれが生じる可能性のある箇所がもう 1 つ増えることになります。
MCP プロトコルのバージョン
このサーバーは、同じエンドポイント上で2 つのプロトコル時代に対応しています。接続時のクライアントの最初のリクエストによって、どちらを適用するかが決まります。もう一方の時代の要求は、後から送られても拒否されます。
| 時代 | リビジョン | 該当するクライアント |
|---|---|---|
initialize ハンドシェイク | 2024-11-05 … 2025-11-25 | 現在のクライアントが使用する方式です。サーバーは要求されたリビジョンで応答します。要求がそれより新しい場合は、上限である 2025-11-25 で応答します。 |
| リクエストごとのエンベロープ | 2026-07-28 | 2026-07-28 の _meta エンベロープを含むリクエストによって、モダンな接続が開始されます。 |
どちらのリビジョンも
tests/test_protocol_version.py に固定され、インストール済み SDK に対して検証されるため、Dependabot による mcp の更新で、どちらかが気付かないうちに変更されることはありません。ハンドシェイクの上限は、定数名から読み取るのではなく、組み立て済みの ASGI スタックを通じたライブの initialize で測定されます。
SDK の LATEST_PROTOCOL_VERSION はハンドシェイク時代ではなく、モダンな時代の別名であることに注意してください。これだけを基準に固定すると、現在のクライアントが実際にネゴシエートする時代が変更されても検知できません。
2026-07-28での変更点
現代の時代にはinitializeハンドシェイクがないため、クライアントが相手の識別情報を知るためのハンドシェイク結果もありません。そこで、SDK のデフォルトに任せず、次の3点を明示的に処理します。
| 項目 | 動作 |
|---|---|
serverInfo | すべてのレスポンスの_metaとserver/discoverに設定されます。名前、タイトル、バージョン、説明、ウェブサイト URL は、インストール済みディストリビューションのメタデータから解決し、手作業では記述しません。MCPServerはversionのデフォルトを""とし、代替値を設定しないため、未設定のバージョンは情報を何も示さない必須フィールドになります。 |
instructions | server/discoverから返されます。これは、現代のクライアントが利用できる唯一の案内チャネルです。エントリーポイント、query_cube_observationsがラベルを自動で解決すること、get_cube_structureの実際の用途を説明します。 |
| ログの配信 | logging/setLevelは廃止されました(SEP-2577)。クライアントは予約済みの_metaキーio.modelcontextprotocol/logLevelを使い、リクエストごとにオプトインします。指定がなければサーバーは何も送信しません。debugを指定すると、そのリクエストのストリーム上でツール呼び出しごとにnotifications/messageを1つ送信します。 |
tools/list、server/discover | ttlMs 300000とcacheScope publicを含みます(SEP-2549)。 |
これらはすべて、組み立てたスタックを通じて
tests/test_spec_2026_07_28.pyで測定します。両方の時代、両方のトランスポート、ログのオプトインの両方の分岐が対象です。ツールの契約自体はtool-definitions.lock.json(SEC-022)で独立して保護されており、現代の時代ではそこに記載されたツールだけを提供することが検証されます。そのため、2つの時代が同じアドレスの背後で異なるサーバーに分岐することはありません。
更新ポリシー。 ゲートに失敗した場合、定数を安易に編集しないでください。2つのリビジョン間の仕様変更履歴を読み、サーバーが引き続き動作することを確認してから、定数、このセクション、README.de.md、CHANGELOG.mdをまとめて更新してください。SDK のアップグレードも、同じ理由でレビュー対象の変更です。プロトコルに影響する更新は、CHANGELOG.mdに記載します。
テスト
PYTHONPATH=src pytest tests/ -m "not live" # offline, used in CI
PYTHONPATH=src pytest tests/ -m "live" # hits the real endpoint
python -m ruff check src tests
ライブテストには実施する価値があります。observationSetの間接参照(キューブの観測値はキューブに直接ではなく、cube:observationSetに紐づきます)は、モックでは検証できない構造上の前提です。これはライブテストでカバーしています。
コントリビューション
基本ルール(読み取り専用、単一の送信先ホスト、アンカー付きクエリ)とローカルでの開発サイクルについては、CONTRIBUTING.mdを参照してください。さらに、対象者別のユースケースとツール選択表はEXAMPLES.md、プロジェクトのフェーズはdocs/roadmap.md、PyPI/MCP Registry のリリース手順はPUBLISHING.mdを参照してください。
セキュリティ
セキュリティ対策と脆弱性の報告方法については、SECURITY.mdを参照してください。
ライセンス
MIT License — LICENSEを参照してください。LINDAS のデータには、各公開者がキューブに指定したライセンスが適用されます。
作成者
Hayal Oezkan · github.com/malkreide
クレジットと関連プロジェクト
- データ:LINDAS Linked Data Service、スイス連邦公文書館
- 語彙:cube.link
- 同じキューブを使った可視化フロントエンド:visualize.admin.ch
- データソースの発見方法は rnckp/awesome-ogd-switzerlandに着想を得ています
- ポートフォリオ:swiss-public-data-mcp
ライセンス:MIT。キューブのデータには、各公開者が指定したライセンスが引き続き適用されます。
MCP Registry
この PyPI パッケージを GitHub 名前空間に紐づけるために、MCP Registryが使用する所有権マーカー:
mcp-name: io.github.malkreide/lindas-mcp
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。