From f9503d4cfe24ddba90a524ba89c3122de24f2db6 Mon Sep 17 00:00:00 2001 From: heznpc Date: Tue, 23 Jun 2026 06:25:35 +0900 Subject: [PATCH] Add stdio smoke for module entrypoint --- README.ko.md | 14 +++++++-- README.md | 14 +++++++-- scripts/smoke_stdio.py | 62 +++++++++++++++++++++++++++++++++++++++ tests/test_stdio_smoke.py | 12 ++++++++ 4 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 scripts/smoke_stdio.py create mode 100644 tests/test_stdio_smoke.py diff --git a/README.ko.md b/README.ko.md index 9ef8a4c..222096b 100644 --- a/README.ko.md +++ b/README.ko.md @@ -38,7 +38,8 @@ MCP 서버를 만들고, 원클릭 배포. 시크릿 불필요. ```bash npx @starter-series/create my-mcp-server --template mcp-server-python cd my-mcp-server -python -m venv .venv && source .venv/bin/activate +python3.12 -m venv .venv && source .venv/bin/activate +python -m pip install --upgrade pip python -m pip install -e '.[dev]' python -m pytest ``` @@ -48,7 +49,8 @@ python -m pytest ```bash git clone https://github.com/starter-series/python-mcp-server-starter my-mcp-server cd my-mcp-server -python -m venv .venv && source .venv/bin/activate +python3.12 -m venv .venv && source .venv/bin/activate +python -m pip install --upgrade pip python -m pip install -e '.[dev]' python -m pytest ``` @@ -153,11 +155,17 @@ python -m mypy src/ # wheel + sdist 빌드 python -m build +# 실제 python -m 엔트리포인트 stdio smoke +python scripts/smoke_stdio.py + # 서버 실행 (stdio) python -m my_mcp_server # 설치된 console script로 동일 서버 실행 my-mcp-server + +# 의존성 audit +pip-audit . --strict ``` ## CI/CD @@ -213,6 +221,7 @@ tests/ ```bash python -m pip install -e ".[dev]" # dev 의존성 포함 설치 +python scripts/smoke_stdio.py # MCP 클라이언트로 python -m my_mcp_server 실행 python -m my_mcp_server # 서버 실행 my-mcp-server # 설치된 console script로 서버 실행 python -m pytest -v # 테스트 실행 @@ -220,6 +229,7 @@ python -m ruff check . # 린트 python -m ruff format . # 포맷 python -m mypy src/ # 타입 확인 python -m build # wheel + sdist 빌드 +pip-audit . --strict # 의존성 audit ``` ## 라이선스 diff --git a/README.md b/README.md index 965f114..fb8d580 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ Build your MCP server. One-click publish. Zero secrets needed. ```bash npx @starter-series/create my-mcp-server --template mcp-server-python cd my-mcp-server -python -m venv .venv && source .venv/bin/activate +python3.12 -m venv .venv && source .venv/bin/activate +python -m pip install --upgrade pip python -m pip install -e '.[dev]' python -m pytest ``` @@ -51,7 +52,8 @@ python -m pytest ```bash git clone https://github.com/starter-series/python-mcp-server-starter my-mcp-server cd my-mcp-server -python -m venv .venv && source .venv/bin/activate +python3.12 -m venv .venv && source .venv/bin/activate +python -m pip install --upgrade pip python -m pip install -e '.[dev]' python -m pytest ``` @@ -173,11 +175,17 @@ python -m mypy src/ # Build wheel + sdist python -m build +# Stdio smoke against the actual python -m entrypoint +python scripts/smoke_stdio.py + # Run the server (stdio) python -m my_mcp_server # Same server via installed console script my-mcp-server + +# Dependency audit +pip-audit . --strict ``` ## CI/CD @@ -233,6 +241,7 @@ tests/ ```bash python -m pip install -e ".[dev]" # Install with dev deps +python scripts/smoke_stdio.py # Start python -m my_mcp_server through an MCP client python -m my_mcp_server # Run server my-mcp-server # Run installed console script python -m pytest -v # Run tests @@ -240,6 +249,7 @@ python -m ruff check . # Lint python -m ruff format . # Format python -m mypy src/ # Type check python -m build # Build wheel + sdist +pip-audit . --strict # Dependency audit ``` ## License diff --git a/scripts/smoke_stdio.py b/scripts/smoke_stdio.py new file mode 100644 index 0000000..c32a2fb --- /dev/null +++ b/scripts/smoke_stdio.py @@ -0,0 +1,62 @@ +"""Stdio smoke test for the packaged MCP entrypoint.""" + +from __future__ import annotations + +import asyncio +import os +import sys +from collections.abc import Sequence + +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client + + +def check(condition: bool, message: str) -> None: + if not condition: + raise RuntimeError(message) + + +async def run_stdio_smoke( + command: str = sys.executable, + args: Sequence[str] = ("-m", "my_mcp_server"), +) -> None: + params = StdioServerParameters( + command=command, + args=list(args), + env={**os.environ, "MCP_DEBUG": "false"}, + ) + + async with ( + stdio_client(params) as (read_stream, write_stream), + ClientSession(read_stream, write_stream) as session, + ): + await session.initialize() + + tools = await session.list_tools() + tool_names = {tool.name for tool in tools.tools} + check("greet" in tool_names, f"greet tool missing from {sorted(tool_names)}") + + result = await session.call_tool("greet", {"name": "Smoke"}) + check(not bool(result.isError), "greet tool returned an MCP error") + check(bool(result.content), "greet tool returned no content") + first = result.content[0] + check(first.type == "text", f"unexpected content type: {first.type}") + check(first.text == "Hello, Smoke!", f"unexpected greeting text: {first.text}") + + resources = await session.list_resources() + resource_uris = {str(resource.uri) for resource in resources.resources} + check("info://server/status" in resource_uris, "server-info resource missing") + + prompts = await session.list_prompts() + prompt_names = {prompt.name for prompt in prompts.prompts} + check("code-review" in prompt_names, "code-review prompt missing") + + +def main() -> int: + asyncio.run(run_stdio_smoke()) + print("stdio smoke passed.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_stdio_smoke.py b/tests/test_stdio_smoke.py new file mode 100644 index 0000000..15ec38d --- /dev/null +++ b/tests/test_stdio_smoke.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +import sys + +import pytest + +from scripts.smoke_stdio import run_stdio_smoke + + +@pytest.mark.asyncio +async def test_python_module_entrypoint_speaks_stdio_mcp() -> None: + await run_stdio_smoke(sys.executable, ("-m", "my_mcp_server"))