mcp-tk
README.md
# mcp-tk
Serwer MCP dla polskiego **orzecznictwa Trybunału Konstytucyjnego**. Korzysta wyłącznie z oficjalnych serwisów TK:
- [OTK ZU — Orzecznictwo Trybunału Konstytucyjnego, Zbiór Urzędowy](https://otkzu.trybunal.gov.pl/)
- [IPO — Internetowy Portal Orzeczeń](https://ipo.trybunal.gov.pl/ipo/)
- [strona TK o obu bazach](https://trybunal.gov.pl/orzeczenia/)
Nie używa SAOS ani komercyjnych baz, nie wymaga konta i nie wymaga klucza API.
## Po co
`mcp-tk` daje modelowi rzeczywiste orzeczenia TK: sygnaturę, datę, rodzaj, przedmiot, skład, pełny tekst, bezpośredni link i pozycję OTK ZU. Długie dokumenty można pobierać sekcjami i porcjami, zamiast zużywać cały budżet kontekstu naraz.
## Instalacja
Wymagany jest Node.js 18+. Serwer nie wymaga konta ani klucza API.
### ChatGPT desktop — zalecany sposób
Zainstaluj serwer jednorazowo w terminalu:
```sh
npm install -g https://github.com/tramer222888-alt/mcp-tk/archive/refs/heads/main.tar.gz
```
Następnie w ustawieniach MCP wybierz `STDIO`.
Windows:
- polecenie: `cmd.exe`
- argumenty: `/d`, `/s`, `/c`, `mcp-tk` — każdy jako osobna pozycja
macOS/Linux:
- polecenie: `mcp-tk`
- bez argumentów
Instalacja przed dodaniem MCP jest celowa: ChatGPT ma krótki limit startu
procesu, a pobieranie pakietu przez `npx` może go przekroczyć.
### Codex CLI
Po wykonaniu powyższego `npm install -g`:
```sh
codex mcp add tk -- mcp-tk
```
Na Windows, jeśli bezpośrednie uruchomienie nie działa:
```powershell
codex mcp add tk -- cmd.exe /d /s /c mcp-tk
```
Gotowe pliki `dist` są wersjonowane w repozytorium, więc serwer nie
kompiluje TypeScriptu podczas startu.
## Narzędzia
| Tool | Działanie |
|---|---|
| `search(query, searchMode?, searchInContent?, where?, inflection?, dateFrom?, dateTo?, kind?, pageSize?, pageNumber?)` | Wyszukiwanie tematyczne. Domyślny `searchMode=auto` przeszukuje lokalny indeks pełnej treści oficjalnych dokumentów IPO, bez czekania na formularz portalu. |
| `search_by_signature(signature, pageSize?, pageNumber?)` | Dokładne wyszukiwanie po sygnaturze, np. `K 23/11`. |
| `list_recent(pageSize?, pageNumber?)` | Najnowsze orzeczenia bezpośrednio z żywej listy IPO. |
| `get_judgment(id, caseId?, section?, offset?, maxChars?)` | Pełny tekst i metadane. `id` może być ID IPO (`25657`) albo pozycją OTK ZU (`2026/A/96`). |
Każda odpowiedź zawiera `structuredContent.citations`. `get_judgment` umieszcza pobrany fragment również w `structuredContent.judgment.content_chunk`, ponieważ część klientów MCP pokazuje modelowi tylko dane strukturalne.
## Cztery tryby wyszukiwania
Domyślny `searchMode=auto` przeszukuje lokalnie pełną treść orzeczeń pobraną z oficjalnego IPO. Dzięki temu skróty i nazwy występujące dopiero w uzasadnieniu — np. `BGK` — nie znikają tylko dlatego, że nie ma ich w polu „Dotyczy”. Wyszukiwanie nie czeka na wolny formularz JSF portalu.
Tryby:
- `searchMode=auto` — zalecany; szybki lokalny indeks pełnej treści, z prostym uwzględnianiem odmiany słów;
- `searchMode=full_text` — ten sam pełnotekstowy indeks, jawnie wymagany;
- `searchMode=metadata` — tylko sygnatura, rodzaj, data i pole „Dotyczy”; najszybszy, ale o słabej kompletności;
- `searchMode=live` — żywy formularz JSF IPO; pozwala ograniczyć sekcję przez `where`, ale jest wolny i awaryjny.
Stary parametr `searchInContent` pozostaje obsługiwany: `true` odpowiada `full_text`, a `false` — `metadata`. Nowe wywołania powinny używać `searchMode`.
Indeksy są automatycznie odświeżane przez GitHub Actions co tydzień. Każda odpowiedź podaje `index_generated_at`. Jeśli podczas pierwszej publikacji brakuje jeszcze indeksu pełnej treści, `auto` jawnie zwraca tymczasowe metadane; nie uruchamia w tle wielominutowego formularza.
`searchMode=live` uruchamia pełnotekstowy formularz JSF oficjalnego IPO. Parametr `where` pozwala ograniczyć wyszukiwanie do:
- `wszedzie`
- `komparycja`
- `sentencja`
- `uzasadnienie`
- `historia`
- `przed_rozprawa`
- `na_rozprawie`
- `ocena_prawna`
- `zdanie_odrebne`
Formularz TK bywa przeciążony i może odpowiadać znacznie wolniej niż pobieranie dokumentów. Błąd `[upstream_error]` oznacza jawną awarię lub timeout źródła, nie „zero wyników”.
Przykład kwerendy nastawionej na kompletność:
```text
search(query="BGK", searchMode="auto", inflection=false, pageSize=50)
```
## Długie orzeczenia: sekcje i offset
`get_judgment` wykrywa następujące sekcje:
| Sekcja | Zawartość |
|---|---|
| `calosc` | cały dokument |
| `sklad` | skład orzekający |
| `sentencja` | rozstrzygnięcie (`orzeka` / `postanawia`) |
| `uzasadnienie` | całe uzasadnienie |
| `stanowiska_uczestnikow` | część poprzedzająca własną ocenę TK |
| `ocena_trybunalu` | fragment od „Trybunał Konstytucyjny zważył…” |
| `zdania_odrebne` | zdania odrębne |
`maxChars` przyjmuje 500–50 000 znaków (domyślnie 15 000). `offset` jest liczony od początku wybranej sekcji. Odpowiedź zawiera bezwzględny zakres znaków, mapę sekcji, `has_more` i gotowy `next_offset`.
Przykładowy tok pracy:
1. `search({ query: "prawo do sądu" })`
2. `get_judgment({ id: "25567", section: "ocena_trybunalu" })`
3. jeżeli `has_more=true`: ponowne wywołanie z tym samym `section` i podanym `next_offset`
## Pozycje OTK ZU
Można podać bezpośrednio identyfikator publikacji:
```text
get_judgment(id="2026/A/96")
```
Serwer pobiera oficjalną stronę OTK ZU, odczytuje z niej powiązane identyfikatory IPO, a następnie pobiera tekst na żywo. W odpowiedzi zachowuje oddzielnie:
- `url` — strona dokumentu IPO,
- `otkzu_url` — pozycja Zbioru Urzędowego,
- `otkzu_pdf_url` — urzędowy PDF, gdy jest dostępny.
Obsługiwany jest nowy format `RRRR/A/POZ` oraz starszy `RRRR/NR-A/POZ`.
## Cytowania
`structuredContent.citations[]` zawiera:
```json
{
"title": "Wyrok K 7/18 z 2026-06-24",
"url": "https://...",
"signature": "K 7/18",
"date": "2026-06-24",
"author": "Trybunał Konstytucyjny",
"snippet": "...",
"doc_id": "25567",
"case_id": "20793"
}
```
Dla pobranego orzeczenia preferowanym URL cytowania jest bezpośrednia pozycja OTK ZU, jeżeli metadane pozwalają ją jednoznacznie wyznaczyć; w pozostałych przypadkach używany jest oficjalny dokument IPO.
## Bezpiecznik na dryf portali
Oba serwisy są publiczne, ale ich kontrakt techniczny nie jest dokumentowany. Konektor sprawdza między innymi:
- obecność formularza `wyszukiwanie`, JSF `ViewState` i znanych pól,
- strukturę listy oraz pagera,
- identyfikatory `dokument` i `sprawa`,
- kontener pełnego tekstu `tekst_{id}`,
- odsyłacz OTK ZU → IPO.
Zniknięcie krytycznego elementu daje `[api_changed]` z linkiem do issues. Awaria sieci lub HTTP daje `[upstream_error]`. Brak dokumentu lub sekcji daje `[not_found]`. Konektor nie zamienia błędu portalu w pozornie poprawny pusty wynik.
## Transport i bezpieczeństwo
- Node.js 18+, TypeScript, `@modelcontextprotocol/sdk`, stdio.
- Natywny `node:http2`: host IPO przy połączeniu HTTP/1.1 potrafi przyjąć połączenie i nie wysłać odpowiedzi.
- Bez wyłączania weryfikacji TLS.
- Stała allowlista dwóch hostów TK i ścieżek portali; argument użytkownika nie staje się dowolnym URL.
- Limit długości zapytań i rozmiaru fragmentów.
- Około 2–3 żądania na sekundę, retry tylko dla błędów przejściowych.
- Brak telemetrii i danych uwierzytelniających.
## Zakres danych
Szybki indeks metadanych odzwierciedla listę dostępną w IPO (elektroniczny korpus portalu, w praktyce od końca lat 90.). Sam OTK ZU ma szerszy katalog historyczny; znaną starszą pozycję można pobrać przez jej identyfikator. Brak wyniku indeksowego nie dowodzi, że bardzo stare orzeczenie nie istnieje.
## Build, testy i indeks
```sh
npm install
npm run build
npm run test:parse # testy offline parserów, sekcji, offsetów i drift guardów
npm run smoke # test LIVE: lista + pełny tekst z oficjalnego IPO
npm run index # przebudowa data/ipo-index.json z oficjalnej listy
npm run index:fulltext # przebudowa skompresowanego indeksu pełnej treści
npm run verify:index # kontrola regresji: co najmniej 7 dokumentów dla BGK
node dist/index.js # serwer MCP na stdio
```
Testy offline używają małych fixture'ów odtwarzających rzeczywistą strukturę HTML portali. Live smoke jest uruchamiany ręcznie, aby zwykłe CI nie zależało od chwilowej dostępności TK.
## Konfiguracja ręczna
```json
{
"mcpServers": {
"tk": {
"command": "node",
"args": ["/ścieżka/do/mcp-tk/dist/index.js"]
}
}
}
```
## Uwaga prawna
Konektor udostępnia źródła, nie udziela porady prawnej. Przy powoływaniu orzeczenia sprawdź jego pełną treść, datę, sentencję, późniejsze orzecznictwo oraz skutki wynikające z art. 190 Konstytucji. Wynik wyszukiwania tematycznego jest kandydatem do analizy, a nie automatycznie „linią orzeczniczą”.
## Podziękowania
Układ MCP, `structuredContent.citations`, porcjowanie i jawna obsługa dryfu są rozwinięciem wzorca z [`HelpToSave/mcp-eureka`](https://github.com/HelpToSave/mcp-eureka), który z kolei wskazuje konektory `mcp-nsa` Wiesława Mazura. Rozpoznanie stabilnych elementów publicznego HTML IPO porównano także z otwartym projektem [`worldwidelaw/legal-sources`](https://github.com/worldwidelaw/legal-sources); implementacja w tym repozytorium jest niezależna.
## Licencja
MIT © 2026 Mateusz Bednarski. Zobacz [LICENSE](LICENSE).
TDQS
A4.1/5.0
Scored across 4 tools
Disambiguation5/5
Each tool has a clear, distinct purpose: general search, signature-specific search, recent listings, and full-text retrieval. No two tools are likely to be confused for one another.
Naming Consistency5/5
All tool names follow a consistent snake_case verb_noun pattern (search, search_by_signature, list_recent, get_judgment), making the API predictable and intuitive.
Tool Count5/5
With 4 tools, the server is well-scoped for a focused domain of retrieving Polish Constitutional Tribunal judgments. Every tool earns its place without redundancy.
Completeness5/5
The tool surface covers the full lifecycle for a read-only lookup service: general search, specific signature lookup, listing recent entries, and fetching full texts. No obvious gaps remain.
Maintenance
ActivityMaintained
ResponsivenessNo issues