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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
30 changes: 30 additions & 0 deletions public/.well-known/ard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"entries": [
{
"identifier": "urn:air:warp.dev:server:documentation",
"displayName": "Warp Documentation MCP Server",
"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",
"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/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"]
}
]
}
2 changes: 1 addition & 1 deletion public/.well-known/mcp.json
Original file line number Diff line number Diff line change
@@ -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"
}
59 changes: 59 additions & 0 deletions scripts/agent-discovery.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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', () => {
// 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) {
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`);
// 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:\/\//);
});

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);
// 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 () => {
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('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);
});
Loading