Swiss Courts Mcp

io.github.malkreide由 GitHub 帳號 malkreide 發布v0.5.0更新於 Oct 24, 2026

Swiss court decisions via entscheidsuche.ch, including federal and cantonal courts

發布方自己的服務:Nyc Tennis Courts

概覽

AI 產生的概覽

讓助理檢索瑞士聯邦與各邦法院裁判,並在即時來源無法使用時退回離線資料。

可以做什麼
透過 entscheidsuche.ch 提供八個唯讀工具:可依邦、法院層級與日期篩選的裁判全文檢索、依簽章取得單一裁判、附法庭篩選的聯邦最高法院檢索,以及依所引法律條文檢索。另可列出法院、傳回最新裁判、提供裁判統計,並查看離線資料快取的覆蓋情形。另附一個法律研究提示詞,每個回應都會標明來源是即時或離線資料。
適用情境
適合瑞士判例法研究,例如查找某項憲法權利的實務或聯邦最高法院在某一主題上的裁判。與瑞士聯邦法律伺服器搭配使用時,可同時取得法條文本與判例。
執行需求
以本機 Python 套件執行(需 Python 3.11 或以上版本),可透過 uvx 或 pip 安裝,並在相容 MCP 的用戶端中使用。不需要 API 金鑰。即時結果需要連線 entscheidsuche.ch,選用的離線資料下載需要連線 zenodo.org。選用的 HTTP 模式使用 MCP_AUTH_SECRET、MCP_OAUTH_AUDIENCE、MCP_OAUTH_ISSUER、MCP_RESOURCE_URL 等環境變數。
安裝前請注意
唯讀:所有工具都標記 readOnlyHint 為 true,沒有寫入或破壞性操作。查詢與結果會傳送給第三方來源 entscheidsuche.ch 與 zenodo.org。離線備援僅涵蓋 2007 年至 2024 年 12 月的聯邦最高法院,且只回傳中繼資料而無全文,屬於部分覆蓋而非等同鏡像。HTTP 傳輸預設關閉;未認證執行會發出警告,應置於具認證的反向代理之後。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Swiss Courts Mcp,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

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

SourceCoverageData
entscheidsuche.ch (live, default)Federal + 26 cantonsCourt decisions since ~2000
SCD dump (offline fallback)Federal Supreme Court only, 2007–2024Metadata/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

bash
pip install swiss-courts-mcp

Or install from source:

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

Quickstart

bash
# Run directlyswiss-courts-mcp
# Or via Python modulepython -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 onlyswiss-courts-mcp --http --port 8000
# Container (binds 0.0.0.0, auth enabled) — see Dockerfiledocker 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):

VariableDefaultPurpose
MCP_HOST127.0.0.1Bind host. Set to 0.0.0.0 only in containers.
MCP_PORT8000Bind port.
MCP_ALLOW_PUBLIC_BINDfalseSuppress the 0.0.0.0 warning (containers).
MCP_STATELESS_HTTPtrueStateless HTTP → horizontal scaling without sticky sessions.
MCP_AUTH_ENABLEDfalseEnable 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_URLbind addressPublic 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.

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)

VariableDefaultPurpose
SWISS_COURTS_FALLBACK_ENABLEDtrueMaster switch. 0 disables the dump fallback (live-only).
SWISS_COURTS_FORCE_DUMPfalseForce the dump path (skip live) — for pre-warming the cache or offline testing.
SWISS_COURTS_CACHE_DIRplatformdirs cacheOverride the cache directory for the downloaded dump.
SWISS_COURTS_DUMP_RECORD14867950Zenodo 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:

EraRevisionConstant in server.pyHandshake
Legacy2025-11-25HANDSHAKE_PROTOCOL_VERSIONinitialize, session ID
Modern2026-07-28MODERN_PROTOCOL_VERSIONnone — 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 they remain.
  • ttlMs / cacheScope ride on the listing methods (SEP-2549) — see CACHE_HINTS.
  • serverInfo is stamped into the _meta of every response, because there is no handshake to carry it once. It names the build version, the display title and the project URL; the purpose text stays in instructions, which travels once with server/discover.

An initialize asking for 2026-07-28 still gets 2025-11-25 back: the handshake is not the way into the modern era, and that answer says nothing about whether the server speaks it.

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

ToolDescription
search_court_decisionsFull-text search across all court decisions with canton, court level, and date filters
get_court_decisionRetrieve a single decision by its unique signature
search_bger_decisionsSearch Federal Supreme Court decisions with optional chamber filter
search_by_law_referenceFind decisions citing a specific law article (e.g., "Art. 8 BV")

Court Information

ToolDescription
list_courtsList all indexed courts, optionally filtered by canton
get_recent_decisionsLatest decisions, filterable by canton and court level
get_decision_statisticsStatistics on indexed decisions by canton and year
get_fallback_statusOffline-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:

AnnotationValue
readOnlyHinttrue
destructiveHintfalse
idempotentHinttrue
openWorldHinttrue

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

Example Use Cases

Use CaseTool Chain
Research case law on data protectionsearch_court_decisions("Datenschutz")
Find practice on a constitutional rightsearch_by_law_reference("Art. 8 BV")
Latest Federal Supreme Court rulingssearch_bger_decisions("Arbeitsrecht", date_from="2024-01-01")
Combined: Law text + case lawfedlex_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

AspectDetails
AccessRead-only (readOnlyHint: true) — the server cannot modify or delete any data
Personal dataNo personal data — all decisions are public court rulings
Rate limitsBuilt-in per-query caps (max 50 results per search, 50 aggregation buckets)
Timeout30 seconds per API call
Data source authNo API keys required — entscheidsuche.ch is publicly accessible
HTTP transport authOptional bearer-token auth (JWT, sub-claim identity); see ADR 0001
EgressCode-layer allow-lists (entscheidsuche.ch for live; zenodo.org for the offline dump), HTTPS-enforced; see egress policy
Error maskingInternal exceptions are logged server-side only; clients receive friendly messages
SecretsNo secrets in code/logs; .env git-ignored, Gitleaks on PRs; see secret management
LicensesCourt decisions are public domain under Swiss law (BGG Art. 27)
Terms of ServiceSubject 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:

bash
PYTHONPATH=src pytest tests/ -m "not live"python scripts/check_ruff_pin.pyruff check src/ tests/ scripts/ruff format --check src/ tests/ scripts/python scripts/check_version_sync.pypython 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:

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

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

來源:README.md,提交 4b8fc7f

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

2
  1. v0.5.0最新Sep 19, 2026
  2. v0.4.0Sep 16, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分

其他 Courts 整合