#!/usr/bin/env bash # Compare MCP tools/list on Python :8000 and Go :8001 when both are up. set -euo pipefail PY_URL="${PY_URL:-http://127.0.0.1:8000/mcp}" GO_URL="${GO_URL:-http://127.0.0.1:8001/mcp}" WANT_TOOLS="${WANT_TOOLS:-stamp verify hash_report signup login session create_user}" need() { command -v "$1" >/dev/null 2>&1 || { echo "parity_smoke: need $1" >&2; exit 2; }; } need curl need python3 port_up() { local hostport="$1" python3 - "$hostport" <<'PY' import socket, sys host, _, port = sys.argv[1].rpartition(":") s = socket.socket() s.settimeout(0.5) try: s.connect((host, int(port))) except OSError: sys.exit(1) finally: s.close() PY } # Print sorted tool names (space-separated) for one MCP Streamable HTTP endpoint. mcp_tools_list() { local url="$1" local label="$2" local hdr body code session hdr="$(mktemp)" body="$(mktemp)" trap 'rm -f "$hdr" "$body"' RETURN code="$(curl -sS -D "$hdr" -o "$body" -w '%{http_code}' \ -X POST "$url" \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"parity-smoke","version":"0.0.1"}}}')" || true if [[ "$code" != "200" && "$code" != "202" ]]; then echo "parity_smoke: $label initialize HTTP $code at $url" >&2 cat "$body" >&2 || true return 1 fi session="$(python3 - "$hdr" <<'PY' import sys session = "" for line in open(sys.argv[1], encoding="utf-8", errors="replace"): if ":" not in line: continue k, v = line.split(":", 1) if k.strip().lower() == "mcp-session-id": session = v.strip() print(session) PY )" local -a headers=( -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' ) if [[ -n "$session" ]]; then headers+=(-H "Mcp-Session-Id: $session") fi curl -sS -o /dev/null \ -X POST "$url" \ "${headers[@]}" \ --data '{"jsonrpc":"2.0","method":"notifications/initialized"}' || true code="$(curl -sS -o "$body" -w '%{http_code}' \ -X POST "$url" \ "${headers[@]}" \ --data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}')" || true if [[ "$code" != "200" ]]; then echo "parity_smoke: $label tools/list HTTP $code at $url" >&2 cat "$body" >&2 || true return 1 fi python3 - "$body" <<'PY' import json, sys raw = open(sys.argv[1], encoding="utf-8", errors="replace").read().strip() text = raw if raw and not raw.lstrip().startswith("{") and "data:" in raw: parts = [line[5:].strip() for line in raw.splitlines() if line.startswith("data:")] text = parts[-1] if parts else raw doc = json.loads(text) result = doc.get("result") or doc tools = result.get("tools") or [] names = sorted({t.get("name") for t in tools if isinstance(t, dict) and t.get("name")}) print(" ".join(names)) PY } sorted_words() { # shellcheck disable=SC2086 printf '%s\n' $1 | sed '/^$/d' | sort -u | tr '\n' ' ' | sed 's/ *$//' } if ! port_up 127.0.0.1:8000; then echo "parity_smoke: Python MCP not listening on 127.0.0.1:8000" >&2 exit 1 fi if ! port_up 127.0.0.1:8001; then echo "parity_smoke: Go MCP not listening on 127.0.0.1:8001" >&2 exit 1 fi echo "parity_smoke: listing tools on Python $PY_URL" py_tools="$(mcp_tools_list "$PY_URL" python)" echo " python: $py_tools" echo "parity_smoke: listing tools on Go $GO_URL" go_tools="$(mcp_tools_list "$GO_URL" go)" echo " go: $go_tools" py_n="$(sorted_words "$py_tools")" go_n="$(sorted_words "$go_tools")" want_n="$(sorted_words "$WANT_TOOLS")" if [[ "$py_n" != "$go_n" ]]; then echo "parity_smoke: FAIL tools/list mismatch" >&2 echo " python: $py_n" >&2 echo " go: $go_n" >&2 exit 1 fi for t in $WANT_TOOLS; do if ! printf '%s\n' $go_tools | grep -qx "$t"; then echo "parity_smoke: FAIL missing expected tool $t (want $want_n)" >&2 exit 1 fi done echo "parity_smoke: OK tools/list match ($go_n)"