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:
parent
4967a9eccc
commit
678f846e23
8 changed files with 87 additions and 356 deletions
61
README.md
61
README.md
|
|
@ -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
|
||||
evidence kept out-of-band and referenced by a bounded pointer.
|
||||
|
||||
This repo follows that steer. Each concern becomes a **separate experimental
|
||||
extension** 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
|
||||
Track SEP. Drafts can graduate independently — directly addressing the "narrower
|
||||
first cut" ask without throwing away the combinatoric value of the full set.
|
||||
This repo follows that steer. The schema-bearing concerns become **separate
|
||||
experimental extensions**, each with its own [reverse-DNS identifier](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2133-extensions.md#definition),
|
||||
reference implementation, and path to a future Extensions Track SEP, so drafts
|
||||
can graduate independently. The concrete data-labelling models that fill an
|
||||
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
|
||||
[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/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
|
||||
root because IFC (an integrity × confidentiality lattice) is one enforcement
|
||||
model among several that reviewers raised — capability tokens, caller/tool
|
||||
cosigning, and sequence-shape audit records. A top-level `ifc/` root would bake
|
||||
one academic model into the namespace and foreclose the others. As one reviewer
|
||||
put it, IFC "fits relatively well if you use annotations" — an endorsement of
|
||||
IFC *as a profile*, not as the wire root. As a `type` value under
|
||||
`trust-annotations`'s open-ended `evidenceRef` slot, the FIDES work stays
|
||||
first-class while every other model can occupy the same slot.
|
||||
## Data-labelling schemes (the `evidenceRef` slot)
|
||||
|
||||
The extensions above keep the wire vocabulary deliberately small. Richer
|
||||
labelling lives **out-of-band**, referenced by the `trust-annotations`
|
||||
[`evidenceRef`](specification/draft/trust-annotations.mdx) pointer, whose `type`
|
||||
is an open string. A **scheme** is a concrete data-labelling or tool-annotation
|
||||
approach that fills that slot under a `type` value. A scheme is **not** an
|
||||
extension and not a sibling of the two above — it is one interchangeable way to
|
||||
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
|
||||
|
||||
|
|
@ -84,7 +110,8 @@ This repo mirrors the structure of official extension repositories such as
|
|||
[`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
|
||||
MAINTAINERS.md # IG facilitators
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
- 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`.
|
||||
- GitHub Enterprise `internal` repo visibility → `public`/`private` mapping
|
||||
|
|
|
|||
|
|
@ -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-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-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
|
||||
|
||||
- **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.
|
||||
- **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).
|
||||
|
|
@ -22,7 +22,7 @@ annotation work. Several were surfaced in IG meetings (notably 2026-05-28).
|
|||
## Implementations & tooling
|
||||
|
||||
- [`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).
|
||||
- **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.
|
||||
|
||||
These are exactly the models that `evidenceRef`'s open `type` is designed to
|
||||
accommodate as profiles.
|
||||
accommodate as schemes — see [`schemes/`](../schemes/).
|
||||
|
|
|
|||
|
|
@ -62,7 +62,10 @@ preference:
|
|||
- **(C)** Close 1913 outright and open three fresh Extensions Track SEPs. Loses
|
||||
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`,
|
||||
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
|
||||
extensions. Cross-link only.
|
||||
- **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.
|
||||
|
||||
## Mapping table
|
||||
|
||||
| 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` |
|
||||
| 1862 | Tool Resolution (pre-flight) | Stays core / Standards Track | — (composes, no dependency) |
|
||||
| 1984 | Comprehensive Tool Annotations | IG discussion item | — |
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Trust model
|
||||
|
||||
A single statement of the enforcement model shared by all extensions in this
|
||||
repository, so individual specs don't re-litigate it.
|
||||
A single statement of the enforcement model shared across this repository's
|
||||
extensions and data-labelling schemes, so individual specs don't re-litigate it.
|
||||
|
||||
## 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
|
||||
while keeping a human on the genuinely risky edges, and is the recommended
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
---
|
||||
title: Information-Flow Control (FIDES profile)
|
||||
---
|
||||
|
||||
<Info>**Protocol Revision**: draft</Info>
|
||||
|
||||
**Extension identifier:** `io.modelcontextprotocol/ifc-fides` · **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.) |
|
||||
Loading…
Reference in a new issue