repo: make trust-annotations the base; FIDES becomes a scheme, not a sibling

Restructure the incubation into two extensions plus interchangeable data-labelling
schemes, shipped as a stack of three PRs:

- This PR (base): shared scaffolding + the trust-annotations extension.
- action-metadata moves to its own stacked PR.
- The IFC/FIDES work moves out of specification/draft/ entirely — it is one
  data-labelling scheme (ifc.fides.v1) that fills the trust-annotations
  evidenceRef slot, not an extension and not a sibling. It lands in a schemes/
  folder built to hold alternative approaches (data-class, capability tokens,
  cosigning, sequence-shape, attestation) so no single academic model is baked
  into the wire.

README, sep-disposition, related-work, trust-model, open-questions and the
decision log are reframed accordingly (two extensions + a schemes folder; the
range of candidate schemes drawn from the SEP-1913 thread and cited literature).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Sam Morrow 2026-06-16 01:23:28 +02:00
commit 678f846e23
No known key found for this signature in database
8 changed files with 87 additions and 356 deletions

View file

@ -25,11 +25,14 @@ potential narrower first cut?" The subsequent design discussion converged on a
layered answer: a small, stable annotation surface on the wire, with richer layered answer: a small, stable annotation surface on the wire, with richer
evidence kept out-of-band and referenced by a bounded pointer. evidence kept out-of-band and referenced by a bounded pointer.
This repo follows that steer. Each concern becomes a **separate experimental This repo follows that steer. The schema-bearing concerns become **separate
extension** with its own [reverse-DNS identifier](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2133-extensions.md#definition), experimental extensions**, each with its own [reverse-DNS identifier](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2133-extensions.md#definition),
its own reference implementation, and its own path to a future Extensions reference implementation, and path to a future Extensions Track SEP, so drafts
Track SEP. Drafts can graduate independently — directly addressing the "narrower can graduate independently. The concrete data-labelling models that fill an
first cut" ask without throwing away the combinatoric value of the full set. extension's evidence slot are kept separate again — as interchangeable **schemes**
rather than extensions — so no single academic model is baked into the wire. This
directly addresses the "narrower first cut" ask without throwing away the
combinatoric value of the full set.
See [docs/decisions.md](docs/decisions.md) for the decision record and See [docs/decisions.md](docs/decisions.md) for the decision record and
[docs/trust-model.md](docs/trust-model.md) for the shared enforcement model. [docs/trust-model.md](docs/trust-model.md) for the shared enforcement model.
@ -40,19 +43,42 @@ See [docs/decisions.md](docs/decisions.md) for the decision record and
| :--- | :--- | :--- | :--- | | :--- | :--- | :--- | :--- |
| [`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. | 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/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). |
### Why FIDES is a profile, not a top-level extension Each extension is proposed in its own pull request so it can be reviewed and
graduate on its own clock.
Information-flow control is modelled as a profile rather than the namespace ## Data-labelling schemes (the `evidenceRef` slot)
root because IFC (an integrity × confidentiality lattice) is one enforcement
model among several that reviewers raised — capability tokens, caller/tool The extensions above keep the wire vocabulary deliberately small. Richer
cosigning, and sequence-shape audit records. A top-level `ifc/` root would bake labelling lives **out-of-band**, referenced by the `trust-annotations`
one academic model into the namespace and foreclose the others. As one reviewer [`evidenceRef`](specification/draft/trust-annotations.mdx) pointer, whose `type`
put it, IFC "fits relatively well if you use annotations" — an endorsement of is an open string. A **scheme** is a concrete data-labelling or tool-annotation
IFC *as a profile*, not as the wire root. As a `type` value under approach that fills that slot under a `type` value. A scheme is **not** an
`trust-annotations`'s open-ended `evidenceRef` slot, the FIDES work stays extension and not a sibling of the two above — it is one interchangeable way to
first-class while every other model can occupy the same slot. populate the evidence an extension carries, and a deployment can adopt, swap, or
ignore it without touching the extension.
The [`schemes/`](schemes/) folder collects these approaches. **FIDES** is the
first worked example, defining `ifc.fides.v1`; it is one model among several that
reviewers and the literature have raised, and the slot is designed so any of them
can occupy it:
| Scheme | `evidenceRef.type` | Source |
| :--- | :--- | :--- |
| FIDES information-flow control (integrity × confidentiality lattice) | `ifc.fides.v1` | [arXiv:2505.23643](https://arxiv.org/abs/2505.23643); emitter candidate [`github-mcp-server`](https://github.com/github/github-mcp-server) |
| Coarse data classification (4-level + regulatory scope) | `data-class.v1` | SEP-1913 taxonomy |
| Design-pattern controls (Plan-Then-Execute, Dual LLM, Map-Reduce) | _candidate_ | [arXiv:2506.08837](https://arxiv.org/abs/2506.08837) |
| Capability-token constraints (SINT) | _candidate_ | pshkv, [SEP-1913 thread](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913) |
| Caller/tool cosigning | _candidate_ | viftode4, SEP-1913 thread |
| Sequence-shape audit records | _candidate_ | marras0914, SEP-1913 thread |
| Tool-call attestation (in-toto / OVERT envelopes) | _candidate_ | [SEP-2787](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2787) |
Modelling IFC as a scheme rather than a namespace root is deliberate: a top-level
`ifc/` extension would bake one academic model into the wire and foreclose the
others. As one reviewer put it, IFC "fits relatively well if you use annotations"
— an endorsement of IFC *behind* the annotation slot, not as the slot itself. See
[`schemes/README.md`](schemes/README.md) for the full list and the bar for adding
a scheme.
## Relationship to SEP-1913 ## Relationship to SEP-1913
@ -84,7 +110,8 @@ This repo mirrors the structure of official extension repositories such as
[`ext-auth`](https://github.com/modelcontextprotocol/ext-auth): [`ext-auth`](https://github.com/modelcontextprotocol/ext-auth):
``` ```
specification/draft/<extension-name>.mdx # one spec per extension specification/draft/<extension-name>.mdx # one spec per extension (trust-annotations, action-metadata)
schemes/ # data-labelling schemes that fill the evidenceRef slot (FIDES, …)
docs/ # decision log, open questions, related work docs/ # decision log, open questions, related work
MAINTAINERS.md # IG facilitators MAINTAINERS.md # IG facilitators
``` ```

View file

@ -89,3 +89,31 @@ Resolution. SEP-1862 remains a core/Standards-Track protocol change.
**Rationale.** The 2026-05-28 IG meeting concluded pre-flight is inherently a **Rationale.** The 2026-05-28 IG meeting concluded pre-flight is inherently a
protocol-level change, not an extension. protocol-level change, not an extension.
## 2026-06-16 — FIDES is a scheme, not a sibling extension
**Decision.** Refines the 2026-06-10 "FIDES is a profile" decision. The IFC/FIDES
work moves out of `specification/draft/` (where it sat next to the two
extensions) into a `schemes/` folder. There are **two** extensions
(`trust-annotations`, `action-metadata`); FIDES is **one data-labelling scheme**
(`ifc.fides.v1`) that fills the `trust-annotations` `evidenceRef` slot.
**Rationale.** FIDES is one model the extension *could* use, not a peer of the
extensions, and must not be presented as a sibling. The original SEP cites it
alongside ShardGuard and "Design Patterns for Securing LLM Agents," and the
SEP-1913 thread adds capability tokens, cosigning, sequence-shape, and
attestation models — so `schemes/` is a folder for interchangeable approaches,
with FIDES as the first worked one. This shows the range the open `evidenceRef`
slot is meant to carry rather than implying IFC is the privileged model.
## 2026-06-16 — Three pull requests, stacked
**Decision.** The work ships as three PRs: `trust-annotations` (the base,
carrying shared repo scaffolding), `action-metadata` (stacked on the base), and
the FIDES scheme in `schemes/` (stacked on the base). The two extensions are
independent; the FIDES scheme depends on `trust-annotations` because it fills
that extension's `evidenceRef` slot.
**Rationale.** Separate PRs let each piece be reviewed and graduate on its own
clock. FIDES stacks on `trust-annotations` because a scheme has no meaning
without the slot it fills.

View file

@ -30,7 +30,7 @@ Tracked here rather than in the spec drafts, so the drafts stay non-temporal.
- Open strings vs. closed enums for `destination` / `source` / `sensitivity`. - Open strings vs. closed enums for `destination` / `source` / `sensitivity`.
- Does `requiresReview` need a machine-readable *reason* for good client UX? - Does `requiresReview` need a machine-readable *reason* for good client UX?
## ifc-fides ## ifc-fides (scheme)
- 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` mapping - GitHub Enterprise `internal` repo visibility → `public`/`private` mapping

View file

@ -10,11 +10,11 @@ annotation work. Several were surfaced in IG meetings (notably 2026-05-28).
- [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.
- [SEP-2787 — Tool Call Attestation](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2787) — candidate `evidenceRef` profile. - [SEP-2787 — Tool Call Attestation](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2787) — candidate `evidenceRef` scheme.
## Research ## Research
- **FIDES** — *Information-flow control for LLM agents.* [arXiv:2505.23643](https://arxiv.org/abs/2505.23643). Basis for the `ifc.fides.v1` profile. - **FIDES** — *Information-flow control for LLM agents.* [arXiv:2505.23643](https://arxiv.org/abs/2505.23643). Basis for the `ifc.fides.v1` scheme in [`schemes/`](../schemes/).
- **Design Patterns for Securing LLM Agents** — IBM/Google/Microsoft. [arXiv:2506.08837](https://arxiv.org/abs/2506.08837). Plan-Then-Execute, Dual LLM, Map-Reduce, etc. - **Design Patterns for Securing LLM Agents** — IBM/Google/Microsoft. [arXiv:2506.08837](https://arxiv.org/abs/2506.08837). Plan-Then-Execute, Dual LLM, Map-Reduce, etc.
- **Trail of Bits** — prompt-injection via hidden content in GitHub issues. [blog](https://blog.trailofbits.com/2025/08/06/prompt-injection-engineering-for-attackers-exploiting-github-copilot/). - **Trail of Bits** — prompt-injection via hidden content in GitHub issues. [blog](https://blog.trailofbits.com/2025/08/06/prompt-injection-engineering-for-attackers-exploiting-github-copilot/).
- **OpenAI Auto Review** — https://alignment.openai.com/auto-review/ (shared in IG chat). - **OpenAI Auto Review** — https://alignment.openai.com/auto-review/ (shared in IG chat).
@ -22,7 +22,7 @@ annotation work. Several were surfaced in IG meetings (notably 2026-05-28).
## Implementations & tooling ## Implementations & tooling
- [`kapil8811/mcp-trust-annotations`](https://github.com/kapil8811/mcp-trust-annotations) — reference Python SDK PoC for `trust-annotations`. - [`kapil8811/mcp-trust-annotations`](https://github.com/kapil8811/mcp-trust-annotations) — reference Python SDK PoC for `trust-annotations`.
- [`github-mcp-server`](https://github.com/github/github-mcp-server) — public MCP server; emitter candidate for `ifc-fides` (knows repo visibility + collaborators). - [`github-mcp-server`](https://github.com/github/github-mcp-server) — public MCP server; emitter candidate for the `ifc-fides` scheme (knows repo visibility + collaborators).
- **Ethyca** data-labeling docs — https://www.ethyca.com/docs (shared in IG chat). - **Ethyca** data-labeling docs — https://www.ethyca.com/docs (shared in IG chat).
- **GitHub Next** agentic-workflows research on data labeling — to be documented as issues in this repo (IG action item, @gokhanarkan / @joannakl). - **GitHub Next** agentic-workflows research on data labeling — to be documented as issues in this repo (IG action item, @gokhanarkan / @joannakl).
@ -35,4 +35,4 @@ annotation work. Several were surfaced in IG meetings (notably 2026-05-28).
- **Sequence-shape** policies — marras0914. - **Sequence-shape** policies — marras0914.
These are exactly the models that `evidenceRef`'s open `type` is designed to These are exactly the models that `evidenceRef`'s open `type` is designed to
accommodate as profiles. accommodate as schemes — see [`schemes/`](../schemes/).

View file

@ -62,7 +62,10 @@ preference:
- **(C)** Close 1913 outright and open three fresh Extensions Track SEPs. Loses - **(C)** Close 1913 outright and open three fresh Extensions Track SEPs. Loses
the discussion history's continuity; not preferred. the discussion history's continuity; not preferred.
**Moved into extensions:** `trust-annotations`, `action-metadata`, `ifc-fides`. **Moved into extensions:** `trust-annotations`, `action-metadata`.
**Moved into `schemes/`:** the IFC/FIDES work, as one data-labelling **scheme**
(`ifc.fides.v1`) that fills the `trust-annotations` `evidenceRef` slot — not an
extension and not a sibling of the two above.
**Parked on the umbrella:** `maliciousActivityHint`, **Parked on the umbrella:** `maliciousActivityHint`,
session-level propagation rules. See [open-questions.md](./open-questions.md). session-level propagation rules. See [open-questions.md](./open-questions.md).
@ -90,14 +93,14 @@ with it if it lands, but do not block on it.
for Tools)** — tracked by the IG as discussion items; not part of these for Tools)** — tracked by the IG as discussion items; not part of these
extensions. Cross-link only. extensions. Cross-link only.
- **SEP-2787 (Tool Call Attestation)** and the various attestation/evidence - **SEP-2787 (Tool Call Attestation)** and the various attestation/evidence
threads — these are natural `evidenceRef` *profile* candidates rather than threads — these are natural `evidenceRef` **scheme** candidates rather than
competitors. Coordinate so the `evidenceRef.type` registry can list them. competitors. Coordinate so the `evidenceRef.type` registry can list them.
## Mapping table ## Mapping table
| 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` (+ `schemes/ifc-fides`) |
| 2061 | Action Security Metadata | **Closed 2026-06-13**; lives as 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 | — |

View file

@ -1,7 +1,7 @@
# Trust model # Trust model
A single statement of the enforcement model shared by all extensions in this A single statement of the enforcement model shared across this repository's
repository, so individual specs don't re-litigate it. extensions and data-labelling schemes, so individual specs don't re-litigate it.
## Annotations are claims, not guarantees ## Annotations are claims, not guarantees
@ -41,7 +41,7 @@ SEP-1913 thread): rather than blanket-blocking flows a policy engine is unsure
about, **flag the specific call for user confirmation**. This preserves utility about, **flag the specific call for user confirmation**. This preserves utility
while keeping a human on the genuinely risky edges, and is the recommended while keeping a human on the genuinely risky edges, and is the recommended
default for `requiresReview` ([`action-metadata`](../specification/draft/action-metadata.mdx)) default for `requiresReview` ([`action-metadata`](../specification/draft/action-metadata.mdx))
and for IFC policy violations ([`ifc-fides`](../specification/draft/ifc-fides.mdx)). and for IFC policy violations (the [`ifc-fides`](../schemes/ifc-fides.md) scheme).
## Cross-domain is the hard case ## Cross-domain is the hard case

View file

@ -1,134 +0,0 @@
---
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. |

View file

@ -1,193 +0,0 @@
---
title: Information-Flow Control (FIDES profile)
---
<Info>**Protocol Revision**: draft</Info>
**Extension identifier:** `io.modelcontextprotocol/ifc-fides` &nbsp;·&nbsp; **Profile of:** `io.modelcontextprotocol/trust-annotations`
> ⚠️ **Experimental draft skeleton.** This defines a *profile* of the
> [`trust-annotations`](./trust-annotations.mdx) `evidenceRef` slot. It is **not**
> a standalone wire root — see [Why a profile](#why-a-profile).
## Abstract
This extension defines `ifc.fides.v1`, a profile of the `trust-annotations`
`evidenceRef` slot that carries an **information-flow-control label** —
integrity plus confidentiality — following the FIDES model
([arXiv:2505.23643](https://arxiv.org/abs/2505.23643)). A host that implements
deterministic information-flow control can consume these labels to decide
whether a tool call is permitted, without baking the IFC model into the core
protocol or into the `trust-annotations` wire surface.
## Why a profile
Information-flow control is one enforcement model among several that reviewers
of SEP-1913 raised — capability tokens, caller/tool cosigning, and
sequence-shape audit records were all put forward. A top-level extension
(`io.modelcontextprotocol/ifc`) would make the FIDES integrity × confidentiality
lattice the namespace root and silently foreclose those other models.
As a `type` value under the open-ended `evidenceRef` slot, the FIDES label is
first-class while the slot stays free for every other model. One reviewer's
framing captured it: IFC "fits relatively well *if you use annotations*" — an
endorsement of IFC as a profile, not as the wire root.
## Motivation
The motivating case is the one raised in the
[2026-05-28 IG meeting](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/2820):
**a model often cannot tell whether a repository is public or private**, and
lacking that signal it may push private content to a public destination. An IFC
label lets the host track confidentiality (who may read this data) and
integrity (is this data trusted) as context accumulates across tool calls, and
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)
returns repository data whose confidentiality follows from repository visibility
and collaborator sets — the same public/private signal — but does **not** emit
IFC labels today. Closing that emitter gap is the concrete proof point for this
profile: a host-side consumer of the label shape already exists, so the missing
half is a server willing to emit it, classifying each resource it returns (see
[per-resource classification](#reference-implementation)).
## Specification
### Profile identity
This profile is selected by `evidenceRef.type == "ifc.fides.v1"` on a
`trust-annotations` annotation. A client that does not implement IFC MUST be
able to ignore it safely (the surrounding `sensitive` / `untrusted` booleans and
the `digest`/`canonicalization` pair remain meaningful).
### Label payload
The record referenced by the `evidenceRef` (and, for low-friction adoption, MAY
be inlined by deployments that accept the wire cost) has the shape:
```jsonc
{
"integrity": "trusted", // "trusted" | "untrusted" (FIDES §4.1 two-level lattice)
"confidentiality": "public" // "public" | "private"
}
```
| Field | Meaning |
| :--- | :--- |
| `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 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
The load-bearing distinction is between the wire and the host: **wire markers are
advisory hints; reader-set semantics are host-resolved.** The asymmetry between
the two joins below follows from that one cut.
- **Join on accumulation.** As a session ingests labeled results, the context
label is the *join* of what it has seen: integrity degrades toward
`untrusted`, confidentiality narrows toward the smallest permitted reader set.
The two joins differ in *where* they can be computed, and the difference is
principled rather than incidental:
- **Integrity join is total and wire-computable.** The integrity lattice is
small and closed (`trusted ⊑ untrusted`), so `untrusted` dominates and the
join needs nothing beyond the wire values.
- **Confidentiality join is partial and host-resolved.** Reader sets are open
and host-knowledge-dependent. `public` is the one wire-computable case,
because its reader set is universal (`⊤`): `public ⊔ anything = public`.
`private ⊔ private`, by contrast, is the *intersection* of two reader sets
that the opaque markers don't carry, so it is **not** computable from the
wire — see [Reader-set resolution](#reader-set-resolution).
- **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
a label is absent, the host falls back to its default (trusted-action)
policy rather than assuming the worst — labels are an *additive* signal.
> The normative integrity/confidentiality lattice definitions follow the FIDES
> paper, §4.1 and §4.3. This draft references the model rather than restating
> the proofs.
### Reader-set resolution
`"private"` is intentionally opaque on the wire — and that opaqueness is a
property of the security model, not a limitation of the spec. A reader set is not
transmissible without policy context, so the wire shape correctly declines to
carry it. 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.
**When resolution is unavailable** — the `ref` is absent, the label is
digest-only, or the originating system is unreachable at decision time — the host
MUST NOT treat two opaque labels as equal, and MUST NOT treat `"private"` as
`"public"`. It denies, prompts, or applies its configured fail-closed policy. Two
`"private"` labels are equal only once resolution proves their sources are; until
a source is established, unknown or mixed provenance classifies as `"private"`,
never defaulted to `"public"` from a repository-level shortcut.
The resolved reader set is a decision-time read performed under the host's own
credentials. It is not a durable grant: a host SHOULD NOT cache it as one or
serialize it back into annotations or evidence unless a deployment explicitly opts
in. This keeps the wire free of user identities while still letting the host make a
precise decision when it holds source provenance plus its own credentials.
### Relationship to `trust-annotations`
`ifc-fides` never appears without a host `trust-annotations` annotation
carrying the `evidenceRef`. The booleans are the universally-actionable signal;
the IFC label is the precise, host-checkable evidence behind them.
## Reference implementation
- **Consumer:** a host-side IFC engine that parses the `{integrity,
confidentiality}` label, maintains a context label across tool results, and
applies a flow policy before egress operations already exists in practice.
(Linked once a public reference is available.)
- **Emitter (gap / proof point):** [`github-mcp-server`](https://github.com/github/github-mcp-server)
is the candidate — it already knows repository visibility and collaborator
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
- Should the label be inlinable on `_meta.ifc` directly for low-friction
adoption, or always behind `evidenceRef` for schema minimalism? (Lean:
permit both; `evidenceRef` is canonical, inline is a convenience.)
- How does GitHub Enterprise `internal` repository visibility map onto the
`public` / `private` confidentiality model? (Audience is the whole org,
strictly broader than collaborators — likely classified `private` and resolved
host-side, or falls back to default policy.)
- Registry coordination with other attestation/evidence profiles
(e.g. SEP-2787) so `evidenceRef.type` values don't collide.
## Changelog
| 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-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.) |
| 2026-06-16 | Lead the semantics with the wire-hint / host-resolved split; state the integrity-total vs confidentiality-partial asymmetry as principled (`public` = `⊤` is wire-computable, `private ⊔ private` is not); add fail-closed handling when resolution is unavailable and a no-durable-grant rule for resolved sets. (Review: @Rul1an.) |