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>
90 lines
2.7 KiB
Bash
Executable File
90 lines
2.7 KiB
Bash
Executable File
#!/bin/sh
|
|
# check-mcp-env — assert every variable a live MCP server interpolates is one
|
|
# the session env-file actually defines.
|
|
#
|
|
# Why this is a gate and not a comment: the failure is silent. A header of
|
|
# "Bearer ${FOO}" where FOO is undefined does not error -- it goes out empty,
|
|
# the server answers 401, and Claude Code reports "requires authentication",
|
|
# which reads exactly like a revoked credential. On 2026-09-07 that cost a
|
|
# full investigation on koala before the credential turned out to be fine.
|
|
#
|
|
# Servers under `_parked` are exempt by design. Parked means "reachable, but
|
|
# nothing puts its token in the environment yet" -- flagging those would
|
|
# report the intended state as a fault.
|
|
#
|
|
# Usage: check-mcp-env <mcp.json> <env-file>
|
|
#
|
|
# Exit 0 every live server's variables are defined
|
|
# 1 at least one is not, named on stderr
|
|
# 2 could not verify (bad usage, unreadable input, jq absent).
|
|
# Deliberately distinct from 0: an unverified config is not a clean
|
|
# one, and a gate that cannot run should say so rather than pass.
|
|
|
|
set -u
|
|
|
|
CONFIG=${1:-}
|
|
ENVFILE=${2:-}
|
|
|
|
if [ -z "$CONFIG" ] || [ -z "$ENVFILE" ]; then
|
|
echo "usage: check-mcp-env <mcp.json> <env-file>" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
echo "SKIP: jq not installed -- MCP env wiring unverified" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [ ! -f "$CONFIG" ]; then
|
|
echo "ERROR: no such config: $CONFIG" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [ ! -f "$ENVFILE" ]; then
|
|
echo "ERROR: no such env-file: $ENVFILE -- cannot verify MCP variables" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Every "<server>\t<VAR>" pair a live server interpolates. `..` walks the whole
|
|
# value, so a variable is found wherever it is placed, not only under headers.
|
|
refs=$(jq -r '
|
|
.mcpServers // {}
|
|
| to_entries[]
|
|
| .key as $server
|
|
| (.value | .. | strings)
|
|
| select(test("\\$\\{"))
|
|
| [ scan("\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}")[] ]
|
|
| .[]
|
|
| "\($server)\t\(.)"
|
|
' "$CONFIG" 2>/dev/null) || {
|
|
echo "ERROR: $CONFIG is not valid JSON -- MCP env wiring unverified" >&2
|
|
exit 2
|
|
}
|
|
|
|
if [ -z "$refs" ]; then
|
|
echo "check-mcp-env: no interpolated variables in live servers"
|
|
exit 0
|
|
fi
|
|
|
|
fail=0
|
|
checked=0
|
|
for pair in $(printf '%s\n' "$refs" | tr '\t' ':'); do
|
|
server=${pair%%:*}
|
|
var=${pair#*:}
|
|
checked=$((checked + 1))
|
|
if grep -qE "^[[:space:]]*(export[[:space:]]+)?$var=" "$ENVFILE"; then
|
|
continue
|
|
fi
|
|
printf 'ERROR: MCP server %s interpolates %s, which %s does not define.\n' \
|
|
"$server" "$var" "$ENVFILE" >&2
|
|
printf ' It will send an empty Authorization header and report as "requires authentication".\n' >&2
|
|
fail=1
|
|
done
|
|
|
|
if [ "$fail" -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
|
|
printf 'check-mcp-env: %d interpolated variable(s) across live MCP servers, all defined in %s\n' \
|
|
"$checked" "$ENVFILE"
|