diff --git a/adr/0002-defer-entry-dependencies.md b/adr/0002-defer-entry-dependencies.md index 64c325c..d31b164 100644 --- a/adr/0002-defer-entry-dependencies.md +++ b/adr/0002-defer-entry-dependencies.md @@ -1,6 +1,9 @@ # ADR-0002: Defer Dependency Expression Between Entries -**Status:** Accepted +**Status:** Accepted (this decision — no dependency field in the *core* +schema — still stands; the deferred capability was later delivered as an +official extension by [ADR-0021](0021-dependencies-extension.md), which +resolves the AND/OR concern raised below) **Date:** 2026-04-02 @@ -66,3 +69,9 @@ semantics. - A mechanism for OR relationships (possibly via nested catalogs or a `relationship` annotation) - Alignment with how OCI expresses dependencies (referrers/layers) + +**Update:** [ADR-0021](0021-dependencies-extension.md) delivers this +capability as the official `https://ai-catalog.org/extensions/dependencies` +extension rather than a core field, so the core schema stays as decided here. +It resolves the AND/OR question by placing AND on a `required` array and OR on +an `anyOf` group (kept distinct from nested-catalog protocol alternatives). diff --git a/adr/0021-dependencies-extension.md b/adr/0021-dependencies-extension.md new file mode 100644 index 0000000..ff860e9 --- /dev/null +++ b/adr/0021-dependencies-extension.md @@ -0,0 +1,172 @@ +# ADR-0021: Entry Dependencies as an Official Extension + +## Status +Proposed + +## Date +2026-08-05 (Proposed) + +## Context +[Issue #63](https://github.com/Agent-Card/ai-catalog/issues/63) (originally +[ards-project/ard-spec#42](https://github.com/ards-project/ard-spec/issues/42)) +asks the catalog to describe an artifact's **downstream dependencies**: the +other agents, MCP servers, or datasets it needs to function, and — critically — +the credentials those dependencies require. The motivating use cases are +pre-flight ("can I use this agent — do I hold the credentials its dependencies +need?"), security and compliance planning (user-attributed vs. agent-attributed +access), discovery filtering ("hide agents requiring credentials I don't have"), +and impact analysis ("what breaks if I decommission MCP server X?"). + +[ADR-0002](0002-defer-entry-dependencies.md) previously **deferred** a +`dependencies` field. It did so for four reasons: (1) the AND-vs-OR ambiguity +(required co-requisites are AND; multi-protocol alternatives are OR); (2) a +nested catalog cannot say whether its members are alternatives or co-requisites; +(3) dependency semantics may belong to the downstream artifact formats; and (4) +scope-creep risk — the catalog should stay a simple index. ADR-0002 explicitly +left the door open: "implementation experience should drive whether and how +dependencies are added." + +Two facts have changed the calculus since then: + +- The specification now has a first-class, well-defined **extension mechanism** + ([ADR-0012](0012-extensibility-via-metadata.md)): a closed core schema plus an + `extensions` map keyed by reverse-DNS or URL, with an "Official Extensions" + tier under the `https://ai-catalog.org/extensions/` prefix (currently just + Metadata). This gives us a sanctioned home for capabilities that not every + catalog needs, *without* enlarging the core. +- The AND/OR modeling problem — ADR-0002's central blocker — has a clean + resolution once the two relationships are placed on separate structural axes + (see Decision). + +The original proposal predates several current conventions and must be +reconciled: it used `urn:ai:` (now `urn:air:{publisher}:{namespace}:{name}`, +[ADR-0015](0015-agent-identifier-naming.md)), `application/mcp-server+json` +(now `application/mcp-server-card+json`), `minVersion` (a lower bound only), +and `identityType` (which already means something else on Publisher and Trust +Manifest — an identifier *scheme* hint like `did`/`dns`/`spiffe`). + +## Decision +Implement [issue #63](https://github.com/Agent-Card/ai-catalog/issues/63) as an **official extension**, not as a core Catalog Entry +field. Register `https://ai-catalog.org/extensions/dependencies` in the +specification's Official Extensions section. This keeps the core schema +minimal and dependency-free — honoring ADR-0002's decision rather than +reversing it — while delivering the capability through the extension point +ADR-0012 established for exactly this purpose. + +**No core change, no version bump.** Because the capability rides on the +existing `extensions` map and unrecognized extensions MUST be ignored, the +change is purely additive. A catalog carrying the extension remains a +conformant `specVersion` "1.0" document; no core field, CDDL rule, data-model +diagram entry, or conformance level changes. + +**Shape.** The extension value has two OPTIONAL arrays, `required` and +`optional`. Each element is a *Dependency Requirement* that is EITHER a single +Dependency object (the flat, common case) OR a Dependency Group carrying an +`anyOf` array of interchangeable alternatives. A Dependency has a REQUIRED +`identifier` (a `urn:air` reference to another artifact) plus OPTIONAL `type`, +`versionConstraint`, `credentialPropagation`, and `purpose`. + +**AND/OR resolution (ADR-0002 concerns 1 & 2).** The two relationships live on +two independent axes so neither field is overloaded: + +- **AND** is the outer `required` array — every element is a co-requisite. +- **OR** is the inner `anyOf` group — any one alternative satisfies that one + slot (e.g. "a vector store: Pinecone *or* pgvector"). + +This is kept deliberately distinct from the nested-catalog dual-protocol +pattern ([ADR-0004](0004-eliminate-bundle-concept.md)): a nested catalog is +**one** logical artifact with multiple protocol faces; `anyOf` is **different** +artifacts that interchangeably fill a capability slot. Routing OR through +nested catalogs was rejected because it recreates ADR-0002's ambiguity. + +**Downstream ownership & scope (ADR-0002 concerns 3 & 4).** The extension +declares *discovery edges* only — references by `identifier`, never embedded +artifacts, and no mandated transitive resolution. The depended-on artifact +still owns its own internal requirements; the catalog does not become a package +manager or resolver. This preserves the thin-pointer posture +([ADR-0013](0013-authoring-vs-distribution-formats.md)) and loose coupling +([ADR-0017](0017-ard-loose-coupling.md)). + +**Field reconciliations.** + +- `versionConstraint` (not `minVersion`, not `version`): a SemVer *range* + matcher. `minVersion` is only a floor and cannot express an upper bound + ("needs v3 but v4 is breaking"), which is exactly the pre-flight + compatibility question. Reusing `version` would mislead readers into + treating a range as a concrete literal (and `version` is the multi-version + uniqueness key). +- `credentialPropagation` (not `identityType`): a distinct field to avoid a + name collision — `identityType` already means an identifier *scheme* hint + (`did`/`dns`/`spiffe`) on Publisher and Trust Manifest, whereas this concept + is credential/audit attribution. Open text with a RECOMMENDED value set + (`obo`, `agent`, `user`, `none`, lowercase to match existing `identityType` + casing), matching how the spec treats every other type-ish field. Marked + **advisory**, never an authoritative security control. + +## Rationale +- **Honors ADR-0002 instead of reversing it.** ADR-0002's goal was a simple + core; an extension keeps the core simple while shipping the capability. +- **Uses the mechanism designed for this.** ADR-0012 created Official + Extensions precisely so non-universal capabilities need not enter the core. +- **Resolves the real blocker.** The AND/OR ambiguity that stalled ADR-0002 is + removed by separating the axes (`required` = AND, `anyOf` = OR). +- **Zero-cost to non-adopters.** Additive, ignorable, no version bump; a + planner that understands the extension gains pre-flight/impact-analysis + without imposing anything on catalogs that don't. +- **Cross-vendor governance.** Standardizing the shape (rather than leaving it + to per-vendor `extensions` keys) is what makes cross-platform pre-flight and + impact analysis interoperable — the concern raised at the end of + [issue #63](https://github.com/Agent-Card/ai-catalog/issues/63). + +## Consequences +- The spec's Official Extensions section gains a **Dependencies Extension** + subsection (structure, Dependency Requirement, Dependency object, resolution + semantics, example, and a CDDL block for the extension value). A new Security + Considerations subsection ("Dependency Confusion and Malicious Dependencies") + and a note under Catalog Poisoning cover reference substitution, advisory + hints not being controls, cycle/depth bounds, and topology leakage. +- No change to the core CatalogEntry CDDL, the Data Model diagram, the + conformance levels, or `specVersion`. +- ADR-0002 is annotated with a forward reference to this ADR but **remains + Accepted**: its decision (no *core* dependency field) still stands. +- The MCP and Claude-Plugins mapping appendices gain a row noting dependencies + as catalog-layer extension metadata the native formats do not carry. +- Docs gain an authoring subsection, a consumer pre-flight-check section, a + dedicated example page, and nav/feature entries. + +## Alternatives Considered +- **A core `dependencies` field on Catalog Entry.** Rejected: it re-opens + ADR-0002's "keep the core simple" decision and forces the capability on every + catalog reader. The extension delivers the same shape without enlarging the + core. (If adoption becomes near-universal, ADR-0012 already anticipates + promoting an official extension to a core field in a future version.) +- **Keeping the proposal's `minVersion`.** Rejected: a lower bound cannot + express an upper bound, the key compatibility question for pre-flight. +- **Reusing `identityType` for credential propagation.** Rejected: name + collision with the existing identifier-scheme meaning. +- **Expressing OR via nested catalogs.** Rejected: recreates the exact + AND/OR ambiguity ADR-0002 flagged; explicit `anyOf` removes it. +- **Always-array `anyOf` (every requirement is a group).** Rejected as verbose + for the common single-dependency case; the discriminated + single-Dependency-or-group union keeps the flat case flat. +- **A closed MUST-enum for `credentialPropagation`.** Rejected for now: open + text with a RECOMMENDED set matches the spec's treatment of `type` and + `identityType` and leaves room for cases like multi-hop delegation. A future + ADR MAY tighten it to a registered enum once usage settles. + +## Open Questions +- Whether `obo`/`agent`/`user`/`none` are sufficient for real deployments + (multi-hop delegation, per-tool credential scoping) before this stabilizes. +- Whether the spec should go beyond SHOULD in RECOMMENDING that catalogs + relying on dependencies for pre-flight decisions carry a catalog-level + signature, given the extension sits outside the Trust Manifest's signed + subject. +- Whether OCI / xRegistry substrate bindings need to round-trip the extension + explicitly, or whether carrying it inline as opaque entry metadata suffices. + +## Meeting Reference +Records a proposal ahead of an AI Catalog bi-weekly working-group discussion, +implementing the deferred capability from [ADR-0002](0002-defer-entry-dependencies.md) +via the extension mechanism of [ADR-0012](0012-extensibility-via-metadata.md). +Update the Status and Date (and note who raised concerns and the agreed +outcome) once the working group ratifies it. diff --git a/docs/examples/agent-with-dependencies.md b/docs/examples/agent-with-dependencies.md new file mode 100644 index 0000000..9c92a17 --- /dev/null +++ b/docs/examples/agent-with-dependencies.md @@ -0,0 +1,101 @@ +# Example: Agent with Dependencies + +A catalog entry can declare the downstream artifacts it needs to run using the official [Dependencies extension](../specification.md). This catalog lists a single CRM assistant agent that requires a Salesforce MCP server, needs one of two interchangeable vector stores, and can optionally use an email-sending agent. + +```json +{ + "specVersion": "1.0", + "host": { + "displayName": "Acme Corp", + "identifier": "did:web:acme-corp.com" + }, + "entries": [ + { + "identifier": "urn:air:acme-corp.com:agent:crm-assistant", + "displayName": "CRM Assistant", + "type": "application/a2a-agent-card+json", + "description": "An agent that reads and writes CRM records and answers questions grounded in retrieved context.", + "url": "https://api.acme-corp.com/agents/crm-assistant.json", + "publisher": { + "identifier": "did:web:acme-corp.com", + "displayName": "Acme Corp" + }, + "extensions": { + "https://ai-catalog.org/extensions/dependencies": { + "required": [ + { + "identifier": "urn:air:salesforce.com:mcp:sales-cloud", + "type": "application/mcp-server-card+json", + "versionConstraint": ">=3.0.0", + "credentialPropagation": "obo", + "purpose": "Read and write Salesforce CRM records" + }, + { + "purpose": "Vector store for retrieval", + "anyOf": [ + { + "identifier": "urn:air:acme-corp.com:mcp:pinecone", + "type": "application/mcp-server-card+json", + "credentialPropagation": "agent" + }, + { + "identifier": "urn:air:acme-corp.com:mcp:pgvector", + "type": "application/mcp-server-card+json", + "credentialPropagation": "agent" + } + ] + } + ], + "optional": [ + { + "identifier": "urn:air:acme-corp.com:agent:email-sender", + "type": "application/a2a-agent-card+json", + "credentialPropagation": "agent", + "purpose": "Send email notifications" + } + ] + } + } + } + ] +} +``` + +## How it works + +**The extension key** (`https://ai-catalog.org/extensions/dependencies`) is an *official* AI Catalog extension — signalled by the `https://ai-catalog.org/extensions/` prefix, the same tier as the built-in `metadata` extension. It lives under the entry's `extensions` map, not as a core field. The core schema is unchanged, so the document stays a conformant `specVersion` `"1.0"` catalog; a consumer that doesn't recognize the extension simply ignores it. + +**`required` is an AND** — every element must be satisfiable for the agent to function. A consumer SHOULD treat an unmet `required` element as a hard blocker. Here the agent has two required slots: the Salesforce MCP server *and* a vector store. + +**`anyOf` is an OR** — a Dependency Group whose slot is filled by *any one* of its listed alternatives. The vector-store slot is satisfied by either `pinecone` *or* `pgvector`; the consumer only needs one. The group carries a single `purpose` ("Vector store for retrieval") stated once for all alternatives. + +**`optional` is graceful degradation** — each element is independently droppable. If the `email-sender` agent is unavailable, the CRM assistant still works; it just can't send email notifications. + +**`credentialPropagation`** is an advisory hint about how the agent authenticates to each dependency at runtime, letting a planner reason upfront about which credentials it (or its caller) must hold: + +- `obo` — *on behalf of*: the caller's user identity is propagated to the dependency, so the audit trail is user-attributed. The Salesforce dependency uses `obo` because it reads and writes records as the end user. +- `agent` — the agent uses its own workload identity, so activity is agent-attributed. The vector stores and email sender use `agent`. +- `user` — a distinct interactive user credential for the dependency is required (the caller authenticates separately). +- `none` — the dependency is public and needs no credential. + +This is an *advisory* hint only: it MUST NOT be treated as a security control. The depended-on artifact enforces its real credential requirements at connection time. It is also distinct from the `identityType` field on a Publisher or Trust Manifest, which hints at an identifier *scheme* (`did`, `dns`, `spiffe`) — a different, orthogonal axis. + +Each Dependency references another artifact by its `identifier` alone (never an embedded copy), and MAY point across publishers or catalogs — the Salesforce dependency lives under `salesforce.com`, not `acme-corp.com`. `type` and `versionConstraint` (a SemVer *range* matcher such as `>=3.0.0`, not a literal version) let a consumer reason about a dependency without dereferencing it. + +!!! note "Dependencies enable pre-flight checks" + Because these edges are declared as discovery metadata, a consumer can answer *"can I use this agent — do I hold the credentials its required dependencies need?"* **before** invoking it, and *"what breaks if I decommission artifact X?"* for impact analysis. See [Consuming Catalogs](../guides/consuming-catalogs.md) for how a client reads and resolves these edges, and [Creating a Catalog](../guides/creating-a-catalog.md) for declaring them on your own entries. Consumers MUST handle an unresolvable `required` dependency gracefully and MUST NOT auto-fetch, install, or invoke a resolved dependency without applying the same trust verification they would apply to any artifact. + +## When to use this pattern + +Use the Dependencies extension when: + +- An artifact relies on other artifacts to function — an agent that calls a downstream MCP server, a skill that requires a specific tool, or a plugin with a companion agent +- You want consumers to run pre-flight credential checks or filter discovery by the credentials they hold +- You need to express interchangeable alternatives (`anyOf`) for one capability slot, or degrade gracefully when an optional dependency is missing +- Governance or compliance tooling needs impact analysis and user- vs agent-attributed audit expectations + +## Related guides + +- [Dependencies Extension](../specification.md) — the canonical specification +- [Consuming Catalogs](../guides/consuming-catalogs.md) — resolving and verifying dependencies +- [Creating a Catalog](../guides/creating-a-catalog.md) — declaring dependencies on your entries diff --git a/docs/getting-started.md b/docs/getting-started.md index fe2b70a..4e76924 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -86,3 +86,4 @@ Serving the catalog at `/.well-known/ai-catalog.json` enables automated discover - [**Serving Your Catalog**](guides/serving-your-catalog.md) — hosting options and `Content-Type` configuration - [**Adding Trust**](guides/adding-trust.md) — reach Level 3 with verifiable identity and attestations - [**Organizing Catalogs**](guides/organizing-catalogs.md) — nest catalogs for large inventories +- [**Declaring Dependencies**](examples/agent-with-dependencies.md) — use the Dependencies extension to declare downstream artifacts and enable consumer [pre-flight credential checks](guides/consuming-catalogs.md#pre-flight-dependency-check) diff --git a/docs/guides/adding-trust.md b/docs/guides/adding-trust.md index 5c630cc..9c78f08 100644 --- a/docs/guides/adding-trust.md +++ b/docs/guides/adding-trust.md @@ -83,6 +83,15 @@ All other fields are optional: | `signature` | Detached JWS signature over the Trust Manifest content | | `metadata` | Open map for custom trust metadata | +!!! note "Three distinct identity concepts — don't conflate them" + AI Catalog uses "identity" in three different senses. Keep them separate: + + - **Publisher / Trust Manifest `identityType`** — a hint at the *scheme* of an identifier (`did`, `spiffe`, `dns`). It tells a client how to resolve and verify the `identity` URI. That is all it does. + - **`credentialPropagation`** (on the [dependencies extension](../specification.md)) — describes how an artifact authenticates *downstream* to the artifacts it depends on: `obo` (on-behalf-of, user identity propagated — a **user-attributed** audit trail), `agent` (the agent's own workload identity — an **agent-attributed** trail), `user` (a separate interactive user credential), or `none` (public). It is an advisory attribution hint, never a security control. + - **`provenance`** — artifact *lineage*: where the artifact was built or derived from (see [Adding provenance](#adding-provenance)). + + So `identityType` is about *who published this*, `credentialPropagation` is about *how it calls its dependencies*, and `provenance` is about *where it came from*. + !!! tip "Attestation document format" Attestation documents are not restricted to any particular format — they can be human-readable (e.g., a PDF audit report) or machine-readable for automated verification (e.g., JWTs, Verifiable Credentials). diff --git a/docs/guides/consuming-catalogs.md b/docs/guides/consuming-catalogs.md index 46cd35f..f3253e1 100644 --- a/docs/guides/consuming-catalogs.md +++ b/docs/guides/consuming-catalogs.md @@ -104,6 +104,159 @@ def resolve_artifact(entry): When fetching from `url`, the server should respond with the content type declared in the entry's `type` field. +## Pre-flight dependency check + +An entry may declare the other artifacts it relies on via the official +[Dependencies extension](../specification.md), keyed by +`https://ai-catalog.org/extensions/dependencies`. Because dependencies live +under `extensions`, they are additive: a catalog carrying them is still a +conformant `1.0` document, and clients that don't recognize the key simply +ignore it. + +Before you invoke or deploy an artifact, run a **pre-flight check**: for each +`required` dependency, confirm it is resolvable and that you hold (or can +acquire) the credentials it needs. `optional` dependencies never block use — +skip them or degrade gracefully when they can't be satisfied. + +An entry's dependencies value looks like this: + +```json +{ + "identifier": "urn:air:acme-corp.com:agent:crm-assistant", + "type": "application/a2a-agent-card+json", + "url": "https://api.acme-corp.com/agents/crm-assistant.json", + "extensions": { + "https://ai-catalog.org/extensions/dependencies": { + "required": [ + { + "identifier": "urn:air:salesforce.com:mcp:sales-cloud", + "type": "application/mcp-server-card+json", + "versionConstraint": ">=3.0.0", + "credentialPropagation": "obo", + "purpose": "Read and write Salesforce CRM records" + }, + { + "purpose": "Vector store for retrieval", + "anyOf": [ + { + "identifier": "urn:air:acme-corp.com:mcp:pinecone", + "type": "application/mcp-server-card+json", + "credentialPropagation": "agent" + }, + { + "identifier": "urn:air:acme-corp.com:mcp:pgvector", + "type": "application/mcp-server-card+json", + "credentialPropagation": "agent" + } + ] + } + ], + "optional": [ + { + "identifier": "urn:air:acme-corp.com:agent:email-sender", + "type": "application/a2a-agent-card+json", + "credentialPropagation": "agent", + "purpose": "Send email notifications" + } + ] + } + } +} +``` + +A requirement is either a single Dependency object (the flat, common case) or +a **Dependency Group** — an object with an `anyOf` array, where any one +alternative satisfies the slot. The following helpers, written in the same +style as `fetch_catalog` and `collect_all_entries`, resolve each dependency +against a catalog index and check credentials before use: + +```python +DEPENDENCIES_EXT = "https://ai-catalog.org/extensions/dependencies" + +def get_dependencies(entry): + """Read the dependencies extension off an entry, if present.""" + ext = entry.get("extensions", {}).get(DEPENDENCIES_EXT) + if not ext: + return [], [] + return ext.get("required", []), ext.get("optional", []) + +def satisfies(dep, index, held_credentials): + """A single Dependency is satisfiable if it resolves, the version + constraint matches, and we hold (or can acquire) any needed credential.""" + target = index.get(dep["identifier"]) + if target is None: + return False # unresolvable — not in any catalog we can reach + + constraint = dep.get("versionConstraint") + if constraint and not version_matches(target.get("version"), constraint): + return False # e.g. requires >=3.0.0 but only 2.9 is published + + # credentialPropagation is an advisory hint (see note below): "none" and + # "obo"/"agent" that we can mint need nothing extra; "user" needs an + # interactive credential we must already hold or be able to obtain. + prop = dep.get("credentialPropagation", "none") + if prop == "user" and dep["identifier"] not in held_credentials: + return False + return True + +def requirement_satisfied(req, index, held_credentials): + """A requirement is a single Dependency or an anyOf group; a group is + satisfied when at least one alternative is satisfiable.""" + if "anyOf" in req: + return any( + satisfies(alt, index, held_credentials) for alt in req["anyOf"] + ) + return satisfies(req, index, held_credentials) + +def preflight(entry, index, held_credentials): + """Return (ok, blockers, degraded). `ok` is False if any required + dependency is unsatisfiable; `degraded` lists droppable optionals.""" + required, optional = get_dependencies(entry) + + blockers = [ + req for req in required + if not requirement_satisfied(req, index, held_credentials) + ] + degraded = [ + req for req in optional + if not requirement_satisfied(req, index, held_credentials) + ] + return (not blockers, blockers, degraded) +``` + +Build `index` by mapping each `identifier` to its entry (for example, from +`collect_all_entries`), and supply `version_matches` from a SemVer range +library such as `packaging` or `semver`. `held_credentials` is whatever set +of credentials your client can present or mint. + +!!! warning "Never auto-fetch or install a dependency without trust verification" + A pre-flight check tells you *whether* a dependency is usable; it is not + permission to pull and run it. Resolve dependencies through catalogs you + trust and verify each artifact (see [Trust verification](#trust-verification)) + before invoking it. + +### Filtering discovery by available credentials + +The same check powers discovery filtering. When listing agents, hide the ones +you couldn't actually use — *"hide agents requiring credentials I don't +hold"* — by dropping any entry whose `preflight` reports a blocker: + +```python +def usable_entries(entries, index, held_credentials): + return [ + entry for entry in entries + if preflight(entry, index, held_credentials)[0] + ] +``` + +!!! note "`credentialPropagation` is advisory" + `credentialPropagation` is an upfront *hint* for planning, not a security + control. The downstream artifact enforces real credentials at connect + time — a `"none"` hint does not make a dependency public, and holding a + credential in your pre-flight set does not guarantee the dependency will + accept it. Use it to plan and filter, never to authorize. See + [Adding Trust](adding-trust.md) for how credentials are actually verified. + ## Handling nested catalogs An entry with `type: "application/ai-catalog+json"` is itself a catalog. To get all artifacts, recurse into it: @@ -174,6 +327,28 @@ def latest_entries(entries): ## TypeScript example ```typescript +const DEPENDENCIES_EXT = "https://ai-catalog.org/extensions/dependencies"; + +interface Dependency { + identifier: string; + type?: string; + versionConstraint?: string; + credentialPropagation?: "obo" | "agent" | "user" | "none" | string; + purpose?: string; +} + +interface DependencyGroup { + anyOf: Dependency[]; + purpose?: string; +} + +type Requirement = Dependency | DependencyGroup; + +interface Dependencies { + required?: Requirement[]; + optional?: Requirement[]; +} + interface CatalogEntry { identifier: string; type: string; @@ -184,6 +359,16 @@ interface CatalogEntry { updatedAt?: string; tags?: string[]; description?: string; + // Extensions are keyed by URI; dependencies live under DEPENDENCIES_EXT. + extensions?: Record; +} + +function isGroup(req: Requirement): req is DependencyGroup { + return "anyOf" in req; +} + +function readDependencies(entry: CatalogEntry): Dependencies | undefined { + return entry.extensions?.[DEPENDENCIES_EXT] as Dependencies | undefined; } interface AICatalog { @@ -220,6 +405,10 @@ Be defensive when consuming catalogs from unknown sources: - **Invalid JSON**: catch parse errors and treat the catalog as unavailable - **Circular references**: track visited URLs and enforce depth limits (see above) - **Untrusted `data` content**: treat inline `data` as untrusted input; don't execute content with script-capable media types +- **Unresolvable required dependency**: if a `required` dependency's `identifier` resolves to no reachable entry, treat the artifact as a hard blocker — don't invoke it (see [Pre-flight dependency check](#pre-flight-dependency-check)) +- **Unavailable credentials for a required dependency**: block use when you can neither present nor acquire a credential a `required` dependency needs; degrade gracefully for `optional` ones +- **`versionConstraint` mismatch**: if the resolved dependency's `version` falls outside the declared range (e.g., requires `>=3.0.0` but only `2.9` is published), treat the requirement as unmet +- **Dependency cycles**: dependencies may reference each other; track visited `identifier`s when walking the dependency graph so a cycle doesn't cause infinite recursion ## Trust verification diff --git a/docs/guides/creating-a-catalog.md b/docs/guides/creating-a-catalog.md index a505dbc..17f3e97 100644 --- a/docs/guides/creating-a-catalog.md +++ b/docs/guides/creating-a-catalog.md @@ -107,6 +107,7 @@ Enrich your entries with additional metadata: | `publisher` | object | Who publishes this artifact (see [Publisher object](#publisher-object)) | | `trustManifest` | object | Trust and identity metadata (see [Adding Trust](adding-trust.md)) | | `metadata` | object | Custom key/value pairs (see [Metadata extensibility](#metadata-extensibility)) | +| `extensions` | object | Named extension map. Includes the official `dependencies` extension (see [Declaring dependencies](#declaring-dependencies)) | ### When to set `displayName` @@ -203,6 +204,125 @@ A catalog may contain multiple entries with the same `identifier` but different When `version` is present, the combination of `identifier` + `version` must be unique. Clients looking for the latest version should sort by `version` (semver) or `updatedAt` and take the most recent. +## Declaring dependencies + +Artifacts rarely stand alone. An agent may need an MCP server for tool access, a +vector store for retrieval, or another agent to delegate work to. The official +**dependencies extension** lets an entry declare what it depends on so consumers +can run pre-flight checks ("can I use this agent — do I hold the required +credentials?"), filter discovery by available credentials, perform impact analysis +("what breaks if I decommission this server?"), and reason about compliance +(user- vs agent-attributed audit). + +Dependencies live under the entry's `extensions` map, keyed by the extension URI +`https://ai-catalog.org/extensions/dependencies`: + +!!! note "Official extension — no `specVersion` bump" + The `https://ai-catalog.org/extensions/` prefix marks this as an **official** + AI Catalog extension, the same tier as the [metadata](#metadata-extensibility) + extension. It is additive: the core schema is unchanged, `specVersion` stays + `"1.0"`, and consumers that don't recognize the extension simply ignore it. + See the [Dependencies Extension](../specification.md) + section of the specification for the normative contract. + +### Extension shape + +The extension value has two optional arrays: + +`required` +: **AND semantics.** Every element must be satisfiable for the artifact to + function. Unresolvable required dependencies are hard blockers. + +`optional` +: Each element is independently droppable — the artifact still works without it, + with graceful degradation. + +Each element of either array is a **Requirement**, which is *either*: + +- a single **Dependency** object (the flat, common case), *or* +- a **Dependency Group** — `{ "anyOf": [ , ... ], "purpose": "..." }` — + expressing **OR semantics**: any one alternative satisfies the slot. State + `purpose` once for the whole group. + +A **Dependency** object has these fields: + +| Field | Required? | Description | +|---|---|---| +| `identifier` | **required** | A `urn:air:{publisher}:{namespace}:{name}` reference to another artifact's entry identifier. May be cross-publisher or cross-catalog — dependencies may reference external domains. | +| `type` | optional, recommended | The media type from the entry `type` vocabulary, e.g. `application/mcp-server-card+json` or `application/a2a-agent-card+json` (note the `-card` suffix). | +| `versionConstraint` | optional | A **SemVer range** string, e.g. `">=3.0.0"`, `"^3.1"`, `"3.x"`. It is a matcher, not a literal — not `minVersion`, not `version`. Absent means any version. | +| `credentialPropagation` | optional | Advisory hint about how credentials flow to this dependency (see below). | +| `purpose` | optional | Free text explaining why the dependency exists, e.g. "Read and write Salesforce CRM records". | + +!!! warning "`credentialPropagation` is distinct from `identityType`" + `credentialPropagation` describes **how credentials flow at connect time** and + is open text with recommended lowercase values: `"obo"` (on-behalf-of — the + user's identity is propagated, producing user-attributed audit), `"agent"` + (the agent's own workload identity), `"user"` (a separate interactive user + credential is required), and `"none"` (public). It is **advisory only** — the + downstream artifact enforces real credentials at connect time, so never treat + it as a security control. + + Do **not** conflate it with [`identityType`](#publisher-object), which is a + completely different field: `identityType` is an identifier *scheme* hint + (`did`, `dns`, `spiffe`) on a Publisher or Trust Manifest, describing how to + resolve an identifier — not how credentials propagate. + +### Full example + +```json +{ + "identifier": "urn:air:acme-corp.com:agent:crm-assistant", + "type": "application/a2a-agent-card+json", + "url": "https://api.acme-corp.com/agents/crm-assistant.json", + "extensions": { + "https://ai-catalog.org/extensions/dependencies": { + "required": [ + { + "identifier": "urn:air:salesforce.com:mcp:sales-cloud", + "type": "application/mcp-server-card+json", + "versionConstraint": ">=3.0.0", + "credentialPropagation": "obo", + "purpose": "Read and write Salesforce CRM records" + }, + { + "purpose": "Vector store for retrieval", + "anyOf": [ + { + "identifier": "urn:air:acme-corp.com:mcp:pinecone", + "type": "application/mcp-server-card+json", + "credentialPropagation": "agent" + }, + { + "identifier": "urn:air:acme-corp.com:mcp:pgvector", + "type": "application/mcp-server-card+json", + "credentialPropagation": "agent" + } + ] + } + ], + "optional": [ + { + "identifier": "urn:air:acme-corp.com:agent:email-sender", + "type": "application/a2a-agent-card+json", + "credentialPropagation": "agent", + "purpose": "Send email notifications" + } + ] + } + } +} +``` + +Here the `crm-assistant` agent hard-requires the Salesforce MCP server (v3 or +later, called on behalf of the user) *and* one of two vector stores, while the +email sender is optional. Note the cross-publisher reference to +`urn:air:salesforce.com:...` — an external domain. + +!!! warning "Consumers must verify trust" + Consumers **must** handle unresolvable required dependencies gracefully and + **must not** auto-fetch or install a dependency without trust verification. + ## Publisher object The `publisher` field on an entry identifies who publishes that artifact: diff --git a/docs/guides/organizing-catalogs.md b/docs/guides/organizing-catalogs.md index bee90e2..4d9cec3 100644 --- a/docs/guides/organizing-catalogs.md +++ b/docs/guides/organizing-catalogs.md @@ -13,6 +13,15 @@ Use nested catalogs when: A flat catalog is simpler. Nest only when you have a clear organizational reason. +## Composition vs. dependency + +Nesting is **composition** — artifacts packaged *together* inside one catalog, either by reference (a nested catalog entry) or inline (the `data` field). Everything on this page is composition. + +A **dependency** is different: it's a reference — carrying requirements like a version range or credential-propagation hint — to a *separate*, possibly external artifact that must exist and be usable at runtime, but is not contained by this catalog. Dependencies are declared with the [`dependencies` extension](../specification.md) on an entry, not by nesting. + +!!! note + The depth limit and visited-URL tracking below govern the **composition tree** only. Dependencies form a separate **dependency graph** that can span publishers and catalogs, and it needs its own cycle detection independent of nesting depth — a chain of dependencies is not bounded by how deeply catalogs are nested. See [Creating a Catalog](creating-a-catalog.md#declaring-dependencies) for declaring dependencies and [Agent with dependencies](../examples/agent-with-dependencies.md) for a worked example. This page does not repeat the field spec. + ## Nested catalog entries An entry whose `type` is `application/ai-catalog+json` is a nested catalog reference. Clients discover it, fetch the URL, and process the result as another catalog: diff --git a/docs/guides/serving-your-catalog.md b/docs/guides/serving-your-catalog.md index 4b1be0f..a45f576 100644 --- a/docs/guides/serving-your-catalog.md +++ b/docs/guides/serving-your-catalog.md @@ -58,6 +58,8 @@ The mapping works as follows: | Nested catalog entry | Nested OCI Image Index | | Trust Manifest | OCI Referrer artifact attached to the entry manifest | +An entry's `dependencies` extension travels along as ordinary entry metadata within its manifest (`config`/`annotations`) — unlike the Trust Manifest, it is **not** modeled as an OCI Referrer. + The logical AI Catalog format remains the authoring and consumption interface — tooling handles the OCI pack/unpack: ``` diff --git a/docs/index.md b/docs/index.md index 87ac122..0fa0b18 100644 --- a/docs/index.md +++ b/docs/index.md @@ -70,6 +70,10 @@ Serve it at `/.well-known/ai-catalog.json` and any AI client can discover everyt Sub-catalogs can be authored, hosted, and updated independently by different teams or organizations. +- **Dependency-aware discovery** + + The optional Dependencies extension lets an entry declare its downstream artifacts — powering pre-flight credential checks ("do I hold what this agent needs?") and impact analysis, all via an official extension that leaves the core schema untouched. + --- diff --git a/docs/specification.md b/docs/specification.md index 856506b..d10b993 100644 --- a/docs/specification.md +++ b/docs/specification.md @@ -10,6 +10,7 @@ The specification covers: - **Core schema** — formal definitions for AI Catalog, Catalog Entry, Host Info, Publisher, and all fields - **Trust Manifest** — identity binding, attestations, provenance links, signing, and verification procedures +- **Official Extensions** — additive extensions defined under the `https://ai-catalog.org/extensions/` prefix, including the Dependencies extension ([`https://ai-catalog.org/extensions/dependencies`](https://ai-catalog.org/extensions/dependencies)) for declaring an entry's downstream dependencies - **Discovery** — well-known URI, link relation headers, and agent-driven discovery - **Conformance levels** — normative requirements for Minimal, Discoverable, and Trusted catalogs - **Version handling** — compatibility rules for producers and consumers diff --git a/mkdocs.yml b/mkdocs.yml index 26645df..7bad074 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -90,6 +90,7 @@ nav: - Minimal Catalog: examples/minimal-catalog.md - Multi-Protocol Agent: examples/multi-protocol-agent.md - Nested Catalogs: examples/nested-catalogs.md + - Agent with Dependencies: examples/agent-with-dependencies.md - Implementations: implementations.md diff --git a/specification/ai-catalog.md b/specification/ai-catalog.md index 2880e5d..837a3e5 100644 --- a/specification/ai-catalog.md +++ b/specification/ai-catalog.md @@ -1151,14 +1151,279 @@ For example, a catalog entry with `extensions` representing metadata and a custo ## Official Extensions While publishers are free to create custom extensions, this specification -defines a set of "Official" known types for commonly requested schemas: +defines a set of "Official" known types for commonly requested schemas. +Official extension keys use the `https://ai-catalog.org/extensions/` +prefix, which signals that the extension is standardized by the AI Catalog +Working Group rather than defined by an individual vendor. A consumer that +does not implement an official extension MUST still ignore it without error, +exactly as it would any other unrecognized extension key. 1. **Metadata** (`https://ai-catalog.org/extensions/metadata`) - Used to store generic, schemaless key-value pairs. +2. **Dependencies** (`https://ai-catalog.org/extensions/dependencies`) + - Declares the downstream artifacts a catalog entry depends on at + runtime. See [Dependencies Extension](#dependencies-extension). + As custom extensions become highly popular, the AI-Catalog TSC may promote them to Official Known Types or core standard fields in future specification versions. +## Dependencies Extension + +An artifact often relies on other artifacts to function: an agent may call +a downstream MCP server, an agent skill may require a specific tool, or a +plugin may depend on a companion agent. The **Dependencies extension** +(`https://ai-catalog.org/extensions/dependencies`) is an official extension +that lets a publisher declare those relationships on a Catalog Entry so +that a consumer — typically an agent planner or a governance tool — can +answer questions such as *"can I use this artifact?"* (do I hold the +credentials its dependencies require?) and *"what breaks if I decommission +artifact X?"* **before** invoking or deploying anything. + +This capability is provided as an extension rather than a core field +deliberately. The core catalog schema is kept minimal and dependency-free +(see [ADR-0002](https://github.com/Agent-Card/ai-catalog/blob/main/adr/0002-defer-entry-dependencies.md)), +and the extension mechanism ([Extensions](#extensions)) is exactly the +sanctioned place for capabilities that not every catalog needs. Because the +extension is additive and unrecognized extensions are ignored, it requires +no `specVersion` change: a catalog carrying it remains a conformant 1.0 +document. + +Dependencies are **discovery metadata, not a resolver**. The extension +declares the edges of a dependency graph; it does not fetch, install, or +version-resolve them. Consistent with the catalog's role as a thin pointer +around artifacts it does not define, a dependency is a *reference* to +another artifact's `identifier`, never an embedded copy of it. The +depended-on artifact remains the authoritative source for its own metadata. + +### Extension Value Structure + +The value of the `https://ai-catalog.org/extensions/dependencies` key is an +object with the following OPTIONAL members: + +`required` +: An array of Dependency Requirement objects that MUST all be satisfiable + for the artifact to function. This array expresses an **AND** + relationship: every element is a co-requisite. A consumer SHOULD treat an + unmet `required` element as a hard blocker — the artifact cannot be + expected to operate without it. + +`optional` +: An array of Dependency Requirement objects that enhance the artifact but + are not essential. Each element is independently droppable: its absence + SHOULD degrade functionality gracefully rather than block use. + +Both arrays MAY be omitted or empty. An extension value with neither member +present is equivalent to omitting the extension entirely. + +### Dependency Requirement + +Each element of `required` and `optional` is a **Dependency Requirement**, +which is one of two shapes: + +- **A single Dependency object** — the common, flat case: the requirement + is satisfied by exactly one specific artifact. +- **A Dependency Group** — a requirement satisfied by *any one* of several + interchangeable alternatives, expressed as an `anyOf` array. This is an + **OR** relationship (for example, "a vector store — either Pinecone or + pgvector"). Any single listed alternative satisfies the slot. + +The presence of the `anyOf` key discriminates the two shapes. A Dependency +Group object contains an `anyOf` array of two or more Dependency objects, +any one of which satisfies the requirement, and an OPTIONAL `purpose` string +stated once for the whole group. A requirement satisfied by exactly one +artifact is written as a flat Dependency object, not as a single-element +group. + +Expressing AND via the `required` array and OR via `anyOf` groups on +separate structural axes keeps the two relationships unambiguous — the +concern that led the working group to defer a core `dependencies` field. +This is also deliberately distinct from the +[nested-catalog dual-protocol pattern](#nested-catalog-entries): a nested +catalog represents **one** logical artifact exposed through multiple +protocol faces (MCP *and* A2A) that a client chooses between at call time, +whereas `anyOf` expresses genuinely **different** downstream artifacts that +interchangeably fill one capability slot. + +### Dependency Object + +A Dependency object references a single depended-on artifact. It MUST +contain: + +`identifier` +: A string referencing the depended-on artifact's `identifier`, using the + same naming rules as a Catalog Entry `identifier` (the + `urn:air:{publisher}:{namespace}:{name}` structure is RECOMMENDED; see + [Catalog Entry](#catalog-entry)). The reference MAY point to an artifact + published by a different organization or hosted in a different catalog; + resolution is not guaranteed (see [Resolution](#resolution) below). + +The following members are OPTIONAL: + +`type` +: A media type indicating what kind of artifact the dependency is, drawn + from the same vocabulary as the Catalog Entry `type` field (e.g., + `application/mcp-server-card+json`, `application/a2a-agent-card+json`). + RECOMMENDED, so that a consumer can reason about a dependency without + dereferencing it. The resolved artifact remains authoritative for its own + type. + +`versionConstraint` +: A string expressing the range of acceptable versions of the dependency. + A [Semantic Versioning](https://semver.org/) range (e.g., `>=3.0.0`, + `^3.1`, `3.x`) is RECOMMENDED, mirroring the entry `version` convention; + when the depended-on artifact does not use SemVer, the value degrades to + an exact-string match against the target's version. This field is named + distinctly from `version` because it is a *matcher*, not a concrete + version literal, and unlike a bare lower bound it can express an upper + bound (e.g., "requires v3 but v4 is a breaking change"). When absent, any + version is acceptable. + +`credentialPropagation` +: A string hint describing how the artifact authenticates to this + dependency at runtime, so a planner can determine upfront which + credentials it (or its caller) must hold. Like `type`, this is an open + text field with a RECOMMENDED value set: + + - `obo` — *on behalf of*: the caller's user identity is propagated to + the dependency (delegated access; the audit trail is + user-attributed). + - `agent` — the artifact uses its own workload/agent identity + (agent-attributed). + - `user` — a distinct interactive user credential for the dependency is + required (the caller must authenticate separately). + - `none` — the dependency is public and requires no credential. + + Values are lowercase to match `identityType` casing elsewhere in this + specification. This field is **advisory**: it MUST NOT be treated as an + authoritative security control. The depended-on artifact enforces its + real credential requirements at connection time; a planner uses this + hint for pre-flight filtering, not for granting access. Note this is a + different concept from the Trust Manifest / Publisher `identityType` + field, which hints at the *scheme* of an identifier (`did`, `dns`, + `spiffe`); `credentialPropagation` describes credential/audit + attribution and is an orthogonal axis. + +`purpose` +: An OPTIONAL free-text description of why the artifact depends on this + dependency (e.g., "Read and write Salesforce CRM records"). It aids + impact analysis and consent UX. When a Dependency appears inside an + `anyOf` group, `purpose` MAY instead be stated once on the group. + +### Resolution + +A dependency is identified by `identifier` alone; the extension does not +carry the dependency's `url` or `data`. A consumer that needs the +depended-on artifact resolves the `identifier` through whatever mechanism +it uses to locate artifacts (the same catalog, a nested or federated +catalog, a registry, or a well-known lookup). Because a dependency MAY +reference an artifact outside the current catalog or publisher, +**resolution is not guaranteed**. Consumers: + +- MUST handle an unresolvable or unavailable `required` dependency + gracefully — surfacing it as a blocker rather than failing abruptly. +- MUST NOT automatically fetch, install, or invoke a resolved dependency + without applying the same trust verification they would apply to any + artifact (see [Trust Manifest](#trust-manifest) and + [Security Considerations](#security-considerations)). +- SHOULD detect cycles and bound traversal when following dependency edges + transitively, analogous to the nested-catalog depth limit (see + [Nested Catalog Depth and Circular References](#nested-catalog-depth-and-circular-references)). + +Because it lives under `extensions`, the Dependencies extension is +publisher-supplied metadata that sits outside the Trust Manifest's signed +`subject` binding, exactly like the entry `publisher` field. Its contents +are therefore only as trustworthy as the catalog document itself unless +brought under a catalog-level `signature` (see +[Dependency Confusion and Malicious Dependencies](#dependency-confusion-and-malicious-dependencies)). + +### Example + +The following entry uses the Dependencies extension to describe an agent +that has a hard dependency on a Salesforce MCP server (accessed on behalf +of the user), a hard dependency on *either* of two interchangeable vector +stores, and an optional dependency on an email-sending agent: + +```json +{ + "identifier": "urn:air:acme-corp.com:agent:crm-assistant", + "type": "application/a2a-agent-card+json", + "url": "https://api.acme-corp.com/agents/crm-assistant.json", + "extensions": { + "https://ai-catalog.org/extensions/dependencies": { + "required": [ + { + "identifier": "urn:air:salesforce.com:mcp:sales-cloud", + "type": "application/mcp-server-card+json", + "versionConstraint": ">=3.0.0", + "credentialPropagation": "obo", + "purpose": "Read and write Salesforce CRM records" + }, + { + "purpose": "Vector store for retrieval-augmented answers", + "anyOf": [ + { + "identifier": "urn:air:acme-corp.com:mcp:pinecone", + "type": "application/mcp-server-card+json", + "credentialPropagation": "agent" + }, + { + "identifier": "urn:air:acme-corp.com:mcp:pgvector", + "type": "application/mcp-server-card+json", + "credentialPropagation": "agent" + } + ] + } + ], + "optional": [ + { + "identifier": "urn:air:acme-corp.com:agent:email-sender", + "type": "application/a2a-agent-card+json", + "credentialPropagation": "agent", + "purpose": "Send email notifications when a deal closes" + } + ] + } + } +} +``` + +A planner reading this entry can determine, without dereferencing any +artifact, that using the CRM assistant requires the caller to be able to +propagate user credentials to Salesforce (`obo`), that one of two vector +stores must be reachable under the agent's own identity, and that email +notifications are best-effort. + +### Schema + +The following CDDL [[RFC8610]] defines the value of the +`https://ai-catalog.org/extensions/dependencies` extension key. + +``` +DependenciesExtension = { + ? required: [* DependencyRequirement], + ? optional: [* DependencyRequirement] +} + +; A requirement is EITHER a single dependency reference (the flat, common +; case) OR a group of interchangeable alternatives. The presence of the +; `anyOf` key discriminates the two shapes. +DependencyRequirement = Dependency / DependencyGroup + +Dependency = { + identifier: text, + ? type: text, + ? versionConstraint: text, + ? credentialPropagation: text, + ? purpose: text +} + +DependencyGroup = { + anyOf: [2* Dependency], + ? purpose: text +} +``` + # Version Handling The `specVersion` field identifies which version of this specification @@ -1421,6 +1686,14 @@ this threat: - **Layer 3** makes modification structurally impossible through content-addressing. +An attacker who can modify a catalog can also tamper with an entry's +[Dependencies extension](#dependencies-extension) — adding or removing +dependency references, or downgrading a `credentialPropagation` value to +coax a false "safe" verdict from a pre-flight check. The same trust layers +apply: only a catalog-level `signature` (Layer 2) or content-addressed +distribution (Layer 3) protects the dependency list from undetected +modification. + ## Trust Manifest Substitution Because a Trust Manifest is a peer element of the catalog entry rather @@ -1446,6 +1719,42 @@ compounding mechanisms: `signature` member itself) and verified exactly as a Trust Manifest signature. +## Dependency Confusion and Malicious Dependencies + +The [Dependencies extension](#dependencies-extension) lets an entry point +at other artifacts by `identifier`. This introduces threats beyond those of +a standalone entry, and consumers MUST treat the extension as advisory +input rather than an instruction to act: + +- **Dependency reference substitution / confusion.** An attacker who can + write the catalog document can add, remove, or repoint dependency + references, or register a typosquatted dependency `identifier` (see + [Identifier Typosquatting](#identifier-typosquatting)) so that a planner + resolves a malicious artifact. Consumers MUST NOT automatically fetch, + install, or invoke a resolved dependency without applying the same trust + verification ([Trust Manifest](#trust-manifest)) they would apply to any + other artifact. +- **Advisory hints are not controls.** `credentialPropagation`, + `versionConstraint`, and `purpose` are publisher assertions. A tampered or + mistaken `credentialPropagation: "none"` MUST NOT be read as evidence that + a dependency is safe to reach without credentials; the depended-on + artifact enforces its real requirements at connection time. +- **Unbounded or circular dependency graphs.** Following dependency edges + transitively can loop or fan out without limit. Consumers SHOULD detect + cycles and bound traversal depth, analogous to the nested-catalog limit + (see [Nested Catalog Depth and Circular References](#nested-catalog-depth-and-circular-references)). +- **Topology leakage.** An enumerated dependency list exposes internal and + third-party service identifiers and reveals which of them require + privileged credentials. Publishers SHOULD treat a published dependency + list as public information and omit or gate references to sensitive + internal systems. + +Because the extension lives under `extensions`, its contents are outside the +Trust Manifest's signed `subject` and are only as trustworthy as the catalog +document itself. Publishers who rely on dependencies for pre-flight +decisions SHOULD bring the catalog under a catalog-level `signature` so the +dependency list cannot be tampered with undetected. + ## Identifier Typosquatting Catalog entries are identified by URIs/URNs. An attacker can register @@ -1774,6 +2083,19 @@ artifact types including a nested catalog packaging related artifacts: "privacyPolicyUrl": "https://acme.com/legal/privacy", "termsOfServiceUrl": "https://acme.com/legal/terms" }, + "extensions": { + "https://ai-catalog.org/extensions/dependencies": { + "required": [ + { + "identifier": "urn:air:acme.com:server:finance-mcp", + "type": "application/mcp-server-card+json", + "versionConstraint": ">=1.4.0", + "credentialPropagation": "obo", + "purpose": "Execute finance tools on behalf of the user" + } + ] + } + }, "updatedAt": "2026-03-15T10:00:00Z" }, { @@ -2365,6 +2687,7 @@ AI Catalog (cross-artifact) | *(not in the Server Card)* | Entry `publisher` | | *(not in the Server Card)* | Entry `trustManifest` (identity, attestations, provenance) | | *(not in the Server Card)* | Entry `tags` for cross-artifact discovery | +| *(not in the Server Card)* | Downstream dependencies via the [Dependencies extension](#dependencies-extension) | ## MCP Server as Catalog Entry @@ -2553,6 +2876,7 @@ plugins/ | Plugin `.claude-plugin/plugin.json` | The artifact content (referenced via `url`) | | *(not in marketplace)* | Entry `trustManifest` (identity, attestations) | | *(not in marketplace)* | Entry `type` | +| *(not in marketplace)* | Downstream dependencies (e.g. a plugin's required MCP servers) via the [Dependencies extension](#dependencies-extension) | | Centralized marketplace repo | AI Catalog (decentralized, any URL) | ## Source Types diff --git a/specification/examples/ai-catalog.json b/specification/examples/ai-catalog.json index c58bc44..8e3e929 100644 --- a/specification/examples/ai-catalog.json +++ b/specification/examples/ai-catalog.json @@ -38,6 +38,27 @@ }, "issuedAt": "2026-02-22T16:30:00Z", "signature": "eyJhbGciOiJFUzI1NiJ9..detached-jws-signature" + }, + "extensions": { + "https://ai-catalog.org/extensions/dependencies": { + "required": [ + { + "identifier": "urn:air:acme-corp.com:mcp:finance-data", + "type": "application/mcp-server-card+json", + "versionConstraint": ">=2.0.0", + "credentialPropagation": "obo", + "purpose": "Query finance market data on behalf of the user" + } + ], + "optional": [ + { + "identifier": "urn:air:acme-corp.com:agent:notifier", + "type": "application/a2a-agent-card+json", + "credentialPropagation": "agent", + "purpose": "Send trade notifications" + } + ] + } } }, {