# Building an Agility Site with AI Coding Tools

> Source: https://agilitycms.com/docs/developers/building-sites-with-ai-coding-tools

You can build and extend an Agility CMS website with an AI coding tool and get code you would actually ship: real components, wired to real content models, cached correctly and previewable by your editors.

This article is part of the [Vibe Coding with Agility](/docs/developers/vibe-coding-with-agility) section.

The trick is the same one that works for [building Agility apps with AI coding tools](/docs/apps/building-apps-with-ai-coding-tools): **give the agent the right context.** For a website that means three things:

1. **A good starting point.** An Agility starter, so the agent extends proven patterns instead of inventing its own.
2. **An `AGENTS.md`** that tells the agent the Agility-specific conventions it can't discover by reading your code.
3. **Two MCP servers.** The [Agility CMS MCP server](/docs/developers/agility-cms-mcp-server) lets the agent read and create content models in your instance. The [Agility Knowledgebase MCP server](/docs/overview/agility-knowledgebase-mcp-server) lets it look things up in these docs instead of guessing.

This guide uses Claude Code in its examples, but the approach works the same in Cursor, GitHub Copilot, Codex, Windsurf, or any tool that reads a project instruction file and supports MCP.

## What You're Building

The flagship starting point is the [Agility Next.js Starter](https://github.com/agility/agilitycms-nextjs-starter), a Next.js 16 App Router site built on React Server Components. Before you hand it to an agent, it helps to know the moving parts (the full tour is in [How the Next.js Starter Works](/docs/nextjs/how-the-next-js-starter-works)):

- **One catch-all route**, `app/[...slug]/page.tsx`, resolves every URL against the Agility sitemap. Editors decide which pages exist.
- **Page models** map to page templates in `components/agility-pages/`, which render named zones with `<ContentZone>`.
- **Component models** map to React components in `components/agility-components/`, looked up by name through a registry.
- **Every CMS read** goes through a thin wrapper in `lib/cms/` (`getContentItem`, `getContentList`, `getSitemapFlat`, `getSitemapNested`) that tags the request with a Next.js fetch cache tag and a 60-second `revalidate`. App-specific shaping lives in `lib/cms-content/`.
- **Publishing** fires an Agility webhook at `/api/revalidate`, which calls `revalidateTag()` for the tags that changed.
- **Preview** uses Next.js `draftMode()`. In draft mode, and always under `npm run dev`, the site requests content with the preview API key, so editors see their latest saved drafts.

The starter is also already set up for AI tools. It ships its own `AGENTS.md`, a `.cursorrules` file, a `docs/` folder written for AI assistants, and a `.vscode/mcp.json` that connects VS Code to the Agility CMS MCP server. You'll build on all of that.

## Step 1: Start from the Starter

Clone the starter and install dependencies:

```bash
git clone https://github.com/agility/agilitycms-nextjs-starter my-site
cd my-site
npm install
cp .env.local.example .env.local
```

Fill in `.env.local` with your instance details. You'll find the GUID and API keys in Agility under **Settings** → **API Keys** (see [API Key Management](/docs/training-guide/admin-api-keys)):

```bash
AGILITY_GUID=
AGILITY_API_FETCH_KEY=
AGILITY_API_PREVIEW_KEY=
AGILITY_SECURITY_KEY=
AGILITY_LOCALES=en-us
AGILITY_SITEMAP=website
```

Run `npm run dev` and confirm the site loads at `http://localhost:3000` before you bring in an agent. If the starter doesn't run cleanly, the agent will spend its first session debugging your environment instead of building.

## Step 2: Extend the Starter's AGENTS.md

Every major AI coding tool reads a project instruction file at the start of a session. [AGENTS.md](https://agents.md/) is the open standard most of them support. Claude Code reads `CLAUDE.md`, so either symlink one to the other (`ln -s AGENTS.md CLAUDE.md`) or keep a short `CLAUDE.md` that points at `AGENTS.md`.

**Start from the `AGENTS.md` that ships with the starter.** It already covers the project layout, the three-tier data pattern (component, then `lib/cms-content/`, then `lib/cms/`, then the SDK), the rule that every new component must be registered in `components/agility-components/index.ts`, how to add a component, domain helper or page template, how to test preview, the cache tags, and how to use the Agility CMS MCP server to check models before writing code. Read it, keep it, and correct anything that no longer matches your project as it evolves.

Then add what's specific to how **you** work with agents. The value of an instruction file comes from things the agent **can't figure out on its own**. It can read `package.json` and see you use Next.js. It can't know that your cache tags are a contract with a webhook, that a list call returns only 10 items unless you ask for more, or which publishing level your team has chosen for it: manual, via approval, or autonomous.

Here are the sections we recommend adding:

```markdown
## Working with the Agility MCP
- save_content_items saves to Staging. Nothing is live until it's published.
- Publishing: [manual | via approval | autonomous]. Keep the one your team chose:
  - manual: never publish. Save to Staging and list what I should review.
  - via approval: save, then request approval (manage_content_workflow / manage_page_workflow).
  - autonomous: publish (publish_content / publish_page) once your checks pass, and list what you published.
- Never unpublish or delete content, pages, or media without asking me.
- Before creating a model, list existing ones (get_content_models,
  get_component_models) and reuse when one fits. After creating one, read it
  back (get_content_model_details / get_component_model_details) and use the
  real field names.
- For Agility API or SDK questions, search the docs with the Agility
  Knowledgebase MCP (search_docs, fetch_doc) before guessing.

## List limits
- getContentList (Fetch SDK) returns 10 items by default and at most 250 per
  request. Always pass take explicitly, and page with skip until you've read
  totalCount.

## Reference names
- When writing to Agility (saving content, linked-content fields), use
  container and model reference names in the exact case get_containers and
  get_content_models return. Reads return reference names lowercased.
- The component registry in components/agility-components/index.ts matches
  the component model's reference name case-insensitively.
- Field values arrive with a lowercase first letter (a field named Title is
  fields.title).

## Cache tags are a contract with /api/revalidate
- Every read goes through lib/cms/. Each wrapper sets
  agilitySDK.config.fetchConfig = { next: { tags: [...], revalidate: 60 } }.
  A new wrapper must do the same.
- Tags used by the wrappers:
  agility-content-{contentID}-{locale}       (getContentItem)
  agility-content-{referenceName}-{locale}   (getContentList)
  agility-sitemap-flat-{locale}              (getSitemapFlat)
  agility-sitemap-nested-{locale}            (getSitemapNested)
- app/api/revalidate/route.ts handles Published events only. For content it
  revalidates the contentID and referenceName tags; for pages it revalidates
  agility-page-{pageID}-{locale} and both sitemap tags.
- Tags are case-sensitive strings. The referenceName in a list tag must match
  the referenceName the webhook sends, character for character. If you change
  a tag format, change it in the wrapper and in the webhook route together.
```

### Why each section is there

- **The MCP rules** make publishing an explicit team setting (manual review, approval, or autonomous publishing after checks), so the agent never has to guess, and stop it creating near-duplicate models or guessing field names.
- **List limits** catch a bug that looks fine with a handful of sample items and silently drops content once a container grows past the default page size.
- **Reference names** prevent a confusing editor bug: a User Selectable linked-content field saved with the wrong case stores the right values but shows a blank dropdown in the editor.
- **The tag contract** is the part most likely to go wrong silently. A component that calls the SDK directly, or a wrapper whose tag doesn't match what the webhook revalidates, works fine in development and then only refreshes when the 60-second revalidate window expires, instead of on publish.

Write these additions yourself, from what you know about the project. A generated instruction file tends to restate what the agent could already read in the code, which costs tokens without adding context.

## Step 3: Connect the Two MCP Servers

The two servers do different jobs, and you want both.

| Server | URL | Auth | What the agent uses it for |
| --- | --- | --- | --- |
| Agility CMS MCP | `https://mcp.agilitycms.com/api/mcp` | OAuth (your Agility login) | Reading and creating content models, component models, containers, content, and pages in your instance |
| Agility Knowledgebase MCP | `https://docs.agilitycms.com/docs/api/mcp` | None (read-only, public docs) | Searching and reading the Agility documentation (`search_docs`, `fetch_doc`) |

### Claude Code

Add both from your project folder:

```bash
claude mcp add --transport http "Agility-CMS" https://mcp.agilitycms.com/api/mcp
claude mcp add agility-knowledgebase --transport http https://docs.agilitycms.com/docs/api/mcp
```

Or commit a `.mcp.json` in the project root for the Knowledgebase server so everyone on the team gets it:

```json
{
  "mcpServers": {
    "agility-knowledgebase": {
      "url": "https://docs.agilitycms.com/docs/api/mcp"
    }
  }
}
```

The first time the agent calls the Agility CMS MCP, a browser window opens so you can sign in to Agility. The server acts with your permissions, so it can only see and change what your account can.

### VS Code (GitHub Copilot), Cursor, and others

- **Agility CMS MCP:** in VS Code, the starter's `.vscode/mcp.json` already points at the server. For other editors, use the one-click install buttons at [mcp.agilitycms.com/instructions](https://mcp.agilitycms.com/instructions) or follow the per-client steps in [Agility CMS MCP Server](/docs/developers/agility-cms-mcp-server#installation).
- **Knowledgebase MCP:** in Cursor, add it to `.cursor/mcp.json` with the same `url` block shown above. Setup for other clients is in [Agility Knowledgebase MCP Server](/docs/overview/agility-knowledgebase-mcp-server#setup).

Check that both are connected before you start (in Claude Code, run `/mcp`). A quick sanity prompt: *"List the component models in my Agility instance, then search the Agility docs for how ContentZone works."* If the agent answers both from tool calls, you're ready.

## Step 4: Model a New Component End to End

This is where the setup pays off. Ask for one complete feature: the model in Agility, the React component, and the registration, in a single focused session.

Here's an example prompt for a testimonials block:

```
Add a "Testimonials" component to the site.

1. Check the Agility docs (Knowledgebase MCP) for component model and
   linked content guidance.
2. Check my instance for existing models. Reuse a Testimonial content model
   if one exists; otherwise create one with: Quote (multi-line text),
   Name (text), Role (text), Photo (image).
3. Create a "Testimonials" component model with a Heading (text) and a
   linked content list of Testimonial items.
4. Build components/agility-components/Testimonials.tsx as a Server
   Component following the starter's patterns, using AgilityPic for the
   photo, and register it.
5. Add 3 sample testimonials to the container. Do not publish anything.
```

A good run looks like this:

1. The agent calls `search_docs` to ground itself, then `get_content_models` and `get_component_models` to see what already exists.
2. It creates the models with `save_content_model` and `save_component_model`, then reads them back with `get_component_model_details` to confirm the field names.
3. It writes the component following the starter's pattern. The starter loads pages with `contentLinkDepth: 0`, so a component receives only `module.contentid` and fetches its own fields through `getContentItem` (the same way the starter's `Heading` component does). The linked list is then read through `getContentList`, which returns the SDK's list response with an `items` array:

```tsx
// components/agility-components/Testimonials.tsx
import { UnloadedModuleProps, AgilityPic, ImageField, ContentItem } from "@agility/nextjs"
import { getContentItem } from "lib/cms/getContentItem"
import { getContentList } from "lib/cms/getContentList"

interface ITestimonial {
  quote: string
  name: string
  role?: string
  photo?: ImageField
}

interface ITestimonials {
  heading: string
  testimonials: { referencename: string }
}

export default async function Testimonials({ module, languageCode }: UnloadedModuleProps) {
  // The component's own fields (tagged agility-content-{contentID}-{locale}).
  const { fields, contentID } = await getContentItem<ITestimonials>({
    contentID: module.contentid,
    languageCode,
  })

  // The linked list (tagged agility-content-{referenceName}-{locale}), with an explicit take.
  const list = await getContentList({
    referenceName: fields.testimonials.referencename,
    languageCode,
    take: 50,
  })
  const items = list.items as ContentItem<ITestimonial>[]

  return (
    <section className="mx-auto max-w-5xl px-8 py-12" data-agility-component={contentID}>
      <h2 className="text-3xl font-semibold dark:text-white">{fields.heading}</h2>
      <ul className="mt-8 grid grid-cols-1 gap-6 md:grid-cols-3">
        {items.map((t) => (
          <li key={t.contentID} className="rounded-lg border p-6">
            <blockquote>{t.fields.quote}</blockquote>
            <div className="mt-4 flex items-center gap-3">
              {t.fields.photo && (
                <AgilityPic image={t.fields.photo} fallbackWidth={64} className="h-12 w-12 rounded-full" />
              )}
              <div>
                <p className="font-medium">{t.fields.name}</p>
                {t.fields.role && <p className="text-sm opacity-70">{t.fields.role}</p>}
              </div>
            </div>
          </li>
        ))}
      </ul>
    </section>
  )
}
```

4. It registers the component so `<ContentZone>` can find it. The registry's `getModule` matches `name` against the component model's reference name, case-insensitively:

```tsx
// components/agility-components/index.ts
import Testimonials from "./Testimonials"

const allModules = [
  // ...existing components
  { name: "Testimonials", module: Testimonials },
]
```

5. It saves the sample items with `save_content_items`. They land in **Staging**.

Treat the snippet above as the shape to expect, not code to paste. The exact field names depend on the model the agent actually created, which is exactly why the agent reads the model back and checks the real response.

Review the diff before you move on. The usual misses are a registry name that doesn't match the model's reference name, a direct SDK call that skips the `lib/cms/` wrappers (and so has no cache tag), and a list call without an explicit `take`.

## Step 5: Test with Preview

1. Add the new component to a page in the Agility page editor (or ask the agent to place it on a test page) and save.
2. Run `npm run dev`. In development the starter always uses the preview API key, so your staged component and sample items render without publishing anything.
3. Deploy a preview build and set it as your preview URL in Agility, then click **Preview** from the editor. The starter's `app/api/preview` route validates the preview key, turns on draft mode, and redirects to the page, which then renders with preview content. `app/api/preview/exit` turns draft mode off.
4. Ask the agent to run `npm run build` and fix anything that fails. The build type-checks the project and prerenders every page in the sitemap, so a component that breaks on real content fails here instead of in production.

## Step 6: Ship

1. **Deploy** to your host. See [Deploying Next.js to Vercel](/docs/nextjs/deploying-next-js-to-vercel) or the other deployment guides.
2. **Wire up the webhook.** In Agility under **Settings** → **Webhooks**, point a webhook at `POST https://your-site.com/api/revalidate`. The starter's route acts on publish events only. It doesn't check a signature, so to make sure only Agility can call it, see [Verifying Signed Webhooks](/docs/developers/verifying-signed-webhooks).
3. **Publish.** Publish the new content and the page at the level your team chose: an editor reviews and publishes, an approver signs off, or the agent publishes with `publish_content` and `publish_page` once its checks pass. Publish tools aren't annotated as destructive; unpublish and delete are. Whether anyone is asked to confirm a publish depends on your client's settings and the level you chose.
4. **Confirm the refresh.** Edit a testimonial, publish it, and reload the live page. If it only updates after about a minute rather than right away, the tag in your wrapper and the tag the webhook revalidates don't match.

The starter's fetch-tag model is the simplest place to start. If you later want long-lived caches with publish-only invalidation, [Caching with Next.js and Agility](/docs/nextjs/caching-with-next-js-and-agility) covers moving to Next.js Cache Components (`"use cache"`) as an upgrade path.

## Tips

**One feature per session.** Clear the context between features (`/clear` in Claude Code, a fresh chat in Cursor or Copilot). Each session starts from your codebase and `AGENTS.md`, nothing else.

**Make the agent read before it writes.** Asking it to list existing models first avoids near-duplicate models that editors then have to choose between.

**Ground it in the docs.** When the agent is unsure about an SDK method or an API parameter, the Knowledgebase MCP gives it the documented answer. Ask it to cite the article it used.

**Pick a publishing level per content type.** Everything the agent saves through `save_content_items` lands in Staging first. Decide per content type whether a person reviews and publishes, an approver signs off, or the agent publishes after its checks pass. If you like, start with review while you build confidence, then move repeatable work to autonomous publishing.

**Keep the instruction files in step.** The starter ships both `AGENTS.md` and `.cursorrules`. If you change a convention in one, change it in the other, or point one at the other.

**Make your site readable by agents too.** Once the site is live, see [Making Your Agility-Powered Site Readable by AI](/docs/developers/making-your-site-readable-by-ai) for llms.txt, Markdown twins and structured data.

## Get Started

- [Agility Next.js Starter](https://github.com/agility/agilitycms-nextjs-starter) (including its `AGENTS.md`)
- [How the Next.js Starter Works](/docs/nextjs/how-the-next-js-starter-works)
- [Caching with Next.js and Agility](/docs/nextjs/caching-with-next-js-and-agility)
- [Agility CMS MCP Server](/docs/developers/agility-cms-mcp-server)
- [Agility Knowledgebase MCP Server](/docs/overview/agility-knowledgebase-mcp-server)
- [Building Apps with AI Coding Tools](/docs/apps/building-apps-with-ai-coding-tools)
- [AGENTS.md specification](https://agents.md/)
