K

Explore

Agent Templates

Build

/Docs Assistant

Docs Assistant: Turn Any Documentation Site Into a Conversational AI

Set one env var, deploy with 21st SDK, and let Claude answer questions about your docs - with citations, streamed in real time.

Serafim Korablev
Serafim Korablev21st Founder
@serafimcloud

The Problem

Documentation exists, but users still ask the same questions - on Discord, Slack, GitHub issues, and support tickets. The answer is almost always in the docs, but finding it requires keyword searching, reading multiple pages, and piecing together an answer.

What if your docs could just answer questions directly?

How It Works

The Docs Assistant is a template built with 21st SDK and powered by Claude Code. It uses the llms.txt standard - a lightweight, widely-adopted convention for making documentation AI-readable.

Here's the flow:

  1. On startup - the agent downloads llms.txt and llms-full.txt from your docs site into the sandbox
  2. User asks a question - the agent greps the local docs for relevant keywords
  3. Focused retrieval - if needed, it fetches specific doc pages for full context
  4. Streamed answer - Claude responds with citations, linking to the source pages

The whole thing runs in a sandbox. No embeddings, no vector database, no infrastructure to manage.

Agent Tools

The agent has three built-in tools:

ToolWhat it does
search_docsGrep through downloaded docs with keyword + surrounding context
list_doc_pagesShow the full docs index from llms.txt
fetch_doc_pageFetch a specific doc page URL for full content

This keeps the architecture simple: grep first, fetch if needed.

Compatible Sites

Any site that publishes llms.txt works out of the box:

  • Anthropic - https://docs.anthropic.com
  • Vercel - https://vercel.com
  • Supabase - https://supabase.com/docs
  • Stripe - https://docs.stripe.com

If the llms.txt is at a non-standard path, use the DOCS_LLMS_TXT_URL env var to point directly to it.

Getting Started

Clone the template and deploy in under a minute:

git clone https://github.com/21st-dev/21st-sdk-examples.git
cd 21st-sdk-examples/docs-assistant
npm install
npx @21st-sdk/cli login
npx @21st-sdk/cli deploy

Set Your Docs URL

In the 21st dashboard → your agent → Environment Variables:

DOCS_URL=https://docs.anthropic.com

Redeploy after adding the variable:

npx @21st-sdk/cli deploy

Run the Frontend

cp .env.example .env.local
# Add your API_KEY_21ST to .env.local
npm run dev

Open http://localhost:3000 - your docs assistant is live.

Build Your Own

The Docs Assistant is a foundation. Point it at any docs site, embed the chat UI into your product, or extend the agent with more tools. The Support Agent template builds on top of this - adding email escalation via Resend for questions the docs can't answer.

Check the full source on GitHub and deploy your own with 21st SDK.

Agent Template

The full agent source - set DOCS_URL and deploy.

agents/docs-assistant/index.tsView on GitHub →
import { agent, tool, Sandbox } from "@21st-sdk/agent"
import { z } from "zod"

export default agent({
  runtime: "claude-code",
  model: "claude-sonnet-4-6",
  permissionMode: "bypassPermissions",
  maxTurns: 25,

  systemPrompt: `You are a documentation assistant. Answer questions using the
documentation loaded into your workspace.

WORKFLOW:
1. Grep /workspace/llms.txt to find relevant page titles and URLs.
2. Grep /workspace/llms-full.txt for detailed content (if available).
3. If you need more detail, use fetch_doc_page to get the full page content.
4. Synthesize a clear answer with citations.`,

  sandbox: Sandbox({
    setup: [
      `mkdir -p /home/user/workspace && cd /home/user/workspace && \
if [ -n "$DOCS_URL" ]; then \
  BASE=$(echo "$DOCS_URL" | sed 's|/$||'); \
  curl -fsSL "$BASE/llms.txt" -o llms.txt 2>/dev/null || echo "# Could not fetch llms.txt" > llms.txt; \
  curl -fsSL "$BASE/llms-full.txt" -o llms-full.txt 2>/dev/null || true; \
else \
  echo "# No DOCS_URL configured. Set DOCS_URL env var." > llms.txt; \
fi`,
    ],
  }),

  tools: {
    fetch_doc_page: tool({
      description: "Fetch a specific documentation page by URL.",
      inputSchema: z.object({
        url: z.string().url().describe("Full URL of the documentation page"),
      }),
      execute: async ({ url }) => {
        const res = await fetch(url, {
          headers: { Accept: "text/plain, text/markdown, text/html" },
          signal: AbortSignal.timeout(15_000),
        })
        const text = await res.text()
        return { content: [{ type: "text", text: text.slice(0, 30_000) }] }
      },
    }),

    list_doc_pages: tool({
      description: "List all available documentation pages from the llms.txt index.",
      inputSchema: z.object({}),
      execute: async () => {
        const { readFile } = await import("fs/promises")
        const content = await readFile("/home/user/workspace/llms.txt", "utf-8")
        return { content: [{ type: "text", text: content }] }
      },
    }),

    search_docs: tool({
      description: "Search through local documentation files for a keyword.",
      inputSchema: z.object({
        query: z.string().min(1).describe("Search term to look for in the docs"),
        file: z.enum(["llms.txt", "llms-full.txt"]).optional().default("llms-full.txt"),
      }),
      execute: async ({ query, file }) => {
        const { execSync } = await import("child_process")
        const result = execSync(
          `grep -i -n -C 5 ${JSON.stringify(query)} /home/user/workspace/${file} | head -200`,
          { encoding: "utf-8", timeout: 10_000 },
        )
        return { content: [{ type: "text", text: result || `No matches found.` }] }
      },
    }),
  },
})
Docs Assistant: Turn Any Documentation Site Into a Conversational AI | 21st | 21st