🚀打造你的第一个 MCP 服务器:完整指南

发布: (2025年12月2日 GMT+8 12:42)
6 min read
原文: Dev.to

Source: Dev.to

介绍

模型上下文协议(MCP)正在革新 AI 应用与外部工具和数据源的交互方式。无论你是在构建 Claude 集成、VSCode 扩展,还是自定义 AI 工作流,了解 MCP 服务器都是现代 AI 开发的必备技能。

在本指南中,我们将从零开始构建一个功能完整的 MCP 服务器,将其部署到云端,并连接到流行的 MCP 客户端。完成后,你将拥有一个可供任何 MCP 客户端使用的天气信息服务器。

MCP(Model Context Protocol)是一种开放协议,标准化了 AI 应用如何连接外部数据源和工具。可以把它看作是一个通用适配器,使得 Claude 等 AI 模型能够安全地访问你的数据库、API、文件系统和业务工具。

  • 标准化集成: 一个协议即可兼容所有 MCP 客户端
  • 安全优先: 内置身份验证和权限控制
  • 灵活架构: 支持本地和远程服务器
  • 工具发现: 客户端可自动发现可用功能

一个 MCP 服务器由三个主要组件组成:

  1. 资源 – 服务器可以提供的数据源(文件、数据库记录、API 数据)
  2. 工具 – 服务器可以执行的操作(创建、更新、删除等)
  3. 提示 – 常见任务的预定义模板

下面我们将构建一个实用的 MCP 服务器,使用公共 API 提供天气信息。

项目设置

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

项目结构

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": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc && node dist/index.js"
  },
  "type": "module"
}

天气服务 (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 服务器 (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();
  }
}

运行服务器

创建一个 .env 文件并填入你的 OpenWeatherMap API 密钥:

OPENWEATHER_API_KEY=your_api_key_here

然后启动服务器:

npm run build
npm start

服务器将通过标准输入/输出监听,随时准备让任何兼容 MCP 的客户端(例如 Claude)发现 get_current_weatherget_forecast 工具,以及 weather://cities 资源。

Back to Blog

相关文章

阅读更多 »

切换账户

@blink_c5eb0afe3975https://dev.to/blink_c5eb0afe3975 正如大家所知,我正重新开始记录我的进展,我认为最好在一个不同的…

Strands 代理 + Agent Core AWS

入门指南:Amazon Bedrock AgentCore 目录 - 前置要求(requisitos‑previos) - 工具包安装(instalación‑del‑toolkit) - 创建…