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 Host โ†’ MCP Client โ†’ MCP Server โ†’ Results 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)