Skip to main content

Part of the Swiss Public Data MCP Portfolio

🏛️ swiss-courts-mcp

Version License: MIT Python 3.11+ MCP No Auth Required CI

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

Demo: Claude searches Swiss court decisions via MCP tool call


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 (live, default) Federal + 26 cantons Court decisions since ~2000
SCD dump (offline fallback) Federal Supreme Court only, 2007–2024 Metadata/regesten, no full text

Synergy with 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). 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

pip install swiss-courts-mcp

Or install from source:

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

Quickstart

# Run directly
swiss-courts-mcp

# Or via Python module
python -m swiss_courts_mcp

Configuration

Claude Desktop

Add to your claude_desktop_config.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.

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

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_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.

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

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

MCP Protocol Version

This server pins MCP protocol version 2025-11-25 (constant PROTOCOL_VERSION in server.py). A regression test detects drift against the installed SDK so a protocol bump is a conscious change (version + CHANGELOG + this section). SDK updates land monthly via Dependabot.

Project Phase

Phase 1 — read-only (see ROADMAP.md). All tools are readOnlyHint: true; there are no writing or destructive operations. A move to Phase 2 (write) requires a clean re-audit and the gates listed in the roadmap.


Available Tools

Court Decision Search

Tool Description
search_court_decisions Full-text search across all court decisions with canton, court level, and date filters
get_court_decision Retrieve a single decision by its unique signature
search_bger_decisions Search Federal Supreme Court decisions with optional chamber filter
search_by_law_reference Find decisions citing a specific law article (e.g., "Art. 8 BV")

Court Information

Tool Description
list_courts List all indexed courts, optionally filtered by canton
get_recent_decisions Latest decisions, filterable by canton and court level
get_decision_statistics Statistics on indexed decisions by canton and year
get_fallback_status Offline-dump cache state, coverage, version, pre-warming (read-only)

Tool Annotations

All eight tools share the same hints — they are read-only, idempotent, non-destructive, and reach an external system:

Annotation Value
readOnlyHint true
destructiveHint false
idempotentHint true
openWorldHint true

A rechtsrecherche prompt is also provided (a second MCP primitive alongside tools).

Example Use Cases

Use Case Tool Chain
Research case law on data protection search_court_decisions("Datenschutz")
Find practice on a constitutional right search_by_law_reference("Art. 8 BV")
Latest Federal Supreme Court rulings search_bger_decisions("Arbeitsrecht", date_from="2024-01-01")
Combined: Law text + case law fedlex_search_laws("DSG") then search_by_law_reference("Art. 25 DSG")

→ More use cases by audience →


Architecture

┌─────────────────────────────────────┐
│         MCP Client (LLM)            │
│   Claude / Cursor / Windsurf        │
└──────────────┬──────────────────────┘
               │ MCP Protocol
┌──────────────▼──────────────────────────────┐
│              swiss-courts-mcp               │
│  8 tools · Pydantic validation              │
│  Elasticsearch query builder                │
│  Provenance envelope: source = live | dump  │
└───────┬──────────────────────────────┬──────┘
        │ ① live (default)             │ ② fallback
        │ HTTPS POST/GET               │ on bot-block / 5xx / 429 /
        │                              │ timeout, or SWISS_COURTS_FORCE_DUMP=1
┌───────▼──────────────────┐   ┌───────▼───────────────────────────────┐
│     entscheidsuche.ch    │   │   SCD dump — Zenodo 14867950 (CC BY)  │
│  Elasticsearch backend   │   │   lazy download → platformdirs cache  │
│  Federal + 26 cantons    │   │   → local SQLite search               │
│  no auth · no SLA        │   │   BGer only · 2007–2024 · no full text │
└──────────────────────────┘   └───────────────────────────────────────┘

Live-first, always: the offline dump only engages on an availability failure (bot-block, HTTP 5xx/429, timeout) or when forced. It is a behaviour of the existing tools, not a separate search tool — why this source and not the full-text one is in ADR 0002; what it does and does not cover is under Known Limitations. Inspect the cache at any time with get_fallback_status.


Safety & Limits

Aspect Details
Access Read-only (readOnlyHint: true) — the server cannot modify or delete any data
Personal data No personal data — all decisions are public court rulings
Rate limits Built-in per-query caps (max 50 results per search, 50 aggregation buckets)
Timeout 30 seconds per API call
Data source auth No API keys required — entscheidsuche.ch is publicly accessible
HTTP transport auth Optional bearer-token auth (JWT, sub-claim identity); see ADR 0001
Egress Code-layer allow-lists (entscheidsuche.ch for live; zenodo.org for the offline dump), HTTPS-enforced; see egress policy
Error masking Internal exceptions are logged server-side only; clients receive friendly messages
Secrets No secrets in code/logs; .env git-ignored, Gitleaks on PRs; see secret management
Licenses Court decisions are public domain under Swiss law (BGG Art. 27)
Terms of Service Subject to entscheidsuche.ch usage terms — please be kind to the server

Known Limitations

  • Search is limited to decisions indexed by entscheidsuche.ch (not all decisions are publicly available)
  • Full-text document content is not returned — only metadata, title, and abstract
  • Statistics depend on Elasticsearch aggregation support of the backend
  • The court taxonomy structure from Facetten_alle.json may vary

Offline fallback (partial coverage — read this): the fallback is a safety net for availability, not an equivalent mirror of the live source:

  • Court scope: Federal Supreme Court only (BGer/BGE). Bundesverwaltungsgericht, Bundesstrafgericht and all 26 cantonal courts are not covered.
  • Time span: 2007 – December 2024 (the SCD dump's range). Decisions outside this window are not in the dump.
  • Content: metadata/regesten only — no full text offline.
  • Update latency: the SCD dump is refreshed roughly quarterly on Zenodo, so the offline data lags the live index. get_fallback_status reports the cached version and can check Zenodo for a newer one.
  • Law-reference search offline only matches references named in the decision's subject/regest (topic/issue) — there is no offline cited-law index.
  • get_court_decision is best-effort offline: SCD case ids (docref, e.g. 1C_517/2016) differ from entscheidsuche signatures, so some lookups are honestly reported as non-resolvable.
  • Responses always disclose their origin via source (live/dump) and a coverage_note; the server never silently narrows coverage — an uncovered query gets an explicit "not covered" answer, never a silent empty result.

Testing

Unit tests mock all HTTP with respx. Run from the project root. The five gates CI runs — check_gate_docs.py holds this list against ci.yml, so it cannot quietly fall behind:

PYTHONPATH=src pytest tests/ -m "not live"
ruff check src/ tests/ scripts/
ruff format --check src/ tests/ scripts/
python scripts/check_version_sync.py
python scripts/check_gate_docs.py

The live tests are not a gate — they hit the real source and run on a schedule (live.yml), not on pull requests:

PYTHONPATH=src pytest tests/ -v -m live

Editing live.yml is a special case: GitHub only honours schedule on the default branch, so changes take effect after the merge — trigger it by hand (workflow_dispatch) to test them before that.

The offline-fallback tests mock the Zenodo download with respx and use a small committed fixture — the ~120 MB dump is never downloaded in CI.


Changelog

See CHANGELOG.md.


Contributing

See CONTRIBUTING.md.


Security

See SECURITY.md for the security posture and how to report a vulnerability.


License

MIT


Author

Hayal Oezkan · malkreide


Credits & Related Projects

Installation

Run via uv's uvx — no clone or manual install needed. Add to your MCP client config (mcpServers for Claude Desktop, Cursor and Windsurf; use a top-level servers key for VS Code in .vscode/mcp.json):

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

swiss_courts_mcp-0.4.0.tar.gz (379.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

swiss_courts_mcp-0.4.0-py3-none-any.whl (46.9 kB view details)

Uploaded Python 3

File details

Details for the file swiss_courts_mcp-0.4.0.tar.gz.

File metadata

  • Download URL: swiss_courts_mcp-0.4.0.tar.gz
  • Upload date:
  • Size: 379.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for swiss_courts_mcp-0.4.0.tar.gz
Algorithm Hash digest
SHA256 a594db040ea75475f5953a4dd7306c6a4d350eddb0144b9c7d516faf226465f9
MD5 d89962b32cd4937801429d16883574f3
BLAKE2b-256 b49c480c8496d8d7ad25e49f5b0769c710c59cd2bba22ffbb691dfde1cea3dcf

See more details on using hashes here.

Provenance

The following attestation bundles were made for swiss_courts_mcp-0.4.0.tar.gz:

Publisher: publish.yml on malkreide/swiss-courts-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file swiss_courts_mcp-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for swiss_courts_mcp-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ef2b3c2acf3ce0addef8b56600c244c36e646ddaee0a644d9d66ab9ed4979be4
MD5 5fda8a7d1998f94dbeda532e39e5d12b
BLAKE2b-256 9d558cf01eff60c58298bc6674da224168c75b6da1550b18c3fb4ad0966ae17f

See more details on using hashes here.

Provenance

The following attestation bundles were made for swiss_courts_mcp-0.4.0-py3-none-any.whl:

Publisher: publish.yml on malkreide/swiss-courts-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 files

0.3.1

2 files

0.3.0

2 files

0.2.3

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page