Skip to main content
Glama

ザエズド

旅の理由から始まる旅行エンジン。ユーザーはどこへ行くかではなく、なぜ行くかを知っている。 自分のテーマに関するカンファレンスに行きたい。ザエズドは最寄りのオフラインイベントを見つけ、往復の交通手段、会場近くのホテル、参加費の全額を計算し、最大3つの説明可能なパッケージを1つの画面に表示する。

最初の垂直統合はITカンファレンス。イベントカタログはconfcal MCP、交通・宿泊はTutu MCPが提供し、すべてをモデルではなく決定論的コンポーザーが組み立てる。

場所

内容

場所

旅行画面

ザエズドを起動したアドレスの/

リンク経由の旅行

/t/<trip_id>、リンクは画面と任意のツールが返す

MCPエンドポイント

/mcp、ストリーミングHTTP、キー不要

ヘルスチェック

/healthz、モードと基準日を返す

ユーザーガイド

docs/user-guide.md

2分間のデモ

docs/demo.mp4、サーバー、画面、エージェント

Related MCP server: trip-planner-mcp

エンドツーエンドのシナリオ

ルートを開く。画面には空のフォームではなく、すでに組み立てられた旅行が表示される。 上部にイベント、日付、都市、会場、最寄りのホテルからそこまでの徒歩時間。その下に最大3枚のカード:電車の便名またはフライト番号付きの行きの交通手段、宿泊費と会場までの距離を含むホテル、帰りの交通手段、合計金額が1つの数字で表示され、その下に計算式が表示され、すべてが一致する。さらに、会場とホテルが表示された都市地図、旅行日数の天気、テーマに関連する他のイベント、含まれなかったものとその理由のリスト。

「支払いリンクを収集」をクリックすると、Tutuのリンクが2〜3件のチェックリストが表示される。各ラベルはTutuが実際に返したものから取得される。カートが開く場所には「カートを開く」、カートがない場所には「選択ページを開く」と表示される。

エージェントも同様:find_event_tripsはテーマと出発都市を指定すると同じ旅行を構造体として返し、get_trip_detailsはパッケージを展開し、create_trip_checkoutはチェックリストを組み立てる。描画できるホストは、MCPアプリとして同じ画面を受け取る。

ソースからの起動

npm install
ZAEZD_MODE=replay npm run dev

http://localhost:8080 が開く。replayモードでは、製品はfixtures/からの記録された応答で動作し、ネットワークにアクセスしない。インターネットもキーもなしで使用でき、基準日は記録日となる。ライブモードはZAEZD_MODE=live。

完全なチェック:

npm run verify

これは、ルールの同期、文書化されたコマンドの照合、型チェック、リンター、ブラウザツリーのビルド、Gherkinの実行可能シナリオ、単体テストである。すべてオフライン。

ブラウザツリーがシナリオの前にビルドされるのは、見た目のためではない。1つのシナリオがサーバーにboot.jsを要求するが、これはホストに送信されるもので、クリーンなクローンにはまだこのファイルがない。

コンテナでの起動

イメージはリポジトリからビルドされ、コードとフィクスチャの両方を内部に含むため、replayモードではコンテナにインターネットもシークレットも不要。

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

画面はhttp://127.0.0.1:8080で起動する。.envファイルがないとcomposeは起動しない。これは意図的で、誰も他人のデフォルト値で製品を起動できないようにするため。

composeなしでも同じことを1つのコマンドで:

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

Tutu MCP

https://mcp.tutu.ru/mcp

キーやトークンは1つもない。両方のソースが公開されている。ZAEZD_PUBLIC_URLの値は正直に設定することが重要だ。このアドレスが画面へのリンクとしてエージェントに送信され、ウィジェットの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の場合、オーバーライドは異なるが、製品は変わらない。PORTをリッスンし、/healthzを返す。

エージェントへの接続

エンドポイント<アドレス>/mcpはストリーミングHTTPで動作し、キーは不要。ローカルではhttp://localhost:8080/mcp、デプロイされたサービスではZAEZD_PUBLIC_URLのアドレス。

Claude Desktopはclaude_desktop_config.jsonでリモートサーバーを直接受け付けない。そこにはstdioコマンドのみが存在する。そのため、設定、コネクタ、カスタムコネクタの追加からザエズドを追加するか、プロキシ起動を指定する:

{
  "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

純粋なルール:日付、実現可能性、価格、パッケージの選択。I/Oなし、クロックなし

L1

src/sources/

confcalとTutuのクライアント、正規化、キャッシュ、リプレイモード

L2

src/enrich/

ジオコーディング、プロダクションカレンダー、天気。それぞれタイムアウトと代替応答付き

L3

src/composer/{build-trip,build-checkout,trip-id}.ts

旅行の組み立て、予算、ライブ支払いリンク、リンク内のリクエスト

L4

src/web/、src/mcp/

画面と3つのツール。ビジネスロジックはここにはない

スキーマと2つのシーケンスはdocs/architecture.mdに、根拠のある決定事項はdocs/decisions.mdにある。

重要なこと:日付、価格、実現可能性はモデルではなくコードが計算する。モデルを3回同じライブ実行すると、3つの異なる宿泊日数と1.5倍の価格差が生じた。そのため、アルゴリズムはsrc/composer/dates.tsにあり、シナリオのテーブルでカバーされている。

制限事項

このリストは正直なもので、製品の一部として読んでほしい。

  • オンラインイベントは旅行を構築せず、同じ都市のイベントも同様。どちらの場合も、ザエズドは空の画面を表示するのではなく、その旨を直接伝える。

  • ライブのconfcalカタログは21都市を知っており、ライブのオフラインイベントがあるのはそのうちの約15都市。空の都市は隠されず、カウンターが画面に表示される。

  • 1リクエストで計算される旅行は1つ。テーマに関連するイベントがさらに最大5つリスト表示されるが、計算はされない。5つのビルドのファンアウトは、製品ではなくスピナーである。

  • 天気予報は16日先までしか利用できない。それを過ぎると、天気ブロックは単に消える。

  • 会場の住所はカタログから取得され、推測されることはない。カタログが提供しなかった場合、画面はその旨を伝え、地図にマーカーを置かず、エージェントの応答は、公開ソースで住所を検索し、住所がカタログからのものではないことをユーザーに伝えるよう求める。

  • Tutuのマルチモーダル交通は大人をカウントし、旅行には子供用・割引運賃はない。

  • コールドブラウザの航空券リンクは、カートではなく検索を開く。カートはTutuのライブセッションがあるブラウザでのみ作成される。そのため、そのようなボタンのラベルはその旨を伝えている。

  • 車両の座席図はない。

  • イベント参加費は、カタログが数値で記述した場合にのみ合計に加算される。「学生無料」のようなテキスト価格は、ザエズドはそのまま表示し、合計には含めない。

  • replayモードでは、支払いリンクは記録から収集されるため、おそらく期限切れ。画面とエージェントへの応答はその旨を警告する。

  • カタログからは一度に8つのイベントが要求される。それ以上要求すると、応答の途中でストリーミングが停止する。測定と根拠は意思決定ログに記載。

測定結果

メトリクス

値

Tutu MCPのツール数

16

Tutu MCPマニフェスト

102,143文字、約25,500トークン

ザエズドゲートウェイのツール数

3

ザエズドゲートウェイのマニフェスト

10,181文字、約2,500トークン

ライブ旅行ビルド、コールド

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