#!/usr/bin/env bash
# Deterministic missing-tool envelope (Task 502, directive 3).
#
# PostToolUse hook. When a maxy MCP tool call resolves to "No such tool
# available: mcp__..." (the failure class Task 502's name-binding is designed to
# eliminate, kept here as defence-in-depth for a server that failed to start or
# a name that slipped the build gate), this hook replaces the agent's latitude
# to narrate ("the servers were still warming up") and blind-retry with a fixed
# failure envelope, and logs one deterministic diagnostic line that names the
# server and tool so a future occurrence identifies itself.
#
# Scope: maxy servers only. A missing non-maxy bridge tool (Playwright etc.,
# upstream-owned) is passed through untouched. Maxy plugins are read from the
# generated list at lib/maxy-mcp-plugins.txt (kept current by the build gate).
#
# Output:
#   stderr (diagnostic): [mcp-tool-missing] server=<server> tool=<tool>
#   stderr (envelope, exit 2): the fixed message the agent must relay verbatim.
#
# A maxy missing-tool match exits 2 so PostToolUse feeds the envelope back to
# the model as the authoritative result. Every other path exits 0 (no-op).

set -uo pipefail

if [ -t 0 ]; then
  exit 0
fi
INPUT=$(cat)

PLUGINS_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/maxy-mcp-plugins.txt"

# Parse hook_event_name, tool_name, and the tool_response text in one pass.
PARSED=$(printf '%s' "$INPUT" | python3 -c '
import sys, json
try:
    d = json.load(sys.stdin)
except Exception:
    print("\t\t"); sys.exit(0)
event = d.get("hook_event_name", "") or ""
tool = d.get("tool_name", "") or ""
resp = d.get("tool_response", "")
# tool_response may be a string, a dict, or a list of content blocks. Flatten
# to text so the "No such tool available" probe sees it whatever the shape.
text = json.dumps(resp) if not isinstance(resp, str) else resp
print(event + "\t" + tool + "\t" + text.replace("\n", " "))
' 2>/dev/null)

HOOK_EVENT="${PARSED%%$'\t'*}"
REST="${PARSED#*$'\t'}"
TOOL_NAME="${REST%%$'\t'*}"
RESP_TEXT="${REST#*$'\t'}"

[ "$HOOK_EVENT" = "PostToolUse" ] || exit 0
case "$TOOL_NAME" in mcp__*) ;; *) exit 0 ;; esac
# Only act on the missing-tool failure class; a tool that ran is none of our
# business.
case "$RESP_TEXT" in *"No such tool available"*) ;; *) exit 0 ;; esac

# Resolve <server> and <tool> from the qualified name.
#   long:  mcp__plugin_<server>_<server>__<tool>
#   short: mcp__<server>__<tool>
SERVER=""
TOOL=""
case "$TOOL_NAME" in
  mcp__plugin_*)
    inner="${TOOL_NAME#mcp__plugin_}"      # <server>_<server>__<tool>
    SERVER="${inner%%__*}"                 # <server>_<server>
    SERVER="${SERVER%%_*}"                 # <server>  (plugin == server)
    TOOL="${TOOL_NAME##*__}"
    ;;
  mcp__*)
    inner="${TOOL_NAME#mcp__}"             # <server>__<tool>
    SERVER="${inner%%__*}"
    TOOL="${TOOL_NAME##*__}"
    ;;
esac
[ -n "$SERVER" ] || exit 0

# Maxy server? A non-maxy bridge (Playwright shares the long-prefix shape) is
# upstream-owned — pass it through.
if [ ! -f "$PLUGINS_FILE" ] || ! grep -qxF "$SERVER" "$PLUGINS_FILE"; then
  exit 0
fi

echo "[mcp-tool-missing] server=${SERVER} tool=${TOOL}" >&2
cat >&2 <<EOF
The maxy tool ${TOOL_NAME} is not available in this session. This is a
deterministic failure of the ${SERVER} MCP server — either it failed to start
on this device or the tool name is not registered. Reply once telling the
operator the ${SERVER} server is unavailable and to run the admin system-status
check; do not retry this call, do not guess another tool name, do not describe
the servers as "warming up". Then stop.
EOF
exit 2
