
Amtsblatt Mcp
io.github.malkreide由 GitHub 帳號 malkreide 發布v0.23.0更新於 Oct 24, 2026
Swiss official gazettes (SHAB + cantonal) — procurement & notices, person-data rubrics excluded
概覽
唯讀的瑞士官方公報(SHAB 及各邦公報)MCP 伺服器,讓助理檢索採購公告與官方發布,並依設計排除含個人資料的類目。
- 可以做什麼
- 依關鍵字、類目、邦與日期範圍檢索官方公報發布,支援單一語言檢視並回報多語言紀錄的語言組成,並執行彙總檢索,在一次呼叫中同時抓取排名前面結果的全文,被攔截的文件會被扣留並計數,並檢索公報採購類目下的公共採購公告,包括不含 simap 參照的瓦萊、上瓦爾登與沙夫豪森子類目,並依 id 取得單一發布的完整官方全文,抓取後再次核對其類目,並列出類目分類體系,含紅綠燈分級及每個被攔截類目被攔截的原因,並檢視上游可達性、延遲、快取時間與範圍指標。
- 適用情境
- 適合需要查詢 amtsblattportal.ch 上瑞士官方公告的情境,例如採購招標、空間規劃、法規以及邦與市鎮通告,尤其是 simap.ch 未收錄的少數採購紀錄。對於一般性的瑞士採購問題,README 建議改用另一個以 simap 為基礎的伺服器。
- 執行需求
- 需要 Python 3.11+ 及 amtsblatt-mcp 套件(以 pip 安裝或 uvx 執行)。無需 API 金鑰,入口網站的讀取 API 可自由存取。需要能連線至 amtsblattportal.ch 的網路。選用的 HTTP 部署需設定 MCP_TRANSPORT=streamable-http 與必填的 MCP_API_KEY bearer 權杖,另有選用的 MCP_HOST、MCP_CORS_ORIGINS 與限流設定。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 Amtsblatt Mcp,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
🇨🇭 Part of the Swiss Public Data MCP Portfolio
📰 amtsblatt-mcp
[Version] [License: MIT] [Python 3.11+] [MCP] [No Auth Required] [CI]
MCP server for amtsblattportal.ch — the Swiss official gazette portal (SHAB + 27 cantonal gazettes). Public procurement and official notices, person-data rubrics excluded by design.
Overview
The Amtsblattportal publishes roughly 2.79 million official notices: public procurement, cantonal and communal announcements, enactments, spatial planning — and also bankruptcies, debt collection, inheritance calls and civil-status records naming natural persons.
This server exposes only the first group. Rubrics carrying systematic natural-person data are not queryable, and no tool accepts a person's name, birth date or address. That is a deliberate data-protection decision, explained in Data Protection & Scope.
Anchor demo query: "Which public tenders did canton Ticino publish this month?"
Demo
[Demo: Claude using gazette_search_procurement and gazette_get_publication]
→ gazette_search_procurement(canton="TI", only_language=True, language="it") → gazette_get_publication(id=…)
For procurement in any other canton — including Zürich, Bern and Basel-Stadt —
use swiss-procurement-mcp;
see Boundary with swiss-procurement-mcp.
Features
- Fail-closed green allow-list — 49 released rubrics out of 152; everything else is blocked by default, including rubrics the upstream adds later
- Explanatory refusals — a blocked rubric returns why, never a silent empty result and never a workaround hint
- Procurement-aware — knows that only AR and TI still publish tenders here,
that BS wound down during 2024 and BL/VS are historical archives, that
OB-ZGwas never filled after the simap switch, and that ZH routes everything through simap.ch — so it explains instead of returning nothing. Activity is measured, not read off the rubric label - Deadline arithmetic in Europe/Zurich, the legally relevant timezone
- Honest multilingual counts — the portal publishes one record per language
with a different publication number each; identical editions are collapsed,
translated ones are reported via
language_mixrather than guessed at, andonly_language=Truegives a single-language view - Defensive XML parsing — the schema is per-sub-rubric; no rubric-specific path is hard-coded, and entity-escaped HTML bodies are unescaped and stripped
- Egress allow-list, retry with backoff, structured JSON logging
- Markdown or JSON output with per-response attribution +
provenance
Prerequisites
- Python 3.11+
- No API key. The read API of amtsblattportal.ch is freely accessible.
Installation
From source:
Configuration
Claude Desktop
Cloud deployment (streamable-http)
The endpoint is /mcp.
Migrating from SSE. Until 0.18.0 this server spoke SSE only, on
/sse+/messages. MCP spec2026-07-28reclassifies HTTP+SSE as deprecated with a twelve-month removal window and removes protocol-level sessions, so streamable-http is now the default.MCP_TRANSPORT=ssestill works and still carries the full bearer-auth, rate-limit and CORS stack — it logs a warning at startup naming the deadline. Update the client URL when you switch: the path change is the part that breaks silently.
Logging
Built on structlog. Every event emitted during a
tool call carries that call's correlation_id, bound via contextvars — so a
retry or an egress denial logged deep in the HTTP path can be joined to the
request that caused it, without threading context through every signature.
Records carry the exception type only — never its message and never an upstream response body.
Ship these to your SIEM and alert on auth_failed, rate_limited,
egress_denied, green_gate_violation and blocked_publication_requested —
the last two mean something tried to reach a rubric this server does not serve.
Available Tools
All tools are readOnlyHint=True.
Example use cases
Data Protection & Scope
The Amtsblattportal systematically publishes personal data of natural persons. Those publications are public — but making them systematically queryable by name through an AI agent is a repurposing the publication never intended, and a profiling instrument under the revised Swiss FADP (revDSG).
Four rules follow, and they are enforced in code, not in documentation:
- Allow-list, never block-list. Not explicitly green ⇒ not queryable. New upstream rubrics are closed by default.
- No person-based search entry in any tool signature.
- No persistence. Publications have statutory deletion periods; a cache outliving them would actively undermine them. Only the taxonomy is cached.
- Blocked ⇒ explained. Never a silent empty result, never a hint at circumvention.
What is excluded
🔴 Konkurse (KK), Schuldbetreibungen (SB), Schuldenrufe (LS, SR),
Nachlass (NA), Erbschaft/Testament/Ableben (ES, TE-*, VA-*),
Familie & Zivilstand (FZ-*, BV-*, BU-*), gerichtliche Vorladungen
(UV, GB-*, GE-*, SJ-BE), Baugesuche (BP-*), Grundbuch (GR-*),
Meldungskatalog GR (AA-GR).
🟡 Deferred: Steuerwesen, Anzeigen, Bewilligungen, Bildungs- und Kirchenwesen and the general catch-all rubrics.
The full audit trail — including three documented extensions to the source
specification — is in docs/rubric-classification.md.
How much each decision covers is measured, not estimated.
docs/coverage-matrix.md enumerates the source's own
rubric axis and marks this server's reach into it: 84.2 % of 2 804 063
publications are reachable, 12.6 % blocked by decision, 3.3 % still
unclassified. The insolvency group alone is 321 704 publications — present in
the source, out of reach here on purpose. Without that figure, "out of scope"
and "not in the source" read the same in a review, and this repository made
exactly that mistake once (see ARCH-003 in SECURITY.md).
The boundary with register-mcp
For publications about a specific company, use
register-mcp. It keeps full
rubric access — including a firm's own bankruptcy — but only ever keyed on a
company UID. A firm's insolvency is corporate data, not natural-person
profiling, and UID scoping makes name-based enumeration impossible.
amtsblatt-mcp has the opposite shape: broad search, narrow rubrics. It does
not expose the upstream uids parameter at all.
Boundary with swiss-procurement-mcp
simap.ch is the primary source for Swiss public procurement — all 26 cantons
plus the Confederation, with CPV and BKP codes, awards and publication history.
Use swiss-procurement-mcp
for procurement questions.
amtsblattportal.ch is the primary source for official notices — commercial register, spatial planning, enactments, cantonal and communal announcements. That is what this server is for; procurement is 6 of its 49 released rubrics.
Procurement here is largely a second publication of the same tenders, and
that is now measured rather than assumed. A publication's XML carries
<simapPublicationNumber> when it originates on simap.ch, which joins the two
corpora exactly. Over the full 2026 OB-TI corpus, 503 of 546 records (92.1%)
carry one; three of the six OB-* rubrics say as much in their own labels
(OB-BL — "über Simap importiert (I N A K T I V)").
The exception is small and sharply bounded: AR-VS40 (Valais, 150 awards),
AR-OW40 (Obwalden, 7), BA-SH40 (Schaffhausen, 2) and the Ticino sub-rubric
OB-TI65 ("Avvisi di gara non CIAP") carry no simap reference at all.
That is the one part of this portal's procurement coverage swiss-procurement-mcp
cannot reach, and gazette_search_procurement serves it for cantons VS, OW and
SH even though they have no active OB-* rubric. Numbers and method in
docs/simap-overlap.md.
The two servers stay separate on purpose: different sources, different reuse
terms, and a fail-closed rubric gate that only means something while it covers
every tool in the server. See
docs/procurement-coverage.md for the numbers.
Maturity & phase
Phase 1 — read-only. All six tools are read-only; there is no write path and none is planned. See ROADMAP.md for the phase-specific backlog, what is deliberately not planned, and what a phase transition would require.
The scope restriction that matters most here is not the phase but the green allow-list — rubrics carrying systematic natural-person data are not queryable, enforced in code and re-checked after every fetch. That does not change with phase. See Data Protection & Scope.
SDK and dependency updates arrive as Dependabot PRs, so a breaking protocol or SDK change is reviewed deliberately rather than drifting in silently.
Architecture
Architecture A (live-API-only). The endpoints answer stably without authentication, so no bulk dump is maintained.
Verified upstream quirks (live-checked 2026-07-20)
Known Limitations
-
Uneven cantonal coverage. Only 16 of 29 mandates expose their own rubric taxonomy; AG, FR, GE, GL, JU, LU, NE, UR are still incomplete.
-
Deletion periods. Publications drop out of the API over time — hence pass-through only.
-
Procurement boundary. Most cantons, including Zürich, route tenders through simap.ch, outside this portal. There is no
OB-ZH, and no CPV classification exists here. What this portal has and simap does not is listed indocs/simap-overlap.md;gazette_get_publicationreportssimap_publication_numberso a mirror is distinguishable from an original. -
Procurement coverage, measured (
publicationStates=PUBLISHED, 2026-07-27, records per calendar year — reproduce withpython scripts/measure_procurement_coverage.py):Only TI and AR still publish actively.
OB-BSis the instructive case: its label is a plain "Öffentliches Beschaffungswesen" with no inactive marker, so only the volume reveals the migration — which is whyactiveis measured, never read. Useinclude_inactive=Trueto reach the BS, BL and VS archives. Details indocs/procurement-coverage.md. -
No push. Polling only; no subscription or webhook mechanism.
-
Legally binding text is the signed PDF, not this API.
MCP Protocol Version
The MCP Python SDK negotiates the protocol version in the session layer and offers no constructor parameter for it, so the version cannot be pinned by configuration. It is pinned as a declared constant and enforced by detection:
- At runtime, a mismatch logs a
protocol_version_driftevent atWARNING. The server keeps working. - In CI,
tests/test_protocol_version.pyfails.
An SDK bump should break our build, not the runtime of someone who upgraded
mcp in their own environment.
That both eras are actually served — through the bearer gate, rate limit and
CORS, not just named in a constant — is measured in
tests/test_modern_era.py: raw 2026-07-28 requests with routing headers, an
mcp.Client in auto mode over HTTP and over stdio, and the legacy handshake
as the negative control.
Update policy
- Dependabot opens SDK update PRs monthly (
.github/dependabot.yml). - When an update moves the protocol version, the CI test fails. The fix is
not to edit the constant blindly: read the spec changelog, verify the
server still behaves — especially the green allow-list invariants — then bump
the constant, this section and
CHANGELOG.mdin one commit. - Protocol-version bumps are called out explicitly in
CHANGELOG.md, not folded into a dependency-bump line.
Cache hints
Spec 2026-07-28 gives every cacheable result a ttlMs and a cacheScope.
The SDK defaults both to «immediately stale, never shared», so a server that
passes no cache_hints is not neutral — it asks every client to re-list on
every connection. This server's tool list is registered at import and identical
for every caller, so it is announced as cacheable for five minutes and
shareable across authorization contexts (CACHE_HINTS in _app.py).
public rests on that second property, not on convenience: the green
allow-list is enforced per request inside the tools, never by hiding a tool from
a caller. The day a tool list becomes caller-dependent, the scope has to become
private in the same commit.
Primitives: tools only
This server exposes tools and neither resources nor prompts. A decision, not an omission (ARCH-008).
Why not resources. Resources address identifiable, listable content the client can enumerate and cache. This corpus is 2.79 million publications that grows daily, and — more importantly — not all of it is servable. Rubrics carrying systematic personal data are excluded by design, and that exclusion is enforced at two points: a pre-request green gate on the filters, and a post-fetch gate on the returned document.
A resource URI would put a publication id in the client's hands as an enumerable address. Since ids are opaque, the rubric behind one cannot be known until the document is fetched — which is exactly why the post-fetch gate exists. Exposing publications as resources would mean either enumerating ids we have not gated yet, or gating at fetch time anyway, at which point the resource abstraction buys nothing and costs a second content path to keep the guarantee on. This repo has already learned that lesson once: the aggregated tool needed the gate extracted into a shared helper precisely because a second path to content is where such guarantees quietly stop holding.
One candidate was checked concretely:
Why not prompts. Question templates would duplicate guidance the tool docstrings already carry, in a second place that can drift out of sync with the allow-list. Given that the docstrings are what tell the model which rubrics are reachable, one source is safer than two.
Return shapes: rendered text, not models
Tools return str — Markdown by default, JSON via response_format='json' —
rather than Pydantic models. This is a documented deviation from SDK-002,
made deliberately rather than by neglect.
The rendered output is not a serialisation of an internal object; it is composed
for the reader. It carries the provenance line, the scope statement
(green_rubrics_only), the deduplication warning when language variants were
merged, and the explanation a blocked rubric returns instead of data. Those
are the parts that keep the model from drawing wrong conclusions, and they are
prose, not fields.
Returning a model would either drop them or smuggle them back in as string
fields, which is the same thing with more ceremony. The json format already
covers the machine-readable case for callers that want it.
What would change this: a caller that needs to compute over results rather than read them. At that point the right move is typed models on the JSON path specifically, not a wholesale change of what every tool returns.
Testing
These are the same gates CI runs, over the same directories. The dev extra
pins ruff to the version CI installs, so a local run and a CI run agree.
The suite covers the mandatory portfolio set: green-rubric search with source URL, blocked rubric → explanation with zero HTTP calls, canton filtering, Europe/Zurich deadline arithmetic against a fixed "today", pagination across a page boundary, language deduplication, boolean normalisation, and API-unreachable handling. Fixtures are shortened real responses, consistently anonymised — no real personal data.
Project Structure
Changelog
See CHANGELOG.md.
Contributing
See CONTRIBUTING.md. Changes to
src/amtsblatt_mcp/rubrics.py require an
explicit rationale in the PR description: releasing a rubric is a
data-protection decision, not a feature.
Security
See SECURITY.md for reporting and operator hardening notes.
License
MIT — see LICENSE. Data-source notice: NOTICE.md.
Data source: amtsblattportal.ch, operated by SECO / State Secretariat for Economic Affairs on behalf of the Swiss Confederation. Freely usable, but without warranty of completeness or accuracy. Only the signed PDF of a publication is legally binding.
Author
Hayal Oezkan · malkreide
Credits & Related Projects
Part of the Swiss Public Data MCP Portfolio:
register-mcp— Zefix commercial register with a company-UID join to the gazettes
來源:README.md,提交 9fe4a6d
工具
0版本歷史
2- v0.23.0最新Sep 27, 2026
- v0.22.1Sep 16, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分