Tutu MCP
Туда, куда не ищется
Собираем поездку через пересадку, когда прямого билета нет.
Поиск любого сервиса отвечает на вопрос «есть ли билет из А в Б». Если прямого сообщения нет, выдача пустая — и человек уходит. Хотя доехать можно, и оба нужных билета продаются там же.
Живой пример. Запрос «Москва → Углич» на 20 августа 2026 в MCP Туту:
search_multitransport → 0 вариантов
search_bus → 0 вариантов
search_rail → 0 вариантовНаш ответ на тот же запрос:
Москва → Ярославль автобус 09:00 → 13:00 863 ₽
пересадка в Ярославле — 40 минут
Ярославль → Углич автобус 13:40 → 16:25 616 ₽
итого 1 479 ₽, на месте в 16:25Оба билета продаёт Туту. Их поиск этого маршрута не покажет никогда.
Как это работает
запрос «Москва → Плёс»
↓
MCP Туту: прямой поиск → 0 вариантов, регион «Ивановская область»
↓
узлы-кандидаты: справочник «регион → центр» + предложения агента
↓
для каждого узла: сначала плечо «узел → цель», затем «откуда → узел»
↓
код сводит стыковки: отбрасывает рейсы, на которые не успеть, считает запас и сумму
↓
карточки маршрутовПоиском управляет агент: он сам решает, какие города и даты проверять, и человек видит ход его работы лентой на экране. Но стыковки, цены и ранжирование считает код — модель к арифметике не допущена, именно там агенты ошибаются. Тяжёлые ответы Туту через модель не проходят: она получает идентификаторы найденных плеч и короткие сводки, а полные данные уходят прямо в интерфейс.
Если уехать в выбранный день нельзя, бэкенд досылает агента за гостиницами: ищется ближайший день отъезда и подбирается ночлег ровно на столько ночей, сколько ждать.
Если агент недоступен или упёрся в лимит, интерфейс молча переключается на детерминированный поиск — экран показывает результат в любом случае.
Related MCP server: Bay Area Transit MCP
Запуск
npm install
cp .env.example .env # вписать OPENROUTER_API_KEY (необязательно)
npm run db:up # Postgres в Docker
npm run migrate # схема базы
npm run warm # прогрев кэша реальными ответами Туту
npm run dev # http://localhost:3100OPENROUTER_API_KEY опционален: без него подсказка городов отключается,
поиск работает на справочнике регионов.
Команды
Команда | Что делает |
| приложение на http://localhost:3100 |
| сборка маршрута в консоли |
| прогрев кэша из |
| что сейчас в кэше |
| проверка живой связки с MCP Туту |
| Postgres в Docker |
| миграции Prisma |
| ESLint |
Структура
Весь код в src/, в корне только конфигурация. Интерфейс собран одной папкой,
логика разложена по модулям — по смыслу, а не по типу файла:
src/
app/ роутинг Next: страница и три API — тонкие, только вызовы модулей
api/agent/ агентный поиск потоком
api/route/ детерминированный поиск (страховка)
api/stay/ гостиницы
frontend/ всё, что видно в браузере
components/route/ форма, календарь, подсказки, лента, карточки, гостиницы
hooks/ чтение потока агента, определение города
design.ts палитра и подписи из макета
format.ts, geo.ts форматирование и расстояния
config.ts константы интерфейса
modules/ логика, зависимости однонаправленные: agent → routing → tutu
tutu/ клиент MCP, кэш, типы, прокси, снятые ответы (fixtures)
routing/ сборка маршрутов, справочники регионов и городов
agent/ цикл, инструменты, промпт, ошибки, ретраи, состояние
lib/ клиент PrismaПочему внутри модулей нет слоёв domain/, infra/, api/. Такая нарезка
оправдана на десятках тысяч строк и полутора десятках доменов. Здесь один
сценарий: слои дали бы папки по одному файлу и пустой contract/. Когда модуль
перерастёт тысячу строк, они появятся вокруг существующих файлов.
Работа с MCP Туту
Подключение: https://mcp.tutu.ru/mcp, streamable HTTP, авторизация не нужна.
Два инженерных решения, без которых это не работает:
Ответы весят ~26 КБ. Инструменты обёрнуты так, что полный JSON уходит в
интерфейс и рисуется карточками, а в языковую модель — сжатая выжимка через
toModelOutput. Замер на реальном поиске: 26 305 → 1 971 символ, экономия 93 %.
У сервера жёсткий rate limit. Около полусотни запросов подряд закрыли нам
доступ ко всему домену на уровне TCP на несколько часов. Поэтому: view: 'compact',
кэш ответов в Postgres, не больше двух узлов на поиск, а перед демо — прогрев
кэша из fixtures/ командой npm run warm. Если сервер недоступен, экран
показывает то, что есть в кэше, и не падает.
Стек
Next.js 16 (App Router) · TypeScript · @ai-sdk/mcp · Prisma 7 + Postgres 18 ·
Tailwind 4 + shadcn/ui · OpenRouter (опционально)
Документы
SPECIFICATION.md— продукт целиком: проблема, доказательства, техника, регламентSCREEN-SPEC.md— спецификация экрана для вёрсткиUSER-GUIDE.md— руководство пользователяfixtures/README.md— что за сохранённые ответы и зачем
This server cannot be deployed
Maintenance
Related MCP Connectors
Read-only public transit departures, stop search, and city coverage for bus and train users.
Real-time transit stops, routes, arrivals, vehicle positions, and schedules via OneBusAway APIs.
Plan journeys by train, bus, tram, metro and ferry across 42 European countries.
Transitland MCP — global GTFS aggregator
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables querying global transit data including agencies, routes, stops, and departures through a GTFS aggregator.22 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables querying real-time BART and SF Muni transit data, including departures, trip planning, fares, advisories, routes, alerts, vehicle positions, and schedules, from any MCP-compatible client.1MIT
- FlicenseNot gradedqualityCmaintenanceEnables querying real-time bike-share feed data, including station availability, nearby bikes and docks, route checks, alerts, and system information across many cities.-
- AlicenseAqualityBmaintenanceEnables public-transport journey planning, place search, realtime departures and timetables, usable from any MCP client.9MIT