From 85c2be73c836d4e7cb9a094dd7d9319d39f04a46 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Tue, 16 Jun 2026 01:23:45 +0200 Subject: [PATCH] spec(action-metadata): add the action-metadata extension draft Stacked on the trust-annotations base. Declarative inputMetadata / returnMetadata + outcome classifiers (incl. requires_review) on ToolAnnotations. Carries forward the field semantics from SEP-2061 (Action Security Metadata, @rreichel3), closed 2026-06-13 in favour of this extension. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- specification/draft/action-metadata.mdx | 134 ++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 specification/draft/action-metadata.mdx diff --git a/specification/draft/action-metadata.mdx b/specification/draft/action-metadata.mdx new file mode 100644 index 0000000..dd27eee --- /dev/null +++ b/specification/draft/action-metadata.mdx @@ -0,0 +1,134 @@ +--- +title: Action Metadata +--- + +**Protocol Revision**: draft + +**Extension identifier:** `io.modelcontextprotocol/action-metadata` + +> ⚠️ **Experimental draft skeleton.** This carries forward +> [SEP-2061: Action Security Metadata](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061) +> by [@rreichel3](https://github.com/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](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061#issuecomment-4675049171) +> 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`](./trust-annotations.mdx) 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`: + +```jsonc +{ + "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 + } + } +} +``` + +| Field | Meaning | +| :--- | :--- | +| `inputMetadata.destination` | Where data passed to the tool may end up (e.g. `local`, `internal`, `external`). | +| `inputMetadata.sensitivity` | The kind of data the tool is designed to accept. | +| `returnMetadata.source` | Where the tool's returned data originates (e.g. `first-party`, `open-world`). | +| `returnMetadata.sensitivity` | The kind of data the tool is designed to return. | +| `outcome` | Real-world effect class: `benign` / `consequential` / `irreversible`. | +| `requiresReview` | The 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`](./trust-annotations.mdx) +(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](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/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`](https://github.com/github/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 + +| Date | Change | +| ---------- | ------------------------------------------------------------------- | +| 2026-06-10 | Initial draft skeleton, carrying SEP-2061 into the experimental repo; absorbed `requiresReview` from the trust taxonomy. | +| 2026-06-15 | SEP-2061 closed in favour of this extension; this draft is now the canonical home for the field semantics. |