polish-legal-mcp
by Emenowicz
README.md
# polish-legal-mcp
Free, open-source **remote MCP server** that lets Claude (web, desktop, mobile) and other MCP
clients retrieve, inspect and cite **official Polish legislation** (ELI API of the Sejm —
Dziennik Ustaw, Monitor Polski), **bills pending in the Sejm** (API Sejmu), **EU law and CJEU
case law in Polish** (EU Publications Office / EUR-Lex) and **court decisions** (SAOS), with
explicit, component-level verification of what could and could not be established.
> **Ważne / Important**
> - Serwer jest narzędziem pomocniczym, **nie** źródłem opinii prawnych. Każde twierdzenie bez
> statusu `VERIFIED` należy traktować jako niezweryfikowane.
> - **Nie wklejaj do publicznych usług AI informacji poufnych, niejawnych, wewnętrznych dokumentów
> urzędu, danych osobowych ani opisów spraw zawierających takie dane.**
> - Projekt nie jest oficjalnym narzędziem żadnego organu ani instytucji publicznej.
## What it does
Main use case: helping public-administration employees prepare applications, letters and legal justifications with **correct, sourced citations**:
> „Przygotowuję wniosek dotyczący zakwaterowania żołnierza. Znajdź odpowiednie przepisy, podaj
> ich aktualne brzmienie, wskaż artykuły i przygotuj propozycję uzasadnienia."
Claude does the language work; this server supplies authoritative source material, links and
verification results. It never uses an extra LLM and never fabricates acts, articles, dates or
case numbers.
## MCP tools
| Tool | Purpose |
|---|---|
| `search_legal_acts` | Search acts by title fragments / ELI keyword; base acts ranked before amending acts. |
| `get_legal_act` | Metadata, status, dates, text formats, consolidated texts, paged table of contents. |
| `get_legal_provision` | Exact wording of art./ust./§/pkt/lit. from official HTML, citation, later amendments, verification report. |
| `search_eu_acts` | EU regulations/directives/decisions by words of the Polish title (EUR-Lex metadata); base legislative acts in force first. |
| `get_eu_transposition` | Polish acts implementing an EU directive as notified to the Commission (EUR-Lex national implementing measures), mapped to ELI with the official ELI title for cross-checking. |
| `get_eu_provision` | EU law (e.g. RODO) by CELEX, act number or alias: most current Polish wording from the EUR-Lex consolidated version (amendments + corrigenda), OJ citation, verification report. |
| `get_legal_act_amendments` | Amending acts with ELI dates, pending amendments, amendments after the latest consolidated text, TK rulings. |
| `verify_legal_citation` | Parse a citation ("art. 61 § 1 k.p.a.", "… (Dz. U. z 2025 r. poz. 817)"), resolve the act, check unit, wording and date. |
| `find_legal_basis` | Candidate acts: ELI keyword vocabulary + title stems; with the optional act index also titles, statute division/chapter headings and the texts of statutes and regulations (pointing at the matching article or §); optionally (`include_case_law`) acts most cited in SAOS judgments matching the description. Candidates, not advice. |
| `search_judgments` | SAOS search (common courts, Supreme Court, Constitutional Tribunal, KIO) with paging. |
| `get_judgment` | Judgment metadata, legal bases, referenced regulations, paged text, phrase excerpts, citation built from SAOS metadata ("wyrok Sądu Najwyższego z dnia …, I CSK …"). |
| `search_cjeu_judgments` | CJEU / General Court decisions interpreting an EU act (optionally one article, e.g. RODO art. 17), newest first, from EU Publications Office metadata. |
| `get_cjeu_judgment` | CJEU decision in Polish by case number, ECLI or CELEX: operative part, selected numbered paragraphs or paragraphs matching a phrase, interpreted provisions, language of the case, citation with ECLI and "pkt N". |
| `find_pending_bills` | Bills pending in the Sejm (current term) whose title names a given act or contains a phrase, with the current stage and links (Sejm, RCL). |
| `find_judgments_for_provision` | Judgments (newest first) whose SAOS metadata cite a given provision, e.g. art. 5 k.c.; each one confirmed against its list of cited provisions. |
Tool names are English; descriptions and messages are Polish. All outputs are typed (structured
content) and bounded.
### Verification statuses
`VERIFIED`, `UNVERIFIED`, `NOT_FOUND`, `NOT_APPLICABLE`, `SOURCE_UNAVAILABLE` — reported
separately for source authenticity, act existence, unit existence, exact wording, act in force on
the date, temporal applicability of the wording, amendment completeness (always `UNVERIFIED`)
and substantive relevance (always `UNVERIFIED`). Full semantics:
[docs/legal-verification.md](docs/legal-verification.md).
Example (real data, 2026-10-30): art. 61 § 1 k.p.a. → wording read from the current consolidated
text Dz. U. 2025 poz. 1691 (PDF only) and confirmed against the last official HTML text →
`exact_wording` and `temporal_applicability: VERIFIED`; `amendment_completeness: UNVERIFIED`.
## Requirements
- Python 3.12+ (3.13 pinned in `.python-version`), [uv](https://docs.astral.sh/uv/)
- Docker (for container deployment); a Raspberry Pi 4/5 with 64-bit OS for the reference setup
- For remote use: Tailscale account (free) and a Google OAuth client or GitHub OAuth App (free)
## Quick start (local)
```bash
uv sync
uv run polish-legal-mcp # http://127.0.0.1:8000/mcp (development, no auth)
curl -fsS http://127.0.0.1:8000/health
```
## Configuration
Environment variables with prefix `PLMCP_` (see [.env.example](.env.example)):
| Variable | Default | Notes |
|---|---|---|
| `PLMCP_ENV` | `development` | `production` requires Google or GitHub OAuth or refuses to start. |
| `PLMCP_HOST` / `PLMCP_PORT` | `127.0.0.1` / `8000` | Docker image sets `0.0.0.0`. |
| `PLMCP_AUTH_MODE` | `none` | `google` or `github` for remote use. |
| `PLMCP_BASE_URL` | – | Public HTTPS URL without `/mcp`. |
| `PLMCP_GITHUB_CLIENT_ID` / `_SECRET` | – | GitHub OAuth App. |
| `PLMCP_JWT_SIGNING_KEY` | – | ≥32 random chars. |
| `PLMCP_ALLOWED_GITHUB_LOGINS` | – | Comma-separated allowlist (GitHub mode). |
| `PLMCP_GOOGLE_CLIENT_ID` / `_SECRET` | – | Google OAuth client (Web application). |
| `PLMCP_ALLOWED_EMAILS` | – | Comma-separated allowlist of Google-verified e-mails (Google mode). |
| `PLMCP_ALLOWED_GOOGLE_DOMAINS` | – | Comma-separated Google Workspace domains: all accounts managed by the domain (Google mode). |
| `PLMCP_OPEN_REGISTRATION` | `false` | Public instance: any Google-verified e-mail / any GitHub login may use the tools; allowlists are ignored. Requires the daily cap below. |
| `PLMCP_DAILY_TOOL_CALLS_PER_USER` | `0` (no cap) | Tool calls per account per UTC day (in memory, resets on restart). |
| `PLMCP_RATE_LIMIT_PER_SECOND` / `_BURST` | `2` / `20` | Per user. |
| `PLMCP_AUTHORIZE_PER_HOUR` / `PLMCP_REGISTER_PER_HOUR` | `60` / `20` | Global hourly caps on OAuth sign-ins and dynamic client registrations; raise before announcing a public instance. |
| `PLMCP_HTTP_TIMEOUT_SECONDS`, `PLMCP_HTTP_MAX_RETRIES` | `20`, `3` | Upstream calls. |
| `PLMCP_CACHE_TTL_SECONDS`, `PLMCP_CACHE_MAX_DOCUMENTS` | `3600`, `32` | In-memory cache (metadata TTL, max documents). |
| `PLMCP_DOCUMENT_CACHE_TTL_SECONDS` | `86400` | Cache time for texts of published acts (ELI struct/HTML/PDF, EUR-Lex documents). |
| `PLMCP_ACT_INDEX` / `PLMCP_ACT_INDEX_PATH` | `false` / – | Production, opt-in: `find_legal_basis` act index in one SQLite file (~160 MB, public ELI data only); the path is required when enabled. |
| `PLMCP_ACT_INDEX_REBUILD_DAYS` | `7` | Incremental refresh interval (only statutes with a new consolidated text are refetched). |
| `PLMCP_PREWARM_ACTS` | k.c., k.p.c., k.p.a., k.p. | Production: acts whose newest texts are loaded at start-up and every 6 h (empty = off). |
## Testing
```bash
uv run pytest # offline suite: real captured ELI/SAOS fixtures, mocked with respx
uv run pytest -m live # optional smoke tests against the real APIs
uv run ruff check . && uv run ruff format --check . && uv run pyright
```
## Docker
```bash
docker compose up -d --build # loopback-only port, non-root, read-only FS, healthcheck
docker compose ps
```
## Remote deployment (Raspberry Pi + Tailscale Funnel)
Short version — full runbook with verification commands in [docs/deployment.md](docs/deployment.md):
1. Pi OS 64-bit, Docker, `sudo tailscale up`, enable MagicDNS + HTTPS + Funnel in the tailnet.
2. GitHub OAuth App with callback `https://<pi>.<tailnet>.ts.net/auth/callback`.
3. `.env` with `PLMCP_ENV=production`, GitHub credentials, JWT key, allowed logins.
4. `docker compose up -d --build && sudo tailscale funnel --bg 8000`.
5. From outside the LAN: `curl https://<pi>.<tailnet>.ts.net/health` and expect `401` on `/mcp`.
Hosts without Docker can use the hardened systemd unit and installer in [deploy/](deploy/)
(§3.8). A VPS + Caddy variant is in the same document.
## Claude integration (incl. mobile)
1. claude.ai → Settings → Connectors → **Add custom connector** (the Free plan allows one).
2. URL `https://<pi>.<tailnet>.ts.net/mcp`; leave OAuth client ID/secret empty.
3. Connect → consent page → sign in with an allowlisted Google or GitHub account.
4. Enable the connector in a chat and ask in Polish, e.g.:
- „Znajdź ustawę o gospodarce nieruchomościami."
- „Podaj artykuły dotyczące zakwaterowania żołnierzy."
- „Sprawdź, czy art. 61 § 1 k.p.a. obowiązuje na dzień 29 października 2026 roku."
- „Znajdź orzeczenia dotyczące gospodarowania nieruchomościami Skarbu Państwa."
- „Zweryfikuj podstawę prawną: art. 61 § 1 k.p.a. i podaj link do oficjalnego źródła."
- „Podaj propozycję uzasadnienia wniosku na podstawie znalezionych przepisów, wyraźnie oznaczając
wszystkie nieweryfikowane twierdzenia."
5. Mobile: the connector is available in the Claude app (Android/iOS) on the same account; the
server is reached through Anthropic's infrastructure, so it must be publicly reachable.
No Anthropic API key and no running Claude Code are needed after deployment. The server cannot
guarantee Claude's final wording — it supplies evidence; check the statuses.
## Security
HTTPS via Funnel, OAuth 2.1 (DCR/CIMD, PKCE) with Google or GitHub sign-in and an allowlist, fail-closed
production config, Host guard, per-user rate limits, bounded inputs/outputs, no arbitrary URL
fetching, no logging of tool arguments, hardened container. Details: [docs/security.md](docs/security.md).
## Known limitations
- ELI has **no HTML for acts published since 2025** → the server reads the PDF: unchanged
provisions are confirmed against the last HTML text (`VERIFIED`); changed ones are read
automatically from the PDF and marked `UNVERIFIED` with the page number to check.
- SAOS has **no administrative-court (WSA/NSA) judgments** and is not complete.
- With the act index, texts of statutes and regulations are searched in the newest version ELI publishes as HTML (often a pre-2025 consolidated text or the original text, since newer consolidated texts are PDF-only); acts available only as PDF are matched by title.
- EU law: acts are found by number/CELEX/alias or words of the Polish title (no full-text search);
wording comes from the EUR-Lex consolidated version, which is informational — the Official
Journal text is binding. CJEU decisions in Polish are translations.
- Amendment completeness, staged entry into force of amending acts and transitional provisions
are not verified.
See [docs/limitations.md](docs/limitations.md).
## Roadmap
Delivered items, sources blocked for lack of an official API (e.g. WSA/NSA) and deliberate
non-goals: [docs/roadmap.md](docs/roadmap.md).
## Documentation
[architecture](docs/architecture.md) · [API research](docs/api-research.md) ·
[legal verification](docs/legal-verification.md) · [security](docs/security.md) ·
[deployment](docs/deployment.md) · [limitations](docs/limitations.md) ·
[roadmap](docs/roadmap.md)
## License
MIT — see [LICENSE](LICENSE). Legal texts and judgments come from public official sources
(api.sejm.gov.pl, saos.org.pl, publications.europa.eu).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues