Deploy with Node.js and Express
The Node adapter (docusaurus-plugin-mcp-server/adapters/node) reads the bundle from build/mcp at startup, so nothing needs bundling. Use it when you run a long-lived Node process: a VM, a container, or a platform like Render or Fly.io.
Express
import express from 'express';
import { createNodeHandler } from 'docusaurus-plugin-mcp-server/adapters/node';
const app = express();
// Mount for every method: GET is the status check, OPTIONS the CORS preflight, POST is MCP.
app.all('/mcp', createNodeHandler({ artifactsDir: './build/mcp' }));
// The static site. `extensions` maps /docs/intro to docs/intro.html (trailingSlash: false builds).
app.use(express.static('build', { extensions: ['html'] }));
const port = Number(process.env.PORT ?? 3000);
app.listen(port, () => {
console.log(`Docs at http://localhost:${port}, MCP at http://localhost:${port}/mcp`);
});
npm install express
npm run build
node server.mjs
- Mount the handler with
app.all, notapp.post.GETis the status check andOPTIONSis the CORS preflight. - You don't need a body parser. The handler reads the body itself, with a 1 MB limit. If
express.json()has already run, the handler usesreq.body. - The handler reads
build/mcpon the first request and keeps it in memory. Restart the process after each rebuild. express.staticdoesn't try.htmlfor a path whose last segment contains a dot (such as/migrations/2.x-3.0.0). UsetrailingSlash: trueindocusaurus.config.jsif your slugs have dots.
Node's http module
To serve only the endpoint and nothing else, use createNodeServer. It answers on every path:
import { createNodeServer } from 'docusaurus-plugin-mcp-server/adapters/node';
createNodeServer({ artifactsDir: './build/mcp' }).listen(Number(process.env.PORT ?? 3456));
createNodeHandler returns a Node (req, res) handler, so it also works with http.createServer, Connect, or any framework that accepts one.
Behind a reverse proxy
If nginx or another proxy already serves build/, proxy only /mcp to the Node process:
location = /mcp {
proxy_pass http://127.0.0.1:3456;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_buffering off;
}
In a container
FROM node:24-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.mjs"]
Building inside the image ties each image to one build of the docs, so redeploying the image redeploys the docs and the endpoint together.
Check it
curl https://docs.example.com/mcp
You should get the status JSON. The Node server pretty-prints it.
Something not working? See Troubleshooting.