contract drift · mcp
tieline
Frontend and backend still agree, or CI fails. Catches drift before production returns a 404. Adapters for common stack shapes. Official MCP server on the registry.
- Node.js
- JavaScript (ESM)
- OpenAPI
- MCP over stdio
- GitHub Actions
Problem
A frontend calls a route the backend no longer serves, and nothing notices until a user does. Contract testing catches it, but only after someone has written contract tests on both sides and stood up a broker. tieline reads the code both sides already have.
Constraints
- Nothing to author and nothing to run: no contract tests, no broker, no booted backend.
- Fast enough to gate every pull request. It finishes in well under a second.
- A URL built at runtime is reported as unverifiable, never guessed.
- Any client adapter has to pair with any server adapter.
- No dependencies and no model. Delete the LLM and a developer still installs it.
Architecture
Each side of the API boundary gets one extractor, and everything after that is shared. A new framework is a new adapter of about 80 lines; the matcher never changes.
Two codebases
- Frontend sourceThe HTTP calls the app makes: RTK Query, axios and fetch, Angular HttpClient.
- Backend sourceThe routes the server declares: NestJS, Express, Fastify, Next, FastAPI, Flask, Spring, or any OpenAPI document.
Adapters
- Client adaptersThree of them. Each returns the method, raw path, file, and line of every call site.
- Server adaptersEight of them. Express walks the mount graph across files; OpenAPI covers any backend that emits a spec.
Shared engine
- NormaliserReduces every parameter syntax to one form, so a colon, braces, brackets, and a template literal compare equal.
- MatcherJoins both sides on method and path into matched, drift, unverifiable, and dead.
- HintsDid-you-mean suggestions for a drifted call, such as the right path with the wrong method.
Out
- ReportsTerminal, JSON, or an HTML report.
- CI gateA non-zero exit when the frontend calls something the backend does not serve.
- MCP serverThe same check for an agent, over stdio.
Doctor: the same engine, pointed at your docs
- Code against specDiffs the routes parsed from source against the published OpenAPI spec.
- Undocumented and phantomRoutes the code serves that the spec leaves out, and routes the spec promises that the code no longer serves.
Key decisions
Static analysis of both sides
Instead of consumer-driven contract tests
Contract testing gives payload-level guarantees and costs tests on both sides, a broker, and provider verification builds. tieline needs only the source, so there is nothing to write or maintain. It checks less, paths and methods and not payloads, and the two compose.
Reading the source
Instead of diffing OpenAPI documents
Spec diff tools compare two versions of a document and never read the code, so they help only when the spec is accurate. tieline reads what the frontend actually calls.
Unverifiable as its own result
Instead of guessing at URLs built at runtime
A URL assembled at runtime cannot be resolved by reading the code. It is reported as unverifiable and never counted as a match or as drift.
One extractor per side and a shared matcher
Instead of a checker for each pair of frameworks
Any client adapter pairs with any server adapter, so one engine checks React against Express and Angular against Spring. Adding a framework touches nothing else.
What broke, and the fix
Did-you-mean hints could point at the wrong route.
- Cause
- The hint was not aware of path segments, so it could suggest a route that was not a real near miss.
- Fix
- Hints are segment-aware and cross-reference every route, including ones under a different prefix.
The first MCP Registry release was rejected.
- Cause
- The server description was over the registry's 100-character limit. The npm package had already shipped.
- Fix
- Trimmed the description and released again the same day.
Outcome
- Published on npm and the MCP Registry.
- 11 adapters, three client and eight server, covering MERN, MEAN, Next, Python, and Spring stacks, plus any backend with an OpenAPI spec.
- 71 tests on Node's built-in runner, including three cross-stack integration proofs.
- Known limits: extraction is regex-based, and it checks that a path and method exist, not the shape of the payload.