DEV Community

Cover image for Building Your First MCP Server: From Zero to AI-Powered Enterprise Tools
Suparn Kumar
Suparn Kumar

Posted on

Building Your First MCP Server: From Zero to AI-Powered Enterprise Tools

🚀 Building Your First MCP Server: From Zero to AI-Powered Enterprise Tools

A comprehensive guide to understanding and implementing Model Context Protocol (MCP) with Node.js and Express


🤔 What is MCP and Why Should You Care?

Imagine if your AI assistant could directly access your company's APIs, databases, and business tools without you having to copy-paste data or manually execute commands. That's exactly what Model Context Protocol (MCP) makes possible.

Released by Anthropic in November 2024, MCP is an open-source standard that creates a secure, standardized bridge between AI applications (like Claude, ChatGPT, or custom AI agents) and external systems.

The Problem MCP Solves

Before MCP, developers faced the "N×M integration problem":

  • N different AI models each requiring custom integrations
  • M different data sources and tools each needing specific connectors
  • Result: N×M custom connectors to maintain 😵

MCP eliminates this complexity by providing a universal interface that any MCP-compatible AI can use to interact with any MCP server.


🆚 MCP vs Traditional APIs: Understanding the Difference

Let's clear up the confusion between MCP and traditional APIs:

Aspect Traditional APIs MCP Servers
Purpose General application integration AI-specific tool integration
Discovery Manual documentation Dynamic tool discovery
Context Stateless requests Contextual, conversational
Protocol REST/GraphQL/SOAP JSON-RPC 2.0 over HTTP/SSE
Integration Manual coding required AI agent auto-discovers tools
Security API keys, OAuth User consent + access controls

Why Not Just Use Regular APIs?

Traditional API approach:

// Manual integration - developer writes code
const weatherData = await fetch(https://api.weather.com/v1/weather?q=${city});
const result = await weatherData.json();
// AI can't discover or use this automatically
Enter fullscreen mode Exit fullscreen mode

MCP approach:

// AI agent discovers and uses tools automatically
server.tool('get-weather', 'Get weather for any city', {
city: z.string().describe("City name")
}, async ({ city }) => {
// AI can discover, understand, and call this tool
return { content: [{ type: "text", text: weatherResult }] };
});
Enter fullscreen mode Exit fullscreen mode

The key difference: APIs require manual integration, MCP enables automatic AI discovery and usage.


🏗️ MCP Architecture: The Building Blocks

MCP follows a client-server architecture with three main components:

1. MCP Host (The AI Application)

  • Examples: Claude Desktop, VS Code with Copilot, custom AI apps
  • Role: Initiates connections and sends requests to MCP servers

2. MCP Client (The Connector)

  • Role: Manages communication between host and servers
  • Transport: HTTP, WebSockets, or Server-Sent Events (SSE)

3. MCP Server (Your Tools & Data)

  • Role: Exposes tools, resources, and capabilities to AI
  • Examples: Weather service, database connector, business workflow automation

Data Flow:

AI HostMCP ClientMCP ServerResults Back

  • 🤖 AI Host: Claude, GPT, VS Code
  • 🔄 MCP Client: HTTP/WebSocket transport layer
  • 🛠️ MCP Server: Your business tools and APIs

🛠️ Building Your First MCP Server: Step-by-Step Tutorial

Let's build a production-ready MCP server that demonstrates the power of AI-integrated business tools.

Prerequisites

  • Node.js 18+ installed
  • Basic knowledge of JavaScript/Express
  • Text editor (VS Code recommended)

Step 1: Project Setup

mkdir enterprise-mcp-server
cd enterprise-mcp-server
npm init -y
Enter fullscreen mode Exit fullscreen mode

text

Step 2: Install Dependencies

npm install express morgan dotenv zod @modelcontextprotocol/sdk

Enter fullscreen mode Exit fullscreen mode

Package breakdown:

  • @modelcontextprotocol/sdk: Official MCP SDK
  • express: Web framework for HTTP transport
  • morgan: Request logging
  • dotenv: Environment variable management
  • zod: Schema validation

Step 3: Environment Configuration

Create .env file:
.env

OPENWEATHER_API_KEY=your_api_key_here
PORT=3001
NODE_ENV=development
Enter fullscreen mode Exit fullscreen mode

Get your free API key: OpenWeatherMap

Step 4: Create the MCP Server

Create app.js:

import express from "express";
import "dotenv/config";
import logger from "morgan";
import {
McpServer,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import { randomUUID } from "node:crypto";

console.log("Environment Variables:", {
OPENWEATHER_API_KEY: process.env.OPENWEATHER_API_KEY ? "✅ Found" : "❌ Not Found",
PORT: process.env.PORT,
NODE_ENV: process.env.NODE_ENV
});

// Map to store transports by session ID
const transports = {};

// Function to create a new MCP server instance
function createMcpServer() {
const server = new McpServer({
name: "enterprise-mcp-demo-server",
version: "1.0.0",
});

// Weather Tool - AI can get real-time weather data
server.tool(
"get-weather",
"Get current weather information for any location",
{
location: z.string().describe("City name, state/country (e.g., 'London', 'New York,US')"),
units: z.enum(["standard", "metric", "imperial"]).optional().describe("Temperature units")
},
async ({ location, units = "metric" }) => {
try {
const apiKey = process.env.OPENWEATHER_API_KEY;
if (!apiKey) {
return {
content: [{
type: "text",
text: "❌ OpenWeatherMap API key not configured. Please add OPENWEATHER_API_KEY to your environment variables."
}]
};
}
    const response = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(location)}&appid=${apiKey}&units=${units}`
    );

    if (!response.ok) {
      throw new Error(`Weather API error: ${response.status}`);
    }

    const data = await response.json();
    const tempUnit = units === "metric" ? "°C" : units === "imperial" ? "°F" : "K";

    return {
      content: [{
        type: "text",
        text: `🌤️ **Weather in ${data.name}, ${data.sys.country}:**
Temperature: ${data.main.temp}${tempUnit} (feels like ${data.main.feels_like}${tempUnit})
Description: ${data.weather.description}
Humidity: ${data.main.humidity}%
Wind: ${data.wind.speed} m/s }] }; } catch (error) { return { content: [{ type: "text", text:❌ Error fetching weather: ${error.message}`
}]
};
}
}
);

// Team Productivity Analysis Tool - Enterprise Demo
server.tool(
"analyze-team-productivity",
"Analyze team productivity and get AI-powered insights",
{
teamName: z.string().describe("Team name to analyze"),
timeframe: z.enum(["today", "week", "month"]).describe("Analysis timeframe")
},
async ({ teamName, timeframe }) => {
// Simulate real business logic
const mockData = {
meetings: timeframe === "today" ? 4 : timeframe === "week" ? 28 : 120,
focusTime: timeframe === "today" ? 6.5 : timeframe === "week" ? 32 : 140,
blockers: ["API integration delays", "Code review bottleneck", "Meeting overload"],
suggestions: ["Block 2hr focus time daily", "Implement async code reviews", "Reduce meetings by 30%"]
};

  return {
    content: [{
      type: "text",
      text: `📊 **${teamName} Team Analysis (${timeframe})**
🎯 Key Metrics:

Meetings: ${mockData.meetings}h

Deep Focus Time: ${mockData.focusTime}h

Productivity Score: ${Math.round((mockData.focusTime / (mockData.meetings + mockData.focusTime)) * 100)}%

⚠️ Current Blockers:
${mockData.blockers.map(b => - ${b}).join('\n')}

💡 AI Recommendations:
${mockData.suggestions.map(s => - ${s}).join('\n')}

🚀 Estimated Impact: +25% team velocity, -40% context switching`
}]
};
}
);

// Business ROI Calculator Tool
server.tool(
"calculate-mcp-roi",
"Calculate ROI of implementing MCP across the organization",
{
teamSize: z.number().describe("Number of team members"),
currentProcesses: z.number().describe("Number of manual processes"),
avgHourlyRate: z.number().optional().describe("Average hourly rate (default: $50)")
},
async ({ teamSize, currentProcesses, avgHourlyRate = 50 }) => {
const hoursPerProcess = 2;
const automationRate = 0.7; // 70% can be automated

  const monthlyHours = teamSize * currentProcesses * hoursPerProcess;
  const currentMonthlyCost = monthlyHours * avgHourlyRate;
  const automatedHours = monthlyHours * automationRate;
  const monthlySavings = automatedHours * avgHourlyRate;

  const implementationCost = 10000;
  const monthlyMaintenance = 1000;
  const breakEvenMonths = Math.ceil(implementationCost / (monthlySavings - monthlyMaintenance));

  return {
    content: [{
      type: "text",
      text: `💰 **MCP ROI Analysis for ${teamSize}-person team**
📊 Current State:

Manual Processes: ${currentProcesses}

Hours/Month: ${monthlyHours.toLocaleString()}

Current Cost: $${currentMonthlyCost.toLocaleString()}/month

🤖 With MCP (70% automation):

Monthly Savings: $${monthlySavings.toLocaleString()}

Annual Savings: $${(monthlySavings * 12).toLocaleString()}

Break-even: ${breakEvenMonths} months

🚀 3-Year ROI: ${Math.round(((monthlySavings * 36) / implementationCost) * 100)}%`
}]
};
}
);

return server;
}

const port = process.env.PORT || 3001;
const app = express();

app.use(logger("dev"));
app.use(express.json());

// CORS headers for MCP compatibility
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, mcp-session-id');
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});

// Health check endpoint
app.get("/", (req, res) => {
res.json({
message: "🚀 Enterprise MCP Demo Server is running!",
version: "1.0.0",
tools: 3,
capabilities: [
"Real-time Weather Data",
"Team Productivity Analysis",
"ROI Calculations",
"AI Agent Integration"
],
endpoints: {
health: "/",
mcp: "/mcp"
},
transport: "StreamableHTTP"
});
});

// MCP endpoint handler
app.post("/mcp", async (req, res) => {
console.log("MCP Request:", {
sessionId: req.headers["mcp-session-id"],
method: req.body?.method
});

const sessionId = req.headers["mcp-session-id"];
let transport;

if (sessionId && transports[sessionId]) {
transport = transports[sessionId];
} else if (!sessionId || req.body?.method === "initialize") {
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (newSessionId) => {
transports[newSessionId] = transport;
console.log("✅ Session initialized:", newSessionId);
},
enableDnsRebindingProtection: false,
});

transport.onclose = () => {
  if (transport.sessionId) {
    console.log("🧹 Cleaning up session:", transport.sessionId);
    delete transports[transport.sessionId];
  }
};

const server = createMcpServer();
await server.connect(transport);
} else {
res.status(400).json({
jsonrpc: "2.0",
error: { code: -32000, message: "Invalid session ID" },
id: null,
});
return;
}

try {
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("❌ MCP Error:", error);
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal error" },
id: null,
});
}
});

// Handle GET requests for server-to-client notifications via SSE
app.get("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"];
if (!sessionId || !transports[sessionId]) {
res.status(400).send("Invalid session ID");
return;
}

try {
await transports[sessionId].handleRequest(req, res);
} catch (error) {
console.error("❌ SSE Error:", error);
res.status(500).send("Internal server error");
}
});

app.listen(port, () => {
console.log(🚀 Enterprise MCP Server running on port ${port});
console.log(🔗 Health check: http://localhost:${port});
console.log(⚡ MCP endpoint: http://localhost:${port}/mcp);
console.log(📊 Available tools: 3); //tools length
});`
Enter fullscreen mode Exit fullscreen mode

Step 5: Configuration for VS Code

Create .vscode/mcp.json:

{
"servers": {
"enterprise-mcp-demo": {
"url": "http://localhost:3001/mcp",
"type": "http",
"dev": {
"watch": "**/*.js",
"debug": { "type": "node" }
}
}
}
}
Enter fullscreen mode Exit fullscreen mode

🏃‍♂️ Running and Testing Your MCP Server

Step 1: Start the Server

node app.js

Enter fullscreen mode Exit fullscreen mode

Expected output:
🚀 Enterprise MCP Server running on port 3001
🔗 Health check: http://localhost:3001
⚡ MCP endpoint: http://localhost:3001/mcp
📊 Available tools: 3

Step 2: Test with VS Code (Requires GitHub Copilot)

  1. Install Extensions:

    • GitHub Copilot
    • GitHub Copilot Chat
  2. Open VS Code in your project directory

  3. The MCP server should auto-connect (check the MCP section in the sidebar)

  4. Test with Copilot Chat:
    @mcp Get weather for London
    @mcp Analyze productivity for DevOps team over the past week
    @mcp Calculate ROI for 10-person team with 20 processes

Step 3: Manual Testing with cURL

Test health check:

curl http://localhost:3001

Enter fullscreen mode Exit fullscreen mode

Test MCP initialization:

curl -X POST http://localhost:3001/mcp
-H "Content-Type: application/json"
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}'
Enter fullscreen mode Exit fullscreen mode

🎯 Understanding StreamableHTTP Transport

StreamableHTTP is MCP's recommended transport for production applications. Here's why it's powerful:

Key Features:

  1. Bidirectional Communication: Server can push notifications to clients
  2. Session Management: Maintains state across multiple requests
  3. Server-Sent Events (SSE): Real-time updates from server to client
  4. HTTP Compatibility: Works with existing web infrastructure

Architecture Flow:

Client sends POST /mcp (Initialize)

Server creates session + transport

Client sends POST /mcp (Tool calls)

Server responds with results

Server can send GET /mcp (SSE updates)

Client sends DELETE /mcp (Cleanup)

Session Management Code Breakdown:

// Session storage
const transports = {};

// Create transport for new sessions
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (newSessionId) => {
transports[newSessionId] = transport; // Store session
},
enableDnsRebindingProtection: false, // Dev mode
});

// Cleanup on disconnect
transport.onclose = () => {
delete transports[transport.sessionId];
};
Enter fullscreen mode Exit fullscreen mode

🔒 Security and Best Practices

MCP implements several security layers:

User Consent Model

  • Explicit authorization for all data access
  • Tool-by-tool permissions
  • Clear capability descriptions

Implementation Guidelines:

// ✅ Good: Clear, descriptive tool names
s

erver.tool("get-weather", "Get current weather information for any location", ...)
Enter fullscreen mode Exit fullscreen mode

// ❌ Bad: Vague or misleading descriptions

server.tool("data", "Gets stuff", ...)

Enter fullscreen mode Exit fullscreen mode
// ✅ Good: Proper error handling
try {
const result = await externalAPI();
return { content: [{ type: "text", text: result }] };
} catch (error) {
return { content: [{ type: "text", text: ❌ Error: ${error.message} }] };
}

// ✅ Good: Input validation with Zod
{
email: z.string().email().describe("Valid email address"),
amount: z.number().positive().describe("Positive dollar amount")
}
Enter fullscreen mode Exit fullscreen mode

Environment Security:

// ✅ Use environment variables for sensitive data

const apiKey = process.env.OPENWEATHER_API_KEY;

Enter fullscreen mode Exit fullscreen mode

// ✅ Validate configuration

if (!apiKey) {
throw new Error("Missing required API key");
}

// ✅ Never expose secrets in responses
API Key: ${apiKey.substring(0, 8)}... // First 8 chars only
Enter fullscreen mode Exit fullscreen mode

🚀 Production Deployment Checklist

Environment Setup

Production .env
NODE_ENV=production
PORT=3001
OPENWEATHER_API_KEY=your_production_key
Enter fullscreen mode Exit fullscreen mode

Performance Optimizations

// Enable compression

import compression from 'compression';
app.use(compression());
Enter fullscreen mode Exit fullscreen mode

// Rate limiting

import rateLimit from 'express-rate-limit';
app.use(rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
}));
Enter fullscreen mode Exit fullscreen mode

// Request timeout

app.use(timeout('30s'));

Enter fullscreen mode Exit fullscreen mode

Docker Deployment

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3001
CMD ["node", "app.js"]
Enter fullscreen mode Exit fullscreen mode

🎉 What You've Built

Congratulations! You've created a production-ready MCP server that:

Exposes 3 powerful tools to AI agents

Uses StreamableHTTP transport for optimal performance

Implements proper session management

Includes comprehensive error handling

Follows MCP security best practices

Works with VS Code, Claude, and other MCP clients

Real-World Applications

Your MCP server can now enable AI agents to:

  • 🌤️ Get real-time weather data for location-aware decisions
  • 📊 Analyze team productivity and suggest improvements
  • 💰 Calculate business ROI for automation projects
  • 🔧 Integrate with existing business systems

🔮 Next Steps: Expanding Your MCP Server

Add More Enterprise Tools:

// Database integration

server.tool("query-database", "Execute SQL queries", ...)

Enter fullscreen mode Exit fullscreen mode

// Slack notifications

server.tool("send-slack-message", "Send team notifications", ...)

Enter fullscreen mode Exit fullscreen mode

// Calendar management

server.tool("schedule-meeting", "Book meetings automatically", ...)

Enter fullscreen mode Exit fullscreen mode

// Document processing

server.tool("analyze-document", "Extract insights from PDFs", ...)

Enter fullscreen mode Exit fullscreen mode

Advanced Features:

  • Resource exposure (files, databases)
  • Prompt templates (pre-defined workflows)
  • Sampling (server-initiated AI actions)
  • Multiple transport support (WebSockets, stdio)

🤝 Join the MCP Community

MCP is rapidly evolving with growing ecosystem support:


🎊 Conclusion

MCP represents a paradigm shift in how AI applications integrate with external systems. By providing a standardized protocol, MCP eliminates the complexity of custom integrations and enables AI agents to dynamically discover and use tools.

With your new MCP server, you've taken the first step toward building truly intelligent business automation that can adapt, learn, and execute complex workflows automatically.

The future of AI isn't just chat—it's AI agents that can take action. 🚀


Found this helpful? Follow me for more AI and development tutorials! Have questions about MCP implementation? Drop them in the comments below. 💬

Top comments (0)