Segun Olumbe

unified verdict · mcp

gate

One pass, warn, or fail from four checks. Same answer in CI, as an official MCP server an agent can call, and as a VS Code / Cursor extension that puts the verdict in your status bar and underlines what failed. Install this first: it runs the checks and gives one answer.

  • Node.js
  • JavaScript (ESM)
  • MCP over stdio
  • JSON-RPC 2.0
  • VS Code extension API
  • TypeScript
  • GitHub Actions

Problem

I had four tools that each answered a different version of the same question, can this ship, and each answered in its own vocabulary. CI ran four jobs, an agent had four tools to call, and nobody, human or model, got one answer. And a check that did not run must never look like a check that passed.

Constraints

  • A check that did not run is never a pass.
  • It has to be safe in a repository that uses only some of the tools.
  • The same answer in CI, in the editor, and to an agent, from one JSON document.
  • A team can adopt it without drowning in warnings: only a failure blocks by default.
  • No runtime dependencies.

Architecture

A small engine with three ways in. Whoever asks, the same four checks run and the same JSON comes back. Read it top to bottom.

  1. Who asks

    • CLI and CIOne command in a terminal, or with a CI flag to fail the build on a blocking verdict.
    • AgentCalls gate_check over MCP before it says a change is done.
    • EditorA VS Code and Cursor extension: status bar, a verdict cockpit, inline diagnostics, and an @gate chat participant.
  2. Orchestrator

    • Tool resolverFinds each tool at runtime: an environment override, then the installed package, then a sibling checkout.
    • RunnerRuns the four tools in parallel as child processes, with a timeout, and captures everything they print.
  3. Adapters

    • AI governanceaiglare: red, amber, or green surfaces.
    • Compliancebouncer: pass, fail, or unknown for each required control.
    • Contract drifttieline: frontend calls that reach no backend route.
    • Merge readinessSecret safety, risk review, and required checks for the change.
  4. Verdict

    • NormaliseEvery dialect mapped onto pass, warn, fail, unknown, skipped, or error.
    • Roll upThe worst status among the checks that ran. Unknown and error count as a warning; skipped does not count.
    • ReportA terminal summary, or versioned JSON with each domain and the blocking reasons.

Release

  • npmPublished from CI with OIDC trusted publishing, after the full quality gate.
  • MCP RegistryThe server manifest is published in the same release.
  • Extension storesOne tag publishes the extension to the VS Code Marketplace and Open VSX.

Key decisions

  1. One verdict from four dialects

    Instead of four separate reports

    Each tool speaks its own language: red, amber, and green; pass, fail, and unknown; matched and drift. gate maps all of them onto one status, so CI, an agent, and the editor read the same answer.

  2. Unknown is never a pass

    Instead of treating anything short of a failure as green

    Unknown and error roll up to a warning, and a skipped check does not count at all. If nothing ran, gate says no checks ran and fails the build, so a mistyped flag cannot turn the gate green.

  3. Finding the tools at runtime

    Instead of bundling all four

    gate looks for each tool in three places and reports one it cannot find as skipped, not as a failure. That makes it safe in a repository that uses only part of the toolchain.

  4. Deriving the blocking verdict itself

    Instead of trusting each tool's exit code

    gate runs a tool without its own CI flag and reads the whole report. A tool that exits before it has flushed its output cannot truncate what gate sees, and a non-zero exit is expected, not treated as a crash.

  5. A hand-written MCP server

    Instead of an SDK dependency

    The server is line-delimited JSON-RPC 2.0 over stdio in under 200 lines, the same convention as the other tools. The package ships with no runtime dependencies at all.

  6. Blocking on failure only by default

    Instead of blocking on every warning from day one

    A team can turn it on without being buried. A strict flag makes warnings block too, for when they are ready.

What broke, and the fix

  1. The editor ran out of findings to underline.

    Cause
    Findings were capped at five per domain. That was a nicety for the terminal that had leaked into the JSON, so any repository with more than a handful of findings lost its squiggles.
    Fix
    The cap came out of the data model. The list is bounded only by a runaway guard of 200, and the summary counts always carried the true totals.
  2. Saving a file could stack up full-repository runs.

    Cause
    The extension ran the gate on save, and nothing stopped one run overlapping the next.
    Fix
    Saves are debounced, and a newer run supersedes the one in flight and kills its child process.

Outcome

  • Published on npm, the MCP Registry, the VS Code Marketplace, and Open VSX.
  • The engine is about 900 lines with no runtime dependencies, covered by 60 tests.
  • One JSON document drives the CLI, the CI gate, the MCP server, and the editor extension, so the assistant writing the code is checked by the same gate as CI.