fix(mcp-server): route diagnostics to stderr to fix stdio protocol crash - #2371
Merged
Conversation
Entry scripts (run_server.py, main.py, run.py) printed diagnostics to
stdout via bare print(). Under the stdio transport (the default and the
path used by all MCP clients per MCP_CONFIG.md: `uv run run_server.py`),
stdout is the JSON-RPC channel, so the plain-text lines emitted before
the initialize handshake corrupted the protocol stream. Strict MCP clients
(Claude Desktop / Cursor / ZCode / VS Code Copilot) treated this as a
fatal protocol error and reported the server as failed-to-start.
Redirect all diagnostic print() to sys.stderr (per MCP spec, only
JSON-RPC may use stdout). weknora_mcp_server.py already routes through
logging (stderr) and is unchanged.
Verified against a live backend (initialize -> tools/list (28 tools) ->
tools/call list_knowledge_bases); stdout first byte is now '{' with all
diagnostics on stderr.
Rename the PyPI distribution name from weknora-mcp-server to weknora-mcp (tools used: pyproject [project].name + setup.py name). The installed console-script commands (weknora-mcp-server, weknora-server) and the Python module (weknora_mcp_server) are unchanged for backward compatibility.
The 1.0.0 wheel was missing upload_paths.py: it was absent from the [tool.setuptools] py-modules list (and setup.py py_modules), so setuptools never packed it. weknora_mcp_server.py line 26 does `from upload_paths import resolve_upload_file_path, set_active_transport`, so any path that fully imports the module — i.e. the MCP client initialize handshake — crashed with ModuleNotFoundError. `--version` survived only because argparse exits before that import runs. Fix: add 'upload_paths' to py-modules (pyproject.toml + setup.py) and bump to 1.0.1 (1.0.0 is immutable on PyPI). Also sync the user-facing version strings (__version__, server_version, main --version) to 1.0.1.
Adds .github/workflows/workflow.yml adapted from annopick/tuomin's ci.yml: - test: matrix Python 3.10–3.13 (matches requires-python >=3.10), runs the flat test_*.py suite in mcp-server/ - build: on mcp-server-v* tags, builds sdist + wheel via uv build, and guards against the 1.0.0 regression by asserting upload_paths.py is in the wheel before publishing - publish: PyPI Trusted Publishing (OIDC, id-token: write) to weknora-mcp, no API token needed Publishing is gated on tags named mcp-server-v* to decouple from the Go release workflow that already targets v* tags.
Prevent MCP stdio startup crashes by sending entry-script diagnostics to stderr, package upload_paths in the wheel, and add unittest-based CI with stdout purity regression tests.
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
中文
本 PR 修复
mcp-server的两个启动即崩溃问题,并补齐打包缺陷。修复版本已发布到 PyPI:weknora-mcp1.0.1。修复 1:stdio 协议流被诊断输出破坏
入口脚本(
run_server.py、main.py、run.py)通过裸print()把诊断信息写到了 stdout。在 stdio 传输下(默认传输,也是MCP_CONFIG.md中所有 MCP 客户端的启动方式uv run run_server.py),stdout 是 JSON-RPC 通道,客户端期望 stdout 每一行都是合法的 JSON-RPC。握手前打到 stdout 的纯文本行破坏了协议流,严格客户端(Claude Desktop / Cursor / ZCode / VS Code Copilot)据此判定协议错误 → "服务器启动失败" → 启动即崩溃。原始字节证据(
PYTHONUNBUFFERED=1+ hex dump):按 MCP 规范,所有诊断
print()改到sys.stderr,stdout 仅保留 JSON-RPC。weknora_mcp_server.py用的是logging(已走 stderr),未改动。修复 2:wheel 漏打包
upload_paths.py[tool.setuptools] py-modules和setup.py的py_modules列表都漏了upload_paths,setuptools 不会打包它。而weknora_mcp_server.py:26有from upload_paths import ...,所以任何走完整导入的启动(即 MCP 客户端initialize握手)都会ModuleNotFoundError崩溃。--version不崩是因为 argparse 在导入前就 exit 了。已发布的 1.0.0 wheel 实测确认upload_paths.py缺失。修复:两处
py-modules/py_modules都补上upload_paths。已排查所有顶层.py,确认只有upload_paths是真正缺失的运行时模块(__init__.py是包标记、setup.py是构建脚本,按设计都不打包)。版本
包名
weknora-mcp,版本1.0.0 → 1.0.1(PyPI 不允许覆盖已发布版本)。同步__version__、server_version、--version三处用户可见版本串。console-script 命令(weknora-mcp-server、weknora-server)与 Python 模块名weknora_mcp_server保持不变,向后兼容。验证
7b({),诊断文本全部落到 stderrinitialize→tools/list(28 tools) →tools/call list_knowledge_bases(success, 4 个知识库) 全链路成功upload_paths.py(1.0.0 缺失);干净 venv 安装后from upload_paths import ...解析成功main.pystdio 同样纯净;--check-only输出仍在终端可见(stderr)改动范围
仅
mcp-server/下 6 个文件,无后端逻辑变更。English
This PR fixes two crash-on-startup issues in
mcp-serverand a packaging defect. The fixed release is published on PyPI:weknora-mcp1.0.1.Fix 1: stdio protocol stream corrupted by diagnostics
Entry scripts (
run_server.py,main.py,run.py) wrote diagnostics to stdout via bareprint(). Under the stdio transport (the default, and the path used by all MCP clients perMCP_CONFIG.md:uv run run_server.py), stdout is the JSON-RPC channel — clients expect every stdout line to be valid JSON-RPC. Plain-text lines emitted before theinitializehandshake corrupted the stream, so strict clients (Claude Desktop / Cursor / ZCode / VS Code Copilot) treated it as a fatal protocol error → "server failed to start" → crash on startup.Raw-byte evidence (
PYTHONUNBUFFERED=1+ hex dump):Per the MCP spec, all diagnostic
print()now goes tosys.stderr; stdout is reserved for JSON-RPC.weknora_mcp_server.pyalready routes throughlogging(stderr) and is unchanged.Fix 2: wheel missing
upload_paths.pyBoth the
[tool.setuptools] py-moduleslist andsetup.py'spy_modulesomittedupload_paths, so setuptools never packed it. Butweknora_mcp_server.py:26doesfrom upload_paths import ..., so any path that fully imports the module — i.e. the MCP clientinitializehandshake — crashed withModuleNotFoundError.--versionsurvived only because argparse exits before that import runs. The published 1.0.0 wheel was confirmed to be missingupload_paths.py.Fix: add
upload_pathsto bothpy-modules/py_modules. All top-level.pyfiles were audited; onlyupload_pathswas a genuinely missing runtime module (__init__.pyis a package marker,setup.pyis the build script — neither is packed by design).Versioning
Package
weknora-mcp, version1.0.0 → 1.0.1(PyPI forbids overwriting published versions). The user-facing version strings (__version__,server_version,--version) are synced to 1.0.1. Console-script commands (weknora-mcp-server,weknora-server) and the Python module nameweknora_mcp_serverare unchanged for backward compatibility.Verification
7b({); all diagnostics land on stderrinitialize→tools/list(28 tools) →tools/call list_knowledge_bases(success, 4 KBs) all succeedupload_paths.py(1.0.0 did not); after a clean-venv install,from upload_paths import ...resolvesmain.pystdio is equally clean;--check-onlyoutput still visible on stderrScope
Only 6 files under
mcp-server/; no backend logic changes.