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 /src | |
| 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 'src')
| -rw-r--r-- | src/mcp_sd15/cli.py | 10 | ||||
| -rw-r--r-- | src/mcp_sd15/config.py | 23 |
2 files changed, 32 insertions, 1 deletions
diff --git a/src/mcp_sd15/cli.py b/src/mcp_sd15/cli.py index 8bc1e20..3a3a9f7 100644 --- a/src/mcp_sd15/cli.py +++ b/src/mcp_sd15/cli.py @@ -37,7 +37,15 @@ def main(argv: Sequence[str] | None = None) -> int: ) backend = ModelManager(config) service = ImageGenerationService(config, backend) - create_server(service).run() + server = create_server(service) + if config.server.transport == "streamable-http": + server.run( + transport="streamable-http", + host=config.server.host, + port=config.server.port, + ) + else: + server.run() except SD15Error as exc: logging.getLogger(__name__).error("startup failed: %s", exc) return 2 diff --git a/src/mcp_sd15/config.py b/src/mcp_sd15/config.py index 1a5a71d..df2da34 100644 --- a/src/mcp_sd15/config.py +++ b/src/mcp_sd15/config.py @@ -12,6 +12,7 @@ from .errors import ConfigError VALID_SIZES = frozenset({"square_512", "landscape_512", "portrait_512"}) VALID_SCHEDULERS = frozenset({"dpmpp_2m_karras", "dpmpp_2m_sde_karras", "euler_a", "ddim", "lcm"}) +VALID_TRANSPORTS = frozenset({"stdio", "streamable-http"}) @dataclass(frozen=True) @@ -72,11 +73,19 @@ class OutputConfig: @dataclass(frozen=True) +class ServerConfig: + transport: str = "stdio" + host: str = "127.0.0.1" + port: int = 8000 + + +@dataclass(frozen=True) class AppConfig: model: ModelConfig generation: GenerationConfig controlnet: ControlNetConfig output: OutputConfig + server: ServerConfig = ServerConfig() def controlnet_path(self, mode: str) -> Path: item = self.controlnet.for_mode(mode) @@ -208,11 +217,25 @@ def load_config(path: str | Path, *, validate_paths: bool = True) -> AppConfig: if not output_dir.is_absolute(): output_dir = config_path.parent / output_dir + server_raw = _table(raw, "server") + transport = _string(server_raw, "transport") or "stdio" + if transport not in VALID_TRANSPORTS: + raise ConfigError(f"unsupported transport: {transport}") + host = _string(server_raw, "host") or "127.0.0.1" + port_value = server_raw.get("port", 8000) + if ( + isinstance(port_value, bool) + or not isinstance(port_value, int) + or not 1 <= port_value <= 65535 + ): + raise ConfigError("server.port must be an integer between 1 and 65535") + config = AppConfig( model=ModelConfig(root, checkpoint, vae, tuple(loras)), generation=GenerationConfig(steps_value, cfg_scale, scheduler, default_size), controlnet=controlnet, output=OutputConfig(output_dir.resolve(), _bool(output_raw, "enabled", False)), + server=ServerConfig(transport, host, port_value), ) if validate_paths: validate_model_paths(config) |
