Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions analysis/technical/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,17 @@ standalone Streamable HTTP demo client
uv run --script demo/streamable_http_client.py
```

The demo checks `/health`, initializes against `/mcp`, prints server identity,
lists the available tools, and prints `Command:` framing before a three-step
discovery -> evidence -> melanoma trials workflow through the remote `biomcp` tool:
The demo initializes against `/mcp` and prints `Command:` framing before a
three-step discovery -> evidence -> melanoma trials workflow through the remote
`biomcp` tool:

- `biomcp search all --gene BRAF --disease melanoma --counts-only`
- `biomcp get variant "BRAF V600E" clinvar`
- `biomcp search trial -c melanoma --mutation "BRAF V600E" --limit 5`

Expected structural output includes `Health check passed:` and `Command:`
markers so the remote run remains readable in screenshots and recorded demos
without replacing the real BioMCP markdown output.
Expected structural output includes the connection line and `Command:` markers
so the remote run remains readable in screenshots and recorded demos without
replacing the real BioMCP markdown output.

## Known Constraints

Expand Down
10 changes: 3 additions & 7 deletions demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,23 @@ server version matches the worktree under test:

## How to run the client

Run the default melanoma scenario:
Run the default workflow:

```bash
uv run --quiet --script demo/streamable_http_client.py
```

Pass an explicit scenario or base URL when needed:
Pass an explicit base URL when needed:

```bash
uv run --quiet --script demo/streamable_http_client.py --scenario braf-melanoma
uv run --quiet --script demo/streamable_http_client.py http://127.0.0.1:8080
```

## What output to expect

The output should include these structural markers:

- `Health check passed: http://127.0.0.1:8080/health`
- `Connecting to BioMCP at http://127.0.0.1:8080/mcp`
- `Scenario: braf-melanoma`
- `Available tools: biomcp`
- `Connecting to http://127.0.0.1:8080/mcp`
- `Command: biomcp search all --gene BRAF --disease melanoma --counts-only`
- `Command: biomcp get variant "BRAF V600E" clinvar`
- `Command: biomcp search trial -c melanoma --mutation "BRAF V600E" --limit 5`
Expand Down
103 changes: 16 additions & 87 deletions demo/streamable_http_client.py
Original file line number Diff line number Diff line change
@@ -1,102 +1,39 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "mcp>=1.1.1",
# ]
# dependencies = ["mcp>=1.1.1"]
# ///
#
# Start the server first:
# biomcp serve-http --host 127.0.0.1 --port 8080
#
# Then run this demo:
# uv run --script demo/streamable_http_client.py
# uv run --script demo/streamable_http_client.py --scenario braf-melanoma
# uv run --script demo/streamable_http_client.py http://127.0.0.1:8080

from __future__ import annotations

import argparse
import asyncio
import json
import urllib.error
import urllib.request
import sys
from datetime import timedelta
from typing import TypeAlias

from mcp import ClientSession, types
from mcp.client.streamable_http import streamable_http_client

DEFAULT_BASE_URL = "http://127.0.0.1:8080"
HEALTH_TIMEOUT_SECONDS = 5
ScenarioStep: TypeAlias = tuple[str, str]

# Named demo scenarios keep the workflow loop stable as stories expand.
SCENARIOS: dict[str, list[ScenarioStep]] = {
"braf-melanoma": [
(
"Step 1 - Discovery: BRAF in melanoma",
"biomcp search all --gene BRAF --disease melanoma --counts-only",
),
(
"Step 2 - Evidence: BRAF V600E ClinVar evidence",
'biomcp get variant "BRAF V600E" clinvar',
),
(
"Step 3 - Trials: melanoma trials mentioning BRAF V600E",
'biomcp search trial -c melanoma --mutation "BRAF V600E" --limit 5',
),
],
}

def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Run the BioMCP Streamable HTTP demo workflow."
)
parser.add_argument(
"base_url",
nargs="?",
default=DEFAULT_BASE_URL,
help=f"Base BioMCP HTTP URL (default: {DEFAULT_BASE_URL})",
)
parser.add_argument(
"--scenario",
default="braf-melanoma",
choices=sorted(SCENARIOS),
help="Named demo scenario to run.",
)
return parser.parse_args(argv)
WORKFLOW = [
"biomcp search all --gene BRAF --disease melanoma --counts-only",
'biomcp get variant "BRAF V600E" clinvar',
'biomcp search trial -c melanoma --mutation "BRAF V600E" --limit 5',
]


def steps_for(scenario: str) -> list[ScenarioStep]:
return SCENARIOS[scenario]
def resolve_base_url(argv: list[str]) -> str:
if len(argv) > 2:
raise SystemExit("Usage: demo/streamable_http_client.py [base_url]")
return argv[1] if len(argv) > 1 else DEFAULT_BASE_URL


def check_health(base_url: str) -> None:
health_url = f"{base_url.rstrip('/')}/health"
try:
with urllib.request.urlopen(
health_url,
timeout=HEALTH_TIMEOUT_SECONDS,
) as response:
payload = json.loads(response.read().decode("utf-8"))
if payload.get("status") != "ok":
raise ValueError(f"unexpected payload: {payload!r}")
except (OSError, ValueError, json.JSONDecodeError, urllib.error.URLError) as exc:
raise SystemExit(
"Health check failed at "
f"{health_url}. Start the server first with "
"biomcp serve-http --host 127.0.0.1 --port 8080. "
f"Details: {exc}"
) from exc

print(f"Health check passed: {health_url}")


async def main(base_url: str, scenario: str) -> None:
async def main(base_url: str) -> None:
mcp_url = f"{base_url.rstrip('/')}/mcp"
print(f"Connecting to BioMCP at {mcp_url}")
print(f"Scenario: {scenario}\n")
print(f"Connecting to {mcp_url}\n")

# The current Python MCP client warns on 202 Accepted during session teardown.
async with streamable_http_client(
Expand All @@ -108,14 +45,8 @@ async def main(base_url: str, scenario: str) -> None:
write_stream,
read_timeout_seconds=timedelta(seconds=30),
) as session:
initialize_result = await session.initialize()
print(initialize_result.serverInfo)
tools_result = await session.list_tools()
tool_names = [tool.name for tool in tools_result.tools]
print(f"Available tools: {', '.join(tool_names)}")

for title, command in steps_for(scenario):
print(f"\n=== {title} ===")
await session.initialize()
for command in WORKFLOW:
print(f"Command: {command}")
call_result = await session.call_tool(
"biomcp",
Expand All @@ -127,6 +58,4 @@ async def main(base_url: str, scenario: str) -> None:


if __name__ == "__main__":
args = parse_args()
check_health(args.base_url)
asyncio.run(main(args.base_url, args.scenario))
asyncio.run(main(resolve_base_url(sys.argv)))
8 changes: 4 additions & 4 deletions docs/getting-started/remote-http.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ workflow over the remote MCP `biomcp` tool:
```bash
biomcp serve-http --host 127.0.0.1 --port 8080
uv run --script demo/streamable_http_client.py
uv run --script demo/streamable_http_client.py --scenario braf-melanoma
uv run --script demo/streamable_http_client.py http://127.0.0.1:8080
```

The demo checks `/health` before opening the MCP session, prints the scenario,
lists available tools, and prints `Command: ...` before each BioMCP step so a
screenshot or recording still makes sense without extra narration.
The demo connects to `/mcp`, prints `Command: ...` before each BioMCP step,
and leaves the real markdown output untouched so a screenshot or recording
still makes sense without extra narration.

See `demo/README.md` for the short newcomer walkthrough, expected output
markers, `uv run --quiet` guidance for first-run dependency noise, and the
Expand Down
32 changes: 21 additions & 11 deletions tests/test_docs_changelog_refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,13 @@ def test_remote_http_docs_are_promoted_for_newcomers() -> None:
assert "demo/streamable_http_client.py" in remote_http
assert "three-step BRAF V600E melanoma" in remote_http
assert "workflow over the remote MCP `biomcp` tool" in remote_http
assert "--scenario braf-melanoma" in remote_http
assert "prints `Command: ...` before each BioMCP step" in remote_http
assert "biomcp search all --gene BRAF --disease melanoma --counts-only" in remote_http
assert 'biomcp get variant "BRAF V600E" clinvar' in remote_http
assert 'biomcp search trial -c melanoma --mutation "BRAF V600E" --limit 5' in remote_http
assert "demo/README.md" in remote_http
assert "--scenario braf-melanoma" not in remote_http
assert "Available tools:" not in remote_http

assert "# Streamable HTTP Demo" in demo_readme
assert "what the demo proves" in demo_readme.lower()
Expand All @@ -99,7 +101,16 @@ def test_remote_http_docs_are_promoted_for_newcomers() -> None:
assert "what output to expect" in demo_readme.lower()
assert "uv run --quiet --script demo/streamable_http_client.py" in demo_readme
assert "./target/release/biomcp serve-http --host 127.0.0.1 --port 8080" in demo_readme
assert "--scenario braf-melanoma" in demo_readme
assert "http://127.0.0.1:8080/mcp" in demo_readme
assert "Command: biomcp search all --gene BRAF --disease melanoma --counts-only" in demo_readme
assert 'Command: biomcp get variant "BRAF V600E" clinvar' in demo_readme
assert (
'Command: biomcp search trial -c melanoma --mutation "BRAF V600E" --limit 5'
in demo_readme
)
assert "--scenario braf-melanoma" not in demo_readme
assert "Health check passed:" not in demo_readme
assert "Available tools:" not in demo_readme


def test_streamable_http_demo_script_is_runnable_repo_artifact() -> None:
Expand All @@ -112,22 +123,21 @@ def test_streamable_http_demo_script_is_runnable_repo_artifact() -> None:
assert "uv run --script demo/streamable_http_client.py" in demo_script
assert 'DEFAULT_BASE_URL = "http://127.0.0.1:8080"' in demo_script
assert 'mcp_url = f"{base_url.rstrip(\'/\')}/mcp"' in demo_script
assert "def parse_args(argv: list[str] | None = None)" in demo_script
assert "def check_health(base_url: str) -> None:" in demo_script
assert 'choices=sorted(SCENARIOS)' in demo_script
assert "def resolve_base_url(argv: list[str]) -> str:" in demo_script
assert "resolve_base_url(sys.argv)" in demo_script
assert "Usage: demo/streamable_http_client.py [base_url]" in demo_script
assert "terminate_on_close=False" in demo_script
assert "list_tools()" in demo_script
assert '"biomcp"' in demo_script
assert 'shell' not in demo_script
assert 'SCENARIO = "braf-melanoma"' not in demo_script
assert '"Step 1' in demo_script
assert '"Step 2' in demo_script
assert '"Step 3' in demo_script
assert '"biomcp search all --gene BRAF --disease melanoma --counts-only"' in demo_script
assert 'biomcp get variant "BRAF V600E" clinvar' in demo_script
assert 'biomcp search trial -c melanoma --mutation "BRAF V600E" --limit 5' in demo_script
assert 'print(f"Command: {command}")' in demo_script
assert 'check_health(args.base_url)' in demo_script
assert "argparse" not in demo_script
assert "check_health" not in demo_script
assert "list_tools()" not in demo_script
assert "--scenario" not in demo_script


def test_release_overview_describes_streamable_http_workflow_demo() -> None:
Expand All @@ -138,7 +148,7 @@ def test_release_overview_describes_streamable_http_workflow_demo() -> None:
assert "discovery -> evidence -> melanoma trials workflow" in overview
assert "lists tools" not in overview
assert "biomcp version" not in overview
assert "Health check passed:" in overview
assert "Health check passed:" not in overview
assert "Command:" in overview
assert "biomcp search all --gene BRAF --disease melanoma --counts-only" in overview
assert 'biomcp get variant "BRAF V600E" clinvar' in overview
Expand Down
Loading
Loading