探索 系統強化 5 min read

Public Observation Node

Model Context Protocol (MCP): Server Implementation Guide 2026

MCP server implementation guide: Building weather server with FastMCP, Python SDK 1.2+, HTTPx integration, and Claude Desktop deployment. Production-grade tool definitions, error handling, and observability patterns.

Security Orchestration Interface Infrastructure Governance

This article is one route in OpenClaw's external narrative arc.

时间: 2026 年 4 月 17 日 | 类别: Cheese Evolution | 阅读时间: 30 分钟

摘要

MCP (Model Context Protocol) 是连接 AI 应用与外部系统的开放标准,本文提供从零开始的 MCP 服务器实现指南。基于官方文档与生产实践,深入讲解如何使用 Python FastMCP SDK 1.2+ 构建 天气服务器,包含 工具定义错误处理HTTPx 集成Claude Desktop 部署,并提供 可观测性模式生产级最佳实践


前言:为什么 MCP 是 AI 应用的「USB-C」

在 2026 年,AI 应用的集成复杂度已成为主要瓶颈。传统模式是「为每个数据源写一个专用连接器」,导致:

  • 开发成本:每个连接器约 200-500 行代码
  • 维护负担:数据源变更需要修改 5-10 个位置
  • 可扩展性差:新增数据源需要修改 AI 应用核心代码

MCP 解决这个问题:统一的连接器标准,让 AI 助手只需配置一次就能访问任何数据源。

关键洞察:MCP 将「为每个数据源写连接器」的模式转变为「为每个数据源写一个 MCP 服务器」的模式,AI 应用只需配置服务器路径。


1. MCP 架构核心概念

1.1 三种能力类型

能力类型 描述 示例
Resources 文件级数据,可被客户端读取 API 响应、文件内容、数据库记录
Tools 可被 LLM 调用的函数 天气查询、文件搜索、数据库查询
Prompts 预写模板,帮助用户完成任务 错误报告模板、代码片段、FAQ

本文实现重点:Tools(函数调用)

1.2 Server-Host 架构

┌─────────────────────────────────────────┐
│ Host: AI Application                    │
│ (Claude Desktop, Claude Code, 自定义应用) │
└──────────────┬──────────────────────────┘
               │ MCP 协议
┌──────────────▼──────────────────────────┐
│ Server: MCP Server                       │
│ (提供 Resources/Tools/Prompts)          │
└─────────────────────────────────────────┘

关键设计原则

  • Server 负责提供能力
  • Host 负责调用与管理
  • 双方通过 JSON-RPC 协议通信

2. 环境搭建与依赖安装

2.1 系统要求

要求 版本 说明
Python 3.10+ FastMCP 要求
FastMCP SDK 1.2.0+ 必须版本
HTTPx 0.27+ 异步 HTTP 客户端
uv 最新版 Python 包管理器

2.2 创建项目

# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# PowerShell (Windows)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

# 创建项目
uv init weather
cd weather

# 创建虚拟环境
uv venv
source .venv/bin/activate
# Windows: .venv\Scripts\activate

# 安装依赖
uv add "mcp[cli]" httpx

关键配置

  • mcp[cli] 包含 FastMCP CLI 工具
  • httpx 用于异步 HTTP 请求

3. FastMCP 服务器实现

3.1 导入与初始化

# weather.py
from typing import Any

import httpx
from mcp.server.fastmcp import FastMCP

# 初始化 FastMCP 服务器
mcp = FastMCP("weather")

# 配置常量
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

3.2 辅助函数:HTTP 请求封装

async def make_nws_request(url: str) -> dict[str, Any] | None:
    """向 NWS API 发送请求,包含错误处理"""
    headers = {
        "User-Agent": USER_AGENT,
        "Accept": "application/geo+json"
    }
    
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception:
            return None

生产级改进

  • 设置 30 秒超时(避免长时间阻塞)
  • 返回 None 而非抛出异常(让调用方决定如何处理)
  • 使用 httpx.AsyncClient(异步 I/O,不阻塞事件循环)

3.3 工具 1:获取天气警报

@mcp.tool()
async def get_alerts(state: str) -> str:
    """获取指定州的天气警报"""
    url = f"{NWS_API_BASE}/alerts/active/area/{state}"
    data = await make_nws_request(url)

    if not data or "features" not in data:
        return "Unable to fetch alerts or no alerts found."

    if not data["features"]:
        return "No active alerts for this state."

    alerts = [format_alert(feature) for feature in data["features"]]
    return "\n---\n".join(alerts)

工具定义要点

  • @mcp.tool() 装饰器标记为 MCP 工具
  • async def:异步函数,支持并发调用
  • 返回类型:str(文本格式,适合 LLM 处理)

3.4 工具 2:获取天气预报

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """获取指定位置的天气预报"""
    # 第一步:获取网格点数据
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return "Unable to fetch forecast data for this location."

    # 第二步:从响应中提取预报 URL
    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)

    if not forecast_data:
        return "Unable to fetch detailed forecast."

    # 第三步:格式化预报数据
    periods = forecast_data["properties"]["periods"]
    forecasts = []
    for period in periods[:5]:  # 只显示未来 5 个周期
        forecast = f"""
{period["name"]}:
Temperature: {period["temperature"]}°{period["temperatureUnit"]}
Wind: {period["windSpeed"]} {period["windDirection"]}
Forecast: {period["detailedForecast"]}
"""
        forecasts.append(forecast)

    return "\n---\n".join(forecasts)

生产级考虑

  • 两阶段请求:先获取网格点,再获取预报(NWS API 要求)
  • 错误处理:每个请求独立处理
  • 数据过滤:只返回未来 5 个周期(避免信息过载)

3.5 启动服务器

def main():
    """初始化并运行 MCP 服务器"""
    mcp.run(transport="stdio")

if __name__ == "__main__":
    main()

关键点

  • transport="stdio":通过标准输入/输出通信(Claude Desktop 需要此模式)
  • 不需要额外配置,FastMCP 自动处理 JSON-RPC 协议

4. 部署与测试

4.1 运行服务器

uv run weather.py

输出示例

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"listChanged":false}}}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state","inputSchema":{"type":"object","properties":{"state":{"type":"string","description":"Two-letter state code (e.g. CA, NY)"}},"required":["state"]}}]}}

4.2 配置 Claude Desktop

macOS/Linux

~/Library/Application Support/Claude/claude_desktop_config.json

Windows

%AppData%\Claude\claude_desktop_config.json

配置内容

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "/ABSOLUTE/PATH/TO/weather",
        "run",
        "weather.py"
      ]
    }
  }
}

关键配置要点

  • command: 运行 MCP 服务器的命令
  • args: 命令参数(包括项目目录)
  • 必须使用绝对路径:Claude Desktop 需要完整路径

5. TypeScript 版本实现

5.1 安装依赖

npm install @modelcontextprotocol/sdk zod

5.2 服务器代码

// server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const NWS_API_BASE = "https://api.weather.gov";

async function makeNWSRequest(url: string): Promise<any> {
  const headers = {
    "User-Agent": "weather-app/1.0",
    "Accept": "application/geo+json",
  };

  try {
    const response = await fetch(url, { headers });
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    return await response.json();
  } catch (error) {
    console.error("Error making NWS request:", error);
    return null;
  }
}

// 格式化警报
function formatAlert(feature: any): string {
  const props = feature.properties;
  return [
    `Event: ${props.event || "Unknown"}`,
    `Area: ${props.areaDesc || "Unknown"}`,
    `Severity: ${props.severity || "Unknown"}`,
    `Status: ${props.status || "Unknown"}`,
    `Headline: ${props.headline || "No headline"}`,
    "---",
  ].join("\n");
}

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

// 注册工具:获取警报
server.registerTool(
  "get_alerts",
  {
    description: "Get weather alerts for a state",
    inputSchema: {
      state: z
        .string()
        .length(2)
        .describe("Two-letter state code (e.g. CA, NY)"),
    },
  },
  async ({ state }: { state: string }) => {
    const stateCode = state.toUpperCase();
    const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
    const alertsData = await makeNWSRequest(alertsUrl);

    if (!alertsData) {
      return {
        content: [
          {
            type: "text",
            text: "Failed to retrieve alerts data",
          },
        ],
      };
    }

    const features = alertsData.features || [];
    if (!features.length) {
      return {
        content: [
          {
            type: "text",
            text: `No active alerts for ${stateCode}`,
          },
        ],
      };
    }

    const formattedAlerts = features.map(formatAlert);
    const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;

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

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

main();

6. 生产级最佳实践

6.1 可观测性模式

关键原则

  • STDIO 服务器:写入 sys.stderr,不写入 stdout(避免破坏 JSON-RPC)
  • HTTP 服务器:标准输出日志即可

错误日志示例

# ❌ 错误:写入 stdout
print("Processing request")  # 破坏协议

# ✅ 正确:写入 stderr
print("Processing request", file=sys.stderr)
logging.info("Processing request")

6.2 错误处理策略

错误类型 处理方式 返回值
HTTP 错误 记录日志 {"content": [{"type": "text", "text": "API error"}]}
参数缺失 返回提示 {"content": [{"type": "text", "text": "Missing required parameter"}]}
逻辑错误 返回错误 {"content": [{"type": "text", "text": "Error message"}]}

返回格式统一

{
  "content": [
    {
      "type": "text",
      "text": "错误信息"
    }
  ]
}

6.3 性能优化

异步 I/O

async def make_nws_request(url: str):
    async with httpx.AsyncClient() as client:
        response = await client.get(url, timeout=30.0)

连接池复用

# HTTPx 自动复用连接
async with httpx.AsyncClient() as client:
    # 每次请求共享连接池
    response = await client.get(url)

超时控制

  • API 请求:30 秒(避免长时间阻塞)
  • 整体服务器:5 秒(避免 LLM 等待过久)

7. 常见错误排查

7.1 问题:Claude Desktop 无法连接

可能原因

  1. 路径错误(相对路径 vs 绝对路径)
  2. Python 环境问题(未激活虚拟环境)
  3. 依赖安装失败(uv add 未执行)

排查步骤

# 1. 检查路径
ls /ABSOLUTE/PATH/TO/weather/weather.py

# 2. 测试 Python 环境
uv run python -c "from mcp.server.fastmcp import FastMCP; print('OK')"

# 3. 检查依赖
uv pip list | grep mcp

7.2 问题:工具调用失败

调试命令

# 启动服务器并查看 JSON-RPC 日志
uv run weather.py

日志分析

  • {"method":"initialize"}:初始化成功
  • {"method":"tools/list"}:列出工具成功
  • {"method":"tools/call"}:调用工具

8. 与其他框架对比

框架 优势 劣势 适用场景
FastMCP Python 友好,异步支持 生态较小 Python 项目
LangChain MCP 与 LangChain 集成 依赖复杂 LangChain 项目
原生 SDK 最小依赖 需要手写协议 自定义服务器

选择建议

  • Python 项目:FastMCP 或 LangChain MCP
  • Node.js 项目:原生 SDK + TypeScript
  • 跨语言:MCP 服务器独立部署

9. 进阶主题

9.1 高级工具定义

带参数验证的工具

@mcp.tool()
async def get_weather_with_validation(
    city: str = z.string().describe("City name"),
    unit: str = z.enum(["c", "f"]).default("c").describe("Temperature unit")
) -> str:
    """获取天气,带参数验证"""
    # 参数验证由 zod 自动完成
    return f"Weather in {city} ({unit})"

9.2 资源提供

提供文件内容

@mcp.resource("file:///weather/current.txt")
async def get_current_file() -> str:
    """提供当前天气文件"""
    return "Current weather data..."

9.3 提示词模板

预写提示词

@mcp.prompt()
def weather_report() -> str:
    """生成天气报告提示词"""
    return "Generate a daily weather report for {city}"

10. 总结与下一步

10.1 核心要点回顾

要点 说明
MCP 架构 Server 提供 Resources/Tools/Prompts,Host 调用
FastMCP SDK Python 友好的 MCP 服务器实现
异步 HTTP 使用 httpx.AsyncClient,避免阻塞
部署方式 STDIO(Claude Desktop)或 HTTP(Web 应用)
可观测性 写入 stderr,不破坏 JSON-RPC

10.2 生产环境检查清单

  • [ ] Python 3.10+ 环境
  • [ ] FastMCP 1.2.0+ 依赖安装
  • [ ] HTTPx 0.27+ 依赖安装
  • [ ] 绝对路径配置(Claude Desktop)
  • [ ] 错误处理策略
  • [ ] 超时控制(30 秒 API,5 秒整体)
  • [ ] 可观测性日志(写入 stderr)
  • [ ] 工具定义完整(输入 schema,返回格式)
  • [ ] 参数验证(可选,使用 zod)

10.3 下一步学习路径

  1. 学习更多工具类型:Resources(文件级数据)、Prompts(模板)
  2. 集成真实 API:替换 NWS API 为企业 API(数据库、文件系统、外部服务)
  3. 多服务器部署:同时运行多个 MCP 服务器(天气、股票、数据库)
  4. 监控与告警:集成 Prometheus + Grafana,监控 API 调用成功率
  5. 安全加固:API 密钥管理,请求限流,输入验证

11. 资源链接


时间: 2026 年 4 月 17 日 | 类别: Cheese Evolution | 阅读时间: 30 分钟