Skip to content

fix(mcp-server): route diagnostics to stderr to fix stdio protocol crash - #2371

Merged
lyingbug merged 5 commits into
Tencent:mainfrom
annopick:fix/mcp-stdio-stdout
Jul 28, 2026
Merged

fix(mcp-server): route diagnostics to stderr to fix stdio protocol crash#2371
lyingbug merged 5 commits into
Tencent:mainfrom
annopick:fix/mcp-stdio-stdout

Conversation

@annopick

@annopick annopick commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

中文

本 PR 修复 mcp-server 的两个启动即崩溃问题,并补齐打包缺陷。修复版本已发布到 PyPI:weknora-mcp 1.0.1

修复 1:stdio 协议流被诊断输出破坏

入口脚本(run_server.pymain.pyrun.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):

修复前 stdout 开头: e5 90 af e5 8a a8 20 ... → "启动 WeKnora MCP Server..."(纯文本)
修复后 stdout 开头: 7b 22 6a 73 6f 6e ...    → {"jsonrpc":"2.0"...(合法 JSON-RPC)

按 MCP 规范,所有诊断 print() 改到 sys.stderr,stdout 仅保留 JSON-RPC。weknora_mcp_server.py 用的是 logging(已走 stderr),未改动。

修复 2:wheel 漏打包 upload_paths.py

[tool.setuptools] py-modulessetup.pypy_modules 列表都漏了 upload_paths,setuptools 不会打包它。而 weknora_mcp_server.py:26from 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-serverweknora-server)与 Python 模块名 weknora_mcp_server 保持不变,向后兼容。

验证

  1. stdout 纯净性:hex dump 首字节为 7b({),诊断文本全部落到 stderr
  2. 完整握手(针对真实后端):initializetools/list(28 tools) → tools/call list_knowledge_bases(success, 4 个知识库) 全链路成功
  3. 打包修复:1.0.1 wheel 实测含 upload_paths.py(1.0.0 缺失);干净 venv 安装后 from upload_paths import ... 解析成功
  4. 回归main.py stdio 同样纯净;--check-only 输出仍在终端可见(stderr)

改动范围

mcp-server/ 下 6 个文件,无后端逻辑变更。


English

This PR fixes two crash-on-startup issues in mcp-server and a packaging defect. The fixed release is published on PyPI: weknora-mcp 1.0.1.

Fix 1: stdio protocol stream corrupted by diagnostics

Entry scripts (run_server.py, main.py, run.py) wrote 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 — clients expect every stdout line to be valid JSON-RPC. Plain-text lines emitted before the initialize handshake 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):

before: stdout starts with e5 90 af e5 8a a8 20 ... → "启动 WeKnora MCP Server..." (plain text)
after:  stdout starts with 7b 22 6a 73 6f 6e ...    → {"jsonrpc":"2.0"... (valid JSON-RPC)

Per the MCP spec, all diagnostic print() now goes to sys.stderr; stdout is reserved for JSON-RPC. weknora_mcp_server.py already routes through logging (stderr) and is unchanged.

Fix 2: wheel missing upload_paths.py

Both the [tool.setuptools] py-modules list and setup.py's py_modules omitted upload_paths, so setuptools never packed it. But weknora_mcp_server.py:26 does from upload_paths import ..., 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. The published 1.0.0 wheel was confirmed to be missing upload_paths.py.

Fix: add upload_paths to both py-modules/py_modules. All top-level .py files were audited; only upload_paths was a genuinely missing runtime module (__init__.py is a package marker, setup.py is the build script — neither is packed by design).

Versioning

Package weknora-mcp, version 1.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 name weknora_mcp_server are unchanged for backward compatibility.

Verification

  1. stdout purity: hex dump first byte is 7b ({); all diagnostics land on stderr
  2. full handshake (against a live backend): initializetools/list (28 tools) → tools/call list_knowledge_bases (success, 4 KBs) all succeed
  3. packaging fix: the 1.0.1 wheel contains upload_paths.py (1.0.0 did not); after a clean-venv install, from upload_paths import ... resolves
  4. regression: main.py stdio is equally clean; --check-only output still visible on stderr

Scope

Only 6 files under mcp-server/; no backend logic changes.

annopick and others added 5 commits July 28, 2026 12:28
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.
@lyingbug
lyingbug merged commit 400bbbf into Tencent:main Jul 28, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants