šŸš€Building Your First MCP Server: A Complete Guide

Published: (December 1, 2025 at 11:42 PM EST)
5 min read
Source: Dev.to

Source: Dev.to

Introduction

The Model Context Protocol (MCP) is revolutionizing how AI applications interact with external tools and data sources. Whether you’re building Claude integrations, VSCode extensions, or custom AI workflows, understanding MCP servers is essential for modern AI development.

In this guide we’ll build a fully functional MCP server from scratch, deploy it to the cloud, and connect it to popular MCP clients. By the end, you’ll have a working weather‑information server that any MCP client can use.

MCP (Model Context Protocol) is an open protocol that standardizes how AI applications connect to external data sources and tools. Think of it as a universal adapter that allows AI models like Claude to safely access your databases, APIs, file systems, and business tools.

  • Standardized Integration: One protocol works across all MCP‑compatible clients
  • Security First: Built‑in authentication and permission controls
  • Flexible Architecture: Support for local and remote servers
  • Tool Discovery: Clients automatically discover available capabilities

An MCP server consists of three main components:

  1. Resources – Data sources the server can provide (files, database records, API data)
  2. Tools – Actions the server can perform (create, update, delete operations)
  3. Prompts – Pre‑defined templates for common tasks

Below we’ll build a practical MCP server that provides weather information using a public API.

Project Setup

mkdir weather-mcp-server
cd weather-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk axios dotenv
npm install -D @types/node typescript

Project Structure

weather-mcp-server/
ā”œā”€ā”€ src/
│   ā”œā”€ā”€ index.ts
│   ā”œā”€ā”€ server.ts
│   └── weatherService.ts
ā”œā”€ā”€ package.json
ā”œā”€ā”€ tsconfig.json
└── .env

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

package.json (scripts)

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc && node dist/index.js"
  },
  "type": "module"
}

Weather Service (src/weatherService.ts)

import axios from 'axios';

export interface WeatherData {
  location: string;
  temperature: number;
  condition: string;
  humidity: number;
  windSpeed: number;
  description: string;
}

export class WeatherService {
  private apiKey: string;
  private baseUrl = 'https://api.openweathermap.org/data/2.5';

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  async getCurrentWeather(city: string): Promise {
    try {
      const response = await axios.get(`${this.baseUrl}/weather`, {
        params: {
          q: city,
          appid: this.apiKey,
          units: 'metric'
        }
      });

      const data = response.data;

      return {
        location: `${data.name}, ${data.sys.country}`,
        temperature: Math.round(data.main.temp),
        condition: data.weather[0].main,
        humidity: data.main.humidity,
        windSpeed: data.wind.speed,
        description: data.weather[0].description
      };
    } catch (error) {
      throw new Error(`Failed to fetch weather data: ${error}`);
    }
  }

  async getForecast(city: string, days: number = 5): Promise {
    try {
      const response = await axios.get(`${this.baseUrl}/forecast`, {
        params: {
          q: city,
          appid: this.apiKey,
          units: 'metric',
          cnt: days * 8 // API returns 3‑hour intervals
        }
      });

      return response.data;
    } catch (error) {
      throw new Error(`Failed to fetch forecast data: ${error}`);
    }
  }
}

MCP Server (src/server.ts)

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
  ReadResourceRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import { WeatherService } from './weatherService.js';

export class WeatherMCPServer {
  private server: Server;
  private weatherService: WeatherService;

  constructor(apiKey: string) {
    this.weatherService = new WeatherService(apiKey);
    this.server = new Server(
      {
        name: 'weather-mcp-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          tools: {},
          resources: {}
        },
      }
    );

    this.setupHandlers();
  }

  private setupHandlers() {
    // List available tools
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'get_current_weather',
          description: 'Get current weather information for a city',
          inputSchema: {
            type: 'object',
            properties: {
              city: {
                type: 'string',
                description: 'City name (e.g., "London", "New York")'
              }
            },
            required: ['city']
          }
        },
        {
          name: 'get_forecast',
          description: 'Get weather forecast for upcoming days',
          inputSchema: {
            type: 'object',
            properties: {
              city: {
                type: 'string',
                description: 'City name'
              },
              days: {
                type: 'number',
                description: 'Number of days (1-5)',
                default: 5
              }
            },
            required: ['city']
          }
        }
      ]
    }));

    // Handle tool calls
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;

      try {
        if (name === 'get_current_weather') {
          const weather = await this.weatherService.getCurrentWeather(
            args.city as string
          );

          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(weather, null, 2)
              }
            ]
          };
        }

        if (name === 'get_forecast') {
          const forecast = await this.weatherService.getForecast(
            args.city as string,
            (args.days as number) || 5
          );

          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(forecast, null, 2)
              }
            ]
          };
        }

        throw new Error(`Unknown tool: ${name}`);
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `Error: ${error}`
            }
          ],
          isError: true
        };
      }
    });

    // List available resources
    this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
      resources: [
        {
          uri: 'weather://cities',
          name: 'Popular Cities',
          description: 'List of popular cities for weather queries',
          mimeType: 'application/json'
        }
      ]
    }));

    // Read resources
    this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
      const { uri } = request.params;

      if (uri === 'weather://cities') {
        const cities = [
          'London', 'New York', 'Tokyo', 'Paris', 'Sydney',
          'Berlin', 'Toronto', 'Singapore', 'Dubai', 'Mumbai'
        ];

        return {
          contents: [
            {
              type: 'application/json',
              data: JSON.stringify(cities, null, 2)
            }
          ]
        };
      }

      // Fallback for unknown resources
      return {
        contents: [],
        isError: true,
        errorMessage: `Resource not found: ${uri}`
      };
    });
  }

  // Start the server (e.g., using stdio transport)
  public start() {
    const transport = new StdioServerTransport(this.server);
    transport.start();
  }
}

Running the Server

Create a .env file with your OpenWeatherMap API key:

OPENWEATHER_API_KEY=your_api_key_here

Then start the server:

npm run build
npm start

The server will listen on standard I/O, ready for any MCP‑compatible client (e.g., Claude) to discover the get_current_weather and get_forecast tools, as well as the weather://cities resource.

Back to Blog

Related posts

Read more Ā»