Back to blog
AI
Intermediate

Building Model Context Protocol Servers with Node.js: A Complete Guide

Model Context Protocol (MCP) enables AI models to connect securely to external tools and data sources. This comprehensive guide shows you how to build production-ready MCP servers using Node.js, with real-world examples, security best practices, and deployment strategies.

January 16, 202522 min read

Introduction

The Model Context Protocol (MCP) represents a significant shift in how AI models interact with external systems. Traditionally, connecting a large language model to a database, file system, or custom API required writing custom integration code for every single connection. MCP changes this by establishing a standardized protocol that allows AI clients to discover and use capabilities exposed by MCP servers.

For Node.js developers, this opens up practical opportunities. You can build MCP servers that expose your existing databases, internal APIs, file systems, or business logic to AI assistants like Claude, Cursor, or any MCP-compatible client. Instead of hardcoding AI integrations into your application, you expose capabilities through a standardized interface, and the AI client handles the orchestration.

This guide walks through the complete process of building, testing, securing, and deploying an MCP server using Node.js. We cover the protocol architecture, the official MCP TypeScript SDK, transport options, real-world examples, and production considerations. By the end, you will have a working MCP server and a clear understanding of how to integrate it into AI workflows.

Table of Contents

Core Concepts

MCP follows a client-server architecture where the MCP client (typically an AI assistant or IDE) connects to one or more MCP servers. Each server exposes capabilities through three primary mechanisms: resources, tools, and prompts.

Resources represent data that the server can provide to the client. Examples include file contents, database query results, or API responses. Resources are identified by URIs and can be static or dynamic. When an AI client requests a resource, the server returns the content along with metadata such as MIME type.

Tools are executable functions that the AI model can call. Unlike resources, which are passive data, tools perform actions. Examples include querying a database, sending an email, creating a GitHub issue, or processing an image. The server defines the tool's name, description, and input schema using JSON Schema, and the client decides when to invoke it based on the model's reasoning.

Templates, sometimes called prompts in MCP documentation, are reusable prompt structures that help clients construct effective requests. They can include parameters and are useful for standardizing common AI interactions.

Transport defines how the client and server communicate. MCP supports stdio transport for local processes and Server-Sent Events (SSE) for HTTP-based communication. The choice of transport affects deployment options, security model, and latency characteristics.

The initialization handshake establishes capabilities between client and server. During this phase, both parties exchange information about protocol versions, supported transports, and available capabilities. This negotiation ensures compatibility before any actual work begins.

Architecture Overview

A typical MCP deployment consists of three layers: the AI client, the MCP server, and the upstream services or data sources. The AI client runs in the user's environment, such as a desktop application or editor extension. It maintains connections to one or more MCP servers and manages the conversation with the underlying language model.

The MCP server is the component you build. It implements the MCP protocol, exposes resources and tools, and connects to your backend systems. In a Node.js implementation, the server runs as a standalone process or HTTP service. It receives requests from the client, translates them into calls to your databases or APIs, and returns formatted responses.

Upstream services are the actual systems being exposed. These can include PostgreSQL databases, Redis caches, S3 buckets, internal REST APIs, or filesystem paths. The MCP server acts as an adapter layer, translating MCP requests into native queries and converting results back into MCP-compatible responses.

Security is enforced at multiple levels. The MCP server should validate all inputs, enforce authentication where required, and operate under the principle of least privilege. Since AI models can be prompted to invoke tools with unexpected arguments, input validation is critical.

For Node.js developers, the official MCP SDK provides TypeScript classes and decorators that simplify server implementation. The SDK handles protocol parsing, transport management, and capability negotiation, allowing you to focus on your business logic.

Step-by-Step Guide

Building an MCP server with Node.js involves several distinct stages. We will walk through project setup, server implementation, capability definition, client connection, and testing.

First, initialize a new Node.js project. Create a directory for your MCP server and run npm init to generate a package.json file. Install the official MCP SDK and TypeScript development dependencies. Configure TypeScript with strict mode enabled to catch type errors early.

Next, create the server entry point. Import the Server class from the MCP SDK and instantiate it with your server's name and version. Define the capabilities your server supports, such as resources and tools. Set up the transport layer based on your deployment needs.

Define your resources using the server's resource handler. Each resource needs a URI pattern, a name, and a handler function that returns the resource content. For example, you might expose a database table as a resource with a URI like database://users.

Define your tools using the server's tool handler. Each tool requires a name, description, input schema, and handler function. The input schema uses JSON Schema to describe the parameters the AI client can pass. Your handler executes the actual logic, such as running a SQL query or calling an external API.

Start the server and connect it to a transport. For local development, stdio transport is simplest because it works with command-line MCP clients. For remote access, configure SSE transport with an HTTP server.

Test the server with an MCP-compatible client. Configure the client to launch your server process and verify that resources and tools appear in the client's capability list. Send test requests to confirm that responses are correctly formatted.

Add error handling and logging. MCP servers should return structured error responses that the client can interpret. Implement request logging for debugging and audit purposes.

Finally, package your server for distribution. Create a build script that compiles TypeScript to JavaScript, and define a start script in package.json. Consider publishing to npm if you want other teams to use your MCP server.

Real-World Examples

One practical example is a PostgreSQL MCP server. This server exposes database tables as resources and SQL query execution as a tool. An AI assistant can browse available tables, inspect schema information, and run read-only queries through the MCP interface. The server validates all queries to prevent destructive operations and enforces read-only access for safety.

Another example is a file system MCP server. It exposes directories and files as resources, allowing AI assistants to read project documentation, configuration files, or source code. Tools can include file search, directory listing, and content reading. Access controls ensure the server only reads from permitted directories.

A third example is a GitHub MCP server. Resources include repository information, issue lists, and pull request details. Tools enable creating issues, commenting on pull requests, and searching code. Authentication is handled through GitHub personal access tokens, and the server scopes operations to the authenticated user's permissions.

These examples share a common pattern: they take existing systems and expose them through a standardized MCP interface. The AI client does not need custom code for each system; it uses the MCP protocol uniformly across all connected servers.

Production Code Examples

import { Server } from '@modelcontextprotocol/sdk/server/index.js';import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';import {  CallToolRequestSchema,  ListToolsRequestSchema,  Tool,} from '@modelcontextprotocol/sdk/types.js';const server = new Server(  {    name: 'example-mcp-server',    version: '1.0.0',  },  {    capabilities: {      tools: {},    },  });const tools: Tool[] = [  {    name: 'query_database',    description: 'Execute a read-only SQL query against the database',    inputSchema: {      type: 'object',      properties: {        sql: {          type: 'string',          description: 'SQL SELECT query to execute',        },      },      required: ['sql'],    },  },];server.setRequestHandler(ListToolsRequestSchema, async () => {  return {    tools,  };});server.setRequestHandler(CallToolRequestSchema, async (request) => {  const { sql } = request.params.arguments as { sql: string };  if (!sql.trim().toLowerCase().startsWith('select')) {    throw new Error('Only SELECT queries are allowed');  }  const result = await executeQuery(sql);  return {    content: [      {        type: 'text',        text: JSON.stringify(result, null, 2),      },    ],  };});const transport = new StdioServerTransport();server.connect(transport);console.error('MCP server running on stdio');

This example demonstrates a minimal MCP server with a single tool. The server validates that only SELECT queries are allowed, preventing destructive database operations. The tool description helps the AI model understand when to invoke it.

import { Server } from '@modelcontextprotocol/sdk/server/index.js';import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';import express from 'express';const app = express();const server = new Server(  {    name: 'production-mcp-server',    version: '2.0.0',  },  {    capabilities: {      resources: {},      tools: {},    },  });app.get('/sse', async (req, res) => {  const transport = new SSEServerTransport('/message', res);  await server.connect(transport);});app.post('/message', express.json(), async (req, res) => {  await SSEServerTransport.handlePostMessage(req, res, server);});app.listen(3000, () => {  console.error('MCP server listening on port 3000');});

This SSE-based example shows how to expose an MCP server over HTTP. The /sse endpoint establishes the Server-Sent Events connection, while /message handles incoming client messages. This pattern is suitable for cloud deployments where clients connect remotely.

Comparison Table

TransportUse CaseConnection TypeBest For
stdioLocal process communicationStandard input/outputCLI tools, desktop integrations, local development
SSEHTTP-based remote communicationServer-Sent EventsCloud deployments, web-based clients, remote teams

Best Practices

Always validate input schemas strictly. Use JSON Schema to define exact types, formats, and constraints for all tool parameters. Never trust data received from an AI client, as models can generate unexpected or malformed arguments.

Implement read-only operations by default. If your MCP server exposes database or file system access, restrict write operations unless explicitly required. This reduces the risk of accidental data modification by an AI agent.

Use descriptive tool names and descriptions. The AI model relies on these descriptions to decide when to invoke a tool. Clear, specific descriptions improve reliability and reduce incorrect tool selection.

Structure errors consistently. Return error messages that help the client understand what went wrong without exposing internal stack traces or sensitive system information.

Log all tool invocations. Maintain an audit trail of which tools were called, with what arguments, and what results were returned. This is essential for debugging and security monitoring.

Version your server capabilities. As you add or modify resources and tools, update the server version and notify connected clients. Breaking changes should be communicated clearly.

Common Mistakes

A common mistake is exposing write operations without confirmation. AI models may invoke destructive tools automatically based on their interpretation of a user request. Always require explicit user confirmation for operations that modify data.

Another mistake is neglecting input validation. Without strict schema validation, an AI model might pass invalid SQL, malformed file paths, or unexpected data types to your server, causing errors or security vulnerabilities.

Some developers hardcode credentials in their MCP server code. Never store API keys, database passwords, or tokens in source code. Use environment variables or secret management systems instead.

Ignoring transport limitations is also problematic. stdio transport works only for local processes, while SSE requires careful CORS and authentication configuration for public endpoints. Choose the transport that matches your deployment architecture.

Failing to document tool behavior clearly leads to poor AI performance. If the model does not understand what a tool does, it will not invoke it appropriately, or it may invoke it with incorrect parameters.

Performance Tips

Cache expensive resource responses. If your MCP server queries a database or calls an external API for resources, implement caching to reduce latency and upstream load. Cache invalidation should match the data's freshness requirements.

Use connection pooling for database access. Creating a new database connection for every MCP request adds overhead. A connection pool reuses connections and improves response times under concurrent load.

Limit concurrent tool executions. AI clients may invoke multiple tools simultaneously. Implement rate limiting or queuing to prevent overwhelming upstream services.

Compress large responses. If your resources return substantial text content, use compression where supported by the transport to reduce bandwidth usage.

Monitor response times. Track the latency of individual tool invocations and resource requests to identify bottlenecks in your upstream integrations.

Security Considerations

Authenticate all MCP connections. For SSE deployments, implement token-based authentication or OAuth to ensure only authorized clients can connect. For stdio transport, rely on operating system process permissions.

Authorize tool access per user or client. Not every MCP client should have access to every tool. Implement role-based access control where sensitive operations are restricted to trusted clients.

Sanitize all outputs returned to the AI client. Remove sensitive data such as passwords, API keys, or personally identifiable information from resource and tool responses before sending them to the client.

Use the principle of least privilege for upstream access. The MCP server should connect to databases and APIs with the minimum permissions necessary to perform its functions.

Implement request size limits. Malicious or malfunctioning clients could send excessively large requests. Set limits on request body sizes and resource content lengths.

Deployment Notes

For stdio-based MCP servers, package them as executable binaries or npm packages. Clients launch the server process directly, so ensure the entry point is clearly defined in package.json.

For SSE-based servers, deploy as a standard Node.js HTTP service. Use a process manager like PM2 or systemd to keep the server running. Place a reverse proxy such as Nginx in front for TLS termination and load balancing.

Configure environment variables for all configuration values, including database connection strings, API keys, and port numbers. Never commit secrets to version control.

Set up health check endpoints for monitoring. Your deployment platform can use these endpoints to verify the MCP server is operational and ready to accept connections.

Plan for scaling. If many AI clients connect to your MCP server, consider horizontal scaling with multiple instances behind a load balancer for SSE transport.

Debugging Tips

Enable verbose logging during development. Log all incoming requests, outgoing responses, and errors. This helps you understand the protocol exchange between client and server.

Use an MCP inspector tool if available. These tools allow you to connect to your server manually and test resources and tools without requiring a full AI client.

Check transport-specific issues. For stdio, verify that your server writes to stderr for logs and stdout only for protocol messages. For SSE, check browser developer tools for connection errors and CORS issues.

Validate JSON Schema definitions. Incorrect input schemas can cause the client to reject tool calls or the model to generate invalid arguments.

Test edge cases. Verify how your server handles empty results, malformed input, network timeouts, and upstream service failures.

FAQ

What is the Model Context Protocol?

MCP is an open standard that enables AI models to connect to external tools, data sources, and services through a consistent interface. It reduces the need for custom integrations for each AI system.

Do I need TypeScript to build an MCP server?

The official MCP SDK is written in TypeScript, but you can use plain JavaScript. TypeScript provides better type safety and autocomplete, which is valuable when working with protocol types.

Can one MCP server expose both resources and tools?

Yes. A single MCP server can declare capabilities for both resources and tools, allowing it to serve data and execute actions through the same connection.

Is MCP only for Anthropic models?

No. MCP is an open protocol, and any AI client or model provider can implement support for it. While Anthropic originated the specification, it is designed to be model-agnostic.

Can I use MCP with existing Node.js applications?

Yes. You can add an MCP server alongside your existing Node.js application, exposing specific capabilities from your application to AI clients without rewriting your core logic.

How do I handle authentication in MCP?

Authentication depends on your transport. For SSE, use HTTP headers or query parameters for tokens. For stdio, rely on the operating system's user permissions.

What happens if my MCP server goes down?

The AI client loses access to the resources and tools provided by that server. The client should handle disconnection gracefully and inform the user that certain capabilities are unavailable.

Can I write MCP servers in languages other than Node.js?

Yes. The MCP specification is language-independent, and SDKs exist for multiple languages including Python. Node.js is one of several viable options.

Conclusion

Building MCP servers with Node.js gives you a practical way to connect your existing infrastructure to AI assistants. By following the patterns in this guide, you can expose databases, file systems, APIs, and business logic through a standardized protocol that any MCP-compatible client can use.

Start small by exposing a single resource or tool, test thoroughly with an MCP client, and gradually expand your server's capabilities. Pay close attention to security, input validation, and error handling, as these determine whether your server is safe for production use.

Ready to start building? Set up a new Node.js project, install the MCP SDK, and expose your first tool today. Your AI workflows will benefit from the seamless integration.