Carve the schema-bearing parts of SEP-1913 into three independent experimental extensions, per @localden's "narrower first cut" ask and the Tool Annotations IG's 2026-05-28 extension-first decision: - io.modelcontextprotocol/trust-annotations (headline): narrow data-classification taxonomy (sensitive, untrusted) + open-ended evidenceRef pointer. - io.modelcontextprotocol/action-metadata: carries forward SEP-2061 (inputMetadata/returnMetadata/outcomes + requiresReview). - io.modelcontextprotocol/ifc-fides: FIDES information-flow control as a profile of evidenceRef, not a wire root. Adds repo meta (README, MAINTAINERS, CONTRIBUTING, LICENSE) and docs (sep-disposition, intent-comment, decisions, open-questions, trust-model, related-work). FIDES demoted to a profile; DataClass and requiresReview rehomed; maliciousActivityHint and propagation parked on the SEP-1913 umbrella. Citations index on public sources only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
136 lines
6 KiB
Text
136 lines
6 KiB
Text
---
|
|
title: Action Metadata
|
|
extension_identifier: io.modelcontextprotocol/action-metadata
|
|
status: Draft (experimental)
|
|
maintainers:
|
|
- "@rreichel3"
|
|
- "@SamMorrowDrums"
|
|
related:
|
|
- https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061
|
|
- https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913
|
|
---
|
|
|
|
> ⚠️ **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 remains the canonical discussion thread for 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 and are **not** re-litigated
|
|
> here; this draft tracks that proposal. Where SEP-2061 evolves, this file
|
|
> follows.
|
|
|
|
### `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. |
|