Skip to main content
Glama
RawsTourix

KudaGo + Nominatim MCP Server

by RawsTourix
README.md
<p align="center">
  <strong>Русский</strong> · <a href="./README.en.md">English</a>
</p>

# KudaGo Nominatim: FastAPI + FastMCP сервис

Асинхронный сервис для поиска событий, мест, фильмов, киносеансов, городских
новостей и подборок KudaGo, разрешения географических названий через Nominatim
и построения маршрутов через Transitous и OpenRouteService.

Одна прикладная логика опубликована через два интерфейса:

- REST API под `/api/v1`;
- фасад FastMCP v2 для агентов через Streamable HTTP `/mcp` и stdio.

> [!IMPORTANT]
> Для REST-команд через очередь и всех MCP-инструментов нужен запущенный arq
> worker.
> API или MCP transport без worker смогут принять запрос, но не выполнят
> application-команду.

> [!WARNING]
> Маршрутизация поддерживается не во всех регионах. Каждый завершённый ответ
> маршрутного MCP-инструмента содержит предупреждение
> `regional_coverage_varies`. `no_route`
> относится только к указанным точкам, времени и ограничениям и не доказывает,
> что физического маршрута или транспорта вообще не существует.

## Содержание

- [Возможности](#возможности)
- [Архитектура](#архитектура)
- [MCP-инструменты](#mcp-инструменты)
- [Маршрутизация и покрытие](#маршрутизация-и-покрытие)
- [Требования](#требования)
- [Быстрый старт](#быстрый-старт)
- [Подключение MCP-клиента](#подключение-mcp-клиента)
- [Конфигурация](#конфигурация)
- [REST API и жизненный цикл задач](#rest-api-и-жизненный-цикл-задач)
- [Тестирование](#тестирование)
- [Структура проекта](#структура-проекта)
- [Документация](#документация)
- [Ограничения и подготовка к промышленному развертыванию](#ограничения-и-подготовка-к-промышленному-развертыванию)

## Возможности

| Область | Возможности | Источник |
|---|---|---|
| События и места | Поиск по городу, свободному названию или координатам; фильтры по датам и категориям | KudaGo + Nominatim |
| Кино | Фильмы и реальные киносеансы | KudaGo |
| Городской контент | Новости и редакционные подборки | KudaGo |
| Геокодирование | Кандидаты координат для города, района, адреса или объекта | Nominatim |
| Общественный транспорт | Маршруты, пересадки, остановки и расписание при наличии данных | Transitous / MOTIS 2 |
| Пешком, велосипед, автомобиль | Независимые маршруты по дорожной сети | OpenRouteService |
| Диагностика | Jobs, события выполнения, полные результаты и журнал upstream-вызовов | PostgreSQL |

Ключевые свойства:

- FastAPI с OpenAPI, Swagger UI и ReDoc;
- фасад FastMCP 3.x версии 2 с описанными JSON Schema;
- MCP-транспорты Streamable HTTP и stdio;
- единый `CommandExecutor` для REST и MCP;
- PostgreSQL, асинхронный SQLAlchemy, Redis и arq;
- geo cache для повторного использования результатов Nominatim;
- сохранение `job_events`, `command_results` и `upstream_calls`;
- компактные agent-facing ответы с явными семантическими флагами;
- отдельные модульные, интеграционные, сценарные и запускаемые вручную проверки
  реальных провайдеров.

## Архитектура

REST-команды через очередь и MCP-инструменты используют один жизненный цикл:

```text
REST-клиент ─┐
             ├→ JobDispatchService → фиксация PostgreSQL → Redis → arq worker
MCP-клиент ──┘                                                  │
                                                                ▼
                                                   CommandExecutor
                                                                │
                                                                ▼
                                              сервис → внешний провайдер
                                                                │
                                                                ▼
                                 результат + события + upstream-диагностика

REST → сразу возвращает ответ с job_id поставленной в очередь задачи
MCP  → ждёт worker → читает сохранённый CommandOutput → сериализует ответ
```

Задача фиксируется в PostgreSQL до постановки в Redis. Обработчик arq получает
только `job_id`, загружает авторитетные команду и входные данные из базы,
выполняет handler и сохраняет итог. Ошибка постановки в очередь не оставляет
задачу навечно в `queued`: она переводится в `failed`.

Справочники и детальные REST GET-запросы выполняются напрямую и не создают
задач. MCP `get_details`, напротив, проходит через общий цикл с очередью.

Подробнее: [docs/architecture.md](docs/architecture.md).

## MCP-инструменты

Полностью настроенный сервер публикует 10 инструментов только для чтения.
Восемь доступны всегда, два маршрутных инструмента — только при наличии
конфигурации провайдера.

| MCP-инструмент | Назначение | Команда приложения | Публикация |
|---|---|---|---|
| `resolve_location` | Разрешить свободное название в кандидаты координат | `geo.resolve` | Всегда |
| `find_events` | Найти события в календарном окне | `events.search` | Всегда |
| `find_places` | Найти места и достопримечательности | `places.search` | Всегда |
| `find_movies` | Найти фильмы | `movies.search` | Всегда |
| `find_movie_showings` | Найти реальные киносеансы | `movie_showings.search` | Всегда |
| `find_city_news` | Найти городские новости | `news.search` | Всегда |
| `find_city_guides` | Найти редакционные подборки | `lists.search` | Всегда |
| `get_details` | Получить полную карточку найденного объекта | `object.detail` | Всегда |
| `plan_public_transport` | Построить маршрут общественного транспорта | `routing.transit.plan` | При непустом `TRANSITOUS_USER_AGENT` |
| `plan_street_route` | Построить пеший, велосипедный или автомобильный маршрут | `routing.street.plan` | При непустом `OPENROUTESERVICE_API_KEY` |

Старые имена MCP v1 (`events`, `places`, `object`, `transit_route`,
`street_route` и другие) не являются псевдонимами.

Все инструменты объявлены как доступные только для чтения, неразрушающие и
идемпотентные. Публичные схемы содержат описания, перечисления (`enum`),
числовые ограничения и межполевую валидацию. Ошибки аргументов возвращаются до
создания job.

В результатах для агента:

- `schedule_verified=true` означает подтверждённое расписание событий или
  киносеансов;
- `showing_times_verified=false` у фильма напоминает, что для времени сеанса
  нужен `find_movie_showings`;
- `route_verified=true` выставляется только при `result_status=ok` и наличии
  полного маршрута в MCP-ответе;
- данные поиска и подборок ограничены 64 KiB;
- детальные данные и маршруты ограничены 128 KiB;
- при усечении удаляются целые items или варианты маршрута, а полный результат
  остаётся в истории job.

Полный контракт: [docs/mcp.md](docs/mcp.md).

## Маршрутизация и покрытие

Инструменты маршрутизации принимают координаты и не геокодируют текст
самостоятельно:

```text
resolve_location → выбрать один candidate → передать его latitude и longitude
```

`plan_public_transport` и `plan_street_route` независимы:

- Transitous отвечает только за общественный транспорт;
- OpenRouteService отвечает только за walking/cycling/driving;
- инструменты не вызывают друг друга;
- автоматического резервного переключения между провайдерами нет.

Условная публикация относится только к MCP-каталогу. REST-маршруты и команды
приложения остаются зарегистрированными, но без настройки провайдера их
выполнение завершится ошибкой конфигурации.

`plan_public_transport` требует ровно одно значение с часовым поясом:
`departure_time` или `arrival_time`. Пеший доступ до и после участка
общественного транспорта ограничен 900 секундами с каждой стороны.

Каждый завершённый результат маршрутизации в MCP содержит:

```json
{
  "type": "coverage_notice",
  "code": "regional_coverage_varies",
  "message": "Routing is not supported in every region. Availability depends on the provider and its underlying routing data."
}
```

На live-тестах июля 2026 года Transitous не показал наблюдаемого покрытия
Москвы и Московской области: для проверенных точек отсутствовали stops,
stoptimes и itineraries, тогда как контрольный Берлин работал. Geoapify и
Google Routes также не удовлетворили требованиям российского
маршрутизации на общественном транспорте по России. Это снимок состояния
провайдеров на дату теста, а не вечная гарантия.

Подробности:

- [routing-контракты](docs/routing.md);
- [отчёт о live-тестах Transitous, Geoapify и Google Routes](docs/transit-provider-live-tests.md).

## Требования

- Python 3.11 или новее;
- PostgreSQL 16;
- Redis 7;
- Docker Engine / Docker Desktop с Compose — для готовой локальной
  инфраструктуры;
- PowerShell — для готового `scripts/smoke_test.ps1`;
- API key OpenRouteService — только если нужен `plan_street_route`;
- корректный Transitous User-Agent с именем приложения, версией и контактом —
  если нужен `plan_public_transport`.

## Быстрый старт

Команды ниже выполняются из корня репозитория.

### 1. Подготовить окружение

```powershell
Copy-Item .env.example .env
```

Перед запуском измените в `.env` как минимум:

- `POSTGRES_PASSWORD`;
- `NOMINATIM_USER_AGENT`, чтобы он идентифицировал ваше приложение;
- контакт в `TRANSITOUS_USER_AGENT`;
- `OPENROUTESERVICE_API_KEY`, если нужна маршрутизация OpenRouteService.

Не коммитьте `.env` с реальными паролями и API keys.

### 2. Запустить весь стек

```powershell
docker compose up --build -d
docker compose ps --all
```

Одна команда собирает приложение, применяет Alembic-миграции и запускает:

- PostgreSQL;
- Redis с AOF persistence;
- FastAPI + FastMCP (`app.main:app`);
- обработчик arq;
- Nginx gateway на единственном публичном HTTP-порту.

PostgreSQL и Redis по умолчанию доступны только сервисам во внутренней
Compose-сети, поэтому не конфликтуют с локальными экземплярами.

Стандартный запуск автоматически использует `docker-compose.override.yml`:
исходники подключены как bind mount, Uvicorn перезапускается при изменениях
в `app/`, а arq следит за тем же каталогом. Устанавливать Python локально для
запуска стека не нужно.

После запуска доступны:

| Назначение | URL |
|---|---|
| REST API | `http://127.0.0.1:8011/api/v1` |
| Swagger UI | `http://127.0.0.1:8011/docs` |
| ReDoc | `http://127.0.0.1:8011/redoc` |
| FastMCP Streamable HTTP | `http://127.0.0.1:8011/mcp` |

### 3. Проверить сервис

```powershell
Invoke-RestMethod http://127.0.0.1:8011/api/v1/health
Invoke-RestMethod http://127.0.0.1:8011/api/v1/health/db
Invoke-RestMethod http://127.0.0.1:8011/api/v1/health/ready
```

Production-like запуск без bind mounts и autoreload:

```powershell
docker compose -f docker-compose.yml up --build -d
```

Масштабирование API и worker не меняет внешний URL:

```powershell
docker compose up -d --scale app=3 --scale worker=4
```

Подробности об обновлениях кода, зависимостей, переменных окружения и миграций
см. в [руководстве по Docker](docs/docker.md).

## Подключение MCP-клиента

### Streamable HTTP

При запущенных API и worker укажите клиенту:

```text
http://127.0.0.1:8011/mcp
```

### Stdio

Отдельный stdio-транспорт запускается так:

```powershell
python -m app.mcp
```

Эквивалентный совместимый entrypoint:

```powershell
python mcp_server.py
```

Обобщённый пример конфигурации MCP-клиента:

```json
{
  "mcpServers": {
    "kudago-nominatim": {
      "command": "python",
      "args": ["-m", "app.mcp"],
      "cwd": "C:\\absolute\\path\\to\\kudago-nominatim-integrate-mcp"
    }
  }
}
```

Формат конфигурации зависит от конкретного клиента. Stdio-сервер сам
подключается к Redis, но application-команды по-прежнему выполняет отдельный
arq worker.

## Конфигурация

Настройки загружаются из переменных окружения и локального `.env`.

### Приложение и инфраструктура

| Переменная | Назначение | Значение в `.env.example` |
|---|---|---|
| `APP_NAME` | Имя FastAPI-приложения | `KudaGo Nominatim FastAPI Service` |
| `DEBUG` | Debug-режим | `0` |
| `DATABASE_ECHO` | Журналирование SQL-запросов; для stdio принудительно отключается | `0` |
| `APP_BIND_ADDRESS` | Host-интерфейс HTTP gateway | `127.0.0.1` |
| `APP_PORT` | Порт HTTP gateway на host | `8011` |
| `UVICORN_WORKERS` | Uvicorn-процессы в одном production-like контейнере | `1` |
| `DATABASE_URL` | Asyncpg URL PostgreSQL | PostgreSQL на `127.0.0.1:5433` |
| `REDIS_URL` | Redis для arq и MCP | `redis://127.0.0.1:6379/0` |
| `COMMAND_JOB_TIMEOUT_SECONDS` | Внутренний бюджет application-команды | `120` |
| `ARQ_JOB_TIMEOUT_SECONDS` | Жёсткий лимит arq; минимум на 5 секунд больше лимита команды | `135` |
| `ARQ_MAX_JOBS` | Максимум одновременно выполняемых задач на один worker-контейнер | `10` |
| `MCP_JOB_WAIT_TIMEOUT_SECONDS` | Максимальное ожидание worker внутри MCP-вызова | `180` |
| `POSTGRES_USER` | Пользователь PostgreSQL в Compose | `kudago` |
| `POSTGRES_PASSWORD` | Пароль PostgreSQL в Compose | `change-me` |
| `POSTGRES_DB` | База PostgreSQL в Compose | `kudago_service` |
| `POSTGRES_PORT` | Порт PostgreSQL при подключении opt-in host-конфигурации | `5433` |
| `REDIS_PORT` | Порт Redis при подключении opt-in host-конфигурации | `6379` |

### Внешние провайдеры

| Переменная | Назначение |
|---|---|
| `KUDAGO_BASE_URL` | Базовый URL KudaGo API v1.4 |
| `KUDAGO_LANG` | Язык KudaGo-запросов |
| `KUDAGO_USER_AGENT` | User-Agent клиента KudaGo |
| `NOMINATIM_USER_AGENT` | Обязательный идентифицирующий User-Agent Nominatim |
| `NOMINATIM_MIN_INTERVAL_SECONDS` | Минимальный интервал между Nominatim-запросами |
| `NOMINATIM_COUNTRYCODES` | Ограничение геокодирования по странам; по умолчанию `ru` |
| `DEFAULT_RADIUS` | Радиус geo search по умолчанию, метры |
| `TRANSITOUS_BASE_URL` | Базовый URL Transitous / MOTIS 2 |
| `TRANSITOUS_USER_AGENT` | Имя приложения, версия и контакт; управляет публикацией transit MCP tool |
| `TRANSITOUS_TIMEOUT_SECONDS` | Тайм-аут Transitous |
| `OPENROUTESERVICE_BASE_URL` | Базовый URL OpenRouteService |
| `OPENROUTESERVICE_API_KEY` | API key; управляет публикацией street-route MCP tool |
| `OPENROUTESERVICE_USER_AGENT` | User-Agent OpenRouteService |
| `OPENROUTESERVICE_TIMEOUT_SECONDS` | Тайм-аут OpenRouteService |

Точные defaults находятся в [.env.example](.env.example) и
[app/core/config.py](app/core/config.py).

## REST API и жизненный цикл задач

### Команды через очередь

Все основные POST-команды создают job и возвращают `job_id` и
`queue_job_id`.

| Метод | Endpoint | Назначение |
|---|---|---|
| `POST` | `/api/v1/geo/resolve` | Геокодирование |
| `POST` | `/api/v1/events/search` | События |
| `POST` | `/api/v1/places/search` | Места |
| `POST` | `/api/v1/movies/search` | Фильмы |
| `POST` | `/api/v1/movie-showings/search` | Киносеансы |
| `POST` | `/api/v1/news/search` | Новости |
| `POST` | `/api/v1/lists/search` | Подборки |
| `POST` | `/api/v1/routing/transit` | Общественный транспорт |
| `POST` | `/api/v1/routing/street` | Пешком, велосипед или автомобиль |

Маршрутные endpoints принимают только координаты. Адрес или название сначала
разрешите через `/geo/resolve` или MCP `resolve_location`.

### Прямые GET-запросы

| Метод | Endpoint | Назначение |
|---|---|---|
| `GET` | `/api/v1/health` | Состояние API |
| `GET` | `/api/v1/health/db` | Проверка PostgreSQL |
| `GET` | `/api/v1/references/event-categories` | Категории событий |
| `GET` | `/api/v1/references/place-categories` | Категории мест |
| `GET` | `/api/v1/references/locations` | Города KudaGo |
| `GET` | `/api/v1/references/locations/{slug}` | Карточка города |
| `GET` | `/api/v1/objects/{type}/{id}` | Детальная карточка объекта |

### Задачи и диагностика

Состояния задачи: `queued`, `running`, `succeeded`, `failed`.

```text
GET /api/v1/jobs/{job_id}
GET /api/v1/jobs/{job_id}?include_result=true
GET /api/v1/jobs/{job_id}/events
GET /api/v1/jobs/{job_id}/results
GET /api/v1/jobs/{job_id}/upstream-calls
```

Обычный `GET /jobs/{job_id}` скрывает большие `items` и `routes`. Полные данные
доступны через `/results` или `include_result=true`.

Успешно выполненная задача может содержать доменный результат `geo_ambiguous`,
`geo_not_found`, `geo_unsupported` или `no_route`. Это не ошибка транспорта.

Точные схемы запросов и ответов: [docs/api.md](docs/api.md) и Swagger UI
`/docs`.

## Тестирование

Установить dev dependencies:

```powershell
python -m pip install -e ".[dev]"
```

### Модульные и интеграционные тесты

```powershell
python -m pytest -q
```

Тесты проверяют обработчики приложения, клиенты провайдеров, жизненный цикл
очереди, MCP-каталог и схемы, межполевую валидацию, сериализаторы, ограничения
размера ответа, условную публикацию маршрутных инструментов и зафиксированный
снимок справочников.

### Проверка REST-сценариев

После запуска PostgreSQL, Redis, API и worker:

```powershell
powershell -ExecutionPolicy Bypass -File scripts/smoke_test.ps1
```

Другой API URL:

```powershell
powershell -ExecutionPolicy Bypass -File scripts/smoke_test.ps1 `
  -BaseUrl "http://127.0.0.1:8011/api/v1"
```

### Проверки MCP

При запущенных PostgreSQL, Redis и worker:

```powershell
python scripts/test_mcp_inmemory.py
python scripts/test_mcp_stdio.py
python scripts/test_mcp_http.py
python scripts/dump_mcp_schemas.py
```

HTTP-проверка дополнительно требует запущенный Uvicorn.

### Проверка маршрутизации с реальными провайдерами

```powershell
python scripts/test_routing_live.py
```

Этот тест обращается к реальным провайдерам, требует корректный
`TRANSITOUS_USER_AGENT` и использует `OPENROUTESERVICE_API_KEY`, если он задан.
Он не входит в обычный pytest.

Отдельные provider-диагностики и необходимые переменные окружения описаны в
[отчёте о live-тестах](docs/transit-provider-live-tests.md).

## Разработка

После добавления новой ревизии применить миграции в уже запущенном Compose:

```powershell
docker compose run --rm migrate
```

Изменения Python-кода подхватываются dev-контейнерами автоматически. После
изменения `pyproject.toml` пересоберите Python-сервисы:

```powershell
docker compose up --build -d app worker
```

Для локальной разработки вне Docker установите пакет с dev-зависимостями.
Создать ревизию миграции можно локально:

```powershell
python -m pip install -e ".[dev]"
python -m alembic revision --autogenerate -m "описание изменения"
```

Обновить зафиксированный в git снимок справочников MCP:

```powershell
python scripts/update_mcp_reference_data.py
```

Проверить и сохранить реальные MCP schemas:

```powershell
python scripts/dump_mcp_schemas.py
```

## Структура проекта

```text
app/
  api/             FastAPI routers и зависимости
  application/     общие контракты команд, executor и обработчики
  core/            settings, PostgreSQL и Redis
  integrations/    HTTP-клиенты внешних провайдеров
  mcp/             схемы, преобразователи, сериализаторы, server и tools
  models/          модели SQLAlchemy
  repositories/    операции с PostgreSQL
  schemas/         контракты Pydantic для REST и прикладного слоя
  services/        бизнес-правила и оркестрация провайдеров
  workers/         задачи arq и WorkerSettings
alembic/            миграции PostgreSQL
docs/               подробная документация
scripts/            сценарные проверки, выгрузка схем и live-диагностика
tests/              модульные и интеграционные тесты
docker/              конфигурация HTTP gateway
Dockerfile           образ FastAPI/FastMCP, worker и миграций
docker-compose.yml  production-like описание полного стека
docker-compose.override.yml  autoreload для локальной разработки
docker-compose.host.yml  opt-in публикация PostgreSQL и Redis на host
```

## Документация

| Документ | Содержание |
|---|---|
| [docs/architecture.md](docs/architecture.md) | Компоненты, цикл очереди, хранение данных и модель ошибок |
| [docs/api.md](docs/api.md) | REST endpoints и примеры данных |
| [docs/mcp.md](docs/mcp.md) | MCP-фасад v2, каталог, схемы и envelopes |
| [docs/docker.md](docs/docker.md) | Compose-стек, обновления и масштабирование |
| [docs/routing.md](docs/routing.md) | Контракты маршрутов общественного транспорта и дорожной сети |
| [docs/testing.md](docs/testing.md) | Модульные, интеграционные, MCP- и live-проверки |
| [docs/mcp-schema-design.md](docs/mcp-schema-design.md) | Принципы схем для агентов |
| [docs/mcp-api-sources.md](docs/mcp-api-sources.md) | Источники перечислений и справочных данных |
| [docs/transit-provider-live-tests.md](docs/transit-provider-live-tests.md) | Сравнение покрытия Transitous, Geoapify и Google Routes |

## Ограничения и подготовка к промышленному развертыванию

- Входящие REST, MCP и диагностические endpoints не имеют аутентификации на
  уровне приложения. Не публикуйте сервис в интернет без внешнего слоя
  аутентификации.
- `/api/v1/jobs/{job_id}/upstream-calls` возвращает сохранённые request/response
  payloads провайдеров. Ограничьте к нему доступ или отключите его в публичном
  окружении.
- Полнота и доступность данных зависят от KudaGo, Nominatim, Transitous и
  OpenRouteService.
- Transitous и OpenRouteService не гарантируют покрытие каждого региона.
- `no_route` не является доказательством отсутствия физического маршрута.
- MCP timeout не отменяет queued job: она может завершиться позднее и остаться
  доступной через REST job endpoints.
- Кэшированный geo result закономерно не создаёт новый upstream-call.
- Docker Compose не контейнеризирует API и обработчик arq; он запускает только
  PostgreSQL и Redis.
- Храните `.env`, реквизиты базы данных и API keys провайдеров вне git.