aureon-public / protocol_measure.py
GaryLeckey's picture
Aureon public MCP server from Aureon-OS main cf35ec95 (PR #224)
ca4d83b verified
Raw History Blame Contribute Delete
7.82 kB
"""Stdio and Streamable HTTP sessions against aureon-public.
Run from measured/aureon_public_protocol_run.sh. Do not hand-edit the output.
The process requires AUREON_PUBLIC_REQUIRE_PIN=1 and an aureon install whose
direct_url.json names the pin. The HTTP listener is 127.0.0.1. One tools/call
is alignment_consonance_read with ratio 1.0.
"""
from __future__ import annotations
import argparse
import asyncio
import importlib.metadata
import json
import os
import signal
import socket
import subprocess
import sys
import tempfile
import time
from pathlib import Path
from typing import Any
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.client.streamable_http import streamable_http_client
from aureon_public import paths
from aureon_public.guard_loader import aureon_source
from aureon_public.server import enforce_public_pin
TOOL_NAME = "alignment_consonance_read"
TOOL_ARGUMENTS = {"ratio": 1.0}
_BIND_HOST = "127.0.0.1"
def _dump(model: Any) -> dict[str, Any]:
return model.model_dump(mode="json", by_alias=True)
def _server_env() -> dict[str, str]:
env: dict[str, str] = {}
for key in ("HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER", "LANG", "TMPDIR"):
value = os.environ.get(key)
if value:
env[key] = value
env["PYTHONUNBUFFERED"] = "1"
env["AUREON_PUBLIC_REQUIRE_PIN"] = "1"
return env
async def _list_pages(method: Any) -> list[dict[str, Any]]:
pages: list[dict[str, Any]] = []
cursor: str | None = None
seen: set[str] = set()
while True:
result = await method() if cursor is None else await method(cursor=cursor)
page = _dump(result)
pages.append(page)
nxt = page.get("nextCursor")
if not isinstance(nxt, str) or not nxt or nxt in seen:
return pages
seen.add(nxt)
cursor = nxt
async def _exchange(session: ClientSession) -> dict[str, Any]:
initialized = await session.initialize()
tools_list = await _list_pages(session.list_tools)
resources_list = await _list_pages(session.list_resources)
prompts_list = await _list_pages(session.list_prompts)
called = await session.call_tool(TOOL_NAME, TOOL_ARGUMENTS)
return {
"initialize": _dump(initialized),
"prompts_list": prompts_list,
"resources_list": resources_list,
"tools_call": {
"arguments": TOOL_ARGUMENTS,
"name": TOOL_NAME,
"result": _dump(called),
},
"tools_list": tools_list,
}
async def _stdio_session() -> dict[str, Any]:
params = StdioServerParameters(
command=sys.executable,
args=["-m", "aureon_public"],
env=_server_env(),
cwd=str(Path.cwd()),
)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
return await _exchange(session)
def _free_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind((_BIND_HOST, 0))
return int(sock.getsockname()[1])
def _port_open(port: int) -> bool:
try:
with socket.create_connection((_BIND_HOST, port), timeout=0.2):
return True
except OSError:
return False
def _stop_process_group(proc: subprocess.Popen[str]) -> None:
if proc.poll() is not None:
return
try:
os.killpg(proc.pid, signal.SIGTERM)
except ProcessLookupError:
return
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
try:
os.killpg(proc.pid, signal.SIGKILL)
except ProcessLookupError:
return
proc.wait(timeout=5)
def _wait_until_listening(proc: subprocess.Popen[str], log_path: Path, port: int) -> None:
deadline = time.monotonic() + 30
while time.monotonic() < deadline:
if proc.poll() is not None:
raise RuntimeError(_log_tail(log_path) or f"server exited {proc.returncode}")
if _port_open(port):
return
time.sleep(0.1)
raise TimeoutError(_log_tail(log_path) or f"{_BIND_HOST}:{port} did not accept a connection")
def _log_tail(path: Path) -> str:
try:
text = path.read_text(encoding="utf-8", errors="replace")
except OSError:
return ""
return text[-4000:]
async def _http_session(port: int) -> dict[str, Any]:
url = f"http://{_BIND_HOST}:{port}/mcp"
log_fd, log_name = tempfile.mkstemp(prefix="aureon-public-http-")
os.close(log_fd)
log_path = Path(log_name)
log = log_path.open("w", encoding="utf-8")
proc = subprocess.Popen(
[
sys.executable,
"-m",
"aureon_public",
"--transport",
"streamable-http",
"--host",
_BIND_HOST,
"--port",
str(port),
],
cwd=str(Path.cwd()),
env=_server_env(),
stdout=log,
stderr=subprocess.STDOUT,
start_new_session=True,
text=True,
)
try:
_wait_until_listening(proc, log_path, port)
last: BaseException | None = None
for _attempt in range(8):
if proc.poll() is not None:
break
try:
body = await _exchange_http(url)
return {
"host": _BIND_HOST,
"port": port,
"url": url,
**body,
}
except Exception as exc: # noqa: BLE001 - the listener can accept TCP before MCP answers
last = exc
await asyncio.sleep(0.25)
tail = _log_tail(log_path)
raise RuntimeError(f"streamable http session failed: {last}\n{tail}")
finally:
log.close()
_stop_process_group(proc)
log_path.unlink(missing_ok=True)
async def _exchange_http(url: str) -> dict[str, Any]:
async with streamable_http_client(url) as (read, write, _get_session_id):
async with ClientSession(read, write) as session:
return await _exchange(session)
def _environment() -> dict[str, Any]:
os.environ["AUREON_PUBLIC_REQUIRE_PIN"] = "1"
enforce_public_pin()
import aureon
direct = None
try:
dist = importlib.metadata.distribution("aureon")
raw = dist.read_text("direct_url.json")
direct = json.loads(raw) if raw else None
except Exception as exc: # noqa: BLE001 - the record should say the pin was not readable
direct = {"error": type(exc).__name__}
from aureon_public.measurement_stamp import measurement_stamp
return {
"aureon_direct_url": direct,
"aureon_file": aureon.__file__,
"aureon_source": aureon_source(),
"mcp": importlib.metadata.version("mcp"),
"python": sys.executable,
"require_pin": "1",
**measurement_stamp(),
}
async def _run(out_path: Path) -> dict[str, Any]:
os.environ["AUREON_PUBLIC_REQUIRE_PIN"] = "1"
enforce_public_pin()
stdio = await _stdio_session()
http = await _http_session(_free_port())
record = {
"environment": _environment(),
"stdio": stdio,
"streamable_http": http,
}
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(record, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return record
def main(argv: list[str] | None = None) -> None:
parser = argparse.ArgumentParser(prog="aureon-public-protocol-measure")
parser.add_argument(
"--out",
type=Path,
default=paths.repo_root() / "measured" / "aureon_public_protocol.json",
)
args = parser.parse_args(argv)
asyncio.run(_run(args.out))
if __name__ == "__main__":
main()