Skip to main content

MCP Server for Swiss court decisions — Federal Supreme Court, Federal Administrative Court, Federal Criminal Court + 26 cantonal courts via entscheidsuche.ch

Project description

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.

Source Coverage Data
entscheidsuche.ch Federal + 26 cantons Court decisions since ~2000

Synergy with fedlex-mcp: Legislation (SR) + case law = complete legal research.


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)
  • 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.


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

Tool Annotations

All seven 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              │
│  7 tools · Pydantic validation      │
│  Elasticsearch query builder        │
└──────────────┬──────────────────────┘
               │ HTTPS (POST/GET)
┌──────────────▼──────────────────────┐
│       entscheidsuche.ch             │
│  Elasticsearch backend              │
│  No authentication required         │
│  Federal + 26 cantonal courts       │
└─────────────────────────────────────┘

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-list (entscheidsuche.ch only, 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

Project Structure

swiss-courts-mcp/
├── src/
│   └── swiss_courts_mcp/
│       ├── __init__.py
│       ├── __main__.py
│       ├── server.py            # MCP server, 7 tools + 1 prompt, lifespan, auth wiring
│       ├── api_client.py        # HTTP client, ES query builder, egress allow-list
│       ├── auth.py              # JWT bearer-token verifier (HTTP transport)
│       ├── config.py            # Settings object (env-driven)
│       ├── logging_config.py    # structured logging on stderr
│       └── models.py            # structured response envelope
├── tests/                       # unit (respx-mocked) + live + security tests
├── docs/                        # egress, secret-management, ADRs
├── .github/workflows/           # ci · security (gitleaks) · live · publish
├── Dockerfile                   # hardened container (non-root, 0.0.0.0 only here)
├── ROADMAP.md
├── pyproject.toml · CHANGELOG.md · CONTRIBUTING.md · LICENSE
└── README.md · README.de.md

Note (single-file tools): the 7 tools live in server.py rather than a tools/ package. At this count a single module stays readable; the registry (register_tools) keeps registration declarative. This is a deliberate deviation from the "split when > 5 tools" convention and will be revisited if the tool count grows.


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

Testing

Unit tests mock all HTTP with respx; live tests hit the real API and run in a separate nightly workflow (live.yml), never blocking PRs.

# Unit tests (HTTP mocked) — what CI runs
pytest tests/ -v -m "not live"

# Live API tests (real entscheidsuche.ch)
pytest tests/ -v -m live

# Linting
ruff check src/ tests/
ruff format src/ tests/

Changelog

See CHANGELOG.md.


Contributing

See CONTRIBUTING.md.


License

MIT


Author

Hayal Oezkan · malkreide


Credits & Related Projects

Project details


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.2.0.tar.gz (71.3 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.2.0-py3-none-any.whl (27.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: swiss_courts_mcp-0.2.0.tar.gz
  • Upload date:
  • Size: 71.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for swiss_courts_mcp-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8ee0a5e31082d29ed198ff417dce4da7d93bc4d73454271efd119283b61d99d7
MD5 ee5d0062b9630a01a2bf95d863cff789
BLAKE2b-256 51b6c38057ca823ff06871be152756bcce55fe9c33b9ebfd9496526a976a83ef

See more details on using hashes here.

Provenance

The following attestation bundles were made for swiss_courts_mcp-0.2.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.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for swiss_courts_mcp-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dc90d18e72cabcf8c5cae590d7f7e917580b3cbb3f1cc847d64434b2e7eccecb
MD5 37bc651ceb158be59d604b0009145dbc
BLAKE2b-256 df4d1098219f0f03f584ca7b8fb29b6bbb18b5f21e443e659679c24e2bf96188

See more details on using hashes here.

Provenance

The following attestation bundles were made for swiss_courts_mcp-0.2.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.

Supported by

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