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に並列で呼ばせると、片方または両方が Stream closed で失敗します。同じセッションに登録した stdio トランスポートのMCPサーバー(nx-mcp)は同条件で正常に動作しており、失敗するのはSDKトランスポート特有の現象です。
再現条件 — どんな構成で起きやすいか
issueのコメント欄には、実際に踏んだ開発者からの再現報告が積み重なっています。共通しているのは次の3点です。
- 2つ以上のツールが同時に実行中であること。1つずつ順番に呼ばれる場合は発生しません
- 入力(メッセージや引数)が大きいこと。コメント主の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からは「何も起きていない」ように見え、非活動タイマーが進み続けます。並列に呼んだツールのうち処理が長いものが1つでもあると、その待ち時間の間に他の完了済みツールの応答枠まで巻き込んでタイムアウトが発生し、「Stream closed」として複数の呼び出しが同時に失敗するというのがissue #114の説明です。issue #41へのqing-antのコメントも、ワークアラウンドとして共有されていた環境変数の効果がこの原因と一致することを指摘し、重複クローズの根拠としています。
この原因は、実際に配布されている sdk.mjs を見ても裏付けが取れます。@anthropic-ai/claude-agent-sdk v0.3.270のパッケージを展開すると、pendingMcpResponses を解決する処理は次のとおりで、issue #114が指摘した1行(resetLastActivityTime() に相当する呼び出し)は追加されていません。
sendMcpServerMessageToCli(e,t){if("id"in t&&t.id!==null&&t.id!==void 0){let r=`${e}:${t.id}`,o=this.pendingMcpResponses.get(r);if(o){o.resolve(t),this.pendingMcpResponses.delete(r);return}}...コミュニティが検証したパッチと効果
issue #114には、利用者のcsellis氏が実際に当てて検証したパッチが投稿されています。差分は1行だけです。
const pending = this.pendingMcpResponses.get(key);
if (pending) {
pending.resolve(message);
this.pendingMcpResponses.delete(key);
+ this.resetLastActivityTime();
return;
}このパッチを pnpm patch で当てて本番相当のリプレイ評価にかけた結果は次のとおりです。
| 指標 | パッチ前(タイムアウト120秒) | パッチ後(タイムアウト15秒) |
|---|---|---|
| 成功率 | パッチ前(タイムアウト120秒)5/5 | パッチ後(タイムアウト15秒)5/5 |
| 平均クエリ時間 | パッチ前(タイムアウト120秒)約80秒 | パッチ後(タイムアウト15秒)約35秒 |
| Stream closedエラー | パッチ前(タイムアウト120秒)0件(ただし120秒待ち続ける) | パッチ後(タイムアウト15秒)0件 |
さらに同時実行数を上げた検証でも、10並列・53回のツール呼び出し全件が成功し(53/53)、Stream closed エラーは0件でした。パッチが効いているのは非活動タイマーのリセット漏れが原因であることを裏付ける結果です。ただしこのパッチはコミュニティが pnpm patch で当てた検証であり、Anthropicの公式リリースに取り込まれた修正ではありません。
環境変数での回避策 — CLAUDE_CODE_STREAM_CLOSE_TIMEOUT
パッチを当てられない環境向けに、issueのコメント欄では非活動タイムアウトの秒数を伸ばす回避策が共有されています。ianbutler-xbowのコメントによると、既定値は60,000ミリ秒(60秒)で、これを長時間実行のツールに合わせて伸ばすと発生を抑えられます。
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関連のタイムアウト設定がありますが、いずれもこのバグの回避策にはなりません。公式ドキュメントでは次のように整理されています。
| 設定 | 対象 | このバグへの効果 |
|---|---|---|
MCP_TOOL_TIMEOUT | 対象MCPツール実行全体の上限時間(既定は約28時間) | このバグへの効果効かない。タイマーのリセット漏れとは無関係 |
CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT | 対象stdio・HTTP・SSE・WebSocketサーバーの無応答検知 | このバグへの効果SDKのインプロセスサーバーには適用されない(公式ドキュメントで明記) |
createSdkMcpServer({ timeout }) | 対象個別のSDKサーバーのツール呼び出し上限 | このバグへの効果上限を伸ばす設定であり、非活動タイマーのリセット漏れは解消しない |
CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT は一見このバグに効きそうな名前ですが、公式ドキュメントは「IDEサーバーやSDKインプロセスサーバーには適用されない」と明記しています。インプロセスのSDK MCPサーバーで Stream closed に当たった場合、対処できるのは前節の CLAUDE_CODE_STREAM_CLOSE_TIMEOUT か、コミュニティパッチの適用、スタンドアロンMCPサーバーへの切り替え、または並列呼び出しを避ける設計変更の4択になります。
猶予時間の延長と今回のバグは別問題
CHANGELOGを遡ると、SDKのMCPチャンネルクローズ猶予はv0.1.12で60秒に設定されています。これは当時報告されていた別issue(#15)への対応で、今回のissue #41・#114が指摘する「応答のたびに非活動タイマーをリセットする処理が無い」という根本原因とは別の対処です。今回のバグに対して、公式が新たにタイムアウトを調整した事実はありません。猶予を伸ばす対処は、同じ種類の間欠的な失敗を先送りするにとどまります。
createSdkMcpServer にはその後、v0.3.248でサーバー単位の timeout オプションが追加され、MCP_TOOL_TIMEOUT をサーバーごとに上書きできるようになりました。ただしこれはツール呼び出し1回あたりの実行時間上限を調整する機能で、非活動タイマーのリセット漏れという今回の原因には作用しません。タイムアウト系の設定を組み合わせるだけでは、根本原因の解消にはたどり着けない構造です。
Python SDKでも同種の報告がある
issue #114には、TypeScript版だけでなくPython版の claude-agent-sdk でも同じ症状が起きているという報告が寄せられています。SDK自体の実装言語は異なっても、内部で起動するCLIプロセスとの通信プロトコルは共通のため、同じ非活動タイマーの実装が影響していると考えられます。csellis氏が検証したパッチは sdk.mjs(TypeScript版のビルド成果物)を対象にしたもので、Python環境にそのまま適用することはできません。
よくあるつまずき
- stdioトランスポートに切り替えれば安全だと思い込む: 今回のバグはSDKトランスポート(
createSdkMcpServer)特有の現象で、stdioサーバーは影響を受けません。ただし処理をインプロセスで完結させたい設計だった場合、トランスポートの変更はサーバー実装自体の作り直しになります - 開発環境で再現しないから本番でも大丈夫だと判断する: エラーは間欠的で、同時実行数・ツールの処理時間・ユーザーメッセージの長さといった条件が揃ったときだけ表面化します。低頻度な手元検証で問題が出なくても、本番相当の同時実行数では発生条件を満たすことがあります
CLAUDE_CODE_STREAM_CLOSE_TIMEOUTを無制限に伸ばして放置する: 猶予を伸ばすほど、ツールが実際にハングしたときの検知も遅れます。エージェントを長時間自律的に走らせる構成では、値を伸ばし続ける対症療法よりコミュニティパッチの検証に進んだほうが安定します
使い分け早見表 — どの対処を選ぶか
| 対処 | 実装コスト | 効果 | 向く場面 |
|---|---|---|---|
CLAUDE_CODE_STREAM_CLOSE_TIMEOUT を伸ばす | 実装コスト低い(環境変数1行) | 効果発生頻度は下がるが根本解決ではない | 向く場面今すぐ動かしたい・ツールの最大処理時間がおおよそ見積もれる |
コミュニティパッチ(resetLastActivityTime() 追加) | 実装コスト中程度(pnpm patch 等でnode_modulesにパッチを当てる) | 効果実測で0件まで抑制 | 向く場面自社でCIやビルドパイプラインにパッチ適用の仕組みがある |
| スタンドアロンMCPサーバーに切り替える | 実装コスト高い(別プロセスとして実装し直す) | 効果stdioトランスポートはこのバグの対象外 | 向く場面並列呼び出しが多く、SDKインプロセスサーバーへの依存を下げたい |
| 並列呼び出しを避ける設計にする | 実装コスト低〜中(ツール呼び出しを直列化) | 効果発生条件そのものを避けられる | 向く場面ツール数が少なく、直列化してもレイテンシ影響が小さい |
createSdkMcpServer そのものの使い方や allowedTools の設定はAgent SDKカスタムツールの作り方にまとめています。並列実行を前提にした設計をしている場合は、このバグの存在を前提に上記4つの対処から選ぶことになります。
まとめ
Agent SDKの createSdkMcpServer で複数ツールを同時に呼ぶと、非活動タイマーが応答のたびにリセットされないバグ(issue #114)が原因で Stream closed エラーが間欠的に発生します。症状を報告したissue #41は2026年3月に#114の重複としてクローズされましたが、根本原因を追跡するissue #114自体はクローズされておらず、公開されている最新パッケージのコードにも修正は反映されていません。当面は CLAUDE_CODE_STREAM_CLOSE_TIMEOUT を伸ばすか、コミュニティが検証したパッチを当てるか、並列呼び出しを避ける設計にするかの選択になります。外部の標準MCPサーバーに繋ぐ構成に切り替える場合はAgent SDK MCP接続ガイドを、Agent SDKの基本的な使い方から確認したい場合はAgent SDKのサンプル集を参照してください。ツールの入力がJSONとして壊れて届く別種のエラーへの対処はツールストリーミングでinvalid jsonが来たときの直し方にまとめています。