MCP SCHEMA DRIFT · CONTRACT CHECK

The check before the call.

Exactlane Preflight is a consumer-driven contract check for MCP tools. It shows whether a tool’s declared schemas still fit what your agent workflow sends and reads, before the workflow relies on it.

Schema drift can go unnoticed until a workflow uses the changed output: a provider revision can stop declaring an output field your workflow reads, and nothing fails at call time.

The call succeeds. The workflow breaks later.

Take an agent that schedules jobs through a provider’s schedule_jobs tool and reads assignments[].start_ms from every result. The provider ships revision rev-42. The tool is still listed, still accepts jobs, still declares assignments — but its output schema no longer declares start_ms.

That is schema drift, and nothing rejects the call. Discovery confirms the tool exists, and the handshake confirms the server speaks MCP. Neither asks whether this revision still fits what this workflow reads, so the gap turns up downstream, after the agent has relied on the result.

What the workflow reads

  • assignments[].job_idstring
  • assignments[].start_msinteger

What rev-42 declares

  • assignments[].job_idstring
  • assignments[].start_msnot declared

Checked before reliance, the same pair produces a report with 13 checks: 12 pass, and the one that fails names the missing field.

A bounded check between discovery and reliance.

Exactlane runs a consumer-driven contract check: it compares a caller-provided capability snapshot with the caller’s stated workflow requirements and returns bounded compatibility evidence.

  1. 01Supply a capability snapshot
  2. 02State the caller’s workflow requirements
  3. 03Receive bounded compatibility evidence
  1. DISCOVER
  2. PREFLIGHT
  3. CALLER DECIDES
  4. EXECUTE

Exactlane sits after discovery and before reliance or execution.

You already have most of it.

A snapshot is the provider’s own tools/list tool entries copied as they are (at most 10 tools), plus three fields: schema_version, protocol (name, revision, transport) and captured_at.

  • title, description, icons, annotations and _meta are accepted and discarded before storage (not stored, digested or evaluated). Any other tool field returns 422 UNKNOWN_FIELDS.
  • max_input_bytes is not part of tools/list. Add it only when the provider documents a limit; if your requirements set estimated_input_bytes without it, that check is NOT_RUN.

Run the failing check yourself.

No signup. No email. No human approval.

One request to the remote MCP adapter returns a finished report. It sends the rev-42 snapshot from above, with the workflow requirements that need start_ms.

In the browser

Run the same example on the static evaluator. No account, no grant, nothing stored.

Pre-check your own tools

Paste your server’s tools/list response. The lint names schema keywords the profile does not interpret. It is not a compatibility check.

The JSON-RPC response, its result, or the tools array. Up to 256 KiB. Used for this one response only; not stored or logged.

From a terminal or MCP client

curl -sS https://exactlane.dev/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/call' \
  -H 'Mcp-Name: check_mcp_capability' \
  --data-binary @- <<JSON
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {}
    },
    "name": "check_mcp_capability",
    "arguments": {
      "capability_key": "quickstart-scheduler",
      "revision_label": "rev-42",
      "snapshot": {
        "schema_version": "exactlane.mcp-snapshot/0.1",
        "protocol": {"name": "mcp", "revision": "2026-07-28", "transport": "streamable-http"},
        "captured_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
        "tools": [{
          "name": "schedule_jobs",
          "max_input_bytes": 262144,
          "inputSchema": {
            "type": "object",
            "properties": {"jobs": {"type": "array", "items": {"type": "object", "properties": {}}}},
            "required": ["jobs"],
            "additionalProperties": false
          },
          "outputSchema": {
            "type": "object",
            "properties": {"assignments": {"type": "array", "items": {
              "type": "object",
              "properties": {"job_id": {"type": "string"}},
              "required": ["job_id"],
              "additionalProperties": false
            }}},
            "required": ["assignments"],
            "additionalProperties": false
          }
        }]
      },
      "requirements": {
        "schema_version": "exactlane.consumer-requirements/0.3",
        "protocol_revision": "2026-07-28",
        "transport": "streamable-http",
        "max_snapshot_age_seconds": 86400,
        "required_tools": [{
          "name": "schedule_jobs",
          "provided_input_fields": {"jobs": "array"},
          "required_output_paths": {
            "assignments[].job_id": "string",
            "assignments[].start_ms": "integer"
          },
          "estimated_input_bytes": 4096
        }]
      }
    }
  }
}
JSON
  • The report is at result.structuredContent.report: verdict is FAIL, 12 of 13 checks pass, and tool:schedule_jobs:output:assignments[].start_ms is the one that fails.
  • Runs in bash or zsh. The heredoc fills captured_at with the current UTC time, so the 24-hour freshness requirement passes.
  • Calling REST instead? profile.version must match requirements.schema_version: 0.3 pairs with exactlane.consumer-requirements/0.3, and the frozen 0.2 and 0.1 profiles pair the same way. A mismatch returns 422 PROFILE_REQUIREMENTS_VERSION_MISMATCH and no report. The MCP tool infers the profile from the requirements.
  • Each call issues and revokes its own short-lived Preview grant, so Preview issuance rate limits apply.

Evidence that stays inside its boundary.

This is what the quickstart request returns, trimmed to the parts that decide the verdict. The full report also identifies the capability revision, profile and runner, with digests, trust dimensions and stated limitations.

Exactlane PreflightActual output · excerpt
{
  "verdict": "FAIL",
  "coverage": {
    "required": 13, "executed": 13, "passed": 12,
    "failed": 1, "not_run": 0, "unsupported": 0, "errors": 0
  },
  "checks": [
    … 12 passing checks omitted …
    {
      "check_id": "tool:schedule_jobs:output:assignments[].start_ms",
      "result": "FAIL",
      "expected": "assignments[].start_ms",
      "observed": "MISSING",
      "reason": "Required output segment start_ms is not declared",
      "evidence_path": "$.properties.assignments.items.properties"
    }
  ],
  "profile": {"id": "mcp-consumer-fit", "version": "0.3"},
  "source": {"kind": "CALLER_PROVIDED", "live_endpoint_observed": false}
}

What Exactlane checks

Exactlane compares a caller-provided MCP capability snapshot with that caller’s workflow requirements for a specific revision. The result is bounded compatibility evidence, not a universal MCP assessment.

  • Required tools. Does this capability revision expose the tools this workflow requires?
  • Declared schemas. Do the declared input and output schemas match this workflow’s required fields and types?
  • Protocol fit. Do the declared protocol revision and transport fit this caller’s requirements?
  • Snapshot bound. Is the supplied snapshot complete and fresh enough for this bounded static check?

What Exactlane is not

Exactlane answers a narrower question only: does this capability revision fit this caller’s requirements?

  • not a security scanner, and not a certifier of servers, providers, or trust
  • not a live probe or uptime monitor
  • not a universal MCP validator or full-conformance checker
  • not a business-result or payment-success guarantee

A verdict says what was decided — and what was not.

UNSUPPORTED is not a softer FAIL. It means Exactlane did not decide: the snapshot depends on something outside this fixed profile, such as another MCP revision or a JSON Schema keyword like enum that the profile does not interpret, so no compatibility call was made in either direction.

INCOMPLETE means at least one check could not run on the evidence supplied. A fresher or fuller snapshot can change the answer.

  • PASS

    Every check ran with supported semantics and met the requirement.

    A snapshot that still declares start_ms: all 13 checks pass.

  • FAIL

    A concrete incompatibility was proven.

    tool:schedule_jobs:presence observed MISSING. Its 7 dependent checks are NOT_RUN, and FAIL still decides the verdict.

  • INCOMPLETE

    Evidence was missing or stale, so a check did not run.

    snapshot:freshness is NOT_RUN: the snapshot is 820800 seconds old and the requirement allows 86400.

  • UNSUPPORTED

    Exactlane did not decide.

    protocol:revision observed 2025-11-25; this profile evaluates 2026-07-28 only.

Each example is actual evaluator output. A report has one verdict: any ERROR gives SYSTEM_ERROR; otherwise any FAIL gives FAIL; otherwise any UNSUPPORTED gives UNSUPPORTED; otherwise any NOT_RUN gives INCOMPLETE; otherwise PASS.

Exactlane does not make the caller’s operational decision, and no verdict maps to an action. The caller owns CONTINUE / UPDATE_INTEGRATION / HOLD.

A short-lived path for autonomous evaluation.

The quickstart’s no-signup access also works over REST, for agents that hold the credential themselves.

  1. request bounded Public Agent Preview access with POST /v0/public-access/grants
  2. receive a short-lived scoped credential, disclosed once
  3. perform a static compatibility check and consume the report
  4. self-revoke or let the credential expire automatically
  • Lifetime30 minutes
  • Quotas2 capabilities · 3 checks · 2 usage events
  • Privilegesno admin scope
  • Live probingOFF
Read the public_access compatibility contract →

A live Streamable HTTP adapter.

Stateless MCP at https://exactlane.dev/mcp (POST only; a browser GET answers 405), with one primary tool, check_mcp_capability. It is an adapter over existing Exactlane service operations, not a second evaluator. Headers, handshake and usage-event pairing are documented in llms.txt.

  • EndpointPOST https://exactlane.dev/mcp
  • TransportStreamable HTTP
  • Primary toolcheck_mcp_capability
  • Live probingOFF

Schema drift and contract checks, briefly.

Short answers for people and agents deciding whether this check fits their workflow.

What is MCP schema drift?

A provider changes a tool’s declared schemas between revisions: a field disappears or changes type, an input becomes required, or a tool is removed. MCP calls can keep succeeding while an agent workflow built on the old shape quietly breaks.

How is a contract check different from diffing two tools/list snapshots?

A diff lists every change, for every caller. A consumer-driven contract check starts from what your workflow sends and reads, so removing a field you never use is not a failure and removing one you depend on is. The fixed profile is conservative: an output field has to be declared and required to pass, a description saying “always returned” does not count, unsupported JSON Schema keywords come back UNSUPPORTED instead of being ignored, and every report carries digests of the exact snapshot, requirements, profile and runner it compared.

Is this contract testing against the live server?

No. Live contract tests call the server and inspect real responses. Exactlane never calls the MCP server: it evaluates the snapshot you supply against your requirements, so it can run before integration, and it does not prove what the live server returns.

Can it compare two revisions of the same tool?

Yes. Keep the snapshot and finished report from your earlier check. When a new revision ships, send them with the new snapshot and the same requirements to POST /v0/revision-checks, or pass them as previous to check_mcp_capability. You get the normal report plus a revision_receipt. It says whether your previous conclusion still holds and which checks changed, and it lists each schema change with what evaluating that change alone showed. Exactlane re-evaluates your earlier snapshot rather than trusting the old report, and nothing is looked up from earlier calls. Isolated evaluation cannot rule out interaction between changes. The whole-revision comparison is the authoritative result, and the verdict still comes only from your requirements.

The older REST option still works: create both capability revisions with the same credential and capability_key, then pass previous_capability_revision_id when you create the check. The report adds revision_changes (tools added, removed or changed, and protocol changes).

Start with the contract.

These machine-readable resources are the authoritative front door for the MCP compatibility contract.

Who this is for

People building agents that depend on MCP tools they do not control, and the agents themselves. If your workflow reads specific fields from a third-party tool, check it before first use and again when the provider ships a new revision.