amtsblatt-mcp

amtsblatt-mcp

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.

Category
访问服务器

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.

🇩🇪 Deutsche Version

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 IT tenders were published in canton Basel-Stadt in the last three months?"search_procurement(canton="BS", keyword="Informatik")get_publication(id=…)

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, BS, TI, ZG publish tenders here and that ZH routes them through simap.ch, so it explains instead of returning nothing
  • Deadline arithmetic in Europe/Zurich, the legally relevant timezone
  • Language deduplication — a notice published in de/fr/it counts once
  • 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

pip install amtsblatt-mcp
# or, without installing:
uvx amtsblatt-mcp

From source:

git clone https://github.com/malkreide/amtsblatt-mcp
cd amtsblatt-mcp
pip install -e ".[dev]"

Configuration

Claude Desktop

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

Cloud deployment (SSE)

export MCP_TRANSPORT=sse
export MCP_API_KEY="$(openssl rand -hex 32)"   # mandatory — fails loud if unset
export PORT=8000
amtsblatt-mcp
Variable Default Purpose
MCP_TRANSPORT stdio stdio or sse
MCP_HOST 127.0.0.1 SSE bind address. Defaults to loopback; set 0.0.0.0 to expose on all interfaces (the Docker image does this deliberately).
MCP_API_KEY Bearer token; required for SSE
MCP_RATE_LIMIT / MCP_RATE_WINDOW 60 / 60 Sliding-window rate limit
MCP_ALLOWED_HOSTS amtsblattportal.ch,www.amtsblattportal.ch Egress allow-list. An override replaces the default entirely.
RUBRICS_TTL 86400 Taxonomy cache TTL (seconds)
LOG_LEVEL INFO Structured JSON logs to stderr

Available Tools

Tool Signature Notes
search_publications (keyword?, rubric?, sub_rubric?, canton?, date_start?, date_end?, limit=20, page=0, language='de') Green rubrics enforced. Without rubric, all green rubrics are injected — a keyword-only query can never reach a blocked one.
search_procurement (keyword?, canton?, date_start?, date_end?, include_inactive=False, limit=20, page=0) OB-* only. A canton without an OB-* rubric gets a simap.ch explainer and no HTTP call. No CPV — the source has none.
get_publication (id, response_format='markdown') Full official text from XML. Re-checks the rubric after fetching; content from a blocked rubric is discarded.
list_rubrics (language='de', rubric_class='green', response_format='markdown') rubric_class='all' shows the full taxonomy with traffic-light classes and reasons — listed ≠ queryable.
source_status (response_format='markdown') Reachability, latency, cache age, scope metrics.

All tools are readOnlyHint=True.

Example use cases

Question Tool chain
IT tenders in Basel-Stadt this quarter search_procurement(canton="BS", keyword="Informatik")
What is even queryable here? list_rubrics()
Why can't I search bankruptcies? list_rubrics(rubric_class="all")
Zoning changes in Zurich search_publications(rubric="RP-ZH")
Full text of a notice get_publication(id="fbf0ff9e-…")
Everything published about one company → use register-mcp

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:

  1. Allow-list, never block-list. Not explicitly green ⇒ not queryable. New upstream rubrics are closed by default.
  2. No person-based search entry in any tool signature.
  3. No persistence. Publications have statutory deletion periods; a cache outliving them would actively undermine them. Only the taxonomy is cached.
  4. 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.

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.

Architecture

   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

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)

Quirk Behaviour Defence
Silent Ignore An unknown parameter name returns HTTP 200 and the full corpus. canton=ZH (singular typo) silently drops the filter. Query params built exclusively from ALLOWED_GAZETTE_PARAMS; plausibility guard rejects results > 2 000 000.
Silent Empty An unknown rubric value returns HTTP 200 with total: 0 — indistinguishable from a real no-hit. Every code validated against the taxonomy before the call.
Metadata only The list endpoint and GET /publications/{id} both return content: null. Full text only via /publications/{id}/xml.
Sorting ignored pageRequest.sortOrders is accepted with 200 but has no effect; sortOrders comes back []. Sorted client-side.
Missing publicationStates Returns 401, not 400 — it does not mean credentials are required. Always injected; the 401 message says so.
No page-size cap pageRequest.size=2000 returns 2000 items. Client-side cap of 100.
Inconsistent plurals rubrics/cantons/subRubrics are plural, keyword/tenant singular. Exact spellings encoded, not a pluralisation rule.

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.
  • No push. Polling only; no subscription or webhook mechanism.
  • Legally binding text is the signed PDF, not this API.

Testing

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/

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

amtsblatt-mcp/
├── src/amtsblatt_mcp/
│   ├── rubrics.py       # Fail-closed green allow-list — the scope decision
│   ├── server.py        # FastMCP server, 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
├── Dockerfile · compose.yaml      # Hardened, non-root, read-only container
└── server.json                    # MCP registry manifest

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: 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

<!-- mcp-name: io.github.malkreide/amtsblatt-mcp -->

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选