Practice what you Pact : Catch breaking API changes before production in the SmartBear MCP
There’s something satisfying about contract testing the contract-testing tool. The SmartBear MCP Server is the integration layer between AI coding assistants and the PactFlow API, so when we decided it needed Pact consumer tests of its own, we were subjecting our own code to the same standards that we recommend.
The motivation was straightforward: when your service depends heavily on an external API, every undocumented provider change is a potential outage. A renamed field, a dropped query parameter, an altered response shape and your tests catch none of it because they never talked to the real thing. This post shows exactly how we solved that for the smartbear-mcp: the test structure, the CI pipeline, and the can-i-deploy gate that blocks a bad deploy before it reaches production.
This post walks through why, what we built and what you can take from it for your own services. You can also have a look at the changes here https://github.com/SmartBear/smartbear-mcp/pull/575.
If you’re a developer looking to add contract testing to a service that depends on one or more external APIs, the same pattern applies to you and the PactFlow skill combined with the MCP Server can scaffold most of it in minutes.
Key takeaways
- Contract tests turn every undocumented change in an upstream API – a renamed field, a dropped parameter, an altered response shape – into a failed check in CI instead of a production incident.
- A can-i-deploy gate makes the compatibility check a release blocker: if a provider change would break the consumer, the deploy stops before it ships, not after.
- With the swagger-contract-testing AI skill and the SmartBear MCP Server, an AI coding assistant can scaffold the consumer tests, apply the right Pact matching rules, and wire up the CI gate from inside your editor.
Why the MCP Server needed a compatibility gate
The MCP Server translates AI tool calls into HTTP requests against the PactFlow API. When a developer asks their AI agent “which services depend on UserService?”, the MCP Server calls GET /pacticipant/{name}/network. When they ask it to record a deployment, it calls POST /deployed-versions.
Without contract tests, the only way to catch a mismatch between the MCP Server’s tool expectations and the PactFlow API is to run the full stack, which means a real PactFlow tenant, real network calls, and tests that are slow, flaky, and can’t run in isolation. Worse, if the PactFlow team changes a response shape or adds a required field, you find out in production, not in CI.
Consumer-driven contract testing solves this cleanly. The consumer (the MCP Server) defines exactly what it needs from each endpoint. The consumer turns those expectations into a pact file and publishes it to PactFlow. The provider verifies the pact against its real implementation. If anything drifts, the verification fails and can-i-deploy blocks the release before it causes a problem.
What we built to catch breaking changes in CI
The implementation covers two test files and a CI pipeline.
Core API consumer tests
pactflow.pact.test.ts covers every HTTP interaction the PactflowClient makes against pactflow-api. That spans 40+ endpoints across core flows including can-i-deploy, matrix queries, BDCT cross-contract verification, etc. Each test uses PactV4 with executeTest() so there is no shared mock server state every test spins up and tears down its own ephemeral server.
it("GET /can-i-deploy – returns deployment eligibility summary", () =>
provider
.addInteraction()
.given("pacticipant ServiceA version 1.0.0 exists")
.uponReceiving(
"a request to check can-i-deploy for ServiceA 1.0.0 in production",
)
.withRequest("GET", "/can-i-deploy", (builder) => {
builder
.query({ pacticipant: "ServiceA", version: "1.0.0", environment: "production" })
.headers({ Authorization: like("Bearer test-token") });
})
.willRespondWith(200, (builder) => {
builder.headers(halJsonResponseHeaders).jsonBody({
summary: like({
deployable: true,
failed: 0,
reason: "All verification results are successful",
success: 1,
unknown: 0,
}),
matrix: [],
notices: [],
});
})
.executeTest(async (mockServer) => {
const client = await createClient(mockServer.url);
const result = await client.canIDeploy({
pacticipant: "ServiceA",
version: "1.0.0",
environment: "production",
});
expect(result.summary.deployable).toBe(true);
}));
The key design choice here is using like() for type matching rather than exact values. The MCP Server cares that the field exists and is a boolean. Using exact matchers makes the pact brittle and causes false failures when the provider returns valid but different data. The same principle applies throughout: eachLike() for arrays, regex() for content-type headers, and like() for any field where only the type matters.
AI endpoint tests
pactflow-ai.pact.test.ts covers the AI-specific endpoints against a separate provider, pactflow-ai-api: the generate endpoint (POST /api/ai/generate), the review endpoint (POST /api/ai/review), and the entitlement check (GET /api/ai/entitlement). We keep these in a separate file because they target a different provider, so they produce a separate pact file. The pipeline publishes and verifies both pact files independently, which means the can-i-deploy gate checks compatibility against both providers before any release proceeds.
Isolated test config
Pact tests sit between unit tests and integration tests on the confidence spectrum – they catch real contract mismatches that unit tests miss, without the infrastructure cost of a full integration suite, and they run pre-deployment against a mock rather than a live environment.
The trade-off is speed: each interaction spins up its own HTTP mock server. Running them mixed in with the main test suite would add minutes to every local feedback loop. We created a dedicated vitest.pact.config.ts that targets only the pact test files, and updated the main vitest.config.ts to exclude them. This means npm test stays fast and npm run test:pact runs the contract suite on demand and in CI.
How the CI pipeline enforces compatibility on every push
Tests that pass locally still don’t prevent an outage on their own. The value shows up when the pipeline publishes the pacts, checks them against what’s live in production, and refuses to ship a version that would break.
The GitHub Actions workflow (.github/workflows/pact.yml) runs on every push and pull request to main and integration branches. It has two jobs:
consumer-tests runs npm run test:pact, which generates the pact files in ./pacts/. It then installs the PactFlow CLI and publishes the generated pacts to PactFlow using the git commit SHA as the consumer version and the branch name as the consumer branch:
pact-broker publish ./pacts \
--consumer-app-version="${GITHUB_SHA}" \
--branch="${GITHUB_REF_NAME}" \
--verbose
can-i-deploy runs after consumer-tests and checks whether this version of the MCP Server is compatible with what’s currently deployed in the production PactFlow environment. It retries for up to five minutes to allow time for provider verification results to arrive from both pactflow-api and pactflow-ai-api:
pact-broker can-i-deploy \
--pacticipant smartbear-mcp \
--version "${GITHUB_SHA}" \
--to-environment production \
--retry-while-unknown 30 \
--retry-interval 10
This is the standard Pact Nirvana CI pattern. If either provider verifies the pact and the results show a compatibility problem, the gate fails before anything reaches production. If you add a new interaction to the pact and the provider hasn’t verified it yet, the pact is in pending state and the gate waits rather than blocking immediately.
The CI pipeline also needed two secrets, PACT_BROKER_BASE_URL and PACT_BROKER_TOKEN, which you can get from your PactFlow account for publishing to PactFlow.
Covering interactions without handwriting every test: How the PactFlow skill helped
Writing 40+ test interactions by hand is tedious. The PactFlow skill, installed as part of the swagger-contract-testing plugin, understands Pact V4 syntax, applies the right matching rules by default, and can generate complete, runnable tests from a description of the interaction. When we needed to add the BDCT test coverage, for example, we could describe the endpoint and ask the skill to generate the test structure rather than writing it from scratch.
The skill also flagged a few places in the initial draft where we had used exact matchers where type matchers would be more appropriate – for instance, matching on a specific UUID string for an environment ID rather than like(). That kind of best-practice feedback is what the AI review capability surfaces automatically when you ask it to review your pact tests.
We used the SmartBear MCP integration itself to check the contract matrix and verify that the PactFlow verification pipeline picked up the pacts correctly once the initial CI run completed.
Five steps to the same coverage on your own service
The pattern we followed is the same one any team should follow when adding contract tests to an existing API client:
First, invoke the PactFlow skill in your coding agent as shown in the diagram, in this case Claude Code.
Second, map every HTTP call your client makes. For the MCP Server, that was straightforward – the PactflowClient class has one method per API call. For a service that makes HTTP calls scattered across multiple modules, point your coding agent to API docs of the project, specify the architecture on your own, or ask the coding agent to discover it for you.
Third, using the information from the second step, pass a detailed prompt to the skill indicating what’s expected and let it run until completion.
Fourth, keep pact tests isolated from your main test suite. They run slower, they require network ports, and mixing them with unit tests makes both harder to run. A dedicated config and a dedicated npm script make the separation explicit. If the coding agent didn’t do this, ask it to do this.
Last, verify that your application and integrations are working by asking your coding agent to display verification results.
The PactFlow skill and the SmartBear MCP integration make it faster to get through those steps generating the initial test scaffolding, reviewing the output for best-practice violations, and connecting directly to your PactFlow workspace to check the matrix and record deployments without leaving your editor.
Get started: Contract tests your assistant can scaffold today
If your team is looking to adopt contract testing, the swagger-contract-testing plugin installs into Claude Code, Cursor, VS Code Copilot, Windsurf, and other AI coding assistants. It brings the PactFlow skill and SmartBear MCP Server together so your AI assistant can write tests, connect to your broker, run can-i-deploy, and walk you through the full CI setup, all from a conversation in your editor/tool of choice.
We pointed the contract-testing tool at itself because the same exposure applies anywhere. If your service depends on an API you don’t control, the next undocumented change to it is already scheduled – you just don’t know when. Contract tests and a can-i-deploy gate decide whether you meet it in CI or in production.
View the full documentation on using PactFlow’s AI tooling at docs.pact.io/ai_tools.
Frequently Asked Questions
What is consumer-driven contract testing, and how is it different from integration testing?
In consumer-driven contract testing, the consumer defines exactly what it needs from an API, and those expectations become a contract the provider verifies against its real implementation. Unlike end-to-end integration testing, each side runs in isolation against a mock, so you get fast, reliable feedback without standing up a full environment.
Why do undocumented API changes break services in production instead of in CI?
If your tests never talk to the real provider, a renamed field, a dropped parameter, or an altered response shape passes every local check and only surfaces once the two services meet in production. Contract testing catches the mismatch in CI, because the provider verifies the consumer’s contract before anything ships.
What does a can-i-deploy gate actually do?
can-i-deploy checks whether the version you’re about to release is compatible with what’s already deployed in a target environment. If a provider change would break a consumer’s contract, the gate fails the build and stops the deploy before it reaches production.
Can an AI coding assistant write contract tests for me?
Yes. With the swagger-contract-testing plugin and the SmartBear MCP Server, an AI assistant can scaffold consumer tests, apply the right Pact matching rules, and wire up the CI gate from inside your editor. You still review the output – the assistant handles the repetitive scaffolding, not the judgment.
Should contract tests run in the same suite as my unit tests?
No, contract tests should not run in the same suite as your unit tests. Contract tests spin up a mock server per interaction, so they run slower than unit tests. A dedicated config and script keep your unit suite fast while the contract suite runs on demand and in CI.