Skip to main content
Glama

Заезд

一个以缘由为起点的出行引擎。人不知道去哪里,但知道为什么:想去参加自己领域的会议。Заезд 找到最近的线下活动,计算往返路程、场地附近的酒店和完整的参与费用,并在一个屏幕上展示最多三个可解释的套餐。

第一个垂直领域是 IT 会议。事件目录由 confcal MCP 提供,交通和住宿由 Tutu MCP 提供,而所有内容由确定性的组合器(composer)而非模型来组装。

Что где

什么

在哪里

出行屏幕

/ 位于您启动 Заезд 的地址

通过链接的出行

/t/<trip_id>,链接由屏幕和任一工具提供

MCP 端点

/mcp,streamable HTTP,无需密钥

健康检查

/healthz,返回模式和基准日期

用户指南

docs/user-guide.md

两分钟演示

docs/demo.mp4,服务器、屏幕和代理

Related MCP server: trip-planner-mcp

端到端场景

打开根地址。屏幕上已经是组装好的出行,而不是空表单。上方是事件、日期、城市、场地以及从最近酒店步行到场地的时间。下方最多三张卡片:去程(含火车车次或航班号)、酒店(含全程住宿价格和到场地距离)、返程,总价一个数字,下面一行能对上的算术式。再往下是带有场地和酒店的城市地图、出行日期的天气、同主题的其他事件,以及未包含项及其原因的列表。

点击“生成支付链接”——会出现一个包含两三个 Туту 链接的清单。每个链接的标签取自 Туту 实际返回的内容:在会打开购物车的地方显示“打开购物车”,在没有购物车的地方显示“打开选择页面”。

对代理也是如此: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

这包括规则同步、文档化命令核对、类型检查、lint、浏览器树构建、Gherkin 可执行场景和单元测试。全部离线。

浏览器树在场景之前构建并非为了好看。有一个场景会向服务器请求那个发送到主机的 boot.js,而在干净的克隆中还没有这个文件。

在容器中运行

镜像从仓库构建,内部同时包含代码和 fixtures,因此在 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

产品对外可见的地址。出行链接和 widget 的 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 的值必须如实设置——正是这个地址作为屏幕链接发送给代理,并进入 widget 的 CSP;如果它与实际地址不一致,主机内的面板将保持空白。

容器带有针对 /healthz 的 HEALTHCHECK,因此只有应用真正响应时,docker ps 才会显示 healthy。

在反向代理后面

docker-compose.yaml 本身对代理、域名和网络一无所知——这些是部署环境的属性,而不是产品的属性。所有这些都放在 docker-compose.override.yaml 中,它位于 .gitignore 中,只存在于部署服务的机器上。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

支持渲染 widget 的主机将通过资源 ui://zaezd/trip-board 获得同一屏幕。不支持 widget 的主机将以文本形式获得同一响应。

架构

依赖只向下流动。

层

位置

职责

L0

src/composer/{types,dates,selection,feasibility,pricing,hotels,packages,checkout-labels}.ts

纯规则:日期、可行性、价格、套餐筛选。无输入输出,无时钟

L1

src/sources/

confcal 和 Туту 的客户端、规范化、缓存、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。

关键点:日期、价格和可行性由代码计算,而不是模型。三次相同的实时模型运行得出了三个不同的晚数,价格波动达 1.5 倍。因此算法位于 src/composer/dates.ts,并由场景表覆盖。

限制

这份清单是诚实的,请把它当作产品的一部分来阅读。

  • 在线事件不会构建出行,您所在城市的事件也不会。在这两种情况下,Заезд 都会直接说明,而不是显示空屏幕;

  • confcal 实时目录覆盖 21 个城市,其中约 15 个有实时线下活动。空城市不会被隐藏,计数会显示在屏幕上;

  • 一次请求只计算一次出行。同主题的最多五个事件会以列表形式显示,但不计算:五个组装结果同时展开是转圈等待,而不是产品;

  • 天气预报仅支持未来 16 天。更远时天气块会直接消失;

  • 场地地址取自目录,绝不臆造。如果目录没有提供地址,屏幕会说明这一点,并且不会在地图上放置标记;代理的响应会要求从公开来源查找地址,并告知用户该地址并非来自目录;

  • Туту 的多交通方式按成人计算,出行中没有儿童票和优惠票;

  • 在冷浏览器中,机票链接打开的是搜索页而不是购物车:购物车只能在具有 Туту 活动会话的浏览器中创建。因此这类按钮的标签会明确说明这一点;

  • 没有车厢座位图;

  • 事件参与费用只有在目录以数字形式给出时才会计入总价。对于像“学生免费”这样的文本价格,Заезд 会原样显示,不计入总价;

  • 在 replay 模式下,支付链接来自录制内容,很可能已经过期。屏幕和代理响应都会对此发出警告;

  • 每次向目录请求八个事件:数量更多时,它会在响应中途停止输出流。测量和理由见决策日志。

测量数据

指标

数值

Tutu MCP 的工具数

16

Tutu MCP 清单

102 143 个字符,约 25.5 千个 token

Заезд 网关的工具数

3

Заезд 网关清单

10 181 个字符,约 2.5 千个 token

实时出行组装(冷)

12.1 秒

实时出行组装(重复)

0.2 秒

从录制内容组装出行

36 毫秒

外部数据源

6

可执行场景

217

单元测试

336

在代理中验证

Claude Code, Qwen Code

仓库地图

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

许可证

MIT,见 LICENSE。

Related MCP Connectors

Related MCP Servers