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 /script/smoke_streamable_http.py | |
| 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 'script/smoke_streamable_http.py')
| -rw-r--r-- | script/smoke_streamable_http.py | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/script/smoke_streamable_http.py b/script/smoke_streamable_http.py new file mode 100644 index 0000000..5c762a2 --- /dev/null +++ b/script/smoke_streamable_http.py @@ -0,0 +1,64 @@ +"""Verify generate_image over the Streamable HTTP transport with the real GPU backend. + +This script connects to a running ``mcp-sd15`` server that was started with +``transport = "streamable-http"`` and calls ``generate_image`` through the HTTP +endpoint. It exercises the full request path: HTTP connect → MCP initialize → +tools/call → PNG response. +""" + +from __future__ import annotations + +import argparse +import asyncio +import base64 +from pathlib import Path + +from mcp import Client +from mcp.types import ImageContent + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--url", default="http://127.0.0.1:8123/mcp") + parser.add_argument("--output", type=Path, required=True) + parser.add_argument( + "--prompt", + default="a small red cube on a white table, studio photograph, detailed", + ) + parser.add_argument("--negative-prompt", default="blurry, low quality") + parser.add_argument("--size", default="square_512") + return parser.parse_args() + + +async def run(args: argparse.Namespace) -> None: + async with Client(args.url, raise_exceptions=True) as client: + tools = (await client.list_tools()).tools + tool_names = {tool.name for tool in tools} + if tool_names != {"generate_image", "generate_guided_image", "inpaint_image"}: + raise RuntimeError(f"unexpected tools: {tool_names}") + + result = await client.call_tool( + "generate_image", + { + "positive_prompt": args.prompt, + "negative_prompt": args.negative_prompt, + "size": args.size, + }, + ) + + if result.is_error or len(result.content) != 1: + raise RuntimeError(f"generate_image failed: {result}") + content = result.content[0] + if not isinstance(content, ImageContent) or content.mime_type != "image/png": + raise RuntimeError(f"generate_image returned unexpected content: {content}") + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_bytes(base64.b64decode(content.data, validate=True)) + print(f"generated={args.output.resolve()}") + + +def main() -> None: + asyncio.run(run(parse_args())) + + +if __name__ == "__main__": + raise SystemExit(main()) |
