diff --git a/README.md b/README.md index 921efe3..7285424 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ Inspector (stdio, memory ledger): uv run mcp dev src/verae_mcp_timestamping_server/server.py ``` +Streamable HTTP (`http://127.0.0.1:8000/mcp`): + +```sh +VERAE_MCP_TRANSPORT=streamable-http uv run verae-mcp-timestamping-server +``` + Tests: ```sh diff --git a/src/verae_mcp_timestamping_server/server.py b/src/verae_mcp_timestamping_server/server.py index 78f8450..c8e094e 100644 --- a/src/verae_mcp_timestamping_server/server.py +++ b/src/verae_mcp_timestamping_server/server.py @@ -63,13 +63,13 @@ async def hash_report( def main() -> None: - url = os.environ.get("VERAE_NATS_URL") - if url: - raise SystemExit( - "NATS leaf mode is wired through NatsLedger; " - "stdio Task 1 uses MemoryLedger. Set VERAE_NATS_URL on Streamable HTTP later." - ) build_mcp(MemoryLedger()) + transport = os.environ.get("VERAE_MCP_TRANSPORT", "stdio") + if transport == "streamable-http": + host = os.environ.get("VERAE_MCP_HOST", "127.0.0.1") + port = int(os.environ.get("VERAE_MCP_PORT", "8000")) + mcp.run(transport="streamable-http", host=host, port=port) + return mcp.run() diff --git a/tests/test_http.py b/tests/test_http.py new file mode 100644 index 0000000..84c5fca --- /dev/null +++ b/tests/test_http.py @@ -0,0 +1,42 @@ +"""Streamable HTTP: stamp over /mcp.""" + +import socket + +import anyio +import pytest +import uvicorn +from mcp import Client + +from verae_mcp_timestamping_server.ledger import SHA256_EMPTY, MemoryLedger +from verae_mcp_timestamping_server.server import build_mcp + +pytestmark = pytest.mark.anyio + + +def _free_port() -> int: + sock = socket.socket() + sock.bind(("127.0.0.1", 0)) + port = sock.getsockname()[1] + sock.close() + return port + + +async def test_stamp_over_streamable_http(): + mcp = build_mcp(MemoryLedger()) + app = mcp.streamable_http_app() + port = _free_port() + server = uvicorn.Server( + uvicorn.Config(app, host="127.0.0.1", port=port, log_level="error") + ) + async with anyio.create_task_group() as tg: + tg.start_soon(server.serve) + with anyio.fail_after(10): + while not server.started: + await anyio.sleep(0.05) + async with Client(f"http://127.0.0.1:{port}/mcp") as client: + result = await client.call_tool( + "stamp", {"sha256": SHA256_EMPTY, "scope": "local"} + ) + assert result.is_error is not True + assert result.structured_content["receipts"][0]["hash"] == SHA256_EMPTY + server.should_exit = True