Skip to main content
Glama

tutu-mcp-proxy

lint lint-pages tests pages

mcp.tutu.ru の前に置く圧縮・根拠付け(grounding)用 MCP プロキシ。Tutu ハッカソン(トラック 2 —「ツール最適化」)のために作成。同じ 16 ツール、同じ動作に加えて:

  • 常時読み込まれるカタログの削減。 本物のサーバーの tools/list は、最初の検索より前に ~108 KB もある(uv run python tutu.py measure、fixtures/ から再現可能)。最も重い 3 つのツール(search_rail、get_rail_seatmap、search_hotels)には短い top-level の description を付与。削った散文は失われず、ペアとなる get_<domain>_instructions の 呼び出し結果 に移される(tutu_mcp/proxy/compact_tools.py)— 実際にそれを読むセッションだけがコストを払う。inputSchema はどこも変更していない。

  • check_groundedness。 回答ドラフトを、それが依拠する tool_result と決定的に照合する — テキストから価格・時刻・列車/便番号・リンクを抽出し、それらが JSON に実際に存在するかを LLM 判定なしで検証する(tutu_mcp/groundedness.py)。

  • 空の結果に対する説明。 最も頻繁な失敗: エージェントが空のフィルタリング済み検索結果を「この列車は運行していない」と読むが、ツールは販売中のものだけを返していた。Tutu 自身のカウンター meta.post_filter_dropped_* がどのフィルターがリストを空にしたかを示す — プロキシはそれを文に展開して結果に添付する(_empty_result_note)。これによりエージェントは、与えられていないダイヤについて推測する代わりに事実を述べる(tutu_mcp/proxy/empty_results.py)。

  • 前提ゲート + assess_request。 check_groundedness はターンの出力を検証する。これは入力側: 検索を絞り込む値は、ユーザーか過去の tool_result から来なければならない。エージェントがでっち上げたフィルター(典型例: イベント終了時刻を黙って仮定し、それで帰りの便をフィルタリングする)には、データの代わりに clarification_required が返る(tutu_mcp/premises.py)。

  • モックモード。 実サーバーの代わりに記録済みフィクスチャで応答する — ハッカソンの共有レート制限に触れずに、好きなだけ頻繁に実行できる。

トレースビューア

各評価実行は 1 つの自己完結型 HTML ファイルになる: ダブルクリックで開き、サーバーもネットワークも不要。公開されたショーケース — https://trum-ok.github.io/tutu-mcp-hackathon/trace-viewer.html。

トレースビューア: 実行メトリクスのヘッダーと 1 シナリオの分析

make viewer        # из последнего настоящего прогона эвалов
make viewer-demo   # из рукописных демо-трейсов — без модели и без ключа

インターフェース: 概要モード — シナリオ × バリアントの全マトリクスを 1 つのテーブルで表示。失敗のみは落ちたものだけにリストを絞り込む。並べて表示は両バリアントから 1 シナリオを並べ、食い違ったチェックをハイライトする。回答内のハイライトされた値のいずれかをクリックすると、その値の出所であるサーバー応答の正確な断片が表示されるドロワーが開く — または、どの応答にも存在しないという直接的な指摘が表示される。合成実行(demo:/scripted:)には琥珀色のバッジ「計測ではない」が付く — 手書きのデモを計測と混同することはできない。

その構築方法 — docs/deploy.md。

Related MCP server: TripWeaver MCP Server

得られた結果

gpt-5.6-luna で --effort low を使用し、22 シナリオ × 4 回実行、2026 年 8 月 19 日。実行間のばらつきはモデル自体に起因する: バックエンドは同一、シナリオセットも同一。

メトリクス

baseline

proxy

ツール表面積、トークン

25 269

15 364

同、バイト

115 329

74 971

タスク成功率

17–18 / 22

19–21 / 22

主張の根拠付け

97–98 %

99 %

実行あたりの捏造された主張

4

1

実行あたりの入力トークン

3,3–4,4 百万

0,43–0,67 百万少ない

前提ゲート発動

0

8–12

余分な確認質問

0

0

捏造された主張の行はパーセントより先に読むべき: 189 の検証対象主張のうち 4 件と 184 件中 1 件は、97.9% 対 99.5% で、差はノイズのように見える。絶対値では、ユーザーに届く誤った事実が 4 分の 1 に減っており、ユーザーが受け取るのはパーセントではなく事実そのものだ。パーセントは検証対象の主張から計算される: ユーザー自身が指定した閾値(「3000 ₽ より安い」)は payload が裏付ける必要はなく、分母にも入らない。

表面積の削減は 39% で、実行に依存しない: これはエージェントが各セッションで最初の検索前に支払う静的な値だ。それ以外はすべてモデルの挙動なので、範囲で示している。

最後の行は最初の行と同じくらい重要: ゲートは実行あたり 8〜12 回発動し、それでも一度も質問すべきことがないシナリオで質問しなかった(ネガティブコントロール no_overask と did_not_over_ask チェック)。何でもかんでも確認するメカニズムは、完璧な前提メトリクスを獲得する代わりに製品を台無しにするだろう。

バリアントが分かれるのは 5 シナリオで、そのすべてで proxy が勝つ: 空のフィルタリング済み結果が「列車は運行していない」と読まれない、曜日のタイプミスが検索前に検出される、黙って代入されたゲスト数はゲートで止まる、隣り合う座席は正しいツールで検索される。proxy の唯一の持続的な失敗は multitransport_basic: エージェントが価格差(2 275,07 − 1 700 = 575)を出力するが、その両半分は確認されているのに、数値自体が payload に存在しない。このような算術を誤ったもの(ホテル価格に泊数を掛ける — 別のシナリオで、そこではエラー)と区別することは、決定的検証ではできない。これは手法の限界であり、プロキシの欠陥ではない。

レポートの誠実さのための 2 点: フィクスチャのミスはツールのエラーとは別にカウントされる — 記録の穴が Tutu の障害として読まれてはならない。そしてトークン数が推定値の場合は ~ が付く — OpenAI にはトークンカウントのエンドポイントがなく、正確な数値は 1 回の実際の試行リクエスト(usage.prompt_tokens)から取得され、--estimate-tokens は代わりに tiktoken のオフライン推定を代入する。

モデルと推論努力は実行ごとに指定(--model/OPENAI_MODEL、--effort/OPENAI_EFFORT)。両方なしの場合、reasoning フィールドはまったく送信されず、モデルは自身のデフォルトを適用する — これは明示的な --effort none と同じではない。ランナーはデフォルトで /v1/responses を叩く — Chat Completions は現在の reasoning モデルで function tools と推論を同時に扱えない。--api chat は /v1/responses を持たない OpenAI 互換ゲートウェイ用。フィクスチャのマッチングは inputSchema のデフォルト値を無視する(モデルは page: 1、sort: "price_asc" などを、人間がフィクスチャを記録するときに何も書かない場所に書き出す)— そうしないと、モデルを使った実行のほぼすべての呼び出しが記録に一致しなくなる。

実行の仕組み、各メトリクスの意味、ハーネスの自己検証が CI にある理由 — docs/evals.md。

クイックスタート

必要なもの: uv と Python ≥ 3.13(uv sync が自動でインストールする)。

git clone https://github.com/Trum-ok/tutu-mcp-hackathon
cd tutu-mcp-hackathon
uv sync
uv run python tutu.py serve            # mock-режим (по умолчанию) — http://127.0.0.1:8800/mcp
TUTU_PROXY_MODE=live uv run python tutu.py serve   # проксирует настоящий mcp.tutu.ru

任意の MCP クライアント — http://127.0.0.1:8800/mcp 宛(Streamable HTTP、upstream と同様に認証なし)。以下の <URL> はこのアドレスか、デプロイされたプロキシのアドレス(docs/deploy.md 参照)。

claude mcp add --transport http tutu <URL>          # Claude Code
// Cursor · ~/.cursor/mcp.json
{ "mcpServers": { "tutu": { "url": "<URL>" } } }

// Claude Desktop · claude_desktop_config.json — через mcp-remote, он не умеет HTTP напрямую
{ "mcpServers": { "tutu": { "command": "npx", "args": ["-y", "mcp-remote", "<URL>"] } } }

期待される結果。 ログに 2 行: モードとリッスンアドレス。クライアントは接続後に 18 ツール を表示する: ネイティブの Tutu 16 個に加えて assess_request と check_groundedness。16 個なら、クライアントはプロキシではなく Tutu 自体に接続している。

どれだけ削減できたか

tools/list: 110 164 → 79 411 バイト(−27.9 %)、両側の initialize 命令も含めると −33.1 %(プロキシは Tutu の 11.2 KB の代わりに 1.9 KB の命令ブロックを返す)。両方の数値は、独自の 2 ツール(assess_request 1 313 バイト、check_groundedness 1 100)を追加した後のもの。

カタログの層ごとの内訳、圧縮のコスト、意図的に越えなかった境界 — docs/compaction.md。

ドキュメント

ユーザー向けの解説は別ページ: make docs で site/index.html をビルド、または公開済みのものを開く: https://trum-ok.github.io/tutu-mcp-hackathon/。

ファイル

内容

docs/findings.md

実サーバーでの生の計測と動機付けとなるケース

docs/compaction.md

何が圧縮されるか、代償、やらなかったこと

docs/evals.md

評価ハーネスの構造、フィクスチャ、実行スナップショット

docs/structure.md

リポジトリ構造と依存関係の方向

docs/configuration.md

環境変数とすべての make ターゲット

docs/deploy.md

Docker、Render、GitHub Pages、両ページのビルド

rezo チーム

  • アルタモノフ・アルカディ (@OpSonata)

ライセンス

MIT

Related MCP Connectors

Related MCP Servers