Files
annopick 400bbbf717 fix(mcp-server): route diagnostics to stderr to fix stdio protocol crash (#2371)
* fix(mcp-server): route diagnostics to stderr to fix stdio protocol crash

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.

* chore(mcp-server): publish package name as weknora-mcp

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.

* fix(mcp-server): include upload_paths in wheel + bump to 1.0.1

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.

* ci(mcp-server): add workflow.yml to build & publish to PyPI via OIDC

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.

* fix(mcp-server): route diagnostics to stderr and fix packaging/CI

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.

---------

Co-authored-by: wizardchen <wizardchen@tencent.com>
2026-07-28 21:23:09 +08:00

52 lines
1.5 KiB
Python

#!/usr/bin/env python3
"""
WeKnora MCP Server 便捷启动脚本
这是一个简化的启动脚本,提供最基本的功能。
对于更多选项,请使用 main.py
注意:在 stdio 传输下,stdout 是 JSON-RPC 通道,所有诊断/提示信息必须写入
stderr,否则会破坏 MCP 协议流导致客户端判定"启动失败"。本脚本所有 print
均通过 stderr 输出。
"""
import os
import sys
from pathlib import Path
def main():
"""简单的启动函数"""
# 添加当前目录到 Python 路径
current_dir = Path(__file__).parent.absolute()
if str(current_dir) not in sys.path:
sys.path.insert(0, str(current_dir))
# 检查环境变量
base_url = os.getenv("WEKNORA_BASE_URL", "http://localhost:8080/api/v1")
api_key = os.getenv("WEKNORA_API_KEY", "")
print("WeKnora MCP Server", file=sys.stderr)
print(f"Base URL: {base_url}", file=sys.stderr)
print(f"API Key: {'已设置' if api_key else '未设置'}", file=sys.stderr)
print("-" * 40, file=sys.stderr)
try:
# 导入并运行
from main import sync_main
sync_main()
except ImportError:
print("错误: 无法导入必要模块", file=sys.stderr)
print("请确保运行: pip install -r requirements.txt", file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("\n服务器已停止", file=sys.stderr)
except Exception as e:
print(f"错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()