DEV Community

Daksh Srivastava
Daksh Srivastava

Posted on Originally published at github.com

Catching Documentation Drift Before It Breaks Production: How I Built Contradiction MCP

Catching Documentation Drift Before It Breaks Production: How I Built Contradiction MCP

Every developer has experienced this at least once.

You clone a repository. The README says:

Prerequisites: Node.js 18.x and PostgreSQL 14.

You install the dependencies, configure the environment, run the migrations, and start the application.

It fails.

After hours of debugging, you discover that the Dockerfile now uses Node.js 22, the deployment configuration specifies PostgreSQL 16, and an internal setup guide still references an older version.

The code changed. The documentation didn't.

flowchart TD
    A["README: Node.js 18"] --> D["Conflicting Technical Information"]
    B["Dockerfile: Node.js 22"] --> D
    C["Deployment: PostgreSQL 16"] --> D
    E["Old Setup Guide: PostgreSQL 14"] --> D
    D --> F["Broken Setup and Wasted Debugging Time"]

This is documentation drift: the gradual divergence between what a system actually does and what its documentation claims.

It creates friction for developers, but it becomes even more problematic when AI coding agents enter the picture.

Agents rely on READMEs, architecture documents, configuration files, and API specifications to understand a project. If those sources disagree, an agent can make a perfectly reasonable decision based on incorrect information.

I wanted to build something that could identify these conflicts systematically rather than relying on someone to notice them during a code review.

That led me to build Contradiction MCP, an open-source Model Context Protocol (MCP) server for detecting and investigating inconsistencies across technical sources.

What is Contradiction MCP?

Contradiction MCP extracts technical claims from different sources, compares claims that are relevant to one another, identifies potential contradictions, and provides evidence to help determine which source should be trusted.

Instead of treating every difference as an error, it considers context such as environment, configuration scope, source authority, and freshness.

For example, debug: true in development and debug: false in production may both be correct. However, two incompatible Node.js requirements for the same production service deserve investigation.

The distinction matters. A useful consistency engine must avoid generating noise just because two files contain different values.

What it can ingest

The project is designed to work across multiple kinds of technical sources:

  • Repositories and configuration: Files such as package.json, Dockerfiles, CI workflows, and deployment manifests.
  • Technical documents: Markdown, YAML, JSON, CSV, plain text, Word documents, PDFs, and supported email formats.
  • Web sources: Public documentation, endpoints, and status pages, subject to connection and security checks.

Claims retain source provenance so an engineer can trace findings back to the relevant file, line, page, or source snippet where available.

The architecture

Contradiction MCP separates source ingestion, claim analysis, contradiction classification, persistence, and resolution into distinct components.

It supports local MCP connections through standard input/output (stdio) and remote connections through its HTTP transport implementation.

flowchart TD
    A["MCP Clients"] --> B["Transport Layer"]
    B --> C["MCP Tool Router"]
    C --> D["Source Connectors"]
    C --> E["Contradiction Engine"]
    D --> F["Normalized Claims"]
    F --> G["Candidate Pair Generation"]
    G --> H["Context Analysis"]
    H --> I["Value and Version Comparison"]
    I --> J["Contradiction Classification"]
    J --> K["Authority and Freshness Scoring"]
    K --> L["Resolution Advice"]
    L --> M[("SQLite Database")]
    M --> N["Audit History"]

The main components have separate responsibilities:

  1. Ingestion: Reads supported sources and extracts technical assertions.
  2. Normalization: Represents extracted assertions in a consistent claim format.
  3. Candidate generation: Identifies claim pairs worth comparing.
  4. Context analysis: Filters out differences that are valid because of environment or scope.
  5. Comparison: Applies appropriate checks to values and version constraints.
  6. Classification: Records the type, severity, and confidence of a detected conflict.
  7. Resolution assistance: Uses evidence, authority, and freshness to recommend what to investigate.
  8. Persistence and auditing: Stores claims, contradictions, lifecycle changes, and supporting history.

The goal is to make the analysis traceable rather than producing a mysterious verdict.

How contradiction detection works

A naive implementation could compare strings across files and report every difference. That would generate a lot of false positives.

Consider these examples:

  • Development uses debug: true, while production uses debug: false.
  • One file specifies port 8080, while another specifies port 9090 for a different service.
  • One source permits Node.js >=20 <23, while another permits ^22.0.0.

These cases require different treatment. The first two may be valid because their contexts differ. The third is compatible because the version ranges overlap.

Contradiction MCP uses a multi-stage pipeline to distinguish meaningful conflicts from harmless differences.

flowchart TD
    A["Ingest Source"] --> B["Extract Claims and Provenance"]
    B --> C["Generate Candidate Pairs"]
    C --> D{"Same Relevant Context?"}
    D -- No --> E["Exclude Contextually Unrelated Pair"]
    D -- Yes --> F["Compare Typed Values"]
    F --> G{"Incompatible?"}
    G -- No --> H["Mark Compatible"]
    G -- Yes --> I["Classify Contradiction"]
    I --> J["Score Authority and Freshness"]
    J --> K["Generate Evidence-Based Advice"]
    K --> L["Persist Finding and Audit Data"]

1. Claims with provenance

An extracted claim should preserve both its meaning and its origin.

A simplified TypeScript representation looks like this:

export interface ExtractedClaim {
  subject: string;
  predicate: string;
  value: string;
  valueType?: string;
  confidence?: number;

  context?: {
    environment?: string;
    scope?: string;
    sourceRole?: string;
  };

  provenance: {
    filePath?: string;
    lineNumber?: number;
    pageNumber?: number;
    rawSnippet: string;
  };
}
Enter fullscreen mode Exit fullscreen mode

For example, a claim might represent the Node.js version required by an API service in production.

Keeping the source path, line number, and original snippet makes it easier to verify a finding without manually searching the entire repository.

This is particularly important when an AI agent is investigating a contradiction. It should be able to inspect the evidence instead of blindly trusting a generated explanation.

2. Deterministic version comparison

Version comparison is one area where ordinary string equality is insufficient.

Consider:

  • ^22.0.0
  • >=22.1.0 <23.0.0

These ranges overlap, so they are not inherently contradictory.

Now consider:

  • >=18.0.0 <19.0.0
  • >=22.0.0 <23.0.0

These ranges are disjoint.

Contradiction MCP uses semantic-versioning logic to reason about version constraints instead of treating every textual difference as a conflict.

The important distinction is between comparing the text of two version declarations and determining whether their permitted version sets intersect.

Other value types, including booleans, URLs, network addresses, and ports, also require appropriate handling. A number inside an IP address should not accidentally be treated as an application version.

3. Context-aware comparison

Different environments can legitimately use different values.

development:
  debug: true
  port: 8080

production:
  debug: false
  port: 8080
Enter fullscreen mode Exit fullscreen mode

The value of debug changes, but the difference is explained by the environment.

Similarly, these settings belong to different services:

server:
  port: 8080

metrics:
  port: 9090
Enter fullscreen mode Exit fullscreen mode

A comparison engine should not report a contradiction simply because two unrelated properties have different values.

Contradiction MCP uses contextual information and property matching to reduce these kinds of false positives.

The broader lesson is that contradiction detection requires understanding what a claim refers to, not just what value it contains.

4. Markdown tables and status badges

Technical information is not always written as ordinary prose.

Markdown tables can encode environment-specific versions, compatibility matrices, and recommended configurations. Status badges can communicate build results or coverage information.

For example:

Runtime Staging Production
Python 3.11 3.12
Node.js 20.x 22.x

A useful extractor needs to preserve the relationship between each value and its column heading. Otherwise, it risks losing the context that makes the value meaningful.

Similarly, when supported badge and status-report formats are parsed, conflicting build-status claims can be surfaced for investigation.

These are useful examples of why source parsing and normalization are as important as the comparison algorithm itself.

Deciding which source to trust

Detecting a conflict is only half the problem.

Suppose a deployment manifest says Node.js 22, but an architecture document says Node.js 18. Which one should an engineer treat as authoritative?

Contradiction MCP includes authority and freshness scoring to help prioritize that decision.

Authority scoring

The implemented scoring model combines several factors:

Authority Score =
    (0.40 * role_score) +
    (0.30 * trust_score) +
    (0.15 * location_score) +
    (0.15 * directness_score)
Enter fullscreen mode Exit fullscreen mode

The factors represent:

  • Source role: Whether the source is a deployment manifest, configuration file, specification, README, or informal example.
  • Source trust: How much confidence is assigned to the originating repository or domain.
  • Document type: Whether the claim comes from a structured configuration file or less structured prose.
  • Citation directness: How directly the evidence supports the claim, including exact source locations.

The configured role weights prioritize authoritative sources and deployment configuration over informal examples.

These scores are decision-support signals, not proof that a particular source is correct. A deployment manifest can itself be outdated or misconfigured, so the evidence still matters.

Freshness decay

Technical information can become less reliable as systems evolve.

Contradiction MCP uses an exponential decay model:

Freshness Score =
    exp((-0.693 * delta_days) / half_life_days)
Enter fullscreen mode Exit fullscreen mode

Here, delta_days represents the age of the observation, and the configured half-life is 180 days.

The model gradually reduces the freshness score as a claim ages. Explicitly deprecated or expired claims can also receive lower score ceilings.

Freshness helps prioritize investigation, but age alone should not decide truth. An older, approved specification may still be authoritative, while a recently modified file may contain an accidental change.

The purpose is to combine several signals and give engineers a more useful starting point.

From detection to resolution

Contradictions should not simply disappear after someone clicks a button.

They need a review process that records what happened and why.

The project models a lifecycle that includes opening, reviewing, resolving, dismissing, and reopening findings.

stateDiagram-v2
    [*] --> OPEN
    OPEN --> REVIEWED: Review
    OPEN --> RESOLVED: Resolve
    OPEN --> DISMISSED: Dismiss
    REVIEWED --> RESOLVED: Resolve
    REVIEWED --> DISMISSED: Dismiss
    RESOLVED --> REOPENED: New conflict detected
    DISMISSED --> REOPENED: Re-evaluate
    REOPENED --> REVIEWED: Review again

The SQLite-backed persistence layer stores contradiction records and associated history. Lifecycle transitions are designed to preserve an audit trail, including the actor, action, reason, and selected canonical claim where applicable.

This makes it possible to answer practical questions:

  • Why was a contradiction dismissed?
  • Which source was accepted as authoritative?
  • Who reviewed the finding?
  • Was the same issue detected again later?

For engineering teams, that history is often as important as the initial detection.

Why SQLite?

Contradiction MCP uses better-sqlite3 for local persistence.

The database configuration includes:

  • Write-Ahead Logging (WAL).
  • Foreign-key enforcement.
  • Schema migrations.
  • Indexes for efficient claim and contradiction lookups.
  • Persistent lifecycle and audit records.

SQLite keeps deployment relatively straightforward and avoids requiring a separate database server for the core use case.

It also makes local development and testing easier because the project's persistent state can live alongside the application.

Security considerations

A tool that reads repositories and fetches web resources needs to treat input boundaries seriously.

Contradiction MCP includes protections for its document and website connectors.

Website access

Web requests can become a security problem if a user supplies a URL that points to an internal service.

The website connector includes SSRF-related safeguards intended to restrict requests to loopback, private, link-local, and cloud metadata addresses. It also validates redirect destinations rather than blindly following redirects.

DNS resolution and address validation are important because a hostname can resolve to an address that should not be accessible.

Local file access

The document connector uses resolved paths and allowed-root boundaries to restrict access to files outside the permitted workspace.

This helps reduce directory traversal and symlink-based path escape risks.

Remote transport

Remote deployments should use appropriate authentication and network controls. Secrets should be supplied through the deployment environment, not committed to source control.

Security controls should always be validated against the actual deployment configuration and threat model. No single URL filter or path check should be treated as a complete security guarantee.

Working with Contradiction MCP

The project is designed to integrate with MCP-compatible clients.

The repository contains installation and configuration instructions for supported environments, including Claude Desktop, Claude Code, Cursor, Google Antigravity, and Windsurf.

Quick start

Install and configure the package using the instructions in the project repository:

Contradiction MCP on GitHub

The project also provides an npm package:

contradiction-mcp on npm

For clients supported by the installer, the intended workflow is to run the package's installation command for the selected environment and then verify that the MCP server starts successfully.

For remote deployments, consult the repository's current configuration documentation for the supported transport, endpoint, and authentication settings.

I recommend following the current README rather than copying a configuration block from an older article, because installation commands and transport options can change between releases.

What I learned building it

The most interesting part of this project was not simply extracting facts from files. It was figuring out how to compare them without generating meaningless warnings.

A few lessons stood out:

1. Context is essential. Two different values do not automatically mean two conflicting claims. Environment, scope, and property identity matter.

2. Evidence should be inspectable. A finding is much more useful when the developer can trace it to the exact source rather than trust an unexplained score.

3. Deterministic checks have a place in AI tooling. Version-range intersection and typed comparisons are better suited to explicit compatibility rules than vague similarity judgments.

4. Detection is only the beginning. Review states, resolution reasons, and audit history make the results useful in a real engineering workflow.

5. Security must be part of the design. A system that reads local files and fetches remote URLs needs explicit boundaries from the beginning.

What's next?

There are several directions in which I want to take the project:

  • Native connectors for Confluence and Notion.
  • A GitHub Actions integration that reports documentation conflicts during pull requests.
  • Better synchronization checks between OpenAPI or AsyncAPI specifications and implementation details.
  • Expanded terminology extraction across programming languages and technical documentation.

The long-term goal is to make consistency checks a normal part of software development rather than something teams perform only after a deployment breaks.

Explore the project

If you're interested in MCP, developer tooling, AI-assisted engineering, or documentation reliability, I'd appreciate feedback and contributions.

I'm building Contradiction MCP in the open, and feedback on its detection accuracy, source coverage, and developer experience is welcome.

The goal is simple: catch inconsistencies while they're still documentation problems, before they become production problems.

Top comments (0)