Skip to main content
Glama
ciembor

teryt-mcp

by ciembor
README.md
# TERYT MCP

Serwer MCP dla oficjalnego rejestru TERYT. Udostępnia dane GUS o jednostkach
terytorialnych, miejscowościach i ulicach jako narzędzia dla asystentów oraz
automatyzacji.

Obsługiwane zbiory:

- `TERC` - województwa, powiaty, gminy i typy jednostek;
- `SIMC` - miejscowości;
- `ULIC` - ulice;
- `WMRODZ` - słownik rodzajów miejscowości.

Pakiet npm: `teryt-mcp`.

## Typowe Pytania

To są najważniejsze zastosowania serwera.

### Znalezienie jednostki TERYT

Prompt:

```text
Znajdź identyfikator TERYT dla województwa dolnośląskiego.
```

Narzędzie: `search_units`

Input:

```json
{
  "query": "dolnoslaskie",
  "limit": 5
}
```

Odpowiedź ma postać:

```json
{
  "units": [
    {
      "confidence": 0.95,
      "matchedBy": "exact_normalized_name",
      "unit": {
        "id": "02",
        "name": "DOLNOŚLĄSKIE",
        "stateDate": "2026-01-01",
        "type": "województwo"
      }
    }
  ],
  "stateDate": "2026-01-01"
}
```

### Znalezienie miejscowości

Prompt:

```text
Znajdź miejscowości SIMC o nazwie Kraków.
```

Narzędzie: `search_places`

Input:

```json
{
  "query": "Kraków",
  "limit": 5
}
```

Odpowiedź ma postać:

```json
{
  "places": [
    {
      "confidence": 0.95,
      "matchedBy": "exact_normalized_name",
      "place": {
        "id": "0000000",
        "name": "Kraków",
        "stateDate": "2026-01-01",
        "unitId": "12-61-00-0"
      }
    }
  ],
  "stateDate": "2026-01-01"
}
```

### Znalezienie ulicy

Prompt:

```text
Znajdź ulice ULIC o nazwie Marszałkowska.
```

Narzędzie: `search_streets`

Input:

```json
{
  "query": "Marszałkowska",
  "limit": 5
}
```

Odpowiedź ma postać:

```json
{
  "streets": [
    {
      "confidence": 0.95,
      "matchedBy": "exact_normalized_name",
      "street": {
        "id": "0009876-00123",
        "code": "00123",
        "name": "Marszałkowska",
        "placeId": "0009876",
        "stateDate": "2026-01-01"
      }
    }
  ],
  "stateDate": "2026-01-01"
}
```

### Rozpoznanie miejscowości i ulicy z tekstu

Prompt:

```text
Rozpoznaj "Wieliszew Marszalkowska" i zwróć identyfikatory TERC, SIMC i ULIC.
```

Narzędzie: `resolve_address`

Input:

```json
{
  "query": "Wieliszew Marszalkowska",
  "limit": 5
}
```

Odpowiedź ma postać:

```json
{
  "addresses": [
    {
      "confidence": 0.95,
      "matchedBy": "exact_normalized_address",
      "address": {
        "id": "0008639-12400",
        "unit": {
          "id": "14-08-05-2",
          "name": "Wieliszew",
          "type": "gmina wiejska"
        },
        "place": {
          "id": "0008639",
          "name": "Wieliszew"
        },
        "street": {
          "id": "0008639-12400",
          "code": "12400",
          "name": "Marszałkowska"
        },
        "stateDate": "2026-06-19"
      }
    }
  ],
  "stateDate": "2026-06-19"
}
```

To nie jest geokodowanie. Narzędzie rozpoznaje identyfikatory rejestrowe do
poziomu miejscowości i ulicy. Dla zapytań zawierających wyłącznie miejscowość
użyj `search_places`. Kody pocztowe i numery budynków są odrzucane z czytelnym
błędem, ponieważ nie należą do TERYT.

### Sprawdzenie konkretnego identyfikatora

Prompt:

```text
Sprawdź jednostkę TERYT 02-01-01-1.
```

Narzędzie: `get_unit`

Input:

```json
{
  "id": "02-01-01-1"
}
```

Odpowiedź ma postać:

```json
{
  "unit": {
    "id": "02-01-01-1",
    "name": "Bolesławiec",
    "stateDate": "2026-01-01",
    "type": "gmina miejska"
  },
  "stateDate": "2026-01-01"
}
```

Dla brakującego identyfikatora odpowiedź ma postać:

```json
{
  "unit": null,
  "stateDate": null
}
```

Analogicznie:

- `get_place` sprawdza identyfikator SIMC miejscowości;
- `get_street` sprawdza identyfikator ULIC ulicy.

## Co Jest W TERYT, A Czego Nie Ma

Ten serwer celowo trzyma się TERYT. Dlatego obsługuje identyfikatory i nazwy
administracyjne, ale nie udaje geokodera ani rejestru adresów punktowych.

W zakresie TERYT:

- województwa, powiaty, gminy i ich typy;
- miejscowości i ich identyfikatory SIMC;
- ulice i ich identyfikatory ULIC;
- powiązania miejscowość-jednostka oraz ulica-miejscowość;
- daty stanu danych z lokalnej migawki.

Poza zakresem TERYT:

- współrzędne;
- działki;
- budynki;
- kody pocztowe;
- walidacja punktu adresowego, np. czy istnieje numer budynku przy ulicy.

Do tych danych potrzebne są inne źródła, np. PRG/EMUiA/GUGiK dla punktów
adresowych i współrzędnych, EGiB dla katastru albo osobny słownik PNA dla kodów
pocztowych. Jeśli te funkcje będą potrzebne, powinny powstać jako osobny serwer
lub osobny bounded context, nie jako ukryty dodatek do TERYT.

## Narzędzia MCP

Wszystkie narzędzia zwracają `structuredContent`.

### `about`

Zwraca informacje o pakiecie, autorze, kontakcie, repozytorium, wersji serwera
oraz stanie lokalnej synchronizacji danych.

Input:

```json
{}
```

Odpowiedź ma postać:

```json
{
  "author": {
    "name": "Maciej Ciemborowicz"
  },
  "contact": {
    "email": "maciej.ciemborowicz@gmail.com"
  },
  "repository": {
    "url": "https://github.com/ciembor/teryt-mcp"
  },
  "server": {
    "name": "teryt-mcp",
    "version": "0.1.13"
  },
  "data": {
    "status": "available",
    "synchronizedSuccessfully": true,
    "lastSynchronizedAt": "2026-06-21T00:00:00.000Z",
    "datasets": [
      {
        "dataset": "TERC",
        "stateDate": "2026-01-01",
        "version": "2026-01-01"
      }
    ]
  }
}
```

TERYT nie ma tu klasycznej wersji semver danych. Pole `version` jest datą
stanu danych `STAN_NA` z lokalnego manifestu synchronizacji.

### `health_status`

Sprawdza, czy serwer odpowiada.

Input:

```json
{}
```

Odpowiedź:

```json
{
  "ok": true
}
```

### `server_status`

Zwraca status runtime serwera.

Input:

```json
{}
```

Odpowiedź ma postać:

```json
{
  "serverName": "teryt-mcp",
  "serverVersion": "0.1.13",
  "frameworkVersion": "0.2.1",
  "transport": "stdio",
  "dataDir": "/path/to/teryt-data",
  "database": {
    "status": "available"
  }
}
```

### `source_status`

Pokazuje status lokalnej bazy i metadane źródeł TERYT.

Input:

```json
{}
```

Odpowiedź ma postać:

```json
{
  "datasets": [
    {
      "dataset": {
        "code": "TERC",
        "name": "Territorial units",
        "sourceUrl": "https://eteryt.stat.gov.pl/eTeryt/"
      },
      "snapshot": null,
      "stateDate": null,
      "sha256": null
    }
  ],
  "lastCheckedAt": null,
  "lastSuccessfulSync": null,
  "localDatabase": {
    "status": "missing"
  },
  "remoteSource": {
    "status": "unknown",
    "errors": []
  }
}
```

### `sync_database`

Buduje albo przebudowuje lokalną bazę SQLite z oficjalnych danych TERYT.

Input:

```json
{
  "mode": "missing"
}
```

Tryby:

```text
missing  zbuduj bazę tylko wtedy, gdy jej brakuje
stale    przebuduj bazę, jeśli ma co najmniej 24 godziny
force    przebuduj bazę pod lockiem
```

Jeśli baza już istnieje, a tryb to `missing`, odpowiedź ma postać:

```json
{
  "status": "skipped",
  "mode": "missing",
  "databasePath": null,
  "datasets": []
}
```

Po przebudowie odpowiedź ma postać:

```json
{
  "status": "synced",
  "mode": "force",
  "databasePath": "/path/to/teryt.sqlite",
  "datasets": [
    {
      "dataset": "TERC",
      "recordCount": 123456,
      "stateDate": "2026-01-01",
      "source": "official-teryt-download",
      "sourceUrl": "https://eteryt.stat.gov.pl/eTeryt/",
      "variant": "full"
    }
  ]
}
```

### `search_units`

Szuka jednostek TERC.

Input:

```json
{
  "query": "Boleslawiec",
  "limit": 20
}
```

Zwraca:

```json
{
  "units": [],
  "stateDate": "2026-01-01"
}
```

### `search_places`

Szuka miejscowości SIMC.

Input:

```json
{
  "query": "Stara Wieś",
  "limit": 20
}
```

Zwraca:

```json
{
  "places": [],
  "stateDate": "2026-01-01"
}
```

### `search_streets`

Szuka ulic ULIC.

Input:

```json
{
  "query": "Marszałkowska",
  "limit": 20
}
```

Zwraca:

```json
{
  "streets": [],
  "stateDate": "2026-01-01"
}
```

### `resolve_address`

Szuka kandydata adresowego do poziomu miejscowości i ulicy.

Input:

```json
{
  "place": "Bolesławiec",
  "street": "Marszałkowska",
  "limit": 20
}
```

Można również przekazać `query`, np. `Marszalkowska Boleslawiec` albo
`ulica Marszalkowska w Boleslawiec`. Pola `place` i `street` są preferowane,
gdy klient potrafi rozdzielić miejscowość od ulicy.

Zwraca:

```json
{
  "addresses": [],
  "stateDate": "2026-01-01"
}
```

### `get_unit`

Pobiera jednostkę TERC po identyfikatorze.

Input:

```json
{
  "id": "02-01-01-1"
}
```

### `get_place`

Pobiera miejscowość SIMC po identyfikatorze.

Input:

```json
{
  "id": "0009876"
}
```

### `get_street`

Pobiera ulicę ULIC po identyfikatorze.

Input:

```json
{
  "id": "0009876-00123"
}
```

## Dopasowanie I Ranking

`matchedBy` mówi, dlaczego wynik został zwrócony:

```text
exact_code
exact_normalized_name
exact_normalized_address
prefix
contains
```

`confidence` jest wynikiem rankingu wyszukiwania, a nie oficjalną wartością z
rejestru. Dopasowanie po dokładnym kodzie ma najwyższy priorytet. Dopasowania
zawierające szukany fragment są niżej.

Wyszukiwanie normalizuje polskie znaki, więc zapytania takie jak `Boleslawiec`
mogą znaleźć `Bolesławiec`.

## Instalacja

Uruchomienie bez instalacji globalnej:

```bash
npx -y teryt-mcp serve
```

Instalacja globalna:

```bash
npm install -g teryt-mcp
teryt-mcp serve
```

Wymagania:

- Node.js `>=20.19.0`

Podczas instalacji pakiet próbuje utworzyć lokalną bazę SQLite z oficjalnych
danych TERYT. Jeśli pierwsza synchronizacja się nie powiedzie, instalacja nadal
kończy się sukcesem. Bazę można zsynchronizować później przez CLI albo narzędzie
`sync_database`.

Pominięcie synchronizacji podczas instalacji:

```bash
TERYT_MCP_SKIP_POSTINSTALL_SYNC=1 npm install -g teryt-mcp
```

## Podłączenie Do Klienta MCP

Konfiguracja dla transportu stdio:

```json
{
  "mcpServers": {
    "teryt": {
      "command": "npx",
      "args": ["-y", "teryt-mcp", "serve"]
    }
  }
}
```

Przy instalacji globalnej:

```json
{
  "mcpServers": {
    "teryt": {
      "command": "teryt-mcp",
      "args": ["serve"]
    }
  }
}
```

W środowisku, które czyści cache między uruchomieniami, ustaw stały katalog
danych:

```json
{
  "mcpServers": {
    "teryt": {
      "command": "npx",
      "args": ["-y", "teryt-mcp", "serve"],
      "env": {
        "MCP_DATA_DIR": "/absolute/path/to/teryt-data"
      }
    }
  }
}
```

### Codex

Codex CLI i rozszerzenie Codex dla IDE współdzielą konfigurację
`~/.codex/config.toml`. Plik VS Code `User/mcp.json` nie rejestruje serwera w
Codex.

Najprościej dodać globalnie zainstalowany serwer poleceniem:

```bash
codex mcp add teryt-mcp -- teryt-mcp serve
```

Odpowiednik w `~/.codex/config.toml`:

```toml
[mcp_servers.teryt-mcp]
command = "teryt-mcp"
args = ["serve"]
```

Po zmianie konfiguracji uruchom nowy proces lub nową sesję Codex i sprawdź
serwer przez `codex mcp list` albo `/mcp` w interfejsie terminalowym.

## Pierwsza Synchronizacja

Serwer może zwracać status bez lokalnej bazy, ale wyszukiwanie i lookupy
wymagają bazy SQLite.

Podstawowe komendy CLI:

```bash
teryt-mcp help
teryt-mcp about
teryt-mcp status
teryt-mcp source-status
```

Wymuszenie synchronizacji:

```bash
npx -y teryt-mcp sync --force
```

Albo, przy instalacji globalnej:

```bash
teryt-mcp sync --force
```

Synchronizacja zapisuje bazę atomowo. Proces czytający powinien zobaczyć albo
starą bazę, albo nową, nigdy częściowo zapisany plik.

## CLI

CLI jest pomocnicze. Pełny zestaw funkcji jest dostępny przez MCP.

```bash
teryt-mcp serve
teryt-mcp status
teryt-mcp source-status
teryt-mcp sync
teryt-mcp sync --force
teryt-mcp sync --mode missing
teryt-mcp search places Kraków --limit 5
```

Obecnie CLI obsługuje wyszukiwanie tylko dla `search places`. Narzędzia MCP
udostępniają również jednostki, ulice, rozpoznawanie adresu i lookupy po
identyfikatorach.

Przykład:

```bash
teryt-mcp search places Boleslawiec --limit 1
```

Odpowiedź ma postać:

```json
{
  "places": [
    {
      "confidence": 0.95,
      "matchedBy": "exact_normalized_name",
      "place": {
        "id": "0009876",
        "name": "Bolesławiec",
        "stateDate": "2026-01-01",
        "unitId": "02-01-01-1"
      }
    }
  ],
  "stateDate": "2026-01-01"
}
```

## HTTP

HTTP jest przydatne do lokalnego testowania albo własnych wrapperów:

```bash
MCP_TRANSPORT=http PORT=3000 teryt-mcp serve
```

Endpointy:

```text
GET  /health
POST /tools/:toolName
```

Przykład:

```bash
curl -s http://127.0.0.1:3000/tools/search_places \
  -H 'content-type: application/json' \
  -d '{"query":"Kraków","limit":3}'
```

Odpowiedź HTTP zawiera wynik narzędzia MCP:

```json
{
  "structuredContent": {
    "places": [
      {
        "confidence": 0.95,
        "matchedBy": "exact_normalized_name",
        "place": {
          "id": "0000000",
          "name": "Kraków",
          "stateDate": "2026-01-01",
          "unitId": "12-61-00-0"
        }
      }
    ],
    "stateDate": "2026-01-01"
  }
}
```

## Konfiguracja Runtime

```text
MCP_TRANSPORT=stdio|http  # domyślnie stdio
MCP_PORT / PORT           # domyślnie 3000 dla HTTP
MCP_DATA_DIR              # katalog bazy i manifestu synchronizacji
XDG_CACHE_HOME            # używane, gdy MCP_DATA_DIR nie jest ustawione
MCP_LOG_LEVEL             # debug|info|warn|error|silent
```

Pliki lokalne:

```text
<data-dir>/teryt.sqlite
<data-dir>/sync-manifest.json
<data-dir>/*.lock
```

Nowe wydania mogą zmieniać schemat SQLite. Serwer wykrywa niezgodną bazę i
przebudowuje ją podczas synchronizacji w trybie `missing`; można też jawnie
uruchomić `teryt-mcp sync --force`.

## Development

Dla osób rozwijających repozytorium:

```bash
pnpm install
pnpm build
pnpm quality
```

Szczegóły architektury i kontraktów:

- [docs/tools.md](docs/tools.md)
- [docs/data-sync.md](docs/data-sync.md)
- [docs/architecture/feature-clean-architecture.md](docs/architecture/feature-clean-architecture.md)
- [docs/architecture/runtime-ecosystem.md](docs/architecture/runtime-ecosystem.md)

## Licencja i źródło danych

Kod projektu: Copyright 2026 Maciej Ciemborowicz. Udostępniany wyłącznie na
warunkach [European Union Public Licence 1.2](LICENSE) (`EUPL-1.2 only`).

Dane TERYT pochodzą z Głównego Urzędu Statystycznego. Informacje o ich
pozyskiwaniu i przetwarzaniu znajdują się w pliku [NOTICE.md](NOTICE.md).