ANTIGRAVITY LABEN
記事一覧/連携・プラグイン
連携・プラグイン/2026-08-04上級

起動 3 ミリ秒の裏で、最初のターンはツールを 0 本しか見ていませんでした

MCP の非ブロッキングロードで起動待ちは 2,907ms から 3ms になりました。けれど待ちは消えず、最初のターンへ移っていました。待ちの行き先を実測し、必要な分だけ待つレディネスゲートを設計します。

MCP22Antigravity CLI21起動時間レディネス設計14

プレミアム記事

Antigravity CLI 1.1.9 の変更点に、対話起動時の MCP ロードが非ブロッキングになった、という一行があります。

起動が速くなる。それだけの話だと思っておりました。

手元の構成を上げ直して、体感で明らかに速くなったことを確認しました。そこまでは期待どおりです。

引っかかったのはその直後でした。起動してすぐ、いつもの調子で「このディレクトリを検索して」と打ったところ、エージェントが検索ツールを使わずに、自前で推測した答えを返してきたのです。

もう一度同じことを打つと、今度はきちんと検索ツールを呼びました。

同じプロンプト、同じ設定、違う結果。差は打鍵のタイミングだけでした。

個人開発で回している環境なので、MCP サーバーは必要になるたびに自分で足してきたものばかりです。何本繋がっていて、どれが重いのかは把握しているつもりでおりました。

待ちは消えたのか、移動しただけなのか

非ブロッキングロードは、起動処理からサーバー接続を外して裏に回す変更です。表側の待ちは減ります。

けれど、サーバーが応答を返すのにかかる時間そのものが短くなったわけではありません。ハンドシェイクの実コストは変わらず存在します。

だとすると、その時間はどこへ行ったのか。

体感で語るには曖昧すぎる話です。計測できる形に落としました。

MCP サーバーは stdio 上の JSON-RPC で喋ります。ならば、起動コストと初期化コストを指定できる最小のサーバーを書いて、接続の組み立て方だけを差し替えれば、待ちの行き先は測れます。

計測ハーネスを組む

まずモックサーバーです。プロセス起動(インポートやランタイム立ち上げ)のコストと、initialize に対する応答遅延を別々に指定できるようにしました。この2つは実運用でも性質が違うためです。

#!/usr/bin/env python3
"""stdio 上の最小 MCP 風サーバー。改行区切りの JSON-RPC。
実サーバーのコールドスタートを模す: 起動コスト -> initialize コスト。
usage: mock_server.py <name> <boot_ms> <init_ms> <tool_ms>
"""
import sys, json, time
 
name, boot_ms, init_ms, tool_ms = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4])
time.sleep(boot_ms / 1000.0)  # プロセス起動 / モジュールインポートのコスト
 
for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    req = json.loads(line)
    m = req.get("method")
    if m == "initialize":
        time.sleep(init_ms / 1000.0)
        res = {"protocolVersion": "2025-06-18", "serverInfo": {"name": name}}
    elif m == "tools/list":
        res = {"tools": [{"name": f"{name}_query"}]}
    elif m == "tools/call":
        time.sleep(tool_ms / 1000.0)
        res = {"content": [{"type": "text", "text": "ok"}]}
    else:
        res = {}
    sys.stdout.write(json.dumps({"jsonrpc": "2.0", "id": req.get("id"), "result": res}) + "\n")
    sys.stdout.flush()

次にハーネス側です。測る地点を3つに分けました。ここを分けないと、そもそも「待ちが移動した」という現象が観測できません。

  • t_prompt — 利用者が最初のターンを打てるようになるまで
  • t_ready — すべてのサーバーが initializetools/list を終えるまで
  • t_first — 最初のツール呼び出しが返るまで

サーバー構成は、自分の mcp_config.json の顔ぶれに寄せました。速いものが数本、重いものが1〜2本、という現実的な偏りを持たせています。

#!/usr/bin/env python3
import json, subprocess, sys, threading, time
 
SERVERS = [
    # (name, boot_ms, init_ms, tool_ms)
    ("fs",       120,  40,  15),
    ("github",   180, 320,  60),
    ("db",       150, 210,  35),
    ("search",   140, 890,  45),   # 重い1本
    ("chrome",   260, 480, 110),
]
 
def spawn(spec):
    n, b, i, t = spec
    return subprocess.Popen(
        [sys.executable, "-u", "mock_server.py", n, str(b), str(i), str(t)],
        stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, bufsize=1)
 
def rpc(p, method, _id):
    p.stdin.write(json.dumps({"jsonrpc": "2.0", "id": _id, "method": method}) + "\n")
    p.stdin.flush()
    return json.loads(p.stdout.readline())
 
def handshake(spec, procs, done, lock):
    p = spawn(spec)
    rpc(p, "initialize", 1)
    rpc(p, "tools/list", 2)
    with lock:
        procs[spec[0]] = p
        done.append((spec[0], time.perf_counter()))
 
def run(strategy):
    t0 = time.perf_counter()
    procs, done, lock = {}, [], threading.Lock()
 
    if strategy == "blocking":
        for s in SERVERS:
            handshake(s, procs, done, lock)
        t_prompt = time.perf_counter() - t0
    else:
        ths = [threading.Thread(target=handshake, args=(s, procs, done, lock)) for s in SERVERS]
        for t in ths: t.start()
        t_prompt = time.perf_counter() - t0     # 入力欄は即座に使える
        for t in ths: t.join()
 
    t_ready = time.perf_counter() - t0
 
    # 最初のツール呼び出しは最も遅いサーバーへ向ける(現実的な最悪ケース)
    t_call0 = time.perf_counter()
    rpc(procs["search"], "tools/call", 3)
    t_first = time.perf_counter() - t0
    call_wall = time.perf_counter() - t_call0
 
    for p in procs.values():
        p.stdin.close(); p.terminate()
    return dict(strategy=strategy, t_prompt=t_prompt, t_ready=t_ready,
                t_first=t_first, call_wall=call_wall)
 
if __name__ == "__main__":
    for s in ("blocking", "nonblocking"):
        rows = [run(s) for _ in range(3)]
        med = lambda k: sorted(r[k] for r in rows)[1]
        print(f"{s:12s} t_prompt={med('t_prompt')*1000:7.1f}ms  "
              f"t_ready={med('t_ready')*1000:7.1f}ms  "
              f"t_first={med('t_first')*1000:7.1f}ms  "
              f"call_wall={med('call_wall')*1000:6.1f}ms")

3回走らせて中央値を取っています。1回の結果で語ると、プロセス起動のばらつきに引きずられます。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
ブロッキングと非ブロッキングの待ち時間を3つの地点で実測し、待ちが消えたのではなく移動していることを数字で示します
「起動が速いほど最初のターンのツールカタログが空になる」という取り違えやすい関係を、到達時刻別の可視率で明らかにします
全サーバーではなく必要集合だけを待つレディネスゲートの実装(デッドライン付き・失敗時も待たせない)を丸ごと掲載します
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

連携・プラグイン2026-07-28
MCP を束ねると read_file が 3 つになる — ツール名の衝突を起動前に落とす
複数の MCP サーバーを束ねるとツール名は静かに衝突します。5 サーバー 21 ツールの構成で 43% が衝突していた実測と、起動前に検出して決定的な別名を割り当てる Python プリフライトの設計をまとめました。
連携・プラグイン2026-07-19
無応答の MCP サーバーでエージェントが止まる — 接続・一覧・呼び出しに別々のタイムアウトを敷く
Antigravity CLI 1.1.3 は、無応答の MCP サーバーがエージェントを無限に止める問題を、接続・ツール一覧・ツール呼び出しごとのタイムアウトで塞ぎました。3つの境界で失敗の出方が違う理由を整理し、防御ラッパーとサーキットブレーカー、失敗時だけの通知を、動くコードと夜間運用の実測でまとめます。
連携・プラグイン2026-08-03
サーバーごとに秒数を書く前に測ったこと — MCP の接続と呼び出しは 486 倍違っていました
2.4.3 で MCP サーバーごとにタイムアウトの秒数を指定できるようになりました。何秒が妥当かを決めるために stdio サーバーを自作して境界別に実測し、単一の値では覆えない理由と、いま設定に書いている値の決め方をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →