MCP serverが起動しているのにhostが接続できない、JSON parse errorになる、initializeで応答を読めないときは、まずstdioの役割を確認してください。stdio transportではstdinがrequest、stdoutがprotocol message、stderrが診断ログです。起動バナー、console.log、進捗表示など通常ログがstdoutへ一行でも出ると、hostはそれをJSON-RPC/MCP messageとして読み、接続前にparse errorになることがあります。ただし、すべての初期接続が現在もinitializeを使うとは限りません。実際のhostとprotocol versionが旧式のinitialize handshakeを使うのか、2026-07-28仕様のserver/discover flowを使うのかを確かめ、stdoutの破損とhandshake/version不一致を分けて直します。
目次
最初にtransportとprotocol versionを確認する
stdioはhostがserverを子processとして起動し、stdin/stdoutで双方向通信するtransportです。MCP specificationでは、clientがstdinへJSON-RPC messageを送り、serverがstdoutへresponseやnotificationを返します。一つのmessageはnewline-delimitedで、各行に一つのJSON-RPC messageが入ります。serverはstdoutへ有効なMCP message以外を書いてはいけません。stderrは情報・debug・error等のloggingに使えますが、clientが表示するか無視するかはhostによって異なります。
次に、clientとserverがどのprotocol versionで接続を試みているかを確認します。古い仕様や対応clientでは、通常initialize requestとinitialize responseのhandshakeが使われます。現行の2026-07-28 specificationでは、modern protocolのclientはserver/discoverを使ってversionを選択でき、legacy versionとの互換性を保つ場合はその結果に応じてinitializeへfallbackする手順が定義されています。したがって「initializeがログにないから必ず壊れている」とは断定できません。host、SDK、設定が想定するversionと、そのversionで期待される最初の交換を照合します。
まずstdoutとstderrを別々に捕まえる
起動失敗の最初の兆候は、画面に表示されるerror messageだけではなく、processの実出力にあります。hostと同じcommand、arguments、working directory、environmentでserverを起動し、stdinへ適切なprotocol requestを送ったときのstdoutとstderrを別ファイルまたは別streamとして記録します。secret、OAuth token、利用者データを採取ログに含めないでください。
確認する順番:
-
起動commandが合っているか。 command、引数、実行path、working directory、runtime version、必要なenvironment variableがhost設定と一致するか確認します。
-
processが起動して待機するか。 起動直後に終了・crashする場合は、stderrのstack traceやruntime errorを確認します。これはprotocol parse errorとは別の問題です。
-
stdoutの先頭行を見る。 先頭バイトからJSON-RPC messageの外形が始まっているか確認します。Starting server...、Listening...、debug log、package managerのbanner、依存moduleのconsole.logなど、人間向け文字列が一行でも混ざれば疑います。
-
stderrを別に確認する。 診断ログはstderrへ出し、stdoutにはMCP messageだけを残します。SDKがprotocol responseを出す前の初期化ログも調べます。
-
一行ごとに構文を確かめる。 JSONとしてparseできても、JSON-RPC/MCP messageの構造や期待するmethodと一致するとは限りません。単なるJSON、空行、途中で分割されたJSON、複数messageを一行に連結した出力も確認します。
-
host側の読み取り位置を確認する。 serverが正しいstdoutへ応答しているか、hostが別process・古いcommand・別の設定を起動していないか突き合わせます。
stdoutを壊す典型例
次のような出力は、運用ログとしては自然でも、stdio transportではprotocolを壊す可能性があります。
-
起動時の「MCP server started」をstdoutへprintする。
-
Pythonでprint()、Node.jsでconsole.log()をdebug用途に使う。
-
package managerやshell wrapperがbanner、warning、progressをstdoutへ出す。
-
依存libraryが標準出力へデバッグ内容を書く。
-
JSON-RPC応答を組み立てる前に環境・configをstdoutへdumpする。
-
serverの一部だけJSON-RPCを出し、別moduleが独自の行をstdoutへ出す。
対策はログを単に消すことではありません。通常ログはstderr、または使用SDKがサポートするprotocol loggingへ送り、stdoutの所有者をstdio transportに一本化します。エラーの調査に必要な診断はstderrに残し、hostのserver logで確認できるようにします。SDKがstdoutを内部管理する場合は、独自に同じstreamへ書き込まないでください。
stdoutとstderrの最小検査表
表は横にスクロールできます
| 観察結果 | 主な原因候補 | 次の確認 |
|---|---|---|
| stdout先頭が普通の文章 | 起動banner、print、依存libraryのlog | 書き込み元を検索しstderrまたはprotocol loggerへ移す |
| stdoutが空でprocessが終了 | command、runtime、env、module import時の例外 | exit codeとstderrを先に調べる |
| stdoutはJSONだがhostがreject | JSON-RPC構造、method、protocol version、改行framing | 期待messageと仕様versionを比較する |
| stderrにwarningがありstdoutは正常 | warning自体はprotocolを壊さないが原因調査に有用 | hostがstderrを記録しているか確認する |
| responseが正しいがhostがtimeout | stdin/stdoutのpipe、buffering、process lifetime、handshake期待差 | hostと同じ起動条件でInspectorまたは対応clientを使い再現する |
| initializeが見えない | hostがmodern server/discoverを使用、またはrequest前に失敗 | client/serverのprotocol versionと最初のrequestを確認する |
表の「原因候補」は診断の入口です。実際のstdout、stderr、exit code、request logを見ずに、すべてをconsole.logのせいにしないでください。
handshake・version問題をstdout汚染と分ける
旧式のinitialize handshakeを使う接続では、clientからのinitialize request、serverからのinitialize response、必要な後続通知など、versionごとに定義された手順を確認します。一方、2026-07-28のMCP仕様ではmodern clientがserver/discoverで対応versionを選ぶflowが記載され、legacy serverと話す場合のfallbackも定義されています。現在動かしているclient/serverが旧版、modern版、両方対応のどれかを先に特定します。
次の点を照合してください。
-
hostまたはSDKのMCP protocol version。
-
最初にstdinへ送るrequest methodと、そのmessageのJSON-RPC形式。
-
serverが返すmethod、id、version、response shape。
-
client/serverが共通して対応するprotocol version。
-
stdoutへ別の文字列が混ざる前に接続が切れていないか。
-
legacy fallbackを実装するclientが、仕様に沿った条件で切り替えているか。
たとえば、hostがserver/discoverを送るのにserverがlegacy initializeだけを想定していると、stdoutがきれいでも最初のmethodが一致しない可能性があります。逆に、protocol versionの取り違えに見えても、実際にはstdoutのbannerが先に届いてhost parserがrequest/responseを読めていない場合があります。両方を一度に変更せず、まずstreamの生出力を確認します。
起動commandとenvironmentの比較
開発shellでは動くのにhostでは起動しないときは、server本体の不具合とは限りません。hostはUIの設定にあるcommandを直接起動するため、対話shellのPATHやshell profileを引き継がないことがあります。host設定から、実行ファイル、引数、working directory、環境変数名を読み、同じ条件で再現します。tokenやsecret値は比較・報告へ出さず、必要なvariableの有無と権限だけを確認します。
ログには次のような安全な情報を残せます。
-
server version、runtime version、選択transport。
-
commandの実行ファイルと、secretを含まない引数。
-
process exit codeと起動から終了までの時間。
-
stdoutの混入行番号・文字種・bytes数。ただし認証情報を含む生内容はマスク。
-
stderrのerror classと該当stack位置。
-
使用protocol version、最初のmethod名、responseのmethod/idの一致。
-
Inspectorやhostで接続できたか、tools/list等の次段階へ進んだか。
修正後の再検証
修正を一度に何種類も混ぜず、原因を一つずつ変えて再実行します。
-
stdoutに人間向けlogがあるなら、その呼び出しをstderrへ移す。
-
serverをhostと同じcommand・environmentで再起動する。
-
stdoutに不要な文字列、空行、壊れたJSON、framing違反がないことを確認する。
-
利用するprotocol versionに応じた最初のrequest/responseを確認する。
-
MCP Inspector等、対象server commandを起動する公式の検査clientが利用できる環境で接続する。
-
handshakeまたはdiscover後にserver情報・capabilities・toolsの一覧を確認する。
-
最小のtool callを一度実行し、期待するresultが返るか確認する。
-
hostへ設定し直して同じ操作を行い、host固有のenvironment差がないか確認する。
Inspectorで成功してhostで失敗する場合は、host側のcommand、PATH、working directory、protocol version、environmentを比較します。両方で失敗する場合は、MCP specに対するstdout framingやserver実装を見ます。接続成功だけでtool resultの正しさまでPASSにせず、一覧と最小callを別に検証します。
secretを漏らさず診断する
起動ログを丸ごと共有する前に、Authorization header、API key、OAuth token、環境変数のsecret、利用者の入力・音声・ファイルpathなどを削除してください。stdout汚染調査では、問題の行を原文のまま公開する必要はありません。「JSON-RPC以外の先頭行あり」「行番号4、ASCII banner、42 bytes」など、構造と長さの情報だけで原因を追える場合があります。
stderrへ移すことは、ログを無制限に出してよいという意味ではありません。hostやSDKのログに機密値が含まれないか点検し、必要な情報だけを残します。MCPのprotocol loggingを使う場合は、clientがlogging capabilityを通知しているかなど、対応protocolの条件を確認します。
復旧判断フロー
-
processがすぐ終了する:stderr・exit code・command・runtime環境を確認する。
-
stdoutにprotocol外の行がある:出力元を特定し、stderrまたは対応するloggingへ移す。
-
stdoutが有効なmessageだけで、method/versionが違う:hostとserverの対応protocolと最初のrequest/responseをそろえる。
-
responseが返るがhostがtimeoutする:message framing、改行、process lifetime、stream接続先、hostの設定を確認する。
-
Inspectorは通るがhostでは失敗する:hostの起動環境、PATH、working directory、protocol versionを差分比較する。
-
原因を説明できない:安全にredactした最小ログと再現条件を揃えて、使用SDKまたはhostの公式issue/supportへ相談する。追加のstdout debug行を足して調査しない。
結論:stdoutはprotocolとして扱う
stdio MCP serverでは、stdoutはユーザー向けログではなく、JSON-RPC/MCP messageを運ぶwireです。起動文字列やdebug logをstdoutへ出すと、hostが最初の行からprotocol messageとしてparseし、接続を失うことがあります。まず生streamをstdoutとstderrに分けて確認し、process起動、stdout汚染、framing、protocol version/handshakeを順に切り分けます。旧式initializeか現行のserver/discoverかは使用client・serverのversionで決め、予想だけでhandshakeを書き換えないでください。
Sources
-
MCP Specification 2026-07-28 — stdio transport: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio(新しいタブで開く)
-
MCP TypeScript SDK — Serve over stdio: https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/serving/stdio.md(新しいタブで開く)
-
MCP TypeScript SDK — Troubleshooting: https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/troubleshooting.md(新しいタブで開く)
-
Sources checked: 2026-10-15.
