Slice of #164 — close the loop on the client side.
#164 shipped the server-side runtime pieces: enforce (seatbelt policy generation + proxy wiring) and wrap/protect (fail-closed handshake against a passing receipt), plus the ObservatoryMonitor runtime. The honest remaining gap is a preflight gate-check that clients (opencode / Claude Code / Cursor / Codex) can query before allowing a connection, driven by declarative policy-as-code ("require score >= 70, no HIGH findings").
Proposed shape
mcp-observatory gate-check <target...> # fresh scan (default)
mcp-observatory gate-check --target config.json
mcp-observatory gate-check --receipt <name> # no rescan: latest run artifact
mcp-observatory gate-check --all # every discovered client server
--policy <file> --json --quiet
Exit 0 when policy is satisfied, 1 otherwise. Fail closed on missing/stale receipt, fatal error, or missing policy.
Policy file — observatory-side .mcp-observatory/gate-policy.json (JSON + zod, no new deps), resolved by walking up from cwd, documented defaults:
{ "version": 1, "minScore": 70,
"maxFindings": { "high": 0, "medium": 0, "low": -1 },
"failClosed": true, "freshness": { "maxAgeDays": 7 } }
The seatbelt policy schema stays unchanged (tool-level runtime rules validated by a separate package); connection thresholds live next to the receipts in .mcp-observatory/.
Reuse: runTarget/writeRunArtifact (enforce path), findLatestArtifact/readArtifact/defaultRunsDirectory (handshake path), artifact.healthScore ?? computeHealthScore (monitor idiom), extractObservatoryFindings (severity counts), scanForTargets/defaultConfigPaths (discovery, for --all), assertPassingReceipt semantics (fail-closed + remediation).
Acceptance: per-condition PASS/FAIL output; --json single machine-parseable object; --all exit 1 if any discovered server fails; fatalError always fails closed; npm test green; no new runtime deps.
Out of scope: extending the mcp-seatbelt YAML schema, proxy/middleware, per-session re-checking, agent-native integrations.
Effort: M (~2–3 days). S if reduced to --receipt-only with hardcoded defaults.
Slice of #164 — close the loop on the client side.
#164 shipped the server-side runtime pieces:
enforce(seatbelt policy generation + proxy wiring) andwrap/protect(fail-closed handshake against a passing receipt), plus theObservatoryMonitorruntime. The honest remaining gap is a preflightgate-checkthat clients (opencode / Claude Code / Cursor / Codex) can query before allowing a connection, driven by declarative policy-as-code ("require score >= 70, no HIGH findings").Proposed shape
Exit 0 when policy is satisfied, 1 otherwise. Fail closed on missing/stale receipt, fatal error, or missing policy.
Policy file — observatory-side
.mcp-observatory/gate-policy.json(JSON + zod, no new deps), resolved by walking up from cwd, documented defaults:{ "version": 1, "minScore": 70, "maxFindings": { "high": 0, "medium": 0, "low": -1 }, "failClosed": true, "freshness": { "maxAgeDays": 7 } }The seatbelt policy schema stays unchanged (tool-level runtime rules validated by a separate package); connection thresholds live next to the receipts in
.mcp-observatory/.Reuse:
runTarget/writeRunArtifact(enforce path),findLatestArtifact/readArtifact/defaultRunsDirectory(handshake path),artifact.healthScore ?? computeHealthScore(monitor idiom),extractObservatoryFindings(severity counts),scanForTargets/defaultConfigPaths(discovery, for--all),assertPassingReceiptsemantics (fail-closed + remediation).Acceptance: per-condition PASS/FAIL output;
--jsonsingle machine-parseable object;--allexit 1 if any discovered server fails;fatalErroralways fails closed;npm testgreen; no new runtime deps.Out of scope: extending the mcp-seatbelt YAML schema, proxy/middleware, per-session re-checking, agent-native integrations.
Effort: M (~2–3 days). S if reduced to
--receipt-only with hardcoded defaults.