zaezd

zaezd

Enables users to plan conference trips by finding nearby offline events, computing travel and hotel costs, and presenting up to three explained trip packages with checkout links.

Category
访问服务器

README

Заезд

Движок поездок, который начинается с повода. Человек не знает, куда ехать, он знает, зачем: хочет на конференцию по своей теме. Заезд находит ближайшее офлайн-событие, считает дорогу туда и обратно, отель рядом с площадкой и полную цену участия, и показывает до трёх объяснимых пакетов на одном экране.

Первая вертикаль - ИТ-конференции. Каталог событий даёт confcal MCP, транспорт и жильё - Tutu MCP, а собирает всё детерминированный композитор, а не модель.

Что где

Что Где
Экран поездки / на том адресе, где вы запустили Заезд
Поездка по ссылке /t/<trip_id>, ссылку отдаёт экран и любой из инструментов
MCP-эндпоинт /mcp, streamable HTTP, без ключа
Проверка живости /healthz, отвечает режимом и опорной датой
Руководство пользователя docs/user-guide.md
Двухминутная запись docs/demo.mp4, сервер, экран и агент

Сквозной сценарий

Открываете корень. На экране уже собранная поездка, а не пустая форма. Сверху событие, дата, город, площадка и время пешком до неё от ближайшего отеля. Ниже до трёх карточек: дорога туда с номером поезда или рейса, отель с ценой за всё проживание и расстоянием до площадки, дорога обратно, итог одной цифрой и строка арифметики под ним, которая сходится. Дальше карта города с площадкой и отелями, погода на даты поездки, другие события по теме и список того, что не вошло и почему.

Нажимаете "Собрать ссылки на оплату" - появляется чек-лист из двух-трёх ссылок Туту. Подпись каждой берётся из того, что Туту реально вернул: "Открыть корзину" там, где откроется корзина, и "Открыть страницу выбора" там, где корзины не будет.

То же самое агенту: find_event_trips с темой и городом отправления возвращает ту же поездку структурой, get_trip_details раскрывает пакет, create_trip_checkout собирает чек-лист. Хост, который умеет рисовать, получает тот же экран как MCP App.

Запуск из исходников

npm install
ZAEZD_MODE=replay npm run dev

Откроется http://localhost:8080. В режиме replay продукт работает на записанных ответах из fixtures/ и не ходит в сеть: с ним можно жить без интернета и без ключей, а опорной датой служит день записи. Живой режим - ZAEZD_MODE=live.

Проверка целиком:

npm run verify

Это синхронность правил, сверка документированных команд, типизация, линтер, сборка браузерного дерева, исполняемые сценарии на Gherkin и юнит-тесты. Всё офлайн.

Браузерное дерево собирается до сценариев не для красоты. Один сценарий запрашивает у сервера тот самый boot.js, который уезжает в хост, и на чистом клоне этого файла ещё нет.

Запуск в контейнере

Образ собирается из репозитория и внутри содержит и код, и фикстуры, поэтому в режиме replay контейнеру не нужен ни интернет, ни секреты.

cp .env.example .env      # и отредактируйте
docker compose up -d --build

Экран поднимется на http://127.0.0.1:8080. Без файла .env compose не стартует: это намеренно, чтобы никто не запустил продукт на чужих значениях по умолчанию.

Без compose то же самое одной командой:

docker build -t zaezd .
docker run -d --name zaezd -p 127.0.0.1:8080:8080 --env-file .env zaezd

Разово, без файла окружения, чтобы просто посмотреть:

docker run --rm -p 127.0.0.1:8080:8080 \
  -e ZAEZD_MODE=replay -e ZAEZD_PUBLIC_URL=http://localhost:8080 zaezd

Переменные окружения

Полный список с комментариями лежит в .env.example. Коротко:

Переменная Зачем По умолчанию
ZAEZD_MODE live ходит в источники, replay читает fixtures/ live
PORT порт внутри контейнера 8080
ZAEZD_PUBLIC_URL адрес, по которому продукт виден снаружи. Из него строятся ссылки на поездку и CSP виджета http://localhost:8080
ZAEZD_CONTACT_EMAIL контакт в User-Agent; Nominatim банит анонимные запросы zaezd@example.com
ZAEZD_CONFCAL_URL MCP каталога событий публичный confcal
ZAEZD_TUTU_URL MCP Туту https://mcp.tutu.ru/mcp

Ключей и токенов нет ни одного: оба источника открытые. Значение ZAEZD_PUBLIC_URL важно задать честно - именно этот адрес уезжает агенту как ссылка на экран и попадает в CSP виджета, и если он не совпадает с реальным, доска внутри хоста останется пустой.

Контейнер несёт HEALTHCHECK на /healthz, так что docker ps показывает healthy только когда приложение действительно отвечает.

За обратным прокси

Сам docker-compose.yaml ничего не знает про прокси, домены и сети - это свойства площадки, а не продукта. Всё это кладётся в docker-compose.override.yaml, он в гитигноре и живёт только на той машине, которая разворачивает сервис. Пример для Traefik:

services:
  zaezd:
    ports: !reset []          # наружу светит прокси, а не контейнер
    environment:
      ZAEZD_PUBLIC_URL: https://zaezd.example.com
    labels:
      traefik.enable: 'true'
      traefik.http.routers.zaezd.rule: Host(`zaezd.example.com`)
      traefik.http.routers.zaezd.entrypoints: https
      traefik.http.routers.zaezd.tls: 'true'
      traefik.http.routers.zaezd.tls.certresolver: letsencrypt
      traefik.http.services.zaezd.loadbalancer.server.port: '8080'
    networks: [proxy]

networks:
  proxy:
    external: true

Имена точки входа, резолвера сертификатов и сети у всех разные, поэтому в примере они подставные. Для nginx или Caddy override будет другим, продукт от этого не меняется: он слушает PORT и отдаёт /healthz.

Подключить к агенту

Эндпоинт <адрес>/mcp работает по streamable HTTP и не требует ключа. Локально это http://localhost:8080/mcp, на развёрнутом сервисе - ваш адрес из ZAEZD_PUBLIC_URL.

Claude Desktop не принимает удалённые серверы прямо в claude_desktop_config.json, там живут только stdio-команды, поэтому либо добавьте Заезд через Settings, Connectors, Add custom connector, либо пропишите проксирующий запуск:

{
  "mcpServers": {
    "zaezd": { "command": "npx", "args": ["-y", "mcp-remote", "http://localhost:8080/mcp"] }
  }
}

Claude Code и Codex CLI:

claude mcp add --transport http zaezd http://localhost:8080/mcp
codex mcp add zaezd --url http://localhost:8080/mcp

Qwen Code:

qwen mcp add --transport http zaezd http://localhost:8080/mcp

Хост, который умеет рисовать виджеты, получит тот же экран через ресурс ui://zaezd/trip-board. Хост без виджетов получит тот же ответ текстом.

Архитектура

Зависимости идут только вниз.

Слой Где Что делает
L0 src/composer/{types,dates,selection,feasibility,pricing,hotels,packages,checkout-labels}.ts чистые правила: даты, выполнимость, цена, отбор пакетов. Без ввода-вывода и без часов
L1 src/sources/ клиенты confcal и Tutu, нормализация, кэш, режим replay
L2 src/enrich/ геокодинг, производственный календарь, погода. Каждый с таймаутом и запасным ответом
L3 src/composer/{build-trip,build-checkout,trip-id}.ts сборка поездки, бюджеты, живые ссылки на оплату, запрос в ссылке
L4 src/web/, src/mcp/ экран и три инструмента. Бизнес-логики здесь нет

Схема и две последовательности - в docs/architecture.md, решения с обоснованиями - в docs/decisions.md.

Ключевое: даты, цены и выполнимость считает код, а не модель. Три одинаковых живых прогона через модель дали три разных числа ночей и разброс цены в полтора раза. Поэтому алгоритм живёт в src/composer/dates.ts и покрыт таблицей сценариев.

Ограничения

Список честный, читайте его как часть продукта.

  • Онлайн-событие поездку не строит, и событие в вашем же городе тоже. В обоих случаях Заезд говорит об этом прямо, а не показывает пустой экран;
  • живой каталог confcal знает 21 город, а живые офлайн-события есть примерно в 15 из них. Пустые города не прячутся, счётчик показан на экране;
  • за один запрос считается одна поездка. Ещё до пяти событий по теме показываются списком, но не считаются: веер из пяти сборок - это спиннер, а не продукт;
  • прогноз погоды доступен только на 16 дней вперёд. Дальше блок погоды просто исчезает;
  • адрес площадки берётся из каталога и никогда не додумывается. Если каталог его не дал, экран говорит об этом и не ставит метку на карте, а агента ответ просит поискать адрес в открытых источниках и сказать человеку, что адрес не из каталога;
  • мультитранспорт Туту считает взрослых, детских и льготных тарифов в поездке нет;
  • ссылка на авиабилет в холодном браузере открывает поиск, а не корзину: корзина заводится только в браузере с живой сессией Туту. Поэтому подпись такой кнопки об этом и говорит;
  • схемы мест в вагоне нет;
  • цена участия в событии складывается в итог только если каталог написал её числом. Текстовую цену вроде "бесплатно для студентов" Заезд показывает как есть и в сумму не берёт;
  • в режиме replay ссылки на оплату собраны из записи и, скорее всего, протухли. Экран и ответ агенту об этом предупреждают;
  • у каталога за раз запрашивается восемь событий: на большем числе он перестаёт отдавать поток посреди ответа. Замер и обоснование в журнале решений.

Измерено

Метрика Значение
Инструментов у Tutu MCP 16
Манифест Tutu MCP 102 143 символа, около 25,5 тыс. токенов
Инструментов у шлюза Заезда 3
Манифест шлюза Заезда 10 181 символ, около 2,5 тыс. токенов
Живая сборка поездки, холодная 12,1 с
Живая сборка поездки, повторная 0,2 с
Сборка поездки из записи 36 мс
Внешних источников 6
Исполняемых сценариев 217
Юнит-тестов 336
Проверено в агентах Claude Code, Qwen Code

Карта репозитория

specs/      спецификация продукта, она же источник правды
features/   исполняемые сценарии на Gherkin и шаги к ним
tests/      юнит-тесты чистого слоя
src/        код продукта по слоям
fixtures/   записанные ответы источников
scripts/    record.ts и вспомогательные утилиты
docs/       руководство пользователя, архитектура, журнал решений

Лицензия

MIT, см. LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选