使用TypeScript写一个获取天气的MCP Server

0 阅读3分钟

MCP Server 是 Model Context Protocol(模型上下文协议) 的服务器端实现。MCP 是由 Anthropic 推出的开放协议,旨在标准化 AI 模型与外部数据源、工具之间的连接方式。你可以把它理解为 AI 应用的"USB-C 接口" —— 统一、即插即用。

功能说明
数据连接让 AI 安全地访问本地文件、数据库、API 等外部资源
工具提供为 AI 提供可调用的功能(如搜索、计算、代码执行)
上下文管理管理 AI 会话中的上下文信息,保持连贯性

Snipaste_2026-04-17_09-52-33.png mcp server开发兼容多种语言,实际上ts的生态最好

技术栈选择

语言SDK适用场景
TypeScript@modelcontextprotocol/sdk最成熟,生态最好
Pythonmcp数据/ML 场景友好
Java / C#社区 SDK企业级应用
概念说明
Tool(工具)AI 可调用的功能,如搜索、计算、API 请求
Resource(资源)AI 可读取的数据,如文件内容、数据库记录
Prompt(提示模板)预定义的提示词模板
Transport(传输层)通信方式:stdio(本地)或 SSE(远程)

如何开发一个 mcp server

ts项目架构:

Snipaste_2026-04-17_10-09-42.png 1.配置package.json

{
  "name": "weather-mcp-server",
  "version": "1.0.0",
  "description": "天气查询 MCP Server",
  "type": "module",
  "bin": {
    "weather-mcp-server": "./dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "start": "node dist/index.js",
    "prepare": "npm run build"
  },
  "keywords": [
    "mcp",
    "weather",
    "model-context-protocol"
  ],
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "@types/node": "^20.19.39",
    "tsx": "^4.0.0",
    "typescript": "^5.0.0"
  }
}

2.配置tsconfig.json

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

3.核心代码index.ts

#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";

// ============ 类型定义 ============

interface WeatherData {
  current: {
    temperature_2m: number;
    weathercode: number;
    windspeed_10m: number;
  };
  daily: {
    time: string[];
    temperature_2m_max: number[];
    temperature_2m_min: number[];
    weathercode: number[];
  };
}

// 天气代码映射(WMO Weather interpretation codes)
const weatherCodes: Record<number, string> = {
  0: "晴朗",
  1: "主要晴朗", 2: "多云", 3: "阴天",
  45: "雾", 48: "沉积雾",
  51: "毛毛雨", 53: "中度毛毛雨", 55: "密集毛毛雨",
  61: "小雨", 63: "中雨", 65: "大雨",
  71: "小雪", 73: "中雪", 75: "大雪",
  80: "阵雨", 81: "中度阵雨", 82: "暴雨",
  95: "雷雨", 96: "雷雨伴冰雹", 99: "强雷雨伴冰雹",
};

// ============ 工具函数 ============

async function getCoordinates(city: string): Promise<{ lat: number; lon: number; name: string } | null> {
  try {
    const response = await fetch(
      `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=zh&format=json`
    );
    const data = await response.json() as { results?: Array<{ latitude: number; longitude: number; name: string }> };
    
    if (!data.results || data.results.length === 0) return null;
    
    const result = data.results[0];
    return {
      lat: result.latitude,
      lon: result.longitude,
      name: result.name,
    };
  } catch (error) {
    return null;
  }
}

async function getWeather(lat: number, lon: number): Promise<WeatherData> {
  const response = await fetch(
    `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current=temperature_2m,weathercode,windspeed_10m&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=auto&forecast_days=3`
  );
  return await response.json() as WeatherData;
}

function formatWeather(data: WeatherData, cityName: string): string {
  const current = data.current;
  const currentWeather = weatherCodes[current.weathercode] || "未知天气";
  
  let result = `🌍 ${cityName}\n`;
  result += `🌡️ 当前温度: ${current.temperature_2m}°C\n`;
  result += `☁️ 天气状况: ${currentWeather}\n`;
  result += `💨 风速: ${current.windspeed_10m} km/h\n\n`;
  
  result += `📅 未来3天预报:\n`;
  for (let i = 0; i < 3; i++) {
    const date = new Date(data.daily.time[i]);
    const dayStr = i === 0 ? "今天" : i === 1 ? "明天" : "后天";
    const maxTemp = data.daily.temperature_2m_max[i];
    const minTemp = data.daily.temperature_2m_min[i];
    const weather = weatherCodes[data.daily.weathercode[i]] || "未知";
    
    result += `${dayStr} (${date.getMonth() + 1}/${date.getDate()}): ${weather}, ${minTemp}°C ~ ${maxTemp}°C\n`;
  }
  
  return result;
}

// ============ MCP Server 核心 ============

const server = new Server(
  {
    name: "weather-mcp-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// 声明可用工具
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get_current_weather",
        description: "获取指定城市的当前天气和未来3天预报",
        inputSchema: {
          type: "object",
          properties: {
            city: {
              type: "string",
              description: "城市名称,例如:北京、上海、New York",
            },
          },
          required: ["city"],
        },
      },
      {
        name: "compare_weather",
        description: "比较两个城市的天气",
        inputSchema: {
          type: "object",
          properties: {
            city1: {
              type: "string",
              description: "第一个城市",
            },
            city2: {
              type: "string",
              description: "第二个城市",
            },
          },
          required: ["city1", "city2"],
        },
      },
    ],
  };
});

// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  // Tool 1: 获取单个城市天气
  if (name === "get_current_weather") {
    const schema = z.object({ city: z.string() });
    const { city } = schema.parse(args);

    const coords = await getCoordinates(city);
    if (!coords) {
      return {
        content: [{ type: "text", text: `❌ 未找到城市: ${city}` }],
        isError: true,
      };
    }

    const weather = await getWeather(coords.lat, coords.lon);
    const formatted = formatWeather(weather, coords.name);

    return {
      content: [{ type: "text", text: formatted }],
    };
  }

  // Tool 2: 比较两个城市天气
  if (name === "compare_weather") {
    const schema = z.object({ city1: z.string(), city2: z.string() });
    const { city1, city2 } = schema.parse(args);

    const [coords1, coords2] = await Promise.all([
      getCoordinates(city1),
      getCoordinates(city2),
    ]);

    if (!coords1 || !coords2) {
      return {
        content: [{ type: "text", text: `❌ 城市未找到: ${!coords1 ? city1 : city2}` }],
        isError: true,
      };
    }

    const [weather1, weather2] = await Promise.all([
      getWeather(coords1.lat, coords1.lon),
      getWeather(coords2.lat, coords2.lon),
    ]);

    let result = `🆚 天气对比: ${coords1.name} vs ${coords2.name}\n\n`;
    result += `【${coords1.name}】\n`;
    result += `当前: ${weather1.current.temperature_2m}°C, ${weatherCodes[weather1.current.weathercode] || "未知"}\n`;
    result += `今日: ${weather1.daily.temperature_2m_min[0]}°C ~ ${weather1.daily.temperature_2m_max[0]}°C\n\n`;
    
    result += `【${coords2.name}】\n`;
    result += `当前: ${weather2.current.temperature_2m}°C, ${weatherCodes[weather2.current.weathercode] || "未知"}\n`;
    result += `今日: ${weather2.daily.temperature_2m_min[0]}°C ~ ${weather2.daily.temperature_2m_max[0]}°C`;

    return {
      content: [{ type: "text", text: result }],
    };
  }

  throw new Error(`未知工具: ${name}`);
});

// ============ 启动服务 ============

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

main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});

天气使用的是Open-Meteo 天气平台,这是一个完全免费、无需 API Key 的天气服务,非常适合开发测试使用。

如何使用这个mcp server

mcp server需要提供给AI大模型调用,可以借助一些已经集成了AI大模型的客户端来调用mcp server,我使用的是Cursor编辑器。

第一步:装完包后启动本地开发的mcp server项目

npm i
npm run build

检查是否成功启动

node dist/index.js

出现 Weather MCP Server running on stdio 说明成功启动了server

Snipaste_2026-04-17_10-54-05.png

第二步,在Crosor进行配置

设置 => Tools & MCPs => New MCP Server,去配置mcp.json文件

{
  "mcpServers": {
    "weather-mcp-server": {
      "name": "weather-mcp-server",
      "transport": "stdio",
      "command": "node",
      "args": [
        "你自己的项目路径\weather-mcp-server\\dist\\index.js"
      ],
      "env": {}
    }
  }
}

配置成功后,可以看到设置里面会多一个mcp server

Snipaste_2026-04-17_11-03-43.png 可以看到这个MCP Server提供了2个工具方法,2 tools enabled,分别是get_current_weather 和compare_weather,可以通过点击该方法设置disabled,再次点击放开,到这里已经全部配置好了,接下来就可以在chat对话框和cursor对话使用这个server了

Snipaste_2026-04-17_11-07-45.png 可以看到Cursor利用AI调用了这个服务