Open source · MIT · v0.8.0
Make your docs answerable, by people and by agents.
One static index, built with your site, serves a cited Ask box for people, and an MCP server and llms.txt for agents. A function you deploy searches it in memory and answers with your own model key, citing the exact section. Agents bring their own model, so their traffic costs you no model tokens.
- No vector database
- No vendor
- Your own function and key
- MCP and llms.txt included
npm i ondocsAsk these docs
Question: How do I add it to Docusaurus?
Put ondocs/docusaurus in the plugins array of docusaurus.config.ts 2:
// docusaurus.config.ts
export default {
// …
plugins: ['ondocs/docusaurus'],
};Set up
One line for Docusaurus and Starlight. A script tag for everything else.
Every setup has the same two halves: an index built with your site, and one function that answers from it. The plugins do the first half for you.
A Docusaurus 3 plugin. After every build it indexes the pages your site serves into build/ask-index.json, and it adds the dialog behind a floating “Ask AI” button and ⌘I.
Install
Terminalnpm i ondocs ai @ai-sdk/openai @radix-ui/react-dialog cmdkAdd the plugin
TypeScript// docusaurus.config.ts export default { plugins: [['ondocs/docusaurus', { endpoint: '/api/ask' }]], };Write the endpoint
One function on your host answers from the index, with your model key, rate-limited and on a daily budget. init writes it for Vercel, Netlify, Cloudflare, or GitHub Pages with Workers AI and no API key, and deploys nothing. What it writes for each host
Terminalnpx ondocs init
A Starlight plugin, and an integration for any other Astro site. It indexes what Starlight’s own search indexes into dist/ask-index.json, and adds the dialog to every page.
Install
Terminalnpm i ondocs ai @ai-sdk/openai react react-dom @radix-ui/react-dialog cmdkAdd the plugin
JavaScript// astro.config.mjs import starlight from '@astrojs/starlight'; import ondocs from 'ondocs/starlight'; import { defineConfig } from 'astro/config'; export default defineConfig({ integrations: [starlight({ title: 'Acme Docs', plugins: [ondocs()] })], });Deploy as you do now
With an SSR adapter, the integration serves the ask and MCP endpoints itself: there is no file to write. A static site writes its endpoint with npx ondocs init. Astro with an adapter
Index your content with the CLI, mount the handler as a route, and render the dialog. This site is built exactly this way.
Index your content
Terminalnpx ondocs index content --base-url /docs -e openai:text-embedding-3-smallMount the endpoint
TypeScript// app/api/ask/route.ts import { openai } from '@ai-sdk/openai'; import { createAskHandler } from 'ondocs/server'; import index from '../../../ask-index.json'; export const POST = createAskHandler({ index, model: openai('gpt-5.4-mini'), embeddingModel: openai.embedding('text-embedding-3-small'), });Render the dialog
TSX'use client'; import { AskDialog } from 'ondocs/react'; import 'ondocs/react/styles.css'; import 'ondocs/embed/launcher.css'; export const Ask = () => <AskDialog launcher />;
Hugo, Jekyll, Eleventy, MkDocs or plain HTML: index the HTML your generator built, write the endpoint with one command, and add one script tag. The dialog loads the first time it is wanted.
Index the built site
Terminalnpx ondocs index public -e openai:text-embedding-3-smallWrite the endpoint
One function on your host answers from the index, with your model key, rate-limited and on a daily budget. init writes it for Vercel, Netlify, Cloudflare, or GitHub Pages with Workers AI and no API key, and deploys nothing. What it writes for each host
Terminalnpx ondocs initAdd the script tag
HTML<script src="https://cdn.jsdelivr.net/npm/ondocs@0.8/dist/embed.global.js" data-endpoint="/api/ask" defer ></script>
For agents
The same index, for the agents that read your docs.
Coding agents and AI apps search and read your docs over MCP, with their own model. Search is keyword-only by default, so it calls no model at all. These docs do it at /api/mcp:
claude mcp add --transport http ondocs https://ondocs.vercel.app/api/mcpsearch and fetch
Ranked sections with a snippet and a link to the anchor, then the page or section as Markdown: the shape ChatGPT’s connectors expect, over MCP 2026-07-28 and 2025-11-25.
llms.txt and .md pages
The plugins also write llms.txt, llms-full.txt and a Markdown copy of every page, and leave alone what another llms plugin writes.
Add to Cursor, VS Code, Claude
With the plugins’ mcp option, a docs page shows the install links and the Claude Code command for your endpoint.
How it works
Indexed when you deploy. Searched in memory when someone asks.
Docs change when you deploy, not between requests, so the index is a file built with the site instead of a database kept in sync with it.
Build time once per deploy
ondocs indexReads your Markdown, MDX or built HTML. Splits it at every heading, embeds each chunk and quantizes the vectors to int8.ask-index.json1.6 MB per 1,000 chunks, deployed with the site.Rebuilt on every deploy, re-embedding only the chunks whose text changed. --check fails CI when a committed index is stale.
Request time per question
Keywords and meaning
BM25 finds exact names like createAskHandler and error codes; vectors find paraphrases. Reciprocal rank fusion merges the two rankings by rank alone.
A gate before the model
Nothing relevant means “I don’t know”, fast and free. The model only sees questions your docs can answer, and is told to answer from the sources alone.
Citations that land
Pages are split at every heading, so 1 opens the section an answer came from, at the URL your framework serves.
Numbers
Measured, at the sizes a docs site has.
- 1.6 MBindex for 1,000 chunks
- int8 vectors in base64; 0.9 MB gzipped. The same index as float JSON is 7.5 MB.
- 0.71 msmedian query over 1,000 chunks
- BM25, cosine scan and fusion, in memory. 6.96 ms at 10,000 chunks.
- 22 / 22agent searches with the right page in the top three
- On docusaurus.io’s own docs, keyword-only: no model call. Checked in CI.
- 2.7 KBscript tag on each page, gzipped
- The button and shortcut. The dialog loads the first time it is wanted.
- 0model calls to answer what the docs don’t cover
- The gate says “I don’t know” before the model is reached; a follow-up may cost a short rewrite first.
- ≤ 800output tokens per answer, by default
- With at most 8,000 characters of sources in: one embedding call and one model call.
From pnpm bench on an Apple M1 Max with Node 24.18: synthetic, documentation-shaped corpora with 512-dimension vectors, 1,000 queries per size. Query times exclude the embedding API call. All results
Compared
A local index like Pagefind’s, plus cited answers. No cloud account.
| Hosted assistantsKapa, Inkeep, Algolia Ask AI, Biel.ai | ondocsA library, on your infrastructure | Search onlyPagefind, Orama’s local index | |
|---|---|---|---|
| What a visitor gets | Written answers with citations | Written answers citing the exact section | A list of matching pages |
| Where your docs are indexed | On the vendor’s platform | A static file in your build | Static files in your build |
| What you run | Nothing, it is hosted | One function: Vercel, Netlify, Cloudflare or any Request → Response runtime | Nothing |
| Model and key | The vendor’s | Yours, through any AI SDK provider | None |
| Account and pricing | A vendor account and plan | None. You pay your model provider per question | None |
| For agents | Varies by vendor | An MCP server and llms.txt from the same index | No |
| Follow-up questions and dashboards | Typically included | Follow-ups, yes. No dashboard: log questions with onFinish | No |
When it’s the wrong tool
- Past about 50,000 chunks. Search is an exact scan; at that size an approximate index or a vector database is better.
- Content that changes per user or per request. The index is rebuilt when you deploy.
- Multi-turn chat with a dashboard, and nothing to run. A hosted assistant does that.
- Search results with no server at all. Use Pagefind.
Add it to your docs.
Try it on your own content in two minutes, without an API key: index with the mock model and run npx ondocs dev.
npm i ondocs