at-ris-mcp
by paragraflabs
README.md
# at-ris-mcp
<!-- mcp-name: io.github.paragraflabs/at-ris-mcp -->
[](https://github.com/paragraflabs/at-ris-mcp/actions/workflows/ci.yml)
[](https://pypi.org/project/at-ris-mcp/)
[](https://pypi.org/project/at-ris-mcp/)
[](LICENSE)
A generic **MCP server** and standalone **Python client library** for the
Austrian **RIS** (Rechtsinformationssystem des Bundes), the official legal
information system of the Republic of Austria, operated by the Bundeskanzleramt.
It covers **federal law** (Bundesrecht — including *consolidated* law `BrKons`,
the official gazettes, drafts and government bills), **state law** (Landesrecht
of the nine Bundesländer), **case law** (Judikatur — OGH, VfGH, VwGH, BVwG,
LVwG and more), ministerial decrees, district and municipal law, plus a
change-monitoring feed — via the keyless OGD API at
`https://data.bka.gv.at/ris/api/v2.6/`.
## Why this exists
Compared with existing RIS tooling, `at-ris-mcp` adds:
- **Consolidated law** (`BrKons`) — the currently applicable text, not just the
gazette novellas.
- **Section-precise access** — retrieve a single `§`/Artikel/Anlage
(`Abschnitt.*`).
- **Historical version** — the law as it stood on a given date
(`Fassung.FassungVom`).
- **Clean Markdown** — RIS HTML (≈40 KB of CSS boilerplate per document) is
stripped to the legal text, with a `raw` switch for the untouched original.
## Two packages, one repo
- **`ris_client`** — a standalone, MCP-independent library. Import it directly.
- **`ris_mcp`** — a thin FastMCP/stdio wrapper exposing 10 tools. Contains no
logic of its own.
## Install
```bash
pip install at-ris-mcp # library only
pip install "at-ris-mcp[mcp]" # + the MCP server (FastMCP)
```
## Run the MCP server (stdio)
```bash
at-ris-mcp
# or
python -m ris_mcp
```
### Remote transport (HTTP / SSE)
For hosted or remote deployments the server can also speak streamable HTTP or
SSE instead of stdio:
```bash
at-ris-mcp --transport http --host 0.0.0.0 --port 8000 # streamable HTTP
at-ris-mcp --transport sse --port 9000 # SSE
# or via environment:
RIS_MCP_TRANSPORT=http RIS_MCP_PORT=8000 at-ris-mcp
```
The HTTP endpoint is served at `/mcp` by default (override with `--path` /
`RIS_MCP_PATH`). stdio remains the default when no transport is given.
### Tools
| Tool | Purpose |
|---|---|
| `ris_search_law` | Search Bundesrecht (default `BrKons`; also `BgblAuth`, `Begut`, `RegV`, …). Section + Fassung filters. |
| `ris_get_law_text` | Full text of a statute/section (markdown \| html \| xml \| raw). |
| `ris_search_case` | Search Judikatur (default `Justiz`; all courts). Filter by applied `norm`. |
| `ris_get_case_text` | Full text of a decision. |
| `ris_search_state_law` | Search Landesrecht (9 Bundesländer; default `LrKons`). Select states via `bundeslaender`. |
| `ris_search_misc` | Search `Sonstige` (ministerial decrees `Erlaesse`, `Avsv`, …). |
| `ris_search_district` | Search district authority notices (`Bezirke` / `Bvb`). |
| `ris_search_municipality` | Search municipal law (`Gemeinden` / Gemeinderecht `Gr`/`GrA`). |
| `ris_list_collections` | Which endpoints/applications are covered. |
| `ris_list_changes` | Change/early-warning feed (RIS History) via the OGD SOAP endpoint, incl. deleted documents. |
## Library usage
```python
import asyncio
from ris_client import RisClient, LawSearchRequest, TextFormat
async def main():
async with RisClient() as c:
res = await c.search_law(LawSearchRequest(suchworte="Datenschutzgesetz",
page_size="Ten"))
hit = res.items[0]
print(hit.human_readable_citation, hit.eli_uri)
text = await c.get_text(hit.content_urls["html"], TextFormat.markdown)
print(text.content[:500])
asyncio.run(main())
```
## Configuration (environment variables)
| Variable | Default | Meaning |
|---|---|---|
| `RIS_BASE_URL` | `https://data.bka.gv.at/ris/api/v2.6` | API base (version isolation). |
| `RIS_SOAP_URL` | `https://data.bka.gv.at/ris/ogd/v2.6/` | OGD SOAP endpoint (used by `ris_list_changes`). |
| `RIS_USER_AGENT` | `at-ris-mcp/<version> (+…)` | Descriptive UA (netiquette). |
| `RIS_RATE_MS` | `1200` | Minimum delay between requests (ms). |
| `RIS_TIMEOUT_S` | `30` | HTTP timeout. |
| `RIS_MAX_RETRIES` | `3` | Retries on 429/5xx (incl. 503). |
| `RIS_CACHE_DIR` | `~/.cache/at-ris-mcp` | SQLite cache location. |
| `RIS_CACHE_ENABLED` | `true` | Toggle the cache. |
| `RIS_AUDIT_DIR` | *(unset)* | If set, append a JSONL audit line per call (no full text, no client data). |
| `RIS_MCP_TRANSPORT` | `stdio` | Server transport: `stdio` \| `http` \| `sse` \| `streamable-http`. |
| `RIS_MCP_HOST` | `127.0.0.1` | Bind host for http/sse. |
| `RIS_MCP_PORT` | `8000` | Bind port for http/sse. |
| `RIS_MCP_PATH` | *(transport default)* | URL path for the HTTP endpoint. |
## Netiquette & rate limiting
Per the RIS OGD FAQ: a 1–2 s pause per page is enforced by the client; bulk
access should occur outside office hours (18:00–06:00) or on weekends and be
announced to `ris.it@bka.gv.at`. A descriptive User-Agent is sent by default.
## Licensing
- **Code:** Apache-2.0 (see `LICENSE`).
- **Data:** the retrieved documents are provided by the Republic of Austria
under **CC BY 4.0**. Every tool response carries an `attribution` field:
> Quelle: RIS – Rechtsinformationssystem des Bundes (data.bka.gv.at), CC BY 4.0
- **No legal advice.** Only the authentic promulgation text
(Bundesgesetzblatt/Landesgesetzblatt authentisch) is legally binding.
Consolidated law and all other documents are for information only. Every
response carries a `legal_notice` to that effect.
## Scope (v1.0)
Covered: `Bundesrecht` (`BrKons`, `BgblAuth`, `BgblPdf`, `BgblAlt`, `Begut`,
`RegV`, `Erv` — English translations), `Judikatur` (all courts), the **History
change-feed** (`ris_list_changes`, incl. deleted documents), **Landesrecht**
(9 Bundesländer: `LrKons`, `LgblAuth`, `Lgbl`, `LgblNO`, `Vbl`), **Sonstige**
(`Erlaesse`, `Avsv`, `Avn`, `Spg`, `KmGer`, `Upts`, `Mrp`, `PruefGewO` — with
app-specific fine filters), **Bezirke** (`Bvb`) and **Gemeinden** (`Gr`, `GrA`).
> **English translations (`Erv`):** search via `ris_search_law` with
> `applikation="Erv"`. `suchworte`/`titel` map to the RIS `SearchTerms`/`Title`
> parameters automatically.
> **State-law note:** for consolidated state law (`LrKons`), select the states
> via `bundeslaender` (e.g. `["Kaernten","Tirol"]`); this maps to the dotted
> `Bundesland.SucheIn<Land>=true` flags the API requires (the flat form is
> silently ignored for `LrKons`).
> **History note:** consolidated federal law is monitored under the application
> name `Bundesnormen` (not `BrKons`), consolidated state law under
> `Landesnormen`. The History query uses the OGD **SOAP** endpoint, since it is
> not exposed via the REST GET API.
## Development
```bash
pip install -e ".[dev,mcp]"
pytest # offline tests
RIS_SMOKE=1 pytest -m smoke # live smoke tests against the real API
```
## Release
Releases are automated. Pushing a version tag (`vX.Y.Z`) triggers GitHub
Actions to build the distributions, publish to **PyPI** (via Trusted
Publishing / OIDC — no API token), create a **GitHub Release**, and register
the new version in the **MCP Registry**.
```bash
# bump the version in pyproject.toml, src/ris_client/config.py and server.json,
# update CHANGELOG.md, then:
git tag -a vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.Z
```
To build locally without publishing:
```bash
python -m build # sdist + wheel into dist/
python -m twine check dist/*
```
The MCP-registry entry is described by `server.json`
(name `io.github.paragraflabs/at-ris-mcp`); the matching `mcp-name:` marker is
embedded near the top of this README.
## Contributing & support
Issues and pull requests are welcome at
<https://github.com/paragraflabs/at-ris-mcp>. See [`CHANGELOG.md`](CHANGELOG.md)
for the release history. This project is not affiliated with or endorsed by the
Republic of Austria or the Bundeskanzleramt; it is an independent client for the
public RIS OGD interface.
TDQS
A4.3/5.0
Scored across 2 tools
Disambiguation5/5
The two tools have clearly distinct purposes: one for searching Austrian federal law, the other for fetching the full text of a specific statute or section. There is no overlap or ambiguity.
Naming Consistency5/5
Both tools follow a consistent 'ris_verb_noun' pattern: ris_search_law and ris_get_law_text. The naming is predictable and clear.
Tool Count3/5
With only two tools, the server is minimal. While they cover search and retrieval, the scope suggests more tools might be expected (e.g., browsing, categorizing), making it borderline but not inadequate.
Completeness4/5
The tool surface covers the essential workflow of searching for a law and retrieving its full text. Minor gaps exist, such as direct access to law lists or metadata, but the core functionality is complete.
Maintenance
ActivitySlowing
ResponsivenessUnresponsive