Skip to main content
ClaudeWave
malkreide avatar
malkreide

swiss-courts-mcp

Ver en GitHub

MCP Server for Swiss court decisions via entscheidsuche.ch - BGer, BVGer, BStGer + 26 cantonal courts

MCP ServersRegistry oficial1 estrellas0 forksPythonMITActualizado today
ClaudeWave Trust Score
95/100
Verified
Passed
  • Open-source license (MIT)
  • Actively maintained (<30d)
  • Clear description
  • Topics declared
  • Documented (README)
Last scanned: 9/20/2026
Install in Claude Code / Claude Desktop
Method: pip / Python · swiss-courts-mcp
Claude Code CLI
claude mcp add swiss-courts-mcp -- python -m swiss-courts-mcp
claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "swiss-courts-mcp": {
      "command": "python",
      "args": ["-m", "swiss_courts_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.
💡 Install first: pip install swiss-courts-mcp
Casos de uso

Resumen de MCP Servers

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

# 🏛️ swiss-courts-mcp

![Version](https://img.shields.io/badge/version-0.5.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/swiss-courts-mcp)
![CI](https://github.com/malkreide/swiss-courts-mcp/actions/workflows/ci.yml/badge.svg)

> MCP Server for Swiss court decisions — Federal Supreme Court (BGer), Federal Administrative Court (BVGer), Federal Criminal Court (BStGer), and all 26 cantonal courts via entscheidsuche.ch

[Deutsche Version](README.de.md)

<p align="center">
  <img src="assets/demo.svg" alt="Demo: Claude searches Swiss court decisions via MCP tool call" width="720">
</p>

---

## Overview

Access Swiss court decisions from all judicial levels through a single MCP interface. Combines full-text search with structured filters for canton, court level, date range, and law references.

**🎯 Anchor demo query:** *"Find Federal Supreme Court case law on data protection (Art. 25 DSG) since 2020 — and if entscheidsuche.ch is down, still answer from the offline dump, clearly flagged."*

| Source | Coverage | Data |
|--------|----------|------|
| [entscheidsuche.ch](https://entscheidsuche.ch) (live, default) | Federal + 26 cantons | Court decisions since ~2000 |
| [SCD dump](https://doi.org/10.5281/zenodo.14867950) (offline fallback) | **Federal Supreme Court only, 2007–2024** | Metadata/regesten, **no full text** |

**Synergy with [fedlex-mcp](https://github.com/malkreide/fedlex-mcp):** Legislation (SR) + case law = complete legal research.

**Availability:** entscheidsuche.ch is non-profit infrastructure without an SLA. When it is unreachable, the server transparently falls back to a cached public dump (see [Offline fallback](#offline-fallback)). Every response declares its origin (`source: "live" | "dump"`), and dump answers carry a `coverage_note` — the fallback is **partial, not equivalent**.

---

## Features

- Full-text search across all Swiss court decisions
- Multi-stage law reference search with regex parser and Elasticsearch boost scoring
- Dedicated Federal Supreme Court search with chamber filter
- Canton and court level filtering
- Recent decisions feed
- Court taxonomy listing
- Decision statistics with aggregations
- Trilingual support (German, French, Italian)
- **Offline fallback** to a cached public dump when entscheidsuche.ch is unreachable — with explicit provenance on every response
- No API key required

---

## Prerequisites

- Python 3.11 or higher
- An MCP-compatible client (Claude Desktop, Cursor, Windsurf, etc.)

---

## Installation

```bash
pip install swiss-courts-mcp
```

Or install from source:

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

---

## Quickstart

```bash
# Run directly
swiss-courts-mcp

# Or via Python module
python -m swiss_courts_mcp
```

---

## Configuration

### Claude Desktop

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "swiss-courts": {
      "command": "python",
      "args": ["-m", "swiss_courts_mcp"]
    }
  }
}
```

### Cloud Deployment (HTTP transport)

The HTTP transport is **off by default**. The default bind host is `127.0.0.1`
(loopback only) — `0.0.0.0` must be opted into explicitly (the Dockerfile does
this). Running HTTP without authentication logs a warning; only do so behind an
authenticating reverse proxy.

```bash
# Local HTTP (loopback), no auth — development only
swiss-courts-mcp --http --port 8000

# Container (binds 0.0.0.0, auth enabled) — see Dockerfile
docker build -t swiss-courts-mcp .
docker run -p 8000:8000 -e MCP_AUTH_SECRET="$(openssl rand -hex 32)" swiss-courts-mcp
```

Relevant environment variables (see [`.env.example`](.env.example)):

| Variable | Default | Purpose |
|---|---|---|
| `MCP_HOST` | `127.0.0.1` | Bind host. Set to `0.0.0.0` only in containers. |
| `MCP_PORT` | `8000` | Bind port. |
| `MCP_ALLOW_PUBLIC_BIND` | `false` | Suppress the `0.0.0.0` warning (containers). |
| `MCP_STATELESS_HTTP` | `true` | Stateless HTTP → horizontal scaling without sticky sessions. |
| `MCP_AUTH_ENABLED` | `false` | Enable bearer-token auth for HTTP. |
| `MCP_AUTH_SECRET` | — | HS256 signing key (dev). |
| `MCP_OAUTH_JWKS_URL` | — | JWKS URL for RS256 validation (production). |
| `MCP_OAUTH_AUDIENCE` | — | **Required with auth.** Resource identifier the IdP binds tokens to (`aud`). |
| `MCP_OAUTH_ISSUER` | — | **Required with auth.** Issuer of the IdP that mints the tokens (`iss`), compared character for character. |
| `MCP_RESOURCE_URL` | bind address | **Public URL of this server** — the RFC 9728 resource identifier. Set it on any non-loopback bind. |
| `MCP_REQUIRED_SCOPES` | — | Comma-separated required scopes. |
| `MCP_CORS_ORIGINS` | — | Comma-separated allowed origins (no wildcard in prod). |

Authentication validates the user identity from the JWT `sub` claim only; see
[ADR 0001](docs/adr/0001-http-auth.md).

**`MCP_OAUTH_AUDIENCE` is mandatory once auth is on.** The `aud` claim is what
binds a token to *this* server. Without it the verifier did not check the
audience at all and accepted any correctly signed token from the same issuer —
including one minted for a different service (confused deputy). The server now
refuses to start in auth mode without it.

**`MCP_OAUTH_ISSUER` is mandatory too, for the same reason on the other side of
the token.** The `iss` claim binds a token to *the* IdP this server trusts. This
matters as soon as several tenants share one JWKS URL — the normal case with a
hosted IdP: the signature is then valid for all of them, and only `iss`
separates them. Measured with the variable unset, audience correct: a token with
`iss: https://another-tenant.example` was **accepted**, and so was a token with
no `iss` at all. The value is compared literally per RFC 8414/9207, trailing
slash included, and is therefore *not* normalised — unlike `MCP_RESOURCE_URL`,
where a trailing slash is trimmed.

It is also what makes discovery work. `authorization_servers[0]` in the RFC 9728
document is the value an SDK client adopts as its authorization server. Without
the variable the server named *itself* there — and under that address
`/.well-known/oauth-authorization-server`,
`/.well-known/openid-configuration`, `/authorize`, `/token` and `/register` all
answer 404, because this is a resource server, not an authorization server.

**`MCP_RESOURCE_URL` is what an OAuth client needs.** Per RFC 9728 the server
publishes a resource identifier at `/.well-known/oauth-protected-resource` and
names it in the `WWW-Authenticate` header of every 401 — that is where a client
looks to find out where to get a token. Without the variable the server
publishes its *bind* address; measured with a container bind, that was
`{"resource": "http://0.0.0.0:8000", …}`, an address no client can dial. Set it
on any non-loopback bind; the server warns at startup when it is missing.

The SDK's own `validate_token_resource` stays **off** for a measured reason: it
compares the token's resource indicator literally against
`resource_server_url`, which here is the *bind* address. A token whose audience
is not that exact URL is rejected with 401 — so switching it on would lock out
every real client while the audience check above is what actually protects the
server.

### Offline fallback (env)

| Variable | Default | Purpose |
|---|---|---|
| `SWISS_COURTS_FALLBACK_ENABLED` | `true` | Master switch. `0` disables the dump fallback (live-only). |
| `SWISS_COURTS_FORCE_DUMP` | `false` | Force the dump path (skip live) — for pre-warming the cache or offline testing. |
| `SWISS_COURTS_CACHE_DIR` | `platformdirs` cache | Override the cache directory for the downloaded dump. |
| `SWISS_COURTS_DUMP_RECORD` | `14867950` | Zenodo record id of the SCD dump to use. |

Pre-warm the cache (downloads the ~120 MB SCD CSV once, so the first real
outage does not pay the download cost):

```bash
SWISS_COURTS_FORCE_DUMP=1 python -m swiss_courts_mcp  # then issue one search
```

---

## MCP Protocol Versions

The server serves **two protocol eras** over the same endpoint; the client's
first request decides which one it gets:

| Era | Revision | Constant in `server.py` | Handshake |
|-----|----------|-------------------------|-----------|
| Legacy | `2025-11-25` | `HANDSHAKE_PROTOCOL_VERSION` | `initialize`, session ID |
| Modern | `2026-07-28` | `MODERN_PROTOCOL_VERSION` | none — per-request `_meta` envelope |

Both revisions are pinned and both have a drift guard against the installed SDK,
so a protocol bump stays a conscious change (constant + CHANGELOG + this
section). SDK updates land monthly via Dependabot.

**Spec `2026-07-28` is the era the SDK's own client picks.** `mcp.Client` probes
`server/discover` and only falls back to the handshake if that is refused; against
this server it does not fall back. Measured through the assembled ASGI stack in
`tests/test_modern_era.py`, not inferred from constant names.

What differs in the modern era:

- **No `initialize`, no session ID.** Every POST is self-contained and carries
  `params._meta` with the protocol version and the client capabilities, plus the
  routing headers `MCP-Protocol-Version`, `Mcp-Method` and (for `tools/call`,
  `prompts/get`, `resources/read`) `Mcp-Name`.
- **`server/discover`** replaces the handshake for capability discovery, and
  `subscriptions/listen` replaces the change notifications.
- **`ping`, `logging/setLevel` and the `resources/subscribe` pair are gone.** In
  this era the server answers them with `-32601`; in the legacy era t
ai-toolsbundesgerichtcase-lawclaudecourt-decisionselasticsearchentscheidsuchefastmcplegal-researchlegal-techllmmcpmodel-context-protocolopen-dataopen-governmentpythonrechtsprechungswiss-lawswiss-public-data-mcpswitzerland

Lo que la gente pregunta sobre swiss-courts-mcp

¿Qué es malkreide/swiss-courts-mcp?

+

malkreide/swiss-courts-mcp es mcp servers para el ecosistema de Claude AI. MCP Server for Swiss court decisions via entscheidsuche.ch - BGer, BVGer, BStGer + 26 cantonal courts Tiene 1 estrellas en GitHub y su última actualización registrada es del 2026-09-19.

¿Cómo se instala swiss-courts-mcp?

+

Puedes instalar swiss-courts-mcp clonando el repositorio (https://github.com/malkreide/swiss-courts-mcp) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.

¿Es seguro usar malkreide/swiss-courts-mcp?

+

Nuestro agente de seguridad ha analizado malkreide/swiss-courts-mcp y le ha asignado un Trust Score de 95/100 (tier: Verified). Revisa el desglose completo de comprobaciones superadas y flags en esta página.

¿Quién mantiene malkreide/swiss-courts-mcp?

+

malkreide/swiss-courts-mcp es mantenido por malkreide. La última actividad registrada en GitHub es del 2026-09-19, con 1 issues abiertos.

¿Hay alternativas a swiss-courts-mcp?

+

Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.

Despliega swiss-courts-mcp en tu cloud

Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.

¿Mantienes este repo? Añade un badge a tu README

Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.

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

Más MCP Servers

Alternativas a swiss-courts-mcp