What the workflow reads
- assignments[].job_idstring
- assignments[].start_msinteger
MCP SCHEMA DRIFT · CONTRACT CHECK
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.
01 / What breaks without it
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.
Checked before reliance, the same pair produces a report with 13 checks: 12 pass, and the one that fails names the missing field.
02 / How it works
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.
Exactlane sits after discovery and before reliance or execution.
Where the snapshot comes from
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.03 / Quickstart
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.
Run the same example on the static evaluator. No account, no grant, nothing stored.
Paste your server’s tools/list response. The lint names schema keywords the profile does not interpret. It is not a compatibility check.
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
result.structuredContent.report: verdict is FAIL, 12 of 13 checks pass, and tool:schedule_jobs:output:assignments[].start_ms is the one that fails.captured_at with the current UTC time, so the 24-hour freshness requirement passes.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.04 / Report
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.
{
"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}
}
05 / Evidence boundary
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.
Exactlane answers a narrower question only: does this capability revision fit this caller’s requirements?
06 / Verdicts
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.
Every check ran with supported semantics and met the requirement.
A snapshot that still declares start_ms: all 13 checks pass.
A concrete incompatibility was proven.
tool: observed MISSING. Its 7 dependent checks are NOT_RUN, and FAIL still decides the verdict.
Evidence was missing or stale, so a check did not run.
snapshot: is NOT_RUN: the snapshot is 820800 seconds old and the requirement allows 86400.
Exactlane did not decide.
protocol: 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.
07 / Public Agent Preview
The quickstart’s no-signup access also works over REST, for agents that hold the credential themselves.
POST /v0/public-access/grants08 / Remote MCP
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.
09 / FAQ
Short answers for people and agents deciding whether this check fits their workflow.
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.
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.
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.
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).
10 / Machine entrypoints
These machine-readable resources are the authoritative front door for the MCP compatibility contract.
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.
Compatibility work: contact. Questions, bug reports, or a check the profile does not cover: open an issue on the public repository.