Table of Contents

Introduction

The Model Context Protocol (MCP) is revolutionizing how AI applications interact with external tools and data sources. For JavaScript and Node.js developers, MCP offers a powerful way to create integrations that connect large language models (LLMs) with various services, databases, and APIs. This guide will walk you through building both MCP servers and clients using the Node.js ecosystem.

MCP Overview for JavaScript Developers

At its core, MCP is a client-server protocol that standardizes how AI applications connect to external tools and data sources. Think of it as a "USB-C port for AI applications" – it provides a universal connector that works across different AI models and applications.

For JavaScript developers, MCP offers several advantages:

  • Leverage Existing Skills: Use familiar JavaScript/TypeScript patterns to build AI integrations
  • Async-First Design: MCP's design aligns perfectly with Node.js's asynchronous nature
  • Ecosystem Compatibility: Easily integrate with the vast Node.js package ecosystem
  • Cross-Platform Support: Deploy your MCP servers on any platform that runs Node.js

Setting Up Your Development Environment

Let's start by setting up our development environment:

Prerequisites

  • Node.js (v16 or higher)
  • npm or yarn
  • Basic understanding of TypeScript (recommended for server development)

Installing the MCP SDK

The official TypeScript SDK for MCP is maintained by the Model Context Protocol team. Install it using npm:

# For an MCP server
npm install @mcp/server

# For an MCP client
npm install @mcp/client

Project Setup

Create a new project structure:

mkdir mcp-js-demo
cd mcp-js-demo
npm init -y

# Add TypeScript (recommended)
npm install typescript @types/node ts-node --save-dev

# Create tsconfig.json
npx tsc --init

# Create project folders
mkdir src
mkdir src/server
mkdir src/client

Creating an MCP Server with TypeScript

Now let's build a simple MCP server that provides weather-related tools:

Basic Server Setup

Create a file src/server/weather-server.ts:

import { Server, ToolDefinition } from '@mcp/server';

// Initialize the server
const server = new Server({
  name: 'Weather MCP Server',
  version: '1.0.0',
  description: 'Provides weather information tools'
});

// Define a simple tool
interface GetWeatherParams {
  location: string;
}

interface WeatherData {
  temperature: number;
  conditions: string;
  humidity: number;
}

// Mock weather data - in a real app, you would call a weather API
const getWeatherMock = (location: string): WeatherData => {
  // Simulate API call
  return {
    temperature: 72,
    conditions: 'Partly Cloudy',
    humidity: 65
  };
};

// Register the tool
server.addTool({
  name: 'get_weather',
  description: 'Get current weather for a location',
  parameters: {
    type: 'object',
    properties: {
      location: {
        type: 'string',
        description: 'The location to get weather for (city name or zip code)'
      }
    },
    required: ['location']
  },
  handler: async (params: GetWeatherParams) => {
    try {
      const weatherData = getWeatherMock(params.location);
      return weatherData;
    } catch (error) {
      // Proper error handling
      console.error('Error fetching weather:', error);
      throw new Error(`Failed to get weather for ${params.location}`);
    }
  }
});

// Start the server
server.listen();

console.log('Weather MCP Server running...');

Running the Server

npx ts-node src/server/weather-server.ts

This will start a local MCP server that listens on the standard input/output (stdio) transport by default.

Building an MCP Client in JavaScript

Now, let's create a client that connects to our weather server:

Client Implementation

Create a file src/client/weather-client.js:

const { MCPClient } = require('@mcp/client');
const { spawn } = require('child_process');

async function runClient() {
  // Create a child process for the server
  const serverProcess = spawn('npx', ['ts-node', 'src/server/weather-server.ts'], {
    stdio: ['pipe', 'pipe', 'pipe']
  });

  // Initialize the client
  const client = new MCPClient({
    protocolVersion: '2024-11-05',
    capabilities: {
      tools: { enabled: true }
    }
  });

  try {
    // Connect to the server using stdio transport
    await client.connect({
      transport: 'stdio',
      process: serverProcess
    });
    
    console.log('Connected to the Weather MCP Server');

    // List available tools
    const tools = await client.listTools();
    console.log('Available tools:', JSON.stringify(tools, null, 2));

    // Call the get_weather tool
    const weatherResult = await client.callTool('get_weather', { 
      location: 'New York' 
    });
    console.log('Weather in New York:', weatherResult);

    // Another example
    const bostonWeather = await client.callTool('get_weather', { 
      location: 'Boston' 
    });
    console.log('Weather in Boston:', bostonWeather);

  } catch (error) {
    console.error('Error:', error);
  } finally {
    // Disconnect and clean up
    await client.disconnect();
    serverProcess.kill();
  }
}

// Run the client
runClient();

Running the Client

node src/client/weather-client.js

Advanced Server Features

Let's enhance our server with more advanced MCP features:

Adding Resources

Resources in MCP provide read-only access to data. Let's add weather forecast resources:

// Add to weather-server.ts

// Define a resource
server.addResource({
  name: 'forecasts://{location}/5day',
  description: 'Get a 5-day forecast for a location',
  parameters: {
    type: 'object',
    properties: {
      location: {
        type: 'string',
        description: 'The location to get forecast for'
      }
    },
    required: ['location']
  },
  handler: async ({ location }) => {
    // Mock forecast data
    return [
      { day: 'Monday', high: 75, low: 62, conditions: 'Sunny' },
      { day: 'Tuesday', high: 78, low: 64, conditions: 'Partly Cloudy' },
      { day: 'Wednesday', high: 80, low: 66, conditions: 'Sunny' },
      { day: 'Thursday', high: 77, low: 63, conditions: 'Rain' },
      { day: 'Friday', high: 72, low: 60, conditions: 'Cloudy' }
    ];
  }
});

Adding Prompts

Prompts help guide the LLM in generating responses. Let's add a weather report prompt:

// Add to weather-server.ts

server.addPrompt({
  name: 'weather_report',
  description: 'Generate a detailed weather report',
  parameters: {
    type: 'object',
    properties: {
      location: {
        type: 'string',
        description: 'The location for the weather report'
      },
      includeForecasts: {
        type: 'boolean',
        description: 'Whether to include forecasts'
      }
    },
    required: ['location']
  },
  handler: async ({ location, includeForecasts }) => {
    const forecastText = includeForecasts 
      ? 'Please include a 5-day forecast in your report.' 
      : '';
    
    return `Please generate a detailed weather report for ${location}. 
    Include current conditions, temperature, and humidity. 
    ${forecastText}
    Make the report conversational and easy to understand.`;
  }
});

Tool Annotations

Tool annotations provide additional metadata about tools:

// Modify the tool definition
server.addTool({
  name: 'set_preferred_location',
  description: 'Set the user\'s preferred location for weather updates',
  parameters: {
    type: 'object',
    properties: {
      location: {
        type: 'string',
        description: 'The preferred location'
      }
    },
    required: ['location']
  },
  annotations: {
    destructive: true,  // This tool makes changes
    requires_approval: true,  // User should approve this action
    cost_estimate: 'low'  // Resource usage is low
  },
  handler: async ({ location }) => {
    // Save the preferred location
    return { success: true, message: `Set preferred location to ${location}` };
  }
});

Deploying MCP Servers

HTTP Server Deployment

For production, you'll often want to deploy your MCP server as an HTTP service:

// Create src/server/http-weather-server.ts

import { Server } from '@mcp/server';
import express from 'express';

// First, install Express
// npm install express @types/express

// Initialize the MCP server (reuse code from previous example)
const mcpServer = new Server({
  name: 'Weather MCP HTTP Server',
  version: '1.0.0',
  description: 'Provides weather information over HTTP'
});

// Add tools, resources, and prompts as before...

// Create an Express application
const app = express();
const PORT = process.env.PORT || 3000;

// Set up the MCP HTTP endpoints
mcpServer.setupExpress(app);

// Start the HTTP server
app.listen(PORT, () => {
  console.log(`Weather MCP HTTP Server running on port ${PORT}`);
});

Containerization with Docker

Create a Dockerfile:

FROM node:16-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .
RUN npm run build

EXPOSE 3000

CMD ["node", "dist/server/http-weather-server.js"]

Build and run:

docker build -t weather-mcp-server .
docker run -p 3000:3000 weather-mcp-server

Integrating with AI Applications

Connecting to Claude Desktop

To use your MCP server with Claude Desktop, you can configure it in the Claude Desktop settings:

{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["dist/server/weather-server.js"]
    }
  }
}

Custom AI Application Integration

For a custom AI application using the Claude API:

const { Client } = require('@anthropic-ai/sdk');
const { MCPClient } = require('@mcp/client');

// Set up Claude client
const claude = new Client({
  apiKey: process.env.ANTHROPIC_API_KEY
});

// Set up MCP client
const mcpClient = new MCPClient({/* config */});
await mcpClient.connect({/* connection details */});

// Get tool definitions for Claude
const tools = await mcpClient.listTools();
const claudeTools = tools.map(tool => ({
  name: tool.name,
  description: tool.description,
  input_schema: tool.parameters
}));

// Create a message with tool access
const message = await claude.messages.create({
  model: "claude-3-7-sonnet-20250219",
  max_tokens: 1000,
  messages: [
    { role: "user", content: "What's the weather like in Seattle?" }
  ],
  tools: claudeTools,
  tool_choice: "auto"
});

// Handle tool calls
if (message.content[0].type === 'tool_use') {
  const toolCall = message.content[0].tool_use;
  const result = await mcpClient.callTool(
    toolCall.name, 
    JSON.parse(toolCall.input)
  );
  
  // Send result back to Claude
  // ...
}

Testing and Debugging

Unit Testing MCP Servers

Create tests/weather-server.test.js:

const { MCPClient } = require('@mcp/client');
const { spawn } = require('child_process');
const { expect } = require('chai');

describe('Weather MCP Server', function() {
  let serverProcess;
  let client;
  
  before(async function() {
    // Start the server
    serverProcess = spawn('npx', ['ts-node', 'src/server/weather-server.ts']);
    
    // Initialize client
    client = new MCPClient({
      protocolVersion: '2024-11-05',
      capabilities: { tools: { enabled: true } }
    });
    
    // Connect
    await client.connect({
      transport: 'stdio',
      process: serverProcess
    });
  });
  
  after(async function() {
    // Clean up
    await client.disconnect();
    serverProcess.kill();
  });
  
  it('should list available tools', async function() {
    const tools = await client.listTools();
    expect(tools).to.be.an('array');
    expect(tools.length).to.be.greaterThan(0);
    expect(tools[0]).to.have.property('name', 'get_weather');
  });
  
  it('should return weather data for a location', async function() {
    const result = await client.callTool('get_weather', { location: 'Seattle' });
    expect(result).to.have.property('temperature');
    expect(result).to.have.property('conditions');
    expect(result).to.have.property('humidity');
  });
});

MCP Inspector Tool

The MCP team provides an inspector tool to debug servers:

npm install -g @mcp/inspector

# Run the inspector
mcp-inspector

This provides a GUI for exploring and testing your MCP servers.

Performance Optimization

Connection Pooling

For HTTP servers handling multiple clients:

import { createPool } from 'generic-pool';

// Create a connection pool
const pool = createPool({
  create: async () => {
    // Create a database connection or other resource
    return new DatabaseConnection();
  },
  destroy: async (client) => {
    // Close the connection
    await client.close();
  }
}, {
  max: 10, // Maximum connections in pool
  min: 2   // Minimum connections in pool
});

// Use in tool handler
server.addTool({
  name: 'query_weather_history',
  // ...
  handler: async (params) => {
    const connection = await pool.acquire();
    try {
      return await connection.query(/* ... */);
    } finally {
      await pool.release(connection);
    }
  }
});

Caching Strategies

import NodeCache from 'node-cache';

// Create a cache
const weatherCache = new NodeCache({ 
  stdTTL: 300, // Cache TTL in seconds (5 minutes)
  checkperiod: 60 // Check for expired items every 60 seconds
});

// Use in tool handler
server.addTool({
  name: 'get_weather',
  // ...
  handler: async ({ location }) => {
    // Check cache first
    const cacheKey = `weather:${location}`;
    const cachedData = weatherCache.get(cacheKey);
    
    if (cachedData) {
      return cachedData;
    }
    
    // If not in cache, fetch data
    const weatherData = await fetchWeatherFromAPI(location);
    
    // Store in cache
    weatherCache.set(cacheKey, weatherData);
    
    return weatherData;
  }
});

Real-World Examples

Database Integration

Here's how to create an MCP server for PostgreSQL integration:

import { Server } from '@mcp/server';
import { Pool } from 'pg';

// Create PostgreSQL connection pool
const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'weather_db',
  password: 'password',
  port: 5432,
});

// Initialize MCP server
const server = new Server({
  name: 'PostgreSQL MCP Server',
  description: 'Provides access to PostgreSQL database'
});

// Add a query tool
server.addTool({
  name: 'run_query',
  description: 'Run a SQL query against the database',
  parameters: {
    type: 'object',
    properties: {
      query: {
        type: 'string',
        description: 'SQL query to execute (SELECT only)'
      },
      params: {
        type: 'array',
        items: { type: 'string' },
        description: 'Query parameters'
      }
    },
    required: ['query']
  },
  annotations: {
    cost_estimate: 'medium'
  },
  handler: async ({ query, params = [] }) => {
    // Security check - only allow SELECT queries
    if (!query.trim().toLowerCase().startsWith('select')) {
      throw new Error('Only SELECT queries are allowed');
    }
    
    try {
      const result = await pool.query(query, params);
      return {
        rows: result.rows,
        rowCount: result.rowCount
      };
    } catch (error) {
      console.error('Database error:', error);
      throw new Error(`Database error: ${error.message}`);
    }
  }
});

// Start the server
server.listen();

API Integration

Creating an MCP server for a REST API:

import { Server } from '@mcp/server';
import axios from 'axios';

// Initialize MCP server
const server = new Server({
  name: 'GitHub MCP Server',
  description: 'Provides access to GitHub API'
});

// Add authentication
const githubToken = process.env.GITHUB_TOKEN;
const apiClient = axios.create({
  baseURL: 'https://api.github.com',
  headers: {
    Authorization: `token ${githubToken}`,
    Accept: 'application/vnd.github.v3+json'
  }
});

// Add a tool to list repositories
server.addTool({
  name: 'list_repositories',
  description: 'List GitHub repositories for a user',
  parameters: {
    type: 'object',
    properties: {
      username: {
        type: 'string',
        description: 'GitHub username'
      },
      perPage: {
        type: 'integer',
        description: 'Number of repositories per page'
      },
      page: {
        type: 'integer',
        description: 'Page number'
      }
    },
    required: ['username']
  },
  handler: async ({ username, perPage = 10, page = 1 }) => {
    try {
      const response = await apiClient.get(`/users/${username}/repos`, {
        params: { per_page: perPage, page }
      });
      
      return response.data.map(repo => ({
        name: repo.name,
        description: repo.description,
        stars: repo.stargazers_count,
        url: repo.html_url
      }));
    } catch (error) {
      console.error('GitHub API error:', error);
      throw new Error(`Failed to fetch repositories: ${error.message}`);
    }
  }
});

// Start the server
server.listen();

Troubleshooting Common Issues

Connection Problems

If your client can't connect to the server:

  1. Transport mismatch: Ensure client and server use the same transport (stdio, HTTP, etc.)
  2. Version incompatibility: Check that protocol versions match or are compatible
  3. Path issues: Verify the correct path to the server executable
  4. Permissions: Check file permissions for stdio servers

Error Handling Best Practices

server.addTool({
  name: 'example_tool',
  // ...
  handler: async (params) => {
    try {
      // Tool implementation
      return result;
    } catch (error) {
      // Detailed error logging (server-side only)
      console.error('Tool error:', error);
      
      // Return user-friendly error (sent to client)
      throw new Error('Could not complete the operation. Please try again later.');
    }
  }
});

Debugging Transport Issues

For stdio transport debugging:

const serverProcess = spawn('node', ['server.js'], {
  stdio: ['pipe', 'pipe', 'pipe']
});

// Log stderr for debugging
serverProcess.stderr.on('data', (data) => {
  console.error(`Server error: ${data}`);
});

// Log stdout if needed
serverProcess.stdout.on('data', (data) => {
  console.log(`Server output: ${data}`);
});

Resources and Community

Official Documentation

Community Resources

Tutorials and Blogs

Conclusion

The Model Context Protocol offers JavaScript and Node.js developers a powerful way to connect AI models with tools, data sources, and services. By leveraging your existing JavaScript skills, you can create MCP servers and clients that enhance AI applications with rich contextual capabilities.

As you build more sophisticated MCP integrations, remember the key principles:

  1. Security First: Always validate inputs and control access
  2. Performance Matters: Use caching and connection pooling for efficiency
  3. Clear Documentation: Document your tools, resources, and prompts thoroughly
  4. Error Handling: Provide meaningful error messages without exposing sensitive information

The MCP ecosystem is growing rapidly, with new tools, libraries, and best practices emerging. Stay connected with the community to learn about the latest developments and share your own innovations.

Related Posts

AIMCPModel Context ProtocolDeveloper ToolsAI integrationTech trends 2025AnthropicOpen sourceBlog

6 min read

The Model Context Protocol (MCP) has emerged as one of the most significant developments in AI technology in 2025. Launched by Anthropic in November 2024, MCP is an open standard designed to bridge AI...

API management solutionsAPI management softwarebest API management toolsBlog

5 min read

APIs (Application Programming Interfaces) are the backbone of modern digital applications. They allow different software systems to communicate, exchange data, and collaborate seamlessly. As businesse...

API security measuressecuring APIsAPI vulnerabilitiesBlog

4 min read

In today’s interconnected digital world, APIs (Application Programming Interfaces) are the backbone of communication between different software applications. From mobile apps to cloud services, APIs e...

Ready to bring your ideas to life?

Let's collaborate and create something amazing together.