Skip to content

Commit dc6b06e

Browse files
committed
feat: add MkDocs developer documentation site
Adds a developer-friendly documentation site powered by MkDocs and the Material theme, making ai-catalog accessible to developers beyond the normative W3C specification. Key changes: - docs/ — 11 pages covering overview, quickstart, 5 guides (creating, serving, consuming, organizing, trust), 3 annotated examples, and a spec signpost - mkdocs.yml — Material theme config with single left-hand nav, no tabs - pyproject.toml — adds [dependency-groups] docs with mkdocs-material - .github/workflows/publish-docs.yml — new CI workflow deploying docs to gh-pages root - tools/update_gh_pages.py — adds subdir mode and preserves spec/ in root mode - .github/workflows/publish-spec.yml — spec now deploys to spec/ subdirectory, freeing root for the docs site URL structure after deployment: https://agent-card.github.io/ai-catalog/ ← developer docs https://agent-card.github.io/ai-catalog/spec/ ← normative spec Signed-off-by: Sam Betts <1769706+Tehsmash@users.noreply.github.com>
1 parent 6a75c6e commit dc6b06e

17 files changed

Lines changed: 2196 additions & 9 deletions

.github/workflows/publish-docs.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Publish Documentation
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- synchronize
8+
- reopened
9+
- ready_for_review
10+
paths:
11+
- .github/workflows/publish-docs.yml
12+
- docs/**
13+
- mkdocs.yml
14+
- pyproject.toml
15+
- uv.lock
16+
push:
17+
branches:
18+
- main
19+
paths:
20+
- .github/workflows/publish-docs.yml
21+
- docs/**
22+
- mkdocs.yml
23+
- pyproject.toml
24+
- uv.lock
25+
workflow_dispatch:
26+
27+
permissions:
28+
contents: read
29+
30+
concurrency:
31+
group: docs-pages-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name }}
32+
cancel-in-progress: true
33+
34+
jobs:
35+
build:
36+
runs-on: ubuntu-latest
37+
38+
steps:
39+
- name: Check out repository
40+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
41+
42+
- name: Set up uv
43+
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
44+
with:
45+
enable-cache: true
46+
cache-dependency-glob: uv.lock
47+
48+
- name: Build documentation site
49+
run: |
50+
uv run --python 3.12 --locked --group docs mkdocs build --site-dir .site/docs --strict
51+
52+
publish:
53+
if: |
54+
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
55+
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
56+
needs: build
57+
runs-on: ubuntu-latest
58+
concurrency:
59+
group: gh-pages-write
60+
cancel-in-progress: false
61+
permissions:
62+
contents: write
63+
64+
steps:
65+
- name: Check out repository
66+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
67+
68+
- name: Set up uv
69+
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
70+
with:
71+
enable-cache: true
72+
cache-dependency-glob: uv.lock
73+
74+
- name: Build documentation site
75+
run: |
76+
uv run --python 3.12 --locked --group docs mkdocs build --site-dir .site/docs --strict
77+
78+
- name: Publish docs to gh-pages root
79+
run: |
80+
python3 tools/update_gh_pages.py root \
81+
--source-dir .site/docs \
82+
--cname ai-catalog.io \
83+
--remote-url "https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" \
84+
--commit-message "Publish documentation site from main"

.github/workflows/publish-spec.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ jobs:
8383

8484
- name: Build canonical specification site
8585
run: |
86-
uv run --python 3.12 --locked python tools/build_spec.py specification/ai-catalog.md .site/root/index.html --config specification/respec-config.json
86+
uv run --python 3.12 --locked python tools/build_spec.py specification/ai-catalog.md .site/spec/index.html --config specification/respec-config.json
8787
88-
- name: Publish canonical site to gh-pages
88+
- name: Publish canonical site to gh-pages/spec/
8989
run: |
90-
python3 tools/update_gh_pages.py root \
91-
--source-dir .site/root \
92-
--cname ai-catalog.io \
90+
python3 tools/update_gh_pages.py subdir \
91+
--source-dir .site/spec \
92+
--subdir-name spec \
9393
--remote-url "https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" \
9494
--commit-message "Publish specification site from main"
9595

docs/examples/minimal-catalog.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Example: Minimal Catalog
2+
3+
The simplest valid AI Catalog — just a `specVersion` and a list of entries. This is Level 1 (Minimal) conformance.
4+
5+
```json
6+
{
7+
"specVersion": "1.0",
8+
"entries": [
9+
{
10+
"identifier": "urn:air:example.com:skill:code-review",
11+
"displayName": "Code Review Assistant",
12+
"type": "application/agent-skills+zip",
13+
"url": "https://skills.example.com/code-review/skill.zip"
14+
},
15+
{
16+
"identifier": "urn:air:example.com:mcp:weather",
17+
"type": "application/mcp-server-card+json",
18+
"url": "https://api.example.com/.well-known/mcp/server-card.json"
19+
},
20+
{
21+
"identifier": "urn:air:example.com:a2a:research",
22+
"type": "application/a2a-agent-card+json",
23+
"url": "https://agents.example.com/researchAssistant"
24+
}
25+
]
26+
}
27+
```
28+
29+
## Breakdown
30+
31+
**`specVersion: "1.0"`**
32+
Identifies the version of the AI Catalog specification this document conforms to. Always present. Clients check this before processing.
33+
34+
**`entries`**
35+
The list of artifacts. Each entry requires `identifier`, `type`, and `url` (or `data`).
36+
37+
**`identifier`**
38+
A stable, unique string for this artifact. The recommended format is `urn:air:{domain}:{namespace}:{name}`. The identifier should not change when the artifact moves or is versioned.
39+
40+
**`type`**
41+
What kind of artifact this is. Clients use this to decide whether they can use the artifact — without fetching it.
42+
43+
**`displayName`**
44+
Optional. Notice that the MCP server and A2A agent entries omit `displayName` — those artifact formats carry their own canonical names (`title` and `name` respectively). The skill bundle entry sets `displayName` because ZIP archives don't embed a self-describing name.
45+
46+
**`url`**
47+
Where to fetch the full artifact document.
48+
49+
## Upgrading to Level 2
50+
51+
Add a `host` object to identify your organization and officially reach Level 2 (Discoverable):
52+
53+
```json hl_lines="3 4 5 6"
54+
{
55+
"specVersion": "1.0",
56+
"host": {
57+
"displayName": "Example Corp",
58+
"identifier": "did:web:example.com"
59+
},
60+
"entries": [...]
61+
}
62+
```
63+
64+
Then serve the catalog at `/.well-known/ai-catalog.json` to enable automated domain-level discovery.
65+
66+
## Related guides
67+
68+
- [Creating a Catalog](../guides/creating-a-catalog.md) — detailed field reference
69+
- [Getting Started](../getting-started.md) — step-by-step quickstart
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Example: Multi-Protocol Agent
2+
3+
An agent that supports both MCP and A2A protocols can be represented as a single logical entry whose content is a nested catalog containing both protocol-specific entries.
4+
5+
```json
6+
{
7+
"specVersion": "1.0",
8+
"host": {
9+
"displayName": "Acme Financial Corp",
10+
"identifier": "did:web:acme-corp.com"
11+
},
12+
"entries": [
13+
{
14+
"identifier": "urn:air:acme-corp.com:agent:finance",
15+
"displayName": "Acme Finance Agent",
16+
"type": "application/ai-catalog+json",
17+
"description": "Finance agent accessible via both MCP and A2A protocols.",
18+
"tags": ["finance", "dual-protocol"],
19+
"publisher": {
20+
"identifier": "did:web:acme-corp.com",
21+
"displayName": "Acme Financial Corp"
22+
},
23+
"data": {
24+
"specVersion": "1.0",
25+
"entries": [
26+
{
27+
"identifier": "urn:air:acme-corp.com:mcp:finance",
28+
"type": "application/mcp-server-card+json",
29+
"url": "https://api.acme-corp.com/.well-known/mcp/server-card.json"
30+
},
31+
{
32+
"identifier": "urn:air:acme-corp.com:a2a:finance",
33+
"type": "application/a2a-agent-card+json",
34+
"url": "https://api.acme-corp.com/agents/finance"
35+
}
36+
]
37+
},
38+
"trustManifest": {
39+
"identity": "urn:air:acme-corp.com:agent:finance",
40+
"attestations": [
41+
{
42+
"type": "SOC2-Type2",
43+
"uri": "https://trust.acme-corp.com/reports/soc2.pdf",
44+
"mediaType": "application/pdf",
45+
"digest": "sha256:a1b2c3d4e5f6"
46+
}
47+
]
48+
}
49+
}
50+
]
51+
}
52+
```
53+
54+
## How it works
55+
56+
**The outer entry** (`urn:air:acme-corp.com:agent:finance`) represents the logical agent. It has `type: "application/ai-catalog+json"`, meaning its content is itself a catalog. Clients that don't need to know about protocols can stop here — they see one agent with a name, description, tags, and trust metadata.
57+
58+
The outer entry sets `displayName` because a catalog document has no intrinsic name of its own — unlike an A2A Agent Card or MCP Server Card which embed their own names. The inner protocol-specific entries omit `displayName` since their native formats carry canonical names.
59+
60+
**The `data` field** inlines the inner catalog directly (instead of referencing it via `url`). This makes the entry self-contained — clients don't need a second network request to see the protocol-specific entries.
61+
62+
**The inner entries** provide protocol-specific access points. A client that speaks MCP fetches the MCP Server Card; one that speaks A2A fetches the A2A Agent Card. The outer entry's trust metadata applies to both.
63+
64+
## When to use this pattern
65+
66+
Use dual-protocol packaging when:
67+
68+
- A single logical service is accessible via more than one AI protocol
69+
- You want to expose one identity and one set of trust claims for both protocol endpoints
70+
- You're listing an agent in a catalog where some clients speak MCP and others speak A2A
71+
72+
## Variation: using `url` instead of `data`
73+
74+
If the inner catalog is large or updated independently, reference it via `url` instead of embedding it:
75+
76+
```json
77+
{
78+
"identifier": "urn:air:acme-corp.com:agent:finance",
79+
"displayName": "Acme Finance Agent",
80+
"type": "application/ai-catalog+json",
81+
"url": "https://acme-corp.com/agents/finance-protocols.json"
82+
}
83+
```
84+
85+
The document at that URL is a full AI Catalog containing the MCP and A2A entries.
86+
87+
## Related guides
88+
89+
- [Organizing Catalogs](../guides/organizing-catalogs.md#dual-protocol-agents)
90+
- [Adding Trust](../guides/adding-trust.md)

docs/examples/nested-catalogs.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Example: Nested Catalogs
2+
3+
This example shows how an enterprise organizes a large artifact inventory using nested catalogs. The root catalog provides an overview; each department maintains its own sub-catalog.
4+
5+
## Root catalog
6+
7+
The root catalog is served at `/.well-known/ai-catalog.json`:
8+
9+
```json
10+
{
11+
"specVersion": "1.0",
12+
"host": {
13+
"displayName": "Acme Enterprise AI",
14+
"identifier": "did:web:acme-corp.com",
15+
"documentationUrl": "https://docs.acme-corp.com/ai"
16+
},
17+
"entries": [
18+
{
19+
"identifier": "urn:air:acme-corp.com:a2a:assistant",
20+
"version": "3.0.0",
21+
"type": "application/a2a-agent-card+json",
22+
"url": "https://api.acme-corp.com/agents/assistant.json",
23+
"description": "General-purpose corporate assistant agent."
24+
},
25+
{
26+
"identifier": "urn:air:acme-corp.com:catalog:finance",
27+
"displayName": "Finance Services",
28+
"type": "application/ai-catalog+json",
29+
"url": "https://acme-corp.com/catalogs/finance.json",
30+
"description": "Financial agents, MCP servers, and datasets.",
31+
"tags": ["finance", "trading", "compliance"]
32+
},
33+
{
34+
"identifier": "urn:air:acme-corp.com:catalog:engineering",
35+
"displayName": "Engineering Tools",
36+
"type": "application/ai-catalog+json",
37+
"url": "https://acme-corp.com/catalogs/engineering.json",
38+
"description": "CI/CD agents, code review tools, and DevOps servers.",
39+
"tags": ["engineering", "devops", "ci-cd"]
40+
}
41+
]
42+
}
43+
```
44+
45+
## Finance sub-catalog
46+
47+
The Finance team owns and hosts `finance.json`. It can be updated independently of the root:
48+
49+
```json
50+
{
51+
"specVersion": "1.0",
52+
"host": {
53+
"displayName": "Acme Finance Team",
54+
"identifier": "did:web:finance.acme-corp.com"
55+
},
56+
"entries": [
57+
{
58+
"identifier": "urn:air:acme-corp.com:a2a:finance",
59+
"type": "application/a2a-agent-card+json",
60+
"url": "https://api.acme-corp.com/agents/finance.json",
61+
"tags": ["finance", "trading"]
62+
},
63+
{
64+
"identifier": "urn:air:acme-corp.com:mcp:finance",
65+
"type": "application/mcp-server-card+json",
66+
"url": "https://api.acme-corp.com/.well-known/mcp/server-card.json",
67+
"tags": ["finance", "mcp"]
68+
},
69+
{
70+
"identifier": "urn:air:acme-corp.com:data:market-2026q1",
71+
"displayName": "Market Dataset Q1 2026",
72+
"type": "application/parquet",
73+
"url": "https://data.acme-corp.com/market-2026q1.parquet",
74+
"tags": ["dataset", "finance"]
75+
}
76+
]
77+
}
78+
```
79+
80+
## How it works
81+
82+
**Direct entries** (like `urn:air:acme-corp.com:a2a:assistant`) appear directly in the root catalog. Clients see them without any additional fetching. The `displayName` is omitted for A2A entries since the agent card itself carries a canonical name.
83+
84+
**Nested catalog entries** (like `urn:air:acme-corp.com:catalog:finance`) have `type: "application/ai-catalog+json"` and set `displayName` since catalogs don't embed their own name. Clients follow the `url` to fetch the sub-catalog and process it as another AI Catalog.
85+
86+
**Dataset entries** set `displayName` since Parquet files don't carry a human-readable name.
87+
88+
**Independent ownership**: each sub-catalog has its own `host` object. The Finance team updates their catalog without touching the root.
89+
90+
**Mix and match**: a catalog can contain both direct entries and nested catalog references in the same `entries` array.
91+
92+
## Client traversal
93+
94+
A client that wants all artifacts walks the tree:
95+
96+
1. Fetch root at `/.well-known/ai-catalog.json`
97+
2. Find entries with `type: "application/ai-catalog+json"` → these are sub-catalogs
98+
3. Fetch each sub-catalog URL
99+
4. Repeat (up to depth limit of 4)
100+
5. Collect all non-catalog entries
101+
102+
See [Consuming Catalogs — Handling nested catalogs](../guides/consuming-catalogs.md#handling-nested-catalogs) for implementation details including cycle detection.
103+
104+
## Related guides
105+
106+
- [Organizing Catalogs](../guides/organizing-catalogs.md)
107+
- [Consuming Catalogs](../guides/consuming-catalogs.md)

0 commit comments

Comments
 (0)