Skip to main content
ClaudeWave

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

MCP ServersOfficial Registry0 stars0 forks● PythonMITUpdated today
ClaudeWave Trust Score
95/100
✓ Verified
Passed
  • ✓Open-source license (MIT)
  • ✓Actively maintained (<30d)
  • ✓Clear description
  • ✓Topics declared
  • ✓Documented (README)
Last scanned: 9/28/2026
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.23.0-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 tenders did canton Ticino publish this month?"*

### Demo

![Demo: Claude using gazette_search_procurement and gazette_get_publication](docs/assets/demo.svg)
→ `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`](https://github.com/malkreide/swiss-procurement-mcp);
see [Boundary with `swiss-procurement-mcp`](#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-ZG`
  was 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](docs/procurement-coverage.md)
- **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_mix` rather than guessed at, and
  `only_language=True` gives 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

```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 (streamable-http)

```bash
export MCP_TRANSPORT=streamable-http
export MCP_API_KEY="$(openssl rand -hex 32)"   # mandatory — fails loud if unset
export PORT=8000
amtsblatt-mcp
```

The endpoint is **`/mcp`**.

> **Migrating from SSE.** Until 0.18.0 this server spoke SSE only, on
> `/sse` + `/messages`. MCP spec `2026-07-28` reclassifies HTTP+SSE as
> deprecated with a twelve-month removal window and removes protocol-level
> sessions, so streamable-http is now the default. `MCP_TRANSPORT=sse` still
> 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.

| Variable | Default | Purpose |
|---|---|---|
| `MCP_TRANSPORT` | `stdio` | `stdio`, `streamable-http` (alias `http`), or the deprecated `sse` |
| `MCP_HOST` | `127.0.0.1` | HTTP bind address. Defaults to loopback; set `0.0.0.0` to expose on all interfaces (the Docker image does this deliberately). |
| `MCP_STATELESS` | _(on)_ | streamable-http runs with no session tracking at all — the native mode of spec `2026-07-28`, which has no protocol-level sessions. Removes session hijacking and session affinity as questions rather than answering them (`SEC-009`, `SCALE-002`). `0` / `false` / `no` restores sessions for handshake-era clients; any other value keeps the default. Ignored on `sse`, which has no stateless mode. |
| `MCP_CORS_ORIGINS` | _(unset)_ | Comma-separated origins allowed to call the endpoint from a browser. Unset means no cross-origin browser access at all — stdio and non-browser clients are unaffected. `Mcp-Session-Id` is exposed and accepted for the listed origins, so a browser client can hold a session. `*` is honoured but logs a warning and disables credentials, because browsers reject a wildcard origin together with credentials. |
| `MCP_API_KEY` | — | Bearer token; **required** on every HTTP transport |
| `MCP_RATE_LIMIT` / `MCP_RATE_WINDOW` | `60` / `60` | Sliding-window rate limit |
| `RUBRICS_TTL` | `86400` | Taxonomy cache TTL (seconds) |
| `LOG_LEVEL` | `INFO` | `DEBUG` \| `INFO` \| `WARNING` \| `ERROR`. Structured JSON, one object per line, always on **stderr** — stdout carries the MCP protocol on a stdio transport. |

### Logging

Built on [structlog](https://www.structlog.org/). 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.

| Level | Emitted when |
|---|---|
| `DEBUG` | a tool call was entered (`tool_call_started`) — tells you whether a hung call ever started |
| `INFO` | a tool call finished cleanly, with latency |
| `WARNING` | upstream retry, egress denied, auth failure, rate limit |
| `ERROR` | a tool call raised |

Records carry the exception *type* only — never its message and never an
upstream response body.

```json
{"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"}
```

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

| Tool | Signature | Notes |
|---|---|---|
| `gazette_search_publications` | `(keyword?, rubric?, sub_rubric?, canton?, date_start?, date_end?, limit=20, page=0, language='de', only_language=False)` | Green rubrics enforced. Without `rubric`, all green rubrics are injected — a keyword-only query can never reach a blocked one. |
| `gazette_search_detailed` | same filters **+ `top_n=3`** | **Aggregated.** Search *and* full text for the top `top_n` hits in one call, fetched in parallel. Same green gate on every expanded document; blocked ones are withheld and counted, never rendered. |
| `gazette_search_procurement` | `(keyword?, canton?, date_start?, date_end?, include_inactive=False, limit=20, page=0, language='de', only_language=False)` | `OB-*` rubrics plus the gazette-native sub-rubrics `AR-VS40`, `AR-OW40`, `BA-SH40`. A canton with neither gets a simap.ch explainer and **no HTTP call**. No CPV — the source has none. |
| `gazette_get_publication` | `(id, response_format='markdown')` | Full official text from XML. Re-checks the rubric after fetching; content from a blocked rubric is discarded. |
| `gazette_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. |
| `gazette_source_status` | `(response_format='markdown')` | Reachability, latency, cache age, scope metrics. |

All tools are `readOnlyHint=True`.

### Example use cases

| Question | Tool chain |
|---|---|
| Tenders in Ticino this quarter | `gazette_search_procurement(canton="TI", only_language=True, language="it")` |
| Procurement simap.ch does **not** have | `gazette_search_procurement(canton="VS")` — 150 Valais awards, none on simap |
| Tenders in any other canton | → use [`swiss-procurement-mcp`](https://github.com/malkreide/swiss-procurement-mcp) |
| What is even queryable here? | `gazette_list_rubrics()` |
| Why can't I search bankruptcies? | `gazette_list_rubrics(rubric_class="all")` |
| Zoning changes in Zurich | `gazette_search_publications(rubric="RP-ZH")` |
| Full text of a notice | `gazette_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 
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 its last recorded update is dated 2026-09-27.

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?

+

Our security agent has analyzed malkreide/amtsblatt-mcp and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.

Who maintains malkreide/amtsblatt-mcp?

+

malkreide/amtsblatt-mcp is maintained by malkreide. The last recorded GitHub activity is dated 2026-09-27, with 0 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