Home /

docs

API Reference

Complete reference for every option in docs.config.ts. All types are exported from @farming-labs/docs.


defineDocs(config)

The entry point for configuring your docs. Returns a typed config object.

docs.config.ts
import { defineDocs } from "@farming-labs/docs";

export default defineDocs({
  entry: "docs",
  // ...all options below
});

DocsConfig

DocsLocalMcpSearchRuntimeConfig

Runtime shape that describes whether the local MCP server should be treated as the search target. Exported from @farming-labs/docs and @farming-labs/docs/server.

PropertyTypeDescription
enabledbooleanWhether the local MCP server is enabled
routestringCanonical HTTP route for the local MCP endpoint (e.g. "/api/docs/mcp")
tools{ searchDocs?: boolean }Tool-level toggle; searchDocs controls whether search_docs is exposed

DocsLocalMcpSearchRuntimeInput

Accepted input shape for resolveLocalDocsMcpSearchConfig. A partial form of DocsLocalMcpSearchRuntimeConfig — all fields are optional. Exported from @farming-labs/docs and @farming-labs/docs/server.

DocsSearchRequestResolutionOptions

Options accepted by the third parameter of resolveSearchRequestConfig. Exported from @farming-labs/docs and @farming-labs/docs/server.

PropertyTypeDescription
localMcpDocsLocalMcpSearchRuntimeInput | null | undefinedWhen present, resolveSearchRequestConfig calls resolveLocalDocsMcpSearchConfig before the self-referential URL check to detect whether the MCP endpoint resolves to the local server

resolveLocalDocsMcpSearchConfig

Public function exported from @farming-labs/docs and @farming-labs/docs/server. Given a McpDocsSearchConfig, an optional DocsLocalMcpSearchRuntimeInput, and an optional request URL, returns a modified search config that uses direct simple search when the configured MCP endpoint resolves to the local server. Returns the original config unchanged for external endpoints.

import { resolveLocalDocsMcpSearchConfig } from "@farming-labs/docs";

const resolved = resolveLocalDocsMcpSearchConfig(
  search,           // McpDocsSearchConfig
  mcp,              // DocsLocalMcpSearchRuntimeInput | null | undefined
  requestUrl,       // string | undefined
);

resolveSearchRequestConfig now accepts an optional third options: DocsSearchRequestResolutionOptions argument. Pass { localMcp: config.mcp } to enable local-MCP detection in custom server adapters. Top-level configuration object passed to defineDocs().

PropertyTypeDefaultDescription
entrystringrequiredDocs source route and folder (e.g. "docs" -> app/docs)
docsPathstringsame as entryPublic docs route prefix in Next.js. Use "" to serve docs from /
contentDirstringsame as entryPath to content files. TanStack Start, SvelteKit, Astro, and Nuxt use it; Next.js uses app/{entry}/
staticExportbooleanfalseSet true for full static builds; hides search and AI (see Configuration)
themeDocsTheme—Theme preset from a factory (fumadocs(), darksharp(), pixelBorder(), etc.)
githubstring | GithubConfig—GitHub repo config for "Edit on GitHub" links
navDocsNav—Sidebar header title and URL
themeToggleboolean | ThemeToggleConfigtrueLight/dark mode toggle
breadcrumbboolean | BreadcrumbConfigtrueBreadcrumb navigation
sidebarboolean | SidebarConfigtrueSidebar visibility and customization
iconsRecord<string, unknown>—Shared icon registry for frontmatter icon fields and built-ins like Prompt
componentsRecord<string, unknown>—Custom MDX component overrides, including built-ins like HoverLink and Prompt
analyticsboolean | DocsAnalyticsConfigfalseProduct/usage event stream for docs UI, search, AI, feedback, agent reads, and MCP usage
telemetryboolean | DocsTelemetryConfigproduction-only enabledFarming Labs maintainer telemetry for package adoption and coarse agent-surface usage
observabilityboolean | DocsObservabilityConfigfalseTrace event stream for Ask AI and MCP runs, including spans, timing, status, and errors
onCopyClick(data: CodeBlockCopyData) => void—Callback when the user clicks the copy button on a code block (runs in addition to copy)
feedbackboolean | FeedbackConfigfalse for UIHuman page feedback UI; agent feedback endpoints are default-on unless opted out
agentDocsAgentConfig—Compaction, golden evaluations, reusable skill publication, and optional A2A metadata
reviewboolean | DocsReviewConfigtrueDocs Review scoring, rule severity, and GitHub Actions workflow generation
readingTimeboolean | ReadingTimeConfigfalseOpt-in estimated reading-time label with per-page overrides
pageActionsPageActionsConfig—"Copy Markdown" and "Open in LLM" buttons
aiAIConfig—RAG-powered AI chat
searchboolean | DocsSearchConfigtrueBuilt-in simple search, Typesense, Algolia, or a custom adapter
mcpboolean | DocsMcpConfigenabledBuilt-in MCP server over stdio, /mcp, and /.well-known/mcp
apiReferenceboolean | ApiReferenceConfigfalseGenerated API reference pages from supported framework route conventions or local/remote OpenAPI JSON and YAML
sitemapboolean | DocsSitemapConfigtrueGenerated sitemap.xml, sitemap.md, /docs/sitemap.md, and /.well-known/sitemap.md
robotsboolean | DocsRobotsConfigtrueRuntime/generated robots.txt policy for docs and agent-readable routes
changelogboolean | ChangelogConfigfalseGenerated changelog feed and entry pages from dated MDX entries
ordering"alphabetical" | "numeric" | OrderingItem[]"alphabetical"Sidebar page ordering strategy
metadataDocsMetadata—SEO metadata

ThemeToggleConfig

PropertyTypeDefaultDescription
enabledbooleantrueShow or hide the theme switcher
default"light" | "dark" | "system""system"When enabled is false, this value is applied as a forced theme (hiding the switcher). When the toggle is enabled, it is used as the initial theme that visitors can still change. "system" skips forced and default derivation in both cases.
mode"light-dark" | "light-dark-system""light-dark"Toggle mode presented to the user
ogOGConfig—Open Graph image config

DocsMarkdownSection, DocsMarkdownSectionRequest, and DocsMarkdownSectionResult

Three interfaces that power section-addressable markdown reads. All are exported from @farming-labs/docs.

DocsMarkdownSection

Represents a single heading-delimited section parsed from a markdown document.

PropertyTypeDescription
idstringStable kebab-case heading id used as the section query parameter
headingstringRaw heading text
levelnumberHeading level (1–6)
contentstringFull markdown content of the section, including the heading line
startLinenumber1-based line number of the heading that opens this section, relative to the Markdown body
endLinenumber1-based inclusive line number of the last line in this section
startLinenumberZero-based line index where the section starts
endLinenumberZero-based line index where the section ends (exclusive)

DocsMarkdownSectionRequest

Input parameters for a section-addressable markdown request.

PropertyTypeDescription
sectionstring | nullHeading id to select, e.g. "prerequisites". When omitted the full document is returned.
tokenBudgetnumber | nullSoft token ceiling; the response is truncated when the estimated token count exceeds this value
byteBudgetnumber | nullSoft UTF-8 byte ceiling applied before the token estimate

DocsMarkdownSectionResult

Result returned by resolveDocsMarkdownSectionRequest.

PropertyTypeDescription
foundbooleantrue when the requested section was resolved
requestedSectionstringThe section value from the request, when provided
sectionDocsMarkdownSectionThe resolved section, present when found is true
documentstringThe content to return — the matched section body or the full document
truncatedbooleantrue when the document was cut to fit the budget
tokenBudgetnumberApplied token budget
byteBudgetnumberApplied byte budget
estimatedTokensnumberEstimated token count for the returned document
utf8BytesnumberUTF-8 byte length of the returned document
availableSectionsDocsMarkdownSection[]All sections parsed from the full document; useful for agent recovery when found is false

Three functions are exported from @farming-labs/docs for working with these types:

FunctionDescription
collectDocsMarkdownSections(markdown)Parse a markdown string into an array of DocsMarkdownSection objects, one per heading block
selectDocsMarkdownSection(sections, id)Find a section by its stable heading id; returns the matching DocsMarkdownSection or undefined
resolveDocsMarkdownSectionRequest(markdown, request)Combine parsing, section lookup, and budget enforcement in one call; returns a DocsMarkdownSectionResult

The constant DOCS_MARKDOWN_SECTION_MAX_BUDGET = 1_000_000 is the hard upper limit accepted for tokenBudget and byteBudget.

DocsMcpPageSectionIndex

DocsMcpPageSectionIndex is the body-free section discovery metadata returned by the list_page_sections MCP tool. It is exported from @farming-labs/docs as a type alias for DocsMarkdownSectionIndex.

DocsMarkdownSectionIndex

The docs-markdown-sections.v1 JSON payload returned by the ?sections endpoint.

PropertyTypeDescription
format"docs-markdown-sections.v1"Fixed format identifier
sectionCountnumberTotal number of sections in the document
sectionsDocsMarkdownSectionMetadata[]Ordered array of section descriptors

DocsMarkdownSectionMetadata

One entry in the sections array of a DocsMarkdownSectionIndex.

PropertyTypeDescription
idstringStable kebab-case heading id, usable as the section query parameter
headingstringRaw heading label
levelnumberHeading level (1–6)
parentIdstring | nullId of the nearest ancestor heading, or null for top-level sections
startLinenumber1-based line of the heading within the Markdown body
endLinenumber1-based inclusive final line of the section
estimatedTokensnumberConservative token estimate for the section content
markdownUrlstringAbsolute URL for fetching this section with ?section={id}

DocsMarkdownSectionIndexOptions

Options accepted by buildDocsMarkdownSectionIndex.

PropertyTypeDescription
markdownstringRaw Markdown source to index
baseUrlstringBase URL used to construct markdownUrl values for each section

Section-index functions

Two additional functions are exported from @farming-labs/docs:

FunctionDescription
buildDocsMarkdownSectionIndex(options)Parse a Markdown string and build a DocsMarkdownSectionIndex response object with per-section metadata and absolute markdownUrl values
isDocsMarkdownSectionIndexRequest(request)Return true when the incoming Request includes the sections query parameter and should receive a section-index response instead of the full Markdown body

The constant DOCS_MARKDOWN_SECTION_INDEX_FORMAT = "docs-markdown-sections.v1" is the fixed format string included in every index response and advertised in /.well-known/agent.json.

DocsConfigMap, DocsConfigMapPointer, and config-map constants

The config-map endpoint (GET /api/docs?format=config) returns a DocsConfigMap JSON document built from the active docs config. All five framework server adapters handle this request automatically.

Constants

ConstantValueDescription
DEFAULT_DOCS_CONFIG_FORMAT"docs-config-map.v1"Schema version identifier included in the response and in the agent discovery spec
DEFAULT_DOCS_CONFIG_ROUTE"/api/docs?format=config"Canonical route for the config-map endpoint

DocsConfigMap

The JSON response shape. Contains a format field set to "docs-config-map.v1" and a pointers array of DocsConfigMapPointer entries. Sensitive config values are redacted before serialization.

DocsConfigMapPointer

PropertyTypeDescription
pathstringDot-path to the config field, e.g. "mcp.tools.readPage"
kindstringCoarse value kind at this pointer, e.g. "boolean", "string", "object"

Response headers

HeaderValue
Content-Typeapplication/json; charset=utf-8
Cache-Controlpublic, max-age=0, s-maxage=3600, stale-while-revalidate=86400
X-Robots-Tagnoindex

Agent discovery spec

The agent discovery spec (GET /.well-known/agent.json) now includes:

{
  "$schema": "https://docs.farming-labs.dev/schema/agent-manifest.v2.json",
  "format": "farming-labs-agent-manifest.v2",
  "version": "2",
  "profile": "full",
  "profiles": {
    "full": "/.well-known/agent.json",
    "compact": "/.well-known/agent.json?profile=compact"
  },
  "api": {
    "config": "/api/docs?format=config"
  },
  "config": {
    "format": "docs-config-map.v1",
    "endpoint": "/api/docs?format=config"
  }
}

Agents that read the discovery spec can use spec.config.endpoint and spec.config.format to fetch and identify the config map without hard-coding the route.

Agents with a small first-hop budget can request ?profile=compact. The compact response is minified and replaces each published skill's companion-file inventory with fileCount; agents can then follow skills.discovery.index only when they need the expanded catalog.

The root $schema and format identify this as the Farming Labs manifest extension. Dynamic manifest responses keep Content-Type: application/json and add:

Link: <https://docs.farming-labs.dev/schema/agent-manifest.v2.json>; rel="describedby"; type="application/schema+json"

This is intentionally distinct from the opt-in A2A v1 Agent Card at /.well-known/agent-card.json.


entry and docsPath

entry is the source route and app folder. In a Next.js app with entry: "docs", the framework expects docs pages under app/docs.

docsPath changes the public route where those pages are exposed. It defaults to the same path as entry, so app/docs/quickstart/page.mdx is served at /docs/quickstart.

For a docs-only deployment or a docs subdomain, set docsPath: "":

docs.config.tsx
import { defineDocs } from "@farming-labs/docs";
import { fumadocs } from "@farming-labs/theme";

export default defineDocs({
  entry: "docs",
  docsPath: "",
  theme: fumadocs(),
});

That keeps the files under app/docs and serves them from the public root:

Source filePublic URL
app/docs/page.mdx/
app/docs/quickstart/page.mdx/quickstart

withDocs({}) reads docsPath from docs.config.tsx. You do not need host-specific rewrites in next.config.ts for a docs-only deployment. Attach the docs deployment to a subdomain like docs.example.com, then root URLs resolve on that subdomain.

Farm.js adapter — withDocs Vite config

When using the @farming-labs/farmjs adapter, withDocs() accepts an optional vite field on the config object passed to it.

OptionTypeDescription
viteFarmViteConfigLike | ((config: FarmViteConfigLike) => FarmViteConfigLike)Optional Vite config object or factory function. The adapter injects its MDX/remark transform plugin ahead of any plugins you supply here.

Both object and function forms are supported:

farm.config.ts
import { withDocs } from "@farming-labs/farmjs";

export default withDocs({
  // object form
  vite: {
    plugins: [myPlugin()],
  },
});
farm.config.ts
import { withDocs } from "@farming-labs/farmjs";

export default withDocs({
  // factory form — receives the resolved config and returns a merged object
  vite: (config) => ({ ...config, plugins: [myPlugin()] }),
});

In both cases the adapter-owned MDX transform is prepended before any plugins you provide, so MDX processing always runs first.

Root docs own root routes

Use docsPath: "" when the deployment is dedicated to docs. If the same app needs a marketing homepage at /, keep docsPath unset so docs stay under /docs.


components is merged into the default MDX component map, so you can both add your own components and replace built-ins such as Callout, Tabs, HoverLink, or Prompt. For built-in defaults like theme.ui.components.HoverLink and theme.ui.components.Prompt, see Creating themes. For usage examples and a live demo, see Components.


telemetry and DocsTelemetryConfig

Farming Labs maintainer telemetry for production adoption and coarse agent-optimized feature usage. It is separate from analytics: telemetry is for framework/package insight, while analytics is for project-owned product events.

Telemetry can be disabled with either:

.env.local
DOCS_TELEMETRY=false
# or
DOCS_TELEMETRY_DISABLED=1

or:

docs.config.ts
telemetry: false,
OptionTypeDefaultDescription
enabledbooleanproduction-only enabledEnable or disable maintainer telemetry
siteOriginstringrequest/deployment originStable public origin to include in telemetry events
endpointstringFarming Labs endpointOverride the ingestion endpoint for self-hosting or tests

Set DOCS_TELEMETRY_INGEST_KEY in both the docs runtime and self-hosted ingestion app when the endpoint should require a shared x-docs-telemetry-key header.

Telemetry may include an anonymous project identity hash, package/framework versions, deployment provider signals, site origin, enabled docs features, and coarse agent-surface events such as llms, markdown, mcp, or skill. It does not collect visitor identities, page views, device fingerprints, search queries, Ask AI questions, feedback comments, copied content, docs source content, cookies, or per-user sessions.

Local development origins are ignored even when telemetry is explicitly enabled. Requests from localhost, *.localhost, IPv4 loopback, and IPv6 loopback or wildcard binding addresses are not sent or stored.


analytics and DocsAnalyticsConfig

Built-in product and usage event stream. Use it for page views, search activity, Ask AI request counts, feedback, page actions, machine-readable markdown reads, agent feedback routes, and MCP usage. See Analytics for the full guide and event list.

OptionTypeDefaultDescription
enabledbooleantrue when object is passedEnable or disable the analytics stream
consoleboolean | "log" | "info" | "debug"true for analytics: trueLog analytics events to the console
includeInputsbooleanfalseInclude raw queries, AI questions, feedback comments, copied text
onEvent(event) => void | Promise<void>—Callback fired for every analytics event
docs.config.ts
analytics: {
  onEvent(event) {
    console.info(event.type, event.properties);
  },
}

Inputs stay out unless requested

Search queries, AI questions, feedback comments, and copied content are omitted by default. Set includeInputs: true only when your project is allowed to store that text.


observability and DocsObservabilityConfig

Built-in trace event stream for agent runtime debugging. Use it for Ask AI and MCP spans, timing, status, previews, errors, retries, and final handoffs. See Observability for the full guide and trace event list.

OptionTypeDefaultDescription
enabledbooleantrue when object is passedEnable or disable the observability stream
consoleboolean | "log" | "info" | "debug"true for observability: trueLog trace events to the console
includeInputsbooleanfalseInclude raw input fields on custom observability events
onEvent(event) => void | Promise<void>—Callback fired for every observability trace event
docs.config.ts
observability: {
  console: "debug",
  onEvent(event) {
    console.info(event.type, event.traceId, event.durationMs);
  },
}

Separate from analytics

analytics.onEvent receives usage events like page_view, api_ai_request, and mcp_tool. observability.onEvent receives trace events like run.start, model.call, and tool.result.


Ordering

Controls how pages are sorted in the sidebar. Three strategies are available:

"alphabetical" (default)

Pages are sorted alphabetically by folder name. No extra configuration needed.

ordering: "alphabetical",

"numeric"

Pages are sorted by the order field in frontmatter. Lower numbers appear first. Pages without order are sorted alphabetically after ordered pages.

ordering: "numeric",

Then in your page frontmatter:

---
title: "Installation"
order: 1
---

Slug-based (OrderingItem[])

Pass an array of { slug, children? } objects for explicit control over page order, including nested pages.

ordering: [
  { slug: "installation" },
  { slug: "cli" },
  { slug: "configuration" },
  {
    slug: "themes",
    children: [
      { slug: "default" },
      { slug: "darksharp" },
      { slug: "pixel-border" },
      { slug: "creating-themes" },
    ],
  },
  {
    slug: "customization",
    children: [
      { slug: "colors" },
      { slug: "typography" },
      { slug: "sidebar" },
      { slug: "components" },
      { slug: "ai-chat" },
      { slug: "page-actions" },
    ],
  },
  { slug: "reference" },
],

OrderingItem

PropertyTypeDescription
slugstringFolder name at this level (not the full path)
childrenOrderingItem[]Ordering for child pages within this folder

Pages not listed in the array appear alphabetically after the listed ones.


DocsNav

Sidebar header configuration.

PropertyTypeDefaultDescription
titlestring | ReactNode"Docs"Sidebar header title. React elements supported in Next.js and TanStack Start
urlstring"/{entry}"URL the title links to
nav: {
  title: "My Docs",
  url: "/docs",
},

LlmsTxtConfig

Generated llms.txt files for agent-readable docs indexes. The root files are enabled by default; section files are opt-in and only add route handling plus discovery metadata. If a native static file already exists at the same public route, such as public/llms.txt or SvelteKit static/llms.txt, that file takes precedence and the generated output remains the fallback.

PropertyTypeDefaultDescription
enabledbooleantrueEnable or disable llms.txt generation
baseUrlstringrequest origin at runtimeBase URL used for absolute links
siteTitlestringnav.title or "Documentation"Heading shown in generated files
siteDescriptionstringundefinedDescription shown below the heading
maxCharsLlmsTxtMaxCharsConfig{ mode: "warn", chars: 50000 }Budget for compact llms.txt files
sectionsLlmsTxtSectionConfig[][]Optional section-level llms.txt files
docs.config.ts
export default defineDocs({
  llmsTxt: {
    baseUrl: "https://docs.example.com",
    maxChars: {
      mode: "warn",
      chars: 50_000,
    },
    sections: [
      {
        title: "API",
        description: "Endpoint, SDK, and integration reference pages.",
        match: "/docs/api/**",
        maxChars: {
          mode: "warn",
          chars: 25_000,
        },
      },
    ],
  },
});

Default routes:

  • /llms.txt
  • /llms-full.txt
  • /.well-known/llms.txt
  • /.well-known/llms-full.txt

For sections, the route is derived from the first match value. For example, /docs/api/** creates /docs/api/llms.txt and /docs/api/llms-full.txt. Root /llms.txt lists configured sections first and keeps matching pages inside those section files.

LlmsTxtMaxCharsConfig

PropertyTypeDefaultDescription
mode"warn" | "error" | "off""warn"Warn, fail the response, or disable budget checks
charsnumber50000Maximum recommended character count

LlmsTxtSectionConfig

PropertyTypeDescription
titlestringSection title shown in root and section files
descriptionstringOptional description shown next to the section link
matchstring | string[]Public URL matcher, such as /docs/api/**
maxCharsLlmsTxtMaxCharsConfigOptional section-specific budget

DocsMcpConfig

Built-in MCP server configuration for AI clients, IDE agents, and local MCP tooling.

PropertyTypeDefaultDescription
enabledbooleantrueEnable the MCP server. MCP is on by default; set false to opt out.
routestring"/api/docs/mcp"Streamable HTTP route used by the MCP endpoint
namestringnav.title or @farming-labs/docsMCP server name reported to clients
versionstring"0.0.0"Version string reported to clients
toolsDocsMcpToolsConfigall enabledFine-grained tool toggles
securityDocsMcpSecurityConfigsee belowHTTP Origin validation, opt-in authentication, and request limits
docs.config.ts
export default defineDocs({
  entry: "docs",
  mcp: {
    route: "/api/docs/mcp",
    name: "My Docs MCP",
    tools: {
      listDocs: true,
      listPages: true,
      listTasks: true,
      readTask: true,
      getNavigation: true,
      searchDocs: true,
      readPage: true,
      getCodeExamples: true,
      getConfigSchema: true,
      getContext: true,
    },
  },
});

DocsMcpSecurityConfig

Authentication is opt-in. Omitting authenticate keeps HTTP MCP public, while Origin validation and the request-size limit still apply. The stdio transport is unaffected.

PropertyTypeDefaultDescription
allowedOrigins"same-origin" | string[] | callback"same-origin"Policy for a supplied HTTP Origin header; requests without Origin are accepted
authenticate(context) => principal | null | ResponsepublicOptional Web Request callback; adding it makes authentication required
protectedResourceDocsMcpProtectedResourceConfig—Opt-in RFC 9728 metadata, Bearer challenges, and endpoint-wide scope requirements; active only with authenticate
maxBodyBytesnumber1048576Maximum Streamable HTTP POST body size
corsboolean | DocsMcpCorsConfigtrueExact-Origin browser CORS for accepted Origins; set false to suppress headers
docs.config.ts
export default defineDocs({
  mcp: {
    security: {
      protectedResource: {
        authorizationServers: ["https://auth.example.com"],
        scopesSupported: ["docs:read"],
        requiredScopes: ["docs:read"],
      },
      authenticate: async ({ request, pathname, resource }) => {
        const user = await authenticateRequest(request, { audience: resource });
        return user
          ? { id: user.id, scopes: user.scopes, claims: { pathname, resource } }
          : null;
      },
    },
  },
});

POST bodies are capped before allowedOrigins or authenticate callbacks run. Browser preflight uses the OPTIONS handler and does not run authentication.

DocsMcpProtectedResourceConfig

RFC 9728 protected-resource discovery for OAuth-aware HTTP MCP clients. This object only becomes active when authenticate is also configured. Omitting both keeps HTTP MCP public by default. It does not issue or validate tokens: authenticate remains responsible for signature, issuer, expiry, the exact callback resource audience, and token-derived scope validation. The callback request retains the real incoming URL and query string.

PropertyTypeRequiredDescription
authorizationServersreadonly string[]yesNon-empty list of HTTPS OAuth issuer URLs without query or fragment; loopback HTTP is accepted for development
scopesSupportedreadonly string[]noScopes advertised as scopes_supported
requiredScopesreadonly string[]noScopes every principal returned by authenticate must include
resourceNamestringnoHuman-readable protected-resource name; defaults to the resolved MCP server name
resourceDocumentationstringnoAbsolute HTTP(S) URL with human-readable authentication instructions

The metadata routes are public and support GET and HEAD:

  • /.well-known/oauth-protected-resource/mcp for /mcp
  • /.well-known/oauth-protected-resource/.well-known/mcp for /.well-known/mcp
  • /.well-known/oauth-protected-resource/api/docs/mcp for the default canonical route; a custom mcp.route produces the matching path instead

When authenticate returns null, protected-resource mode returns 401 with a Bearer WWW-Authenticate challenge containing the metadata URL and required scopes. A valid principal missing a required scope receives 403 with error="insufficient_scope". Returning a Web Response bypasses framework-generated challenges and preserves the custom response. Use HTTPS for hosted MCP and authorization-server URLs; plain HTTP is appropriate only for loopback development. Each alias is a distinct OAuth resource identifier, so token audience validation must accept the exact endpoint used by the client. For Next.js, protected MCP is incompatible with basePath; host it at the origin root or terminate the MCP and RFC 9728 routes at an edge proxy. Inline MCP objects are detected automatically. Pass the live docs config to withDocs(nextConfig, docsConfig) when MCP configuration is imported, spread, or shorthand.

DocsMcpCorsConfig

PropertyTypeDefaultDescription
allowedHeadersstring[]MCP standard headersAdditional browser request headers allowed during preflight
exposedHeadersstring[]MCP response headersAdditional response headers exposed to browser JavaScript
allowCredentialsbooleanfalseEmit credentialed CORS for the validated exact Origin; wildcard credentials are never emitted
maxAgeSecondsnumber600Browser preflight cache lifetime
The default allowedHeaders set includes Authorization, Content-Type, Last-Event-ID, MCP-Method, MCP-Name, MCP-Protocol-Version, and MCP-Session-Id. Use cors.allowedHeaders to add application-specific headers on top of these defaults.

DocsMcpToolsConfig

PropertyTypeDescription
listDocsbooleanExpose the list_docs tool
listPagesbooleanExpose the list_pages tool
listPageSectionsbooleanExpose the list_page_sections tool that returns body-free section discovery metadata for a page (default: true)
listTasksbooleanExpose the list_tasks tool for concise actionable-task discovery
readTaskbooleanExpose the read_task tool for full structured contracts
getNavigationbooleanExpose the get_navigation tool
searchFacetsbooleanExpose the list_search_facets tool for body-free framework, version, package, and tag discovery (default: true)
listContentChangesbooleanExpose the list_content_changes tool, which returns body-free document change metadata with polling support via DOCS_MCP_CONTENT_CHANGES_CURRENT_URI and DOCS_MCP_CONTENT_CHANGES_URI_TEMPLATE resources (default: false)
hydrateContentChangesbooleanExpose hydrate_content_changes for budget-aware section bodies and deletion tombstones after polling list_content_changes. Accepts a tokenBudget parameter (default 5000; min 4; max 32000) (default: true)
searchDocsbooleanExpose the search_docs tool
readPagebooleanExpose the read_page tool
getCodeExamplesbooleanExpose the get_code_examples tool
getConfigSchemabooleanExpose the get_config_schema tool
getContextbooleanExpose deterministic get_context retrieval with a conservative UTF-8 byte ceiling

Default MCP surface:

  • Public HTTP route: /mcp
  • Well-known HTTP route: /.well-known/mcp
  • Canonical HTTP route: /api/docs/mcp
  • stdio command: pnpx @farming-labs/docs mcp
  • Built-in tools: list_docs, list_pages, list_tasks, read_task, get_navigation, search_docs, read_page, get_code_examples, get_config_schema, get_context

list_pages and list_docs keep their agent value concise: hasContract, task, outcome, and appliesTo only. Use list_tasks to filter actionable pages by query/framework/version/package, then read_task for the full normalized contract (also available as Markdown through read_page).

Framework notes:

  • Next.js: withDocs() auto-generates the default /api/docs/mcp route and public /mcp plus /.well-known/mcp rewrites

FarmDocsAdapterOptions

Options accepted by the second argument of withDocs(farmConfig, options) in @farming-labs/farmjs.

PropertyTypeDefaultDescription
enabledbooleantrueEnable the docs runtime
configPathstringauto-detectedPath to docs.config.ts, relative to the Farm application directory
configPartial<DocsConfig>—Inline docs config overrides
codeBlockThemesFarmDocsCodeBlockThemes—Shiki themes used to compile Markdown and MDX code blocks
buildCachebooleantrueReuse unchanged production Markdown/MDX compilation and highlighting. Applies to build commands outside watch mode.
contentRootsstring[]—Additional documentation roots outside the Farm application directory. Each path is resolved relative to the application root and added to the dev server's server.fs.allow list so the MDX plugin can compile content from shared or repository-owned locations.

Production compilation is cached under <Vite cacheDir>/farming-docs/mdx-v1.json, normally node_modules/.vite/farming-docs/mdx-v1.json. Preserve that directory in CI to reuse work between builds. Source, module path, highlighting themes, mode, Node version, and compiler implementation changes invalidate the relevant entries. Missing or unusable caches fall back to compilation. Set buildCache: false to diagnose builds without cache reuse; the lower-level docsMdx() plugin accepts the same option.

farm.config.ts
import { withDocs } from "@farming-labs/farmjs";

export default withDocs({}, {
  contentRoots: ["../shared-docs"],
});
  • TanStack Start / SvelteKit / Astro / Nuxt: use the generated public forwarder for MCP aliases, or add an equivalent framework hook/middleware that forwards to the built-in MCP handler
  • Custom routes: set mcp.route in docs.config and update the framework public forwarder so the configured path and the actual endpoint stay aligned

See MCP Server for the route snippets and examples.

createDocsAuthoringMcpServer(options)

Server-only factory for the separate protected authoring MCP. It is not registered by createDocsMcpServer and is never added to public framework routes.

OptionTypeDefaultDescription
rootDirstringrequiredGit project root
contentDirstringentryOnly directory authoring tools may change
entrystring"docs"Docs route/content fallback
branchPrefixstring"docs/"Required prefix for created and published branches
doctorCommandcommand argv tuple—Shell-free command executed by authoring_run_doctor
allowPublishbooleanfalseRegister commit/push/draft-PR publication
baseBranchstringrepository defaultDraft PR base branch

Use runDocsAuthoringMcpStdio() or docs mcp author for local stdio. If a team wraps the factory in HTTP, the transport must require an authoring-specific OAuth scope. Public docs MCP remains read-only.


DocsAgentConfig

PropertyTypeDescription
okfboolean | DocsOkfConfigPublish OKF v0.2 trust metadata in Markdown, search, MCP, doctor, and Static Agent Bundles
compactDocsAgentCompactConfigDefaults for docs agent compact
evaluationsboolean | DocsAgentEvaluationsConfigOffline-by-default golden tasks used by docs doctor and docs review
skillsDocsAgentSkillsInputProject or workspace skills published by the runtime, Static Agent Bundle, and MCP
a2aDocsAgentA2AConfigOpt-in Agent Card metadata for a real A2A interface

agent.okf and DocsOkfConfig

OKF metadata makes provenance and freshness explicit for machine readers. agent.okf: true derives conservative defaults; an object can provide corpus defaults and a staleness policy.

docs.config.ts
agent: {
  okf: {
    generatedBy: "software:docs-build",
    staleAfterDays: 90,
    verified: [{ by: "human:docs-team", at: "2026-08-01" }],
  },
},
PropertyTypeDefaultDescription
enabledbooleantrue inside the objectEnable OKF projection
routestring"/.well-known/okf.json"Static knowledge-bundle route
generatedBystring"software:@farming-labs/docs"Generator actor for derived records
staleAfterDaysnumber—Derive stale_after from the best page timestamp
sourcesDocsOkfSource[]derived per pageDefault source provenance
generated{ by, at }derivedExplicit generation record
verified{ by, at }[][]Verification records; a human: actor produces human-reviewed trust
status"draft" | "stable" | "deprecated""stable"Knowledge lifecycle status
stale_afterstringderived when configuredExplicit expiry date

Pages can override these fields with okf frontmatter. docs agent export --public writes the JSON bundle, Markdown frontmatter carries the resolved trust object, local search results expose a trust field, MCP adds get_trust_metadata, and docs doctor --agent reports expired pages.

agent.contentChanges and DocsAgentContentChangesConfig

Body-free runtime document synchronization feed. Enabled by default on server-rendered adapters; always disabled (false) in static bundles produced by docs agent export.

PropertyTypeDefaultDescription
contentChangesboolean | DocsAgentContentChangesConfigtrueEnable or configure the content-change feed. Set false to opt out.
contentChanges.enabledbooleantrueWhether runtime adapters serve response=changes.
contentChanges.maxSnapshotsnumber8Maximum number of content snapshots retained for delta computation. Capped at 64.
contentChanges.loadSnapshot(key: string) => Promise<DocsContentSnapshot | null>—Optional callback to load a previously saved snapshot from durable storage.
contentChanges.saveSnapshot(key: string, snapshot: DocsContentSnapshot) => Promise<void>—Optional callback to persist a snapshot for future delta requests.

Content-change feed endpoint

All five framework adapters handle GET /api/docs?audience=agent&response=changes. The response carries Content-Type: application/json, an ETag derived from the indexGeneration digest, and standard Cache-Control headers. Conditional GET with If-None-Match returns 304 when the index has not changed.

The response format is docs-content-changes.v1:

FieldTypeDescription
format"docs-content-changes.v1"Fixed format identifier
indexGenerationstringSHA-256 digest of the current content index
mode"snapshot" | "delta" | "reset"Feed mode selected for this response
resetRequiredbooleantrue when the caller must discard its local state and re-index
documentCountnumberTotal number of documents in the current index
addedArray<{ canonicalUrl, digest, content? }>Documents added since the since generation
modifiedArray<{ canonicalUrl, digest, content? }>Documents whose digest changed since the since generation
removedArray<{ canonicalUrl }>Documents removed since the since generation

Query parameters:

ParameterDescription
audienceMust be agent to select the content-changes response
responseMust be changes
sinceOptional indexGeneration value from a previous response; omit for a full snapshot

The agent discovery manifest gains capabilities.contentChanges: boolean and a top-level contentChanges block describing the endpoint, supported modes, query parameters, and HTTP validators (etag: true, bodyFree: true).

DocsAgentSkillsInput and DocsAgentSkillsConfig

type DocsAgentSkillsInput =
  | string
  | readonly string[]
  | {
      paths: string | readonly string[];
      progressiveDisclosure?: {
        maxSkillLines?: number;
        instructionTokenBudget?: number;
        maxReferenceDepth?: number;
        compatibility?: "when-needed" | "always" | "off";
        checkScripts?: boolean;
      };
    };

Each path is relative to the project root. It may name a SKILL.md, its containing skill directory, or a collection directory whose descendants contain SKILL.md files. A path may reach another project inside the same workspace, but it cannot escape the workspace. Only non-symlink regular files and directories are accepted. Published content is limited to SKILL.md and regular files below references/, scripts/, and assets/.

The project-root skill.md remains the site-level compatibility skill. It is always published alongside agent.skills, using a generated fallback when that root file is absent. Duplicate skill names are rejected.

docs doctor --agent analyzes every configured skill. docs review runs the same analysis when a configured skill, companion file, or docs config changes. Defaults follow the Agent Skills progressive-disclosure guidance: at most 500 lines, an approximately 5,000-token instruction body, and one local Markdown reference hop. The checks also report broken, escaping, or unpublished references; environment-dependent skills without compatibility; and bundled scripts without dependency and validation guidance. Override the thresholds with progressiveDisclosure.

Discovery preserves both client generations:

  • /.well-known/agent-skills/index.json is the Agent Skills v0.2 index
  • a skill containing only SKILL.md uses /.well-known/agent-skills/<name>/SKILL.md as its indexed skill-md artifact
  • a skill with companions uses /.well-known/agent-skills/<name>.tar.gz as a deterministic indexed archive; the SHA-256 digest covers the exact archive bytes
  • each safe file is also served at /.well-known/agent-skills/<name>/<path> with its own hash
  • /.well-known/skills/index.json, /.well-known/skills/<name>/skill.md, and legacy companion paths support older clients
  • Static Agent Bundles emit the same indexes, artifacts, direct files, and hashes
  • MCP registers every file at docs://skills/<name>/<path>; text-compatible files use text, and binary files use a base64 blob

DocsAgentA2AConfig

The generated document follows the A2A v1 Agent Card wire shape.

PropertyTypeRequiredDescription
namestringyesAgent display name
descriptionstringyesAgent purpose
supportedInterfacesreadonly DocsAgentA2AInterfaceConfig[]yes*Ordered implemented interfaces; the first is preferred
interfaceUrlstringdeprecated alternativeSingle-interface shorthand; prefer supportedInterfaces
documentationUrlstringnoAbsolute HTTPS documentation URL; HTTP is limited to loopback development
provider{ organization: string; url: string }noProvider identity and absolute HTTPS URL outside loopback
versionstringnoAgent version; defaults to "1.0.0"
iconUrlstringnoAbsolute HTTPS icon URL; HTTP is limited to loopback development
capabilitiesDocsAgentA2ACapabilitiesnoImplemented streaming, push notification, extension, and extended-card capabilities
defaultInputModesreadonly string[]noAgent-wide input media types; defaults to ["text/plain"]
defaultOutputModesreadonly string[]noAgent-wide output media types; defaults to ["text/plain"]
skillsreadonly DocsAgentA2ASkill[]yes with supportedInterfacesExplicit capabilities implemented by the A2A service; optional only for the deprecated shorthand
securitySchemesReadonly<Record<string, DocsAgentA2ASecurityScheme>>noNamed API key, HTTP auth, OAuth 2, OpenID Connect, or mutual TLS schemes
securityRequirementsreadonly DocsAgentA2ASecurityRequirement[]noAgent-wide alternatives over named security schemes
protocolVersionstringdeprecated shorthand onlyProtocol for interfaceUrl; retains the historical "0.3" default
protocolBindingstringdeprecated shorthand onlyBinding for interfaceUrl; defaults to "HTTP+JSON"

supportedInterfaces must contain at least one { url, protocolBinding?, protocolVersion?, tenant? } entry. Each entry defaults to A2A protocol 1.0 over HTTP+JSON. The core bindings are JSONRPC, GRPC, and HTTP+JSON; a custom binding is an absolute URI. Ordering is meaningful: the first interface is the client's preferred starting point. The deprecated interfaceUrl form cannot be combined with supportedInterfaces; it retains protocol 0.3 as its default so existing services are not silently misadvertised. Set its protocolVersion explicitly for other versions. Core interfaces and all HTTP card/authentication metadata require HTTPS outside loopback development. Custom bindings use a secure binding-appropriate absolute URL such as WSS or MQTTS.

DocsAgentA2ASkill requires id, name, description, and non-empty tags; it can also set examples, capability-specific inputModes and outputModes, and securityRequirements. It does not have a URL in A2A v1. URLs for configured Agent Skill documents and artifacts remain in /.well-known/agent-skills/index.json, its legacy compatibility index, and MCP resources. The preferred supportedInterfaces form requires explicit A2A skills. Published documentation skills are projected only when a2a.skills is omitted from the deprecated shorthand, preserving its previous behavior.

Security schemes use the A2A v1 discriminated wrappers, and each requirement maps a configured scheme name to its scope list. For example:

securitySchemes: {
  bearer: {
    httpAuthSecurityScheme: {
      scheme: "bearer",
      bearerFormat: "JWT",
    },
  },
},
securityRequirements: [
  {
    schemes: {
      bearer: { list: ["docs.read"] },
    },
  },
],

Use exactly one wrapper per named scheme. Requirement objects are alternatives; multiple schemes inside one object are required together. capabilities.extendedAgentCard: true requires at least one declared security scheme and a non-empty applicable security requirement.

When a2a is set, /.well-known/agent-card.json uses the strict v1 shape with supportedInterfaces; it does not emit legacy top-level url, protocolVersion, preferredTransport, or per-skill url fields. Capabilities default to non-streaming with no push notifications, while security metadata is omitted unless configured. When a2a is omitted, the route is not published. Do not point an advertised interface at a docs, Ask AI, or MCP route; those transports do not implement A2A.

DocsAgentEvaluationsConfig

PropertyTypeDefaultDescription
enabledbooleantrueEnable configured golden-task evaluation
tokenBudgetnumber4000Default hard UTF-8 context-byte ceiling
topKnumber5Default ranked retrieval depth
surface"mcp-context" | "configured-search" | "ask-ai-context""mcp-context"Retrieval/context surface measured by the suite
allowNetworkbooleanfalseAllow framework-managed external search, HTTP answers, and runtime example execution
searchTimeoutMsnumber30000Per-attempt timeout for configured-search and Ask AI retrieval; transient failures are retried twice
answerDocsAgentEvaluationAnswerProvider—Opt-in callback or HTTP provider for actual-answer evaluation
tasksDocsAgentGoldenTask[][]Representative tasks; an empty list is reported as unmeasured

Each task requires id, query, and expect.relevantSources. It can set filters.framework, filters.version, filters.locale, per-task surface, and per-task tokenBudget or topK. Expectations can include allowedSources, forbiddenSources, requiredCitations, minRecallAtK, maxFirstRelevantRank, minUsefulByteRatio, scope, answer, and code-fence matches through examples. filters narrow retrieval; expect.scope asserts the framework, version, or locale of the returned sources without narrowing them first.

DocsAgentEvaluationAnswerProvider is either { provider: "callback", run } or { provider: "http", endpoint, headers?, timeoutMs? }. The callback receives the task id, query, filters, surface, context, retrieved sources, and an abort signal, but never receives the task expectations. The serializable HTTP request omits that callback-only signal. Both providers return { text, citations? }. HTTP auth is opt-in through headers, and header values never appear in reports. Managed external search and HTTP requests require allowNetwork: true, while callbacks remain explicit user code.

DocsAgentGoldenSafetyExpectation

Optional adversarial safety assertions on a golden task. All fields are evaluator-only and must never contain real credentials or private data. Exported as DocsAgentGoldenSafetyExpectation from @farming-labs/docs.

PropertyTypeDescription
promptInjectionDocsAgentGoldenPromptInjectionExpectationAssert prompt-injection resistance using canary markers
poisonedCitationsbooleanAssert that retrieval rejects injected citation payloads
authenticatedContentDocsAgentGoldenAuthenticatedContentExpectationAssert that authenticated content does not leak into public retrieval
freshnessDocsAgentGoldenFreshnessExpectationAssert retrieval-index generation and per-source content digests
rejectConflictingFrameworkVersionsbooleanAssert that sources with conflicting framework version provenance are rejected
deletedSectionTombstonesbooleanAssert that deleted-section tombstones are surfaced
queryVariantsDocsAgentGoldenQueryVariant[]Additional query variant kinds to evaluate alongside the primary query

DocsAgentGoldenPromptInjectionExpectation has markers: string[] (canary fragments that must appear in retrieved context) and an optional forbiddenAnswerText?: string[] (fragments the answer must not repeat; defaults to markers).

DocsAgentGoldenAuthenticatedContentExpectation has optional forbiddenSources?: string[] (protected URLs that public retrieval must not expose) and forbiddenText?: string[] (canary fragments that must not appear in context or answers).

DocsAgentGoldenFreshnessExpectation has optional indexGeneration?: string (expected retrieval-index generation digest) and sourceDigests?: Record<string, string> (expected full-document digest keyed by canonical page or section URL).

DocsAgentGoldenQueryVariant has kind: DocsAgentGoldenQueryVariantKind (a string such as "rephrased" or "adversarial").

All five types are exported from @farming-labs/docs and @farming-labs/docs/server.

DocsAgentGoldenExpectedExample.verification accepts "present", "syntax", or "execute". Runnable examples default to syntax verification and explicitly non-runnable examples default to presence. Execution is never inferred: it requires allowNetwork: true, verification: "execute", and an enabled codeBlocks.validate configuration in "report" mode; only an actual PASS receives credit.

Evaluation is offline by default on mcp-context, with no implicit model or network call. The configured-search and ask-ai-context surfaces exercise the real configured retrieval/context paths, and provider failures or timeouts fail instead of falling back to a different surface. Configured-search preserves provider ranking and does not supplement it with local hits. See Golden agent evaluations for a complete example.


DocsReviewConfig

Docs Review configuration for changed docs files, score thresholds, and GitHub Actions behavior.

Omitted review config is enabled by default. Set review: false to opt out of the review command and generated workflow. The generated CI file is a small repository-root wrapper around the hosted reusable workflow at farming-labs/docs/.github/workflows/docs-review-reusable.yml@main.

PropertyTypeDefaultDescription
enabledbooleantrueEnable Docs Review
ciboolean | DocsReviewCiConfigenabledGitHub Actions behavior and workflow generation
scoreDocsReviewScoreConfig{ threshold: 80 }Healthy score threshold and severity weights
rulesDocsReviewRulesConfigdefault severitiesPer-rule severity overrides
docs.config.ts
export default defineDocs({
  entry: "docs",
  review: {
    ci: {
      name: "agent-docs-review",
      mode: "warn",
      annotations: true,
      comment: true,
    },
    score: {
      threshold: 85,
    },
  },
});

DocsReviewCiConfig

PropertyTypeDefaultDescription
enabledbooleantrueEnable review CI workflow generation
namestring"docs-review"GitHub Actions job/check name for the generated workflow
mode"off" | "warn" | "block""warn"Report only, warn without failing, or block unhealthy PRs
annotationsbooleantrueEmit GitHub workflow annotations
commentbooleantrueReserved for PR comments from the official action/bot

DocsReviewRulesConfig

PropertyDefaultDescription
brokenLinks"error"Check internal docs links
frontmatter"error"Check title and description frontmatter
duplicateSlugs"error"Check duplicate docs routes
invalidMdx"error"Check markdown/MDX read and frontmatter parsing
configExamples"warn"Check config examples when possible
codeFenceMetadata"warn"Check code fences for metadata such as title
runnableMetadata"warn"Check runnable snippets for package manager metadata
agentContext"warn"Validate structured contracts and detect duplicate, generic, incomplete, or ambiguous agent context
commandHealth"warn"Statically validate package managers, scripts, working directories, and docs CLI commands
relatedCoverage"suggestion"Check valid related-page coverage for actionable docs
configConfidence"warn"Report partial static fallback when the config module cannot be evaluated
agentSurfaceDrift"error"Detect discovery, resolved config, contract, route, MCP tool, and schema drift
goldenTasks"warn"Report failed or unmeasured configured golden tasks
agentSkills"warn"Check configured skills for progressive disclosure, reference integrity, compatibility, and script guidance

Rule severities accept "off", "suggestion", "warn", or "error".


DocsRobotsConfig

Runtime and generated robots.txt policy for docs and agent-readable routes. Server-rendered apps serve /robots.txt through the shared docs handler when no static file already owns that path; the CLI writes or appends a static file for static export and committed policies.

PropertyTypeDefaultDescription
enabledbooleantrueEnable or disable the robots policy
pathstringpublic/robots.txt, or static/robots.txt for SvelteKitFile path used by docs robots generate
baseUrlstringsitemap.baseUrl or llmsTxt.baseUrl in the CLIPublic site URL used for the Sitemap: line
aiboolean | "allow" | "disallow""allow"Explicitly allow or disallow common AI crawler user agents
aiUserAgentsstring | string[]built-in common AI crawler listExtra AI user-agent names to include beside the defaults
extraRulesDocsRobotsRule[][]Extra robots rule blocks appended after the generated agent policy
docs.config.ts
export default defineDocs({
  robots: {
    enabled: true,
    baseUrl: "https://docs.example.com",
    path: "public/robots.txt",
    ai: "allow",
  },
});

Generated policy covers the docs entry, .md routes, llms.txt, sitemap routes, AGENTS.md, skill.md, MCP aliases, agent discovery routes, and common AI crawler user agents. Existing files are preserved by default; use docs robots generate --append to add or update the managed block, or --force to replace the file.

The agent discovery JSON advertises this surface as robots.enabled, robots.route, and robots.defaultRoute, so agents can find the policy without guessing.

DocsRobotsRule

PropertyTypeDescription
userAgentstring | string[]User-agent name or names for this rule block
allowstring | string[]Routes to allow
disallowstring | string[]Routes to disallow
crawlDelaynumberOptional crawl-delay value emitted as-is

See CLI for docs robots generate and Configuration for the high-level setup guide.


DocsSearchConfig

Search is enabled by default. If you do not configure anything, the docs API uses the built-in simple adapter with section-based chunking.

docs.config.ts
export default defineDocs({
  entry: "docs",
  search: true,
});

GET /api/docs?query=<query> searches the human projection by default. The exact lowercase audience=agent query parameter selects the agent projection:

GET /api/docs?query=installation&audience=agent

The human projection contains shared plus human-only content; the agent projection contains shared plus agent-only content. audience=human, a missing value, and invalid values all resolve to human. This is content projection, not authentication or authorization.

Built-in providers:

  • simple — zero-config docs search with section chunking
  • typesense — external Typesense collection with optional hybrid mode
  • algolia — external Algolia index
  • mcp — Streamable HTTP MCP endpoint that exposes a search_docs tool
  • custom — your own adapter implementation

SimpleDocsSearchConfig

PropertyTypeDefaultDescription
provider"simple"impliedUse the built-in search adapter
enabledbooleantrueToggle search on or off
maxResultsnumber10Maximum results to return
chunkingDocsSearchChunkingConfig{ strategy: "section" }Controls whether indexing happens per page or per section
docs.config.ts
search: {
  provider: "simple",
  maxResults: 8,
  chunking: {
    strategy: "section",
  },
},

TypesenseDocsSearchConfig

PropertyTypeDefaultDescription
provider"typesense"—Use Typesense as the search backend
baseUrlstring—Typesense API base URL
collectionstring—Collection name used for docs documents
apiKeystring—Search API key
adminApiKeystring—Optional admin key used for on-demand indexing
maxResultsnumber10Maximum results to return
syncOnSearchbooleantrue when adminApiKey is presentRe-index docs on the first search request
queryBystring[]built-in fieldsCustom Typesense query_by fields
mode"keyword" | "hybrid""keyword"Keyword search only, or keyword + embeddings
embeddingsDocsSearchEmbeddingsConfig—Semantic embeddings config for hybrid search
chunkingDocsSearchChunkingConfig{ strategy: "section" }Chunking strategy used before indexing/search
docs.config.ts
search: {
  provider: "typesense",
  baseUrl: process.env.TYPESENSE_URL!,
  collection: "docs",
  apiKey: process.env.TYPESENSE_SEARCH_API_KEY!,
  adminApiKey: process.env.TYPESENSE_ADMIN_API_KEY,
  mode: "hybrid",
  embeddings: {
    provider: "ollama",
    model: "embeddinggemma",
  },
},
terminal
pnpm dlx @farming-labs/docs search sync --typesense

AlgoliaDocsSearchConfig

PropertyTypeDefaultDescription
provider"algolia"—Use Algolia as the search backend
appIdstring—Algolia application id
indexNamestring—Algolia index name
searchApiKeystring—Search API key
adminApiKeystring—Optional admin key used for on-demand indexing
maxResultsnumber10Maximum results to return
syncOnSearchbooleantrue when adminApiKey is presentRe-index docs on the first search request
chunkingDocsSearchChunkingConfig{ strategy: "section" }Chunking strategy used before indexing/search
docs.config.ts
search: {
  provider: "algolia",
  appId: process.env.ALGOLIA_APP_ID!,
  indexName: "docs",
  searchApiKey: process.env.ALGOLIA_SEARCH_API_KEY!,
  adminApiKey: process.env.ALGOLIA_ADMIN_API_KEY,
},
terminal
pnpm dlx @farming-labs/docs search sync --algolia

McpDocsSearchConfig

PropertyTypeDefaultDescription
provider"mcp"—Use an MCP endpoint as the search backend
endpointstring—Streamable HTTP MCP endpoint
headersRecord<string, string>—Extra headers sent to initialize and tool calls
toolNamestring"search_docs"MCP tool name used for search
protocolVersionstring"2025-11-25"MCP protocol version header
maxResultsnumber10Maximum results to request
docs.config.ts
search: {
  provider: "mcp",
  endpoint: "/mcp",
},

Relative endpoints are resolved against the current docs API request URL. That makes local setups like the Next example easy to test without hardcoding a host.

CustomDocsSearchConfig

PropertyTypeDescription
provider"custom"Use your own adapter
adapterDocsSearchAdapter | DocsSearchAdapterFactorySearch runtime implementation
maxResultsnumberMaximum results to return
chunkingDocsSearchChunkingConfigChunking strategy used before your adapter runs
docs.config.ts
import { createCustomSearchAdapter, defineDocs } from "@farming-labs/docs";

export default defineDocs({
  entry: "docs",
  search: createCustomSearchAdapter({
    name: "my-search",
    async search(query, context) {
      return context.documents
        .filter((doc) =>
          `${doc.title} ${doc.section ?? ""} ${doc.content}`.toLowerCase().includes(query.query.toLowerCase()),
        )
        .slice(0, query.limit ?? 10)
        .map((doc) => ({
          id: doc.id,
          url: doc.url,
          content: doc.section ? `${doc.title} — ${doc.section}` : doc.title,
          description: doc.description,
          type: doc.type,
          section: doc.section,
        }));
    },
  }),
});

DocsSearchAdapter

Custom adapters receive normalized source pages and chunked search documents so you can focus on retrieval and ranking instead of rebuilding the docs scan pipeline. DocsSearchQuery.audience and DocsSearchAdapterContext.audience are both "human" | "agent" and contain the resolved request projection; context.documents is projected to match. Hosted index synchronization always uses the human projection. Custom adapters should apply query.audience while filtering and ranking provider results.

search-adapter.ts
import type { DocsSearchAdapter } from "@farming-labs/docs";

export const adapter: DocsSearchAdapter = {
  name: "my-search",
  async search(query, context) {
    return context.documents.map((doc) => ({
      id: doc.id,
      url: doc.url,
      content: doc.section ? `${doc.title} — ${doc.section}` : doc.title,
      description: doc.description,
      type: doc.type,
      section: doc.section,
    }));
  },
};

DocsSearchChunkingConfig

PropertyTypeDefaultDescription
strategy"page" | "section""section"Chunk by whole page or split by headings

SearchSyncOptions — additional sync fields

These options are available on the search sync CLI command and on the SearchSyncOptions interface:

PropertyTypeDescription
syncNamespacestringOptional namespace that scopes the synced corpus. Maximum 1024 characters. Set via --sync-namespace flag or the DOCS_SEARCH_SYNC_NAMESPACE environment variable. A value exceeding 1024 chars is rejected with error code invalid-search-sync-namespace.
siteUrlstringCanonical public URL embedded in indexed documents during sync. Set via --site-url flag.

DocsSearchSourcePage — additional fields

PropertyTypeDescription
canonicalUrlstringOptional canonical public URL override for this page. Relative values are resolved against the request base URL.
agentLastModifiedstringISO-8601 modification time for an explicit agent-source override file (e.g. agent.md), tracked separately from the page's own lastModified.

Retrieval provenance types and exports

The following are exported from @farming-labs/docs and @farming-labs/docs/server:

ExportKindDescription
buildDocsRetrievalDigestProjectionfunctionBuilds a projection object containing a SHA-256 digest (sha256:<hex>) of normalized page content, suitable for change-detection in search indexes.
enrichDocsSearchDocumentsWithProvenancefunctionAttaches retrieval provenance metadata (scope, canonical URL, digest) to an array of search documents.
resolveDocsRetrievalLastModifiedfunctionResolves the canonical last-modified date for a page, preferring the lastmod frontmatter value over the filesystem mtime.
DocsRetrievalSourceProvenancetypeNormalized provenance metadata attached to a retrieved source, including canonicalUrl, lastModified, digest, and scope fields.
DocsRetrievalSourceScopetypeApplicability metadata for a retrieved source: audience, locale, framework, version, versionGroups, and package.
EnrichDocsSearchDocumentsWithProvenanceOptionstypeOptions accepted by enrichDocsSearchDocumentsWithProvenance.

HTTP cache integrity helpers

The following are exported from @farming-labs/docs:

ExportKindDescription
createDocsCacheableResponsefunctionBuild a cacheable Response for an agent-surface request. Computes a strong ETag ("<64-hex-char sha256>"), an RFC 9530 Content-Digest: sha-256=:…: header, and an optional Last-Modified header. Returns an empty-body response for HEAD requests and 304 Not Modified when If-None-Match matches.
formatDocsContentDigestfunctionFormat a SHA-256 hex digest as an RFC 9530 Content-Digest field value (sha-256=:…:).
requestHasFreshDocsDatefunctionReturn true when the request's If-Modified-Since header indicates the client already has a fresh copy.
requestMatchesDocsEtagfunctionReturn true when the request's If-None-Match header matches the supplied strong ETag.
resolveDocsHttpDatefunctionParse a string, Date, or null into an RFC 7231 HTTP-date string, returning null for invalid or absent values.
sha256DocsContentfunctionHash exact bytes (string or Uint8Array) with portable SHA-256; returns the lowercase hex digest. Works in Node, edge, and browser runtimes.
digestDocsContentfunctionHash a string or Uint8Array and return the result in sha256:<hex> provenance format.
DocsCacheableResponseOptionstypeOptions accepted by createDocsCacheableResponse: request, content, status?, headers?, sha256? (pre-computed hex digest), and lastModified? (string | Date | null).

ETag format change: Agent-surface responses now use a strong ETag ("<64-hex-char sha256>") instead of the previous weak form (W/"…"). Clients that cache or compare ETags must update their If-None-Match logic accordingly. Conditional GET and HEAD requests with a matching strong ETag receive 304 Not Modified with the Content-Digest header preserved.

lastModified on discovery responses: DocsStandardsDiscoveryResponseOptions and CreateDocsStandardsResponseOptions both accept a new optional lastModified?: string | Date | null field. When supplied, the response includes a Last-Modified header and supports If-Modified-Since conditional requests.

DocsSearchFacetValue

One selectable search-scope value and the number of matching source pages.

PropertyTypeDescription
valuestringNormalized filter value
countnumberNumber of source pages that carry this value

DocsSearchFacet

Bounded values for one search-scope field.

PropertyTypeDescription
valueCountnumberNumber of distinct values before the response limit is applied
truncatedbooleanWhether additional values were omitted from this response
valuesDocsSearchFacetValue[]Ordered list of values and counts
totalnumberTotal number of distinct values. Alias of valueCount for pagination clients
hasMorebooleanWhether another page of values can be requested using nextCursor
nextCursorstring | undefinedOpaque cursor for the next page. Omitted when this is the final page

DocsSearchFacetsResponse

Cheap, body-free discovery response for the search scopes available in an index. Returned by GET /api/docs?audience=agent&response=facets with Cache-Control: public, max-age=60.

Two optional query parameters refine the ?response=facets request:

ParameterTypeDescription
facetframework | version | package | tagsPin the response to a single field and enable cursor-based pagination for that field. Requires response=facets.
limitinteger 1–100Maximum number of facet values to return per page. Defaults to the built-in cap when omitted.
cursorstringOpaque continuation token returned as nextCursor in a previous response. Accepted alongside response=facets (and the existing response=structured) when facet is set.

When facet is present, only the named field is computed and returned. Check hasMore in the response to determine whether another page is available, then pass the returned nextCursor value as cursor in the next request to retrieve it.

PropertyTypeDescription
format"docs-search-facets.v1"Fixed format identifier
filtersDocsSearchFiltersActive filters applied to the facet computation
matchedPageCountnumberNumber of source pages matched after filters
facetsRecord<string, DocsSearchFacet>Facet values keyed by field name (framework, version, package, tags)

BuildDocsSearchFacetsOptions

Options accepted by buildDocsSearchFacets. Exported from @farming-labs/docs.

PropertyTypeDescription
pagesDocsSearchSourcePage[]Source pages to compute facets over
searchDocsSearchConfigActive search config
audience"human" | "agent"Content projection used when filtering pages
localestringActive locale
baseUrlstringPublic base URL for provenance
filtersDocsSearchFiltersOptional filters to apply before computing facets
indexGenerationstringDigest of the current index for cache freshness
scopeobjectOptional audience/locale scope
facetDocsSearchFilterFieldOptional single field to compute and paginate (framework, version, package, or tags)
limitnumberMaximum number of facet values to return (1–100)
cursorstringOpaque continuation cursor from a previous response
PropertyTypeDescription
-----------------------
pagesDocsSearchSourcePage[]Source pages to compute facets over
searchDocsSearchConfigActive search config
audience"human" | "agent"Content projection used when filtering pages
localestringActive locale
baseUrlstringPublic base URL for provenance
filtersDocsSearchFiltersOptional filters to apply before computing facets
indexGenerationstringDigest of the current index for cache freshness
scopeobjectOptional audience/locale scope

buildDocsSearchFacets

Builds a DocsSearchFacetsResponse from source pages without fetching page content. Exported from @farming-labs/docs.

import { buildDocsSearchFacets } from "@farming-labs/docs";

const response = await buildDocsSearchFacets(options);
// response.format === "docs-search-facets.v1"

DocsSearchEmbeddingsConfig

PropertyTypeDefaultDescription
provider"ollama"—Embeddings provider for hybrid Typesense mode
modelstring—Embedding model id
baseUrlstring"http://127.0.0.1:11434"Ollama base URL

Notes:

  • search: false disables search entirely
  • Search requires the docs API route; static export hides the UI because there is no server route
  • GET /api/docs?query=... remains human by default; only exact audience=agent opts into agent content
  • On Next.js, generated routes from withDocs() pass docsConfig directly into createDocsAPI
  • MCP-backed search works with relative endpoints like /mcp or /.well-known/mcp and absolute remote endpoints like https://docs.example.com/mcp
  • same-origin MCP search_docs routes receive the resolved audience; set forwardAudience: true only when a remote endpoint or custom tool supports the same argument; human-projection requests fall back to local search until then
  • If MCP-backed search points at the same relative MCP route, the built-in search_docs tool falls back to simple search internally to avoid recursive loops
  • On custom/manual Next routes, import createDocsAPI from @farming-labs/next/api and pass the whole config: createDocsAPI(docsConfig)

@farming-labs/theme/api still works for now

@farming-labs/theme/api remains supported as a compatibility import path today, but prefer @farming-labs/next/api in Next.js apps. The theme-level path will be deprecated.


ApiReferenceConfig

Generates API reference pages from framework route conventions or a local/remote OpenAPI JSON or YAML document.

Use route scanning when the API lives in the same project and no contract exists. Use specUrl for an existing project-relative file, absolute URL, or request-relative URL.

Current support

apiReference is supported in Next.js, TanStack Start, Farm.js, SvelteKit, Astro, and Nuxt.

Renderer dependencies are bundled

The native "farming-labs" renderer has no extra renderer dependency and is the default on every framework. You do not need to install fumadocs-openapi, fumadocs-ui, @scalar/core, or related packages separately. Set renderer: "scalar" on any framework or renderer: "fumadocs" on Next.js to use the bundled alternatives. Use the Next.js fumadocsRenderer() helper when the Fumadocs renderer needs playground or runtime options.

Important

apiReference in docs.config controls scanning, theming, routeRoot, and exclude.

In Next.js, withDocs() also generates the route automatically.

Farm.js serves the route through withDocs(). In TanStack Start, SvelteKit, Astro, and Nuxt, you must additionally add the framework route handler for /{path}.

The native renderer uses the same normalized operation model as Markdown, Ask AI, search, and MCP. It includes operation search and grouping, stable operation permalinks, version switching, parameters, request and response schemas, authentication metadata, copyable cURL examples, and responsive light/dark UI. Each operation also includes a Try it tab that executes requests in the browser and renders status, timing, response headers, and formatted response bodies.

The request console starts from the operation's first server, parameters, media type, and schema examples. Readers can change the server, path/query/header parameters, request body, browser credential mode, and supported bearer, basic, or API-key authentication values before sending. Authentication values stay in memory and are never persisted by Farming Labs. Cookie parameters remain browser-managed; choose Include cookies when the target API relies on them. POST, PUT, PATCH, and DELETE operations require an explicit confirmation before Send request is enabled.

Browser security still applies

Cross-origin APIs must allow the docs origin through CORS. A relative server URL resolves against the current docs origin, so replace it with an absolute API URL when a remotely loaded contract uses a server path relative to its own host. The console supports JSON and raw request bodies; specialized multipart file controls are not generated yet.

The interactive console is part of the native "farming-labs" renderer. Selecting "scalar" or "fumadocs" preserves that renderer's own request tooling and presentation.

OpenAPI document mode

specUrl accepts project-relative JSON/YAML files such as ./openapi.yaml, hosted JSON/YAML URLs, and request-relative URLs such as /api/openapi.json.

Contract validation and source limits

Sources must use Swagger 2.0 or OpenAPI 3.0.x, 3.1.x, or 3.2.x. Invalid operations and duplicate operationId values are rejected before rendering. Each local or remote document is limited to 5 MiB, a build can load at most 32 documents, and remote requests time out after 10 seconds.

Agent schema discovery

When apiReference is enabled, the shared docs API also serves GET /api/docs?format=openapi. The agent discovery spec advertises that OpenAPI schema URL, and generated llms.txt / AGENTS.md / skill.md output points agents there before they scrape API reference pages.

PropertyTypeDefaultDescription
enabledbooleantrue inside the objectEnable generated API reference pages
rendererApiReferenceRendererInput"farming-labs"Renderer name or { name, options } object. Scalar works on every framework; Fumadocs and its typed fumadocsRenderer() helper are available on Next.js
pathstring"api-reference"URL path where the generated API reference lives
specUrlstring—Project-relative file, file: URL, absolute HTTP(S) URL, or request-relative URL for an OpenAPI JSON/YAML document. When set, framework route scanning is skipped
overlaysreadonly string[][]Ordered OpenAPI Overlay 1.0/1.1 JSON/YAML sources for the single specUrl
versionsReadonly<Record<string, ApiReferenceVersionConfig>>—Named OpenAPI sources with stable routes, labels, and optional per-version overlays
defaultVersionstring—Required key from versions; selects the default unversioned response
routeRootstring"api"Filesystem route root to scan. Bare values like "api" resolve inside app/ or src/app/; full values like "app/internal-api" and "src/app/v2/api" are supported
excludestring[][]Route entries to omit from the generated reference. Accepts URL-style paths like "/api/hello" or route-root-relative values like "hello" / "hello/route.ts"
mcpboolean | DocsOpenApiMcpConfigfalseDeny-by-default projection of selected OpenAPI operations into MCP tools
docs.config.ts
apiReference: {
  enabled: true,
  path: "api-reference",
  routeRoot: "api",
  exclude: ["/api/internal/health", "internal/debug"],
},
docs.config.ts
apiReference: {
  enabled: true,
  path: "api-reference",
  specUrl: "./openapi.yaml",
  renderer: "farming-labs",
},

Pin an alternate renderer when you want its existing presentation:

docs.config.ts
apiReference: {
  enabled: true,
  specUrl: "./openapi.yaml",
  renderer: "scalar", // or "fumadocs" in Next.js
},

Pass typed Fumadocs playground and runtime options on Next.js:

docs.config.ts
import { fumadocsRenderer } from "@farming-labs/next/fumadocs-renderer";
import { authProviders } from "./app/api-reference/auth-providers";

apiReference: {
  enabled: true,
  specUrl: "./openapi.yaml",
  renderer: fumadocsRenderer({
    playground: {
      enabled: true,
      authProviders,
    },
    oauthRedirectUrl: "/api/docs/oauth",
    proxyUrl: "/api/docs/proxy",
    showResponseSchema: true,
    schemaUI: { showExample: true },
  }),
},

The helper accepts Fumadocs createOpenAPIPage() options together with the proxyUrl and disableCache server options. Browser-side providers and callbacks must not contain secrets. Proxy and OAuth URLs must point to application-owned routes.

docs.config.ts
apiReference: {
  enabled: true,
  path: "api-reference",
  specUrl: "https://petstore3.swagger.io/api/v3/openapi.json",
},
docs.config.ts
apiReference: {
  enabled: true,
  path: "api-reference",
  versions: {
    v1: { specUrl: "./openapi/v1.yaml", label: "Version 1" },
    v2: {
      specUrl: "./openapi/v2.yaml",
      label: "Version 2",
      overlays: ["./openapi/public.overlay.yaml"],
    },
  },
  defaultVersion: "v2",
},

When specUrl is set, routeRoot and exclude are ignored and the API reference is rendered from the configured document instead. Project-relative sources must remain inside the project root; use an explicit file: URL for a trusted local contract outside it.

versions is mutually exclusive with specUrl and top-level overlays. Every version has a stable /{path}/{version} UI route and GET /api/docs?format=openapi&version={version} schema identity. The unversioned routes use defaultVersion. Discovery, llms.txt, and the API catalog publish all version identities.

Overlay sources support local files, file: URLs, absolute HTTP(S) URLs, and request-relative URLs. Farming Labs supports OpenAPI Overlay 1.0.x and 1.1.x, applies overlays and actions in their declared order, and evaluates targets with RFC 9535 JSONPath before $ref bundling. Invalid overlay documents, invalid or unmatched targets, incompatible updates, and invalid final OpenAPI contracts are reported as source diagnostics. Overlay sources use the same timeout, size, and local containment protections as OpenAPI sources.

$ref supports internal and external URI references with JSON Pointer fragments. External local or remote JSON/YAML documents are fetched once per build and rewritten into the returned OpenAPI document, so downstream renderers and projections receive a self-contained contract. References from project-relative entry documents stay inside the project root. The synchronous builder cannot load HTTP(S) references; use buildApiReferenceOpenApiDocumentAsync() or a framework route for mixed local/remote contracts. Named anchors and $dynamicRef resolution are not supported yet.

Normalized OpenAPI operation model

Server integrations can import buildNormalizedOpenApiModel and its NormalizedOpenApi* types from @farming-labs/docs/server. The additive API produces stable operation IDs and slugs, method and path selectors, tags, parameters, request bodies, responses, schemas, examples, servers, and the security schemes used by each operation. Farming Labs uses the same model for OpenAPI-backed MCP tools, keeping renderer and agent projections aligned.

DocsOpenApiMcpConfig

PropertyTypeDefaultDescription
operationsreadonly string[][]Allowed operationId or METHOD /path selectors
baseUrlstringfirst OpenAPI serverAPI origin used for operation requests
allowMutationsbooleanfalsePermit allowlisted POST, PUT, PATCH, and DELETE operations
headersRecord<string, string> | callback—Server-owned credentials resolved for each operation
timeoutMsnumber10000Request timeout
allowInsecureHttpbooleanfalsePermit plain HTTP destinations for explicitly trusted development environments
allowPrivateNetworkbooleanfalsePermit loopback, private, link-local, and reserved destinations
resolveHostcallbackNode DNSServer-owned DNS resolver used to validate every destination
maxRedirectsnumber0Validated redirects to follow; redirects are blocked by default
maxResponseBytesnumber1000000Maximum response bytes read into an MCP result
requestsPerMinutenumber60Per-principal, per-operation request budget
maxConcurrentRequestsnumber4Per-principal, per-operation concurrency budget

An operation can also opt in with x-farming-labs-mcp: true. Security requirements and only the referenced components.securitySchemes are included in the tool metadata. Credentials remain server-owned and are applied after parsed tool parameters.

Every request and redirect is DNS-checked before egress. HTTPS, public-network destinations, bounded response streaming, rate limits, and concurrency limits are enforced by default. Development servers on private networks must opt in explicitly; never enable those exceptions for untrusted OpenAPI documents or model-supplied destinations.

This does not remove the framework route requirement on adapters that do not own the application server. Next.js and Farm.js serve the route through withDocs(). TanStack Start, SvelteKit, Astro, and Nuxt still need their /{path} handler files so the docs app has a route that serves the generated API reference page.

When output: "export" is used in Next.js, the generated API reference route is skipped automatically because it requires a server route handler.

Scanned route conventions:

  • Next.js: app/api/**/route.ts and src/app/api/**/route.ts
  • TanStack Start: src/routes/api.*.ts and nested route files inside the configured route root
  • Farm.js: app/api/**/route.ts and src/app/api/**/route.ts
  • SvelteKit: src/routes/api/**/+server.ts or +server.js
  • Astro: src/pages/api/**/*.ts or .js
  • Nuxt: server/api/**/*.ts or .js

Minimal route handlers for non-Next frameworks:

TanStack Start — src/routes/api-reference.index.ts
import { createFileRoute } from "@tanstack/react-router";
import { createTanstackApiReference } from "@farming-labs/tanstack-start/api-reference";
import docsConfig from "../../docs.config";

const handler = createTanstackApiReference(docsConfig);

export const Route = createFileRoute("/api-reference/")({
  server: {
    handlers: {
      GET: handler,
    },
  },
});

Create a second src/routes/api-reference.$.ts file with the same handler and createFileRoute("/api-reference/$").

SvelteKit — src/routes/api-reference/+server.ts
import { createSvelteApiReference } from "@farming-labs/svelte/api-reference";
import config from "$lib/docs.config";

export const GET = createSvelteApiReference(config);

Create src/routes/api-reference/[...slug]/+server.ts with the same GET export.

Astro — src/pages/api-reference/index.ts
import { createAstroApiReference } from "@farming-labs/astro/api-reference";
import config from "../../lib/docs.config";

export const GET = createAstroApiReference(config);

Create src/pages/api-reference/[...slug].ts with the same GET export.

Nuxt — server/routes/api-reference/index.ts
import { defineApiReferenceHandler } from "@farming-labs/nuxt/api-reference";
import config from "~/docs.config";

export default defineApiReferenceHandler(config);

Create server/routes/api-reference/[...slug].ts with the same default export.


ChangelogConfig

Generates changelog listing and entry pages from dated MDX folders inside the docs content tree.

Current support

The turn-key generated changelog pages are currently wired in Next.js when you use withDocs().

PropertyTypeDefaultDescription
enabledbooleantrue inside the objectEnable generated changelog pages
pathstring"changelog"URL path where the changelog listing lives inside the docs layout
contentDirstring"changelog"Source directory under the docs content root. Example: app/docs/changelog/2026-03-04/page.mdx
titlestring"Changelog"Listing page title
descriptionstring—Listing page description shown in the header and metadata
searchbooleantrueShow the built-in changelog search field
actionsComponentReactNode | Component—Custom action content rendered in the changelog rail
docs.config.ts
changelog: {
  enabled: true,
  path: "changelogs",
  contentDir: "changelog",
  title: "Changelog",
  description: "Latest product updates and release notes.",
  search: true,
},

With entry: "docs" and the config above, the public pages render at:

  • /docs/changelogs
  • /docs/changelogs/2026-03-04

When you use withDocs(), the route files are generated automatically. There is no separate __changelog.generated.tsx file to maintain.

ChangelogFrontmatter

Optional frontmatter fields for each changelog entry:

PropertyTypeDescription
titlestringEntry title shown in the feed and entry page
descriptionstringShort summary shown in the feed and metadata
imagestringCover image path or URL
authorsstring | string[]Author name(s) shown in entry meta
versionstringCompact version badge like v0.1.0
tagsstring[]Badge list shown in the feed and entry page
pinnedbooleanKeep important entries pinned to the top of the listing
draftbooleanHide the entry from generated routes and search indexes
---
title: "OpenAPI mode is now the default"
description: "The docs example now ships with the faster API reference experience."
version: "v0.1.13"
tags: ["api-reference", "next"]
---

GithubConfig

GitHub repository configuration. Enables "Edit on GitHub" links in page footers.

PropertyTypeDefaultDescription
urlstringrequiredRepository URL (e.g. "https://github.com/my-org/repo")
branchstring"main"Branch name
directorystring—Content subdirectory for monorepos
// Simple
github: "https://github.com/my-org/my-docs",

// Monorepo
github: {
  url: "https://github.com/my-org/monorepo",
  branch: "main",
  directory: "apps/docs/docs",
},

The "Edit on GitHub" link only appears when both url and directory are provided.


onCopyClick and CodeBlockCopyData

Optional callback on DocsConfig. Fired when the user clicks the copy button on a code block — in addition to copying to the clipboard. Use it for analytics, logging, or custom behavior. See Code block copy callback for setup (Next.js / TanStack Start vs SvelteKit / Astro / Nuxt).

CodeBlockCopyData

Argument passed to onCopyClick(data: CodeBlockCopyData).

PropertyTypeDescription
titlestring | undefinedCode block title (e.g. from fenced code meta), if present
contentstringRaw code content (what was copied)
urlstringCurrent page URL at the time of copy
languagestring | undefinedLanguage/syntax hint (e.g. "tsx", "bash"), if present
docs.config.ts
import type { CodeBlockCopyData } from "@farming-labs/docs";
import { defineDocs } from "@farming-labs/docs";

export default defineDocs({
  entry: "docs",
  onCopyClick(data: CodeBlockCopyData) {
    // data.title, data.content, data.url, data.language
    console.log("Copied:", data.language, data.title ?? "(no title)");
  },
});

feedback, FeedbackConfig, AgentFeedbackConfig, and feedback payloads`

Optional docs page feedback UI. When enabled, a built-in "Good / Bad" prompt is rendered at the end of the page and emits a feedback payload when the user clicks one of the buttons.

FeedbackConfig

PropertyTypeDefaultDescription
enabledbooleantrueShow or hide the feedback UI
questionstring"How is this guide?"Prompt shown above the buttons
positiveLabelstring"Good"Label for the positive button
negativeLabelstring"Bad"Label for the negative button
requireCommentbooleanfalseRequire a non-empty comment before feedback can be submitted
onFeedback(data: DocsFeedbackData) => void—Callback fired when the user clicks a button
agentboolean | AgentFeedbackConfigtrueAgent feedback routes served through /api/docs

DocsFeedbackData

PropertyTypeDescription
value"positive" | "negative"Which feedback button the user clicked
titlestring | undefinedCurrent page title, when available
descriptionstring | undefinedCurrent page description, when available
urlstringFull current page URL
pathnamestringCurrent pathname without origin
pathstringAlias of pathname
entrystringDocs entry root, e.g. "docs"
slugstringPage slug relative to the docs entry
localestring | undefinedActive locale when docs i18n is enabled
docs.config.ts
import type { DocsFeedbackData } from "@farming-labs/docs";
import { defineDocs } from "@farming-labs/docs";

export default defineDocs({
  entry: "docs",
  feedback: {
    enabled: true,
    onFeedback(data: DocsFeedbackData) {
      console.log("Feedback:", data.value, data.slug, data.url);
    },
  },
});

This docs site persists feedback through a local Next.js route at website/app/api/feedback/route.ts, backed by the DocsFeedback Prisma model in website/prisma/schema.prisma.

See Page feedback for framework-specific notes. The built-in UI does not require a separate client bridge file.

AgentFeedbackConfig

Optional machine-facing feedback routes for agents, scripts, or evaluation flows.

PropertyTypeDefaultDescription
enabledbooleantrueEnable or disable the agent feedback endpoints
routestring"/api/docs/agent/feedback"Public HTTP route for feedback submission
schemaRoutestring${route}/schemaPublic HTTP route for the machine-readable schema
schemaRecord<string, unknown>built-in payload schemaSchema used to validate the payload object
onFeedback(data: DocsAgentFeedbackData) => Promise<void> | void—Async callback fired after a valid submission

Notes:

  • agent feedback routes are enabled by default; set feedback: false or feedback: { agent: false } to opt out
  • feedback.agent does not enable the human footer UI by itself
  • the request body always uses { context?, payload }
  • without feedback.agent.onFeedback, valid submissions return { ok: true, handled: false }
  • submitted feedback is not added to Ask AI prompts or docs search context by the framework
  • in Next.js, withDocs() adds the public route rewrites automatically
  • Astro, SvelteKit, TanStack Start, and Nuxt expose the same feedback contract through the shared /api/docs?feedback=agent query route
  • the shared /api/docs handler is still the source of truth, so ?feedback=agent also works
  • agents can discover APIs through GET or HEAD /.well-known/api-catalog (RFC 9727), and can discover integrity-checked skills through GET or HEAD /.well-known/agent-skills/index.json
  • agents should discover site identity, locale config, capability flags, search, active markdown routes, Accept: text/markdown, Signature-Agent support, JSON-LD structured data, llms.txt, OpenAPI schema routes, sitemap routes, robots.txt, AGENTS.md, skill.md, skills, MCP, and feedback routes with GET /.well-known/agent.json; GET /.well-known/agent is the public fallback and GET /api/docs/agent/spec is the canonical framework route

DocsAgentFeedbackContext

PropertyTypeDescription
pagestring | undefinedDocs page path such as "/docs/installation"
urlstring | undefinedFull docs URL when available
slugstring | undefinedDocs slug relative to the entry root
localestring | undefinedActive locale when docs i18n is enabled
sourcestring | undefinedArbitrary source label such as "md-route", "mcp", or "api"

DocsAgentFeedbackData

PropertyTypeDescription
contextDocsAgentFeedbackContext | undefinedOptional docs/page transport context
payloadRecord<string, unknown>Agent feedback payload validated by feedback.agent.schema

analyzeDocsAgentFeedback() converts recurring failed reports into evidence clusters, golden-task drafts, issue drafts, and a draft docs PR description. readDocsAgentFeedbackFile() accepts JSON arrays/objects and JSON Lines. Both are exported from @farming-labs/docs/server; the CLI wrapper is docs agent feedback --input <path> [--write].

docs.config.ts
import { defineDocs } from "@farming-labs/docs";

export default defineDocs({
  entry: "docs",
  feedback: {
    agent: {
      enabled: true,
      route: "/api/docs/agent/feedback",
      async onFeedback(data) {
        console.log(data.context?.page, data.payload);
      },
    },
  },
});
default request body
{
  "context": {
    "page": "/docs/installation",
    "source": "md-route"
  },
  "payload": {
    "task": "install docs in an existing Next.js app",
    "understanding": "partial",
    "outcome": "implemented",
    "confidence": 0.78
  }
}

Quick checks:

curl "http://127.0.0.1:3000/.well-known/agent.json"

curl -I "http://127.0.0.1:3000/.well-known/api-catalog"

curl "http://127.0.0.1:3000/.well-known/agent-skills/index.json"

curl "http://127.0.0.1:3000/AGENTS.md"

curl "http://127.0.0.1:3000/skill.md"

curl "http://127.0.0.1:3000/api/docs/agent/feedback/schema"

curl -X POST "http://127.0.0.1:3000/api/docs/agent/feedback" \
  -H "content-type: application/json" \
  -d '{"payload":{"task":"demo","outcome":"implemented"}}'

ThemeToggleConfig

Light/dark mode toggle in the sidebar.

PropertyTypeDefaultDescription
enabledbooleantrueShow or hide the toggle
default"light" | "dark" | "system""system"Forced theme when enabled: false
mode"light-dark" | "light-dark-system""light-dark"Toggle behavior
// Show toggle (default)
themeToggle: true,

// Hide toggle, follow system preference
themeToggle: false,

// Hide toggle, force dark mode
themeToggle: { enabled: false, default: "dark" },

// Show toggle with system option
themeToggle: { mode: "light-dark-system" },

Breadcrumb navigation above page content.

PropertyTypeDefaultDescription
enabledbooleantrueShow or hide breadcrumbs
componentComponent—Custom breadcrumb component (Next.js only)
breadcrumb: true,              // show (default)
breadcrumb: false,             // hide
breadcrumb: { enabled: false },  // hide

SidebarConfig

Sidebar visibility and customization. See Sidebar for the full guide including custom sidebar components.

PropertyTypeDefaultDescription
enabledbooleantrueShow or hide the sidebar
component(props: SidebarComponentProps) => ReactNode—Custom sidebar render function. Receives { tree, collapsible, flat }. Supported in Next.js and TanStack Start — other frameworks use slots.
footerReactNode—Content rendered below nav items
bannerReactNode—Content rendered above nav items
collapsiblebooleantrueWhether sidebar is collapsible on desktop
flatbooleanfalseRender all items flat (Mintlify-style, no collapsible groups)
folderIndexBehavior"link" | "toggle" | "hidden"adapter defaultWhether folder parents with their own page navigate, only expand/collapse, or render as label-only groups
folderIndexBehaviorOverridesRecord<string, "link" | "toggle" | "hidden">—Selective per-folder overrides keyed by the folder landing-page URL

Folder landing pages can still override both of those with frontmatter:

page.mdx
---
sidebar:
  folderIndexBehavior: "toggle"
---

SidebarComponentProps

Props passed to a custom sidebar component:

PropertyTypeDescription
treeSidebarTreeFull page tree with all parent-child relationships
collapsiblebooleanWhether folders are collapsible
flatbooleanWhether folders are rendered flat

SidebarTree

PropertyTypeDescription
namestringRoot name (e.g. "Docs")
childrenSidebarNode[]Top-level sidebar items

SidebarNode

A union of SidebarPageNode | SidebarFolderNode.

SidebarPageNode

PropertyTypeDescription
type"page"Node type
namestringDisplay name
urlstringPage URL
iconunknownIcon from registry

SidebarFolderNode

PropertyTypeDescription
type"folder"Node type
namestringDisplay name
iconunknownIcon from registry
indexSidebarPageNodeFolder's landing page
childrenSidebarNode[]Child pages and sub-folders
collapsiblebooleanWhether this folder collapses
defaultOpenbooleanWhether it starts open
sidebar: true,                     // show (default)
sidebar: false,                    // hide
sidebar: { collapsible: false },   // non-collapsible
sidebar: { flat: true },           // Mintlify-style flat
sidebar: { folderIndexBehavior: "toggle" }, // all folder parents only toggle children
sidebar: {
  folderIndexBehavior: "link",
  folderIndexBehaviorOverrides: {
    "/docs/components": "toggle",
    "/docs/customization": "toggle",
  },
},
sidebar: {                         // custom component
  component: ({ tree }) => <MySidebar tree={tree} />,
},

DocsTheme

Theme configuration returned by theme factories.

PropertyTypeDescription
namestringTheme identifier (e.g. "fumadocs", "darksharp")
uiUIConfigVisual configuration — colors, typography, layout

Created via theme factories:

import { fumadocs } from "@farming-labs/theme";
import { darksharp } from "@farming-labs/theme/darksharp";
import { pixelBorder } from "@farming-labs/theme/pixel-border";
import { colorful } from "@farming-labs/theme/colorful";

theme: fumadocs({ ui: { /* overrides */ } }),

UIConfig

Fine-grained visual configuration passed to theme factories.

PropertyTypeDescription
colorsColorsConfigTheme color tokens
typographyTypographyConfigFont families, heading styles
radiusstringGlobal border-radius (e.g. "0px", "0.5rem")
layoutLayoutConfigContent width, sidebar width, TOC settings
codeBlockCodeBlockConfigSyntax highlighting and line numbers
sidebarSidebarStyleConfigSidebar visual style
cardCardConfigCard component styling

ColorsConfig

All color tokens. Accepts any CSS color value (hex, rgb, oklch, hsl).

TokenDescription
primaryPrimary brand color
primaryForegroundText color on primary backgrounds
backgroundPage background
foregroundDefault text color
mutedMuted background (e.g. code blocks)
mutedForegroundText on muted backgrounds
borderDefault border color
cardCard background
cardForegroundCard text color
accentAccent color
accentForegroundText on accent backgrounds
secondarySecondary color
secondaryForegroundText on secondary backgrounds
popoverPopover/dropdown background
popoverForegroundPopover text color
ringFocus ring color
colors: {
  primary: "oklch(0.72 0.19 149)",
  background: "hsl(0 0% 2%)",
  border: "#262626",
},

LayoutConfig

PropertyTypeDefaultDescription
contentWidthnumber—Max width of content area in px
sidebarWidthnumber—Sidebar width in px
tocWidthnumber—Table of contents width in px
toc.enabledbooleantrueShow table of contents
toc.depthnumber3Max heading depth for TOC
header.heightnumber—Header height in px
header.stickybooleantrueSticky header

CodeBlockConfig

PropertyTypeDefaultDescription
showLineNumbersbooleanfalseShow line numbers
showCopyButtonbooleantrueShow copy button
themestring—Shiki theme for light mode
darkThemestring—Shiki theme for dark mode

SidebarStyleConfig

PropertyTypeDefaultDescription
style"default" | "bordered" | "floating""default"Sidebar visual style
backgroundstring—Background color override
borderColorstring—Border color override

TypographyConfig

typography: {
  font: {
    style: {
      sans: "Inter, sans-serif",
      mono: "JetBrains Mono, monospace",
    },
    h1: { size: "2.25rem", weight: 700, letterSpacing: "-0.02em" },
    h2: { size: "1.75rem", weight: 600 },
    h3: { size: "1.25rem", weight: 600 },
    body: { size: "1rem", lineHeight: "1.75" },
    small: { size: "0.875rem" },
  },
},

FontStyle

Used for each heading level and body text.

PropertyTypeDescription
sizestringCSS font-size (e.g. "2.25rem", "clamp(1.8rem, 3vw, 2.5rem)")
weightstring | numberCSS font-weight (e.g. 700, "bold")
lineHeightstringCSS line-height (e.g. "1.2", "28px")
letterSpacingstringCSS letter-spacing (e.g. "-0.02em")

AIConfig

RAG-powered AI chat. See Ask AI for usage guide.

PropertyTypeDefaultDescription
enabledbooleanfalseEnable AI chat
mode"search" | "floating""search"UI mode: integrated in search or floating widget
position"bottom-right" | "bottom-left" | "bottom-center""bottom-right"Floating button position (only when mode: "floating")
floatingStyle"panel" | "modal" | "popover" | "full-modal""panel"Floating chat visual style
triggerComponentComponent—Custom floating button component
modelstring"gpt-4o-mini"LLM model name (OpenAI-compatible)
systemPromptstringauto-generatedCustom system prompt
baseUrlstring"https://api.openai.com/v1"OpenAI-compatible API base URL
apiKeystringprocess.env.OPENAI_API_KEYAPI key for the LLM provider
maxResultsnumber5Number of doc pages to include as RAG context
useMcpboolean | DocsAskAIMcpConfigfalseRoute Ask AI retrieval through MCP search_docs without changing the search API
suggestedQuestionsstring[]—Pre-filled questions shown when chat is empty
aiLabelstring"AI"Display name for the AI assistant
feedbackboolean | DocsAskAIFeedbackConfigtrueCopy, like, and dislike action row for completed answers
onActions(data: DocsAskAIActionData) => void | Promise<void>—Callback for copy, like, and dislike response actions
packageNamestringinferred from docs contextOptional package-name override for import examples
docsUrlstring—Base URL the AI uses for links
loaderstring"shimmer-dots"Loading indicator variant ("shimmer-dots", "circular", "dots", "typing", "wave", "bars", "pulse", "pulse-dot", "terminal", "text-blink", "text-shimmer", "loading-dots")
loadingComponent(props: { name: string }) => ReactNode—Custom loading component (overrides loader, Next.js only)

Floating styles

StyleDescription
"panel"Tall panel sliding up from the button. No backdrop.
"modal"Centered modal with backdrop overlay, like Cmd+K search.
"popover"Compact popover near the button.
"full-modal"Full-screen immersive overlay with pills for suggested questions.

PageActionsConfig

Action buttons shown on each docs page. See Page Actions for usage guide.

PropertyTypeDefaultDescription
position"above-title" | "below-title""below-title"Where to render the buttons
alignment"left" | "right""left"Horizontal alignment of the action row
copyMarkdownboolean | CopyMarkdownConfigfalse"Copy Markdown" button
openDocsboolean | OpenDocsConfigfalse"Open in LLM" dropdown

CopyMarkdownConfig

PropertyTypeDefaultDescription
format"markdown" | "text""markdown"Content format copied by the button: "markdown" fetches the .md route; "text" reads visible page text
enabledbooleanfalseShow the button
labelstring"Copy page"Button label shown before the page is copied
copiedLabelstring"Copied!"Button label shown after a successful copy
includeTitlebooleanfalsePrepend the current page title to copied content. In "markdown" format this produces a # Title heading; in "text" format it prepends the plain title. Skipped if the content already begins with the title.

OpenDocsConfig

PropertyTypeDefaultDescription
enabledbooleanfalseShow the dropdown
target"markdown" | "page" | "source" | "github""markdown"URL inserted into {url} for built-in provider prompts
promptstring"Read this documentation: {url}"Prompt text for built-in providers
providersOpenDocsProvider[]ChatGPT, ClaudeLLM/tool providers

OpenDocsProvider

PropertyTypeDescription
idstringBuilt-in provider preset ("chatgpt", "claude", "cursor", "gemini", "copilot", "perplexity", "github")
namestringDisplay name override
iconReactNodeIcon rendered next to the name
target"markdown" | "page" | "source" | "github"Per-provider target override
promptstringPer-provider prompt override
mode"web" | "app"Cursor-only option. "app" uses the Cursor app deeplink
urlTemplatestringCustom URL template. Supports {prompt}, {url}, {pageUrl}, {markdownUrl}, {sourceUrl}, {mdxUrl}, and {githubUrl}.
promptUrlTemplatestringOptional URL template used by the built-in Prompt component. {prompt} is replaced with the prompt text.
pageActions: {
  copyMarkdown: { enabled: true },
  openDocs: {
    enabled: true,
    target: "markdown",
    providers: ["chatgpt", "claude", "cursor"],
  },
},

LastUpdatedConfig

Controls the "Last updated" date shown on docs pages.

PropertyTypeDefaultDescription
enabledbooleantrueShow or hide the last-updated date
labelstring"Last updated"Text prefix shown before the formatted date
position"footer" | "below-title""footer"Where to render the date: next to "Edit on GitHub" or below the page title
docs.config.ts
lastUpdated: {
  label: "Updated",
  position: "below-title",
},

Set label to an empty string to show only the formatted date with no prefix. Use any locale-appropriate string to rebrand the prefix text (e.g. "Dernière mise à jour" for French docs). Date formatting uses UTC so the calendar day is deployment-timezone independent.

ReadingTimeConfig

Estimated reading-time label shown on docs pages.

PropertyTypeDefaultDescription
enabledbooleanfalseExplicitly opt in to the computed read-time label
wordsPerMinutenumber220Word-count rate used for the estimate
format"long" | "short""long"Label style: 3 min read for "long" or 3 min for "short"
includeCodebooleanfalseWhether fenced and inline code blocks count toward the reading time estimate. Disabled by default so the label reflects human prose. Set to true for code-heavy guides.
readingTime: {
  enabled: true,
  wordsPerMinute: 220,
  format: "short",
},

Notes:

  • readingTime is disabled by default; an options object must include enabled: true

  • the estimate strips code blocks, inline code, links, images, HTML, and URLs before counting

  • set includeCode: true to preserve fenced and inline code word counts in the estimate (useful for code-heavy guides where examples make up a significant portion of the page)

  • format changes only the rendered label; it does not affect word counting or per-page overrides

  • page frontmatter can disable or override the value with readingTime: false, readingTime: true, or readingTime: 8, even when the global config is off

  • placement follows the page title slot: it sits directly below the page actions row when page actions are shown above or below the title


DocsMetadata

SEO metadata configuration. Page title and description also feed the generated Schema.org JSON-LD structured data for each docs page.

PropertyTypeDefaultDescription
titleTemplatestring—Page title template. %s is replaced with the page title.
descriptionstring—Default meta description
twitterCard"summary" | "summary_large_image"—Twitter card type
metadata: {
  titleTemplate: "%s – My Docs",
  description: "Documentation for my project",
},

Structured Data

Every docs page emits Schema.org JSON-LD automatically. The generated object uses @type: "TechArticle" and includes the page title, description, canonical URL, dateModified, and a BreadcrumbList.

No separate config flag is required. Absolute URLs are resolved from the first available public base URL in sitemap.baseUrl, llmsTxt.baseUrl, robots.baseUrl, or ai.docsUrl; otherwise the page route is emitted as a relative URL.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "Installation",
  "description": "Install the framework",
  "url": "https://docs.example.com/docs/installation",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://docs.example.com/docs/installation"
  },
  "dateModified": "2026-05-10T00:00:00.000Z",
  "breadcrumb": {
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "Docs",
        "item": "https://docs.example.com/docs"
      },
      {
        "@type": "ListItem",
        "position": 2,
        "name": "Installation",
        "item": "https://docs.example.com/docs/installation"
      }
    ]
  }
}
</script>

The agent discovery spec advertises this as capabilities.structuredData and structuredData.schema: "https://schema.org/TechArticle".


OGConfig

Open Graph image configuration. When endpoint is set, each docs page gets a dynamic OG image: the framework passes the page’s title and description (from frontmatter) to the endpoint, and the layout injects the resulting image URL into og:image and twitter:image meta tags. For a full guide (how dynamic OG works, what context is passed, and how the docs website implements it), see OG Images.

PropertyTypeDefaultDescription
enabledbooleanfalseEnable OG image generation
type"static" | "dynamic"—Static images or dynamic generation
endpointstring—API endpoint for dynamic OG images
defaultImagestring—Fallback OG image URL

Example (Next.js): In docs.config.ts, set og.endpoint to your OG route path. Then implement app/api/og/route.tsx (or route.ts) that accepts title and description query params and returns a 1200×630 image (e.g. using ImageResponse from next/og).

og: {
  enabled: true,
  type: "dynamic",
  endpoint: "/api/og",
},

Testing your OG image

  1. Start the dev server for the app that serves the OG route (e.g. cd website && pnpm dev or cd examples/next && pnpm dev).
  2. Open the OG URL in the browser, e.g.
    http://localhost:3000/api/og?title=Test&description=My+description
    (use your app’s port if different).
  3. After changing the OG route code, refresh the page. If the image doesn’t update, do a hard refresh (e.g. Cmd+Shift+R on macOS, Ctrl+Shift+R on Windows) or open the URL in a private/incognito window. You can also add a cache-busting query param (e.g. &_=1, &_=2) to force a new request.

Page Frontmatter

Each docs page (.mdx / .md) supports these frontmatter fields:

PropertyTypeDescription
titlestringRequired. Page title, shown in sidebar and page heading
descriptionstringPage description, used for meta tags and search
relatedstring[]Related doc URLs rendered as a comma-separated metadata line in .md routes and MCP page output
agentPageAgentFrontmatterPer-page structured task contract and compaction token budget
sidebarPageSidebarFrontmatterPer-folder sidebar metadata such as folderIndexBehavior overrides
readingTimeboolean | numberDisable the estimate for one page (false) or force an exact minute count
iconstringIcon key from the icons registry. Shows in sidebar.
ordernumberSort order in the sidebar (only when ordering: "numeric"). Lower numbers first.
tagsstring[]Tags for categorization
ogImagestringShorthand for a single OG image path. Ignored if openGraph is set.
openGraphPageOpenGraphFull Open Graph object (e.g. images: [{ url, width?, height? }]). When set, replaces generated OG from the config (e.g. dynamic endpoint).
twitterPageTwitterFull Twitter card object (e.g. card, images). When set, replaces generated twitter metadata.
hiddenbooleanIf true, hide the page from the sidebar (page remains reachable by URL). Optional; behavior may vary by adapter.

PageAgentFrontmatter

Page-level metadata for machine-readable docs workflows.

PropertyTypeDescription
tokenBudgetnumberApproximate output token target for docs agent compact on this page
accessDocsPageAccessPolicyDeny-by-default scopes and claims for agent-facing page surfaces
taskstringConcrete task the page helps an agent complete
outcomestringObservable end state that indicates completion
appliesTo{ framework?, version?, package? }Applicability values; each accepts a string or string array
prerequisitesstring[]Setup or conditions that must already be true
filesstring[]Files the task is expected to read or change
commands(string | PageAgentCommand)[]Command strings or { run, cwd?, description? } objects
sideEffectsstring[]Material state changes or external effects
verification(string | PageAgentVerification)[]Checks or { description?, run?, expect? } objects
rollbackstring[]Steps that restore the prior state
failureModes(string | PageAgentFailureMode)[]Symptoms or { symptom, resolution? } objects
page-frontmatter.md
---
title: "Installation"
description: "Install the framework"
agent:
  tokenBudget: 777
  access:
    visibility: authenticated
    scopes:
      - docs:private
    claims:
      role:
        - admin
        - editor
  task: Install the framework
  outcome: The docs route renders locally.
  appliesTo:
    framework: nextjs
    version: ">=16"
    package: "@farming-labs/next"
  prerequisites:
    - The app uses the App Router
  files:
    - package.json
    - next.config.ts
  commands:
    - run: pnpm add @farming-labs/docs @farming-labs/next
      description: Install the required packages
  verification:
    - run: pnpm dev
      expect: The docs route returns HTTP 200
  failureModes:
    - symptom: The route returns 404
      resolution: Confirm withDocs wraps the Next.js config
---

Notes:

  • agent.tokenBudget overrides global agent.compact.maxOutputTokens defaults and CLI --max-output-tokens for that one page
  • agent.access.visibility: authenticated requires a principal; every listed scope and claim must match, while claim arrays accept any overlapping value
  • protected pages are omitted from public Markdown discovery, llms.txt, search/Ask AI, content sync, unauthenticated MCP results, and static agent bundles; application HTML authorization remains the host application's responsibility
  • structured fields are normalized into served Markdown frontmatter, a deterministic ## Agent Contract section, search/Ask AI context, Schema.org HowTo JSON-LD, and MCP task tools
  • MCP list-page results contain only hasContract, task, outcome, and appliesTo; call list_tasks or read_task when an agent needs actionable discovery or the full contract
  • docs review reports malformed or unknown fields with closest-name suggestions, missing task or outcome, commands without verification, and side effects without rollback guidance
  • if the page already has a sibling agent.md, docs agent compact compacts that file
  • if the page does not have agent.md, the command compacts the generated machine-readable page output and writes a new sibling agent.md
  • docs agent compact --changed only processes docs pages changed in the current git working tree, including staged, unstaged, and untracked docs changes
  • docs agent compact --stale --include-missing will also create missing agent.md files for pages that define agent.tokenBudget
  • agent.compact.reviewManifestPath (default .farming-labs/agent-compaction-reviews.json) sets the project-relative path to the JSON manifest for hash-bound reviewed compaction exceptions; override per run with --review-manifest
  • inherited minOutputTokens is clamped down to tokenBudget when needed so the compression API never receives min_output_tokens > max_output_tokens

PageSidebarFrontmatter

Page-level sidebar metadata used while building the docs navigation tree.

PropertyTypeDescription
folderIndexBehavior"link" | "toggle" | "hidden"Override how this folder page behaves in the default sidebar when it has child pages
app/docs/components/page.mdx
---
title: "Components"
sidebar:
  folderIndexBehavior: "toggle"
---

Notes:

  • this is only read from folder landing pages such as page.mdx / index.md
  • it overrides global sidebar.folderIndexBehavior
  • it also overrides sidebar.folderIndexBehaviorOverrides for that same folder
  • folderIndexBehavior: "hidden" keeps the folder route available but omits the folder index link from the sidebar tree; in flat mode this becomes a label-only group with child links only, and direct visits to the hidden parent route redirect to the first visible child

Static OG example — use openGraph and twitter in frontmatter to serve a static image instead of the dynamic OG endpoint:

page-frontmatter.md
---
title: "Title of Docs"
description: "Title of the docs goes here"
icon: "harddrive"
openGraph:
  images:
    - url: "/og/path-to-image/image.png"
      width: 1200
      height: 630
twitter:
  card: "summary_large_image"
  images:
    - "/og/path-to-image/image.png"
---

Minimal example (shorthand ogImage or generated OG):

page.md
---
title: "Getting Started"
description: "Set up your docs in 5 minutes"
related:
  - /docs/installation
  - /docs/customization/agent-primitive
readingTime: 4
icon: "rocket"
order: 1
---

Data types

Frontmatter is YAML between the --- fences. Supported value types:

TypeExample in frontmatterUse in framework fields
stringtitle: "Hello" or title: Hellotitle, description, icon, ogImage, framework, version, locale
numberorder: 1order
booleanhidden: trueOptional (e.g. hide from sidebar)
string[]tags: [a, b] or tags: ["a", "b"]tags

You can use other YAML types (e.g. nested objects) for custom keys; the fields in the table above are used by the framework for sidebar, SEO, JSON-LD structured data, OG, search, scoped MCP context, and machine-readable markdown. Quote numeric version values so they remain strings for MCP filtering.

How frontmatter is passed

  • Next.js — The MDX pipeline uses remark-frontmatter and remark-mdx-frontmatter with name: "metadata". The parsed frontmatter is exposed as the metadata export from the page. The layout and generateMetadata receive it when resolving the page (e.g. via getPage(params)), and it is used for createPageMetadata, sidebar tree (title, icon, order), description maps, and JSON-LD structured data.
  • TanStack Start / SvelteKit / Astro / Nuxt — The docs loader (e.g. import.meta.glob or content layer) reads each .md/.mdx file, parses frontmatter, and builds the nav tree and page data. Frontmatter is passed to the layout as page data (e.g. page.data or the props your content component receives). The same fields (title, description, icon, order, tags, ogImage, framework, version, locale) are used for sidebar, meta tags, JSON-LD structured data, search, and scoped MCP context.

So in all frameworks, the same frontmatter fields drive titles, descriptions, icons, ordering, JSON-LD, and OG; only the mechanism (MDX metadata vs. content loader page.data) differs. The related field is intentionally machine-readable first: add URL strings, and .md routes plus MCP page output render them next to Description as Related: /docs/a, /docs/b without adding a visible related-links block to the human page UI. A sibling agent.md remains the page-content override, so add a Related: line there manually if the override should include it.


createTheme(options)

Create a custom theme from scratch. See Creating Themes.

import { createTheme } from "@farming-labs/docs";

export const myTheme = createTheme({
  name: "my-theme",
  ui: {
    colors: { primary: "#ff4d8d", background: "#0a0a0a" },
    radius: "0px",
    sidebar: { style: "bordered" },
  },
});

extendTheme(base, overrides)

Extend an existing theme with overrides.

import { extendTheme } from "@farming-labs/docs";
import { fumadocs } from "@farming-labs/theme";

export const myTheme = extendTheme(fumadocs(), {
  ui: {
    colors: { primary: "oklch(0.72 0.19 149)" },
  },
});

TanStack Start Server API

createDocsServer(config)

Creates the TanStack Start docs server adapter.

import { createDocsServer } from "@farming-labs/tanstack-start/server";

Parameters:

PropertyTypeDescription
configDocsConfigThe config object from defineDocs()
rootDirstringOptional project root used to resolve contentDir. Defaults to process.cwd()

Returns:

PropertyTypeDescription
load({ pathname, locale? }) => Promise<DocsServerLoadResult>Loads docs page data and the sidebar tree for a pathname
GET({ request }) => ResponseSearch, Markdown, discovery standards, llms.txt, OpenAPI, sitemap, AGENTS.md, and skill.md handler
HEAD({ request }) => Promise<Response>Bodyless public-read handler that preserves GET status and headers
POST({ request }) => Promise<Response>AI chat endpoint handler
MCPDocsMcpHttpHandlersMCP HTTP transport handlers
src/lib/docs.server.ts
import { createDocsServer } from "@farming-labs/tanstack-start/server";
import docsConfig from "../../docs.config";

export const docsServer = createDocsServer({
  ...docsConfig,
  rootDir: process.cwd(),
});

TanStack Start Components

ComponentImportDescription
TanstackDocsPage@farming-labs/tanstack-start/reactBuilt-in page renderer that resolves the compiled MDX module and wraps it in the shared docs layout
RootProvider@farming-labs/theme/tanstackRoot provider for theme state, search, and AI UI
src/routes/docs.index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { TanstackDocsPage } from "@farming-labs/tanstack-start/react";
import { loadDocPage } from "@/lib/docs.functions";
import docsConfig from "../../docs.config";

export const Route = createFileRoute("/docs/")({
  loader: () => loadDocPage({ data: { pathname: "/docs" } }),
  component: DocsIndexPage,
});

function DocsIndexPage() {
  const data = Route.useLoaderData();
  return <TanstackDocsPage config={docsConfig} data={data} />;
}
src/routes/api.docs.ts
import { createFileRoute } from "@tanstack/react-router";
import { isDocsStandardsDiscoveryRequest } from "@farming-labs/docs";
import { docsServer } from "@/lib/docs.server";
import docsConfig from "../../docs.config";

async function handleUnsupportedDocsMethod(request: Request) {
  if (
    isDocsStandardsDiscoveryRequest(new URL(request.url), {
      apiRoute: docsConfig.cloud?.apiRoute,
    })
  ) {
    return docsServer.GET({ request });
  }
  return new Response("Method Not Allowed", {
    status: 405,
    headers: { Allow: "GET, HEAD, POST" },
  });
}

export const Route = createFileRoute("/api/docs")({
  server: {
    handlers: {
      GET: async ({ request }) => docsServer.GET({ request }),
      HEAD: async ({ request }) => docsServer.HEAD({ request }),
      POST: async ({ request }) => docsServer.POST({ request }),
      ANY: async ({ request }) => handleUnsupportedDocsMethod(request),
    },
  },
});

TanStack Start also uses the docsMdx() Vite plugin from @farming-labs/tanstack-start/vite so .mdx files compile without extra local module-loader glue.


SvelteKit Server API

createDocsServer(config)

Creates all server-side functions for a SvelteKit docs site.

import { createDocsServer } from "@farming-labs/svelte/server";

Parameters:

PropertyTypeDescription
configDocsConfigThe config object from defineDocs()
_preloadedContentRecord<string, string>Pre-loaded markdown files from import.meta.glob. Required for serverless.

Returns:

PropertyTypeDescription
load(event) => Promise<PageData>Layout load function (use in +layout.server.js)
GET(event) => ResponseSearch endpoint handler
HEAD(event) => Promise<Response>Bodyless public-read handler
POST(event) => Promise<Response>AI chat endpoint handler
MCPDocsMcpHttpHandlersMCP HTTP transport handlers
src/lib/docs.server.ts
import { createDocsServer } from "@farming-labs/svelte/server";
import config from "./docs.config";

const contentFiles = import.meta.glob(
  ["/docs/**/*.{md,mdx,svx}", "/AGENTS.md", "/AGENT.md", "/skill.md", "/.farming-labs/sitemap-manifest.json"],
  {
    query: "?raw",
    import: "default",
    eager: true,
  },
) as Record<string, string>;

export const { load, GET, HEAD, POST, MCP } = createDocsServer({
  ...config,
  _preloadedContent: contentFiles,
});

SvelteKit Components

Imported from @farming-labs/svelte-theme:

ComponentPropsDescription
DocsLayouttree, config, title?, titleUrl?Main layout with sidebar, nav, search, AI
DocsContentdata, configPage content with TOC, breadcrumb, footer nav
DocsPageentry, tocEnabled, breadcrumbEnabled, previousPage, nextPage, editOnGithub, lastModifiedLow-level page wrapper (used by DocsContent)

Astro Server API

createDocsServer(config)

Same as SvelteKit — returns { load, GET, HEAD, POST, MCP }.

src/lib/docs.server.ts
import { createDocsServer } from "@farming-labs/astro/server";

Parameters:

PropertyTypeDescription
configDocsConfigThe config object from defineDocs()
_preloadedContentRecord<string, string>Pre-loaded markdown files from import.meta.glob. Required for serverless.

Returns:

PropertyTypeDescription
load(pathname: string) => Promise<PageData>Takes a URL pathname string, returns page data
GET({ request }) => ResponseSearch endpoint handler for GET /api/docs?query=...
HEAD({ request }) => Promise<Response>Bodyless public-read handler
POST({ request }) => Promise<Response>AI chat endpoint handler with SSE streaming
MCPDocsMcpHttpHandlersMCP HTTP transport handlers
src/lib/docs.server.ts
import { createDocsServer } from "@farming-labs/astro/server";
import config from "./docs.config";

const contentFiles = import.meta.glob(
  ["/docs/**/*.{md,mdx}", "/AGENTS.md", "/AGENT.md", "/skill.md", "/.farming-labs/sitemap-manifest.json"],
  {
    query: "?raw",
    import: "default",
    eager: true,
  },
) as Record<string, string>;

export const { load, GET, HEAD, POST, MCP } = createDocsServer({
  ...config,
  _preloadedContent: contentFiles,
});

Astro Components

ComponentImportDescription
DocsLayout@farming-labs/astro-theme/src/components/DocsLayout.astroMain layout with sidebar, search, and theme toggle
DocsContent@farming-labs/astro-theme/src/components/DocsContent.astroPage content with metadata
DocsPage@farming-labs/astro-theme/src/components/DocsPage.astroPage structure (TOC, breadcrumb, nav)
ThemeToggle@farming-labs/astro-theme/src/components/ThemeToggle.astroLight/dark toggle
SearchDialog@farming-labs/astro-theme/src/components/SearchDialog.astroSearch + AI dialog

CSS Imports

Same pattern as SvelteKit:

ImportDescription
@farming-labs/theme/default/cssDefault theme
@farming-labs/theme/pixel-border/cssPixel border theme
@farming-labs/theme/darksharp/cssDarksharp theme

Theme Factories

FactoryImport
fumadocs()@farming-labs/astro-theme
pixelBorder()@farming-labs/astro-theme/pixel-border
darksharp()@farming-labs/astro-theme/darksharp

Nuxt Server API

defineDocsHandler(config, useStorage)

Creates a single Nitro event handler that serves docs loading, search, and AI chat for Nuxt.

import { defineDocsHandler } from "@farming-labs/nuxt/server";

Parameters:

PropertyTypeDescription
configDocsConfigThe config object from defineDocs()
useStorageFunctionNitro's useStorage utility for accessing server assets

Returns:

A Nitro event handler that responds to GET (page loading + search) and POST (AI chat) requests.

server/api/docs.ts
import { defineDocsHandler } from "@farming-labs/nuxt/server";
import config from "../../docs.config";

export default defineDocsHandler(config, useStorage);

Nuxt uses Nitro's serverAssets to load markdown files. Configure nitro.serverAssets in nuxt.config.ts to point to your docs directory.

Nuxt Components

Imported from @farming-labs/nuxt-theme:

ComponentPropsDescription
DocsLayouttree, config, triggerComponent?Main layout with sidebar, nav, search, AI
DocsContentdata, configPage content with TOC, breadcrumb, footer nav

CSS Imports

ImportDescription
@farming-labs/theme/default/cssDefault theme

Theme Factories

FactoryImport
fumadocs()@farming-labs/nuxt-theme/fumadocs

Next.js API

RootProvider

Client-side provider that wraps your entire app. Enables search, theme switching (light/dark), and AI chat. Must be placed in your root app/layout.tsx.

app/layout.tsx
import { RootProvider } from "@farming-labs/theme";
import "./global.css";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <RootProvider>{children}</RootProvider>
      </body>
    </html>
  );
}
PropTypeDefaultDescription
searchSearchProviderProps{ options: { api: "/api/docs" } }Search configuration. API defaults to the unified docs handler.
themeThemeProviderProps—Theme provider options (passed to next-themes)
childrenReactNoderequiredYour app content

suppressHydrationWarning on <html> prevents React warnings caused by the theme class being injected before hydration.

withDocs(nextConfig, docsConfig?)

Wraps your Next.js config with docs framework support. Handles MDX, routing, search index generation, and auto-generates mdx-components.tsx and app/docs/layout.tsx if missing.

next.config.ts
import { withDocs } from "@farming-labs/next/config";
export default withDocs({});

The optional second argument supplies the live docs config when build-time routing cannot be read statically from an imported, spread, or shorthand docs.config value. Protected MCP users should pass it so RFC 9728 rewrites and the incompatible-basePath check use the resolved configuration.

createDocsLayout(config)

Creates the docs layout component from your config. Auto-generated by withDocs() — you typically don't need to create this manually.

app/docs/layout.tsx
import { createDocsLayout } from "@farming-labs/theme";
import config from "@/docs.config";

const { DocsLayout } = createDocsLayout(config);

export default function Layout({ children }) {
  return <DocsLayout>{children}</DocsLayout>;
}
Customize
Live preview on these docs
Reset
Customize
Export Code
Preset
Explore more themes
Primary Color
Primary
Colors
Background
Foreground
Muted Text
Border
Card
Muted BG
Ring
Layout
SidebarBordered
TOC StyleDirectional (tree)
TOC DepthH2 + H3
Ask AI
ModeFloating
Floating StyleFull Modal
PositionBottom Right
Features