feat(www): publish agent discovery catalog and complete json-ld (#49768)

I added the agent-discovery surfaces the www app was missing: a resource
catalog at `/.well-known/ard.json` plus completed structured data on the
homepage. I scoped this from the agent-readiness gaps that are
truthfully closable on the www side; the catalog lists only resources
that already exist and serve 200 (MCP OAuth metadata, Management API
OpenAPI spec, llms.txt, agent-skills index).

**Changed:**
- **Agents can discover our machine-readable resources from one
document**: new static catalog at `/.well-known/ard.json` (Agentic
Resource Discovery format); the legacy `/.well-known/ai-catalog.json`
path serves the same file via rewrite, keeping a single source artifact.
- **Organization JSON-LD carries verifiable company details**: adds
`legalName`, a support `contactPoint`, and the registered address
already public on our Terms of Service.
- **Homepage declares the product as an application entity**: emits
`SoftwareApplication` JSON-LD via the existing
`softwareApplicationSchema` builder, same pattern as the vector module
page.

## To test
Tested on Vercel preview:
- [ ] `curl <preview-url>/.well-known/ard.json`: expect 200 with a JSON
catalog of 5 entries
- [ ] `curl <preview-url>/.well-known/ai-catalog.json`: expect the same
document with status 200 (rewrite, not a redirect)
- [ ] View homepage page source: expect three `application/ld+json`
scripts: Organization now includes `address` and `contactPoint`, and a
`SoftwareApplication` block is present

## Linear
- fixes GROWTH-1164



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added an Agent Resource Description catalog listing Supabase’s MCP,
API, documentation, and agent skill resources.
- Added support for the legacy AI Catalog URL through a canonical
redirect.
- Enhanced website structured data with software application details,
legal information, support contact details, and business address.

- **Tests**
- Added validation ensuring discoverable `.well-known` resources are
cataloged and resolve correctly.

- **Chores**
- Updated marketing site test coverage for `.well-known` resource
changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Pamela Chia authored and GitHub committed 2026-09-01 21:07:46 +08:00
1 parent 0c8dc73bf4
commit 3dddb60149
6 files changed
+191 -1

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ on:
- 'apps/www/lib/**/*.js'
- 'apps/www/content/md/**'
- 'apps/www/scripts/**/*.mjs'
- 'apps/www/public/.well-known/**'
# Cancel old builds on new commit for same workflow + branch/PR
concurrency:
+20 -1
View File
@@ -2,7 +2,13 @@ import type { Metadata } from 'next'
import { FrameworksSection } from './_components/FrameworksSection'
import { HomeContent } from './_components/HomeContent'
import { organizationSchema, serializeJsonLd, websiteSchema } from '@/lib/json-ld'
import { DEFAULT_META_DESCRIPTION } from '@/lib/constants'
import {
organizationSchema,
serializeJsonLd,
softwareApplicationSchema,
websiteSchema,
} from '@/lib/json-ld'
import { mdAlternates } from '@/lib/md-alternates'
export const metadata: Metadata = {
@@ -23,6 +29,19 @@ export default function HomePage() {
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: serializeJsonLd(websiteSchema()) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: serializeJsonLd(
softwareApplicationSchema({
name: 'Supabase',
description: DEFAULT_META_DESCRIPTION,
url: 'https://supabase.com',
image: 'https://supabase.com/images/og/supabase-og.png',
})
),
}}
/>
<HomeContent frameworksSlot={<FrameworksSection />} />
</>
)
+80
View File
@@ -0,0 +1,80 @@
import { existsSync, promises as fs } from 'node:fs'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import rewrites from './lib/rewrites'
const CANONICAL_ORIGIN = 'https://supabase.com'
const NOT_AGENT_RESOURCES: Record<string, string> = {
'ard.json': 'the catalog itself',
'api-catalog':
'peer catalog (RFC 9727); its resource, the Management API spec, has its own entry',
'ai-catalog.json': 'legacy ARD alias, rewritten to ard.json',
'mcp-registry-auth': 'domain-ownership verification token',
'openai-apps-challenge': 'domain-ownership verification token',
'security.txt': 'vulnerability disclosure contact',
vercel: 'Vercel toolbar dev tooling',
}
type ArdEntry = { identifier: string; url: string }
async function loadArdCatalog(): Promise<{ entries: ArdEntry[] }> {
const raw = await fs.readFile(
path.join(process.cwd(), 'public', '.well-known', 'ard.json'),
'utf-8'
)
return JSON.parse(raw)
}
function wellKnownRewriteSources(): string[] {
return rewrites
.map((rewrite: { source: string }) => rewrite.source)
.filter((source: string) => source.startsWith('/.well-known/'))
.map((source: string) => source.replace('/.well-known/', ''))
}
describe('agent discovery catalog (.well-known/ard.json)', () => {
it('every .well-known surface is cataloged or explicitly marked as not an agent resource', async () => {
const { entries } = await loadArdCatalog()
const catalogedUrls = entries.map((entry) => entry.url)
const wellKnownDir = path.join(process.cwd(), 'public', '.well-known')
const dirents = await fs.readdir(wellKnownDir, { withFileTypes: true })
const surfaces = [...dirents.map((dirent) => dirent.name), ...wellKnownRewriteSources()]
expect(surfaces.length).toBeGreaterThan(0)
for (const surface of surfaces) {
if (surface in NOT_AGENT_RESOURCES) continue
const cataloged = catalogedUrls.some((url) =>
url.startsWith(`${CANONICAL_ORIGIN}/.well-known/${surface}`)
)
expect(
cataloged,
`new .well-known surface "${surface}": add an entry for it to public/.well-known/ard.json, or add it to NOT_AGENT_RESOURCES in this test with the reason it doesn't belong in the catalog`
).toBe(true)
}
})
it('every same-origin catalog entry resolves to a public file, app route, or rewrite', async () => {
const { entries } = await loadArdCatalog()
expect(entries.length).toBeGreaterThan(0)
const rewriteSources = rewrites.map((rewrite: { source: string }) => rewrite.source)
for (const entry of entries) {
const url = new URL(entry.url)
if (url.origin !== CANONICAL_ORIGIN) continue
const publicFile = path.join(process.cwd(), 'public', url.pathname)
const appRoute = path.join(process.cwd(), 'app', url.pathname, 'route.ts')
const resolves =
existsSync(publicFile) || existsSync(appRoute) || rewriteSources.includes(url.pathname)
expect(
resolves,
`ard.json entry "${entry.identifier}" points at ${entry.url}, but ${url.pathname} is not a file in public/, an app route, or a rewrite source — the catalog is advertising a dead URL`
).toBe(true)
}
})
})
+13
View File
@@ -41,6 +41,19 @@ export function organizationSchema(input: OrganizationSchemaInput = {}) {
url: ORG_LOGO_URL,
},
description: input.description ?? DEFAULT_META_DESCRIPTION,
legalName: 'Supabase Pte. Ltd.',
contactPoint: {
'@type': 'ContactPoint',
contactType: 'customer support',
url: `${CANONICAL_ORIGIN}/support`,
},
address: {
'@type': 'PostalAddress',
streetAddress: '65 Chulia Street #38-02/03, OCBC Centre',
addressLocality: 'Singapore',
postalCode: '049513',
addressCountry: 'SG',
},
sameAs: ORG_SAMEAS,
}
}
+5
View File
@@ -75,6 +75,11 @@ const rewrites = [
source: '/.well-known/security.txt',
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}/.well-known/security.txt`,
},
{
// legacy AI Catalog path; /.well-known/ard.json is the canonical source
source: '/.well-known/ai-catalog.json',
destination: '/.well-known/ard.json',
},
{
source: '/openapi.json',
destination: 'https://api.supabase.com/api/v1-json',
+72
View File
@@ -0,0 +1,72 @@
{
"specVersion": "1.0",
"host": {
"displayName": "Supabase",
"identifier": "https://supabase.com",
"documentationUrl": "https://supabase.com/docs"
},
"entries": [
{
"identifier": "urn:air:supabase.com:mcp:server",
"displayName": "Supabase MCP server",
"type": "application/json",
"url": "https://mcp.supabase.com/mcp",
"description": "The Supabase MCP server endpoint (streamable HTTP, OAuth-protected) for managing projects, database schema, and queries from MCP clients.",
"tags": ["mcp", "server", "agents"],
"representativeQueries": [
"connect my agent to Supabase over MCP",
"run a query against my Supabase project from an MCP client",
"manage my Supabase project from my coding agent"
]
},
{
"identifier": "urn:air:supabase.com:metadata:mcp-oauth",
"displayName": "Supabase MCP OAuth metadata",
"type": "application/json",
"url": "https://api.supabase.com/.well-known/oauth-protected-resource/mcp",
"description": "OAuth protected-resource metadata for connecting clients to the Supabase MCP server.",
"tags": ["mcp", "oauth", "authorization", "metadata"],
"representativeQueries": [
"authenticate an MCP client to the Supabase MCP server with OAuth",
"what OAuth scopes does the Supabase MCP server support"
]
},
{
"identifier": "urn:air:supabase.com:api:management",
"displayName": "Supabase Management API",
"type": "application/openapi+json",
"url": "https://api.supabase.com/api/v1-json",
"description": "OpenAPI 3.0 description of the Supabase Management API for managing organizations, projects, branches, and configuration.",
"tags": ["api", "openapi", "projects", "management"],
"representativeQueries": [
"create a new Supabase project",
"list the branches of my Supabase project",
"update the Postgres config of a Supabase project"
]
},
{
"identifier": "urn:air:supabase.com:docs:llms-txt",
"displayName": "Supabase llms.txt",
"type": "text/plain",
"url": "https://supabase.com/llms.txt",
"description": "Index of Supabase documentation and content available as agent-readable markdown.",
"tags": ["documentation", "llms", "markdown"],
"representativeQueries": [
"how do I set up Supabase auth in Next.js",
"Supabase Row Level Security documentation"
]
},
{
"identifier": "urn:air:supabase.com:skills:index",
"displayName": "Supabase Agent Skills",
"type": "application/json",
"url": "https://supabase.com/.well-known/agent-skills/index.json",
"description": "Agent Skills discovery index for installable Supabase and Postgres skills.",
"tags": ["skills", "agents", "postgres"],
"representativeQueries": [
"write RLS policies for my Supabase tables",
"create a Postgres migration for my Supabase project"
]
}
]
}