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
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ npx ax init

It runs the same source-tree detection a build does (no `next build` needed), prints what it found,
then asks **only what the code can't answer** — your production `siteUrl`, which detected surfaces
agents can use without signing in (the rest are gated and never advertised as open), and one
agents can use without signing in (the rest are gated and never advertised as open), how agents
authenticate to the gated ones (API key/bearer with a docs URL — the realistic common case — or
OAuth 2.0; written as a declared `auth` entry in `ax.config`. Endpoint questions only appear when
the source tree has no OAuth to read: committed authorization-server metadata is adopted verbatim,
and a wired RFC 9728 protected-resource route means agents discover the endpoints at runtime, so
nothing is asked), and one
pre-selected checklist of every opt-in scaffold, each line stating why agents need it — deselect
anything you don't want, then press Enter. It writes an `ax.config.ts` (or `.js`,
matching your project) with a one-line comment on every field, so the config it commits doubles as
Expand Down Expand Up @@ -235,7 +240,28 @@ const config: AxConfig = {
// Hand-declared entries — e.g. docs/skills pointers zero-config detection can't guess at. An
// `identifier` matching a detected entry overrides/extends it field-by-field (never replaces it
// outright); anything else is appended as a new entry.
entries: [{ identifier: 'urn:example:docs', type: 'text/html', url: 'https://example.com/docs' }],
entries: [
{ identifier: 'urn:example:docs', type: 'text/html', url: 'https://example.com/docs' },
// An entry's `auth` declares how agents authenticate when ax can't derive it — the endpoints
// detection can never see (a withMcpAuth-wrapped MCP mount is only ever detectable as
// "requires auth, scheme unknown"). Exactly the secret-free EntryAuth shape registries read:
// status, OAuth endpoint URLs, scope keys, and a human docs URL — never credentials. Declared
// once, it flows to the catalog entry, the MCP server card, and the generated /auth.md, and —
// like a detected scheme — marks the surface gated. URL fields must be absolute http(s); the
// config gate rejects anything else loudly, and a declared status that contradicts a detected
// one wins with a warning.
{
identifier: 'urn:air:example.com:mcp-server',
auth: {
status: 'oauth2',
oauth: {
authorizationEndpoint: 'https://auth.example.com/authorize',
tokenEndpoint: 'https://auth.example.com/token',
},
docsUrl: 'https://example.com/docs/auth',
},
},
],
};

export default config;
Expand Down Expand Up @@ -416,6 +442,10 @@ declared), and where a human obtains credentials (`auth.docsUrl` when declared;
point at it — the CLI prints that recommendation (`WWW-Authenticate` + a `Link` to `/auth.md`);
ax never rewrites your handlers.

When detection can only say "requires auth, scheme unknown" (a `withMcpAuth`-wrapped MCP mount),
declare the real endpoints on that entry in `ax.config` `entries` (see the `auth` example above) —
auth.md, the server card, and the catalog entry all pick the declaration up from that one place.

### The serving manifest — `ax manifest` and the `prebuild` slot

`ax manifest` regenerates a data module (`ax-manifest.ts`/`.js`, beside where `middleware.ts`
Expand Down
9 changes: 6 additions & 3 deletions fixtures/bare/report.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
"found": false
}
},
"auth": {
"gatedSurfaces": []
},
"scaffolds": {},
"markdownTwins": {
"enabled": true,
Expand Down Expand Up @@ -139,13 +142,13 @@
"id": "markdown-negotiation",
"artifact": "middleware",
"status": "actionable",
"note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware."
"note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware, and keeps your own gating (UA filters, bot walls) from ever blocking the published discovery artifacts."
},
{
"id": "markdown-negotiation-vary",
"artifact": "middleware",
"status": "actionable",
"note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware."
"note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware, and keeps your own gating (UA filters, bot walls) from ever blocking the published discovery artifacts."
}
]
},
Expand All @@ -158,6 +161,6 @@
"No agents.md found — consider adding one (public/agents.md) with when-to-use / when-NOT-to-use guidance for agents. ax won't write its content for you; the companion skill can help author it from your repo.",
"No JSON-LD structured data found in your layouts/pages — structured data is how registries and agents disambiguate and rank your site. Add an Organization block in your root layout <head> (<script type=\"application/ld+json\">) with a \"sameAs\" array linking your LinkedIn/GitHub/npm/social profiles (the entity-disambiguation signal registries value), and at least one more schema.org @type beyond Organization — SoftwareApplication or Product for an app/API, or a FAQPage — since covering more types helps registries understand your site more fully. JSON-LD pairs with an llms.txt: llms.txt tells agents what your site is for, JSON-LD identifies it as an entity registries can rank — add both, not one alone. ax won’t author the block (the fields are judgment content); the companion skill can help draft it from your repo.",
"No app/not-found.tsx found — agents that hit a missing URL get Next.js’s bare default 404, a dead end that makes them give up or guess. Add one that tells agents why the 404 happened and how to continue (links to llms.txt, the ai-catalog, and your real routes), or set scaffoldAgent404: true in ax.config to have an agent-aware page (plus a build-time route manifest) written for you.",
"Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware."
"Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware, and keeps your own gating (UA filters, bot walls) from ever blocking the published discovery artifacts."
]
}
9 changes: 6 additions & 3 deletions fixtures/config-overrides/report.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
"found": false
}
},
"auth": {
"gatedSurfaces": []
},
"scaffolds": {},
"markdownTwins": {
"enabled": true,
Expand Down Expand Up @@ -158,13 +161,13 @@
"id": "markdown-negotiation",
"artifact": "middleware",
"status": "actionable",
"note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware."
"note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware, and keeps your own gating (UA filters, bot walls) from ever blocking the published discovery artifacts."
},
{
"id": "markdown-negotiation-vary",
"artifact": "middleware",
"status": "actionable",
"note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware."
"note": "Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware, and keeps your own gating (UA filters, bot walls) from ever blocking the published discovery artifacts."
}
]
},
Expand All @@ -179,6 +182,6 @@
"No agents.md found — consider adding one (public/agents.md) with when-to-use / when-NOT-to-use guidance for agents. ax won't write its content for you; the companion skill can help author it from your repo.",
"No JSON-LD structured data found in your layouts/pages — structured data is how registries and agents disambiguate and rank your site. Add an Organization block in your root layout <head> (<script type=\"application/ld+json\">) with a \"sameAs\" array linking your LinkedIn/GitHub/npm/social profiles (the entity-disambiguation signal registries value), and at least one more schema.org @type beyond Organization — SoftwareApplication or Product for an app/API, or a FAQPage — since covering more types helps registries understand your site more fully. JSON-LD pairs with an llms.txt: llms.txt tells agents what your site is for, JSON-LD identifies it as an entity registries can rank — add both, not one alone. ax won’t author the block (the fields are judgment content); the companion skill can help draft it from your repo.",
"No app/not-found.tsx found — agents that hit a missing URL get Next.js’s bare default 404, a dead end that makes them give up or guess. Add one that tells agents why the 404 happened and how to continue (links to llms.txt, the ai-catalog, and your real routes), or set scaffoldAgent404: true in ax.config to have an agent-aware page (plus a build-time route manifest) written for you.",
"Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware."
"Serve agents the generated markdown automatically: First run `npx ax manifest` (and wire it as the \"prebuild\" script so it stays fresh — `middleware.ts` is compiled during `next build`, so the manifest must exist before the build starts). Then: add `import { withAx } from '@ora-ai/ax/middleware';` and `import { axManifest } from './ax-manifest';`, create middleware.ts with `export default withAx({ manifest: axManifest });`, and export the matcher literal `export const config = { matcher: ['/((?!_next|api|.*\\\\..*|favicon|robots|health|status).*)'] };`. The middleware rewrites a request to its markdown twin only when the manifest lists one, never touches gated paths, and answers unknown URLs from detected agents with a 200 markdown wayfinding body — it composes with (never replaces) your existing middleware, and keeps your own gating (UA filters, bot walls) from ever blocking the published discovery artifacts."
]
}
Loading
Loading