From 07272dcd0ca88e0f7c21e79769e297517a188bce Mon Sep 17 00:00:00 2001 From: "warp-agent-staging[bot]" <240773466+warp-agent-staging[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:13:07 +0000 Subject: [PATCH 1/3] Publish ARD catalog at /.well-known/ard.json List the docs MCP server manifest, the Warp Agent API OpenAPI spec, and the docs llms.txt in an Agentic Resource Discovery catalog, mirroring the catalog on www.warp.dev. Identifiers match www for shared resources. Add test:agent-discovery (node --test) that validates entry shape, checks the OpenAPI media-type version against the spec, and blocks retired Oz wording. Run it in CI before the build. --- .github/workflows/ci.yml | 3 ++ package.json | 1 + public/.well-known/ard.json | 30 +++++++++++++++++ scripts/agent-discovery.test.mjs | 56 ++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 public/.well-known/ard.json create mode 100644 scripts/agent-discovery.test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff229d761..2490f1deb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,6 +132,9 @@ jobs: - name: Test tracking redirect middleware run: npm run test:tracking-redirect + - name: Test agent discovery catalog + run: npm run test:agent-discovery + - name: Build site run: npm run build diff --git a/package.json b/package.json index 18983cd39..297a79792 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:heading-tocs": "node scripts/validate-heading-tocs.mjs", "test:heading-vars": "node --test src/plugins/heading-vars-core.test.mjs", "test:tracking-redirect": "node --test scripts/tracking-redirect.test.mjs", + "test:agent-discovery": "node --test scripts/agent-discovery.test.mjs", "test:tracking-routes": "node scripts/validate-tracking-routes.mjs", "test:kapa-webmcp": "node --test src/lib/kapa-webmcp.test.mjs", "test:kapa-support-escalation": "node --test src/lib/kapa-support-escalation.test.mjs", diff --git a/public/.well-known/ard.json b/public/.well-known/ard.json new file mode 100644 index 000000000..ef916ce4b --- /dev/null +++ b/public/.well-known/ard.json @@ -0,0 +1,30 @@ +{ + "entries": [ + { + "identifier": "urn:air:warp.dev:server:documentation", + "displayName": "Warp Documentation MCP Server", + "type": "application/mcp-server+json", + "url": "https://docs.warp.dev/.well-known/mcp.json", + "capabilities": ["Search Warp documentation", "Retrieve Warp documentation"], + "description": "Search and retrieve content from Warp's official documentation", + "representativeQueries": ["How do I use Warp agents?", "How do I use Warp Factories?"] + }, + { + "identifier": "urn:air:warp.dev:api:agent", + "displayName": "Warp Agent API", + "type": "application/vnd.oai.openapi+json;version=3.2", + "url": "https://docs.warp.dev/openapi.json", + "capabilities": ["Create cloud agent runs", "Inspect cloud agent runs"], + "description": "OpenAPI description for programmatically creating, managing, and querying Warp cloud agent runs.", + "representativeQueries": ["Create a Warp cloud agent run", "Check the status of a Warp agent run"] + }, + { + "identifier": "urn:air:warp.dev:resource:docs-llms", + "displayName": "Warp Documentation LLM Index", + "type": "text/plain", + "url": "https://docs.warp.dev/llms.txt", + "description": "Machine-readable index of the Warp documentation, with links to the full, abridged, and per-section text sets.", + "representativeQueries": ["Find the Warp documentation for AI agents", "Get the full Warp documentation as plain text"] + } + ] +} diff --git a/scripts/agent-discovery.test.mjs b/scripts/agent-discovery.test.mjs new file mode 100644 index 000000000..45d628144 --- /dev/null +++ b/scripts/agent-discovery.test.mjs @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { test } from 'node:test'; + +const DOCS_ORIGIN = 'https://docs.warp.dev'; +const repoFile = (path) => new URL(`../${path}`, import.meta.url); +const readJson = async (path) => JSON.parse(await readFile(repoFile(path), 'utf8')); + +const catalog = await readJson('public/.well-known/ard.json'); +const byId = (identifier) => catalog.entries.find((entry) => entry.identifier === identifier); + +test('ARD catalog entries are valid', () => { + assert.deepEqual(Object.keys(catalog), ['entries']); + assert.ok(catalog.entries.length > 0); + const identifiers = catalog.entries.map((entry) => entry.identifier); + assert.equal(new Set(identifiers).size, identifiers.length, 'Identifiers must be unique'); + for (const entry of catalog.entries) { + assert.match(entry.identifier, /^urn:air:warp\.dev:[a-z0-9._-]+:[a-z0-9._-]+$/); + assert.equal(typeof entry.displayName, 'string'); + assert.equal(typeof entry.type, 'string'); + assert.equal(typeof entry.description, 'string'); + assert.notEqual('url' in entry, 'data' in entry, `${entry.identifier} needs exactly one of url or data`); + assert.ok(entry.url.startsWith('https://'), `${entry.identifier} url must use HTTPS`); + assert.ok(entry.representativeQueries.length >= 2 && entry.representativeQueries.length <= 5); + for (const query of entry.representativeQueries) assert.equal(typeof query, 'string'); + } +}); + +test('MCP entry points to the served MCP manifest', async () => { + const entry = byId('urn:air:warp.dev:server:documentation'); + assert.equal(entry.url, `${DOCS_ORIGIN}/.well-known/mcp.json`); + const manifest = await readJson('public/.well-known/mcp.json'); + assert.match(manifest.url, /^https:\/\//); +}); + +test('OpenAPI entry media type matches the spec version', async () => { + const entry = byId('urn:air:warp.dev:api:agent'); + assert.equal(entry.url, `${DOCS_ORIGIN}/openapi.json`); + const spec = await readFile(repoFile('developers/agent-api-openapi.yaml'), 'utf8'); + const [, major, minor] = spec.match(/^openapi:\s*['"]?(\d+)\.(\d+)/m); + assert.equal(entry.type, `application/vnd.oai.openapi+json;version=${major}.${minor}`); +}); + +test('LLM index entry points to the generated docs llms.txt', async () => { + const entry = byId('urn:air:warp.dev:resource:docs-llms'); + assert.equal(entry.url, `${DOCS_ORIGIN}/llms.txt`); + assert.equal(entry.type, 'text/plain'); + const astroConfig = await readFile(repoFile('astro.config.mjs'), 'utf8'); + assert.match(astroConfig, /from 'starlight-llms-txt'/, 'starlight-llms-txt generates /llms.txt'); +}); + +test('catalog does not use retired Oz wording or URLs', () => { + const serialized = JSON.stringify(catalog); + assert.doesNotMatch(serialized, /\boz\b/i); + assert.doesNotMatch(serialized, /\/oz(?:[/#?"]|$)/i); +}); From 6994e5b5a733f6e83c506100d4acaa9bd8dedba5 Mon Sep 17 00:00:00 2001 From: "warp-agent-staging[bot]" <240773466+warp-agent-staging[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:13:35 +0000 Subject: [PATCH 2/3] Align docs MCP manifest description with www.warp.dev Drop the retired Oz name and the agentic development environment framing from /.well-known/mcp.json, which the ARD catalog now references, and extend the Oz wording check to cover it. --- public/.well-known/mcp.json | 2 +- scripts/agent-discovery.test.mjs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/.well-known/mcp.json b/public/.well-known/mcp.json index a52298503..612d432aa 100644 --- a/public/.well-known/mcp.json +++ b/public/.well-known/mcp.json @@ -1,5 +1,5 @@ { "name": "Warp Documentation", - "description": "Search and retrieve Warp documentation — the agentic development environment and Oz, Warp's programmable agent platform.", + "description": "Search and retrieve content from Warp's official documentation", "url": "https://warp.mcp.kapa.ai" } diff --git a/scripts/agent-discovery.test.mjs b/scripts/agent-discovery.test.mjs index 45d628144..e5733638f 100644 --- a/scripts/agent-discovery.test.mjs +++ b/scripts/agent-discovery.test.mjs @@ -49,8 +49,8 @@ test('LLM index entry points to the generated docs llms.txt', async () => { assert.match(astroConfig, /from 'starlight-llms-txt'/, 'starlight-llms-txt generates /llms.txt'); }); -test('catalog does not use retired Oz wording or URLs', () => { - const serialized = JSON.stringify(catalog); +test('discovery manifests do not use retired Oz wording or URLs', async () => { + const serialized = JSON.stringify([catalog, await readJson('public/.well-known/mcp.json')]); assert.doesNotMatch(serialized, /\boz\b/i); assert.doesNotMatch(serialized, /\/oz(?:[/#?"]|$)/i); }); From 9d2fdef43ad93747d52db896ba9d6db28803233f Mon Sep 17 00:00:00 2001 From: "warp-agent-staging[bot]" <240773466+warp-agent-staging[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:25:12 +0000 Subject: [PATCH 3/3] Use current ARD and IANA media types for the MCP and OpenAPI entries --- public/.well-known/ard.json | 4 ++-- scripts/agent-discovery.test.mjs | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/public/.well-known/ard.json b/public/.well-known/ard.json index ef916ce4b..683a81dca 100644 --- a/public/.well-known/ard.json +++ b/public/.well-known/ard.json @@ -3,7 +3,7 @@ { "identifier": "urn:air:warp.dev:server:documentation", "displayName": "Warp Documentation MCP Server", - "type": "application/mcp-server+json", + "type": "application/mcp-server-card+json", "url": "https://docs.warp.dev/.well-known/mcp.json", "capabilities": ["Search Warp documentation", "Retrieve Warp documentation"], "description": "Search and retrieve content from Warp's official documentation", @@ -12,7 +12,7 @@ { "identifier": "urn:air:warp.dev:api:agent", "displayName": "Warp Agent API", - "type": "application/vnd.oai.openapi+json;version=3.2", + "type": "application/openapi+json;version=3.2", "url": "https://docs.warp.dev/openapi.json", "capabilities": ["Create cloud agent runs", "Inspect cloud agent runs"], "description": "OpenAPI description for programmatically creating, managing, and querying Warp cloud agent runs.", diff --git a/scripts/agent-discovery.test.mjs b/scripts/agent-discovery.test.mjs index e5733638f..cd679ab68 100644 --- a/scripts/agent-discovery.test.mjs +++ b/scripts/agent-discovery.test.mjs @@ -10,8 +10,8 @@ const catalog = await readJson('public/.well-known/ard.json'); const byId = (identifier) => catalog.entries.find((entry) => entry.identifier === identifier); test('ARD catalog entries are valid', () => { - assert.deepEqual(Object.keys(catalog), ['entries']); - assert.ok(catalog.entries.length > 0); + // ARD requires only `entries` at the root; other root members are allowed. + assert.ok(Array.isArray(catalog.entries) && catalog.entries.length > 0); const identifiers = catalog.entries.map((entry) => entry.identifier); assert.equal(new Set(identifiers).size, identifiers.length, 'Identifiers must be unique'); for (const entry of catalog.entries) { @@ -29,6 +29,8 @@ test('ARD catalog entries are valid', () => { test('MCP entry points to the served MCP manifest', async () => { const entry = byId('urn:air:warp.dev:server:documentation'); assert.equal(entry.url, `${DOCS_ORIGIN}/.well-known/mcp.json`); + // ARD v0.91 (ADR-0008) replaced application/mcp-server+json with the server-card type. + assert.equal(entry.type, 'application/mcp-server-card+json'); const manifest = await readJson('public/.well-known/mcp.json'); assert.match(manifest.url, /^https:\/\//); }); @@ -38,7 +40,8 @@ test('OpenAPI entry media type matches the spec version', async () => { assert.equal(entry.url, `${DOCS_ORIGIN}/openapi.json`); const spec = await readFile(repoFile('developers/agent-api-openapi.yaml'), 'utf8'); const [, major, minor] = spec.match(/^openapi:\s*['"]?(\d+)\.(\d+)/m); - assert.equal(entry.type, `application/vnd.oai.openapi+json;version=${major}.${minor}`); + // application/openapi+json is the registered type; application/vnd.oai.openapi+json is a deprecated alias. + assert.equal(entry.type, `application/openapi+json;version=${major}.${minor}`); }); test('LLM index entry points to the generated docs llms.txt', async () => {