diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-09-12 20:40:07 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-09-12 20:40:07 +0900 |
| commit | 4f3f548b1b55d4c7477250fc3373b3bd6d0d397f (patch) | |
| tree | 5688ea7fa04af051055323e316e53533b9db6193 /tests | |
| parent | cc6165947a82d2182df8accbc7b4adfe2606db9e (diff) | |
streamable-httpトランスポート対応を追加
MCP SDK 2.xのMCPServer.run(transport="streamable-http")を使い、stdioに
加えてStreamable HTTPトランスポートで起動できるようにする。SSEはMCP仕様で
非推奨のため選択肢から除外し、transport enumはstdioとstreamable-httpの
2種類のみとした。
transportの選択はTOML [server] tableで指定し、CLI引数は追加しない。
仕様の「CLI上書きを行わない」「TOML集約」原則に準拠する。[server]省略時は
stdio(後方互換)、streamable-http時はhost=127.0.0.1 port=8000を既定値とする。
変更内容:
- config.py: ServerConfig dataclass、VALID_TRANSPORTS定数、AppConfig.server
フィールド、load_configの[server]読込・validation追加
- cli.py: main()でconfig.server.transportに応じたrun()引数分岐
- tests/conftest.py: app_config fixtureにserver=ServerConfig()追加
- tests/test_config.py: [server]の正常系・異常系・境界値テスト追加
(省略時stdio既定値、streamable-http読込、sse/http/ws/unknown拒否、
port境界値0/65536/-1/文字列、host空文字拒否)
- doc/config.md: [server]セクション追記、設定項目一覧表更新
- config.example.toml: [server]コメント例追記
- doc/Specifications.md: スコープ表にMCPトランスポート行追加
- doc/Design.md: 責務表と起動フロー更新
- doc/mcp.md: 概要のtransport記載更新
- README.md: streamable-http起動例追記
- script/smoke_streamable_http.py: streamable-http経由の検証スクリプト追加
検証:
- ruff format/check、pyright 0 errors、pytest 50 passed
- 実GPU・実モデルでstreamable-http経由generate_imageの全ルート確認
(HTTP接続→MCP initialize→tools/list→tools/call→PNG生成・返却)
- 生成結果: 335KB PNG、512x512、20 steps、CFG 7.0
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/conftest.py | 2 | ||||
| -rw-r--r-- | tests/test_config.py | 72 |
2 files changed, 74 insertions, 0 deletions
diff --git a/tests/conftest.py b/tests/conftest.py index d364dbd..862b4f5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,7 @@ from mcp_sd15.config import ( GenerationConfig, ModelConfig, OutputConfig, + ServerConfig, ) @@ -28,6 +29,7 @@ def app_config(tmp_path: Path) -> AppConfig: inpaint=ControlModelConfig(True, "inpaint.safetensors"), ), output=OutputConfig(tmp_path / "outputs", False), + server=ServerConfig(), ) diff --git a/tests/test_config.py b/tests/test_config.py index c24064b..9a6bf89 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -123,3 +123,75 @@ def test_can_skip_path_checks_for_schema_only_use(tmp_path: Path) -> None: config = load_config(path, validate_paths=False) assert config.model.root == (tmp_path / "missing").resolve() assert config.generation.scheduler == "dpmpp_2m_karras" + + +def test_server_defaults_to_stdio_when_omitted(tmp_path: Path) -> None: + path = tmp_path / "config.toml" + path.write_text( + f'[model]\nroot = "{tmp_path.as_posix()}"\ncheckpoint = "base.safetensors"\n', + encoding="utf-8", + ) + config = load_config(path, validate_paths=False) + assert config.server.transport == "stdio" + assert config.server.host == "127.0.0.1" + assert config.server.port == 8000 + + +def test_server_reads_streamable_http(tmp_path: Path) -> None: + path = tmp_path / "config.toml" + path.write_text( + f'[model]\nroot = "{tmp_path.as_posix()}"\ncheckpoint = "base.safetensors"\n' + '[server]\ntransport = "streamable-http"\nhost = "0.0.0.0"\nport = 9000\n', + encoding="utf-8", + ) + config = load_config(path, validate_paths=False) + assert config.server.transport == "streamable-http" + assert config.server.host == "0.0.0.0" + assert config.server.port == 9000 + + +@pytest.mark.parametrize( + "transport", + ["sse", "http", "ws", "unknown"], +) +def test_rejects_invalid_transport(tmp_path: Path, transport: str) -> None: + path = tmp_path / "config.toml" + path.write_text( + f'[model]\nroot = "{tmp_path.as_posix()}"\ncheckpoint = "base.safetensors"\n' + f'[server]\ntransport = "{transport}"\n', + encoding="utf-8", + ) + with pytest.raises(ConfigError, match="transport"): + load_config(path, validate_paths=False) + + +@pytest.mark.parametrize( + "port, message", + [ + (0, "port"), + (65536, "port"), + (-1, "port"), + ("8000", "port"), + ], +) +def test_rejects_invalid_port(tmp_path: Path, port: str | int, message: str) -> None: + path = tmp_path / "config.toml" + port_literal = f'"{port}"' if isinstance(port, str) else str(port) + path.write_text( + f'[model]\nroot = "{tmp_path.as_posix()}"\ncheckpoint = "base.safetensors"\n' + f'[server]\ntransport = "streamable-http"\nport = {port_literal}\n', + encoding="utf-8", + ) + with pytest.raises(ConfigError, match=message): + load_config(path, validate_paths=False) + + +def test_rejects_empty_host(tmp_path: Path) -> None: + path = tmp_path / "config.toml" + path.write_text( + f'[model]\nroot = "{tmp_path.as_posix()}"\ncheckpoint = "base.safetensors"\n' + '[server]\ntransport = "streamable-http"\nhost = ""\n', + encoding="utf-8", + ) + with pytest.raises(ConfigError, match="host"): + load_config(path, validate_paths=False) |
