Build a Weather MCP Server with the Official TypeScript SDK

Build the same weather server using the official MCP TypeScript SDK. Compare the functional approach with mcp-framework's class-based style and learn when to use each.


title: "Build a Weather MCP Server with the Official TypeScript SDK" description: "Build the same weather server using the official MCP TypeScript SDK. Compare the functional approach with mcp-framework's class-based style and learn when to use each." order: 2 keywords:

  • mcp typescript sdk
  • official mcp sdk tutorial
  • weather server typescript
  • model context protocol sdk
  • mcp server from scratch date: "2026-04-01" level: "beginner" duration: "25 min"

Quick Summary

Build a Weather MCP Server using the official MCP TypeScript SDK (@modelcontextprotocol/sdk). This tutorial covers the same weather functionality as the mcp-framework version, giving you a direct comparison of both approaches.

Why the Official SDK?

The official MCP TypeScript SDK (@modelcontextprotocol/sdk) is maintained by Anthropic and provides the most direct, low-level access to the MCP protocol. While mcp-framework offers higher-level abstractions, the official SDK gives you full control over every aspect of your server.

Official MCP TypeScript SDK

The reference implementation of the Model Context Protocol for TypeScript/JavaScript. Published as @modelcontextprotocol/sdk on npm, it provides the core Server class, transport layers, and type definitions.

Prerequisites

  • Node.js 18+ installed
  • npm or yarn package manager
  • Basic TypeScript knowledge
  • Optional: complete the mcp-framework tutorial first for comparison

Project Setup

1

Create the project directory

mkdir weather-server-sdk
cd weather-server-sdk
npm init -y
2

Install dependencies

npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
3

Configure TypeScript

Create tsconfig.json:

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

Set up the project structure

weather-server-sdk
src
index.ts
weather.ts
package.json
tsconfig.json

Building the Weather Helper

First, create the weather data fetching logic in src/weather.ts:

export interface WeatherResult {
  location: string;
  temperature: string;
  windSpeed: string;
  humidity: string;
  condition: string;
}

export interface GeocodingResult {
  city: string;
  country: string;
  latitude: number;
  longitude: number;
}

const WEATHER_CONDITIONS: Record<number, string> = {
  0: "Clear sky",
  1: "Mainly clear",
  2: "Partly cloudy",
  3: "Overcast",
  45: "Foggy",
  48: "Depositing rime fog",
  51: "Light drizzle",
  53: "Moderate drizzle",
  55: "Dense drizzle",
  61: "Slight rain",
  63: "Moderate rain",
  65: "Heavy rain",
  71: "Slight snowfall",
  73: "Moderate snowfall",
  75: "Heavy snowfall",
  95: "Thunderstorm",
};

export async function getWeather(
  latitude: number,
  longitude: number,
  city?: string
): Promise<WeatherResult> {
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,wind_speed_10m,weather_code,relative_humidity_2m`;

  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Weather API returned ${response.status}`);
  }

  const data = await response.json();
  const current = data.current;

  return {
    location: city || `${latitude}, ${longitude}`,
    temperature: `${current.temperature_2m}°C`,
    windSpeed: `${current.wind_speed_10m} km/h`,
    humidity: `${current.relative_humidity_2m}%`,
    condition: WEATHER_CONDITIONS[current.weather_code] || "Unknown",
  };
}

export async function lookupCity(name: string): Promise<GeocodingResult> {
  const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(name)}&count=1`;

  const response = await fetch(url);
  const data = await response.json();

  if (!data.results || data.results.length === 0) {
    throw new Error(`City "${name}" not found`);
  }

  const result = data.results[0];
  return {
    city: result.name,
    country: result.country,
    latitude: result.latitude,
    longitude: result.longitude,
  };
}

Creating the MCP Server

Now create the main server in src/index.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { getWeather, lookupCity } from "./weather.js";

const server = new McpServer({
  name: "weather-server",
  version: "1.0.0",
});

// Register the get_weather tool
server.tool(
  "get_weather",
  "Get current weather data for a given location by latitude and longitude",
  {
    latitude: z.number().min(-90).max(90).describe("Latitude (-90 to 90)"),
    longitude: z.number().min(-180).max(180).describe("Longitude (-180 to 180)"),
    city: z.string().optional().describe("Optional city name for display"),
  },
  async ({ latitude, longitude, city }) => {
    try {
      const weather = await getWeather(latitude, longitude, city);
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify(weather, null, 2),
          },
        ],
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : "Unknown error";
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify({ error: message }),
          },
        ],
        isError: true,
      };
    }
  }
);

// Register the lookup_city tool
server.tool(
  "lookup_city",
  "Look up the latitude and longitude of a city by name",
  {
    city: z.string().min(1).describe("The city name to look up"),
  },
  async ({ city }) => {
    try {
      const result = await lookupCity(city);
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify(result, null, 2),
          },
        ],
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : "Unknown error";
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify({ error: message }),
          },
        ],
        isError: true,
      };
    }
  }
);

// Start the server with stdio transport
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Weather MCP server running on stdio");
}

main().catch(console.error);
Error Handling in SDK Tools

Always wrap your tool handlers in try/catch and return isError: true when something goes wrong. This tells the AI assistant that the tool call failed, so it can inform the user or try a different approach.

Key Differences from mcp-framework

AspectOfficial SDKmcp-framework
Tool registrationserver.tool() function callsClass-based, auto-discovered
Project setupManual npm init + configCLI scaffolding
Return format{ content: [...], isError? }Return string directly
Schema definitionZod objects passed to server.tool()Schema property on class
Server creationnew McpServer({ name, version })new MCPServer()
Transport setupExplicit transport connectionHandled automatically
Both Are Valid Approaches

The official SDK and mcp-framework both produce fully compatible MCP servers. Choose the SDK when you need maximum control or minimal dependencies. Choose mcp-framework when you want faster scaffolding and convention-based structure.

Building and Testing

1

Add build scripts to package.json

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  }
}
2

Build and test

npm run build
npx @modelcontextprotocol/inspector node dist/index.js
3

Connect to Claude Desktop

Add to your Claude Desktop configuration:

{
  "mcpServers": {
    "weather-sdk": {
      "command": "node",
      "args": ["/absolute/path/to/weather-server-sdk/dist/index.js"]
    }
  }
}

Adding a Resource

One advantage of the SDK is how straightforward it is to add resources alongside tools. Let's add a resource that lists supported weather codes:

server.resource(
  "weather-codes",
  "weather://codes",
  async (uri) => {
    return {
      contents: [
        {
          uri: uri.href,
          mimeType: "application/json",
          text: JSON.stringify(WEATHER_CONDITIONS, null, 2),
        },
      ],
    };
  }
);
When to Use Resources vs Tools

Use resources for static or semi-static data that the AI might want to read. Use tools for actions that take input and produce dynamic output. In this case, weather codes are reference data (resource), while fetching live weather is an action (tool).

Next Steps

Now that you have seen both approaches, you can:

Frequently Asked Questions