Skip to main content
Glama
README.md
<h1 align="center">cvd-mcp</h1>

<p align="center"><strong>Fail loud, not fail confident.</strong></p>

<p align="center">
A five-field validity contract for agent-served context.<br/>
When the context expires, the agent <em>refuses and names a human</em> — instead of confidently lying.
</p>

<p align="center">
  <a href="https://doi.org/10.5281/zenodo.21660205"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.21660205.svg" alt="DOI"></a>
  <img src="https://img.shields.io/badge/python-3.9%2B-blue" alt="Python 3.9+">
  <img src="https://img.shields.io/badge/core_dependencies-zero-brightgreen" alt="Zero dependencies">
  <img src="https://img.shields.io/badge/MCP-ready-8A2BE2" alt="MCP ready">
  <img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license">
</p>

<p align="center">
  <a href="#the-silent-failure">Problem</a> ·
  <a href="#how-it-works">How it works</a> ·
  <a href="#the-five-primitives">Primitives</a> ·
  <a href="#install">Install</a> ·
  <a href="#quickstart">Quickstart</a> ·
  <a href="#guarding-an-mcp-tool">MCP server</a> ·
  <a href="#citation">Citation</a>
</p>

---

Reference implementation of the **Context Validity Declaration (CVD)** from the position paper *The Agent Steward: Context Provenance and Temporal Validity as a Missing Governance Dimension in Multi-Agent Enterprise Ecosystems* (Kefreen Lacerda, 2026).

## The silent failure

An agent serving organizational context — prices, policies, product facts — that has silently gone out of date **passes every technical health check**. It responds within SLA. It returns success. It raises no exception. It is simply wrong.

A stale spreadsheet at least shows an old timestamp. A stale agent answer carries **no observable signal at all**:

<table>
<tr>
<th width="50%">😌 Without CVD</th>
<th width="50%">🚨 With CVD</th>
</tr>
<tr>
<td valign="top">

```json
{
  "sku": "WIDGET-PRO",
  "unit_price_usd": 4800.00
}
```

Price list changed 9 days ago.
The agent doesn't know. Neither do you.
**Confidently wrong — and invisible.**

</td>
<td valign="top">

```json
{
  "status": "refused",
  "reason": "context_expired",
  "disclosure": "…last validated 14 days
   ago… The accountable steward is
   Ana Souza; contact them to revalidate."
}
```

**Visibly stale — and recoverable.**
Someone accountable is one message away.

</td>
</tr>
</table>

The CVD attaches a small, declarative validity contract to any exposed context (an MCP tool/resource, or an Agent Card) so that **obsolescence becomes detectable at runtime**.

## How it works

```mermaid
flowchart LR
    A["agent calls<br/>guarded tool"] --> B{"age =<br/>now − last_validated"}
    B -->|"age ≤ freshness_slo"| C["✅ FRESH<br/>answer, unchanged"]
    B -->|"age > freshness_slo"| D{"decay_policy<br/>.on_violation"}
    D -->|refuse_with_disclosure| E["🛑 refusal that<br/>names the steward"]
    D -->|warn| F["⚠️ answers,<br/>but flagged"]
    D -->|escalate_to_steward| G["📣 routed to<br/>escalation target"]
    D -->|"unknown policy"| E
```

One decision point, four outcomes — and the unsafe path (answering on expired context as if nothing happened) **does not exist**.

## The five primitives

A CVD manifest declares exactly five fields. Nothing more:

| # | Primitive | Meaning |
|:-:|---|---|
| 1 | `steward` | Named identity of the accountable **human** — a person, not a functional mailbox |
| 2 | `source_of_record` | The system this context derives from |
| 3 | `freshness_slo` | Maximum tolerated staleness, as an ISO 8601 duration (`P7D`, `PT12H`, `P1DT6H`) |
| 4 | `last_validated` | ISO 8601 timestamp of the last **affirmative human validation** |
| 5 | `decay_policy` | `on_violation` (required) + optional `fallback` |

As a manifest ([examples/pricing_context.json](examples/pricing_context.json)):

```json
{
  "steward": "Ana Souza (Head of Pricing Operations)",
  "source_of_record": "SAP ERP price list PL-2026-Q3",
  "freshness_slo": "P7D",
  "last_validated": "2026-07-15T09:00:00Z",
  "decay_policy": {
    "on_violation": "refuse_with_disclosure",
    "fallback": "escalate_to_steward"
  }
}
```

> [!NOTE]
> `freshness_slo` rejects years and months (`P1Y`, `P1M`) — they don't map to a fixed duration. Express the window in days: `P30D`, `P365D`. Timestamps accept a trailing `Z`; naive timestamps are treated as UTC.

## Install

```bash
pip install -e .            # core — zero dependencies, Python ≥ 3.9
pip install -e ".[mcp]"     # + official MCP Python SDK, for the example server
pip install -e ".[dev]"     # + pytest
```

The core (`cvd.models`, `cvd.evaluator`, `cvd.guard`) is **standard library only**. It installs, imports, and tests without `mcp` — the SDK is needed only for the example server.

## Quickstart

Two lines of ceremony: declare the contract, guard the function.

```python
from cvd import ContextValidity, DecayPolicy, cvd_guard

pricing_cv = ContextValidity(
    steward="Ana Souza (Head of Pricing Operations)",
    source_of_record="SAP ERP price list PL-2026-Q3",
    freshness_slo="P7D",
    last_validated="2026-07-15T09:00:00Z",
    decay_policy=DecayPolicy(on_violation="refuse_with_disclosure"),
)

@cvd_guard(pricing_cv)
def get_enterprise_price(sku: str) -> dict:
    return {"sku": sku, "unit_price_usd": 4800.00}
```

While the context is fresh, the function behaves as if the guard weren't there. Once it expires, **the function is not even called** — the caller gets the structured refusal instead.

Need the decision itself, or prefer exceptions?

```python
from cvd import evaluate, ContextExpired

decision = evaluate(pricing_cv)          # Decision(freshness, allowed, action, age, slo, …)

@cvd_guard(pricing_cv, raise_on_expired=True)
def get_price(sku: str) -> dict: ...     # raises ContextExpired, carrying the Decision
```

<details>
<summary><strong>The four decay policies</strong></summary>

| `on_violation` | Wrapped call | Result when stale |
|---|:-:|---|
| `refuse_with_disclosure` / `refuse` | **not invoked** | `{"status": "refused", "reason": "context_expired", "disclosure": …}` |
| `warn` | invoked | `{"status": "stale_warning", "disclosure": …, "result": <original>}` |
| `escalate_to_steward` | **not invoked** | refusal + `escalate_to` = `fallback`, or the `steward` if absent |
| *anything unknown* | **not invoked** | fails safe as `refuse_with_disclosure`, noting the unknown policy |

</details>

## Guarding an MCP tool

The guard stacks directly under `@mcp.tool()` — `functools.wraps` keeps the signature intact for the SDK's introspection ([src/cvd/mcp_server.py](src/cvd/mcp_server.py)):

```python
from mcp.server.fastmcp import FastMCP
from cvd import cvd_guard

mcp = FastMCP("pricing-context-server")

@mcp.tool()
@cvd_guard(PRICING_CV)
def get_enterprise_price(sku: str) -> dict:
    ...
```

```bash
pip install -e ".[mcp]"
python -m cvd.mcp_server
```

The bundled manifest is deliberately past its window, so the tool refuses out of the box. Update `last_validated` to a recent timestamp and it answers normally.

## See it fail loud

`python demo.py` — deterministic, no third-party packages. It evaluates the same manifest at two explicit instants:

```text
=== 2. STALE — past the 7-day window (age: 14 days) ===
freshness      : STALE
allowed        : False
action         : refuse
tool result    : {
  "status": "refused",
  "reason": "context_expired",
  "disclosure": "This context derives from SAP ERP price list PL-2026-Q3 and has
    exceeded its freshness SLO of P7D: it was last validated 14 days ago, on
    2026-07-15T09:00:00Z. The accountable steward is Ana Souza (Head of Pricing
    Operations); contact them to revalidate."
}
```

<details>
<summary><strong>Full demo output</strong></summary>

```text
CVD demo — fail loud, not fail confident
manifest: examples/pricing_context.json

=== 1. FRESH — within the 7-day window (age: 2 days) ===
now            : 2026-07-17T09:00:00+00:00
last_validated : 2026-07-15T09:00:00Z
freshness_slo  : P7D
freshness      : FRESH
allowed        : True
action         : answer
tool result    : {
  "sku": "WIDGET-PRO",
  "unit_price_usd": 4800.0,
  "currency": "USD"
}

=== 2. STALE — past the 7-day window (age: 14 days) ===
now            : 2026-07-29T09:00:00+00:00
last_validated : 2026-07-15T09:00:00Z
freshness_slo  : P7D
freshness      : STALE
allowed        : False
action         : refuse
tool result    : {
  "status": "refused",
  "reason": "context_expired",
  "disclosure": "This context derives from SAP ERP price list PL-2026-Q3 and has exceeded its freshness SLO of P7D: it was last validated 14 days ago, on 2026-07-15T09:00:00Z. The accountable steward is Ana Souza (Head of Pricing Operations); contact them to revalidate.",
  "escalate_to": null
}
```

</details>

Run the tests with `pytest` — 34 cases covering the freshness boundary, all four decay policies, both parsers, and the guard's no-call guarantee.

## Why refuse instead of answer?

**Because a visible refusal is a recoverable operational event; a confident wrong answer is an invisible one.**

An agent whose context has exceeded its freshness window must refuse to answer with confidence — returning a disclosure that names the accountable steward — rather than answer incorrectly with confidence. The refusal is not a failure of the system. It **is** the system working: obsolescence, which used to be silent, now produces a signal, an owner, and a path back to validity.

<details>
<summary><strong>Design notes — deliberate choices</strong></summary>

- **`age == slo` counts as FRESH.** The SLO is a tolerance, not a fuse; the boundary belongs to the caller.
- **Unknown policies fail safe.** A typo in `decay_policy` must never silently become "answer anyway".
- **The steward is a person.** Disclosure that names `pricing-team@` names no one. Accountability needs a face.
- **Years/months rejected in SLOs.** `P1M` is 28–31 days depending on when you ask — an ambiguous freshness window is a contradiction in terms.
- **The clock is injectable** (`evaluate(cv, now=…)`, `cvd_guard(cv, now_fn=…)`), so every behavior in this README is reproducible in a test.

</details>

## Citation

```
Lacerda, K. (2026). The Agent Steward: Context Provenance and Temporal Validity
as a Missing Governance Dimension in Multi-Agent Enterprise Ecosystems.
Zenodo. https://doi.org/10.5281/zenodo.21660205
```

---

<p align="center">MIT © 2026 Kefreen Lacerda</p>

Maintenance

ActivityInactive
ResponsivenessNo issues