action-metadata.mdx
June 16, 2026 · View on GitHub
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
}
}
}
| 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
(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/sensitivityenums should be open strings (consistent withevidenceRef.type) or closed enums. - Whether
requiresReviewneeds 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. |