Skip to content

Latest commit

 

History

History
26 lines (14 loc) · 3.4 KB

File metadata and controls

26 lines (14 loc) · 3.4 KB

The moat: a tool surface that explains itself and can't inflate

The problem

Two failure modes show up the moment a system exposes more than a handful of tools to an agent or an API consumer:

  1. Opaque results. A tool returns a payload with no record of why it is what it is or how much to trust it. The caller — human or agent — can't filter low-confidence answers, can't escalate, and can't audit after the fact. Provenance that wasn't captured at the boundary can't be reconstructed later.
  2. Surface inflation. "We expose 50 tools" quietly comes to mean "50 things that look like they do something," when in reality some execute server-side work, some are read-only, and some are just guidance a human has to walk. The capability claim drifts away from what the surface actually does, and nobody notices until a caller relies on a tool that doesn't really act.

The structural advantage

mcp-tool-provenance makes both problems structural rather than aspirational, with one tiny dependency.

Every result self-describes. stamp(result, toulmin=…, tags=…) attaches a fixed envelope — a Toulmin argument (claim/grounds/warrant/qualifier/rebuttal) and a four-dimension tag block (scope/signal_type/attribution/uncertainty) — non-destructively. Because the wrap preserves the original payload and returns a new dict, retrofitting provenance onto an existing surface is a one-line change at the return boundary, not a migration. Now a caller can route on _toulmin.qualifier, filter on _tags.uncertainty, or query an audit log by _tags.attribution, uniformly, across every tool.

The surface can't inflate. drift_check(registry, classification) partitions the registry into exactly three buckets — real_dispatch (executes + writes), read_only (real reads), native_only (guidance only) — and fails if any exposed tool is unclassified or appears in two buckets. The honest "how many tools actually execute" number is derived (real_dispatch + read_only), never asserted. Wire drift_check into CI and a newly registered tool must be classified before it can ship; the published capability count is forced to stay true.

The decisive design choice is the same in both halves: the contract is fixed, the content is yours. The envelope keys and the three buckets never change; the tag values and the tools are caller-supplied. That is what lets the same primitive govern a fintech tool surface, a healthtech one, and a logistics one without a fork.

Why it is hard to retrofit

Both properties are nearly free on day one and expensive on day three. Stamping costs one line at each return site if you do it as you build; bolting it on later means touching every handler and back-filling reasoning that was never recorded. A drift gate added before the surface grows keeps the capability map honest for free; added after, it surfaces a backlog of unclassified tools and inflated claims that someone now has to reconcile. The smallness — ~250 lines, zero deps — is the point: there's no excuse not to adopt it at the start.

Pairs well with

A Toulmin-argument layer (the reasoning vocabulary) and a provenance-tag schema (the four-dimension tags) — both of which this package vendors — and a sign-off ledger so every governed mutation a tool performs carries a maker-checker trail next to its envelope. Together they make a tool surface auditable, trustworthy, and impossible to overstate.