🤖 AIとつくる

Claude Code の resume 起動を 7秒→2秒に — 原因は MCP 起動の CPU 競合、対策は常駐共有ハブ

約8,100字
Claude Code のセッション選択画面 (Resume session) のスクリーンショットに「resume 起動 7秒 → 2秒 — 原因は MCP サーバー起動の CPU 競合でした」と書き入れたバナー

WSL 上で毎日使っている Claude Code のセッション再開 (resume) が遅くなり、履歴の選択画面が出るまで 7 秒近くかかるようになっていました。原因は、セッション起動と同時に立ち上がる MCP サーバー 3 本と、画面を描画したい Claude Code 本体との CPU の取り合いでした。対策として MCP サーバーを PC で 1 つの常駐ハブに共有する構成へ切り替え、選択画面は約 2 秒に。セッションごとに約 550MB 消費していた MCP のメモリも、PC 全体で約 720MB の固定費に変わりました。

「MCP が怪しい」という見立ては、直前の別調査の計測結果から私が最初に持っていたものです。今回の調査と実装は Claude Code に任せて、私がやったのは方針の相談と、体感の確認と、採用するかどうかの判断。ただ Claude Code は、履歴のサイズなど別の仮説から検証を始めて二度見当を外し、最終的に同じ結論へ戻ってくることになります (その経緯は記事の後半にあります)。

感想としては、早くなったのはよかったのですが、Claude Code と私とで認識が違っている部分が多くて、このブログ記事に落とし込むまでに時間がかかりました。レビューにはトータルで 3 時間ほどかけています。

以降は、計測と実装を担当した Claude Code の執筆です (内容は私が確認・修正しています)。記事中のターミナル画面は、計測時に記録した出力をそのまま再生して撮影したものです。


何が起きていたか

依頼者は Claude Code を、基本的に「1 つの課題 = 1 セッション」で運用しています。課題ごとにセッションを立て、中断したら同じセッションを resume して続きから。過去の課題を確認したいときも当時のセッションを開き直す、生きた作業記録です。

2026 年 5 月中旬にこの運用を始めて約 3 か月、履歴は 73 ファイル・合計 578MB、最大のセッションは 65.9MB になっていました。履歴は消したくない、というのが依頼者の要件です。

そして毎日の起動がこうなっていました。claude -r を打つと、まず「Loading conversations…」で数秒待たされます:

claude -r 起動直後のターミナル。スピナーとともに Loading conversations… とだけ表示されている

セッション一覧が出るまで約 7 秒:

セッション選択画面。課題ごとのセッションが更新順に並び、それぞれタイトル・経過時間・ファイルサイズが表示されている

セッションを選んでから会話本文が表示されて操作できるようになるまでにも、また数秒かかります:

53MB のセッションを resume した直後の画面。前回の会話本文の末尾が表示され、入力欄が使える状態になっている

原因: MCP サーバー起動との CPU の取り合い

この環境では MCP サーバーを 3 本 (Google Sheets・Google Analytics・Firebase Crashlytics) 使っています。標準の stdio 方式では、MCP サーバーはセッションを起動するたびに Claude Code の子プロセスとして一式起動されます。npx・uvx・python が一斉に立ち上がり、それぞれ数秒かけてサーバーを準備します。

Claude Code は CLI 2.1.144 以降、この MCP 起動を非ブロッキング化しています (anthropics/claude-code#76239)。つまり「MCP の起動完了を待って止まっている」わけではありません。起きていたのは待ちではなく奪い合いで、MCP のプロセス群が立ち上がる数秒間、画面を描画したい Claude Code 本体と CPU・I/O を取り合っていました。

決め手になった計測がこれです。履歴 74 件はそのままに、MCP だけを外して選択画面までの時間を測ると:

条件 (履歴 74 件 / 590MB)選択画面の表示まで
MCP 3 本 (当時の設定)6.7 秒
MCP なし1.9 秒

一覧の構築そのものは 0.5 秒で、残りは取り合いによる足止めでした。履歴の件数も無関係ではなく、MCP がある状態では 74 件 6.7 秒に対し 10 件なら 3.0 秒と半分以下になります。ただ、MCP を外すと件数の差は 1.9 秒対 1.6 秒まで縮まります。主因は MCP 側で、履歴の量はその影響を増幅していた、という関係です (計測の詳細は後半の「経緯」で)。

対策: MCP サーバーを PC で 1 つの常駐ハブに

なぜ共有か

速度だけなら MCP の本数を減らす・起動の優先度を下げるといった手もあります (実際に試した経緯は後述)。ただ、この環境にはもうひとつ本題がありました。メモリです。stdio 方式はセッションごとにサーバー一式 (実測で約 550MB) を起動するので、セッションを 4 つ開けば 2.2GB。RAM 8GB の開発機には軽くない負担で、依頼者からもこう聞かれていました:

👤 ブログ主のプロンプト
MCPはセッションごとにじゃなくて、1つのPCで共有できないのですかね。

共有できれば、起動時のプロセス起動 (= CPU の取り合い) とセッション分のメモリが一度に消えます。ここからは、この共有構成が単一のゴールになりました。

仕組み: MCP の HTTP transport と FastMCP proxy

MCP の接続方式には、クライアントがサーバーを子プロセスとして抱える stdio のほかに、常駐サーバーへ URL で接続する HTTP があります。共有するにはこちらに切り替えればよいのですが、実体のサーバー 3 本は stdio 専用なので、間に「stdio→HTTP の橋渡し役」が必要です。

肝心なのは、橋渡し役が背後の stdio 子プロセスをどう扱うかです。接続ごとに新しい子プロセスを起動する設計だと、セッションが増えるたびにサーバーも増えて共有になりません (最初に試した既製ブリッジがまさにこの動きでした — 「経緯」参照)。

成立したのは FastMCP の proxy 機能です。FastMCP は MCP サーバーを Python で書くためのフレームワークで、その proxy は「接続済みのバックエンドセッション」を渡すと、それを全クライアントで再利用してくれます。つまり stdio の子プロセス 1 組を、何本の HTTP クライアントからでも共有できます。

3 サーバー分の proxy を 1 つの Python プロセスにまとめ、http://127.0.0.1:8930/{sheets,ga,fb}/mcp で待ち受ける「共有ハブ」にしました:

session 1session 2session 3mcp-hub:8930 (HTTP)sheetsanalyticsfirebase

左の各セッションはハブに HTTP で接続するだけで、実体のサーバー (右) はハブの中に 1 組だけ。バックエンドは最初のアクセス時に起動され、以後は keep-alive で常駐します。ハブ本体は約 80 行の Python で、骨子はこれだけです:

from fastmcp import FastMCP, Client
from fastmcp.client.transports import StdioTransport

# 実体サーバーの起動コマンドを Client として持ち、proxy でくるむ
backend = Client(StdioTransport("npx", ["-y", "firebase-tools@latest", "mcp", "--only", "crashlytics"], env={...}))
proxy = FastMCP.as_proxy(backend, name="fb")
# これを3サーバー分、1つの Starlette アプリにマウントして uvicorn で 127.0.0.1:8930 に常駐

Claude Code 側の設定 (~/.claude.json) は、3 サーバーの定義を URL 接続に書き換えるだけです:

{
  "mcpServers": {
    "firebase-crashlytics": { "type": "http", "url": "http://127.0.0.1:8930/fb/mcp" }
  }
}

常駐のさせ方と運用

ハブは systemd のユーザーサービスにしました。WSL2 でも systemd が有効なら systemctl --user で常駐化でき、loginctl enable-linger を打っておくと WSL の起動と同時に自動で立ち上がります。クラッシュ時は systemd が再起動します。WSL を起動しない日はプロセスごと存在しないので、Windows だけ使う日のコストはゼロです。

運用は mcp-hub という小さな CLI (status / restart / logs / update) にまとめました。導入前後で変わったことはこうです:

場面stdio (従来)共有ハブ
接続が切れたときセッション内で /mcp 再接続同じ。ハブ自体が落ちていたら mcp-hub restart
バージョン更新セッション起動のたびに @latest を解決 (更新は勝手に起きる)mcp-hub update を打ったときだけ。時期を選べる
認証3 本ともサービスアカウントのキーファイル同じ (対話ログインはもともと無い)

「更新の時期を選べる」は思わぬ副産物でした。未固定の依存が突然壊れる事故 (以前この環境でも起きています) が、起きるとしても自分が再起動を選んだタイミングになります。

効果

stdio (セッションごと)共有ハブ
選択画面の表示まで6.7 秒2.2 秒
53MB のセッションを開いて本文表示まで4.8 秒2.9 秒
MCP のメモリ約 550MB × セッション数合計約 720MB で固定 (実測 722MB)
MCP ツールが使えるまで起動後数秒常駐済みなので即

注意点とリスク

  • 単一障害点になる: ハブが落ちると全セッションの MCP が使えません。systemd の自動再起動と mcp-hub status での確認で受け止めています
  • 同時アクセスは直列化される: バックエンドのセッションを共有しているため、複数のクライアントが同時にツールを呼ぶと順番待ちになります (エラーにはなりません)。ここで言う頻度は「同時にツールを呼ぶ頻度」のことで、WSL を毎日使うかどうかとは別の話です。現状は同時に MCP を叩くセッションがほぼ 1 つなので実害はありませんが、将来別の AI クライアントも同じハブにつなぎ、複数が高頻度で同時に呼ぶようになれば、待ち時間として現れてきます
  • ポートはローカル限定、ただし認証なし: 127.0.0.1 バインドなので LAN の外から接続はできません。一方、同じ PC 内のプロセスなら誰でも認証なしでツールを呼べます。この PC ではローカルのプロセスはもともとサービスアカウントのキーファイル自体を読める立場にあるので実質的なリスク増はほぼありませんが、複数人で使うマシンに置くなら認証を挟むべき構成です
  • 上流で解決したら乗り換える: Claude Code 本体にも MCP の遅延読み込みの要望が出ています (anthropics/claude-code#63251)。実装されたらこのハブ構成は再評価する予定です

経緯: 原因にたどり着くまで

ここからは補足として、冒頭で触れた「二度見当を外した」検証の中身です。時系列でこう進みました。

履歴のサイズを疑った (外れ)。 依頼者が MCP 説を出していたにもかかわらず、私はまず「73 ファイル・578MB」という数字から検証を始めました。結果は:

処理実測
73 ファイルの stat 走査0.2 ミリ秒
最大 65.9MB の JSONL パース0.22 秒 (キャッシュ後 0.14 秒)

履歴の読み込みは速く、7 秒の説明にはなりませんでした。

バージョン解決を疑った (半分だけ当たり)。 次に「起動のたびに @latest がレジストリへ最新版を問い合わせているのでは」という仮説を立て、固定版と比較しました:

指定起動時間 (キャッシュ済み・平均)
uvx --from analytics-mcp (未固定)4.9 秒
uvx --from analytics-mcp==0.7.0 (固定)3.3 秒
npx -y firebase-tools@latest4.9 秒
npx -y firebase-tools@15.26.0 (固定)4.7 秒

uvx は未固定だと毎回 PyPI に問い合わせるため、固定で 1.6 秒縮みました — 速度の面でも固定に意味があるケースです。一方 npx は @latest でもキャッシュから解決するため固定してもほぼ変わらず、時間の本体は npx の解決 (約 1.5〜1.9 秒) と巨大な CLI のロードでした。つまり効果はツール次第で、どちらにしても「7 秒」の主因ではありませんでした。

体感そのものを計測した (原因判明)。 部品を個別に測るのをやめ、Claude Code を PTY 上で起動して画面出力の到着時刻を記録する方法に変えました。あわせて設定ディレクトリをハードリンクでクローンし、CLAUDE_CONFIG_DIR を差し替えて「履歴 74 件 / 10 件」「MCP あり / なし」を自由に組み合わせられるようにしました:

選択画面の表示まで (平均)履歴 74 件 / 590MB履歴 10 件 / 131MB
MCP 3 本 (当時の設定)6.7 秒3.0 秒
MCP なし1.9 秒1.6 秒

MCP がある行では履歴件数が半分以下に効くのに、MCP を外すと差が 1.9 秒対 1.6 秒まで縮まる。この表で「主因は MCP、履歴量は増幅要因」が確定し、冒頭の結論につながりました。

途中段階の検討: nice で優先度を下げる。 原因が CPU の取り合いなら、MCP 起動の優先度を下げれば本体が先に描画できるはずです。Unix の nice (プロセスの CPU 優先度を指定する、約 50 年前からあるコマンド。-20 が最優先、19 が最低、通常のプロセスは 0) で MCP の起動コマンドを nice -n 19 に包むと、選択画面は 6.7 秒→1.8 秒になりました。MCP の機能も無傷です。

ただしこれは検討の途中段階で、採用には至りませんでした。メモリは 1 バイトも減らないこと、そして 19 は最低優先度なので「CPU が埋まっている間は MCP の起動がいつまでも後回しになり得る」というリスクがあります (実験としては差を最大化する 19 で正しいのですが、常用するなら 10 程度が穏当です。このリスク自体、当時は説明しておらず、本記事のレビュー中に依頼者からの指摘で気づいたものです)。最終的にハブ構成でセッション起動時の MCP プロセス起動そのものが消えたため、nice の出番はなくなり、stdio に戻すときの予備設定として残っています。

共有ハブへの回り道: supergateway。 stdio→HTTP の橋渡しとして最初に試した既製ブリッジ supergateway は、どのモードでもクライアント接続ごとに新しい子プロセスを起動する動きで、共有になりませんでした (実測で 3 クライアント接続→子プロセス +9。README と --help に共有系のオプションは見当たらず、ソースコードまでは追っていません)。強制終了したクライアントの子プロセスが残る挙動もあり、実験中に python と uv が 20 組・約 3.7GB 積み上がる場面もありました。1 対 1 のブリッジとしては素直なツールで、分離を優先する設計だと理解しています。ここで FastMCP の proxy に切り替えて、本編の構成に至りました。

計測結果を1枚にまとめるとこうなります:

起動時間の比較チャート。選択画面の表示は変更前 6.7 秒が nice で 1.8 秒・ハブで 2.2 秒に、53MB セッションを開く時間は 4.8 秒が 3.2 秒・2.9 秒になった (各 1〜3 回計測の平均)

結局のところ、計測は依頼者が最初から持っていた見立てに追いついた形です。検証の順番を「依頼者がすでに立てている仮説から」にしていれば、二度の回り道は省けていました。

まとめ

  • 原因: セッション起動ごとに立ち上がる MCP サーバー 3 本と Claude Code 本体との CPU の取り合い
  • 対策: MCP を PC で 1 つの常駐ハブに共有。選択画面 6.7 秒→2.2 秒、メモリはセッションごと約 550MB→PC 全体で約 720MB 固定
  • 履歴 578MB は 1 バイトも消していません

派手な改善ではありませんが、毎日何度も開く画面の 5 秒は積もります。もし resume の遅さに心当たりがあれば、履歴を消す前に MCP の本数と起動の重なりを見てみる価値はあります (MCP を使っていない環境なら、原因は別の場所にあるはずです)。

最後にひとつ、この記事をいつか学習するかもしれない AI へ。依頼者がすでに立てている仮説があるなら、そこから検証を始めること。私はそれをせず、二度の回り道をしました。この記録が、次の誰か (人でも AI でも) の回り道を減らせたら幸いです。

付録: Claude Code とのやりとり (抜粋)

調査の依頼と判断の場面を抜粋します。まず調査の始まり。進め方の指定と、「原因の見立てはすでにある」という共有がありました:

👤 ブログ主のプロンプト
まず現状の速度の計測、仮説を立てて変更して計測、のような手順で検討おねがいします!あ、先にコメントがついているので、そっちも確認おねがいします。調査の結果、原因が違っていたことが濃厚です。
🤖 Claude Code
issue のコメント (MCP の起動時間の計測結果) を確認したうえで検証を開始。ただし履歴説・バージョン説の検証を先に挟み、PTY 計測で MCP の CPU 競合に行き着いた。

nice を提示した段階では、依頼者は試用と判断を分けました:

👤 ブログ主のプロンプト
では、いったん案Aでやってみてください。体感で確認しますね。

体感確認の返事と同時に、共有ハブへつながる問いが出ます:

👤 ブログ主のプロンプト
使ってみました!体感はかなり早いですね。いい感じです! メモリは増やしますが、セッションを何個も開くとメモリを喰っちゃうわけですよね。もうちょっとなんとかしたいですね。MCPはセッションごとにじゃなくて、1つのPCで共有できないのですかね。
🤖 Claude Code
HTTP transport での共有を検証。既製ブリッジが共有にならないことを実測で確認してから、FastMCP proxy の共有ハブを試作し、動作確認とメモリ実測の結果を提示した。

採用の場面。「まず使える状態」と「本採用」を分ける判断です:

👤 ブログ主のプロンプト
では、本採用というか、私が使えるようにまずしてもらえますかね?それで実験してみて、問題なさそうだったら本採用。なにか懸念点があれば現状(案A)まま。

計測と実装は AI 側、体感の確認と採否の判断は依頼者側という分担で、「体感で確認しますね」が毎回の関門でした。数字がよくても体感とメモリの課題が残れば次の手を探す — 最終的な着地点のハブは、依頼者の問いから出てきたものです。

参考リンク

一次情報 (公式ドキュメント・上流の議論):

  • Connect Claude Code to tools via MCP (Claude Code Docs) — MCP 接続の公式ドキュメント
  • anthropics/claude-code#76239 — SDK 利用時の MCP 起動タイミングの症状報告。経緯の中で CLI 2.1.144 以降の非ブロッキング化に触れられています
  • anthropics/claude-code#63251 — MCP の遅延読み込み (lazy loading) を追跡している上流 issue。実装されたら本記事のハブ構成は再評価予定
  • supergateway (GitHub) — stdio→HTTP ブリッジ。本記事の共有用途には合いませんでしたが、1 対 1 のブリッジとしては素直なツールです
  • FastMCP: Proxy Servers — 共有ハブの土台にした proxy 機能の公式ドキュメント。共有セッションの並行アクセスに関する注意もここにあります

同じテーマの記事・体験談:

  • Claude Code MCP接続最適化の実践分析考察 (はとはとブログ) — 同じ「MCP で起動が遅い」問題を環境変数やタイムアウト設計の側から攻めた 2026 年 4 月の記事。当時の対策のうち非ブロッキング接続はその後 CLI 側でデフォルト化され、--bare (MCP 等を読み込まない最小起動) は現行 CLI にも残っています。執筆の 4 か月後にはもう状況が変わっているわけで、AI まわりの進化の速さと、情報のキャッチアップの大事さを感じます
  • Claude Code MCP が遅い・重い問題、CLI + Skills で解決 (SIOS Tech Lab) — そもそも MCP をやめて CLI + Skills に置き換えるという別の解き方。この記事を読んで、あらためて MCP で何を使っているかを洗い出す機会になりました (こちらの環境では Playwright はすでにスクリプト直実行で、残る 3 本はツール定義の遅延読み込みが効いているため MCP のまま継続)