A Docusaurus plugin that exposes an MCP (Model Context Protocol) server endpoint, allowing AI agents like Claude, Cursor, and other MCP-compatible tools to search and retrieve your documentation.
The server speaks MCP 2026-07-28 and still serves clients on 2025-era revisions (the initialize handshake) from the same endpoint. It can also serve Agent Skills that teach agents how to use your docs tools.
Upgrading from 1.x? See Upgrading to 2.0.
npm install docusaurus-plugin-mcp-server// docusaurus.config.js
module.exports = {
plugins: [
[
'docusaurus-plugin-mcp-server',
{
server: {
name: 'my-docs',
version: '1.0.0',
},
},
],
],
};The MCP server runs on any web-standard serverless or edge runtime β Cloudflare Workers, modern Netlify functions, Vercel Edge, Deno, Bun. Import the artifact bundle the build writes (build/mcp/bundle.json) and pass it to createWebRequestHandler, which returns a standard (request: Request) => Promise<Response>. (These runtimes can't read the filesystem, so the bundle is imported as a module rather than loaded from disk.)
import { createWebRequestHandler } from 'docusaurus-plugin-mcp-server/adapters';
import bundle from '../build/mcp/bundle.json';
export default {
// Name, version, and site URL come from the build; pass them here to override.
fetch: createWebRequestHandler({ artifacts: bundle }),
};The bundle holds the documents, the search index, and the skills (when enabled), so there's nothing else to wire up. A stale or malformed bundle fails with a message saying how to fix it, both on requests and on GET (the status endpoint).
The export default { fetch } form works on Cloudflare Workers, Deno, and Bun. Other runtimes use their own entry convention (e.g. modern Netlify functions export default async (request) => Response) β the handler is identical, only the export wrapper differs.
The handler is unauthenticated and allows all origins (Access-Control-Allow-Origin: *) by default, since a docs MCP endpoint is meant to be public β pass corsOrigin to restrict it.
For local development, run the server over Node's http with createNodeServer from docusaurus-plugin-mcp-server/adapters/node (see Adapter Exports).
npm run build
# Deploy to your platformClaude Code:
claude mcp add --transport http my-docs https://docs.example.com/mcpCursor / VS Code:
{
"mcpServers": {
"my-docs": {
"url": "https://docs.example.com/mcp"
}
}
}Add a dropdown button to your docs site so users can easily install the MCP server in their AI tool:
import { McpInstallButton } from 'docusaurus-plugin-mcp-server/theme';
function NavbarItems() {
return <McpInstallButton serverUrl="https://docs.example.com/mcp" serverName="my-docs" />;
}The button shows a dropdown with copy-to-clipboard configurations for all supported MCP clients.
| Light Mode | Dark Mode |
|---|---|
![]() |
![]() |
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
serverUrl |
string |
(plugin config) | MCP server endpoint URL. Falls back to the plugin's global data when omitted |
serverName |
string |
(plugin config) | MCP server name. Falls back to the plugin's global data when omitted |
label |
string |
(none) | Button label. If omitted, shows only the MCP icon |
headerText |
string |
"Choose your AI tool:" |
Text shown at the top of the dropdown |
className |
string |
"" |
Optional CSS class |
clients |
ClientId[] |
All HTTP-capable | Which clients to show |
The server exposes two tools for AI agents:
Search across documentation with relevance ranking. Returns matching documents, best first, with URLs, snippets, and matching sections. See Search for how results are ranked.
{
"name": "docs_search",
"arguments": {
"query": "authentication",
"limit": 16
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
query |
string |
required | Search query, up to 500 characters. Only the first 16 distinct terms are searched. |
limit |
number |
16 |
Max results (1-20) |
Response includes:
- Full URL for each result (use with
docs_fetch) - Title and relevance score
- Snippet of matching content
- Matching headings
Retrieve full page content as markdown. Use this after searching to get the complete content of a specific page.
{
"name": "docs_fetch",
"arguments": {
"url": "https://docs.example.com/docs/authentication"
}
}| Parameter | Type | Description |
|---|---|---|
url |
string |
Full URL of the page (from search results) |
Response includes:
- Page title and description
- Table of contents with anchor links
- Full markdown content
Tool descriptions tell an agent what docs_search and docs_fetch do, not how to research your docs well. The server can also ship that guidance as Agent Skills via the MCP skills extension (SEP-2640, io.modelcontextprotocol/skills).
By default the plugin packages one built-in skill, docs-research, which covers the search β fetch β cite workflow for your site. It's generated from your site at build time:
- Its description names your docs, host, and
tagline, e.g. "Answer questions using the Acme documentation at acme.dev (Build faster)". Agents read the description to decide when to load the skill. - A Where things are section groups the indexed pages by URL path, largest sections first. Each section lists its page count, a link to its overview page if there is one, and a few example page titles. It's left out when the pages don't form at least two multi-page sections.
The built-in skill is generic by design. For a skill that knows your product (its terminology, where each topic lives, the questions people actually ask), write your own docs-research skill: it replaces the built-in one. Start from a copy of skills-builtin/docs-research/.
Add your own skills (a directory per skill with a SKILL.md at its root) and point the plugin at them:
// docusaurus.config.js
module.exports = {
plugins: [
[
'docusaurus-plugin-mcp-server',
{
server: { name: 'my-docs' },
// Serve the built-in docs-research skill plus every skill in ./mcp-skills
skills: { dir: 'mcp-skills' },
},
],
],
};mcp-skills/
βββ api-migration/
βββ SKILL.md # YAML frontmatter with name + description, then instructions
βββ references/
βββ v1-to-v2.md
At build time the plugin validates every skill (frontmatter name must match the directory name; at most 512 files and 16 MiB per skill), precomputes SHA-256 digests, and writes build/mcp/skills.json. Invalid skills fail the build with a SkillValidationError. Symlinks inside a skill are skipped, and bundled scripts (.sh, .py, .js, ...) are packaged with a warning. The skills are part of the artifact bundle, so the handler serves them with no extra config. An author skill named docs-research replaces the built-in one. If you copy the built-in skill, replace its placeholders ({{siteDocs}}, {{siteSummary}}, {{siteMap}}) with your own wording: they're only filled in for the built-in copy. Set skills: { builtin: false, dir: '...' } to ship only your own, or skills: false to turn skills off.
At runtime the server:
- declares the
io.modelcontextprotocol/skillsextension and implementsskills/listandskills/get - serves every skill file as a resource at
skill://<name>/<path>(for exampleskill://docs-research/SKILL.md) - appends the skill URIs to the server
instructions, so clients that don't support the extension yet can still find the skills and load them withresources/read
Keep skills to markdown. MCP hosts treat served skills as untrusted input and won't run bundled scripts without explicit user approval.
| Option | Type | Default | Description |
|---|---|---|---|
outputDir |
string |
'mcp' |
Output directory for MCP artifacts (relative to build dir) |
contentSelectors |
string[] |
['article', 'main', ...] |
CSS selectors for finding content, in priority order. Invalid selectors are skipped with a build warning (before 2.2, one failed every page) |
excludeSelectors |
string[] |
['nav', 'header', ...] |
CSS selectors for elements to remove. Full CSS since 2.2 (div.sidebar, main .toc, [data-noindex]); before, only tag, .class, and [attr="v"] matched. Invalid selectors are skipped with a build warning |
minContentLength |
number |
50 |
Minimum content length to consider a page valid |
server.name |
string |
'docs-mcp-server' |
Name of the MCP server |
server.version |
string |
'1.0.0' |
Version of the MCP server |
server.url |
string |
(derived) | Explicit MCP HTTP endpoint URL for the install button |
server.urlBase |
'origin' | 'site' |
'origin' |
How to derive the MCP URL when server.url is not set. 'origin' β {siteUrl}/{outputDir}; 'site' β under Docusaurus baseUrl |
excludeRoutes |
string[] |
['/404*', '/search*'] |
Routes to exclude (glob patterns) |
indexers |
string[] | false |
['local'] |
Indexers to run during build. Use false to disable. Supports built-in ('local'), relative paths, or npm packages. |
search |
string |
'local' |
Deprecated since 2.2, removed in 3.0. Has no effect (since 2.0): the search provider is chosen where the server runs, with the server config's search option. 'flexsearch' is rejected. |
skills |
{ builtin?: boolean; dir?: string } | false |
built-in skill only | Agent Skills to package into skills.json. dir is relative to the site directory. false disables skills. |
Build-time options control artifact generation and the install-button URL (server.url / server.urlBase). Runtime-only options such as instructions, tools, and baseUrl belong on the adapter/handler config β see Server Configuration.
Content selectors (in priority order):
['article', 'main', '.main-wrapper', '[role="main"]'];Exclude selectors:
[
'nav',
'header',
'footer',
'aside',
'[role="navigation"]',
'[role="banner"]',
'[role="contentinfo"]',
];The built-in 'local' search needs no external service. At build time it adds a search index to the artifact bundle; at runtime docs_search answers from it.
Results are ranked with BM25+ over each page's title, route, headings, description, and body:
- Query words are combined with OR. A page does not need every word in the query to match; pages with more of the words, rarer words, or matches in more important fields rank higher.
- The route is indexed.
/docs/errors/expired-cursormatches "expired cursor" even if the title says something else. - Long pages are not favored. Scores are normalized by field length, so a changelog that mentions every topic does not outrank the page about the topic.
- Words are stemmed, accents are folded, and common words are ignored. A Porter stemmer makes "indexing" match "index" and "route" match "routes"; "deploiement" matches "dΓ©ploiement"; and "how do I" adds nothing to a query. Words of three or more letters also match as prefixes, so "auth" finds "authentication".
Tokenization and stemming are fixed, so the index built at docusaurus build always matches the one queried at runtime. Field boosts apply at query time and can be changed on the server config without a rebuild:
createWebRequestHandler({
artifacts: bundle,
// Defaults: title 3, slug 3, headings 2, description 1.5, content 1
localSearch: { fieldBoosts: { headings: 3 } },
});The built-in search is tuned for English. For other languages, or to use a hosted search service, write a custom search provider.
evaluateSearch runs labeled queries against any search provider and reports how often the right page comes back near the top. Use it to compare providers, or to guard ranking in CI after docusaurus build. It is experimental: its options and report shape may change in a 2.x minor release.
import { loadSearchProvider, evaluateSearch } from 'docusaurus-plugin-mcp-server';
import { readArtifactBundle } from 'docusaurus-plugin-mcp-server/adapters/node';
const provider = await loadSearchProvider('local');
await provider.initialize(
{ baseUrl: 'https://docs.example.com', serverName: 'eval', serverVersion: '0', outputDir: '' },
{ bundle: await readArtifactBundle('build/mcp') }
);
const report = await evaluateSearch(provider, [
{ query: 'install the CLI', expected: ['/docs/installation'] },
{ query: 'rotate an API token', expected: ['/docs/auth/tokens', '/docs/auth/rotation'] },
]);
console.log(report.hitsAt[3], '/', report.total, 'in the top 3; MRR', report.mrr.toFixed(2));expected lists every page that fully answers the query, as routes or full URLs. hitsAt[k] counts queries whose first correct page ranked at or above k (defaults: 1, 3, 5), and mrr is the mean reciprocal rank of that page (1.0 means it was always first). report.cases has each query's rank and returned routes, for finding the misses.
The plugin uses a two-phase provider model: indexers run at build time to process documents, and search providers handle queries at runtime. Both are pluggable.
Implement ContentIndexer to push documents to an external system during build:
import type { ContentIndexer, ProviderContext, ProcessedDoc } from 'docusaurus-plugin-mcp-server';
export default class AlgoliaIndexer implements ContentIndexer {
readonly name = 'algolia';
shouldRun(): boolean {
return process.env.ALGOLIA_SYNC === 'true';
}
async initialize(context: ProviderContext): Promise<void> {
console.log(`[Algolia] Initializing for ${context.baseUrl}`);
}
async indexDocuments(docs: ProcessedDoc[]): Promise<void> {
// Push docs to Algolia
}
async finalize(): Promise<Map<string, unknown>> {
// No local artifacts needed
return new Map();
}
}The plugin always writes the documents (docs.json) itself, whichever indexers run. What finalize() returns is added to the artifact bundle:
search-index.jsonbecomes the bundle's search index. Only one indexer may return it.bundle.json,manifest.json, andskills.jsonare written by the plugin; returning one fails the build.- Any other filename (a relative path inside the output directory) is kept as an indexer extra and written to
build/mcp/. getManifestData(), if implemented, is recorded inmanifest.jsonunderindexerData.<name>.
To delegate runtime search to an external service, pass a search provider as the server's search option. Since 2.2 it only needs a name and a search function (the SearchRanker type), so a plain object works:
import type { SearchRanker } from 'docusaurus-plugin-mcp-server';
import { createWebRequestHandler } from 'docusaurus-plugin-mcp-server/adapters';
import bundle from '../build/mcp/bundle.json';
const glean: SearchRanker = {
name: 'glean',
async search(query, options) {
// Call the Glean Search API and map each hit to a SearchResult:
// { url, route, title, score, snippet }
return [];
},
};
export default {
fetch: createWebRequestHandler({ artifacts: bundle, search: glean }),
};A class works the same way, and can add the optional members:
import type {
SearchRanker,
ProviderContext,
SearchOptions,
SearchResult,
} from 'docusaurus-plugin-mcp-server';
export default class GleanSearchProvider implements SearchRanker {
readonly name = 'glean';
private apiEndpoint = process.env.GLEAN_API_ENDPOINT;
private apiToken = process.env.GLEAN_API_TOKEN;
// Optional. Rejecting fails the server's initialization.
async initialize(context: ProviderContext): Promise<void> {
if (!this.apiEndpoint || !this.apiToken) {
throw new Error('GLEAN_API_ENDPOINT and GLEAN_API_TOKEN required');
}
}
async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
// Call Glean Search API and transform results
return [];
}
}With an artifacts or artifactsDir server config, initialize receives the artifact bundle as initData.bundle: the documents, the search index (if an indexer produced one), and any indexer extras. So a provider can read what its indexer wrote without touching the filesystem. (With the deprecated configs it gets the same initData as in 2.1.) getDocument and getDocCount are optional: without them, docs_fetch and the status endpoint use the bundle's documents.
This holds whether the server config passes an instance or a module path (search: './my-search.js', whose default export is a SearchRanker class or object). Existing SearchProvider classes keep working unchanged. (loadSearchProvider(), called directly with a module path, still requires a full SearchProvider in 2.x.)
Deprecated since 2.2: isReady() and healthCheck(). Through 2.x the server still calls isReady(), and when it returns false the tools answer "Server not initialized"; 3.0 stops calling it. Reject from initialize() or throw from search() instead. The server has never called healthCheck(); use the GET status endpoint (McpDocsServer.getStatus()). In 3.0, SearchProvider becomes SearchRanker's shape. See migrations/2.x-3.0.0.md.
// docusaurus.config.js: build time
module.exports = {
plugins: [
[
'docusaurus-plugin-mcp-server',
{
// Run both the built-in local search indexer and a custom one
indexers: ['local', './my-algolia-indexer.js'],
},
],
],
};
// worker.js: runtime. The search provider is chosen where the server runs.
import GleanSearchProvider from '@myorg/glean-search';
createWebRequestHandler({ artifacts: bundle, search: new GleanSearchProvider() });These options apply to McpDocsServer, createWebRequestHandler, createNodeServer, and createNodeHandler: where the MCP server actually runs. They are not McpServerPluginOptions; the Docusaurus plugin only builds the artifact bundle.
| Option | Type | Required | Description |
|---|---|---|---|
artifacts |
object |
Yes* | The artifact bundle: the contents of build/mcp/bundle.json |
artifactsDir |
string |
Yes* | Directory holding the bundle, e.g. ./build/mcp (createNodeServer/createNodeHandler only) |
name |
string |
No | Server name. Default: the plugin's server.name from the build |
version |
string |
No | Server version. Default: the plugin's server.version from the build |
baseUrl |
string |
No | Base URL for full page URLs in responses. Default: the site URL from the build |
instructions |
string |
No | Instructions describing how to use the server, surfaced to MCP clients in the server/discover (2026-07-28) or initialize (2025-era) result. When skills are served, their URIs are appended |
tools |
object |
No | Per-tool overrides. Supports docs_search.description and docs_fetch.description to customize tool descriptions |
search |
string | SearchRanker |
No | Search provider: a module name or path, or an instance (since 2.2, { name, search } is enough). Default: the built-in 'local' search. See SearchProvider |
localSearch |
{ fieldBoosts?: {...} } |
No | Field boosts for the built-in search. See Search |
*Pass artifacts (edge and serverless, or McpDocsServer directly) or artifactsDir (Node). In Node, readArtifactBundle(dir) from docusaurus-plugin-mcp-server/adapters/node gives you the artifacts value.
The 2.0/2.1 configs (docsPath/indexPath/skillsPath, or docs/searchIndexData/skills with a required name) still work through 2.x, but are deprecated and will be removed in 3.0.
Example with extended configuration:
export default {
fetch: createWebRequestHandler({
artifacts: bundle,
instructions: 'Search the Acme product docs. Use docs_search to find pages, then docs_fetch for full content.',
tools: {
docs_search: { description: 'Search the Acme product documentation.' },
docs_fetch: { description: 'Fetch the full markdown of an Acme docs page.' },
},
}),
};After running npm run build, use the included CLI to verify the MCP output:
npx docusaurus-mcp-verifyThis checks that:
- The artifact bundle reads and validates (
bundle.json, or the per-file layout from 2.0/2.1 builds, with a warning) - It has a search index for the built-in local search
- The MCP server can initialize and load the content
You can specify a custom build directory, and pass --output-dir if you changed the plugin's outputDir option:
npx docusaurus-mcp-verify ./custom-build
npx docusaurus-mcp-verify ./custom-build --output-dir agents/mcpExample output:
π MCP Build Verification
==================================================
Build directory: /path/to/your/project/build
MCP directory: /path/to/your/project/build/mcp
π Checking build output...
β Found 42 documents
β Artifact bundle is valid
π Testing MCP server...
β Server "my-docs" initialized with 42 documents
β
All checks passed!The easiest way to test your MCP server is with the official MCP Inspector:
npx @modelcontextprotocol/inspectorThis opens a visual interface where you can:
- Connect to your server URL
- Browse available tools
- Execute tool calls interactively
- View responses in a formatted display
Alternatively, test with curl:
# List available tools
curl -X POST https://docs.example.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# Search documentation
curl -X POST https://docs.example.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc":"2.0",
"id":2,
"method":"tools/call",
"params":{
"name":"docs_search",
"arguments":{"query":"getting started"}
}
}'The plugin operates in two phases:
Build Time: During docusaurus build, the plugin's postBuild hook processes all rendered HTML pages, extracts content, converts to markdown, builds a search index, and writes the artifact bundle to build/mcp/bundle.json. Through 2.x it also writes each part as its own file (docs.json, search-index.json, skills.json, manifest.json) for existing deployments.
Runtime: A serverless function loads the pre-built artifacts and handles MCP JSON-RPC requests from AI agents. The server is stateless (MCP 2026-07-28 has no sessions), so any instance can answer any request, and list/read results carry cache hints (ttlMs 5 minutes, cacheScope: public) because content only changes on redeploy. All indexing happens at build time.
- Full-text Search - BM25-ranked local search, with no external service
- Page Retrieval - Get complete page content as clean markdown
- MCP 2026-07-28, backward compatible - Stateless modern protocol plus 2025-era clients on the same endpoint
- Agent Skills - Ships a docs-research skill (and yours) over the MCP skills extension
- Runs Anywhere - One web-standard handler (
createWebRequestHandler) for any serverless/edge runtime β Cloudflare Workers, modern Netlify functions, Vercel Edge, Deno, Bun β plus a Node server (createNodeServer) for local development - CORS Support - The web and Node handlers send CORS headers for browser-based clients; restrict with
corsOrigin - Build-time Processing - Extracts content from rendered HTML, capturing React component output
- Zero Runtime Docusaurus Dependency - The MCP server runs independently
Run a local MCP server for testing using the built-in Node adapter:
// mcp-server.mjs
import { createNodeServer } from 'docusaurus-plugin-mcp-server/adapters/node';
createNodeServer({
artifactsDir: './build/mcp',
baseUrl: 'http://localhost:3000',
}).listen(3456, () => {
console.log('MCP server at http://localhost:3456');
});The Node adapter handles CORS, preflight requests, and health checks (GET) automatically.
Connect Claude Code:
claude mcp add --transport http my-docs http://localhost:3456import {
// Docusaurus plugin (also the default export)
mcpServerPlugin,
// MCP server class (advanced / custom runtimes)
McpDocsServer,
// Tool definitions
docsSearchTool,
docsFetchTool,
// Provider loaders (built-in 'local' or custom indexers/providers)
loadIndexer,
loadSearchProvider,
// The built-in local search, for passing an instance as `search`
LocalSearchIndexer,
LocalSearchProvider,
// Measure how well a search provider ranks the right pages (experimental)
evaluateSearch,
// Resolve the MCP endpoint URL the install button uses
resolveServerUrl,
// Package Agent Skills into a skills.json artifact outside the plugin (experimental)
buildSkillsArtifact,
// Default plugin options
DEFAULT_PLUGIN_OPTIONS,
} from 'docusaurus-plugin-mcp-server';Derives the public MCP HTTP endpoint URL β the same logic the plugin uses for the install button and globalData. Use this when building custom theme UI that must stay in sync with plugin URL resolution.
Types ResolveServerUrlInput and ServerUrlBase are also exported from ..
import { resolveServerUrl, type ResolveServerUrlInput } from 'docusaurus-plugin-mcp-server';
const serverUrl = resolveServerUrl({
siteUrl: 'https://docs.example.com',
baseUrl: '/docs/',
outputDir: 'mcp',
server: { urlBase: 'site' },
});
// β 'https://docs.example.com/docs/mcp'| Field | Type | Description |
|---|---|---|
siteUrl |
string |
Docusaurus siteConfig.url |
baseUrl |
string |
Docusaurus siteConfig.baseUrl |
outputDir |
string |
Plugin outputDir (default 'mcp') |
server.url |
string |
Explicit endpoint; when set, urlBase is ignored |
server.urlBase |
ServerUrlBase |
'origin' (default) β {siteUrl}/{outputDir}; 'site' β under baseUrl |
ServerUrlBase is 'origin' | 'site'. Mirrors the server.url / server.urlBase plugin options.
import { createWebRequestHandler } from 'docusaurus-plugin-mcp-server/adapters';
import {
createNodeServer,
createNodeHandler,
readArtifactBundle,
} from 'docusaurus-plugin-mcp-server/adapters/node';-
createNodeServer(options)β Creates a complete Node.js HTTP server for local development. Returns anhttp.Serverready to.listen(). -
createNodeHandler(options)β Creates a request handler function compatible withhttp.createServer()and Connect-style frameworks such as Express. Use this when you need to integrate with an existing server. Mount it for all methods (GETis the status check,OPTIONSthe CORS preflight). If a body parser such asexpress.json()has already read the request, the handler uses itsreq.body:import express from 'express'; import { createNodeHandler } from 'docusaurus-plugin-mcp-server/adapters/node'; const app = express(); app.use(express.json()); app.all('/mcp', createNodeHandler({ artifactsDir: './build/mcp' })); app.listen(3456);
-
readArtifactBundle(dir)β Reads and validates the artifact bundle in a build directory (bundle.json, or the 2.0/2.1 per-file layout). Pass the result asartifactstoMcpDocsServer, or asinitData.bundleto a search provider you drive yourself.
The ArtifactBundle type is exported from all three entry points.
import {
McpInstallButton,
type McpInstallButtonProps,
useMcpRegistry,
createDocsRegistry,
createDocsRegistryOptions,
type McpConfig,
} from 'docusaurus-plugin-mcp-server/theme';McpInstallButtonβ Dropdown button for users to install the MCP server in their AI tool.useMcpRegistry()β React hook that returns the MCP config registry from plugin global data. Returnsundefinedif the plugin is not installed.createDocsRegistry(config)β Creates a pre-configuredMCPConfigRegistryfor documentation servers.createDocsRegistryOptions(config)β Returns registry options without creating the registry.McpConfigβ Type for{ serverUrl: string; serverName: string }.
2.2 changes nothing you have to act on. It adds build/mcp/bundle.json and the artifacts / artifactsDir server options, and deprecates the file (docsPath, indexPath, skillsPath) and pre-loaded data (docs, searchIndexData, skills) configs, and McpDocsServer.handleHttpRequest() (use createNodeHandler). It also deprecates the search provider methods isReady() and healthCheck() (a provider can now be just { name, search }, a SearchRanker) and the plugin's search option, which has done nothing since 2.0. Those still work through 2.x and are removed in 3.0. migrations/2.x-3.0.0.md has the before/after code and a checklist for an agent to run.
Two extraction fixes change the generated docs:
excludeSelectorsare full CSS. Before 2.2, only tag names,.class, and[attr="v"]on plain attributes matched. Anything else (div.sidebar,.a .b,[data-x="y"]) was silently ignored, and a selector starting with an attribute ([role="tab"] span) removed the[role="tab"]element itself. If you listed such selectors, they now remove what they say, so check the generated docs. The defaults behave as before.- Headings are read correctly. Every Docusaurus heading used to carry its permalink into the text and id (
Setup[β](#setup "Direct link to Setup"), idsetupsetup-direct-link-to-setup), and#lines inside code blocks counted as headings. Now heading text is plain, ids are the page's real anchors (including-1duplicates and custom ids), code blocks are skipped, and the permalinks are gone from the Markdown. This changesdocs_fetchoutput and search scores, and result order can shift where permalink text inflated a page's score (a query containing "link", for example). Custom indexers that readDocHeadingsee the corrected values.
Follow migrations/1.x-2.0.0.md. It lists every breaking change with before/after code, and ends with a checklist an AI agent can run to migrate a project. Errors thrown for 1.x configuration link to it.
In short:
- Requirements: Node.js >= 22 and zod >= 4.2.
- Rebuild and redeploy
build/mcp/. Thesearch-index.jsonformat changed, and a 1.x index is rejected with a message saying to rebuild. Deployment problems like this areConfigurationErrors: their message, which says how to fix them, is returned to clients and in theGETstatus. Other errors are still reported only asInternal server error. - FlexSearch is replaced by the built-in
localBM25 search. Removeflexsearchoptions andflexsearchvalues. Tune ranking at runtime withlocalSearch.fieldBoosts. Search now matches any of the query words (OR) instead of all of them, andqueryis capped at 500 characters. docsSearchTool.inputSchema/docsFetchTool.inputSchemaarez.object(...)schemas. The raw shapes are still exported asdocsSearchInputSchema/docsFetchInputSchema.- Unknown tools return JSON-RPC
-32602instead of anisErrorresult. - Skills: the build writes
build/mcp/skills.json. Pass it to your handler to serve it, or setskills: false.
MCP clients need no changes. 2026-07-28 and 2025-era clients are served from the same endpoint, and the guide lists the small response differences older clients see.
- Node.js >= 22
- Docusaurus 3.x
- zod >= 4.2
MIT
This repository ships agent skill(s) under skills/. Install them into your
AI agent with npx skills:
npx skills add -g scalvert/docusaurus-plugin-mcp-server # global β available in every repo
npx skills add scalvert/docusaurus-plugin-mcp-server # or scoped to the current repo
