MCPサーバーをstdioで実装したところ、ターミナルからは起動できるのに、Claude CodeやClaude Desktop、MCP Inspectorなどから接続するとエラーになることがあります。
特に見落としやすいのがstdoutへのログ出力です。
stdio版MCPでは、標準出力は単なるコンソール表示用ではありません。MCPクライアントへJSON-RPCメッセージを送信するための通信路そのものとして使用されます。
そのため、次のような何気ないコードでも問題になる可能性があります。
console.log("MCP Server started");
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サーバーが起動するのに接続できない原因と、stdoutとstderrの違い、ログ出力を安全に行う方法を解説します。パスや環境変数、ビルド設定などstdout以外の起動トラブル全般はMCPサーバーが起動しない原因で扱っているため、この記事ではstdout/stderrに絞って深掘りします。
- MCPのstdioとは
- stdoutは画面表示ではなくMCPの通信路
- Pythonのprint()もstdoutへ出力する
- JavaScriptならconsole.errorを使う
- stderrに出力したからサーバーエラーという意味ではない
- MCPのLogging機能とstderrは別
- import時のprint()にも注意する
- ラッパースクリプトのechoもstdoutを壊す
- subprocessから起動した別プログラムにも注意する
- ログをTool Resultとして返す必要はない
- MCP Python SDKではlog_levelも設定できる
- APIキーや個人情報をログへ出さない
- Tool Callのデバッグでは処理時間や成否も記録する
- ログファイルへ保存してもよい
- stderrに大量のDebug Logを出し続けない
- stdoutにログが出ているか確認する方法
- Python SDKがstdoutを保護してくれる場合でもprintは避ける
- バッファリングされたprintが終了時に問題になることもある
- stdinへ余計なデータを入れてもいけない
- 文字コードはUTF-8を使う
- JSON-RPCメッセージ内に勝手な改行を入れない
- サーバーを直接起動すると何も起こらないのは正常
- MCP Inspectorでstdioを直接確認する
- commandとargsの設定も確認する
- ターミナルでは動くのにHostでは動かない理由
- 環境変数がHostに渡っているか確認する
- 起動時例外を握りつぶさない
- Host側の設定変更後は完全再起動が必要な場合もある
- 2026-07-28対応ではstdioの起動方法も確認する
- stdio接続エラーではstderrを最初に見る
- stdoutへ何も出していないのに接続できない場合
- stdout汚染を見つけるならプロジェクト全体を検索する
- MCP stdioでは「stdoutを使わない」が最優先
- stdioのstdout/stderrに関するよくある質問
- まとめ
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の通信路
通常のコマンドラインアプリケーションでは、
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が概念的に次のようになる可能性があります。
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モジュールを利用します。
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のサーバー例でも、
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しているとします。
from tools.weather import get_weather
その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です。
#!/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が呼ばれた瞬間、
Processing... Finished.
のようなCLI出力がMCP Serverのstdoutへ流れ込む可能性があります。
外部Commandの結果が必要なら、stdoutをCaptureします。
result = subprocess.run(
["some-command"],
capture_output=True,
text=True
)
output = result.stdout
取得した内容を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を見たい場合は次のようにできます。
from mcp.server import MCPServer
mcp = MCPServer(
"ProductServer",
log_level="DEBUG"
)
Tool内部では、
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を利用できます。
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へ起動メッセージが大量に表示される必要はありません。
一方、
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("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を作って、
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
のようなPretty Print JSONをそのまま複数行でstdoutへ流す実装は避けます。
Wire上では1メッセージを1行として送ります。
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}
通常はMCP SDKが処理するため、自分でJSON-RPCを出力していないなら意識する必要はありません。
サーバーを直接起動すると何も起こらないのは正常
stdio MCP Serverをターミナルで起動すると、
python server.py
あるいは、
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の使い方で解説しています。
たとえば次のような方法で起動できます。
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などの違いです。
ターミナルでは、
python server.py
で動いていても、Host側では別のPython Runtimeを参照している可能性があります。
Node.jsでも同様に、
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())を利用する方式が案内されています。
たとえば新しい構成では、
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を設定していれば、
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なら、
print( sys.stdout stdout.write
を検索します。
Node.jsなら、
console.log( process.stdout.write(
などを探します。
さらにShell Scriptの、
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アプリの感覚で、
print("Server started")
や、
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が指定しているcommandとargs、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専用の通信路であるという前提を守ることが、安定した接続の基本になります。

