Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions adr/0016-displayname-optional.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ADR-0016: displayName Is Optional on a Catalog Entry

## Status
Proposed

## Date
2026-06-18 (Proposed)

## Context
A Catalog Entry is a thin pointer: it carries an `identifier`, a `mediaType`, and exactly one of `url` or `data` locating the full artifact. `displayName` (a human-readable name for the artifact) is a REQUIRED member of every Catalog Entry, and is listed among the required-at-minimum members of a Minimal Catalog (Level 1).

For the card-shaped artifact types this spec calls out by media type, a human-readable name already lives inside the referenced artifact:

- An **A2A Agent Card** has a REQUIRED, human-readable `name`.
- An **MCP Server Card** carries a human-readable `title` (and a required human-readable `description`); its `name` is a reverse-DNS machine identifier.

Requiring `displayName` on the entry therefore forces every catalog publisher to copy a value that already exists in the document the client is about to fetch, and to keep the two in sync indefinitely. When they drift, a consumer is left with two conflicting human-readable names and no principled way to pick.

Not all artifacts self-name, however. Opaque artifacts referenced by a catalog — a raw dataset (`application/parquet`), a model blob, a skill bundle (`application/agentskill+zip`), or a nested catalog — have no embedded canonical name. For those, the entry is the only place a human-readable name can live.

## Decision
`displayName` is an **OPTIONAL** member of a Catalog Entry, not a required one, and is removed from the Minimal Catalog (Level 1) required-at-minimum set.

Guidance: a publisher SHOULD set `displayName` only when the referenced artifact does not already carry its own canonical human-readable name. When the artifact does carry one, that artifact is the authoritative source and `displayName` SHOULD be omitted. When `displayName` *is* present, however, it takes precedence and is authoritative for display: a consumer SHOULD render it as given, even if it differs from a name carried by the referenced artifact — setting `displayName` is how a publisher deliberately overrides the artifact's own name.

When rendering an entry, a consumer SHOULD resolve a name in order: (1) the entry's `displayName` if present, (2) the referenced artifact's own canonical name (e.g. an A2A Agent Card `name` or MCP Server Card `title`) if already fetched or cached, then (3) the trailing segment of the entry's `identifier` URN. See [Resolving an Artifact's Display Name](../specification/ai-catalog.md#resolving-an-artifacts-display-name) in the specification.

This decision concerns only the Catalog Entry. `displayName` on `HostInfo` and `Publisher` is unchanged — those name the catalog host and publishing organization, which is genuinely catalog-authored metadata with no other home.

## Rationale
- For self-naming artifacts (A2A Agent Cards, MCP Server Cards), requiring an entry-level name guarantees permanent duplication and a standing sync burden.
- Keeping the name authoritative at its source keeps it fresh and avoids the "two conflicting names" failure mode.
- The entry stays a thin pointer, consistent with the catalog's role as a lightweight directory of links (ADR-0013: authoring/discovery format).
- The field is preserved (not removed) so opaque, nameless artifacts still have a place to carry a human-readable name.
- Mirrors the project's existing "optional, not mandatory" posture for discovery mechanics (ADR-0011).

## Alternatives Considered
- **Keep `displayName` required.** The strongest argument is browsing UX: a UI listing N entries shouldn't have to fetch N artifacts just to show names. Rejected as a permanent duplication/sync cost; the optional form still lets any publisher populate `displayName` on every entry when a self-describing list view is wanted — it just isn't forced on card-backed entries.
- **Remove `displayName` from the entry entirely.** Rejected because opaque artifacts (datasets, model blobs, skill bundles, nested catalogs) embed no self-describing name and would be left nameless in a catalog.

## Open Question
An MCP Server Card's `title` is itself OPTIONAL. The guidance above is keyed on whether the artifact *carries a canonical name*, not on its media type: if a referenced Server Card omits `title`, the entry SHOULD keep `displayName` so the artifact is not left with only a reverse-DNS identifier and a prose description.

## Meeting Reference
Slated for discussion at the 2026-06-18 AI Catalog bi-weekly working-group call; this ADR records the proposal ahead of that discussion. Update the Status and Date (and note who raised concerns and the agreed outcome, as in ADR-0011) once the working group ratifies it.
77 changes: 51 additions & 26 deletions specification/ai-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,11 @@ For example, a minimal catalog listing three AI artifacts:
},
{
"identifier": "urn:air:example.com:mcp:weather",
"displayName": "Weather Service",
"type": "application/mcp-server-card+json",
"url": "https://api.example.com/.well-known/mcp/server-card.json"
},
{
"identifier": "urn:air:example.com:a2a:research",
"displayName": "Research Assistant",
"type": "application/a2a-agent-card+json",
"url": "https://agents.example.com/researchAssistant"
}
Expand Down Expand Up @@ -207,9 +205,6 @@ It MUST contain the following members:

See [Multi-Version Entries](#multi-version-entries) for uniqueness rules when multiple versions are present.

`displayName`
: A string containing a human-readable name for the artifact.

`type`
: A string containing the identifier that specifies the type of the
referenced artifact. This field is an open text format, so any string value is accepted. However, to ensure interoperability, it is RECOMMENDED to use one of the following recognized "known types" in the ecosystem when applicable, partitioned by their respective governance boundaries:
Expand Down Expand Up @@ -245,6 +240,24 @@ provide the artifact content:

The following members are OPTIONAL:

`displayName`
: A string containing a human-readable name for the artifact.
This field SHOULD be set only when the referenced artifact does not
already carry its own canonical human-readable name — for example a
raw dataset (`application/parquet`), a model blob, or a skill bundle
(`application/agent-skills+zip`), none of which embed a self-describing
name. When the referenced artifact does carry such a name — for
example the `name` field of an A2A Agent Card or the `title` field of
an MCP Server Card — that artifact is the authoritative source and
`displayName` SHOULD be omitted to avoid duplicating a value that can
drift out of sync. When `displayName` *is* present, however, it takes
precedence: it is the authoritative value for display, and a consumer
SHOULD render it as given even when it differs from a name carried by
the referenced artifact. Setting `displayName` is how a publisher
deliberately overrides the artifact's own name. See
[Resolving an Artifact's Display Name](#resolving-an-artifacts-display-name)
for the full consumer resolution order.

`description`
: A string containing a short description of the artifact.

Expand Down Expand Up @@ -275,6 +288,36 @@ The following members are OPTIONAL:
providing verifiable identity and trust metadata for this artifact.
See [Trust Manifest](#trust-manifest) for details.

### Resolving an Artifact's Display Name

Because `displayName` is OPTIONAL, a consumer rendering a catalog entry
cannot assume it is present. To obtain a human-readable name, a consumer
SHOULD resolve one in the following order:

1. **`displayName` on the entry**, if present. A publisher-supplied
`displayName` always wins, even when it differs from a name carried by
the referenced artifact.
2. **The referenced artifact's own canonical name**, if the consumer has
already fetched or cached the artifact — for example the `name` field
of an A2A Agent Card or the `title` field of an MCP Server Card.
3. **The trailing segment of the entry's `identifier`** as a last
resort — the portion after its final `:` or `/` delimiter. For
example, `urn:air:example.com:mcp:weather` yields `weather` and
`urn:air:anonymous.modelcontextprotocol.io:mcp:brave-search` yields
`brave-search`.

A consumer SHOULD NOT dereference an artifact at render time solely to
obtain a name. A registry, directory, or other service built on top of a
catalog SHOULD resolve the name once at ingestion — alongside any other
derived metadata it attaches, such as relevance scores or tags — and
cache the result, rather than fetching artifacts on the rendering path.

This order also covers a referenced MCP Server Card whose `title` is
itself absent: step 2 yields no name, so the consumer falls through to
the `identifier` segment in step 3. A publisher MAY still set
`displayName` on such an entry to provide a better name than the bare
identifier segment.

## Multi-Version Entries

A catalog MAY contain multiple entries with the same `identifier` and
Expand All @@ -300,15 +343,13 @@ For example, a catalog listing two versions of the same agent:
"entries": [
{
"identifier": "urn:air:acme.com:agent:finance",
"displayName": "Acme Finance Agent",
"version": "2.1.0",
"type": "application/a2a-agent-card+json",
"url": "https://api.acme-corp.com/agents/finance/v2.1.json",
"updatedAt": "2026-03-15T10:00:00Z"
},
{
"identifier": "urn:air:acme.com:agent:finance",
"displayName": "Acme Finance Agent",
"version": "2.0.0",
"type": "application/a2a-agent-card+json",
"url": "https://api.acme-corp.com/agents/finance/v2.0.json",
Expand Down Expand Up @@ -911,7 +952,7 @@ A conformant Minimal Catalog is a JSON document with media type

- `specVersion` — the specification version string
- `entries` — an array of Catalog Entry objects, each containing at
minimum `identifier`, `displayName`, `type`, and exactly one of `url` or
minimum `identifier`, `type`, and exactly one of `url` or
`data`

All other fields (`host`, `publisher`, `trustManifest`,
Expand Down Expand Up @@ -1315,7 +1356,6 @@ artifact types including a nested catalog packaging related artifacts:
"entries": [
{
"identifier": "urn:air:acme.com:agent:finance-a2a",
"displayName": "Acme Finance A2A Agent",
"version": "2.1.0",
"type": "application/a2a-agent-card+json",
"url": "https://api.acme-corp.com/agents/finance.json",
Expand Down Expand Up @@ -1347,7 +1387,6 @@ artifact types including a nested catalog packaging related artifacts:
},
{
"identifier": "urn:air:acme.com:server:finance-mcp",
"displayName": "Acme Finance MCP Server",
"version": "1.4.0",
"type": "application/mcp-server-card+json",
"url": "https://api.acme-corp.com/.well-known/mcp/server-card.json",
Expand All @@ -1366,13 +1405,11 @@ artifact types including a nested catalog packaging related artifacts:
"entries": [
{
"identifier": "urn:air:acme.com:agent:finance-a2a",
"displayName": "Finance A2A Agent",
"type": "application/a2a-agent-card+json",
"url": "https://api.acme-corp.com/agents/finance.json"
},
{
"identifier": "urn:air:acme.com:server:finance-mcp",
"displayName": "Finance MCP Server",
"type": "application/mcp-server-card+json",
"url": "https://api.acme-corp.com/.well-known/mcp/server-card.json"
},
Expand Down Expand Up @@ -1421,7 +1458,6 @@ document:
"entries": [
{
"identifier": "urn:air:acme.com:agent:assistant",
"displayName": "Acme Corporate Assistant",
"version": "3.0.0",
"type": "application/a2a-agent-card+json",
"url": "https://api.acme-corp.com/agents/assistant.json",
Expand Down Expand Up @@ -1482,13 +1518,11 @@ containing both protocol-specific entries:
"entries": [
{
"identifier": "urn:air:acme.com:agent:finance:mcp",
"displayName": "Acme Finance MCP Server",
"type": "application/mcp-server-card+json",
"url": "https://api.acme-corp.com/.well-known/mcp/server-card.json"
},
{
"identifier": "urn:air:acme.com:agent:finance:a2a",
"displayName": "Acme Finance A2A Agent",
"type": "application/a2a-agent-card+json",
"url": "https://api.acme-corp.com/agents/finance"
}
Expand Down Expand Up @@ -1748,7 +1782,7 @@ does not address.
|:---|:---|
| `server.json` document (whole file) | Artifact content via entry `url` or `data` |
| `name` (reverse-DNS identifier) | Entry `identifier` (mapped to URI form) |
| `title` | Entry `displayName` |
| `title` | Stays in the artifact (`server.json` carries its own `title`); entry `displayName` is omitted unless the artifact lacks a name |
| `description` | Entry `description` |
| `version` | Entry `version` |
| `repository` | Entry `metadata.repository` |
Expand Down Expand Up @@ -1786,7 +1820,6 @@ reflects the Registry format:
```json
{
"identifier": "urn:air:anonymous.modelcontextprotocol.io:mcp:brave-search",
"displayName": "Brave Search",
"version": "1.0.2",
"type": "application/mcp-server-card+json",
"url": "https://registry.modelcontextprotocol.io/servers/brave-search/server.json",
Expand Down Expand Up @@ -1848,7 +1881,6 @@ agents, skills, and other artifacts:
"entries": [
{
"identifier": "urn:air:anonymous.modelcontextprotocol.io:mcp:brave-search",
"displayName": "Brave Search",
"version": "1.0.2",
"type": "application/mcp-server-card+json",
"url": "https://registry.modelcontextprotocol.io/servers/brave-search/server.json",
Expand All @@ -1857,7 +1889,6 @@ agents, skills, and other artifacts:
},
{
"identifier": "urn:air:modelcontextprotocol.github.io:mcp:filesystem",
"displayName": "Filesystem",
"version": "1.0.2",
"type": "application/mcp-server-card+json",
"url": "https://registry.modelcontextprotocol.io/servers/filesystem/server.json",
Expand All @@ -1866,7 +1897,6 @@ agents, skills, and other artifacts:
},
{
"identifier": "urn:air:example.github.io:mcp:weather-mcp",
"displayName": "Weather",
"version": "0.5.0",
"type": "application/mcp-server-card+json",
"url": "https://registry.modelcontextprotocol.io/servers/weather/server.json",
Expand Down Expand Up @@ -1952,7 +1982,6 @@ server can reference the Server Card as its artifact content:
```json
{
"identifier": "urn:air:example.com:mcp:finance-server",
"displayName": "Acme Finance MCP Server",
"type": "application/mcp-server-card+json",
"url": "https://api.acme-corp.com/.well-known/mcp/server-card.json",
"description": "MCP server for financial data and trading tools",
Expand Down Expand Up @@ -2034,7 +2063,7 @@ plugins/
| Marketplace `description` | Catalog `metadata.description` |
| Marketplace `owner` | Catalog `host` (with `identifier` derived from owner) |
| `plugins[]` array | Catalog `entries[]` array |
| Plugin `name` | Entry `displayName` and `identifier` (derived as URN) |
| Plugin `name` | Entry `identifier` (derived as URN); the plugin manifest carries its own name, so entry `displayName` is omitted |
| Plugin `description` | Entry `description` |
| Plugin `category` | Entry `tags[]` (first tag) |
| Plugin `tags` | Entry `tags[]` (merged with category) |
Expand Down Expand Up @@ -2082,7 +2111,6 @@ maps to an AI Catalog where each plugin is an entry:
"entries": [
{
"identifier": "urn:claude-plugin:anthropic:agent-sdk-dev",
"displayName": "agent-sdk-dev",
"type": "application/vnd.anthropic.claude-plugin+json",
"url": "https://github.com/anthropics/claude-plugins-official/tree/main/plugins/agent-sdk-dev",
"description": "Development kit for working with the Claude Agent SDK",
Expand All @@ -2097,7 +2125,6 @@ maps to an AI Catalog where each plugin is an entry:
},
{
"identifier": "urn:claude-plugin:adspirer:ads-agent",
"displayName": "adspirer-ads-agent",
"type": "application/vnd.anthropic.claude-plugin+json",
"url": "https://github.com/amekala/adspirer-mcp-plugin.git",
"description": "Cross-platform ad management for Google Ads, Meta Ads, TikTok Ads, and LinkedIn Ads.",
Expand All @@ -2118,7 +2145,6 @@ maps to an AI Catalog where each plugin is an entry:
},
{
"identifier": "urn:claude-plugin:aikido:security",
"displayName": "aikido",
"type": "application/vnd.anthropic.claude-plugin+json",
"url": "https://github.com/AikidoSec/aikido-claude-plugin.git",
"description": "Aikido Security scanning — SAST, secrets, and IaC vulnerability detection.",
Expand Down Expand Up @@ -2165,7 +2191,6 @@ contains multiple artifact types:
"entries": [
{
"identifier": "urn:claude-plugin:anthropic:example-plugin:mcp",
"displayName": "Example Plugin MCP Server",
"type": "application/mcp-server-card+json",
"url": "https://github.com/anthropics/claude-plugins-official/blob/main/plugins/example-plugin/server-card.json"
},
Expand Down
1 change: 0 additions & 1 deletion specification/examples/ai-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"entries": [
{
"identifier": "urn:example:agent-finance-001",
"displayName": "Acme Finance Agent",
"mediaType": "application/a2a-agent-card+json",
"description": "Multi-protocol finance agent.",
"tags": [
Expand Down
Loading