In this section
How to Build an MCP Server in Node.js to Provide Up-To-Date API Documentation
If you’re just getting started with vibe coding, connecting MCP Server as integrations and diving into the world of agentic AI then you’re in the right place. In this tutorial, I want to show you how to build a basic MCP Server in Node.js using the official Anthropic Model Context Protocol (MCP) SDK.
An MCP Server for up-to-date Node.js API documentation
Our MCP Server will fetch documentation from the official Node.js API documentation (https://nodejs.org/api/) in JSON format, and make them available as tools call to MCP Clients such as those implemented by Claude Desktop, Cursor, Qodo, and others.
This is handy because the Node.js runtime gets routine updates and support for new methods and utilities, but LLMs aren’t trained on the latest data. Even when LLMs have capabilities to browse the web, they might choose not to execute that action to answer a question that their training data already has, unless the host application (such as Cursor) has been specifically prompted to do that.
So, we’re going to build an MCP Server that reveals dedicated tools for each of the Node.js built-in core modules and sections as part of the API documentation structure and allow MCP Clients to call them.

The MCP Server project files
We start with the basic MCP Server project files, which are the package.json
metadata for dependencies and such:
{
"name": "mcp-server-nodejs-docs",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"description": "A MCP server that provides documentation for Node.js core modules API",
"scripts": {
"start": "node index.js --debug"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.0",
"pino": "^9.6.0",
"zod": "^3.24.4"
}
}
After you created the package.json
file with the above contents, run npm install
to fetch dependencies.
Initializing an MCP Server in Node.js using TypeScript SDK
Next, create an index.js
file and put the following contents in it:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { initializeDocumentationServer } from './server/documentation-server.js';
// Create an MCP server
const server = new McpServer({
name: "Node.js API Documentation",
version: "1.0.0",
capabilities: {
resources: {},
tools: {},
},
});
// Initialize the server with Node.js API documentation
async function startServer() {
try {
await initializeDocumentationServer(server);
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
} catch (error) {
console.error(`Fatal error during server initialization. Check logs for details.`);
process.exit(1);
}
}
// Start the server
startServer();
The above code starts with the Node.js UNIX shebang syntax of #!/usr/bin/env node
to allow the file to be executable in case you just want to run it from the command-line as an executable such as ./index.js
. However, that’s not required.
Next, we import the MCP Server definition from the TypeScript SDK, the STDIO Transport type and a helper documentation server initializer code that we’ll get to in a minute. Then we continue to create the MCP Server and connect it to the process STDIO transport type.
Fetch data and prepare MCP Server tools
What’s in ./server/documentation-server.js
?
The way to define tools for MCP Servers is the following:
// Math tool to find the maximum of two numbers
server.tool(
"math-find-maximum-two-numbers",
{
number1: z.number(),
number2: z.number()
},
async ({ number1, number2 }) => ({
content: [{
type: "text",
text: Math.max(number1, number2))
}]
})
);
However, we don’t want to manually call these server.tool()
functions for each built-in Node.js module but rather loop over all of them in a programmatic manner.
To do that, first, we need to grab the Node.js API documentation reference as a JSON data type.
In the initializeDocumentationServer
function, we receive the server
variable (this is the instantized MCP Server) which we can then pass along to the different methods that create tool definitions on it.
import { fetchNodeApiDocs } from '../services/api-docs-service.js';
import { createModuleTool, createSearchTool, createListTool } from '../tools/documentation-tools.js';
export async function initializeDocumentationServer(server) {
const apiDocs = await fetchNodeApiDocs();
// Remove entries without Class or Method
const originalCount = apiDocs.modules?.length;
apiDocs.modules = apiDocs.modules.filter(module =>
module?.classes?.length > 0 || module?.methods?.length > 0
);
// Create tools for each module
apiDocs.modules.forEach(module => {
createModuleTool(server, module);
});
// Create search and list tools
createSearchTool(server, apiDocs.modules);
createListTool(server, apiDocs.modules);
}
To fetch the Node.js API documentation, here’s the fetchNodeAPIDocs
function implementation from the ../services/api-docs-service.js
file:
const url = 'https://nodejs.org/docs/latest/api/all.json';
export async function fetchNodeApiDocs() {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
// TODO later implement with logging etc
throw error;
}
}
export function findModuleByName(modules, searchName) {
const normalizedSearch = normalizeModuleName(searchName);
return modules.find(module =>
normalizeModuleName(module.name) === normalizedSearch ||
normalizeModuleName(module.textRaw) === normalizedSearch ||
(module.displayName && normalizeModuleName(module.displayName) === normalizedSearch)
);
}
export function normalizeModuleName(name) {
return name.toLowerCase().replace(/[_\s-]/g, '');
}
When fetching the documentation, we also make available here a function to easily find modules by their name and match that to one of either naming properties and patterns that exist in the returned JSON object from the URL https://nodejs.org/docs/latest/api/all.json
.
MCP Server tools definition
Now we can programmatically process all the Node.js core modules from the JSON file and expose them as their own tool for each one.
Following is the code for ./tools/documentation-tools.js
which includes all the functions referenced in the documentation-server.js file:
import { z } from "zod";
import { findModuleByName } from '../services/api-docs-service.js';
function createModuleDocumentation(module, { class: classQuery, method: methodQuery } = {}) {
let content = `# ${module.textRaw}\n\n`;
if (module.desc) {
content += `## Description\n${formatContent(module.desc)}\n\n`;
}
const formatItems = (items, title, query) => {
if (!items || items.length === 0) return '';
const filteredItems = query
? items.filter(item =>
item.textRaw.toLowerCase().includes(query.toLowerCase()) ||
(item.desc && item.desc.toLowerCase().includes(query.toLowerCase()))
)
: items;
if (filteredItems.length === 0) return '';
let sectionContent = `## ${title}\n\n`;
filteredItems.forEach(item => {
sectionContent += `### ${item.textRaw}\n`;
if (item.desc) sectionContent += `${formatContent(item.desc)}\n\n`;
});
return sectionContent;
};
content += formatItems(module.classes, 'Classes', classQuery);
content += formatItems(module.methods, 'Methods', methodQuery);
content += formatItems(module.modules, 'Submodules');
return content;
}
export function createModuleTool(server, module) {
const { name, textRaw } = module;
const toolName = `node-${name.toLowerCase().replace(/[^a-zA-Z0-9]/g, '-')}`;
server.tool(
toolName,
{
class: z.string().optional(),
method: z.string().optional()
},
async (params) => {
try {
const content = createModuleDocumentation(module, params);
return { content: [{ type: "text", text: content }] };
} catch (error) {
// TODO
throw error;
}
}
);
}
export function createSearchTool(server, modules) {
server.tool(
"node-search",
{ module: z.string().optional() },
async (params) => {
const moduleName = params?.module;
const foundModule = moduleName ? findModuleByName(modules, moduleName) : null;
if (!foundModule) {
let listContent = 'Available Node.js core modules and their methods:\n\n';
modules.forEach(module => {
listContent += formatModuleSummary(module);
});
return { content: [{ type: "text", text: listContent }] };
}
const content = createModuleDocumentation(foundModule);
return { content: [{ type: "text", text: content }] };
}
);
}
export function createListTool(server, modules) {
server.tool(
"node-list",
{},
async () => {
const content = formatModulesList(modules);
return { content: [{ type: "text", text: content }] };
}
);
}
function formatModuleSummary(module) {
let content = `## ${module.displayName || module.textRaw} (${module.name})\n`;
if (module.methods && module.methods.length > 0) {
content += `### Methods\n`;
module.methods.forEach(method => {
content += `- ${method.textRaw}\n`;
});
} else {
content += `_No methods found_\n`;
}
return content + '\n';
}
function formatModulesList(modules) {
const modulesList = modules.map(module => ({
name: module.name,
displayName: module.displayName || module.textRaw,
description: module.desc || 'No description available'
}));
return `# Available Node.js Modules\n\n${modulesList.map(m =>
`## ${m.displayName}\n*Name:* ${m.name}\n*Description:* ${formatContent(m.description)}\n`
).join('\n')}`;
}
export function formatContent(content) {
if (!content) return '';
return content.replace(/\n/g, '\n\n');
}
Debug the Node.js MCP Server with MCP Inspector
To debug if this server works ok and what exactly our tools definition looks like we can use the MCP Inspector from Anthropic.
Add the following to your package.json
file’s scripts
property:
{
"scripts": {
"debug": "npx @modelcontextprotocol/inspector node index.js --debug"
}
}
Next, install Anthorpic’s npm package @modelcontextprotocol/inspecto
r as part of your development dependencies in the project:
npm install @modelcontextprotocol/inspector --save-dev
Finally, let’s start the MCP Inspector by running the following:
npm run debug
Browse to the MCP Inspector address and you can see all the exposed tools and their definitions:

What’s next with MCP Servers?
The full source code for a working MCP Server for Node.js API documentation is available in this GitHub repository URL: https://github.com/snyk-labs/mcp-server-nodejs-api-docs. It is up-to-date and includes logging and MCP Server resources exposed too, for more complex examples on building MCP Servers.
If you’re searching for MCP Servers to connect to, don’t miss out Snyk’s AI Security guardrails with Snyk introducing an MCP Server as part of the Snyk CLI which means you can take a more proactive approach to vibe coding and building applications with AI code assistants.
Get Snyk to audit your GenAI code assistant when these tools suggest open source dependencies and code, as easy as the following:
snyk mcp -t stdio --experimental
Also don’t forget that installing and connecting MCP Servers can introduce security risks, run arbitrary code and other problems.
Sign-up for Snyk API & Web
Start using our dev-first DAST engine today
Automatically find and expose vulnerabilities at scale with Snyk's AI-driven DAST engine to shift left with automation and fix guidance that integrates seamlessly into your SDLC.