Skip to content

Tool declarations have no integrity binding, so a same-version redefinition is invisible to Inspect-Dynamic #38

Description

@Mayur021

The gap

steps/toolCallRequest carries tool: {name, version, provider}, an abstract capability string, and arguments. Nothing in that payload binds the call to the tool declaration the Guardian vetted.

The AgBOM tool component requires name, version, provider, and tool_fields.capability. That is the whole record for a tool.

So a server that edits a tool's description, parameter schema, or protocol-level annotations without bumping version produces a changed diff whose before and after are identical. There is nothing for agbom/changed to report. ACS-Inspect-Dynamic does not miss this case, it cannot express it.

Over MCP

protocols/MCP/tools/call is wrapped. tools/list and notifications/tools/list_changed are not. The strings annotation, readOnlyHint and destructiveHint do not appear in the specification or the docs.

A Guardian sitting on the MCP wrapping therefore sees every call and never sees the declaration move underneath it. Wrapping the MCP notification would help, but it relies on the server choosing to send it, which is the same trust problem the spec already rejects for skillLoad.digest_verified.

The spec already accepts this argument, for skills

skill_fields.definition.digest is required, over the complete loadable artifact. steps/skillLoad carries the digest again so the Guardian can bind the activation to an approved registration, with (skill_id, digest) as the durable correlation key across sessions. From skill-load.json:

Without this binding the lifecycle gate is bypassable

Carrying the digest here, rather than only a digest_verified boolean, lets the Guardian verify the binding itself instead of trusting the framework

Both sentences transfer to tools unchanged. A skill gets a digest because its definition is the surface attackers poison. A tool declaration is the same surface, reached by more deployments, and it is the one with published measurements behind it.

Measured, not hypothetical

Across 35 crawl observations of the public MCP registry between 2026-06-09 and 2026-08-01, covering 44,172 tools on reachable servers, the declared-versus-verified binding gap is 24.46 percentage points, and the corpus contains 17 permissive destructiveHint flips, true to false. The deposit states the strength of those plainly, and it is worth carrying rather than rounding up: each flip was confirmed between 17 and 33 times, but confirmations are repeated crawls by a single crawler, and independent-observer corroboration applies to the receipt network rather than this baseline series. (Bharti, MCP Declared-Effect Coverage and Contract Binding v1, CC BY 4.0, https://doi.org/10.5281/zenodo.21778282)

A separate registry-wide measurement over 88.6 days finds that a prior declaration change lifts next-period change probability 4.8x, but only 5.0% of the population carries any prior change, so re-auditing by drift ranking exhausts its signal pool almost immediately. (https://doi.org/10.5281/zenodo.21751498) A digest reaches every entry. A ranking reaches only the entries that have already moved.

Proposed direction

  1. Add an OPTIONAL declaration_digest to the AgBOM tool component, computed over the canonical form of the tool's full declaration: name, description, parameter schema, and any protocol-level annotations. Same scope discipline as skill_fields.definition.digest, same {algorithm, value} shape. The canonical form should be whatever canonicalization Audit chain trust depends on a single Guardian, no cross-Guardian verification protocol #18 lands rather than a second one defined here, since two conformant verifiers disagreeing about a declaration's bytes becomes a spurious deny under item 4. If Feature Proposal: Add Data Sensitivity and Trust Boundary Metadata to ToolDefinition to Prevent "Lethal Trifecta" Architectures #8's security_context lands this scope covers it, and a required_controls entry that disappears at unchanged version is the same measured class as the destructiveHint flips: a control switched off by the field that reads it.
  2. Carry it on steps/toolCallRequest, so a Guardian can bind the call to the declaration it vetted, exactly as steps/skillLoad binds to registration. The durable correlation key becomes the tool component id plus its declaration_digest.
  3. Make a digest change at unchanged version a reportable mutation, so agbom/changed with reason: configuration_changed has something to carry.
  4. Under ACS-Inspect-Dynamic, a call whose declaration digest does not match the approved one is unverifiable and SHOULD be denied, mirroring the skillLoad rule.
  5. Wrap protocols/MCP/tools/list, so the Guardian computes the digest from the declaration bytes it observed rather than comparing a framework-computed value against a framework-registered one. That second shape is the digest_verified problem one level up, which the spec already rejects. tools/list is client-initiated, so wrapping it does not depend on the server volunteering anything, which is what makes this avoidable for MCP tools where it is not avoidable for skills. extend_mcp.md currently mentions tools/call twenty times and tools/list zero.

Item 5, the #18 canonicalization dependency and the correction to the flip wording all come from @avp9-nexus's review below.

Optional rather than mandatory keeps ACS-Core unchanged. A harness that cannot compute the digest stays conformant, and a deployment that needs the binding declares Inspect-Dynamic.

If this lands, the negative vector for it is small and I am happy to write it alongside the work in #29 and #30.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions