ardkit-ai
v0.1.0
Published
Plug-and-play ARD (Agentic Resource Discovery) publisher & registry middleware for Next.js and MCP/A2A backends.
Downloads
52
Maintainers
Readme
ardkit (TypeScript)
Plug-and-play ARD (Agentic Resource Discovery) publisher & registry middleware for Next.js and MCP / A2A backends.
ARD lets AI clients discover your agents, MCP servers and skills before they
invoke them — instead of every tool description being crammed into the model's
context window. ardkit turns any Next.js App-Router backend into an ARD
publisher (and, optionally, a search registry) in a few lines.
npm install ardkit-ai # or pnpm add ardkit-ai / yarn add ardkit-aiThe npm package is
ardkit-ai(the bare nameardkitwas already taken); import fromardkit-ai(andardkit-ai/next,ardkit-ai/adapters).
Quickstart — publish
// lib/catalog.ts
import { Catalog } from "ardkit-ai";
export const catalog = new Catalog({
host: "Acme AI",
publisher: "acme.com",
identifier: "did:web:acme.com",
});
catalog.addMcpServer({
name: "Billing",
url: "https://acme.com/mcp",
capabilities: ["create_invoice", "list_invoices"],
representativeQueries: ["create an invoice", "show unpaid invoices"],
});Define your handlers once with createArd, then each App-Router route file is a
single re-export (App Router needs the files; no CLI, no codegen):
// lib/ard.ts
import { createArd } from "ardkit-ai/next";
import { catalog } from "@/lib/catalog";
export const ard = createArd({ catalog /*, registry */ });// app/.well-known/ai-catalog.json/route.ts
import { ard } from "@/lib/ard";
export const { GET } = ard.wellKnown;// app/robots.txt/route.ts
import { ard } from "@/lib/ard";
export const { GET } = ard.robots; // emits an `Agentmap:` directive// app/api/ard/[...ard]/route.ts (only if you pass a registry)
import { ard } from "@/lib/ard";
export const { GET, POST } = ard.registry;The manifest is served with Content-Type: application/json and
Access-Control-Allow-Origin: * so crawlers can fetch it cross-origin. Prefer
the individual helpers (ardCatalogRoute, robotsRoute, createRegistryCatchAll)?
They're exported too.
Validate a manifest any time from code:
import { validateManifest } from "ardkit-ai";
validateManifest(catalog.toManifest()); // [] when it conforms to ai-catalog v1.0Auto-derive entries from your frameworks
import { fromMcpServer, fromA2aCard } from "ardkit-ai/adapters";
catalog.add(
fromMcpServer({
name: "Billing",
url: "https://acme.com/mcp",
publisher: "acme.com",
tools: ["create_invoice", "list_invoices"], // -> capabilities
}),
);
catalog.add(fromA2aCard(myAgentCard, { url: "https://acme.com/a2a/agent", publisher: "acme.com" }));Apex discovery for a backend on another host
When the catalog's source of truth lives on a backend (api.example.com) but
discovery must answer on the apex (example.com), use the content-origin /
edge-publisher split: the backend serves the catalog document; the apex app
publishes /.well-known/ai-catalog.json by consuming it with remoteCatalog —
edge-cached via Next.js revalidate, with a timeout and fallback so apex
discovery never hard-depends on backend uptime.
// apex app: app/.well-known/ai-catalog.json/route.ts
import { ardCatalogRoute, remoteCatalog } from "ardkit-ai/next";
import { staticManifest } from "@/lib/fallback";
export const revalidate = 300; // route-level ISR
export const GET = ardCatalogRoute(
remoteCatalog("https://api.example.com/api/ard/ai-catalog.json", {
fallback: staticManifest,
}),
);ardCatalogRoute adds a weak ETag + Cache-Control and answers If-None-Match
with 304. Entry urls still point at the backend (api.example.com/...); only
the manifest's hosting host and identity are the apex.
Optional: be a registry (bring your own search)
ardkit-ai owns the ARD wire contract (request parsing, filters, pagination,
federation, error codes, response shaping). You own ranking by implementing
SearchProvider — or use the bundled InMemorySearchProvider.
// app/api/ard/[...ard]/route.ts
import { createRegistryCatchAll } from "ardkit-ai/next";
import { RegistryService, InMemorySearchProvider } from "ardkit-ai";
import { catalog } from "@/lib/catalog";
const service = new RegistryService(
new InMemorySearchProvider(catalog.entries), // swap for your engine
{ source: "https://acme.com/api/ard/" },
);
export const { GET, POST } = createRegistryCatchAll(service);
// -> POST /api/ard/search, POST /api/ard/explore, GET /api/ard/agentsYour own backend just satisfies the interface:
import type { SearchProvider } from "ardkit-ai";
const provider: SearchProvider = {
async search(query, { pageSize, pageToken }) {
const hits = await myVectorDb.query(query.text, { filter: query.filter, k: pageSize });
return { items: hits.map((h) => ({ entry: h.card, score: h.score })) };
},
};explore and listAgents are optional — omit them and those endpoints return
HTTP 501 as the spec allows.
Entry points
| Import | Contents |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| ardkit-ai | Catalog, schema + types, discovery helpers, validation, registry (RegistryService, InMemorySearchProvider, SearchProvider), errors |
| ardkit-ai/next | ardCatalogRoute, robotsRoute, withAgentmap, createRegistryRoutes, createRegistryCatchAll |
| ardkit-ai/adapters | fromMcpServer, fromMcpHandler, fromA2aAgent, fromA2aCard |
Ships ESM + CJS + types; the ardkit-ai/next handlers use the Web Request/Response
API, so there's no hard dependency on next and they run on the Edge runtime.
Development
pnpm install
pnpm test
pnpm typecheck
pnpm lint && pnpm format:check
pnpm buildLicense
Apache-2.0 — matching the ARD specification. See LICENSE.
