About Covenant
Covenant is a Rust command-line tool for detecting MCP server interface changes, aimed at teams maintaining servers and checking compatibility in CI. It snapshots interfaces to a lockfile and classifies changes by direction and required SemVer impact.
Key features
- 01Interface snapshots and lockfiles
- 02Breaking-change detection
- 03Direction-aware schema diffing
- 04Interface hygiene checks
- 05SARIF and GitHub Action support
- 06stdio and Streamable HTTP
Use cases
- 01Block breaking interface changes in CI
- 02Review required version bumps
- 03Check tool descriptions and parameter schemas
- 04Compare interface lockfiles offline
MCP support
Covenant performs the MCP handshake and reads tool, resource, and prompt listings to inspect a server's interface; it does not call tools. It connects over stdio or Streamable HTTP.
Configuration
Local server config is the same for every client. Copy it into your client’s MCP config file.
We haven’t collected a package or remote endpoint for this server. Follow the instructions in the project README.
Tools
Local servers expose tools only once running on your machine, and the maintainer has not provided a tool list.
Details
From the project READMEPart of the StudioMeyer MCP Stack — Built in Mallorca 🌴 · ⭐ if you use it
mcp-covenant
Contract & breaking-change detection for Model Context Protocol servers — semver for your MCP interface.
When you ship a typed library you have a public API and tooling that screams when you break
it. MCP servers have neither: they just serve whatever tools/list returns today. Rename a
tool, add a required argument, narrow an enum — every agent built on your server breaks, and
nothing in your pipeline noticed.
mcp-covenant is one static binary that snapshots your server's interface into a committed
lockfile and fails CI when a change would break existing clients — classified the way a
human would: breaking / minor / patch.
$ mcp-covenant check --baseline mcp-covenant.lock -- node dist/server.js
mcp-covenant: 3 change(s) — required version bump: MAJOR
BREAKING (2)
x tool:legacy — tool was removed [tool.removed]
x tool:search → inputSchema.properties.limit — new required property [schema.property.required.added]
MINOR (1)
+ tool:newtool — new tool [tool.added]
# exit code 1 → the job fails
Why a Rust one
The idea isn't new — there are already several schema-drift / contract tools for MCP, all in
Node, Python or the JVM. mcp-covenant is the one that's a single static binary: nothing
to npm install, no Python venv, no JVM in your CI image. Drop it into any pipeline — Go
shops, Rust shops, bare Alpine containers — and it talks MCP over stdio or
Streamable HTTP.
It's also direction-aware, which is the part most drift tools get wrong (see below).
Companion to
mcp-armor(runtime defense) andmcp-gauntlet(pre-deploy fuzz + load).mcp-covenantis the interface-stability leg;mcp-herald(spec migration) andmcp-passport(registry publish gate) complete the stack.
Install
cargo install mcp-covenant
Or build from source:
git clone https://github.com/studiomeyer-io/mcp-covenant
cd mcp-covenant && cargo build --release
# binary at ./target/release/mcp-covenant
A leaner stdio-only build (no HTTP transport, fewer dependencies):
cargo install mcp-covenant --no-default-features
Use it in three commands
1. Snapshot your current interface into a baseline you commit to git:
mcp-covenant snapshot -o mcp-covenant.lock -- node dist/server.js
# or against a running HTTP server:
mcp-covenant snapshot -o mcp-covenant.lock --http https://my-server.example/mcp
2. Check in CI — non-zero exit on a breaking change:
mcp-covenant check --fail-on breaking -- node dist/server.js
3. Lint the interface for schema hygiene (the things that quietly hurt tool selection):
$ mcp-covenant lint -- node dist/server.js
mcp-covenant lint: 2 finding(s) (0 error, 1 warning, 1 info)
WARNING (1)
tool:newtool — tool has no description; the model cannot tell when to call it [tool.missing_description]
INFO (1)
tool:search → limit — parameter has no description [tool.param.missing_description]
Everything also runs fully offline against two lockfiles — no server needed:
mcp-covenant check --baseline v1.lock --against v2.lock
mcp-covenant lint --from v1.lock
How it classifies changes
The interesting part: a tool's inputSchema is what a caller sends; its outputSchema
is what a caller receives. The same structural change has opposite blast radius
depending on direction, and mcp-covenant models both.
| change | input (caller sends) | output (caller receives) |
|---|---|---|
| tool / resource / prompt removed | breaking | — |
| new tool / resource / prompt | minor | — |
| new required field | breaking | minor (stronger guarantee) |
| new optional field | minor | minor |
| field removed | breaking if required, else minor | breaking (field is gone) |
| optional → required | breaking | minor |
| required → optional | minor | breaking (may now be absent) |
type narrowed (e.g. ["string","number"] → string) | breaking | minor |
| enum value added | minor (accepts more) | breaking (unknown value) |
| enum value removed | breaking (rejects it) | minor |
array gains an items schema | breaking (elements now constrained) | minor |
array drops its items schema | minor | breaking (elements now unconstrained) |
additionalProperties: true → false | breaking | — |
| description / title changed | patch | patch |
Nested object properties and array items are diffed recursively. A keyword that is absent
on one side is compared as empty: a schema without properties declares no fields, and
required counts even for a field the schema never declares. The overall result is the
most severe change found, which maps to the semver bump you owe your users.
CI
GitHub Action
# .github/workflows/mcp-contract.yml
name: MCP contract
on: [pull_request]
jobs:
contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: studiomeyer-io/[email protected]
with:
command: "node dist/server.js" # or: http: https://my-server/mcp
baseline: mcp-covenant.lock
fail-on: breaking
Raw, any CI
mcp-covenant check --fail-on breaking -- node dist/server.js
SARIF → GitHub code scanning
mcp-covenant check --format sarif -- node dist/server.js > covenant.sarif
# then upload with github/codeql-action/upload-sarif
Findings show up inline on the PR, anchored to your mcp-covenant.lock.
The lockfile
mcp-covenant.lock is plain, pretty-printed, deterministically ordered JSON — commit it and
review it like any other lockfile. A regeneration with no interface change is byte-identical,
so a noisy diff is the signal.
{
"covenant_version": "0.1.1",
"captured_at_unix": 1750000000,
"server": { "name": "demo", "version": "1.0.0", "protocolVersion": "2025-11-25" },
"surface": {
"tools": [ { "name": "search", "description": "Search the catalog.", "inputSchema": { … } } ],
"resources": [],
"prompts": []
}
}
Scope
mcp-covenant is read-only: it does the initialize handshake and calls tools/list,
resources/list, prompts/list — it never calls a tool. It models the slice of JSON Schema
that MCP tools actually use (type, enum, properties, required, items, additionalProperties),
not the entire spec. See CONTRIBUTING.md for the classification rules and
how to add one.
License
MIT © StudioMeyer
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.