action-metadata.mdx

June 16, 2026 · View on GitHub

Protocol Revision: draft

Extension identifier: io.modelcontextprotocol/action-metadata

⚠️ Experimental draft skeleton. This carries forward SEP-2061: Action Security Metadata by @rreichel3 into the IG's experimental repo, per the May 28 2026 decision to pursue trust/privacy work as an extension first. SEP-2061 was closed on 2026-06-13 in favour of this extension as the home for the work; this draft is now the canonical place to discuss the field semantics.

Abstract

This extension adds a small, declarative contract to a tool's static ToolAnnotations describing what the tool does with data: where inputs may go, where outputs originate, and what real-world outcome invoking it can cause. Where trust-annotations classifies data in transit, this extension classifies tool behavior. The two are complementary and independently adoptable — a client can consume action metadata without implementing trust annotations at all.

Motivation

MCP today treats all tool calls as equivalent at the protocol level beyond the coarse readOnlyHint / destructiveHint / idempotentHint / openWorldHint hints. A tool that reads drafts and a tool that sends email are otherwise indistinguishable, even though their privacy and consent implications differ radically. Runtimes fall back to inferring risk from tool names or model behavior, which does not scale.

This was reinforced in the May 28 2026 IG meeting: a model often cannot tell whether a target is private or public, and absent that signal it may push content somewhere it should not. A declarative behavioral contract lets clients and models make safer decisions without baking domain knowledge into every model.

The canonical worked example from SEP-2061: read_drafts, list_inbox, and send_email can share an identical JSON Schema yet have completely different security semantics — only action metadata distinguishes them.

Specification

Dependencies

This extension annotates the existing ToolAnnotations object returned by tools/list. It has no dependency on trust-annotations or on Tool Resolution.

Fields

Carried under the extension-namespaced key on ToolAnnotations:

{
  "annotations": {
    "io.modelcontextprotocol/action-metadata": {
      "inputMetadata": {
        "destination": "external",   // where input data may be stored/sent
        "sensitivity": "personal"    // kind of data the tool accepts
      },
      "returnMetadata": {
        "source": "open-world",      // where returned data originates
        "sensitivity": "public"
      },
      "outcome": "consequential",    // benign | consequential | irreversible
      "requiresReview": true          // host SHOULD seek human confirmation
    }
  }
}
FieldMeaning
inputMetadata.destinationWhere data passed to the tool may end up (e.g. local, internal, external).
inputMetadata.sensitivityThe kind of data the tool is designed to accept.
returnMetadata.sourceWhere the tool's returned data originates (e.g. first-party, open-world).
returnMetadata.sensitivityThe kind of data the tool is designed to return.
outcomeReal-world effect class: benign / consequential / irreversible.
requiresReviewThe tool author signals that a host SHOULD obtain explicit human confirmation before invocation.

Exact enum value sets are inherited from SEP-2061 as the starting point and are not re-litigated here. With SEP-2061 now closed, this draft is where they evolve.

requiresReview lives here, deliberately

requiresReview is a workflow/consent signal, not a data-classification property. It was intentionally moved out of trust-annotations (which stays strictly data-classifying) to avoid reproducing SEP-1913's "several concerns in one schema" problem at smaller scale. It sits next to outcome because both describe the act of calling the tool rather than the data in flight.

Lifecycle and list_changed

These fields are part of the tool definition (ToolAnnotations). They are therefore covered by tools/list_changed: a server that changes a tool's action metadata MUST emit list_changed as it would for any tool-definition change. (This is the opposite of trust-annotations, which is response-level.)

Relationship to existing annotations

outcome: irreversible overlaps conceptually with destructiveHint but is strictly richer (a three-way classification vs. a boolean) and is scoped to the real-world effect rather than to whether the operation is destructive to server-side state. The IG will need to decide whether action metadata supersedes or coexists with the legacy hints before any graduation.

Reference implementation

Per SEP-2061: the read_drafts / list_inbox / send_email worked example with identical schemas and divergent action metadata. A public MCP server emitting these fields (candidate: github-mcp-server) would anchor the draft in a real ecosystem.

Open questions

  • Coexistence vs. replacement of destructiveHint / readOnlyHint.
  • Whether destination / source / sensitivity enums should be open strings (consistent with evidenceRef.type) or closed enums.
  • Whether requiresReview needs a machine-readable reason (vs. a bare boolean) to drive good client UX.

Changelog

DateChange
2026-06-10Initial draft skeleton, carrying SEP-2061 into the experimental repo; absorbed requiresReview from the trust taxonomy.
2026-06-15SEP-2061 closed in favour of this extension; this draft is now the canonical home for the field semantics.