amtsblatt-mcp について
amtsblatt-mcpは、調達、都市計画、公共告知など、スイス官報の一部を読み取り専用で検索できるMCPサーバーです。官報を調べるユーザー向けで、自然人データを体系的に扱う分類は厳格な許可リストにより除外されます。
主な機能
- 01許可された官報公告の検索
- 02公告全文の取得
- 03調達公告の検索
- 04多言語の公告結果を絞り込み
- 05ブロック対象の分類理由を表示
活用シーン
- 01ティチーノ州の入札公告を検索
- 02チューリヒの都市計画変更を検索
- 03官報公告の全文を取得
- 04検索可能な公告分類を確認
MCP 対応
読み取り専用の検索、取得、分類一覧、情報源ステータスのツールをMCPクライアントに提供します。フェイルクローズの許可リストで対象分類を限定し、個人データを含む分類をブロックします。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
🇨🇭 Swiss Public Data MCP Portfolio の一部です
📰 amtsblatt-mcp
amtsblattportal.ch 向けの MCP サーバーです。スイスの官報ポータル (SHAB および 27 の州官報)を対象としています。公共調達や公式告知を扱い、 個人データを含む分類項目は設計上除外しています。
概要
Amtsblattportal には、公共調達、州や自治体の告知、法令、空間計画のほか、 自然人の氏名を含む破産、債権回収、相続人捜索、身分登録など、約 279 万件 の公式告知が掲載されています。
このサーバーが公開するのは、最初のグループのみです。自然人のデータを 体系的に扱う分類項目は検索できず、人物の氏名、生年月日、住所を受け付ける ツールもありません。これは意図的なデータ保護上の判断であり、データ保護と対象範囲で説明しています。
デモ用の代表的なクエリ: 「今月、ティチーノ州が公告した公共入札は?」
デモ
→
gazette_search_procurement(canton="TI", only_language=True, language="it") → gazette_get_publication(id=…)
チューリッヒ、ベルン、バーゼル=シュタットを含むその他の州の公共調達には、
swiss-procurement-mcp を使用してください。
swiss-procurement-mcp との境界を参照してください。
機能
- 安全側に倒す許可リスト方式 — 公開対象は 152 件中 49 件の分類項目です。後から上流で追加された分類項目も含め、それ以外はすべて既定でブロックされます
- 理由を説明する拒否応答 — ブロックされた分類項目には、その理由を返します。何も返さないだけの空結果や、回避方法を示唆する情報は返しません
- 調達状況を考慮 — 現在も入札を掲載しているのは AR と TI のみであること、BS は 2024 年中に掲載を終了し、BL/VS は過去分のアーカイブであること、
OB-ZGは simap への移行後も一度も登録されていないこと、ZH はすべて simap.ch 経由であることを把握しています。そのため、何も返さずに終わるのではなく、状況を説明します。掲載状況は分類項目のラベルから推測せず、実測しています - Europe/Zurich のタイムゾーンを使用した期限の計算(法的に適用されるタイムゾーンです)
- 多言語の件数を正確に表示 — ポータルは言語ごとに異なる公告番号を持つレコードを 1 件ずつ公開します。同一の版は統合し、翻訳版は推測せず
language_mixで報告します。また、only_language=Trueを指定すると単一言語で表示できます - 堅牢な XML 解析 — スキーマは分類項目ごとに異なります。分類項目固有のパスはハードコードせず、エンティティエスケープされた HTML 本文はエスケープ解除してからタグを除去します
- 送信先許可リスト、バックオフ付き再試行、構造化 JSON ログ
- 応答ごとの出典情報と
provenanceを含むMarkdown または JSON 出力
前提条件
- Python 3.11+
- API キーは不要です。 amtsblattportal.ch の読み取り API は無料で利用できます。
インストール
pip install amtsblatt-mcp
# or, without installing:
uvx amtsblatt-mcp
ソースからインストールする場合:
git clone https://github.com/malkreide/amtsblatt-mcp
cd amtsblatt-mcp
pip install -e ".[dev]"
設定
Claude Desktop
{
"mcpServers": {
"amtsblatt": {
"command": "uvx",
"args": ["amtsblatt-mcp"]
}
}
}
クラウドデプロイ(streamable-http)
export MCP_TRANSPORT=streamable-http
export MCP_API_KEY="$(openssl rand -hex 32)" # mandatory — fails loud if unset
export PORT=8000
amtsblatt-mcp
エンドポイントは /mcp です。
SSE からの移行。 0.18.0 までは、このサーバーは
/sseと/messagesで SSE のみを提供していました。MCP spec2026-07-28では、HTTP+SSE は 12 か月の削除猶予期間を設けて非推奨に分類され、プロトコルレベルの セッションが削除されたため、streamable-http がデフォルトになりました。MCP_TRANSPORT=sseも 引き続き動作し、ベアラートークン認証、レート制限、CORS のスタックもすべて適用されますが、 起動時に期限を示す警告がログに出力されます。切り替える際はクライアント URL を更新してください: パスの変更が、気付かないうちに動作しなくなる原因です。
| 変数 | デフォルト | 用途 |
|---|---|---|
MCP_TRANSPORT | stdio | stdio、streamable-http(エイリアスは http)、または非推奨の sse |
MCP_HOST | 127.0.0.1 | HTTP のバインドアドレス。デフォルトはループバックです。すべてのインターフェースで公開するには 0.0.0.0 を設定します(Docker イメージでは意図的にこの設定を使用します)。 |
MCP_STATELESS | (有効) | streamable-http はセッション追跡を一切行わずに動作します。プロトコルレベルのセッションがない spec 2026-07-28 のネイティブモードです。セッションハイジャックとセッションアフィニティを、対処すべき問題ではなく、そもそも問題にならないようにします(SEC-009、SCALE-002)。0/false/no を指定すると、ハンドシェイク方式のクライアント向けにセッションが復元されます。それ以外の値ではデフォルトが維持されます。ステートレスモードがない sse では無視されます。 |
MCP_CORS_ORIGINS | (未設定) | ブラウザーからエンドポイントへの呼び出しを許可するオリジンを、カンマ区切りで指定します。未設定の場合、クロスオリジンのブラウザーアクセスは一切許可されません。stdio とブラウザー以外のクライアントには影響しません。指定したオリジンでは Mcp-Session-Id が公開され、受け入れられるため、ブラウザークライアントはセッションを保持できます。* も有効ですが、警告がログに出力され、認証情報は無効になります。ブラウザーではワイルドカードオリジンと認証情報を併用できないためです。 |
MCP_API_KEY | — | ベアラートークン。すべての HTTP トランスポートで必須です。 |
MCP_RATE_LIMIT / MCP_RATE_WINDOW | 60 / 60 | スライディングウィンドウ方式のレート制限 |
RUBRICS_TTL | 86400 | タクソノミーのキャッシュ TTL(秒) |
LOG_LEVEL | INFO | DEBUG | INFO | WARNING | ERROR。構造化 JSON を 1 行につき 1 オブジェクト出力し、出力先は常に stderr です。stdio トランスポートでは stdout が MCP プロトコル用です。 |
ロギング
structlog を基盤としています。ツール呼び出し中に出力されるすべてのイベントには、contextvars でバインドされた、その呼び出しの correlation_id が含まれます。そのため、HTTP パスの深い箇所でログに記録された再試行やエグレス拒否を、すべてのシグネチャにコンテキストを渡さなくても、原因となったリクエストと結び付けられます。
| レベル | 出力されるタイミング |
|---|---|
DEBUG | ツール呼び出しが開始されたとき(tool_call_started)。停止した呼び出しが開始されたかどうかを確認できます。 |
INFO | ツール呼び出しが正常に完了したとき。所要時間も含まれます。 |
WARNING | 上流への再試行、エグレス拒否、認証失敗、レート制限 |
ERROR | ツール呼び出しで例外が発生したとき |
レコードに含まれるのは例外の型のみです。例外メッセージや上流からのレスポンス本文は含まれません。
{"event":"tool_call_started","tool":"gazette_search_procurement","correlation_id":"23221af26ae640c7","level":"debug","timestamp":"2026-07-27T22:20:07.494276Z"}
{"status":"ok","latency_ms":312,"event":"tool_call","tool":"gazette_search_procurement","correlation_id":"23221af26ae640c7","level":"info","timestamp":"2026-07-27T22:20:07.806Z"}
これらを SIEM に送信し、auth_failed、rate_limited、
egress_denied、green_gate_violation、blocked_publication_requested をアラート対象にしてください。
後者 2 つは、このサーバーが提供していないルーブリックにアクセスしようとしたことを意味します。
利用可能なツール
| ツール | シグネチャ | 備考 |
|---|---|---|
gazette_search_publications | (keyword?, rubric?, sub_rubric?, canton?, date_start?, date_end?, limit=20, page=0, language='de', only_language=False) | グリーンに分類されたルーブリックが適用されます。rubric を指定しない場合、すべてのグリーンルーブリックが自動的に対象に含まれるため、キーワードのみのクエリがブロック対象に到達することはありません。 |
gazette_search_detailed | 同じフィルター + top_n=3 | 集約型です。 検索と、上位 top_n 件の全文取得を、並列処理による1回の呼び出しで行います。展開された各文書にも同じグリーンゲートが適用され、ブロック対象は表示されず、件数のみが集計されます。 |
gazette_search_procurement | (keyword?, canton?, date_start?, date_end?, include_inactive=False, limit=20, page=0, language='de', only_language=False) | OB-* ルーブリックに加え、官報固有のサブルーブリック AR-VS40、AR-OW40、BA-SH40 が対象です。該当するものがない州の場合は simap.ch の説明が返され、HTTP 呼び出しは行われません。CPV はありません。ソースに含まれていないためです。 |
gazette_get_publication | (id, response_format='markdown') | XML から公式全文を取得します。取得後にルーブリックを再確認し、ブロック対象のルーブリックに属する内容は破棄されます。 |
gazette_list_rubrics | (language='de', rubric_class='green', response_format='markdown') | rubric_class='all' を指定すると、信号機の分類と理由を含む完全な分類体系が表示されます。一覧にあることは検索可能であることを意味しません。 |
gazette_source_status | (response_format='markdown') | 接続状況、レイテンシ、キャッシュの経過時間、対象範囲の指標を表示します。 |
すべてのツールで readOnlyHint=True です。
使用例
| 質問 | ツールチェーン |
|---|---|
| 今四半期のティチーノ州の入札 | gazette_search_procurement(canton="TI", only_language=True, language="it") |
| simap.ch に掲載されていない調達案件 | gazette_search_procurement(canton="VS") — ヴァレー州の落札情報150件。simap には掲載されていません |
| その他の州の入札 | → swiss-procurement-mcp を使用 |
| ここでは何を検索できるのか | gazette_list_rubrics() |
| なぜ破産情報を検索できないのか | gazette_list_rubrics(rubric_class="all") |
| チューリッヒの用途地域の変更 | gazette_search_publications(rubric="RP-ZH") |
| 告知の全文 | gazette_get_publication(id="fbf0ff9e-…") |
| ある企業について公表されたすべての情報 | → register-mcp を使用 |
データ保護と対象範囲
Amtsblattportal は、自然人の個人データを体系的に公開しています。これらの公示は公開されていますが、AI エージェントを通じて氏名で体系的に検索可能にすることは、当初の公示の意図を逸脱する二次利用であり、改正スイス連邦データ保護法(revDSG)におけるプロファイリング手段に該当します。
このため、次の4つのルールがあり、文書上だけでなくコードで適用されています。
- ブロックリストではなく、許可リストを使用します。 明示的にグリーンに分類されていないものは、検索できません。 上流で新たに追加されたルーブリックは、デフォルトで閉鎖されます。
- どのツールのシグネチャにも、個人を基準にした検索項目はありません。
- データを永続化しません。 公示には法定の削除期間があり、それを超えてキャッシュを保持すると、その削除期間を実質的に損ないます。キャッシュされるのは分類体系だけです。
- ブロック対象には理由を説明します。 結果が黙って空になることも、回避方法を示唆することもありません。
対象外
🔴 破産手続き(KK)、債務執行(SB)、債務公告(LS、SR)、
債務整理(NA)、相続/遺言/死亡(ES、TE-*、VA-*)、
家族・身分関係(FZ-*、BV-*、BU-*)、裁判所への召喚状
(UV、GB-*、GE-*、SJ-BE)、建築申請(BP-*)、土地登記簿(GR-*)、
GR 通知カタログ(AA-GR)。
🟡 保留:税務、広告、許認可、教育・宗教関連、および一般的な包括分類。
ソース仕様への変更を3件記録したものを含む、監査証跡の全容は
docs/rubric-classification.md にあります。
各判断がどの範囲を対象にするかは、推定ではなく測定されています。
docs/coverage-matrix.md では、ソース独自の
分類軸を列挙し、このサーバーが対象とする範囲を示しています。2 804 063 件の
公告のうち、84.2 % にアクセスでき、12.6 % は判断によりブロックされ、
3.3 % は未分類です。倒産グループだけで321 704件の公告があり、
ソースには存在しますが、意図的にここではアクセスできないようになっています。
この数値がなければ、レビュー時に「対象外」と「ソースに存在しない」は同じ意味に
見えてしまいます。このリポジトリは実際に一度、まさにその誤りを犯しました
(SECURITY.md の ARCH-003 を参照)。
register-mcp との境界
特定の企業に関する公告には、
register-mcp を使用してください。
このサーバーは、その企業自身の破産を含め、すべての分類へのアクセスを維持しますが、
検索キーは常に企業の UID に限定されます。企業の倒産は法人データであり、
自然人のプロファイリングではありません。また、UID で対象を限定することで、
名前による列挙は不可能になります。
amtsblatt-mcp は反対の構成です。検索範囲は広く、分類は限定されています。
上流の uids パラメーターは公開していません。
swiss-procurement-mcp との境界
スイスの公共調達の一次情報源は simap.ch です — 26の全州と連邦を網羅し、
CPV および BKP コード、落札情報、公告履歴を提供します。調達に関する質問には、
swiss-procurement-mcp
を使用してください。
公式公告の一次情報源は amtsblattportal.ch です — 商業登記、空間計画、 法令、州および自治体の告知を扱います。このサーバーはそのためのものであり、 調達は公開済み49分類のうち6つです。
ここに掲載される調達情報の大部分は、同じ入札の二次掲載であり、現在は
推定ではなく測定されています。公告の XML には、simap.ch に由来する場合、
<simapPublicationNumber> が含まれます。これにより、2つのコーパスを正確に
照合できます。2026年の OB-TI コーパス全体では、546件中503件(92.1%)に
この番号が含まれています。OB-* 分類のうち3つは、独自のラベルにもその旨が
記されています(OB-BL —「über Simap importiert (I N A K T I V)」)。
例外は少数で、範囲も明確です。AR-VS40(ヴァレー州、落札150件)、
AR-OW40(オプヴァルデン準州、7件)、BA-SH40(シャフハウゼン州、2件)、
およびティチーノ州の下位分類 OB-TI65(「Avvisi di gara non CIAP」)には、
simap 参照がまったくありません。これが、このポータルの調達情報のうち
swiss-procurement-mcp ではアクセスできない唯一の部分です。
gazette_search_procurement は、OB-* の有効な分類がないにもかかわらず、
VS、OW、SH各州の情報を提供します。数値と方法については
docs/simap-overlap.md を参照してください。
2つのサーバーは、意図的に分けたままにしています。ソースも再利用条件も異なり、
フェイルクローズの分類ゲートが意味を持つのは、サーバー内のすべてのツールを
対象としている場合に限られるためです。数値については
docs/procurement-coverage.md を参照してください。
成熟度とフェーズ
フェーズ 1 — 読み取り専用。 6 つのツールはすべて読み取り専用で、書き込み経路はなく、今後も追加する予定はありません。フェーズごとのバックログ、意図的に予定していないこと、フェーズ移行に必要な条件については、ROADMAP.md を参照してください。
ここで最も重要なスコープ制限はフェーズではなく、グリーン許可リストです。自然人のデータを体系的に含むルーブリックはクエリできません。これはコードで適用され、取得後に毎回再確認されます。この制限はフェーズが変わっても変わりません。データ保護とスコープを参照してください。
SDK と依存関係の更新は Dependabot の PR として届くため、破壊的なプロトコルや SDK の変更が知らないうちに入り込むことなく、意図的にレビューできます。
アーキテクチャ
Claude / MCP client
│
amtsblatt-mcp
│
┌────────┴────────┐
│ green gate │ ← rubrics.py: fail-closed allow-list
└────────┬────────┘ (checked at the tool AND at the query builder)
│
┌────────┴────────┐
│ param allow- │ ← Silent Ignore guard
│ list + quirks │ ← Silent Empty guard (taxonomy validation)
└────────┬────────┘ ← plausibility guard (corpus-size check)
│
┌────────┴────────┐
│ egress allow- │
│ list (httpx) │
└────────┬────────┘
│
amtsblattportal.ch/api/v1
/publications · /publications/{id}/xml · /rubrics · /tenants
アーキテクチャ A(ライブ API のみ)。 エンドポイントは認証なしで安定して応答するため、一括ダンプは保持していません。
検証済みの上流の癖(2026-07-20 にライブ確認済み)
| 癖 | 動作 | 対策 |
|---|---|---|
| サイレント無視 | 不明なパラメーター名を指定すると、HTTP 200 が返され、コーパス全体が返ります。canton=ZH(単数形の誤記)ではフィルターが黙って無視されます。 | クエリパラメーターは ALLOWED_GAZETTE_PARAMS のみから構築し、妥当性チェックで結果が 2 000 000 件を超える場合は拒否します。 |
| サイレント空結果 | 不明なルーブリック値を指定すると、total: 0 を伴う HTTP 200 が返されます。これは実際に該当結果がない場合と区別できません。 | 呼び出しの前に、すべてのコードを分類体系に照らして検証します。 |
| メタデータのみ | 一覧エンドポイントと GET /publications/{id} はどちらも content: null を返します。 | 全文は /publications/{id}/xml からのみ取得できます。 |
| 並べ替えが無視される | pageRequest.sortOrders は 200 で受け付けられますが、効果はなく、sortOrders は [] として返されます。 | クライアント側で並べ替えます。 |
publicationStates がない場合 | 400 ではなく 401 が返されます。これは認証情報が必要という意味ではありません。 | 常に挿入します。401 のメッセージにその旨が記載されています。 |
| ページサイズの上限なし | pageRequest.size=2000 を指定すると、2000 件が返されます。 | クライアント側で上限を 100 件に設定します。 |
| 複数形の不統一 | rubrics/cantons/subRubrics は複数形ですが、keyword/tenant は単数形です。 | 複数形化の規則ではなく、正確なつづりをエンコードします。 |
既知の制限事項
-
州ごとの対応状況にばらつきがあります。 29 の委任機関のうち、独自のルーブリック分類を公開しているのは 16 のみです。AG、FR、GE、GL、JU、LU、NE、UR はまだ不完全です。
-
掲載期間。 掲載情報は時間の経過とともに API から消えるため、パススルーのみです。
-
調達の対象範囲。 Zürich を含むほとんどの州は、入札をこのポータルの対象外である simap.ch 経由で行います。
OB-ZHは存在せず、ここには CPV 分類もありません。このポータルにあって simap にない情報はdocs/simap-overlap.mdに記載されています。gazette_get_publicationはsimap_publication_numberを返すため、ミラーとオリジナルを区別できます。 -
調達の対応状況の測定値(
publicationStates=PUBLISHED、2026-07-27、暦年ごとのレコード数 —python scripts/measure_procurement_coverage.pyで再現できます):ルーブリック 2022 2023 2024 2025 2026 最新 状態 OB-TI517 491 625 607 546 2026-07-27 稼働中 OB-AR95 85 79 56 40 2026-05-22 稼働中 OB-BS1 149 1 058 319 15 2 2026-05-20 2024 年中に縮小 OB-VS0 1 052 1 0 0 2024-01-05 アーカイブ — 2023 年末まで simap からインポート OB-BL0 74 0 0 0 2023-03-30 アーカイブ — 「I N A K T I V」と表示 OB-ZG0 0 0 0 0 — 一度も登録されていない 現在も積極的に掲載しているのは TI と AR のみです。
OB-BSは参考になる事例です。ラベルは「Öffentliches Beschaffungswesen」という通常の表記で、非アクティブを示すマーカーがないため、移行があったことは件数からしか分かりません。そのため、activeは読み取らず、測定しています。BS、BL、VS のアーカイブを参照するにはinclude_inactive=Trueを使用してください。詳細はdocs/procurement-coverage.mdを参照してください。 -
プッシュ通知はありません。 ポーリングのみで、サブスクリプションや Webhook の仕組みはありません。
-
法的拘束力のある文書は署名済み PDF であり、この API ではありません。
MCP プロトコルバージョン
initialize ハンドシェイク経由で提供 | 2024-11-05 … 2025-11-25 — ハンドシェイクの上限 |
| リクエストごとのエンベロープ経由で提供 | 2026-07-28 |
| 選択方法 | HTTP では、各リクエストの MCP-Protocol-Version ヘッダーで選択します。2026-07-28 は、ハンドシェイクもセッションもない自己完結型リクエストとして提供されます。ハンドシェイク時代の値、またはヘッダーがない場合は、initialize の経路に進みます。stdio では、クライアントの最初のリクエストで選択します。server/discover のプローブによって最新の接続が確立されます。 |
serverInfo | 名前 amtsblatt_mcp とパッケージバージョン。すべての最新の結果の _meta に記録されます |
| 固定箇所 | _app.py の MCP_PROTOCOL_VERSION。server.py から再エクスポートされます |
| SDK | mcp[cli]>=2.0.0,<3 |
| キャッシュヒント | tools/list と server/discover:ttlMs 300000、cacheScope public |
MCP Python SDK はセッションレイヤーでプロトコルバージョンをネゴシエートし、バージョンを指定するコンストラクターパラメーターを提供していないため、設定でバージョンを固定することはできません。宣言済みの定数として固定し、検出によって適用しています。
- 実行時は、不一致があると
WARNINGレベルでprotocol_version_driftイベントを記録します。サーバーは動作を続けます。 - CI では、
tests/test_protocol_version.pyが失敗します。
SDK の更新によって壊れるのは、実行時に mcp を独自の環境でアップグレードした人の環境ではなく、こちらのビルドであるべきです。
両方の時代のプロトコルが、定数に名前が記載されているだけでなく、ベアラートークンゲート、レート制限、CORS を経由して実際に提供されていることは、tests/test_modern_era.py で測定しています。ルーティングヘッダー付きの未加工の 2026-07-28 リクエスト、HTTP および stdio での auto モードの mcp.Client、そしてネガティブコントロールとしてのレガシーハンドシェイクが対象です。
更新ポリシー
- Dependabot は SDK の更新 PR を毎月作成します(
.github/dependabot.yml)。 - 更新によってプロトコルのバージョンが変わると、CI テストが失敗します。修正する際、定数をむやみに編集してはいけません。仕様の変更履歴を読み、サーバーが引き続き正しく動作することを確認します。特に、緑の許可リストに関する不変条件を確かめたうえで、定数、このセクション、
CHANGELOG.mdを1つのコミットで更新します。 - プロトコルのバージョン更新は、依存関係の更新行にまとめず、
CHANGELOG.mdで明示的に取り上げます。
キャッシュヒント
仕様 2026-07-28 では、キャッシュ可能な結果すべてに ttlMs と cacheScope が指定されています。SDK では、どちらも「即座に期限切れ、共有しない」がデフォルトです。そのため、cache_hints を渡さないサーバーの動作は中立ではなく、すべてのクライアントに接続のたびに再リストするよう求めることになります。このサーバーのツールリストはインポート時に登録され、すべての呼び出し元で同一なので、5分間キャッシュ可能で、認可コンテキスト間で共有可能なものとして通知されます(_app.py の CACHE_HINTS)。
public は利便性ではなく、2つ目の特性に基づいています。緑の許可リストはツール内でリクエストごとに適用され、呼び出し元にツールを隠す方法は使っていません。ツールリストが呼び出し元によって異なるようになった場合は、同じコミットでスコープを private に変更する必要があります。
プリミティブ:ツールのみ
このサーバーが公開するのはツールのみで、リソースもプロンプトもありません。これは省略ではなく、意図的な判断です(ARCH-008)。
リソースを使わない理由。 リソースは、クライアントが列挙してキャッシュできる、識別可能でリスト可能なコンテンツを対象とします。このコーパスには279万件の出版物があり、日々増え続けています。そして、さらに重要なのは、すべてのコンテンツを提供できるわけではないことです。体系的な個人データを含む分類項目は設計上除外されており、この除外は2つの時点で適用されます。フィルターに対するリクエスト前の緑のゲートと、返却された文書に対する取得後のゲートです。
リソース URI を使うと、出版物 ID が列挙可能なアドレスとしてクライアントに渡ります。ID は不透明なので、対応する分類項目は文書を取得するまでわかりません。これこそ、取得後のゲートが存在する理由です。出版物をリソースとして公開するには、まだゲートを通していない ID を列挙するか、取得時にいずれにしてもゲートを適用する必要があります。後者では、リソースの抽象化に利点がない一方、保証を維持するために2つ目のコンテンツ経路を管理するコストがかかります。このリポジトリは、すでに一度この教訓を得ています。集約ツールでゲートを共有ヘルパーに切り出す必要があったのは、コンテンツへの2つ目の経路こそ、こうした保証がいつの間にか維持されなくなる箇所だからです。
候補の1つを具体的に検討しました。
| 候補 | ツールのままにする理由 |
|---|---|
gazette_list_rubrics | リソースに非常に適した候補です。有限で変化が遅い分類体系で、すでに TTL 付きでキャッシュされています。しかし、このツールの目的は、一覧にあることと検索可能であることは異なると伝えることです。信号機形式の分類と、各分類項目がブロックされている理由を示します。リソースにすると、モデルが読むかどうかを選べる文書になりますが、ツールならモデルが尋ねた質問への回答になります。 |
プロンプトを使わない理由。 質問テンプレートは、ツールの docstring にすでに記載されているガイダンスと重複し、許可リストとの同期がずれる可能性のある場所をもう1つ増やします。どの分類項目にアクセスできるかをモデルに伝えるのは docstring なので、情報源は2つより1つのほうが安全です。
戻り値の形式:モデルではなくレンダリング済みテキスト
ツールは、Pydantic モデルではなく str を返します。デフォルトでは Markdown、response_format='json' を指定すると JSON です。これは SDK-002 からの文書化された逸脱であり、見落としではなく意図的なものです。
レンダリングされた出力は内部オブジェクトをシリアライズしたものではなく、読者向けに構成されています。出典を示す行、対象範囲の説明(green_rubrics_only)、言語バリエーションを統合した場合の重複排除に関する警告、ブロックされたルーブリックがデータの代わりに返す説明が含まれています。これらはモデルが誤った結論を導き出さないために重要な情報であり、フィールドではなく文章です。
モデルを返す場合、これらの情報が失われるか、文字列フィールドとして詰め込まれることになります。後者は、より煩雑なだけで同じことです。呼び出し側が機械可読な形式を必要とする場合は、すでに json 形式で対応できます。
この方針を変える条件: 結果を読むのではなく、結果に対して計算する必要がある呼び出し側があることです。その場合は、すべてのツールの戻り値を全面的に変更するのではなく、JSON 経路に限って型付きモデルを導入するのが適切です。
テスト
pip install -e ".[dev]"
PYTHONPATH=src pytest tests/ -m "not live" # 75 tests, no network
PYTHONPATH=src pytest tests/ -m live # hits the real API
ruff check src/ tests/ scripts/
ruff format --check src/ tests/ scripts/
python scripts/check_version_sync.py
これらは、CI が同じディレクトリに対して実行するものと同じゲートです。dev エクストラでは、ruff のバージョンを CI のインストール版に固定しているため、ローカル実行と CI の実行結果が一致します。
テストスイートでは、必須のポートフォリオ一式を網羅しています。出典 URL を含むグリーンルーブリック検索、ブロックされたルーブリック → HTTP 呼び出しを行わずに説明を返す処理、州によるフィルタリング、固定の「今日」を基準にした Europe/Zurich の期限計算、ページ境界をまたぐページネーション、言語の重複排除、ブール値の正規化、API に到達できない場合の処理です。フィクスチャには実際のレスポンスを短縮したものを使用し、一貫した方法で匿名化しています。実在する個人データは含まれていません。
プロジェクト構成
amtsblatt-mcp/
├── src/amtsblatt_mcp/
│ ├── rubrics.py # Fail-closed green allow-list — the scope decision
│ ├── server.py # MCPServer, 5 tools, quirk guards, XML parsing
│ ├── _log.py # Structured JSON logging + per-tool call events
│ ├── _middleware.py # Bearer auth + sliding-window rate limit (SSE only)
│ └── _otel.py # Optional OpenTelemetry wiring
├── tests/
│ ├── test_allowlist.py # Data-protection invariants (own CI job)
│ ├── test_search.py # Search, procurement, pagination, dedup, errors
│ ├── test_publication.py # XML parsing, deadlines, egress allow-list
│ └── fixtures.py # Anonymised real responses
├── docs/
│ ├── rubric-classification.md # Why each of the 152 rubrics is open/closed
│ ├── procurement-coverage.md # Measured OB-* volume; why `active` is measured
│ ├── coverage-matrix.md # Measured reach: which part of the corpus the tools cover
│ └── simap-overlap.md # Mirror vs. original, joined on simapPublicationNumber
├── scripts/
│ ├── measure_procurement_coverage.py
│ └── measure_coverage_matrix.py
├── Dockerfile · compose.yaml # Hardened, non-root, read-only container
└── server.json # MCP registry manifest
変更履歴
CHANGELOG.md を参照してください。
コントリビューション
CONTRIBUTING.md を参照してください。
src/amtsblatt_mcp/rubrics.py の変更には、PR の説明に明確な根拠を記載する必要があります。ルーブリックの公開は機能追加ではなく、データ保護に関する判断です。
セキュリティ
報告方法と運用者向けのセキュリティ強化に関する注意事項は、SECURITY.md を参照してください。
ライセンス
MIT — LICENSE を参照してください。データソースに関する注記:NOTICE.md。
データソース:amtsblattportal.ch。スイス連邦を代表して、SECO(経済事務局)が運営しています。自由に利用できますが、完全性や正確性は保証されません。法的拘束力を持つのは、刊行物の署名済み PDF のみです。
作成者
Hayal Oezkan · malkreide
クレジットと関連プロジェクト
Swiss Public Data MCP Portfolio の一部です。
register-mcp— 官報と会社 UID を照合する機能を備えた Zefix 商業登記
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。