infra#368 added jq to the gitea-runner image on the stated grounds that check-mcp-env would then verify in CI instead of skipping. Verified after rolling baked-v16: jq IS present now, the `SKIP: jq not installed` branch is gone -- and the check still reports unverified, because CI has no ~/.op-env. The env-file branch was always the binding one. It holds live credentials, so it is not on the runner and must not be, which means this check can only ever verify on a host that has one. Putting ~/.op-env on the runner to turn the NOTE green would be trading a real secret for a green tick. So the message now says so, at the point someone reads it. The risk otherwise is not the skip -- it is a future reader seeing a passing pipeline and concluding the MCP wiring is covered. It is covered on developer hosts only. jq was still worth adding: it is a genuine dependency of this checker, the runner's tool-contract.txt now asserts it, and a future image built without it is a red pod at roll time rather than a silently degraded check. Bump-Type: patch Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
100 lines
3.3 KiB
Bash
Executable File
100 lines
3.3 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
|
|
# EXPECTED IN CI, and permanently so. The env-file holds live credentials, so
|
|
# it is not on the runner and must not be -- which means this check can only
|
|
# ever verify on a host that has one. infra#368 added jq to the runner image
|
|
# believing that would let CI verify; it removed the jq branch below and
|
|
# changed nothing else, because this branch is the binding one.
|
|
#
|
|
# Said plainly so nobody concludes CI covers this. It does not, it cannot,
|
|
# and putting ~/.op-env on the runner to make it would be trading a real
|
|
# secret for a green tick.
|
|
echo "ERROR: no such env-file: $ENVFILE -- cannot verify MCP variables" >&2
|
|
echo " (expected in CI: this check only verifies on a host with ~/.op-env)" >&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"
|