chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""Smoke test for the MCP manager example app.
|
||||
|
||||
This script starts the sibling Streamable HTTP MCP server on a temporary local
|
||||
port, loads the app with matching environment variables, and verifies the
|
||||
manager-backed endpoints without calling a model.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import importlib
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
||||
import httpx
|
||||
|
||||
HOST = "127.0.0.1"
|
||||
PORT_WAIT_SECONDS = 10.0
|
||||
|
||||
|
||||
def _free_port() -> int:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.bind((HOST, 0))
|
||||
return int(sock.getsockname()[1])
|
||||
|
||||
|
||||
def _wait_for_port(process: subprocess.Popen[str], port: int) -> None:
|
||||
deadline = time.monotonic() + PORT_WAIT_SECONDS
|
||||
while time.monotonic() < deadline:
|
||||
if process.poll() is not None:
|
||||
output, _ = process.communicate(timeout=1)
|
||||
raise RuntimeError(f"MCP server exited before it was ready:\n{output}")
|
||||
try:
|
||||
with socket.create_connection((HOST, port), timeout=0.2):
|
||||
return
|
||||
except OSError:
|
||||
time.sleep(0.1)
|
||||
raise RuntimeError(f"MCP server did not listen on {HOST}:{port}.")
|
||||
|
||||
|
||||
def _stop_process(process: subprocess.Popen[str]) -> str:
|
||||
if process.poll() is not None:
|
||||
output, _ = process.communicate(timeout=1)
|
||||
return output
|
||||
|
||||
process.terminate()
|
||||
try:
|
||||
output, _ = process.communicate(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
output, _ = process.communicate(timeout=5)
|
||||
return output
|
||||
|
||||
|
||||
def _start_mcp_server(port: int) -> subprocess.Popen[str]:
|
||||
env = {
|
||||
**os.environ,
|
||||
"STREAMABLE_HTTP_HOST": HOST,
|
||||
"STREAMABLE_HTTP_PORT": str(port),
|
||||
}
|
||||
return subprocess.Popen(
|
||||
[sys.executable, "-u", "-m", "examples.mcp.manager_example.mcp_server"],
|
||||
env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
def _load_app_module(mcp_port: int) -> Any:
|
||||
mcp_server_url = f"http://{HOST}:{mcp_port}/mcp"
|
||||
os.environ["MCP_SERVER_URL"] = mcp_server_url
|
||||
# Point both configured MCP servers at the same temporary server so this
|
||||
# smoke test stays on the clean app integration path.
|
||||
os.environ["INACTIVE_MCP_SERVER_URL"] = mcp_server_url
|
||||
os.environ["USE_MCP_MANAGER"] = "1"
|
||||
|
||||
module_name = "examples.mcp.manager_example.app"
|
||||
if module_name in sys.modules:
|
||||
module = importlib.reload(sys.modules[module_name])
|
||||
else:
|
||||
module = importlib.import_module(module_name)
|
||||
return cast(Any, module)
|
||||
|
||||
|
||||
def _require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
async def _exercise_app(mcp_port: int) -> None:
|
||||
app_module = _load_app_module(mcp_port)
|
||||
app = app_module.app
|
||||
expected_server_url = f"http://{HOST}:{mcp_port}/mcp"
|
||||
|
||||
async with app.router.lifespan_context(app):
|
||||
transport = httpx.ASGITransport(app=app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
health_response = await client.get("/health")
|
||||
health_response.raise_for_status()
|
||||
health = health_response.json()
|
||||
_require(
|
||||
any(expected_server_url in name for name in health["connected_servers"]),
|
||||
f"Expected connected MCP server in health response: {health}",
|
||||
)
|
||||
_require(
|
||||
health["failed_servers"] == [],
|
||||
f"Expected no failed MCP servers in health response: {health}",
|
||||
)
|
||||
|
||||
tools_response = await client.get("/tools")
|
||||
tools_response.raise_for_status()
|
||||
tools = tools_response.json()["tools"]
|
||||
_require({"add", "echo"} <= set(tools), f"Expected add and echo tools: {tools}")
|
||||
|
||||
add_response = await client.post("/add", json={"a": 2, "b": 3})
|
||||
add_response.raise_for_status()
|
||||
add_result = add_response.json()["result"]
|
||||
texts = [
|
||||
item.get("text")
|
||||
for item in add_result.get("content", [])
|
||||
if item.get("type") == "text"
|
||||
]
|
||||
_require("5" in texts, f"Expected add tool result to include 5: {add_result}")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
mcp_port = _free_port()
|
||||
process = _start_mcp_server(mcp_port)
|
||||
try:
|
||||
_wait_for_port(process, mcp_port)
|
||||
await _exercise_app(mcp_port)
|
||||
finally:
|
||||
_stop_process(process)
|
||||
|
||||
print("MCP manager example smoke test completed successfully.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user