hush/scripts/install-mcp.sh
jx12n 62c95f8c2f docs, ops scripts, and the MCP install
Written after the service was live, so every command and every number here was
run against the real deployment rather than assumed:

  * DEPLOY.md records the things a rebuild needs and git does not hold — the
    Redis ACL user (and why +getdel is the one to notice), the GCP Secret
    Manager entry, the DNS record, and the three coordinated edits vmalert
    needs because it has no ConfigMap auto-discovery.
  * It also records two blockers rather than hiding them: Woodpecker is NOT
    activated (the token in rdev-credentials returns 401), so pushes do not
    deploy yet and the Kaniko Job is the interim path; and the host is
    hush.threesix.ai rather than hush.orchard9.ai because orchard9.ai is on
    GoDaddy and no GoDaddy credential exists anywhere I can reach.
  * OPERATIONS.md is one section per alert, plus the failure modes that are not
    alerts — chiefly that "gone" cannot distinguish already-revealed from
    expired from LRU-evicted, on purpose, so the operator's default reading of
    an unexpected "gone" is that the secret is compromised and should be
    rotated.
  * scripts/logs.sh and alerts-check.sh verify rather than assert:
    alerts-check asks vmalert what it actually loaded AND checks each rule's
    series exists, because a rule reading a metric nothing exports can never
    fire and looks exactly like a healthy service.
  * scripts/smoke.sh is a real client — it generates a key, encrypts, posts only
    ciphertext, reveals, decrypts, then asserts the second reveal is 410, that
    three GETs did not consume the secret, that missing and malformed ids are
    indistinguishable, and that a plaintext field is refused.

install-mcp.sh proves the MCP handshake before writing any config, backs up
mcp.json, and rewrites only hush's entry — a config pointing at a broken server
surfaces as an opaque host-side connect failure, which is worth one extra check
to avoid.
2026-09-03 00:18:53 -06:00

80 lines
2.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build hush-mcp and register it with omp.
#
# The server runs LOCALLY and does the AES-256-GCM itself. That is the point: if
# hushd served an MCP endpoint and encrypted server-side, the server could read
# every secret created through MCP, and hush's guarantee would hold for browser
# users while quietly not holding for agent users. This binary is a peer of the
# browser, not of the server.
#
# Idempotent: re-running rebuilds the binary and rewrites only hush's entry in
# ~/.omp/agent/mcp.json, leaving every other server alone.
set -euo pipefail
BIN_DIR="${BIN_DIR:-$HOME/.local/bin}"
MCP_JSON="${MCP_JSON:-$HOME/.omp/agent/mcp.json}"
BASE_URL="${HUSH_BASE_URL:-https://hush.threesix.ai}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
echo "building hush-mcp"
mkdir -p "$BIN_DIR"
(cd "$ROOT" && go build -trimpath -ldflags="-s -w" -o "$BIN_DIR/hush-mcp" ./cmd/hush-mcp)
echo " installed $BIN_DIR/hush-mcp"
# Prove the binary speaks MCP before wiring it in. A config pointing at a broken
# server surfaces as an opaque host-side connect failure, so the handshake is
# checked here where the error is legible.
echo "verifying the MCP handshake"
HANDSHAKE=$(printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| HUSH_BASE_URL="$BASE_URL" "$BIN_DIR/hush-mcp" 2>/dev/null)
echo "$HANDSHAKE" | python3 -c '
import json, sys
tools = None
for line in sys.stdin:
m = json.loads(line)
if m.get("id") == 1:
print(" protocol", m["result"]["protocolVersion"], "server", m["result"]["serverInfo"]["name"])
if m.get("id") == 2:
tools = [t["name"] for t in m["result"]["tools"]]
if not tools:
sys.exit(" the server did not answer tools/list")
print(" tools:", ", ".join(tools))
'
echo "registering with omp at $MCP_JSON"
mkdir -p "$(dirname "$MCP_JSON")"
[ -f "$MCP_JSON" ] || printf '{"mcpServers":{}}\n' > "$MCP_JSON"
# Back up before touching a config that may hold other servers.
cp "$MCP_JSON" "$MCP_JSON.bak.$(date +%Y%m%d%H%M%S)"
BIN="$BIN_DIR/hush-mcp" BASE="$BASE_URL" TARGET="$MCP_JSON" python3 - <<'PY'
import json, os
target = os.environ["TARGET"]
with open(target) as f:
cfg = json.load(f)
cfg.setdefault("$schema",
"https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json")
servers = cfg.setdefault("mcpServers", {})
# stdio, not http: the encryption has to happen on this machine.
servers["hush"] = {
"type": "stdio",
"command": os.environ["BIN"],
"env": {"HUSH_BASE_URL": os.environ["BASE"]},
"timeout": 20000,
}
with open(target, "w") as f:
json.dump(cfg, f, indent=2)
f.write("\n")
print(" servers now configured:", ", ".join(sorted(servers)))
PY
echo
echo "done. Restart omp to pick up the new server, then: hush_create / hush_reveal"