Migrating from 2.x to 3.0.0
3.0.0 is not released yet. Everything below is already deprecated in 2.2.0 and still works through 2.x. You can make these changes now, on 2.2 or later, and 3.0 will need nothing more from you. This guide grows as 3.0 takes shape.
2.2.0 added the artifact bundle: the build writes everything the server needs
to one file, build/mcp/bundle.json, and the server takes that one value. The
older ways of passing the pieces separately are removed in 3.0. See
ADR-0001 for why.
If you're an agent, go to For agents. It is a checklist you can run top to bottom.
1. Pass the artifact bundle to the web handler
Import bundle.json instead of docs.json, search-index.json, and
skills.json. name, version, and baseUrl now default to what the site was
built with (the plugin's server.name, server.version, and the site URL), so
you can drop them unless you want to override them.
import { createWebRequestHandler } from 'docusaurus-plugin-mcp-server/adapters';
-import docs from '../build/mcp/docs.json';
-import searchIndex from '../build/mcp/search-index.json';
-import skills from '../build/mcp/skills.json';
+import bundle from '../build/mcp/bundle.json';
export default {
fetch: createWebRequestHandler({
- docs,
- searchIndexData: searchIndex,
- skills,
- name: 'my-docs',
- baseUrl: 'https://docs.example.com',
+ artifacts: bundle,
}),
};
The same applies to new McpDocsServer({ docs, searchIndexData, skills, ... }):
use new McpDocsServer({ artifacts: bundle }).
2. Point the Node server at the build directory
createNodeServer({
- docsPath: './build/mcp/docs.json',
- indexPath: './build/mcp/search-index.json',
- skillsPath: './build/mcp/skills.json',
- name: 'my-docs',
+ artifactsDir: './build/mcp',
});
For new McpDocsServer({ docsPath, indexPath, skillsPath, ... }), read the
bundle first:
+import { readArtifactBundle } from 'docusaurus-plugin-mcp-server/adapters/node';
+
-const server = new McpDocsServer({
- docsPath: './build/mcp/docs.json',
- indexPath: './build/mcp/search-index.json',
- name: 'my-docs',
-});
+const server = new McpDocsServer({ artifacts: await readArtifactBundle('./build/mcp') });
3. Read the bundle in custom search providers
With an artifacts or artifactsDir server config (steps 1 and 2),
SearchProvider.initialize receives initData.bundle. The docsPath,
indexPath, docs, and indexData fields are removed in 3.0.
async initialize(context, initData) {
- const docs = initData.docs ?? JSON.parse(await readFile(initData.docsPath, 'utf8'));
+ // McpDocsServer sets bundle for artifacts/artifactsDir configs (steps 1 and 2).
+ if (!initData?.bundle) throw new Error('Pass { bundle } to initialize()');
+ const docs = initData.bundle.docs;
+ const index = initData.bundle.searchIndex; // if an indexer produced one
+ const mine = initData.bundle.extras?.['my-index.json']; // what your indexer's finalize() returned
}
If your code drives a provider itself (for example with evaluateSearch),
pass the bundle instead of paths or pre-loaded data:
+import { readArtifactBundle } from 'docusaurus-plugin-mcp-server/adapters/node';
+
await provider.initialize(context, {
- docsPath: 'build/mcp/docs.json',
- indexPath: 'build/mcp/search-index.json',
+ bundle: await readArtifactBundle('build/mcp'),
});
4. Rename the deprecated config types
The 2.1 config types keep their 2.1 shapes through 2.x and are removed in 3.0. Their replacements accept both the new and the old configs:
| 2.1 type (deprecated) | Use instead |
|---|---|
McpServerConfig | McpDocsServerConfig (any config), or McpServerBundleConfig |
McpServerFileConfig, McpServerDataConfig | McpServerBundleConfig |
WebRequestAdapterConfig | WebRequestHandlerConfig |
NodeServerOptions | NodeAdapterOptions |
In the new types, name is optional (it defaults to the build's), so code
that reads config.name should handle undefined.
getDocument and getDocCount are optional since 2.2: without them the server
answers docs_fetch and the status document count from the bundle.
5. Only bundle.json is written
Through 2.x the build also writes docs.json, search-index.json,
skills.json, manifest.json, and each indexer extra as separate files. In
3.0 it writes only bundle.json. If anything outside this package reads those
files (deploy scripts, a CDN rule, your own tooling), read bundle.json
instead, or use readArtifactBundle(dir).
LocalSearchIndexer.finalize() stops returning docs.json; the plugin writes
documents itself.
6. Serve Node requests with createNodeHandler
McpDocsServer.handleHttpRequest(req, res, parsedBody?) is removed in 3.0.
createNodeHandler does the same bridging and also answers the status check,
the CORS preflight, and other methods, the same way the web handler does. It
uses req.body when a body parser has already read the request, so it works
behind express.json():
-const server = new McpDocsServer({ artifacts });
-app.post('/mcp', express.json(), (req, res) => server.handleHttpRequest(req, res, req.body));
+import { createNodeHandler } from 'docusaurus-plugin-mcp-server/adapters/node';
+
+app.all('/mcp', express.json(), createNodeHandler({ artifacts }));
To keep sending only POST to it, mount it with app.post instead.
createNodeHandler sends CORS headers allowing every origin
(Access-Control-Allow-Origin: *) by default, which handleHttpRequest never
did. Pass corsOrigin: 'https://your.site' to restrict them, or
corsOrigin: false to send none (for example, if you set your own).
7. Search providers: SearchRanker, isReady, healthCheck, and the plugin search option
Since 2.2, the server needs only a name and a search function from a
search provider (the SearchRanker type); initialize, getDocument, and
getDocCount are optional. In 3.0:
SearchProviderbecomesSearchRanker's shape:initializeis optional, andisReadyandhealthCheckare removed.- The server stops calling
isReady(). Through 2.x, when it returns false the tools answer "Server not initialized". To fail a provider that can't work, reject frominitialize()(the server then fails to initialize, and requests and theGETstatus return an error) or throw fromsearch()(thatdocs_searchcall gets an error result). healthCheck()was never called by the server. Use theGETstatus endpoint orMcpDocsServer.getStatus().- The plugin's
searchoption is removed. It has done nothing since 2.0: the provider is chosen in the server config.
-import type { SearchProvider } from 'docusaurus-plugin-mcp-server';
+import type { SearchRanker } from 'docusaurus-plugin-mcp-server';
import type {
ProviderContext,
SearchOptions,
SearchProviderInitData,
SearchResult,
} from 'docusaurus-plugin-mcp-server';
-export default class GleanSearchProvider implements SearchProvider {
+export default class GleanSearchProvider implements SearchRanker {
readonly name = 'glean';
async initialize(context: ProviderContext, initData?: SearchProviderInitData) {
if (!process.env.GLEAN_API_TOKEN) throw new Error('GLEAN_API_TOKEN required');
}
- isReady() {
- return Boolean(process.env.GLEAN_API_TOKEN);
- }
-
- async healthCheck() {
- return { healthy: this.isReady() };
- }
-
async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
return [];
}
}
A provider with no setup can be a plain object:
createWebRequestHandler({
artifacts: bundle,
search: { name: 'glean', search: async (query, options) => [] },
});
Code that drives a provider itself should call the optional members optionally:
-await provider.initialize(context, { bundle });
-if (!provider.isReady()) throw new Error('not ready');
+await provider.initialize?.(context, { bundle });
In docusaurus.config.js, drop the plugin's search option and pass the
provider where the server runs:
// docusaurus.config.js
plugins: [
['docusaurus-plugin-mcp-server', {
server: { name: 'my-docs' },
- search: '@myorg/glean-search',
}],
],
// worker.js
+import GleanSearchProvider from '@myorg/glean-search';
+
-createWebRequestHandler({ artifacts: bundle });
+createWebRequestHandler({ artifacts: bundle, search: new GleanSearchProvider() });
This works however the server config gives the provider: as an instance, or
as a module path (search: './my-search.js') whose default export is a
SearchRanker class or object. (loadSearchProvider(), called directly with
a module path, still requires a full SearchProvider through 2.x, since it
promises one.)
For agents
To prepare a project for 3.0.0 (on 2.2 or later), run this checklist in order.
- In
package.json, make suredocusaurus-plugin-mcp-serveris^2.2.0or later. - Search the repo for
searchIndexData,docsPath,indexPath, andskillsPath:- In
createWebRequestHandler(...)ornew McpDocsServer(...)calls that passdocs/searchIndexData/skills: replace the three imports ofbuild/mcp/*.jsonwithimport bundle from '<same dir>/bundle.json', and passartifacts: bundleinstead of those three options. - In
createNodeServer(...)/createNodeHandler(...)calls that passdocsPath/indexPath/skillsPath: passartifactsDir: '<the directory those paths were in>'instead. - In
new McpDocsServer(...)calls that passdocsPath/indexPath: passartifacts: await readArtifactBundle('<that directory>'), importingreadArtifactBundlefromdocusaurus-plugin-mcp-server/adapters/node. - Remove
name,version, andbaseUrlfrom those calls only if they match the plugin'sserver.name,server.version, and the site URL indocusaurus.config.*. Otherwise keep them; they override the build's values (baseUrlonly changes what the status reports and providers receive; page URLs come from the build).
- In
- In classes that implement
SearchProvider, replace reads ofinitData.docs,initData.indexData,initData.docsPath, andinitData.indexPathwithinitData.bundle.docsandinitData.bundle.searchIndex. Files the matching indexer wrote are ininitData.bundle.extras, keyed by filename.bundleis optional in the type; guard for it. McpDocsServer sets it once step 2 has moved the server config toartifacts/artifactsDir, so do step 2 first. Where code callsprovider.initialize(context, { docsPath, indexPath })or{ docs, indexData }itself, pass{ bundle: await readArtifactBundle(dir) }instead. - Replace the deprecated types:
McpServerConfig→McpDocsServerConfig(orMcpServerBundleConfig),McpServerFileConfig/McpServerDataConfig→McpServerBundleConfig,WebRequestAdapterConfig→WebRequestHandlerConfig,NodeServerOptions→NodeAdapterOptions. Where code reads.namefrom the new types, handleundefined. - Search deploy scripts and CI config for
docs.json,search-index.json,skills.json, andmanifest.jsonunderbuild/mcp. Switch them tobundle.json. - Search the repo for
handleHttpRequest(. Replace eachserver.handleHttpRequest(req, res, body?)route withcreateNodeHandler(<the config that server was constructed with>)fromdocusaurus-plugin-mcp-server/adapters/node, mounted on the same path and methods. It sendsAccess-Control-Allow-Origin: *by default: if that code sets CORS headers itself, or the endpoint must not be callable from other origins' browser pages, passcorsOrigin: false(or a specific origin). - Search the repo for
isReady(. For each class or object that implementsSearchProvider:- First, search for
loadSearchProvider(calls that load it by module path (loadSearchProvider('./my-search.js')). Through 2.x those still require a fullSearchProvider: change each one to import the module and pass an instance (loadSearchProvider(new MySearch())), or leave this provider'sisReady()in place until 3.0 and skip the rest of this step for it. (Server configs that pass it assearch: './my-search.js'are fine.) - Delete the
isReady()method; if it could return false, makeinitialize()reject in that case instead (orsearch()throw). If the class'shealthCheck()callsthis.isReady(), do step 8 for this class now. - Change
implements SearchProvidertoimplements SearchRanker(and the import). For an object declaredconst mySearch: SearchProvider = {...}, replace the annotation withsatisfies SearchRanker(if the object defineshealthCheck(), do step 8 for it first:satisfiesrejects membersSearchRankerdoesn't have). - For other variables, parameters, and fields annotated
: SearchProviderthat hold it: annotate them with its own type (: MySearchfor a class,: typeof mySearchfor an object), which keeps every method it defines callable. Or annotate them: SearchRanker, and then callinitialize,getDocument, andgetDocCountthrough them optionally (provider.initialize?.(...)): they are optional onSearchRanker. - Where code calls
provider.isReady()on a provider it drives itself, delete the call.
- First, search for
- Search the repo for
healthCheck(. DeletehealthCheck()methods on search providers, and replace calls to them with theGETstatus endpoint orMcpDocsServer.getStatus(). - In
docusaurus.config.*, search the plugin options forsearch:. Delete the option. If its value was not'local', the plugin option never chose it: check which provider the server config (createWebRequestHandler(...),createNodeServer(...),createNodeHandler(...), ornew McpDocsServer(...)) passes assearch, and tell the user if it is not the one the plugin option named. - Run
docusaurus build, thennpx docusaurus-mcp-verify(add--output-dir <dir>if the plugin'soutputDiris set). It must pass with no warnings.