spec(ifc-fides): address review; reframe action-metadata for SEP-2061 closure

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>
This commit is contained in:
Sam Morrow 2026-06-15 23:29:44 +02:00
commit b799af5bea
No known key found for this signature in database
7 changed files with 87 additions and 40 deletions

View file

@ -39,7 +39,7 @@ See [docs/decisions.md](docs/decisions.md) for the decision record and
| Identifier | Status | What it specifies | Reference implementation(s) | | Identifier | Status | What it specifies | Reference implementation(s) |
| :--- | :--- | :--- | :--- | | :--- | :--- | :--- | :--- |
| [`io.modelcontextprotocol/trust-annotations`](specification/draft/trust-annotations.mdx) | Draft skeleton | **Primary extension.** A small, scheme-agnostic client-facing data-classification vocabulary (`sensitive`, `untrusted`) on result `_meta`, plus an optional `evidenceRef` pointer slot that carries richer payloads out-of-band. | Python SDK: [`kapil8811/mcp-trust-annotations`](https://github.com/kapil8811/mcp-trust-annotations) (138-test suite, healthcare demo, LLM usability study). | | [`io.modelcontextprotocol/trust-annotations`](specification/draft/trust-annotations.mdx) | Draft skeleton | **Primary extension.** A small, scheme-agnostic client-facing data-classification vocabulary (`sensitive`, `untrusted`) on result `_meta`, plus an optional `evidenceRef` pointer slot that carries richer payloads out-of-band. | Python SDK: [`kapil8811/mcp-trust-annotations`](https://github.com/kapil8811/mcp-trust-annotations) (138-test suite, healthcare demo, LLM usability study). |
| [`io.modelcontextprotocol/action-metadata`](specification/draft/action-metadata.mdx) | Draft skeleton | `inputMetadata` / `returnMetadata` / outcome classifiers (incl. `requires_review`) on `ToolAnnotations`, describing where inputs go, where outputs originate, and what real-world effects a tool can cause. | Carries forward [SEP-2061 (Action Security Metadata)](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061) by [@rreichel3](https://github.com/rreichel3); reference impl per that proposal (`read_drafts` / `list_inbox` / `send_email`). | | [`io.modelcontextprotocol/action-metadata`](specification/draft/action-metadata.mdx) | Draft skeleton | `inputMetadata` / `returnMetadata` / outcome classifiers (incl. `requires_review`) on `ToolAnnotations`, describing where inputs go, where outputs originate, and what real-world effects a tool can cause. | Originally [SEP-2061 (Action Security Metadata)](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061) by [@rreichel3](https://github.com/rreichel3) — closed 2026-06-13 in favour of this extension; worked example `read_drafts` / `list_inbox` / `send_email`. |
| [`io.modelcontextprotocol/ifc-fides`](specification/draft/ifc-fides.mdx) | Draft skeleton | A **profile** of the `trust-annotations` `evidenceRef` slot: `type: "ifc.fides.v1"` carrying an integrity + confidentiality label for deterministic information-flow control, following the FIDES paper ([arXiv:2505.23643](https://arxiv.org/abs/2505.23643)). | Emitter candidate: [`github-mcp-server`](https://github.com/github/github-mcp-server) (does not emit IFC labels today — closing that gap is the proof point). | | [`io.modelcontextprotocol/ifc-fides`](specification/draft/ifc-fides.mdx) | Draft skeleton | A **profile** of the `trust-annotations` `evidenceRef` slot: `type: "ifc.fides.v1"` carrying an integrity + confidentiality label for deterministic information-flow control, following the FIDES paper ([arXiv:2505.23643](https://arxiv.org/abs/2505.23643)). | Emitter candidate: [`github-mcp-server`](https://github.com/github/github-mcp-server) (does not emit IFC labels today — closing that gap is the proof point). |
### Why FIDES is a profile, not a top-level extension ### Why FIDES is a profile, not a top-level extension

View file

@ -1,11 +1,12 @@
# Intent comment (draft, pre-post review) # Intent comments (posted)
This is the comment we plan to post on Both comments below have been **posted**. Kept here as the source of record,
[SEP-1913](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913), in sync with [sep-disposition.md](./sep-disposition.md).
with an abbreviated pointer version for
[SEP-2061](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061). - **SEP-1913** umbrella comment — [posted 2026-06-10](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913#issuecomment-4675047154).
Kept here so it can be reviewed and stays in sync with - **SEP-2061** coordination note — [posted 2026-06-10](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061#issuecomment-4675049171);
[sep-disposition.md](./sep-disposition.md). @localden then **closed SEP-2061 on 2026-06-13** in favour of the
`action-metadata` extension.
--- ---

View file

@ -33,8 +33,12 @@ Tracked here rather than in the spec drafts, so the drafts stay non-temporal.
## ifc-fides ## ifc-fides
- Inline `_meta.ifc` for low-friction adoption vs. always behind `evidenceRef`. - Inline `_meta.ifc` for low-friction adoption vs. always behind `evidenceRef`.
- GitHub Enterprise `internal` repo visibility → public/private/reader-set - GitHub Enterprise `internal` repo visibility → `public`/`private` mapping
mapping (audience is the whole org, broader than collaborators). (audience is the whole org, broader than collaborators; resolved host-side).
- Reader-set resolution is host-side by design — confidentiality join across two
`private` sources needs the intersection, which the opaque wire marker can't
express. Is the 3-step host resolution enough, or do some hosts need a
standard `evidenceRef.ref` shape to locate the originating system?
## Parked (SEP-1913 umbrella) ## Parked (SEP-1913 umbrella)

View file

@ -6,7 +6,7 @@ annotation work. Several were surfaced in IG meetings (notably 2026-05-28).
## SEPs ## SEPs
- [SEP-1913 — Trust and Sensitivity Annotations](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913) — the umbrella proposal these extensions derive from. - [SEP-1913 — Trust and Sensitivity Annotations](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913) — the umbrella proposal these extensions derive from.
- [SEP-2061 — Action Security Metadata](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061) — carried forward as `action-metadata`. - [SEP-2061 — Action Security Metadata](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061) — closed 2026-06-13; carried forward as `action-metadata`.
- [SEP-1862 — Tool Resolution / pre-flight checks](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1862) — core-protocol, composes with these extensions. - [SEP-1862 — Tool Resolution / pre-flight checks](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1862) — core-protocol, composes with these extensions.
- [SEP-2133 — Extensions](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2133-extensions.md) — the framework this repo incubates under. - [SEP-2133 — Extensions](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2133-extensions.md) — the framework this repo incubates under.
- [SEP-2127 — Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2893) — precedent for the Standards→Extensions Track refactor. - [SEP-2127 — Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2893) — precedent for the Standards→Extensions Track refactor.

View file

@ -68,12 +68,13 @@ session-level propagation rules. See [open-questions.md](./open-questions.md).
### SEP-2061 — Action Security Metadata ### SEP-2061 — Action Security Metadata
**Proposed:** becomes the [`action-metadata`](../specification/draft/action-metadata.mdx) **Disposition:** **closed 2026-06-13** in favour of the
extension. SEP-2061 is by [@rreichel3](https://github.com/rreichel3), who is [`action-metadata`](../specification/draft/action-metadata.mdx) extension.
also an IG co-facilitator and SEP-1913 co-author, so this is a fold-in, not a SEP-2061 is by [@rreichel3](https://github.com/rreichel3), who is also an IG
collision. Disposition mirrors 1913 option (A): keep the thread as the field- co-facilitator and SEP-1913 co-author, so this was a fold-in, not a collision.
semantics discussion, add a pointer comment linking it to the [@localden](https://github.com/localden) closed the PR (no active sponsor) after
`action-metadata` extension, refactor to Extensions Track when ready. agreeing the extension is the right home; the extension now carries the field
semantics forward, with SEP-2061 preserved as the origin and credit.
### SEP-1862 — Tool Resolution (pre-flight checks) ### SEP-1862 — Tool Resolution (pre-flight checks)
@ -97,7 +98,7 @@ with it if it lands, but do not block on it.
| SEP | Title | Proposed disposition | Extension home | | SEP | Title | Proposed disposition | Extension home |
| :-- | :-- | :-- | :-- | | :-- | :-- | :-- | :-- |
| 1913 | Trust & Sensitivity Annotations | Umbrella thread; schema moves to extensions | `trust-annotations` (+ `ifc-fides`) | | 1913 | Trust & Sensitivity Annotations | Umbrella thread; schema moves to extensions | `trust-annotations` (+ `ifc-fides`) |
| 2061 | Action Security Metadata | Fold into extension | `action-metadata` | | 2061 | Action Security Metadata | **Closed 2026-06-13**; lives as extension | `action-metadata` |
| 1862 | Tool Resolution (pre-flight) | Stays core / Standards Track | — (composes, no dependency) | | 1862 | Tool Resolution (pre-flight) | Stays core / Standards Track | — (composes, no dependency) |
| 1984 | Comprehensive Tool Annotations | IG discussion item | — | | 1984 | Comprehensive Tool Annotations | IG discussion item | — |
| 2417 | Model Preferences for Tools | IG discussion item | — | | 2417 | Model Preferences for Tools | IG discussion item | — |

View file

@ -10,8 +10,9 @@ title: Action Metadata
> [SEP-2061: Action Security Metadata](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061) > [SEP-2061: Action Security Metadata](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061)
> by [@rreichel3](https://github.com/rreichel3) into the IG's experimental repo, > 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 > 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 > first. SEP-2061 was [closed](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061#issuecomment-4675049171)
> semantics. > 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 ## Abstract
@ -81,9 +82,9 @@ Carried under the extension-namespaced key on `ToolAnnotations`:
| `outcome` | Real-world effect class: `benign` / `consequential` / `irreversible`. | | `outcome` | Real-world effect class: `benign` / `consequential` / `irreversible`. |
| `requiresReview` | The tool author signals that a host SHOULD obtain explicit human confirmation before invocation. | | `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 > Exact enum value sets are inherited from SEP-2061 as the starting point and
> here; this draft tracks that proposal. Where SEP-2061 evolves, this file > are **not** re-litigated here. With SEP-2061 now closed, this draft is where
> follows. > they evolve.
### `requiresReview` lives here, deliberately ### `requiresReview` lives here, deliberately
@ -130,3 +131,4 @@ would anchor the draft in a real ecosystem.
| Date | Change | | Date | Change |
| ---------- | ------------------------------------------------------------------- | | ---------- | ------------------------------------------------------------------- |
| 2026-06-10 | Initial draft skeleton, carrying SEP-2061 into the experimental repo; absorbed `requiresReview` from the trust taxonomy. | | 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. |

View file

@ -44,11 +44,12 @@ integrity (is this data trusted) as context accumulates across tool calls, and
deny or prompt before a flow violates policy. deny or prompt before a flow violates policy.
A public MCP server is the natural emitter. [`github-mcp-server`](https://github.com/github/github-mcp-server) A public MCP server is the natural emitter. [`github-mcp-server`](https://github.com/github/github-mcp-server)
returns repository data whose confidentiality is determined by repo visibility returns repository data whose confidentiality follows from repository visibility
and collaborator sets — exactly the public/private signal above — but does and collaborator sets — the same public/private signal — but does **not** emit
**not** emit IFC labels today. Closing that emitter gap is the concrete proof IFC labels today. Closing that emitter gap is the concrete proof point for this
point for this profile: a host-side consumer of the label shape already exists, profile: a host-side consumer of the label shape already exists, so the missing
so the missing half is a server willing to emit it. half is a server willing to emit it, classifying each resource it returns (see
[per-resource classification](#reference-implementation)).
## Specification ## Specification
@ -67,32 +68,62 @@ be inlined by deployments that accept the wire cost) has the shape:
```jsonc ```jsonc
{ {
"integrity": "trusted", // "trusted" | "untrusted" (FIDES §4.1 two-level lattice) "integrity": "trusted", // "trusted" | "untrusted" (FIDES §4.1 two-level lattice)
"confidentiality": "public" // "public" | "private" | ["login1","login2", …] "confidentiality": "public" // "public" | "private"
} }
``` ```
| Field | Meaning | | Field | Meaning |
| :--- | :--- | | :--- | :--- |
| `integrity` | Two-level integrity lattice (`trusted` ⊑ `untrusted`): trusted data may flow to untrusted sinks, not vice versa. | | `integrity` | Two-level integrity lattice (`trusted` ⊑ `untrusted`): trusted data may flow to untrusted sinks, not vice versa. |
| `confidentiality` | `"public"` = world-readable; `"private"` = an opaque marker the host resolves to a reader set (e.g. repo collaborators); an explicit `string[]` = pre-resolved reader logins. Fewer readers = more confidential = higher in the lattice. | | `confidentiality` | `"public"` = world-readable; `"private"` = an opaque marker meaning "restricted to some reader set". The concrete reader set is resolved host-side at policy-decision time (see [Reader-set resolution](#reader-set-resolution)). |
> **Confidentiality is `public` / `private` only — never a reader list on the
> wire.** Emitting concrete reader identities (e.g. logins) is out of scope: user
> identity is not uniform across servers using different auth methods, the
> identities are themselves access-restricted data, and a single resource can
> have hundreds of readers. The opaque marker keeps the wire shape stable and the
> sensitive resolution host-side.
### Label semantics ### Label semantics
- **Join on accumulation.** As a session ingests labeled results, the context - **Join on accumulation.** As a session ingests labeled results, the context
label is the *join* of what it has seen: integrity degrades toward label is the *join* of what it has seen: integrity degrades toward
`untrusted`, confidentiality narrows toward the smallest reader set. `untrusted`, confidentiality narrows toward the smallest permitted reader set.
The **integrity join is total and computable from the wire values alone**
(`untrusted` dominates). The **confidentiality join is NOT computable from the
opaque wire markers alone** — see [Reader-set resolution](#reader-set-resolution).
- **Policy check before egress.** Before a write/egress tool call, the host - **Policy check before egress.** Before a write/egress tool call, the host
checks whether the current context label may flow to the call's target. When checks whether the current context label may flow to the call's target. When
a label is absent, the host falls back to its default (trusted-action) a label is absent, the host falls back to its default (trusted-action)
policy rather than assuming the worst — labels are an *additive* signal. policy rather than assuming the worst — labels are an *additive* signal.
- **Confidentiality resolution.** `"private"` is intentionally opaque on the
wire; resolving it to a concrete reader set (e.g. via a collaborators lookup)
is a host concern, so servers need not enumerate audiences inline.
> The normative integrity/confidentiality lattice definitions follow the FIDES > The normative integrity/confidentiality lattice definitions follow the FIDES
> paper, §4.1 and §4.3. This draft references the model rather than restating > paper, §4.1 and §4.3. This draft references the model rather than restating
> the proofs. > the proofs.
### Reader-set resolution
`"private"` is intentionally opaque on the wire. Two distinct `"private"`
markers (e.g. file contents from two different private repositories) are **not
equal**, and their confidentiality join is **not** the same `"private"` token:
data derived from both may flow only to principals who can read *both* sources —
the intersection of their reader sets. The opaque marker cannot express this
intersection, so a host that needs to make a precise cross-source flow decision
MUST resolve each `"private"` marker to a concrete reader set before joining.
Resolution is a host-side concern, performed at policy-decision time:
1. The host maps each contributing `"private"` label back to its source (e.g.
via the `evidenceRef.ref` locator, or its own record of which tool result
carried the label).
2. The host queries the originating system for the current reader set (e.g. a
repository collaborators lookup) using its own credentials.
3. The host computes the flow decision over the resolved sets (intersection for
a join of multiple private sources) and then discards them.
Keeping resolution host-side is what lets the wire stay at `public` / `private`
while still supporting precise multi-source decisions.
### Relationship to `trust-annotations` ### Relationship to `trust-annotations`
`ifc-fides` never appears without a host `trust-annotations` annotation `ifc-fides` never appears without a host `trust-annotations` annotation
@ -106,17 +137,24 @@ the IFC label is the precise, host-checkable evidence behind them.
applies a flow policy before egress operations already exists in practice. applies a flow policy before egress operations already exists in practice.
(Linked once a public reference is available.) (Linked once a public reference is available.)
- **Emitter (gap / proof point):** [`github-mcp-server`](https://github.com/github/github-mcp-server) - **Emitter (gap / proof point):** [`github-mcp-server`](https://github.com/github/github-mcp-server)
is the candidate — it already knows repo visibility and collaborator sets, is the candidate — it already knows repository visibility and collaborator
which are exactly the confidentiality inputs. sets, which are the confidentiality inputs. Repository visibility is only a
*default* hint, not the whole story: a public repository can serve
sub-resources that are **not** world-readable (draft security advisories,
draft releases, the collaborator roster itself, authenticated-user fields), so
a correct emitter MUST classify **per resource returned**, not per repository.
That makes the emitter a non-trivial proof point rather than a one-line
`repo.private` read.
## Open questions ## Open questions
- Should the label be inlinable on `_meta.ifc` directly for low-friction - Should the label be inlinable on `_meta.ifc` directly for low-friction
adoption, or always behind `evidenceRef` for schema minimalism? (Lean: adoption, or always behind `evidenceRef` for schema minimalism? (Lean:
permit both; `evidenceRef` is canonical, inline is a convenience.) permit both; `evidenceRef` is canonical, inline is a convenience.)
- How does GitHub Enterprise `internal` repo visibility map onto the - How does GitHub Enterprise `internal` repository visibility map onto the
public/private/reader-set confidentiality model? (Audience is the whole org, `public` / `private` confidentiality model? (Audience is the whole org,
strictly broader than collaborators — likely falls back to default policy.) strictly broader than collaborators — likely classified `private` and resolved
host-side, or falls back to default policy.)
- Registry coordination with other attestation/evidence profiles - Registry coordination with other attestation/evidence profiles
(e.g. SEP-2787) so `evidenceRef.type` values don't collide. (e.g. SEP-2787) so `evidenceRef.type` values don't collide.
@ -125,3 +163,4 @@ the IFC label is the precise, host-checkable evidence behind them.
| Date | Change | | Date | Change |
| ---------- | ------------------------------------------------------------ | | ---------- | ------------------------------------------------------------ |
| 2026-06-10 | Initial draft skeleton. Reframed from a top-level `ifc` extension to a `trust-annotations` `evidenceRef` profile (`ifc.fides.v1`). | | 2026-06-10 | Initial draft skeleton. Reframed from a top-level `ifc` extension to a `trust-annotations` `evidenceRef` profile (`ifc.fides.v1`). |
| 2026-06-15 | Confidentiality limited to `public` / `private` on the wire (dropped reader-list); added Reader-set resolution section; emitter classifies per resource, not per repository. (Review: @JoannaaKL.) |