Skip to main content
ClaudeWave

MCP server for Swiss official gazettes (SHAB + cantonal) — procurement and notices, no person data

MCP ServersOfficial Registry0 stars0 forksPythonNOASSERTIONUpdated today
Install in Claude Code / Claude Desktop
Method: UVX (Python) · amtsblatt-mcp
Claude Code CLI
claude mcp add amtsblatt-mcp -- uvx amtsblatt-mcp
claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "amtsblatt-mcp": {
      "command": "uvx",
      "args": ["amtsblatt-mcp"]
    }
  }
}
1. Run the command above in your terminal (Claude Code), or paste the JSON config into claude_desktop_config.json (Claude Desktop).
2. Replace any <placeholder> values with your API keys or paths.
3. Restart Claude. The MCP server and its tools appear automatically.
Use cases

MCP Servers overview

> 🇨🇭 **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)**

# 📰 amtsblatt-mcp

![Version](https://img.shields.io/badge/version-0.1.2-blue)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-purple)](https://modelcontextprotocol.io/)
[![No Auth Required](https://img.shields.io/badge/auth-none%20required-brightgreen)](https://github.com/malkreide/amtsblatt-mcp)
![CI](https://github.com/malkreide/amtsblatt-mcp/actions/workflows/ci.yml/badge.svg)

> 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](README.de.md)

## 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](#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

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

From source:

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

## Configuration

### Claude Desktop

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

### Cloud deployment (SSE)

```bash
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`](https://github.com/malkreide/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`](docs/rubric-classification.md).

### The boundary with `register-mcp`

For publications about a specific **company**, use
[`register-mcp`](https://github.com/malkreide/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.*
amtsblattllmmcpmodel-context-protocolprocurementpythonshabswiss-open-dataswiss-public-data-mcp

What people ask about amtsblatt-mcp

What is malkreide/amtsblatt-mcp?

+

malkreide/amtsblatt-mcp is mcp servers for the Claude AI ecosystem. MCP server for Swiss official gazettes (SHAB + cantonal) — procurement and notices, no person data It has 0 GitHub stars and was last updated today.

How do I install amtsblatt-mcp?

+

You can install amtsblatt-mcp by cloning the repository (https://github.com/malkreide/amtsblatt-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.

Is malkreide/amtsblatt-mcp safe to use?

+

malkreide/amtsblatt-mcp has not been audited yet by our security agent. Review the original repository on GitHub before using it in production.

Who maintains malkreide/amtsblatt-mcp?

+

malkreide/amtsblatt-mcp is maintained by malkreide. The last recorded GitHub activity is from today, with 1 open issues.

Are there alternatives to amtsblatt-mcp?

+

Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.

Deploy amtsblatt-mcp to your cloud

Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.

Maintain this repo? Add a badge to your README

Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.

Featured on ClaudeWave: malkreide/amtsblatt-mcp
[![Featured on ClaudeWave](https://claudewave.com/api/badge/malkreide-amtsblatt-mcp)](https://claudewave.com/repo/malkreide-amtsblatt-mcp)
<a href="https://claudewave.com/repo/malkreide-amtsblatt-mcp"><img src="https://claudewave.com/api/badge/malkreide-amtsblatt-mcp" alt="Featured on ClaudeWave: malkreide/amtsblatt-mcp" width="320" height="64" /></a>

More MCP Servers

amtsblatt-mcp alternatives