Table of Contents
- Introduction
- MCP Overview for JavaScript Developers
- Setting Up Your Development Environment
- Creating an MCP Server with TypeScript
- Building an MCP Client in JavaScript
- Advanced Server Features
- Deploying MCP Servers
- Integrating with AI Applications
- Testing and Debugging
- Performance Optimization
- Real-World Examples
- Troubleshooting Common Issues
- Resources and Community
- Conclusion
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:
- Transport mismatch: Ensure client and server use the same transport (stdio, HTTP, etc.)
- Version incompatibility: Check that protocol versions match or are compatible
- Path issues: Verify the correct path to the server executable
- 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:
- Security First: Always validate inputs and control access
- Performance Matters: Use caching and connection pooling for efficiency
- Clear Documentation: Document your tools, resources, and prompts thoroughly
- 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
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...
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...