ifc-fides (review by @JoannaaKL): - confidentiality is public/private only on the wire; drop the reader-list (logins aren't uniform across servers, are themselves access-restricted, and reader sets can be huge) - add Reader-set resolution section: two private markers aren't equal, the confidentiality join is the intersection and isn't computable from opaque wire markers, so hosts resolve to concrete reader sets at decision time - integrity join is total/wire-computable; confidentiality join is not - emitter MUST classify per resource, not per repository (public repos serve non-world-readable sub-resources), making github-mcp-server a real proof point action-metadata / docs: - SEP-2061 closed 2026-06-13 in favour of this extension; this draft is now the canonical home. Updated action-metadata.mdx, README, sep-disposition, related-work, and intent-comment status accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
134 lines
6.1 KiB
Text
134 lines
6.1 KiB
Text
---
|
|
title: Action Metadata
|
|
---
|
|
|
|
<Info>**Protocol Revision**: draft</Info>
|
|
|
|
**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. |
|