MCP Stream closedエラーはAgent SDKの並列呼び出しで起きる
Agent SDKのcreateSdkMcpServerで複数ツールを同時に呼ぶとStream closedで失敗する既知バグの原因と、環境変数による回避策をGitHub issueの一次情報から解説します。
Agent SDKのMCPサーバーで何が起きるか
createSdkMcpServer で作ったインプロセスのMCPサーバーに対し、Claudeが2つ以上のツールを同時に呼び出すと、一部の呼び出しが Stream closed というエラーで失敗します。エラーは is_error: true の tool_result として返ります。この現象はGitHub issue #41として2025年10月23日に報告されました。
報告者の環境は @anthropic-ai/claude-agent-sdk v0.1.21です。PRの差分を返す pr_diff とタスク出力を返す task_outputs という2つの非同期ツールを持つSDKサーバーを登録し、Claudeに並列で呼ばせています。同じメッセージ内の mcp__utilities__pr_diff と mcp__utilities__task_outputs はどちらも Stream closed で返り、同じセッションの stdio サーバー(nx-mcp)のツールだけが結果を返しました。
再現条件 — どんな構成で起きやすいか
issueのコメント欄には、実際に踏んだ開発者からの再現報告が積み重なっています。共通しているのは次の3点です。
- 2つ以上のツールが同時に実行中であること。報告者の再現手順は、2つ以上のツールを並列で呼ばせる形です
- 入力(メッセージや引数)が大きいこと。コメント主の13point5は「ユーザーメッセージのトークン数が多いときに後続のツール呼び出しが失敗する」と報告しており、coryvirokは「ツールの引数が大きいほど起きやすい」と述べています
- エラーが間欠的であること。Cammisuliは「毎回ではないが無視できない頻度で起きた」として、
createSdkMcpServerをやめてスタンドアロンのMCPサーバーに切り替える運用回避を選んでいます
サブエージェント定義に同じツールを渡して並列に複数インスタンスを起動した場合にも同様の症状が出るとr3coreが報告しています。単一の query() 呼び出し内の並列ツールコールに限らず、サブエージェント経由で並列に起動する構成でも再現するという報告です。
根本原因 — 応答が来ても非活動タイマーがリセットされない
issue #41自体は2026年3月26日に、根本原因を突き止めた別issueの重複としてクローズされました。原因を特定したのはissue #114です。
SDKは waitForInactivity() という内部の非活動タイマーを持ち、一定時間「動きがない」とストリームを閉じます。ところが sdk.mjs の sendMcpServerMessageToCli() 内で、MCPサーバーからの応答を解決する処理には、このタイマーをリセットする呼び出しが入っていません。
// sdk.mjs:8158-8162(issue #114の指摘箇所)
const pending = this.pendingMcpResponses.get(key);
if (pending) {
pending.resolve(message); // 応答は正しく届く
this.pendingMcpResponses.delete(key);
return;
// resetLastActivityTime() の呼び出しが無い
}ツールが実際に動いていても、SDKからは「何も起きていない」ように見え、タイマーは進み続けます。issue #114はこれを、タイマーの設定値によって2通りに現れる症状として説明しています。
非活動タイマーの設定値で変わる症状(issue #114)
タイマーが短い(15秒)
15秒を超えて動くMCPツールの呼び出しで Stream closed が出ます。応答を待っている間にタイマーが満了し、ストリームが閉じられます。
タイマーが長い(60秒・120秒)
エラーは0件ですが、Claudeの処理が終わってからタイマーの設定値と同じ秒数だけ待たされます。報告者は、タイマーを30秒延ばすとクエリ時間も30秒延びる1対1の対応を確認しています。
issue #41へのコメントでは、ianbutler-xbowが「長く動く並列ツールがあると既定の60秒に達し、保留中のすべてのツール呼び出しのストリームが閉じる」と書いています。並列で複数のツールが同時に失敗する理由は、この説明が最も近いものです。ただし、タイマーが満了した瞬間にどの呼び出しが巻き込まれるかは、issueの記述だけでは決まりません。qing-antのクローズコメントは、このコメント欄で共有された CLAUDE_CODE_STREAM_CLOSE_TIMEOUT の回避策が #114と同じ原因を示す根拠だと述べています。
コミュニティが検証したパッチと効果
issue #114には、利用者のcsellis氏が実際に当てて検証したパッチが投稿されています。差分は1行です。
const pending = this.pendingMcpResponses.get(key);
if (pending) {
pending.resolve(message);
this.pendingMcpResponses.delete(key);
+ this.resetLastActivityTime();
return;
}対象は @anthropic-ai/claude-agent-sdk v0.1.70で、pnpm patch で当てて本番相当のリプレイ評価にかけています。5件の評価は5件とも成功し、パッチ後の所要時間は28.2〜40.6秒でした。パッチ前の120秒タイムアウトでは73.0〜94.4秒かかっています。
パッチ前後の計測(issue #114)
平均クエリ時間
約80秒 → 約35秒
タイムアウト120秒 → 15秒
Stream closed
0件
パッチ後は15秒設定でも発生なし
10並列の検証
53/53
全件成功
パッチが効いているのは、リセット漏れが原因だという見立てと整合する結果です。ただしこのパッチはコミュニティが検証したもので、公式リリースの変更履歴には #114の修正に当たる項目が載っていません。
環境変数での回避策 — CLAUDE_CODE_STREAM_CLOSE_TIMEOUT
パッチを当てられない環境向けに、非活動タイムアウトの秒数を伸ばす回避策が共有されています。ianbutler-xbowのコメントによると、既定値は60,000ミリ秒(60秒)です。issue #114の「Workaround」節は、設定値を最も遅いMCPツールより長くするよう書いています。ただし、すべてのクエリに無駄な待ち時間が加わる点も併記されています。
export CLAUDE_CODE_STREAM_CLOSE_TIMEOUT=300000この環境変数は code.claude.com/docs/en/env-vars の一覧に載っておらず、issueのコメント欄で共有された設定値です。公式ドキュメントに記載がない以上、将来のバージョンで挙動が変わる可能性は残ります。
限界も報告されています。paradite氏は、エージェントを自律的に長く走らせるほど必要な値が大きくなり、300,000から600,000、さらに1,200,000ミリ秒まで引き上げ続けたと述べています。同氏は「バグではなく仕様かもしれない」とも書いています。根本原因が「応答のたびにタイマーをリセットする」処理の欠落だとすると、値を伸ばすだけでは、必要な待ち時間が伸びるほど設定値も伸ばし続けることになります。
MCP_TOOL_TIMEOUTやアイドルタイムアウトとの違い
Agent SDKには他にもMCP関連のタイムアウト設定がありますが、いずれもこのバグの回避策としては報告されていません。環境変数の一覧とAgent SDKのMCPページでは、次のように説明されています。
| 設定 | 対象 | このバグへの効果 |
|---|---|---|
MCP_TOOL_TIMEOUT | 対象MCPツール実行全体の上限時間(既定は100000000ミリ秒、約28時間) | このバグへの効果回避策としては報告されていない。#114の原因はタイマーのリセット漏れで、この設定は実行時間の上限を決めるもの |
CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT | 対象stdio・HTTP・SSE・WebSocket・claude.aiコネクタの無応答検知 | このバグへの効果SDKのインプロセスサーバーには適用されない(環境変数の一覧に明記) |
createSdkMcpServer({ timeout }) | 対象個別のSDKサーバーのツール呼び出し上限 | このバグへの効果上限を設定する機能で、#114の回避策としては報告されていない |
CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT は一見このバグに効きそうな名前ですが、環境変数の一覧は「IDEサーバーやSDKインプロセスサーバーには適用されない」と明記しています。インプロセスのSDK MCPサーバーで Stream closed に当たった場合の選択肢は、前節の CLAUDE_CODE_STREAM_CLOSE_TIMEOUT、コミュニティパッチ、スタンドアロンMCPサーバーへの切り替え、並列呼び出しを避ける設計変更の4つです。外部のHTTP/SSEサーバーで5分ちょうどに切れる別の症状はAgent SDKのMCP_TOOL_TIMEOUTが5分で切れる報告にまとめています。
変更履歴で追うStream closedとタイムアウト
TypeScript版のCHANGELOGには、Stream closed やMCPのタイムアウトに触れた項目が複数あります。このバグとは別の原因もまじっているため、版ごとに並べると切り分けやすくなります。
CHANGELOGに載っている関連項目
- 0.1.12SDK MCPチャンネルの終了猶予を60秒に延長
issue #15への対応です。非活動タイマーのリセット漏れ(#114)に当たる修正ではありません。
- 0.1.28カスタムツールが30秒で打ち切られる問題を修正
MCP_TOOL_TIMEOUTが無視されていた問題の修正です(issue #42)。 - 0.1.73Stopフックが一貫して動かない問題を修正
Stream closedエラーが原因だったStopフックの不具合です。 - 0.3.248SDKサーバー単位のtimeoutオプションを追加
createSdkMcpServer({ timeout })で、そのサーバーのツール呼び出しだけMCP_TOOL_TIMEOUTを上書きできます。 - 0.3.284完了したバックグラウンドエージェントが起こす次ターンの失敗を修正
query()が先にstdinを閉じてしまい、次ターンのフック・canUseTool・SDK MCP呼び出しがStream closedで失敗する問題です。
v0.3.284より前のSDKでバックグラウンドエージェント完了後の次ターンに当たっている場合は、SDKを上げる選択肢があります。
Python SDKでも同種の報告がある
issue #114には、TypeScript版だけでなくPython版の claude-agent-sdk でも同じ問題が報告されています(gladsonorama氏のコメント、2026年2月9日)。同氏はPython SDK向けのパッチも求めています。csellis氏が検証したパッチは sdk.mjs(TypeScript版のビルド成果物)が対象なので、Python環境にはそのまま適用できません。
どの対処を選ぶか — 症状からの切り分け
手元の失敗がどの原因によるものかは、次の順に絞り込めます。
Stream closedの切り分け
- 1
失敗の直前にバックグラウンドエージェントが完了していないか見る
完了後の次ターンで、フックや
canUseToolも一緒にStream closedで失敗しているなら、v0.3.284で修正された経路です。SDKのバージョンを確認します。 - 2
失敗するのがSDKトランスポートのツールだけか見る
createSdkMcpServerのツールだけが失敗し、stdioのMCPサーバーが同じ条件で動いているなら、issue #41の再現と同じ形です。 - 3
最も遅いツールの実行時間と非活動タイマーの設定値を比べる
タイマーの設定値より長く動くツールがあるなら、#114の原因に当たります。次の表から対処を選びます。
| 対処 | 実装コスト | 効果 | 向く場面 |
|---|---|---|---|
CLAUDE_CODE_STREAM_CLOSE_TIMEOUT を伸ばす | 実装コスト低い(環境変数1行) | 効果発生は抑えられるが、全クエリに待ち時間が乗る | 向く場面今すぐ動かしたい・最も遅いツールの時間が見積もれる |
コミュニティパッチ(resetLastActivityTime() 追加) | 実装コスト中程度(pnpm patch 等でnode_modulesにパッチを当てる) | 効果報告では5件・53件とも0件 | 向く場面CIやビルドにパッチ適用の仕組みがある |
| スタンドアロンMCPサーバーに切り替える | 実装コスト高い(別プロセスとして実装し直す) | 効果issue #41の再現では、stdioのツールは成功している | 向く場面並列呼び出しが多く、インプロセスサーバーへの依存を下げたい |
| 並列呼び出しを避ける設計にする | 実装コスト低〜中(ツール呼び出しを直列化) | 効果同時実行という条件を外す | 向く場面ツール数が少なく、直列化してもレイテンシ影響が小さい |
createSdkMcpServer そのものの使い方や allowedTools の設定はAgent SDKカスタムツールの作り方にまとめています。
よくあるつまずき
- stdioに切り替えれば安全だと思い込む: issue #41の再現では
stdioのツールは成功していますが、これは報告者の環境での観察です。処理をインプロセスで完結させたい設計の場合、トランスポートの変更はサーバー実装の作り直しになります - 開発環境で再現しないから本番でも大丈夫だと判断する: エラーは間欠的です。リポジトリのコラボレーターであるdltnも、最初の報告の内容では手元で再現できないと返しています。同時実行数やツールの処理時間が揃う本番相当の負荷で初めて出ることがあります
CLAUDE_CODE_STREAM_CLOSE_TIMEOUTを伸ばし続けて放置する: 自律的に長く走らせるほど必要な値が増え、設定を引き上げ続けることになりがちです。paradite氏は300,000から1,200,000ミリ秒まで上げ直しており、値を上げた分だけ失敗の発覚も遅れます
まとめ
createSdkMcpServer のツールが Stream closed で失敗するときは、SDKの版、同時に動いているツールの数、最も遅いツールの実行時間の3点を見れば、原因を絞り込めます。issue #114の非活動タイマーが原因なら、設定値を最も遅いツールより長くする回避策か、コミュニティパッチが現実的な選択肢です。外部の標準MCPサーバーに繋ぐ構成に切り替える場合はAgent SDK MCP接続ガイドを、Agent SDKの基本的な使い方はAgent SDKのサンプル集を参照してください。ツールの入力がJSONとして壊れて届く別種のエラーへの対処はツールストリーミングでinvalid jsonが来たときの直し方にまとめています。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。