Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fd5a054b3 | ||
|
|
da76a6b3d2 | ||
|
|
fc6d5628a7 | ||
|
|
1f7f53e6c0 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "skills",
|
"name": "skills",
|
||||||
"description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.",
|
"description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.",
|
||||||
"version": "0.18.0",
|
"version": "0.19.1",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Mathias",
|
"name": "Mathias",
|
||||||
"url": "https://git.d-ma.be/mathias"
|
"url": "https://git.d-ma.be/mathias"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"_comment": "Tailscale Serve on koala, not the public d-ma.be hostnames. The public path exits to Cloudflare and NPM to reach a k3s service on the same machine most sessions run on, which imposes rate limits and message-size caps on tool results. Serve terminates TLS with a real Tailscale cert and proxies to the NodePort over loopback, so traffic reaches the service from koala itself and satisfies the default-deny-ingress NetworkPolicy with no cluster change. Clients need 'tailscale set --accept-dns=true' for the MagicDNS name to resolve; see mathias/skills#24.",
|
"_comment": "Tailscale Serve on koala, not the public d-ma.be hostnames. The public path exits to Cloudflare and NPM to reach a k3s service on the same machine most sessions run on, which imposes rate limits and message-size caps on tool results. Serve terminates TLS with a real Tailscale cert and proxies to the NodePort over loopback, so traffic reaches the service from koala itself and satisfies the default-deny-ingress NetworkPolicy with no cluster change. Clients need 'tailscale set --accept-dns=true' for the MagicDNS name to resolve; see mathias/skills#24. Every ${VAR} below must be one ~/.op-env defines -- bin/check-mcp-env enforces that, because an undefined one sends an empty Authorization header and reports as 'requires authentication', which is indistinguishable from a revoked credential.",
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"brain": {
|
"brain": {
|
||||||
"type": "http",
|
"type": "http",
|
||||||
@@ -11,23 +11,24 @@
|
|||||||
"infra": {
|
"infra": {
|
||||||
"type": "http",
|
"type": "http",
|
||||||
"url": "https://koala.tail30472e.ts.net:8444/mcp"
|
"url": "https://koala.tail30472e.ts.net:8444/mcp"
|
||||||
|
},
|
||||||
|
"gitea": {
|
||||||
|
"_comment": "gitea-mcp authenticates by PASS-THROUGH: the bearer is a Gitea PAT, which the server validates against Gitea and then acts as. So no dedicated MCP token is needed, and the AgentSecrets blocker that parked this entry (skills#32, #33) never applied to it -- DMABE_GITEA_API_TOKEN is op://HomeLab and already in the session environment. Verified 2026-09-07: initialize 200 (serverInfo gitea-mcp) and tools/list returns 41 tools. Two DIFFERENT PATs both authenticate while GITEA_MCP_TOKEN 401s, which is what proves this is pass-through validation and not a static-token comparison.",
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://koala.tail30472e.ts.net:8446/mcp",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer ${DMABE_GITEA_API_TOKEN}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"_parked": {
|
"_parked": {
|
||||||
"_reason": "cad and gitea-mcp are proxied, reachable and AUTHENTICATING: tailscale serve 8445 -> 127.0.0.1:31877 and 8446 -> 127.0.0.1:30340; an MCP initialize with the correct bearer returns 200 from both (serverInfo gitea-mcp 0.1.0, cad aed2aaf). They stay parked only because nothing puts those two tokens into the session environment. Both live in the 1Password AgentSecrets vault, and by deliberate design (infra#224, closed) no single identity on koala reads both vaults: koala-homelab is HomeLab-only and resolves ~/.op-env, koala-agentsecrets-write is AgentSecrets-only and lives in ~/.op-agent-write.env. Adding an AgentSecrets reference to ~/.op-env does NOT degrade gracefully -- `op run` fails wholesale and every other token, BRAIN_MCP_TOKEN included, goes unresolved with it. Enabling needs a read-scoped AgentSecrets identity in the session-env path; see mathias/skills#32, #33.",
|
"_reason": "cad is proxied, reachable and AUTHENTICATING: tailscale serve 8445 -> 127.0.0.1:31877, and an MCP initialize with the correct bearer returns 200 (serverInfo cad aed2aaf). It stays parked only because nothing puts CAD_MCP_TOKEN into the session environment. It lives in the 1Password AgentSecrets vault, and by deliberate design (infra#224, closed) no single identity on koala reads both vaults: koala-homelab is HomeLab-only and resolves ~/.op-env, koala-agentsecrets-write is AgentSecrets-only and lives in ~/.op-agent-write.env. Adding an AgentSecrets reference to ~/.op-env does NOT degrade gracefully -- `op run` fails wholesale and every other token, BRAIN_MCP_TOKEN included, goes unresolved with it. Enabling needs a read-scoped AgentSecrets identity in the session-env path; see mathias/skills#32, #33. gitea was parked here for the same stated reason and has been unparked: it needs no AgentSecrets token at all, because gitea-mcp is pass-through-authenticated.",
|
||||||
"cad": {
|
"cad": {
|
||||||
"type": "http",
|
"type": "http",
|
||||||
"url": "https://koala.tail30472e.ts.net:8445/mcp",
|
"url": "https://koala.tail30472e.ts.net:8445/mcp",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Bearer ${CAD_MCP_TOKEN}"
|
"Authorization": "Bearer ${CAD_MCP_TOKEN}"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"gitea": {
|
|
||||||
"type": "http",
|
|
||||||
"url": "https://koala.tail30472e.ts.net:8446/mcp",
|
|
||||||
"headers": {
|
|
||||||
"Authorization": "Bearer ${GITEA_MCP_STATIC_TOKEN}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,26 @@ tasks:
|
|||||||
echo "ERROR: missing .claude-plugin/plugin.json" >&2; fail=1
|
echo "ERROR: missing .claude-plugin/plugin.json" >&2; fail=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Every ${VAR} a live .mcp.json server interpolates must be one the
|
||||||
|
# session env-file defines. An undefined one is silent: the header
|
||||||
|
# goes out empty and the server reports "requires authentication",
|
||||||
|
# which reads as a revoked credential. Exit 2 means the check could
|
||||||
|
# not run (no jq, or no env-file — CI has neither); that is reported
|
||||||
|
# and not fatal, but it is never treated as a pass.
|
||||||
|
if [ -x "{{.TASKFILE_DIR}}/bin/check-mcp-env" ]; then
|
||||||
|
# `|| rc=$?` is load-bearing, not style. Task runs this block under
|
||||||
|
# `sh -e`, so a bare invocation exiting non-zero kills the shell
|
||||||
|
# before `case` is reached -- the tolerance below never ran, and the
|
||||||
|
# gate died with `exit status 2` on the commit that added it.
|
||||||
|
rc=0
|
||||||
|
"{{.TASKFILE_DIR}}/bin/check-mcp-env" "{{.TASKFILE_DIR}}/.mcp.json" "$HOME/.op-env" || rc=$?
|
||||||
|
case $rc in
|
||||||
|
0) ;;
|
||||||
|
2) echo "NOTE: MCP env wiring unverified on this host" >&2 ;;
|
||||||
|
*) fail=1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $fail -ne 0 ]; then
|
if [ $fail -ne 0 ]; then
|
||||||
echo "check FAILED" >&2; exit 1
|
echo "check FAILED" >&2; exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
Executable
+89
@@ -0,0 +1,89 @@
|
|||||||
|
#!/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"
|
||||||
+21
-2
@@ -58,8 +58,8 @@ adr: 0004
|
|||||||
title: <the decision, as a sentence — not "database choice">
|
title: <the decision, as a sentence — not "database choice">
|
||||||
status: proposed | accepted | superseded | amended
|
status: proposed | accepted | superseded | amended
|
||||||
date: 2026-08-29
|
date: 2026-08-29
|
||||||
deciders: <who accepted it, by name — or "pending: <name>" while proposed>
|
deciders: <who accepted it, by name — or "pending: <name>" (QUOTED) while proposed>
|
||||||
supersedes: <adr number, or ->
|
supersedes: <adr number, or null>
|
||||||
---
|
---
|
||||||
|
|
||||||
# ADR-0004 — <same title>
|
# ADR-0004 — <same title>
|
||||||
@@ -97,6 +97,25 @@ the condition that fires a revisit. An ADR with no validation section is a prefe
|
|||||||
with a template around it.
|
with a template around it.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### The frontmatter must actually parse
|
||||||
|
|
||||||
|
It exists to be machine-read, so a block that does not load is worse than no block at
|
||||||
|
all: it drops that ADR silently out of every "what is still open?" query, while looking
|
||||||
|
correct. Two shapes this template used to suggest are invalid YAML, and both were found
|
||||||
|
live in the estate on 2026-09-05 — `openbanking-api` ADR-0001 and `infra` ADR-0019, each
|
||||||
|
copied verbatim from here:
|
||||||
|
|
||||||
|
| Wrong | Why | Right |
|
||||||
|
|---|---|---|
|
||||||
|
| `supersedes: -` | a bare dash is a YAML *sequence entry*, not a string | `supersedes: null` |
|
||||||
|
| `deciders: pending: Mathias` | the second, unquoted colon reads as a nested mapping | `deciders: "pending: Mathias"` |
|
||||||
|
|
||||||
|
Check before committing:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -c "import yaml,sys;yaml.safe_load(open(sys.argv[1]).read().split('---')[1]);print('ok')" docs/adr/0004-*.md
|
||||||
|
```
|
||||||
|
|
||||||
## Preserving a productive tension
|
## Preserving a productive tension
|
||||||
|
|
||||||
Some tradeoffs are not unresolved because nobody thought hard enough. They are
|
Some tradeoffs are not unresolved because nobody thought hard enough. They are
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# `task check` must survive a check that reports "could not verify".
|
||||||
|
#
|
||||||
|
# The regression these exist for: bin/check-mcp-env exits 2 to mean "I could
|
||||||
|
# not run — no jq, or no env-file", which the gate deliberately treats as a
|
||||||
|
# reported NOTE rather than a failure. The Taskfile said exactly that:
|
||||||
|
#
|
||||||
|
# "$DIR/check-mcp-env" "$DIR/.mcp.json" "$HOME/.op-env"
|
||||||
|
# case $? in 0) ;; 2) echo "NOTE: ..." ;; *) fail=1 ;; esac
|
||||||
|
#
|
||||||
|
# and it did not work. Task runs each cmds block under `sh -e`, so a bare
|
||||||
|
# command exiting non-zero aborts the shell *before* `case $?` is ever
|
||||||
|
# reached. The whole gate died with `exit status 2`. Locally the check found
|
||||||
|
# jq and exited 0, so it went green; CI has neither jq nor ~/.op-env, so main
|
||||||
|
# went red on the very commit that added it (skills run #57, 2026-09-07).
|
||||||
|
#
|
||||||
|
# A tolerated exit code that is only tolerated on the host where it never
|
||||||
|
# fires is not tolerated at all. This pins the tolerance itself.
|
||||||
|
#
|
||||||
|
# No mocking: pointing HOME at an empty directory removes ~/.op-env, which is
|
||||||
|
# exactly the condition CI runs under, so check-mcp-env genuinely exits 2.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
|
||||||
|
pass=0
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
ok() { pass=$((pass + 1)); printf ' ok %s\n' "$1"; }
|
||||||
|
no() { fail=$((fail + 1)); printf ' FAIL %s\n' "$1"; }
|
||||||
|
|
||||||
|
if ! command -v task >/dev/null 2>&1; then
|
||||||
|
printf ' SKIP: task not installed -- cannot exercise the gate\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
|
||||||
|
# Sanity: the precondition must actually hold, or the test proves nothing.
|
||||||
|
# An empty HOME means no ~/.op-env, so check-mcp-env cannot verify anything.
|
||||||
|
"$ROOT/bin/check-mcp-env" "$ROOT/.mcp.json" "$TMP/.op-env" >/dev/null 2>&1
|
||||||
|
rc=$?
|
||||||
|
if [ "$rc" -eq 2 ]; then
|
||||||
|
ok 'precondition: check-mcp-env exits 2 when the env-file is absent'
|
||||||
|
else
|
||||||
|
no "precondition: expected check-mcp-env to exit 2, got $rc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The assertion. `task check` must still pass, and must say why it could not
|
||||||
|
# verify -- silence would mean the check was skipped rather than tolerated.
|
||||||
|
out=$(HOME="$TMP" task --dir "$ROOT" check 2>&1)
|
||||||
|
rc=$?
|
||||||
|
|
||||||
|
if [ "$rc" -eq 0 ]; then
|
||||||
|
ok 'task check passes when MCP env wiring cannot be verified'
|
||||||
|
else
|
||||||
|
no "task check exited $rc when check-mcp-env reported unverifiable"
|
||||||
|
printf '%s\n' "$out" | sed 's/^/ | /' | tail -8
|
||||||
|
fi
|
||||||
|
|
||||||
|
if printf '%s' "$out" | grep -q 'unverified'; then
|
||||||
|
ok 'the unverifiable check is reported, not silently skipped'
|
||||||
|
else
|
||||||
|
no 'no NOTE reported -- an unverifiable check must never look like a pass'
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf ' %d passed, %d failed\n' "$pass" "$fail"
|
||||||
|
[ "$fail" -eq 0 ]
|
||||||
Executable
+118
@@ -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 ]
|
||||||
Reference in New Issue
Block a user