Skip to content
BuildBase

Make your app agent ready

Serve the discovery files, sign-in and tools AI agents look for from one SDK config, and check your app the way agent readiness scanners do.

This page is part of Agent-ready apps: making the app you build with BuildBase ready for your own users' AI agents. An agent-ready app lets an AI agent find out what it does, sign a person in and call its tools, with nobody pasting a key. With @buildbase/sdk most of it is one config: createAgentStack serves your MCP server, the sign-in metadata and the discovery files from your own domain.

Note

Want to manage BuildBase itself from your AI? That is the other track, BuildBase in your AI tools: connect Claude, ChatGPT, Cursor or another AI tool to BuildBase's own MCP server, at BuildBase in your AI tools.

// lib/agent.ts
import { createAgentStack } from '@buildbase/sdk/mcp';

export const agent = createAgentStack({
  serverUrl: process.env.NEXT_PUBLIC_BUILDBASE_SERVER_URL!,
  orgId: process.env.NEXT_PUBLIC_BUILDBASE_ORG_ID!,
  siteUrl: 'https://app.example.com',
  site: {
    name: 'Acme',
    description: 'Render on-brand images from templates.',
    documentationUrl: 'https://docs.example.com',
  },
  secret: process.env.SYSTEM_SECRET!,
  mcp: {
    representativeQueries: ['render my Open Graph image', 'list my templates'],
  },
  discovery: {
    robots: { contentSignals: { search: true, aiInput: true, aiTrain: false } },
    llmsTxt: '# Acme\n\n> Render on-brand images from templates.\n',
    security: { contact: 'mailto:[email protected]' },
    sitemap: { urls: ['/', '/pricing', '/templates'] },
  },
});

Before you start

An MCP server set up as in MCP and agents, and Agent readiness turned on in the console (Set up agent access). Parts of this page need the next release of the SDK: mcp.representativeQueries, poppy.json, the A2A 1.0 card, discovery ETags, WebMCP through document.modelContext and Web Bot Auth signing.

Agent readiness is under User management > Access > Authentication. It is for your app's users, and it says so: to connect your own AI tool to BuildBase instead, its note points you to Security, in your avatar menu.

How AI agents connect to your app, at the top of the Agent readiness tab, with the note ringed: this makes your app agent-ready for your users; to manage BuildBase itself from your AI tools, see Account > Security

What agents look for

Scanners such as isitagentready.com read your site as an agent does. Each check, and what answers it:

CheckWhat serves itWhere you set it
robots.txt, AI bot rules, Content Signalsrobots.txtdiscovery.robots: aiBots (allowed by default) and contentSignals
Sitemapsitemap.xmldiscovery.sitemap.urls, or keep your own generator
Link headerLink on your pagesMiddleware, step 2
llms.txtllms.txtdiscovery.llmsTxt
Markdown for agentsAccept: text/markdown answered with MarkdownStep 3
MCP server card, AI catalog (ARD)<mcp-path>/server-card, /.well-known/ai-catalog.jsonDerived from mcp and site
OAuth discovery, protected resourceRFC 8414 and RFC 9728 metadataDerived; BuildBase runs the authorization server
auth.md/auth.md, and agent_auth on the authorization serverGenerated; turn on registration by email, step 4
Agent skills, API catalog/.well-known/agent-skills/, /.well-known/api-catalogdiscovery.skills; the API catalog lists your MCP server, and discovery.apiCatalog replaces that list
A2A agent card/.well-known/agent-card.jsonServed by default; discovery.a2aCard
Personal Agent Protocol/.well-known/poppy.jsonServed by default; discovery.personalAgent
WebMCPTools your page registersStep 5
Web Bot Auth, DNS-AIDYour own bot's keys; DNS recordsOptional, steps 6 and 7

1. Serve the files

One route answers every discovery file. In the Next.js App Router:

// app/.well-known/[...path]/route.ts
import { agent } from '@/lib/agent';

export const GET = agent.serveAgentPath;
export const OPTIONS = agent.serveAgentPath;

Put the same two lines in app/robots.txt/route.ts, app/llms.txt/route.ts, app/auth.md/route.ts, app/sitemap.xml/route.ts and app/security.txt/route.ts. Each answer carries CORS, a cache lifetime and an ETag, so an agent polling a file gets 304 when nothing changed. Outside Next.js, agent.resolvePath(path) returns the document or null; send its contentType, cacheControl, vary and every entry of headers.

2. Advertise them on every page

Agents read a Link header before anything else. buildDiscoveryLinkHeader is pure, so it runs in edge middleware:

// middleware.ts
import { NextResponse } from 'next/server';
import { buildDiscoveryLinkHeader } from '@buildbase/sdk';

const LINK = buildDiscoveryLinkHeader({
  serverUrl: process.env.NEXT_PUBLIC_BUILDBASE_SERVER_URL!,
  orgId: process.env.NEXT_PUBLIC_BUILDBASE_ORG_ID!,
  siteUrl: 'https://app.example.com',
  site: { name: 'Acme' },
});

export function middleware() {
  const res = NextResponse.next();
  res.headers.set('Link', LINK);
  return res;
}

Where a route can import the stack, agent.linkHeader is the same value with the API catalog, the MCP server card and the AI catalog added.

3. Answer agents in Markdown

An agent asking for your home page with Accept: text/markdown should get Markdown, at a fraction of the tokens. A server-rendered page picks the variant with negotiateMarkdown(accept, { html, markdown }), which sets Vary: Accept; wantsMarkdown(accept) answers the question alone, for a rewrite to a Markdown route of your own. A static export cannot negotiate: on Cloudflare, turn on Markdown for Agents for the zone.

4. Let agents register by a person's email

An agent that knows only someone's email address can ask for access in their name. In the console, open User management > Access > Authentication > Agent readiness, turn on Let agents register with a person's email and pick one of your clients of the Agent type with Device code on.

The agent posts the address to the registration endpoint and shows the person a code. They sign in on your sign-in page as that address, type the code and approve; the agent then gets a token through your application token URL, as for any agent. Only the person whose confirmed address it is can approve, and no account is created. Your authorization server's metadata gains an agent_auth block and your generated /auth.md points agents to it. There is nothing new to code. If you write /auth.md yourself (discovery.authMd), start it with an H1 naming auth.md, such as # Acme auth.md, or readers skip it.

5. Offer tools in the browser (WebMCP)

An agent in the user's browser can call actions your page registers. Register on page load and unregister on unmount:

'use client';

import { useEffect } from 'react';
import { provideWebMcpTools } from '@buildbase/sdk';

import { searchTemplates } from '@/lib/templates';

export function TemplateTools() {
  useEffect(() => {
    const controller = new AbortController();
    provideWebMcpTools(
      [
        {
          name: 'search_templates',
          description: 'Search Acme templates by keyword.',
          inputSchema: {
            type: 'object',
            properties: { query: { type: 'string' } },
            required: ['query'],
          },
          execute: ({ query }: { query: string }) => searchTemplates(query),
        },
      ],
      { signal: controller.signal }
    );
    return () => controller.abort();
  }, []);
  return null;
}

provideWebMcpTools uses document.modelContext, the API current browsers expose, and falls back to the older navigator.modelContext. In browsers without WebMCP it does nothing. Treat every input as untrusted, and register read-only tools unless an action is safe for any agent the user runs.

6. Sign your own bot's requests (optional)

Only for an app that itself calls other sites, such as fetching images by URL. Publish your keys and sign each request, and Cloudflare and others can tell your bot from an impersonator:

import { signWebBotAuthRequest } from '@buildbase/sdk';

const privateKey = JSON.parse(process.env.WEB_BOT_AUTH_PRIVATE_KEY!); // an Ed25519 JWK
// In createAgentStack: discovery: { webBotAuth: { signingKeys: [privateKey] } }

const headers = await signWebBotAuthRequest({
  url,
  signatureAgent: 'https://app.example.com',
  privateKey,
});
await fetch(url, { headers });

The directory at /.well-known/http-message-signatures-directory lists only the public half of each key and is signed with it, as Cloudflare requires.

7. Publish DNS records (optional)

DNS for AI Discovery cannot be served over HTTP. buildDnsAidRecords(agent.config) returns the _agents records for your DNS provider; publish them and sign the zone with DNSSEC.

Check it works

npx buildbase agent doctor --url https://app.example.com/api/mcp --site https://app.example.com

The doctor checks the MCP sign-in chain and every file above by the shape scanners require. Then scan your domain at isitagentready.com.

The scanner saysWhat to do
1 interface missing required "url"Upgrade the SDK: earlier releases wrote the A2A card's interface in a shape no version of A2A defines
entry 0 is missing displayNameUpgrade the SDK, so the AI catalog entry has a display name
missing the expected Auth.md headingYour own authMd needs an H1 naming auth.md
agent_auth metadata was not foundStep 4
Site does not support Markdown for AgentsStep 3
DNS-AID ... records not foundStep 7

The commerce checks (x402, MPP, UCP, ACP, AP2) appear for sites with prices and are left out of the score.

Watch out for

  • A static export serves only the files it writes. A catch-all route needs every path listed in generateStaticParams, or the file is a 404 on your host.
  • The A2A card names siteUrl as its endpoint unless you set discovery.a2aCard.serviceUrl to the A2A server you run.
  • Discovery files are public. Never put a secret, an internal URL or a user's data in llmsTxt, a skill or extraPaths.

Next steps