Claude Code向けMCPは、接続成功だけで検収するとログ混入を見逃し、導入後の切り分けに時間を取られる。
今日、起動ログを混ぜたら、エラーが出たのに足し算は通った。意外だったんだよね。
Q. JSONのログなら混ぜてもいい?
{"msg": "boot"} もエラーになった。
手元の公式Python SDKの stdout_reader を読んだ。改行で区切った各行を JSONRPCMessage.model_validate_json に渡している。普通のJSONとして読めても、JSON-RPCの必須フィールドが足りない。
Q. どう再現した?
確認環境はPython 3.12.13、mcp 1.27.1、anyio 4.13.0、pydantic 2.13.4。probe_stdio.py に保存し、同じ環境で python3 probe_stdio.py を実行する。ローカル子プロセスで完結する。
import json
import sys
import anyio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.server.fastmcp import FastMCP
def server(mode):
text = json.dumps({"msg": "boot"}) if mode == "json" else "boot"
dest = sys.stderr if mode == "stderr" else sys.stdout
print(text, file=dest, flush=True)
app = FastMCP("stdio-check", log_level="ERROR")
@app.tool()
def add(a: int, b: int) -> int:
return a + b
app.run(transport="stdio")
async def probe(mode):
errors = []
async def incoming(message):
if isinstance(message, Exception):
errors.append(type(message).__name__)
params = StdioServerParameters(
command=sys.executable, args=[__file__, mode])
async with stdio_client(params) as (read, write):
async with ClientSession(read, write, message_handler=incoming) as s:
await s.initialize()
result = await s.call_tool("add", {"a": 1, "b": 2})
assert not result.isError and result.content[0].text == "3"
print(f"{mode}: errors={errors}, add={result.content[0].text}")
async def main():
for mode in ("plain", "json", "stderr"):
await probe(mode)
if __name__ == "__main__":
if len(sys.argv) == 2:
server(sys.argv[1])
else:
anyio.run(main)
実行結果。stderrには2ケースで Failed to parse JSONRPC message from server と詳細が出た。
plain: errors=['ValidationError'], add=3
json: errors=['ValidationError'], add=3
stderr: errors=[], add=3
Q. なぜエラー後も通る?
SDKはパース例外を読み取り側へ送って次の行を読む。message_handler で数えるとstdoutの2ケースで各1件。後続の応答は届き、足し算も通った。
成功だけ拾うテストだと、この差が消える。
stderrの boot は残った。起動ログはやっぱり見たい。
Q. 検収に何を足す?
合格条件に errors == [] も加える。ラッパーの 2>&1 でstderrのログがstdoutへ戻る点にも注意。
今回はPython SDKのstdio経路を測った。Claude Code本体の接続可否は未確認だ。起動ログをstderrへ寄せ、正常応答とパース例外0件をセットで検収する。今後は起動ラッパーも検収に含めたい。
