Skip to main content
Glama

去往搜索不到的地方

在没有直达票时,通过中转来规划行程。

任何服务的搜索回答的都是“从A到B有没有票”这个问题。如果没有直达 班次,结果就是空的——用户就走了。但其实是可以到达的,而且所需的两张票 在同一个地方就能买到。

真实案例。 在MCP Туту上查询“莫斯科 → 乌格利奇”,日期为2026年8月20日:

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:3100

OPENROUTER_API_KEY 是可选的:没有它,城市提示功能会关闭, 搜索基于区域参考手册运行。

命令

命令

功能

npm run dev

应用运行于 http://localhost:3100

npm run route -- Москва Углич 2026-08-20

在控制台构建路线

npm run warm

从 src/modules/tutu/fixtures/ 预热缓存

npm run cache

查看当前缓存内容

npm run smoke

检查与MCP Туту的实时连接

npm run db:up / db:down

Docker中的Postgres

npm run migrate

Prisma迁移

npm run lint

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,流式HTTP,无需授权。

两个不可或缺的工程决策:

响应体积约26 KB。 工具经过封装,完整JSON进入界面并以卡片形式 呈现,而发送给语言模型的是通过 toModelOutput 压缩后的摘要。在真实搜索 中实测:26,305 → 1,971字符,节省93%。

服务器有严格的速率限制。 连续约五十个请求导致我们在TCP层面 被封锁了整个域好几个小时。因此:使用 view: 'compact',在Postgres中 缓存响应,每次搜索不超过两个节点,演示前用 npm run warm 命令从 fixtures/ 预热缓存。如果服务器不可用,屏幕会显示缓存中的内容, 不会崩溃。

技术栈

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 — 保存的响应是什么以及为什么

Related MCP Connectors

Related MCP Servers