feat(mcp): unpark the gitea server — it needs no AgentSecrets token
CI / Check / Test (push) Failing after 1s
CI / Tag release (push) Skipped

gitea-mcp was parked alongside cad on the stated reason that nothing puts its
token into the session environment: GITEA_MCP_STATIC_TOKEN lives in the
AgentSecrets vault, which the session identity deliberately cannot read
(infra#224), so enabling it was blocked behind #32/#33.

That reason never applied to gitea. gitea-mcp authenticates by PASS-THROUGH:
the bearer is a Gitea PAT, which the server validates against Gitea and then
acts as. No dedicated MCP token exists to be missing. DMABE_GITEA_API_TOKEN is
op://HomeLab and already in the session environment.

Verified on koala 2026-09-07 against tailscale serve 8446 -> NodePort 30340:
initialize returns 200 with serverInfo gitea-mcp, tools/list returns 41 tools.
Two DIFFERENT PATs both authenticate while GITEA_MCP_TOKEN 401s -- that is what
proves pass-through validation rather than a static-token comparison, and it
also shows GITEA_MCP_TOKEN is stale (it 401s against git-mcp.d-ma.be too).

cad stays parked; its blocker is real and unchanged.

Also adds bin/check-mcp-env and tests/mcp_config_test.sh, wired into task check.
The fault it gates is silent: a header interpolating an undefined variable does
not error, it sends an empty Authorization, and Claude Code reports "requires
authentication" -- indistinguishable from a revoked credential. That cost a full
investigation on koala today before the credential turned out to be fine. The
check exits 2 rather than 0 when it cannot run (no jq, no env-file, as in CI),
so an unverified config is never reported as a clean one. Servers under _parked
are exempt: referencing an unprovided variable is what parked means.

Bump-Type: minor

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 07:24:23 +02:00
co-authored by Claude Opus 5
parent fc6d5628a7
commit da76a6b3d2
5 changed files with 233 additions and 10 deletions
+118
View File
@@ -0,0 +1,118 @@
#!/bin/sh
# Tests for bin/check-mcp-env.
#
# The fault this gate exists for is silent, which is why it needs a gate at
# all. An `.mcp.json` server whose Authorization header interpolates a
# variable nothing in the session environment defines does not error: the
# header goes out empty, the server answers 401, and Claude Code reports
# "requires authentication". That is indistinguishable from a revoked or
# expired credential, and on 2026-09-07 it cost a full investigation on koala
# to establish that the credential was fine and the variable was simply never
# set.
#
# So the property is: every ${VAR} a LIVE server interpolates must be a
# variable the session env-file actually defines.
#
# The `_parked` block is deliberately exempt. Parked servers reference
# variables nothing provides on purpose -- that is what parked means -- and a
# gate that flagged them would be reporting the intended state as a fault.
#
# Fixtures drive the checker through argv, so no test touches the real host.
set -u
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
CHECK="$ROOT/bin/check-mcp-env"
pass=0
fail=0
WORK=${TMPDIR:-/tmp}/mcp_config_test.$$
if [ ! -f "$CHECK" ]; then
printf 'FAIL: %s does not exist\n' "$CHECK"
exit 1
fi
cleanup() { rm -rf "$WORK"; }
trap cleanup EXIT INT TERM
mkdir -p "$WORK"
if ! command -v jq >/dev/null 2>&1; then
printf 'SKIP: jq not installed -- bin/check-mcp-env cannot be exercised\n'
exit 0
fi
# ok <name> / bad <name> <detail>
ok() { pass=$((pass+1)); printf ' ok %s\n' "$1"; }
bad() { fail=$((fail+1)); printf ' FAIL %s %s\n' "$1" "${2:-}"; }
# run <mcp-json> <env-file> -> sets $rc and $out
run() {
out=$("$CHECK" "$1" "$2" 2>&1); rc=$?
}
ENVFILE="$WORK/op-env"
cat > "$ENVFILE" <<'ENVEOF'
BRAIN_MCP_TOKEN=op://HomeLab/brain/token
DMABE_GITEA_API_TOKEN=op://HomeLab/gitea/token
ENVEOF
# --- a live server whose variable IS defined ---------------------------------
cat > "$WORK/good.json" <<'JSONEOF'
{"mcpServers":{"brain":{"type":"http","url":"https://x/mcp",
"headers":{"Authorization":"Bearer ${BRAIN_MCP_TOKEN}"}}}}
JSONEOF
run "$WORK/good.json" "$ENVFILE"
[ "$rc" -eq 0 ] && ok "defined variable passes" || bad "defined variable passes" "rc=$rc $out"
# --- a live server whose variable is NOT defined ------------------------------
# The exact shape that broke: the server looks correct and fails at runtime.
cat > "$WORK/missing.json" <<'JSONEOF'
{"mcpServers":{"gitea":{"type":"http","url":"https://x/mcp",
"headers":{"Authorization":"Bearer ${GITEA_MCP_STATIC_TOKEN}"}}}}
JSONEOF
run "$WORK/missing.json" "$ENVFILE"
if [ "$rc" -ne 0 ]; then
case "$out" in
*gitea*GITEA_MCP_STATIC_TOKEN*) ok "undefined variable is flagged, naming server and variable" ;;
*) bad "undefined variable is flagged, naming server and variable" "report did not name both: $out" ;;
esac
else
bad "undefined variable is flagged, naming server and variable" "rc=0"
fi
# --- a server with no headers at all (the `infra` shape) ----------------------
# Unauthenticated by design. Nothing to resolve, so nothing to flag.
cat > "$WORK/noauth.json" <<'JSONEOF'
{"mcpServers":{"infra":{"type":"http","url":"https://x/mcp"}}}
JSONEOF
run "$WORK/noauth.json" "$ENVFILE"
[ "$rc" -eq 0 ] && ok "header-less server is not flagged" || bad "header-less server is not flagged" "rc=$rc $out"
# --- parked servers are exempt ------------------------------------------------
cat > "$WORK/parked.json" <<'JSONEOF'
{"mcpServers":{"brain":{"type":"http","url":"https://x/mcp",
"headers":{"Authorization":"Bearer ${BRAIN_MCP_TOKEN}"}}},
"_parked":{"_reason":"no identity provides these yet",
"cad":{"type":"http","url":"https://x/mcp",
"headers":{"Authorization":"Bearer ${CAD_MCP_TOKEN}"}}}}
JSONEOF
run "$WORK/parked.json" "$ENVFILE"
if [ "$rc" -eq 0 ]; then
ok "parked server is exempt"
else
bad "parked server is exempt" "rc=$rc $out"
fi
# --- malformed JSON must not pass silently ------------------------------------
# A config the checker cannot read is an unverified config, not a clean one.
printf '{"mcpServers":{' > "$WORK/broken.json"
run "$WORK/broken.json" "$ENVFILE"
[ "$rc" -ne 0 ] && ok "unparseable config does not report success" || bad "unparseable config does not report success" "rc=0"
# --- a missing env-file is unverifiable, not clean ----------------------------
run "$WORK/good.json" "$WORK/no-such-env"
[ "$rc" -ne 0 ] && ok "missing env-file does not report success" || bad "missing env-file does not report success" "rc=0"
printf '\n%s: %d passed, %d failed\n' "$(basename "$0")" "$pass" "$fail"
[ "$fail" -eq 0 ]