MCPのstdioサーバーが接続できない原因|stdoutにログを出してはいけない理由

MCPのstdioサーバーが接続できない原因|stdoutにログを出してはいけない理由 AI開発

MCPサーバーをstdioで実装したところ、ターミナルからは起動できるのに、Claude CodeやClaude Desktop、MCP Inspectorなどから接続するとエラーになることがあります。

特に見落としやすいのがstdoutへのログ出力です。

stdio版MCPでは、標準出力は単なるコンソール表示用ではありません。MCPクライアントへJSON-RPCメッセージを送信するための通信路そのものとして使用されます。

そのため、次のような何気ないコードでも問題になる可能性があります。

JavaScript
console.log("MCP Server started");

Pythonなら次のコードです。

Python
print("MCP Server started")

これらは通常stdoutへ文字列を書き込みます。

MCPクライアントはそこへJSON-RPCメッセージが流れてくることを期待しているため、普通のログが1行混ざるだけでProtocol Streamを正常に解析できなくなる可能性があります。

MCP仕様でも、stdio TransportではサーバーがstdinからJSON-RPCメッセージを読み、stdoutへJSON-RPCメッセージを出力し、有効なMCPメッセージ以外をstdoutへ書き込んではならないと定められています。ログにはstderrを利用できます。

この記事では、MCPのstdioサーバーが起動するのに接続できない原因と、stdoutstderrの違い、ログ出力を安全に行う方法を解説します。パスや環境変数、ビルド設定などstdout以外の起動トラブル全般はMCPサーバーが起動しない原因で扱っているため、この記事ではstdout/stderrに絞って深掘りします。

スポンサーリンク
  1. MCPのstdioとは
  2. stdoutは画面表示ではなくMCPの通信路
  3. Pythonのprint()もstdoutへ出力する
  4. JavaScriptならconsole.errorを使う
  5. stderrに出力したからサーバーエラーという意味ではない
  6. MCPのLogging機能とstderrは別
  7. import時のprint()にも注意する
  8. ラッパースクリプトのechoもstdoutを壊す
  9. subprocessから起動した別プログラムにも注意する
  10. ログをTool Resultとして返す必要はない
  11. MCP Python SDKではlog_levelも設定できる
  12. APIキーや個人情報をログへ出さない
  13. Tool Callのデバッグでは処理時間や成否も記録する
  14. ログファイルへ保存してもよい
  15. stderrに大量のDebug Logを出し続けない
  16. stdoutにログが出ているか確認する方法
  17. Python SDKがstdoutを保護してくれる場合でもprintは避ける
  18. バッファリングされたprintが終了時に問題になることもある
  19. stdinへ余計なデータを入れてもいけない
  20. 文字コードはUTF-8を使う
  21. JSON-RPCメッセージ内に勝手な改行を入れない
  22. サーバーを直接起動すると何も起こらないのは正常
  23. MCP Inspectorでstdioを直接確認する
  24. commandとargsの設定も確認する
  25. ターミナルでは動くのにHostでは動かない理由
  26. 環境変数がHostに渡っているか確認する
  27. 起動時例外を握りつぶさない
  28. Host側の設定変更後は完全再起動が必要な場合もある
  29. 2026-07-28対応ではstdioの起動方法も確認する
  30. stdio接続エラーではstderrを最初に見る
  31. stdoutへ何も出していないのに接続できない場合
  32. stdout汚染を見つけるならプロジェクト全体を検索する
  33. MCP stdioでは「stdoutを使わない」が最優先
  34. stdioのstdout/stderrに関するよくある質問
  35. まとめ

MCPのstdioとは

MCPではクライアントとサーバーを接続するTransportとして、stdioやStreamable HTTPを利用できます。

stdioでは、MCPクライアントがサーバープログラムを子プロセスとして起動します。

通信には標準入力と標準出力を使います。

概念的には次のような関係です。

通信の流れ
MCP Client
    ↓ stdin
MCP Server
    ↓ stdout
MCP Client

クライアントからサーバーへのJSON-RPC Requestはサーバーのstdinへ送られます。

サーバーからクライアントへのJSON-RPC Responseはstdoutへ送られます。

MCP仕様ではstdioのメッセージをUTF-8でエンコードし、改行で区切って送受信します。サーバーはstderrへUTF-8文字列をログとして出力できますが、stdoutにはMCPメッセージ以外を書いてはいけません。

通常のCLIアプリとは、stdoutの意味が大きく違います。

stdoutは画面表示ではなくMCPの通信路

通常のコマンドラインアプリケーションでは、

普通のCLI
console.log("Started");

と書くことに大きな問題はありません。

しかしstdio MCP Serverの場合、console.log()によって書き込まれた文字列は、MCP Clientから見るとProtocol Streamの一部です。

本来なら次のようなJSON-RPCメッセージが流れる場所です。

本来のメッセージ
{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}

ところが起動時に、

汚染する例
console.log("MCP Server started");

を書いていると、実際のstdoutが概念的に次のようになる可能性があります。

汚染されたstdout
MCP Server started
{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}

クライアントからすると最初の行、

余計な行
MCP Server started

はJSON-RPCメッセージではありません。

そのためJSON Parserで処理できず、接続を切断したり、サーバーを無効として扱ったりする可能性があります。

MCP TypeScript SDKのドキュメントでも、stdioではstdoutがProtocol Channelであるため、ログにはconsole.error()を利用するよう明確に案内されています。

Pythonのprint()もstdoutへ出力する

Pythonでも同じ問題があります。

次のコードは避けます。

避ける例
print("Server started")

print()は通常sys.stdoutへ出力します。

stdioサーバーではstdoutをMCPが使用しているため、ログにはPython標準のloggingモジュールを利用します。

loggingを使う
import logging

logger = logging.getLogger(__name__)

logger.info("Server started")

Pythonの標準loggingは通常stderrへ出力するため、MCPのProtocol Streamを汚しません。

MCP Python SDKでも、stdio Serverでは「stdoutはProtocolのもの」として扱われ、print()ではなくlogger.debug()logger.info()を使うことが推奨されています。

JavaScriptならconsole.errorを使う

Node.jsやTypeScriptでstdio MCP Serverを実装している場合は、デバッグメッセージをconsole.log()へ出さないようにします。

避けたいコードは次のようなものです。

避ける例
console.log("MCP server started");

ログを表示したいなら、

修正例
console.error("MCP server started");

へ変更します。

console.error()は通常stderrへ出力されます。

MCP TypeScript SDKのサーバー例でも、

SDKの例
void serveStdio(createServer);
console.error("weather MCP server running on stdio");

という形で起動メッセージをstderrへ出しています。

「エラーではないのにconsole.error()を使ってよいのか」と感じるかもしれません。

ここで重要なのはログレベルではなく出力先です。

stdio MCP Serverではstderrが運用ログ用の経路として利用できます。

stderrに出力したからサーバーエラーという意味ではない

stderrという名前から、

誤解
エラーだけを出力する場所

と思われることがあります。

しかしstdio MCP Serverでは、通常のデバッグ情報や起動情報もstderrへ出して問題ありません。

MCP仕様でも、Serverは情報、Debug、Errorを含むLogging用途でstderrを使用でき、Clientはその出力を取得、転送、無視できます。またClientはstderrへ何か出ているだけでServer Errorだと判断すべきではないとされています。

そのため、

運用ログの例
Server started

Tool registered

Request received

Database connected

といった運用ログをstderrへ送っても構いません。

MCPのLogging機能とstderrは別

MCPにはProtocolとしてのLogging機能も存在します。

Serverがlogging Capabilityを宣言すると、構造化されたLog MessageをMCP Clientへ通知できます。MCPのLogging仕様では、debug、info、warning、errorなどのSeverityを持つログをProtocol経由で送信できます。

ただし、これは開発者向けにstderrへログを書くこととは別の仕組みです。

サーバー開発中の、

内部デバッグ
関数に到達した

環境変数を読み込んだ

DB接続を開始した

といった内部デバッグには通常のloggingやstderrで十分です。

一方、MCP Clientへ構造化されたログ通知として見せたい場合にはMCP Logging機能を利用します。

import時のprint()にも注意する

stdioのstdout汚染で厄介なのが、MCP Server本体にはprint()を書いていないのに接続できないケースです。

たとえば別ファイルをImportしているとします。

server.py
from tools.weather import get_weather

そのweather.pyに、

weather.py
print("weather module loaded")

が入っていれば、Importされた瞬間にstdoutへ文字列が出ます。

つまりServer本体だけ、

不十分な修正
logger.info("Server started")

へ直しても不十分です。

stdio Serverで接続できない場合はプロジェクト全体から、

検索キーワード
print(
console.log(
process.stdout.write(
sys.stdout.write(

などを検索すると原因を発見できることがあります。

Import時のprint()などがstdio Protocol Streamへ混入すると、1行の余計な出力だけでHostが接続を切る場合があります。

ラッパースクリプトのechoもstdoutを壊す

MCP Serverのコードに問題がなくても、Serverを起動するShell ScriptやBatch Fileがstdoutへ文字列を出している場合があります。

たとえば次のShell Scriptです。

start.sh
#!/bin/bash
echo "Starting MCP server..."
python server.py

この、

汚染する行
echo "Starting MCP server..."

もstdoutへ出ます。

MCPクライアントがこのScriptをServer Commandとして起動しているなら、ServerがJSON-RPCを返す前に普通の文字列がProtocol Streamへ送られます。

ログを残したいなら、Unix系ではstderrへリダイレクトします。

修正例
echo "Starting MCP server..." >&2
python server.py

stdoutへechoするWrapper Scriptは、stdio接続を壊す代表的な原因のひとつです。

subprocessから起動した別プログラムにも注意する

MCP Tool内部で別のCLIを実行する場合にも注意が必要です。

たとえばPythonから、

危険な呼び出し
subprocess.run(["some-command"])

を実行すると、その子プロセスのstdoutが現在のProcessへ継承される構成があります。

するとToolが呼ばれた瞬間、

CLI出力
Processing...

Finished.

のようなCLI出力がMCP Serverのstdoutへ流れ込む可能性があります。

外部Commandの結果が必要なら、stdoutをCaptureします。

安全な呼び出し
result = subprocess.run(
    ["some-command"],
    capture_output=True,
    text=True
)

output = result.stdout

取得した内容をTool Resultとして明示的に返します。

Tool Result
return output

「Toolの戻り値としてAIへ渡す情報」と「Processのstdoutへ直接出る情報」を混同しないことが重要です。

ログをTool Resultとして返す必要はない

Tool内部で処理経過をモデルへ見せたいわけではないなら、Log MessageをReturn Valueへ混ぜる必要もありません。

たとえば、

正しい使い分け
logger.info("Searching database")

result = search_database(query)

logger.info("Search completed")

return result

とします。

logger.info()はServer運用者向けです。

モデルにはresultだけが返ります。

通常のLoggingはモデルには見えず、モデルへ情報を渡したい場合はToolからreturnする必要があります。

MCP Python SDKではlog_levelも設定できる

MCP Python SDKでは、MCPServer作成時にLog Levelを設定できます。

たとえば開発中だけ詳細なDebug Logを見たい場合は次のようにできます。

log_levelを指定
from mcp.server import MCPServer

mcp = MCPServer(
    "ProductServer",
    log_level="DEBUG"
)

Tool内部では、

Debug Log
logger.debug("query=%s", query)

と書けます。

本番環境ではlog_level="INFO"などに変更できます。

現在のMCP Python SDKではMCPServer作成時にstderr向けのLogging Handlerが自動的に設定され、デフォルトのLog LevelはINFOです。自分で先にLoggingを設定している場合は、その設定が優先されます。

APIキーや個人情報をログへ出さない

stderrなら何でも出してよいわけではありません。

MCPサーバーのToolにはAPI Key、Access Token、メールアドレス、ファイル内容などが渡される可能性があります。

たとえば次のようなログは避けるべきです。

危険なログ
logger.debug("API_KEY=%s", api_key)

Tool Argumentsを丸ごと出力する方法も注意が必要です。

危険なログ
logger.debug("arguments=%r", arguments)

argumentsにPasswordやTokenが入っていれば、そのままLogへ保存されます。

代わりに必要な項目だけ記録します。

安全なログ
logger.info("search_products called query_length=%d", len(query))

Request IDやTool名、処理時間、成功・失敗など、トラブルシューティングに必要な情報を中心に残すと安全です。

Tool Callのデバッグでは処理時間や成否も記録する

Tool Callingを調査する場合は、Tool名と処理結果を追跡できるようにしておくと便利です。

処理時間と成否を記録
import logging
import time

logger = logging.getLogger(__name__)


@mcp.tool()
def search_products(query: str) -> str:
    started = time.perf_counter()
    logger.info("tool_started tool=search_products")

    try:
        result = search_database(query)
        elapsed = (time.perf_counter() - started) * 1000
        logger.info("tool_completed tool=search_products duration_ms=%.1f", elapsed)
        return result
    except Exception:
        logger.exception("tool_failed tool=search_products")
        raise

この形式なら、Toolが呼ばれたかだけでなく、正常終了したか、処理にどれくらい時間がかかったか、どこで例外になったかを調べられます。

Tool Argumentsそのものを大量に保存しなくても、かなりのトラブルシューティングが可能です。

例外発生時は、単にlogger.error("Tool failed")とするだけではStack Traceが分かりません。上記のようにlogger.exception(...)を使えば、現在処理中の例外情報とStack Traceを記録できます。このログもstdioではstderrへ出るため、MCP Protocolには混ざりません。

ログファイルへ保存してもよい

stdioでstderrを使うのは、「すべてのログを必ずターミナルへ表示しなければいけない」という意味ではありません。

Python LoggingならFile Handlerを利用できます。

File Handler
import logging

logger = logging.getLogger(__name__)

handler = logging.FileHandler("mcp-server.log", encoding="utf-8")
logger.addHandler(handler)

これならログをFileへ保存できます。

ただしログファイルが際限なく大きくならないよう、実運用ではRotatingFileHandlerなどを利用することも検討します。

重要なのは、MCP ProtocolとApplication Loggingを同じstdoutへ流さないことです。ログの保存先はstderr、Log File、Logging Serviceなどから運用環境に合わせて選べます。

stderrに大量のDebug Logを出し続けない

stderrならMCP通信を壊さないとはいえ、何でも大量に出力すればよいわけではありません。

Toolが大量の検索結果を処理するときに、

避けたい例
for item in results:
    logger.debug(item)

のように全データを出すと、Log Fileが急激に増える可能性があります。

またHostがstderrをCaptureしている場合、過剰なログはHost側のStorageやPerformanceにも影響します。

必要な情報へ絞ります。

必要な情報に絞る
logger.debug("search completed count=%d", len(results))

とすれば、1000件の商品データ全体を保存する必要はありません。

ログは「後から問題を再現・特定するために必要な情報」を中心にすると運用しやすくなります。

stdoutにログが出ているか確認する方法

stdio Serverが怪しい場合は、単独で起動してstdoutとstderrを分けて確認すると原因を特定しやすくなります。

Unix系なら概念的には、

分離して確認
python server.py >stdout.log 2>stderr.log

とできます。

正常なstdio Serverを単独起動した状態では、ClientからRequestが届いていないので、通常stdoutへ起動メッセージが大量に表示される必要はありません。

一方、

stdout.log
Server started

Loading tools...

Connected database

といったログが入っているなら要注意です。

これらはstderrへ移します。

Python SDKがstdoutを保護してくれる場合でもprintは避ける

現在のMCP Python SDKには、Server提供中にflushされた余計なstdout出力をstderrへ退避させる保護処理があります。

ただし、これは完全な対策ではありません。

Serving開始前(Import段階やWrapper Scriptからの出力)にstdoutへ出たもの、interpreter終了時にBufferがflushされて出てくるprint()などは、この保護の対象外でProtocol Streamへ混入する可能性があります。

そのため、

誰でもの思い込み
SDKが自動で守ってくれるからprint()を使ってよい

とは考えないほうが安全です。

最初からLoggingをstderrへ送る設計にします。

バッファリングされたprintが終了時に問題になることもある

Pythonの標準出力は、実行環境によってBufferingされる場合があります。

そのため、

バッファされるprint
print("debug")

を書いても直ちにProtocol Streamへ出ないことがあります。

一見するとServerは正常に動いているように見えます。

しかしProcess終了時にBufferがFlushされ、

遅延して出る出力
debug

がstdoutへ書き出される場合があります。

このため、

分かりにくい症状
起動時は正常

しばらくすると切断される

Server終了時だけエラーが出る

といった分かりにくい症状になる可能性もあります。

このBufferingされたprint()がInterpreter終了時にProtocol Streamへ出てしまう可能性には注意してください。

stdinへ余計なデータを入れてもいけない

stdoutだけでなく、Client側にも同様のルールがあります。

MCP仕様では、stdio Serverのstdinへは有効なMCP Message以外を書いてはいけません。

独自MCP Clientを実装している場合に、

余計な文字列
connecting...

などのDebug文字列までServerのstdinへ書き込むと、Serverから見ると壊れたJSON-RPCメッセージになります。

Client側のLoggingもProtocol Streamとは分離してください。

文字コードはUTF-8を使う

stdio MCPではJSON-RPCメッセージをUTF-8で扱う必要があります。

MCP Transport仕様でもJSON-RPC MessageはUTF-8 Encodedであることが要求されています。

特にWindowsで独自Wrapperや古いCLIを利用している場合、標準出力がUTF-8以外になっていないか確認する価値があります。

日本語を含むログやTool Resultを扱ったときだけ接続がおかしくなる場合は、Encoding周辺も調査対象になります。

ただし公式SDKを標準的な方法で使っている場合はSDK側で処理されるため、独自にstdin/stdoutを操作している場合ほど重要な項目です。

JSON-RPCメッセージ内に勝手な改行を入れない

stdioのMCP Messageは改行によって区切られます。

仕様ではメッセージは個別のJSON-RPC Request、Notification、Responseとして扱われ、改行で区切られ、メッセージ内に埋め込み改行を含めてはいけません。

そのため独自Transportを作って、

Pretty Print
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

のようなPretty Print JSONをそのまま複数行でstdoutへ流す実装は避けます。

Wire上では1メッセージを1行として送ります。

1行にまとめる
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}

通常はMCP SDKが処理するため、自分でJSON-RPCを出力していないなら意識する必要はありません。

サーバーを直接起動すると何も起こらないのは正常

stdio MCP Serverをターミナルで起動すると、

Python
python server.py

あるいは、

TypeScript
npx tsx src/index.ts

の後に何も表示されず、そのままProcessが止まっているように見えることがあります。

これは必ずしも異常ではありません。

stdio ServerはClientからstdinへJSON-RPC Messageが届くのを待っています。

stdio Serverを直接起動すると、ServerはstdinからClientが会話を開始するのを待つため、それ以上何も起こらない状態が正常です。

「起動したか分からないから」と、

危険な対応
console.log("started");

を追加すると、かえってstdioを壊す原因になります。

起動確認を出すならstderrを利用します。

MCP Inspectorでstdioを直接確認する

Host固有の問題かServer自体の問題か判断しにくい場合は、MCP Inspectorからstdio Serverを直接起動すると切り分けやすくなります。具体的な操作方法はMCP Inspectorの使い方で解説しています。

たとえば次のような方法で起動できます。

Inspector起動
npx @modelcontextprotocol/inspector npx tsx src/index.ts

InspectorがServer Commandを起動し、stdio経由で接続します。

Inspectorでも接続できないならServerや起動環境を優先して調べます。

Inspectorでは正常なのに特定Hostでだけ失敗するなら、そのHostのMCP設定や実行環境へ範囲を絞れます。

commandとargsの設定も確認する

stdoutに問題がなくても、MCP HostからServer Processそのものを正常に起動できなければ接続できません。

たとえば設定が、

相対パスの例
{
  "command": "python",
  "args": ["server.py"]
}

となっていても、HostのWorking Directoryが開発時のTerminalとは違えばserver.pyを見つけられない可能性があります。

そのためstdio MCPではScriptのAbsolute Pathを指定すると安定しやすくなります。

絶対パスの例
{
  "command": "python",
  "args": ["/path/to/project/server.py"]
}

uv自体をHostが見つけられない場合には、uvのAbsolute Pathが必要になることもあります。PATHの通し方や環境変数の渡し方など、command/args設定まわりの原因はMCPサーバーが起動しない原因でさらに詳しく解説しています。

ターミナルでは動くのにHostでは動かない理由

stdio Serverで非常に多いのが、

よくあるパターン
自分のターミナルでは動く

Hostから起動すると失敗する

というケースです。

理由として考えられるのは、実行ユーザーやPATH、Working Directory、Environment Variableなどの違いです。

ターミナルでは、

Terminal
python server.py

で動いていても、Host側では別のPython Runtimeを参照している可能性があります。

Node.jsでも同様に、

PATHの例
node

npx

tsx

のPATHがHost Processから見えているとは限りません。

stdio ServerはHost自身がSubprocessとして起動するため、普段使っているShellと同じ環境だと仮定しないことが重要です。

環境変数がHostに渡っているか確認する

MCP Server起動にAPI KeyなどのEnvironment Variableが必要な場合があります。

たとえば、

環境変数依存
api_key = os.environ["WEATHER_API_KEY"]

となっている場合、HostからWEATHER_API_KEYが見えなければServerは起動直後に終了します。

TerminalにはEnvironment Variableが設定されているため動く一方、GUIアプリから起動すると存在しないというケースがあります。

この場合、stdoutには何も出ていなくてもServer ProcessがCrashして接続できません。

Host側のMCP設定でEnvironment Variableを指定できるなら、そこへ設定します。

Server起動失敗の詳細はstdoutではなくstderrへ出るようにしておけば原因を確認しやすくなります。

起動時例外を握りつぶさない

Server起動時に例外が発生しても、

握りつぶす例
try:
    start_server()
except Exception:
    pass

のように握りつぶしていると、Hostから見ると単にServerが終了したようにしか見えません。

少なくともstderrへExceptionを残します。

修正例
import logging

logger = logging.getLogger(__name__)

try:
    start_server()
except Exception:
    logger.exception("MCP server failed to start")
    raise

これならProtocol Streamを汚さず、起動エラーだけを確認できます。

Host側の設定変更後は完全再起動が必要な場合もある

MCP Server設定を変更したのに古いCommandで起動され続ける場合もあります。

HostによってはMCP設定を起動時に読み込むため、設定ファイルを書き換えただけでは反映されないことがあります。Claude Desktopについては、ウィンドウを閉じるだけではなく完全終了して再起動する必要があります。

stdoutを修正したのに同じエラーが続く場合は、古いServer ProcessやHost Processが残っていないかも確認してください。

2026-07-28対応ではstdioの起動方法も確認する

2026年7月28日版MCP Protocolへ対応する場合は、利用しているSDKのstdio Entry Pointにも注意が必要です。

現在のTypeScript SDK v2では、StdioServerTransportへ直接接続する従来構成では2025世代のProtocolとして動作し、2026-07-28世代または新旧両方へ対応するstdio ServerではserveStdio(() => buildServer())を利用する方式が案内されています。

たとえば新しい構成では、

serveStdioの例
import { serveStdio } from "@modelcontextprotocol/server/stdio";

void serveStdio(() => buildServer());

のようにします。

接続の開始時にどちらのProtocol Revisionで通信するかが決まり、serveStdioはConnectionごとに1つのServer Instanceを固定して使う設計になっています。

SDKだけ更新したのに古い起動コードをそのまま使っている場合は、意図しているProtocol Revisionで通信しているか確認してください。

ただしProtocol Revisionが変わっても、stdoutをMCP Protocol専用にするというstdioの基本ルールは変わりません。最新の仕様でも、stdoutには有効なMCP Message以外を書いてはいけないとされています。

stdio接続エラーではstderrを最初に見る

stdio MCP Serverをデバッグするときは、Server Processのstderrが重要です。

正常にLoggingを設定していれば、

stderrに出る例
Tool registration failed

Missing API key

ModuleNotFoundError

Database connection failed

といったServer側の問題がstderrへ出ます。

Claude Desktopの場合、Serverごとのstderrログをmcp-server-<NAME>.logとして確認でき、接続関連のmcp.logも用意されています。Windowsでは%APPDATA%\Claude\logs、macOSでは~/Library/Logs/Claude以下です。

Hostがログ機能を提供している場合は、まずそこを確認すると原因を探しやすくなります。

stdoutへ何も出していないのに接続できない場合

stdoutを完全に整理しても接続できないなら、Server Process自体の起動条件を確認します。

特に、

確認項目
commandのPATH

argsのファイルパス

Working Directory

Environment Variable

依存パッケージ

PythonやNode.jsのRuntime

起動直後の例外

Protocol Revision

に相当する部分が重要です。これらはstdout以外のstdio起動トラブルの原因です。

また、Serverが起動した直後に終了していないかも確認します。

stdio ServerはHostによって管理されるSubprocessなので、Processが終了すれば当然Protocol Connectionも切断されます。

TerminalからServerを実行したときに、

正常な状態
stdinを待って何も表示されない

なら正常な可能性があります。

一方、実行した直後にShell Promptへ戻ってしまうなら、Server Processが終了しています。

stdout汚染を見つけるならプロジェクト全体を検索する

MCP stdio Serverの接続障害を調べるときは、Server Entry Pointだけを見ると原因を見逃す可能性があります。

Pythonなら、

Pythonで検索
print(

sys.stdout

stdout.write

を検索します。

Node.jsなら、

Node.jsで検索
console.log(

process.stdout.write(

などを探します。

さらにShell Scriptの、

Shellで検索
echo

printf

や、起動時に実行する外部CLIにも注意が必要です。

ログとして必要なものはstderrへ移します。

Pythonならlogging、Node.jsならLogging Libraryまたはconsole.error()を利用すれば、Protocol Streamと運用ログを分離できます。

MCP stdioでは「stdoutを使わない」が最優先

MCPのstdioサーバーが起動するのに接続できない場合、最初に確認したいのがstdoutです。

stdioではServerがstdinからJSON-RPC Messageを受け取り、stdoutからJSON-RPC Messageを返します。

そのため通常のCLIアプリの感覚で、

Python
print("Server started")

や、

JavaScript
console.log("Server started")

を書くと、MCPの通信路へ通常文字列が混ざる可能性があります。

MCP仕様でも、stdio Serverは有効なMCP Message以外をstdoutへ書き込んではならず、Loggingにはstderrを利用できることが定められています。

Pythonならlogging、Node.jsやTypeScriptならconsole.error()などを利用して、ログをstderrへ分離してください。

Server本体だけでなく、ImportしたModule、Wrapper Script、Subprocessからstdoutへ余計な出力が流れていないかも確認する必要があります。わずか1行のstdout汚染によってHostが接続を切る場合があります。

stdoutに問題がなければ、次にHostが指定しているcommandargs、Absolute Path、Environment Variable、起動時例外、Protocol Versionを確認します。

MCPのstdio Serverでは、stdoutはデバッグ用のコンソールではなくProtocol専用の通信路です。

この前提を守り、ログをstderrへ完全に分離することが、stdio MCP Serverを安定して接続するための基本になります。

stdioのstdout/stderrに関するよくある質問

Qなぜstdoutへログを出すと接続できなくなるのですか

AstdioのMCPでは、標準出力そのものがJSON-RPCメッセージの通信路として使われているためです。console.log()やprint()で通常のログを混ぜると、クライアント側のJSON Parserが解析に失敗し、接続を切断したりサーバーを無効として扱ったりする可能性があります。

Qstderrに出力してもエラー扱いになりませんか

Aなりません。stderrは情報・デバッグ・エラーを含む一般的なロギング用途に使える経路で、クライアントはstderrへ何か出力されているだけでサーバーエラーだと判断すべきではないとされています。起動メッセージや進捗ログもstderrで問題ありません。

QMCP Python SDKがstdoutを守ってくれるなら、print()を使っても大丈夫ですか

A安全ではありません。SDKはサーバー提供中にflushされた迷子のstdout出力をstderrへ退避させますが、サービス開始前のimport時の出力や、Interpreter終了時にバッファがflushされて出てくるprint()はこの保護の対象外です。最初からloggingを使うほうが安全です。

QServer本体にprint()やconsole.log()がないのに接続できません。何を確認すればよいですか

Aimportしている別モジュールやライブラリ、Serverを起動するWrapper Script(echoなど)、subprocessで実行している外部コマンドの標準出力を確認してください。これらの出力もServerのstdoutへ流れ込み、Protocol Streamを汚す原因になります。

QMCPのLogging機能を使えばstderrへのログは不要になりますか

A別の仕組みなので不要にはなりません。MCPのLogging機能はCapabilityを宣言してクライアントへ構造化されたログ通知を送るためのものです。開発者向けの通常のデバッグ出力は、引き続きstderrやloggingモジュールを使います。

Qターミナルで起動しても何も表示されないのは異常ですか

A異常とは限りません。stdio ServerはクライアントからのJSON-RPCメッセージをstdin経由で待っているだけなので、直接起動した状態で何も表示されずプロンプトに戻らないのは正常な状態です。起動確認のためにconsole.log()などをstdoutへ追加すると、かえって接続を壊す原因になります。

Q2026-07-28対応のSDKでもstdoutのルールは変わりますか

AstdoutをProtocol専用にするという基本ルール自体は変わりません。ただしTypeScript SDK v2ではstdioの起動方法がserveStdio(() => buildServer())という新しい形に変わっているため、古いSDKの起動コードをそのまま使っていないかは確認してください。

まとめ

MCPのstdioサーバーが起動するのに接続できない場合、最初に確認したいのがstdoutです。

stdioではstdoutがJSON-RPCメッセージそのものの通信路であり、console.log()print()による何気ないログ出力がProtocol Streamを壊す代表的な原因になります。

Server本体だけでなく、Importしたモジュール、Wrapper Script、subprocessの出力にも注意し、ログはstderrやloggingモジュールへ分離してください。

stderrはエラー専用ではなく、情報やデバッグを含む一般的なロギング用途に使える点も覚えておくと安心です。

stdoutを整理しても解決しない場合は、command/argsのパス設定、環境変数、起動時例外、Protocol Versionなど、stdout以外の起動要因を確認してください。こうした原因はMCPサーバーが起動しない原因で個別に解説しています。

MCPのstdio Serverでは、stdoutはデバッグ用のコンソールではなくProtocol専用の通信路であるという前提を守ることが、安定した接続の基本になります。