# Add a CI Gate for MCP Contract Coverage in 10 Minutes

> Source: <https://dev.to/kioiek/add-a-ci-gate-for-mcp-contract-coverage-in-10-minutes-2ga0>
> Published: 2026-06-18 12:17:00+00:00

Your PR is green. `tools/call`

still breaks on Tuesday.

That gap is familiar: **CI validates what you ship**, not **what your agent consumes**. Cursor and Claude read `mcp.json`

(or `.cursor/mcp.json`

) and trust whatever `tools/list`

returns today. When a vendor removes a tool or tightens `inputSchema`

, your pipeline does not notice — because nothing in Git ever referenced that contract.

We already covered the failure mode in [why MCP integrations break silently](https://dev.to/kioiek/why-your-mcp-integrations-break-silently-and-how-we-built-driftguard-to-close-the-gap-4g0g) and walked a hands-on lab in [ToolSchema Kit](https://dev.to/kioiek/catch-mcp-tool-schema-drift-in-10-minutes-live-demo-optional-watch-4ao2). This post is the **CI half**: wire a progressive gate so every `mcp.json`

endpoint is either watched or explicitly ignored before merge.

DriftGuard CI is a **hook → preview → trial → paid gate** funnel. You can stop at any layer:

| Layer | Action | API key | Blocks CI? |
|---|---|---|---|
1 — Hook |
`drift-diff` / `compare_json`
|
No | On breaking fixture diff only |
2 — Preview |
`drift-coverage-preview` |
No | No (writes Step Summary + trial link) |
3 — Trial gate |
`drift-coverage` + trial session |
Trial secret | Yes — 1 endpoint max |
4 — Pro gate |
`drift-coverage` + API key |
`dg_…` |
Yes — plan limit (50 on Pro) |

Layer 2 is the fastest win: zero secrets, scans your repo, prints which MCP URLs are not monitored. Layer 4 is what teams adopt after one postmortem like [MCP tool removed over the weekend](https://dev.to/kioiek/postmortem-mcp-tool-removed-over-the-weekend-detected-on-scheduled-poll-not-prod-traffic-1pdi).

Full reference: [docs/CI.md](https://github.com/kioie/driftguard/blob/main/docs/CI.md) in the open-source repo.

Create `.github/workflows/driftguard.yml`

:

```
name: DriftGuard

on:
  pull_request:
  push:
    branches: [main]

jobs:
  schema-hook:
    runs-on: ubuntu-latest
    steps:
      - uses: kioie/driftguard/.github/actions/drift-diff@v0.3.3
        with:
          before: '{"status":"ok","data":{"id":1,"name":"test"}}'
          after: '{"status":"ok","data":{"id":1}}'

  coverage-preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: kioie/driftguard/.github/actions/drift-coverage-preview@v0.3.3
        with:
          scan-paths: mcp.json,.cursor/mcp.json,package.json
```

**Pin @v0.3.3** (or current release) — never

`@main`

in production pipelines.Open a PR. The **DriftGuard** check runs two jobs:

`scan-paths`

, discovers MCP and API URLs, writes a No `files-json`

boilerplate — `scan-paths`

walks the repo for you.

After the preview job finishes, expand **Summary** on the workflow run. You should see something like:

```
Discovered endpoints: 3
Watched: 0
Missing: 3

→ https://driftguard.org/ci/setup?from=ci&import=…
```

That link opens ** CI setup**: mint a trial session, copy

`DRIFTGUARD_TRIAL_SESSION`

into GitHub secrets, and import the first missing watch without leaving the browser.Preview is **non-blocking by default** — it nudges without breaking existing repos. When you are ready to enforce, keep reading.

Add a secret `DRIFTGUARD_TRIAL_SESSION`

(from Step Summary or `POST /api/trial/session`

). Uncomment a third job:

```
  coverage-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: kioie/driftguard/.github/actions/drift-coverage@v0.3.3
        with:
          trial-session: ${{ secrets.DRIFTGUARD_TRIAL_SESSION }}
          scan-paths: mcp.json,.cursor/mcp.json,package.json
```

**Trial intentionally limits you to one watched endpoint.** If preview finds three MCP servers and only one is covered, the gate **fails with an upgrade message**. That is the funnel working — not a bug.

For a single-server team (one Stripe MCP, one internal ops server), trial gate is enough to block merges until that URL is on a schedule.

After [pricing](https://driftguard.org/pricing) → activate, replace the trial header with your API key:

```
  coverage-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: kioie/driftguard/.github/actions/drift-coverage@v0.3.3
        with:
          api-key: ${{ secrets.DRIFTGUARD_API_KEY }}
          scan-paths: mcp.json,.cursor/mcp.json,package.json
```

One `dg_…`

key unlocks **assert_coverage** in MCP, the hosted API, and CI. Failures include `upgrade.console`

URLs to bulk-import missing watches.

Local equivalent (useful in pre-commit or agent loops):

```
export DRIFTGUARD_API_KEY=dg_…
driftguard coverage assert --mcp-json .cursor/mcp.json
```

Exit code **1** when a discovered dependency is not watched.

| Tool | Role |
|---|---|
oasdiff |
Diff your OpenAPI specs at merge time |
MockDrift / ToolChange |
Gate packages for fixtures and MCP manifest lint — see
|

The CI gate answers: **"Every URL in mcp.json that our agents depend on — is it on a watch?"** Scheduled polling and breaking-classified alerts are hosted; the diff engine stays open source.

```
Week 1   drift-diff on PRs (fixture or snapshot you control)
Week 2   drift-coverage-preview (see the gap, no secrets)
Week 3   Trial gate on one critical MCP server
Week 4   Pro gate when preview lists 2+ production dependencies
```

Optional: turn preview blocking early with `fail-on-missing: true`

once the team agrees every discovered URL should be watched or removed from config.

| Free in GitHub Actions | Hosted (trial / Pro) |
|---|---|
`drift-diff` , `compare_json`
|
`register_watch` , scheduled polls |
`drift-coverage-preview` |
Alerts, drift history, console |
Step Summary + `/ci/setup` deep links |
`assert_coverage` enforcement |

Clone path until npm publish is fully wired: [github.com/kioie/driftguard](https://github.com/kioie/driftguard) → `npm ci && npm run build`

.

**Question for you:** Do you gate third-party dependencies in CI today — OpenAPI only, MCP included, or not at all? I read every reply and will link follow-up posts (agent embedding, contract drift monitoring) based on what teams are actually running.

| Post | Topic |
|---|---|
|

GitHub: [kioie/driftguard](https://github.com/kioie/driftguard) · Hosted: [driftguard.org](https://driftguard.org)
