# SMCP — Wire Specification v0.1

> **SMCP** — *Service / Sub-agent / Secure Model-Context Protocol.* The
> MCP-compatible **governance layer** for AI agents: a versioned, implementable
> wire spec for how agents declare capabilities, negotiate consent, exchange the
> minimum necessary data, and settle payment — over MCP and A2A, not instead of
> them.
>
> **Status:** v0.1 — DRAFT / pre-1.0. While `< 1.0`, any `MINOR` bump may carry
> breaking shape changes; this document is the source of truth for each version.
> **Canonical strategy:** [`dev-platform-smcp.md`](./dev-platform-smcp.md) (§3 maps
> every SMCP concern to the machinery it reuses).
> **Reference implementation:** this repository (DiviDen). All shapes below are the
> exact ones emitted by the endpoints named.

---

## 0. Why SMCP exists

MCP standardized *how a model calls a tool*. A2A standardized *how two agents talk*.
Neither standardizes the **governance** an agent platform needs the moment money,
private user data, and third-party code enter the loop:

- *What may this agent do, and can it prove it hasn't changed?* (attestation)
- *Exactly which fields of my data does it get, and for how long?* (consent + minimization)
- *On what terms — price, hold, review window — does a task execute?* (payment-as-contract)
- *What did it actually do with my data?* (auditability)
- *How much autonomy has it earned, and who can see/relax that?* (trust)

SMCP is **wire-compatible with MCP and A2A** — an SMCP node also serves an MCP
server card and an A2A agent card — and adds the governance envelope around them.
SMCP is designed to be implemented by other vendors; this spec is the contract.

---

## 1. Versioning & negotiation

### 1.1 Version string

`MAJOR.MINOR` (e.g. `0.1`). Pre-1.0, `MINOR` may break. A node advertises the full
set it can serve so a client picks the highest it understands.

### 1.2 How a client requests a version

Either transport (the **header wins** when both are present):

| Transport | Form |
|---|---|
| Request header | `SMCP-Version: 0.1` |
| Query param | `?smcp=0.1` or `?v=0.1` |

A requested value MAY carry a `v` or `smcp/` prefix (`v0.1`, `smcp/0.1`); it is
normalized away. An empty/garbage value is treated as "no request".

### 1.3 How a node responds (negotiation is LENIENT)

Discovery MUST NOT hard-fail on an unknown version.

- **No request** → node responds with its **preferred** (latest) version.
- **Supported request** → node responds with that version.
- **Unsupported request** → node responds with its preferred version **and** flags
  the mismatch (`versionNegotiation.ok = false`). The client decides whether to
  downgrade, warn, or proceed.

Every negotiated response:
- echoes `SMCP-Version: <resolved>` in the response headers,
- sets `Vary: SMCP-Version`,
- carries a machine-readable `versionNegotiation` block (see §2).

Reference negotiation logic: [`src/lib/smcp/version.ts`](../src/lib/smcp/version.ts)
(`negotiateSmcpVersion`, `pickRequestedVersion`).

---

## 2. Node discovery — `GET /.well-known/dividen-agent.json`

The version-negotiation anchor and entry point. Public, **CORS-open**
(`Access-Control-Allow-Origin: *`), cacheable (`max-age=300`). Independent of any
product feature flag — the protocol is discoverable the moment it ships.

> This path is shared, by design, with the **agent-host attestation document**
> (§3.4): it is the canonical "DiviDen agent manifest" any node serves. A
> **platform** node serves a *superset discovery* variant (below). An **agent
> host** serves the *signed attestation* variant. They are distinguished by their
> fields, not their path.

```jsonc
{
  "smcp": "0.1",                       // negotiated version
  "smcpVersions": ["0.1"],             // everything this node can serve (newest last)
  "smcpMinVersion": "0.1",
  "smcpPreferredVersion": "0.1",

  "node": { "name": "DiviDen", "url": "https://dividen.ai", "kind": "platform", "description": "…" },

  "discovery": {
    "spec": "https://smcp.denominator.ventures/spec",
    "agentCard": "https://dividen.ai/.well-known/agent-card.json",
    "mcpServerCard": "https://dividen.ai/.well-known/mcp/server-card.json",
    "oauthProtectedResource": "https://dividen.ai/.well-known/oauth-protected-resource",
    "agentManifest": "https://dividen.ai/api/smcp/agents/{slug}/manifest"   // {slug} template
  },

  "protocols": {
    "mcp": { "supported": true, "transport": "streamable-http", "endpoint": "…/api/mcp", "serverCard": "…" },
    "a2a": { "supported": true, "endpoint": "…/api/a2a", "agentCard": "…" }
  },

  "governance": {                      // §5 — named, enforced promises
    "returnGuard": "deny-by-default, fail-closed moderation on every agent return",
    "secretFreeResolver": "credential resolution never returns key material",
    "relaxOnlyTrust": "earned-autonomy gate only ever relaxes; nothing silent by default",
    "signedAttestation": "ed25519-signed, fingerprinted capability manifests with drift detection",
    "consent": "execution requires explicit approval for non-owner runs (coarse whole-run consent active; field-level ConsentGrant projection implemented, gated behind SMCP_CONSENT_ENFORCE_DISPATCH)"
  },

  "authentication": { "schemes": ["bearer"], "description": "…" },

  "versionNegotiation": {
    "requested": "0.1" | null,
    "resolved": "0.1",
    "supported": ["0.1"],
    "min": "0.1",
    "ok": true,
    "header": "smcp-version",
    "query": ["smcp", "v"]
  }
}
```

Reference: [`src/app/.well-known/dividen-agent.json/route.ts`](../src/app/.well-known/dividen-agent.json/route.ts).

### 2.1 Optional set-shaped `agents[]` section (NON-AUTHORITATIVE — verb-ladder S4)

A node MAY add an **OPTIONAL, additive** top-level `agents[]` array enumerating the
agents it advertises, gated by an **INDEPENDENT** `agentsVersion` integer. It rides under
the same negotiated sMCP `0.1` wire — `smcpVersions` is **unchanged** — and existing
consumers ignore both keys. Absence is normal; this section is a discovery convenience,
**not** a conformance requirement.

```jsonc
{
  "smcp": "0.1",
  // …all §2 node-discovery keys unchanged…
  "agents": [
    {
      "slug": "invoice-parser",              // SLUG_RE; required
      "name": "Invoice Parser",              // required
      "endpoint": "https://host/agents/invoice-parser/execute",  // public http(s); required
      "description": "…",                    // optional
      "type": "webhook",                     // optional: webhook | mcp
      "inputFormat": "json",                 // optional: text | json | a2a | mcp
      "category": "documents",               // optional
      "pricePerTask": 0                      // optional; 0 / absent = free
    }
  ],
  "agentsVersion": 1
}
```

Rules a consumer applies (see `parseDiscoveryDoc` in
[`cli/connect/discovery.ts`](../src/packages/smcp/cli/connect/discovery.ts)): auth is
**never** carried in the document; entries with an invalid slug, missing name,
non-http(s) endpoint, or an endpoint resolving to a **private/internal** address are
DROPPED to a skip ledger; duplicate slugs keep the first; the array is capped (~50) with a
`truncated` notice. The platform serves this section built from its publicly-listed agents,
exposing the **public per-agent manifest URL** as `endpoint` (never a raw provider endpoint).
`divi connect --from-discovery <url>` consumes it to bulk-**observe** a host's agents
locally (no account, no `/register`, no badge). A breaking change to the `agents` shape
bumps `agentsVersion` — the wire shape itself is RC-ratified and change-controlled.

Shape reference: [`src/lib/smcp/agents-discovery.ts`](../src/lib/smcp/agents-discovery.ts).

---

## 3. The four wire shapes

### 3.1 Conventions

- Hashes are `sha256:<64 lower-case hex>` over the **canonical JSON** of the value
  (keys sorted lexicographically at every level, `undefined` dropped, `null` kept,
  no whitespace). Reference: `canonicalJson` in
  [`agent-host-attestation.ts`](../src/lib/agent-host-attestation.ts).
- All discovery/manifest shapes are **secret-free by construction**: a node never
  serializes auth tokens, the agent's raw dispatch endpoint, payout internals, or
  health URLs into any SMCP wire shape. Buyers execute **through** the platform's
  endpoints, never the developer's webhook.

### 3.2 Agent capability manifest — `GET /api/smcp/agents/{slug}/manifest`

The agent's capability **declaration** + its **catalog of task contracts** + the
**negotiation** half (what it requests). Public, CORS-open, version-negotiated. A
non-public agent (draft / private / suspended / paused) returns **404,
indistinguishable from missing**.

```jsonc
{
  "smcp": "0.1",
  "agent": {
    "id": "…", "slug": "company-research", "name": "Company Research",
    "description": "…", "longDescription": null,
    "category": "research", "version": "1.0.0", "qualityTier": "gold"
  },
  "declares": {
    "formats": ["a2a", "json"],            // deduped, sorted; derived from supportsA2A/MCP + inputFormat
    "supportsA2A": true,
    "supportsMCP": false,
    "inlinePurchaseEnabled": true,
    "declaredWidgetTypes": ["checkout_card", "data_table"],   // sorted
    "inputSchemaHash": "sha256:…",         // null when none declared
    "outputSchemaHash": "sha256:…",
    "tasks": [ /* §3.3 task contracts */ ],
    "presentation": { "avatar": "leather-divi" }  // OPTIONAL + additive (Den asset A3): the
                                           // Den-catalog avatar the agent asks to be RENDERED as.
                                           // OMITTED when undeclared — an undeclared manifest is
                                           // byte-identical to pre-A3 (capabilityHash unchanged).
                                           // Declared at register via `presentation: { avatar }`,
                                           // validated against the CLOSED catalog at ingest AND
                                           // emit; consumers treat an unknown id as absent (default
                                           // body), never as an error. A Den operator's LOCAL
                                           // avatar assignment always overrides the declaration.
  },
  "requests": {                            // the negotiation half — what the agent asks FOR
    "dataScopes": [ { "scope": "board.card.read", "duration": "execution", "purpose": "task" } ],
    "credentials": [ /* { kind, provider, mode } — full wiring is roadmap Phase 3 */ ]
  },
  "execution": {
    "manifest":  "https://dividen.ai/api/smcp/agents/company-research/manifest",
    "publicPage":"https://dividen.ai/a/company-research",
    "a2a":       "https://dividen.ai/api/a2a",        // dispatch is PROXIED by the platform
    "mcp":       "https://dividen.ai/api/mcp"
  },
  "attestation": { "status": "verified" | null, "manifestHash": "sha256:…" | null },

  "capabilityHash": "sha256:…"             // stable hash over the PUBLIC declaration (NOT the private endpoint)
}
```

`capabilityHash` lets a client **pin / cache-validate** the declared capabilities.
It is **distinct** from the attestation `manifestHash` (§3.4), which binds the
agent's private dispatch endpoint and is signed by the host.

Reference: [`src/lib/smcp/manifest.ts`](../src/lib/smcp/manifest.ts),
[`src/app/api/smcp/agents/[slug]/manifest/route.ts`](../src/app/api/smcp/agents/[slug]/manifest/route.ts).

### 3.3 Task contract — *payment authorization as part of the execution contract*

One per purchasable task; appears inside `declares.tasks[]` and is pinned into the
execution ledger at quote time.

```jsonc
{
  "smcp": "0.1",
  "taskId": "…", "slug": "company-brief",
  "outcome": "A 2-page company brief",
  "inputSchema":  { /* declared JSON-Schema-ish */ },
  "outputSchema": { /* declared JSON-Schema-ish */ },
  "permissions": { "dataScopes": ["board.card.read", "calendar.event.title"] },
  "resultWidgets": ["data_table"],
  "pricing": { "model": "per_task", "amountCents": 2500, "currency": "USD" },
                  // model ∈ free | per_task | subscription | dynamic
                  // free omits amount/currency; dynamic ⇒ two-phase price_quote at runtime
  "sla": { "expectedRuntimeMs": 120000 | null },
  "settlement": { "hold": true, "reviewWindowHours": 48, "callbackSigned": true }
}
```

`settlement` states **platform invariants** (held PaymentIntent, 48h review window,
HMAC-signed callback), not per-task config.

### 3.4 Capability attestation — `GET https://<agent-host>/.well-known/dividen-agent.json`

Served by the **agent host** (not the platform). The host signs its OWN manifest
fingerprint; the platform verifies against the public key it registered (TOFU at
register, co-signed at review). The platform NEVER holds the private key.

```jsonc
{
  "manifestHash": "sha256:…",   // SHA-256 over the canonical, secret-free manifest (endpoint + widgets + schema hashes + capability flags)
  "keyId": "…" | null,
  "alg": "ed25519",
  "signature": "<base64 ed25519 over the manifestHash ASCII bytes>"
}
```

A `MINOR` of SMCP MAY extend this with `smcp`, `declares`, and `requests` blocks
(mirroring §3.2) while keeping `manifestHash` + `signature` for backward compat.
Reference + canonicalization rule:
[`agent-host-attestation.ts`](../src/lib/agent-host-attestation.ts).

---

## 4. Governance model (enforced today; some enforcement flag-gated/phased)

### 4.1 Identity

Bearer tokens (`dvd_` / `dvdn_`). Agents may hold a **scoped, revocable JWT**
(`AgentScopes`: board/threads/knowledge read, card create, dispatch, config-post),
epoch-revocable per owner. SMCP does not require OAuth (see
`/.well-known/oauth-protected-resource`).

### 4.2 Consent & minimal-field exchange

- A **non-owner run requires explicit approval** today (coarse, whole-run consent) —
  this is the default-active behavior.
- SMCP's durable, field-level, time-boxed, revocable grant — **`ConsentGrant`**
  (subject, agent, scopes, fields, purpose, `expiresAt`, `revokedAt`, audit ref) —
  is **specified here and IMPLEMENTED**, but **gated behind the default-off
  `SMCP_CONSENT_ENFORCE_DISPATCH`** flag (on top of `SMCP_CONSENT_V1`). With the
  flag off — the shipped default — coarse whole-run consent is what runs. With it
  on, the pure engine (`evaluateConsentGrant` deny-before-egress + the
  `projectToGrantedScopes` minimal-field redact in
  [`src/lib/smcp/consent.ts`](../src/lib/smcp/consent.ts)) projects the runtime
  payload an agent receives **down to the granted scopes** before dispatch, so an
  agent cannot receive a field it wasn't granted. The single egress choke point is
  `enforceConsentDispatch`; both paid run paths share it.
- `requests.dataScopes[]` (§3.2) is the agent's declared ask; a `ConsentGrant` is
  the human's durable answer. The two are intersected with the JWT's coarse scopes
  at every data-access boundary.

### 4.3 Trust (earned autonomy)

A per-`user × domain` time-decayed score advances `observe → suggest → ask-once →
auto`. The gate is **relax-only** — it never tightens silently, and an operator can
see/cap it. Reference: [`trust-engine.ts`](../src/lib/trust-engine.ts).

### 4.4 Safety (agent returns)

Every value an agent returns passes a **deny-by-default, fail-closed** moderation
guard (deterministic threat assessment + optional LLM judge). Reference:
[`agent-return-guard.ts`](../src/lib/agent-return-guard.ts).

### 4.5 Auditability

Per-execution ledger + audit log with privacy-preserving anonymization. A unified,
queryable SMCP audit API + a customer-facing "what did this agent do with my data"
view is **specified** and **delivered in a later phase** (roadmap Phase 3).

### 4.6 Settlement & spend invariants (enforced)

Two payment-side guarantees that govern money rather than the data/return envelope,
hence stated here under the governance model (not in §5's discovery list):

- **Settlement invariant.** Every paid task carries the same `settlement` block in
  its task contract (§3.3): a **held PaymentIntent**, a **48-hour review window**,
  and an **HMAC-signed callback**. These are stated as platform constants (`hold:
  true, reviewWindowHours: 48, callbackSigned: true`), not per-task config — they
  mirror the live `MarketplaceExecution` lifecycle (Stripe hold/capture, `reviewStatus`,
  per-execution `callbackSecret`). Reference:
  [`src/lib/smcp/manifest.ts`](../src/lib/smcp/manifest.ts) (`buildSmcpTaskContract`).
- **Spend-budget invariant (agent-as-buyer).** When an agent spends money on a
  principal's behalf, the authority is a **bounded, revocable `AgentSpendBudget`**
  (total cap, optional per-task cap, optional task-type allowlist, currency, expiry,
  `revokedAt`) and the authorization engine is **deny-by-default**: it denies on any
  ambiguity (missing/invalid budget, malformed charge, revocation, expiry,
  out-of-scope task type, over per-task cap, over total cap), reasons in integer
  cents only, and accumulates settled spend idempotently. The pure engine is shipped;
  the IO/fund-movement layer is **gated behind the default-off `A2A_MONEY_V1`**.
  Reference: [`src/lib/smcp/agent-spend.ts`](../src/lib/smcp/agent-spend.ts)
  (`evaluateAgentSpend`, `accumulateSpend`).

---

## 5. Named guarantees (the marketed SMCP promises)

These are the **unconditionally-enforced** promises surfaced in the node discovery
`governance` block (§2) — they hold in every flag state, on every run:

1. **Deny-by-default return guard** — fail-closed moderation on every agent return.
2. **Secret-free resolver boundary** — credential resolution returns status/metadata, never key material.
3. **Relax-only trust gate** — earned autonomy only ever relaxes; nothing silent by default.
4. **Signed, fingerprinted attestation** — ed25519-signed capability manifests with drift detection.

> Scope note: this list enumerates exactly the four discovery-block guarantees that
> are enforced unconditionally. The discovery block's fifth key, `consent`, is the
> coarse-whole-run promise plus the flag-gated field-level projection (§4.2). The
> settlement and spend-budget invariants are stated, and cited to their engines, in
> §4.6 — they govern payment terms and delegated spend rather than the data/return
> envelope, so they live under the governance model, not in this discovery list.

---

## 6. Conformance

An implementation is **SMCP v0.1 node-conformant** if it:

1. Serves `GET /.well-known/dividen-agent.json` with the §2 shape, CORS-open, and
   honors §1 version negotiation (header + query, lenient, echoes `SMCP-Version`,
   `Vary`s on it).
2. Serves `GET /api/smcp/agents/{slug}/manifest` with the §3.2 shape for every
   publicly-listed agent, 404-indistinguishable for non-public agents, secret-free.
3. Emits task contracts (§3.3) with a `pricing` model from the enumerated set and
   the `settlement` invariants.
4. Advertises MCP + A2A compatibility truthfully in `protocols`.
5. Never serializes secrets (auth tokens, raw endpoints, payout internals) into any
   SMCP wire shape.

An **agent host** is **attestation-conformant** if it serves §3.4 with a valid
ed25519 signature over its canonical manifest fingerprint.

Consent (`ConsentGrant`) and minimal-field projection are **v0.1-specified and
IMPLEMENTED but flag-gated** (DiviDen ships them behind the default-off
`SMCP_CONSENT_ENFORCE_DISPATCH`; §4.2). The unified audit API remains
**v0.1-specified, later-phase-delivered** (§4.5). An implementation MAY advertise
coarse whole-run consent in the interim (as DiviDen does by default) without losing
node conformance.

---

## 7. Change log

- **v0.1.2** (2026-06-30) — additive, OPTIONAL `agents[]` node-discovery section
  (verb-ladder S4; §2.1), gated by an INDEPENDENT `agentsVersion`. No sMCP wire-version
  change (`smcpVersions` stays `["0.1"]`); existing consumers ignore both keys. Consumed by
  `divi connect --from-discovery <url>` for bulk local observe.
- **v0.1.1** (2026-06-27) — doc reconciliation with shipped behavior (no wire-shape
  change). Reflects the following merges:
  - **Consent enforcement & minimal-field projection** — moved from "roadmap Phase
    3" to **implemented, flag-gated** behind the default-off
    `SMCP_CONSENT_ENFORCE_DISPATCH` (on top of `SMCP_CONSENT_V1`); coarse whole-run
    consent stays the shipped default (§2 governance / §4.2 / §6). The deny-before-egress
    and `projectToGrantedScopes` minimal-field redact share one choke point,
    `enforceConsentDispatch` (`src/lib/smcp/consent.ts`).
  - **Settlement invariant (#590)** — `settlement` block documented as enforced
    platform constants (held PaymentIntent / 48h review window / HMAC-signed callback)
    mirroring the `MarketplaceExecution` lifecycle; own-distribution public-page runs
    settle 100% fee-free to the developer behind `OWN_DISTRIBUTION_SETTLEMENT_V1`. New §4.6.
  - **Spend-budget invariant (#591 / L15)** — bounded, revocable `AgentSpendBudget`
    with a deny-by-default, integer-cents authorization engine
    (`src/lib/smcp/agent-spend.ts`), IO gated behind default-off `A2A_MONEY_V1`. New §4.6.
  - **Attestation honesty gate (#589)** — `attestation.status` now distinguishes
    trust-on-first-use (`attested-tofu`) from a real ed25519 `signatureVerified` grade;
    the "signed…verified and drift-free" promise is reserved for genuine signature
    verification, so the badge can no longer overstate assurance off admin approval alone.
  - **Agent-card visibility gate (#586)** — the public, CORS-open
    `/.well-known/agent-card.json` now applies the shared registry visibility gate
    (`certifiedAgentWhere()` + `isCertifiedAgentVisible()`), closing the owner-bypass
    leak of installed private/draft agent metadata, consistent with §3.2's
    404-indistinguishable rule for non-public agents.
- **v0.1** (2026-06-26) — initial draft. Node discovery + version negotiation,
  per-agent capability manifest, task contract, attestation doc shape, governance
  model (identity/consent/trust/safety/audit), named guarantees, conformance.
  Consent enforcement, minimal-field projection, and the unified audit API are
  specified but phased.

---

*This spec is versioned wire contract, not marketing. Where it says a shape is
emitted, the cited endpoint emits exactly that shape.*
