> ## Documentation Index
> Fetch the complete documentation index at: https://mcp.zhcndoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 构建一个 MCP 服务器

> 开始构建你自己的服务器，以便在 Claude for Desktop 和其他客户端中使用。

在本教程中，我们将构建一个简单的 MCP 天气服务器，并将其连接到主机（Claude for Desktop）。

### 我们将构建什么

我们将构建一个提供两个工具的服务器：`get_alerts` 和 `get_forecast`。然后我们将把服务器连接到一个 MCP 主机（在本例中是 Claude for Desktop）：

<Frame>
  <img src="https://mintcdn.com/mcp-zhcndoc/e93uqR4nmQj7tWn4/images/current-weather.png?fit=max&auto=format&n=e93uqR4nmQj7tWn4&q=85&s=71cfa944e77a0303f630ab432dd4dc9b" width="2780" height="1849" data-path="images/current-weather.png" />
</Frame>

<Note>
  服务器可以连接到任何客户端。为了简单起见，我们这里选择了 Claude for Desktop，但我们也有一份关于[构建你自己的客户端](/docs/develop/build-client)的指南。
</Note>

### 核心 MCP 概念

MCP 服务器可以提供三种主要能力：

1. **[资源](/docs/learn/server-concepts#resources)**：类似文件的数据，客户端可以读取（如 API 响应或文件内容）
2. **[工具](/docs/learn/server-concepts#tools)**：可由 LLM 调用的函数（需要用户批准）
3. **[提示](/docs/learn/server-concepts#prompts)**：预先编写的模板，帮助用户完成特定任务

本教程将主要聚焦于工具。

<Tabs>
  <Tab title="Python">
    让我们开始构建我们的天气服务器吧！
    [你可以在这里找到我们将要构建的完整代码。](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python)

    ### 先决知识

    本快速入门假设你已经熟悉：

    * Python
    * 像 Claude 这样的 LLM

    ### 登录 MCP 服务器

    在实现 MCP 服务器时，请小心处理日志输出方式：

    **基于 STDIO 的服务器：** 永远不要向 stdout 写入内容。向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。`print()` 函数默认会写入 stdout，但可以通过 `file=sys.stderr` 安全使用。

    **基于 HTTP 的服务器：** 标准输出日志是没问题的，因为它不会干扰 HTTP 响应。

    ### 最佳实践

    * 使用一个写入 stderr 或写入文件的日志库。

    ### 快速示例

    ```python theme={null}
    import sys
    import logging

    # ❌ 不好（STDIO）
    print("Processing request")

    # ✅ 好（STDIO）
    print("Processing request", file=sys.stderr)

    # ✅ 好（STDIO）
    logging.info("Processing request")
    ```

    ### 系统要求

    * 安装了 Python 3.10 或更高版本。
    * 你必须使用 Python MCP SDK 1.2.0 或更高版本。

    ### 配置你的环境

    首先，安装 `uv` 并设置我们的 Python 项目和环境：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      curl -LsSf https://astral.sh/uv/install.sh | sh
      ```

      ```powershell Windows theme={null}
      powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
      ```
    </CodeGroup>

    确保在之后重新启动你的终端，以确保能识别到 `uv` 命令。

    接下来，我们创建并设置我们的项目：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      # 为我们的项目创建一个新目录
      uv init weather
      cd weather

      # 创建虚拟环境并激活
      uv venv
      source .venv/bin/activate

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

      # 创建我们的服务器文件
      touch weather.py
      ```

      ```powershell Windows theme={null}
      # 为我们的项目创建一个新目录
      uv init weather
      cd weather

      # 创建虚拟环境并激活
      uv venv
      .venv\Scripts\activate

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

      # 创建我们的服务器文件
      new-item weather.py
      ```
    </CodeGroup>

    现在让我们进入构建你的服务器。

    ## 构建你的服务器

    ### 导入包并设置实例

    将以下内容添加到你的 `weather.py` 文件顶部：

    ```python theme={null}
    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"
    ```

    FastMCP 类使用 Python 的类型标注和文档字符串来自动生成工具定义，这使得创建和维护 MCP 工具变得容易。

    ### 辅助函数

    接下来，让我们添加用于查询并格式化来自国家气象服务（National Weather Service）API 的数据的辅助函数：

    ```python theme={null}
    async def make_nws_request(url: str) -> dict[str, Any] | None:
        """Make a request to the NWS API with proper error handling."""
        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


    def format_alert(feature: dict) -> str:
        """Format an alert feature into a readable string."""
        props = feature["properties"]
        return f"""
    Event: {props.get("event", "Unknown")}
    Area: {props.get("areaDesc", "Unknown")}
    Severity: {props.get("severity", "Unknown")}
    Description: {props.get("description", "No description available")}
    Instructions: {props.get("instruction", "No specific instructions provided")}
    """
    ```

    ### 实现工具执行

    工具执行处理器负责实际执行每个工具的逻辑。让我们添加它：

    ```python theme={null}
    @mcp.tool()
    async def get_alerts(state: str) -> str:
        """Get weather alerts for a US state.

        Args:
            state: 两位字母的美国州代码（例如 CA、NY）
        """
        url = f"{NWS_API_BASE}/alerts/active/area/{state}"
        data = await make_nws_request(url)

        if not data or "features" not in data:
            return "无法获取警报，或未找到任何警报。"

        if not data["features"]:
            return "该州没有正在生效的警报。"

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


    @mcp.tool()
    async def get_forecast(latitude: float, longitude: float) -> str:
        """Get weather forecast for a location.

        Args:
            latitude: 位置的纬度
            longitude: 位置的经度
        """
        # 首先获取预报网格端点
        points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
        points_data = await make_nws_request(points_url)

        if not points_data:
            return "无法获取该位置的预报数据。"

        # 从 points 响应中获取预报 URL
        forecast_url = points_data["properties"]["forecast"]
        forecast_data = await make_nws_request(forecast_url)

        if not forecast_data:
            return "无法获取更详细的预报。"

        # 将 periods 格式化为可读的预报
        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)
    ```

    ### 运行服务器

    最后，让我们初始化并运行服务器：

    ```python theme={null}
    def main():
        # 初始化并运行服务器
        mcp.run(transport="stdio")


    if __name__ == "__main__":
        main()
    ```

    你的服务器已经完成了！
    运行 `uv run weather.py` 来启动 MCP 服务器，它将监听来自 MCP 主机的消息。

    现在，让我们使用现有的 MCP 主机来测试你的服务器——Claude for Desktop。

    ## 使用 Claude for Desktop 测试你的服务器

    ## 使用 Claude for Desktop 测试你的服务器

    <Note>
      Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 [构建客户端](/docs/develop/build-client) 教程，以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
    </Note>

    首先，确保你已安装 Claude for Desktop。
    [你可以在这里安装最新版本。](https://claude.ai/download)
    如果你已经安装了 Claude for Desktop，**请确认已更新到最新版本。**

    我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
    为此，在文本编辑器中打开 Claude for Desktop 应用配置文件：`~/Library/Application Support/Claude/claude_desktop_config.json`。
    如果文件不存在，请先创建它。

    例如，如果你安装了 [VS Code](https://code.visualstudio.com/)：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      code ~/Library/Application\ Support/Claude/claude_desktop_config.json
      ```

      ```powershell Windows theme={null}
      code $env:AppData\Claude\claude_desktop_config.json
      ```
    </CodeGroup>

    然后在 `mcpServers` 键中添加你的服务器。
    只有当至少有一个服务器被正确配置时，MCP UI 元素才会显示在 Claude for Desktop 中。

    在这种情况下，我们将按下面方式添加我们的单个天气服务器：

    <CodeGroup>
      ```json macOS/Linux theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "uv",
            "args": [
              "--directory",
              "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
              "run",
              "weather.py"
            ]
          }
        }
      }
      ```

      ```json Windows theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "uv",
            "args": [
              "--directory",
              "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
              "run",
              "weather.py"
            ]
          }
        }
      }
      ```
    </CodeGroup>

    <Warning>
      你可能需要在 `command` 字段中填写 `uv` 可执行文件的完整路径。
      你可以在 macOS/Linux 上运行 `which uv`，在 Windows 上运行 `where uv` 来获取它。
    </Warning>

    <Note>
      请确保在 `cwd`（此处为目录参数）中传入你服务器的绝对路径。
      你可以在 macOS/Linux 上通过运行 `pwd` 获取；在 Windows 的命令提示符中通过运行 `cd` 获取。
      在 Windows 中，请记得在 JSON 路径里使用双反斜杠（`\\`）或正斜杠（`/`）。
    </Note>

    这会告诉 Claude for Desktop：

    1. 有一个名为“weather”的 MCP 服务器
    2. 通过运行 `uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py` 来启动它

    保存文件，然后重启 **Claude for Desktop**。
  </Tab>

  <Tab title="TypeScript">
    让我们开始构建我们的天气服务器吧！
    [你可以在这里找到我们将要构建的完整代码。](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript)

    ### 先决知识

    本快速入门假设你已经熟悉：

    * TypeScript
    * 像 Claude 这样的 LLM

    ### 登录 MCP 服务器

    在实现 MCP 服务器时，请小心处理日志输出方式：

    **基于 STDIO 的服务器：** 永远不要使用 `console.log()`，因为它默认会写入标准输出（stdout）。
    写入 stdout 会破坏 JSON-RPC 消息并导致你的服务器失效。

    **基于 HTTP 的服务器：** 标准输出日志是没问题的，因为它不会干扰 HTTP 响应。

    ### 最佳实践

    * 使用 `console.error()`（它写入 stderr），或者使用写入 stderr 或文件的日志库。

    ### 快速示例

    ```javascript theme={null}
    // ❌ 不好（STDIO）
    console.log("Server started");

    // ✅ 好（STDIO）
    console.error("Server started"); // stderr 是安全的
    ```

    ### 系统要求

    对于 TypeScript，请确保你安装的是最新版本的 Node。

    ### 配置你的环境

    首先，如果你还没有安装，请先安装 Node.js 和 npm。
    你可以从 [nodejs.org](https://nodejs.org/) 下载它们。
    验证你的 Node.js 安装：

    ```bash theme={null}
    node --version
    npm --version
    ```

    在本教程中，你需要 Node.js 版本 16 或更高。

    接下来，让我们创建并设置我们的项目：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      # 为我们的项目创建一个新目录
      mkdir weather
      cd weather

      # 初始化一个新的 npm 项目
      npm init -y

      # 安装依赖
      npm install @modelcontextprotocol/sdk zod@3
      npm install -D @types/node typescript

      # 创建我们的文件
      mkdir src
      touch src/index.ts
      ```

      ```powershell Windows theme={null}
      # 为我们的项目创建一个新目录
      md weather
      cd weather

      # 初始化一个新的 npm 项目
      npm init -y

      # 安装依赖
      npm install @modelcontextprotocol/sdk zod@3
      npm install -D @types/node typescript

      # 创建我们的文件
      md src
      new-item src\index.ts
      ```
    </CodeGroup>

    更新你的 package.json，加入 `type: "module"` 和一个构建脚本：

    ```json package.json theme={null}
    {
      "type": "module",
      "bin": {
        "weather": "./build/index.js"
      },
      "scripts": {
        "build": "tsc && chmod 755 build/index.js"
      },
      "files": ["build"]
    }
    ```

    在项目根目录创建一个 `tsconfig.json`：

    ```json tsconfig.json theme={null}
    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "Node16",
        "moduleResolution": "Node16",
        "outDir": "./build",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules"]
    }
    ```

    现在让我们进入构建你的服务器。

    ## 构建你的服务器

    ### 导入包并设置实例

    将以下内容添加到你的 `src/index.ts` 文件顶部：

    ```typescript theme={null}
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    import { z } from "zod";

    const NWS_API_BASE = "https://api.weather.gov";
    const USER_AGENT = "weather-app/1.0";

    // 创建服务器实例
    const server = new McpServer({
      name: "weather",
      version: "1.0.0",
    });
    ```

    ### 辅助函数

    接下来，让我们添加用于查询并格式化来自国家气象服务（National Weather Service）API 数据的辅助函数：

    ```typescript theme={null}
    // Helper function for making NWS API requests
    async function makeNWSRequest<T>(url: string): Promise<T | null> {
      const headers = {
        "User-Agent": USER_AGENT,
        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()) as T;
      } catch (error) {
        console.error("Error making NWS request:", error);
        return null;
      }
    }

    interface AlertFeature {
      properties: {
        event?: string;
        areaDesc?: string;
        severity?: string;
        status?: string;
        headline?: string;
      };
    }

    // 格式化警报数据
    function formatAlert(feature: AlertFeature): 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");
    }

    interface ForecastPeriod {
      name?: string;
      temperature?: number;
      temperatureUnit?: string;
      windSpeed?: string;
      windDirection?: string;
      shortForecast?: string;
    }

    interface AlertsResponse {
      features: AlertFeature[];
    }

    interface PointsResponse {
      properties: {
        forecast?: string;
      };
    }

    interface ForecastResponse {
      properties: {
        periods: ForecastPeriod[];
      };
    }
    ```

    ### 实现工具执行

    工具执行处理器负责实际执行每个工具的逻辑。让我们添加它：

    ```typescript theme={null}
    // 注册天气工具

    server.registerTool(
      "get_alerts",
      {
        description: "获取某个州的天气警报",
        inputSchema: {
          state: z
            .string()
            .length(2)
            .describe("两位字母的州代码（例如 CA、NY）"),
        },
      },
      async ({ state }) => {
        const stateCode = state.toUpperCase();
        const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
        const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);

        if (!alertsData) {
          return {
            content: [
              {
                type: "text",
                text: "无法获取警报数据",
              },
            ],
          };
        }

        const features = alertsData.features || [];
        if (!features.length) {
          return {
            content: [
              {
                type: "text",
                text: `${stateCode} 没有正在生效的警报`,
              },
            ],
          };
        }

        const formattedAlerts = features.map(formatAlert);
        const alertsText = `当前 ${stateCode} 的警报：\n\n${formattedAlerts.join("\n")}`;

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

    server.registerTool(
      "get_forecast",
      {
        description: "获取某个位置的天气预报",
        inputSchema: {
          latitude: z
            .number()
            .min(-90)
            .max(90)
            .describe("位置的纬度"),
          longitude: z
            .number()
            .min(-180)
            .max(180)
            .describe("位置的经度"),
        },
      },
      async ({ latitude, longitude }) => {
        // 获取预报网格端点
        const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
        const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);

        if (!pointsData) {
          return {
            content: [
              {
                type: "text",
                text: `无法为该坐标获取预报网格数据：${latitude}, ${longitude}。该位置可能不被 NWS API 支持（仅支持美国位置）。`,
              },
            ],
          };
        }

        // 从 points 响应中获取预报 URL
        const forecastUrl = pointsData.properties?.forecast;
        if (!forecastUrl) {
          return {
            content: [
              {
                type: "text",
                text: "无法从网格点数据中获取预报 URL",
              },
            ],
          };
        }

        // 获取预报数据
        const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
        if (!forecastData) {
          return {
            content: [
              {
                type: "text",
                text: "无法获取详细预报数据",
              },
            ],
          };
        }

        // 格式化 periods
        const periods = forecastData.properties?.periods || [];
        if (periods.length === 0) {
          return {
            content: [
              {
                type: "text",
                text: "没有可用的预报时间段",
              },
            ],
          };
        }

        // 格式化预报时间段
        const formattedForecast = periods.map((period: ForecastPeriod) =>
          [
            `${period.name || "未知"}:`,
            `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
            `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
            `${period.shortForecast || "暂无预报"}`,
            "---",
          ].join("\n"),
        );

        const forecastText = `${latitude}, ${longitude} 的预报：\n\n${formattedForecast.join("\n")}`;

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

    ### 运行服务器

    最后，实现用于运行服务器的 main 函数：

    ```typescript theme={null}
    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 in main():", error);
      process.exit(1);
    });
    ```

    确保运行 `npm run build` 来构建你的服务器！
    这是让你的服务器连接起来的一个非常重要步骤。

    现在，让我们使用现有的 MCP 主机来测试你的服务器——Claude for Desktop。

    ## 使用 Claude for Desktop 测试你的服务器

    <Note>
      Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 [构建客户端](/docs/develop/build-client) 教程，以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
    </Note>

    首先，确保你已安装 Claude for Desktop。
    [你可以在这里安装最新版本。](https://claude.ai/download)
    如果你已经安装了 Claude for Desktop，**请确认已更新到最新版本。**

    我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
    为此，在文本编辑器中打开 Claude for Desktop 应用配置文件：`~/Library/Application Support/Claude/claude_desktop_config.json`。
    如果文件不存在，请先创建它。

    例如，如果你安装了 [VS Code](https://code.visualstudio.com/)：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      code ~/Library/Application\ Support/Claude/claude_desktop_config.json
      ```

      ```powershell Windows theme={null}
      code $env:AppData\Claude\claude_desktop_config.json
      ```
    </CodeGroup>

    然后在 `mcpServers` 键中添加你的服务器。
    只有当至少有一个服务器被正确配置时，MCP UI 元素才会显示在 Claude for Desktop 中。

    在这种情况下，我们将按下面方式添加我们的单个天气服务器：

    <CodeGroup>
      ```json macOS/Linux theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "node",
            "args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"]
          }
        }
      }
      ```

      ```json Windows theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "node",
            "args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"]
          }
        }
      }
      ```
    </CodeGroup>

    这会告诉 Claude for Desktop：

    1. 有一个名为“weather”的 MCP 服务器
    2. 通过运行 `node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js` 来启动它

    保存文件，然后重启 **Claude for Desktop**。
  </Tab>

  <Tab title="Java">
    <Note>
      这是一个基于 Spring AI 的 MCP 自动配置和启动器（boot starters）的快速入门演示。
      要学习如何手动创建同步和异步的 MCP 服务器，请查阅 [Java SDK Server](https://java.sdk.modelcontextprotocol.io/) 文档。
    </Note>

    让我们开始构建我们的天气服务器吧！
    [你可以在这里找到我们将要构建的完整代码。](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server)

    更多信息请查看 [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) 的参考文档。
    若要手动实现 MCP 服务器，请参考 [MCP Server Java SDK 文档](https://java.sdk.modelcontextprotocol.io/)。

    ### 在 MCP 服务器中记录日志

    在实现 MCP 服务器时，请小心处理日志输出方式：

    **基于 STDIO 的服务器：** 永远不要使用 `System.out.println()` 或 `System.out.print()`，因为它们会写入标准输出（stdout）。
    向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。

    **基于 HTTP 的服务器：** 标准输出日志是没问题的，因为它不会干扰 HTTP 响应。

    ### 最佳实践

    * 使用写入 stderr 或文件的日志库。
    * 确保任何已配置的日志框架都不会向 stdout 输出。

    ### 系统要求

    * 已安装 Java 17 或更高版本。
    * [Spring Boot 3.3.x](https://docs.spring.io/spring-boot/installing.html) 或更高版本

    ### 配置你的环境

    使用 [Spring Initializer](https://start.spring.io/) 来引导创建项目。

    你需要添加以下依赖：

    <CodeGroup>
      ```xml Maven theme={null}
      <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-starter-mcp-server</artifactId>
            </dependency>

            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
            </dependency>
      </dependencies>
      ```

      ```groovy Gradle theme={null}
      dependencies {
        implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
        implementation platform("org.springframework:spring-web")
      }
      ```
    </CodeGroup>

    然后通过设置应用程序属性来配置你的应用：

    <CodeGroup>
      ```bash application.properties theme={null}
      spring.main.bannerMode=off
      logging.pattern.console=
      ```

      ```yaml application.yml theme={null}
      logging:
        pattern:
          console:
      spring:
        main:
          banner-mode: off
      ```
    </CodeGroup>

    [Server Configuration Properties](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_configuration_properties) 文档中列出了所有可用属性。

    现在让我们进入构建你的服务器。

    ## 构建你的服务器

    ## 构建你的服务器

    ### 天气服务

    让我们实现一个 [WeatherService.java](https://github.com/spring-projects/spring-ai-examples/blob/main/model-context-protocol/weather/starter-stdio-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java)，它使用 REST 客户端来查询来自国家气象服务 API 的数据：

    ```java theme={null}
    @Service
    public class WeatherService {

    	private final RestClient restClient;

    	public WeatherService() {
    		this.restClient = RestClient.builder()
    			.baseUrl("https://api.weather.gov")
    			.defaultHeader("Accept", "application/geo+json")
    			.defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
    			.build();
    	}

      @Tool(description = "获取指定纬度/经度的天气预报")
      public String getWeatherForecastByLocation(
          double latitude,   // 纬度坐标
          double longitude   // 经度坐标
      ) {
          // 返回包含详细预报信息：
          // - 温度及单位
          // - 风速和风向
          // - 详细的预报描述
      }

      @Tool(description = "获取某个美国州的天气警报")
      public String getAlerts(
          @ToolParam(description = "两位字母的美国州代码（例如 CA、NY）") String state
      ) {
          // 返回正在生效的警报信息，包括：
          // - 事件类型
          // - 受影响区域
          // - 严重程度
          // - 描述
          // - 安全指导
      }

      // ......
    }
    ```

    `@Service` 注解会自动在你的应用上下文中注册该服务。
    Spring AI 的 `@Tool` 注解可以轻松创建和维护 MCP 工具。

    自动配置会自动将这些工具注册到 MCP 服务器中。

    ### 创建你的 Boot 应用

    ```java theme={null}
    @SpringBootApplication
    public class McpServerApplication {

    	public static void main(String[] args) {
    		SpringApplication.run(McpServerApplication.class, args);
    	}

    	@Bean
    	public ToolCallbackProvider weatherTools(WeatherService weatherService) {
    		return  MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
    	}
    }
    ```

    使用 `MethodToolCallbackProvider` 工具类来把 `@Tools` 转换成 MCP 服务器可执行的回调。

    ### 运行服务器

    最后，构建服务器：

    ```bash theme={null}
    ./mvnw clean install
    ```

    这会在 `target` 文件夹中生成一个 `mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` 文件。

    现在让我们使用现有的 MCP 主机——Claude for Desktop 来测试你的服务器。

    ## 使用 Claude for Desktop 测试你的服务器

    <Note>
      Claude for Desktop 目前在 Linux 上不可用。
    </Note>

    首先，确保你已安装 Claude for Desktop。
    [你可以在这里安装最新版本。](https://claude.ai/download)
    如果你已经安装了 Claude for Desktop，**请确认已更新到最新版本。**

    我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
    为此，在文本编辑器中打开 Claude for Desktop 应用配置文件：`~/Library/Application Support/Claude/claude_desktop_config.json`。
    如果文件不存在，请先创建它。

    例如，如果你安装了 [VS Code](https://code.visualstudio.com/)：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      code ~/Library/Application\ Support/Claude/claude_desktop_config.json
      ```

      ```powershell Windows theme={null}
      code $env:AppData\Claude\claude_desktop_config.json
      ```
    </CodeGroup>

    然后在 `mcpServers` 键中添加你的服务器。
    只有当至少有一个服务器被正确配置时，MCP UI 元素才会显示在 Claude for Desktop 中。

    在这种情况下，我们将按下面方式添加我们的单个天气服务器：

    <CodeGroup>
      ```json macOS/Linux theme={null}
      {
        "mcpServers": {
          "spring-ai-mcp-weather": {
            "command": "java",
            "args": [
              "-Dspring.ai.mcp.server.stdio=true",
              "-jar",
              "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
            ]
          }
        }
      }
      ```

      ```json Windows theme={null}
      {
        "mcpServers": {
          "spring-ai-mcp-weather": {
            "command": "java",
            "args": [
              "-Dspring.ai.mcp.server.transport=STDIO",
              "-jar",
              "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
            ]
          }
        }
      }
      ```
    </CodeGroup>

    <Note>
      请确保传入你服务器的绝对路径。
    </Note>

    这会告诉 Claude for Desktop：

    1. 有一个名为“my-weather-server”的 MCP 服务器
    2. 通过运行 `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` 来启动它

    保存文件，然后重启 **Claude for Desktop**。

    ## 使用 Java 客户端测试你的服务器

    ### 手动创建一个 MCP Client

    使用 `McpClient` 连接到服务器：

    ```java theme={null}
    var stdioParams = ServerParameters.builder("java")
      .args("-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
      .build();

    var stdioTransport = new StdioClientTransport(stdioParams);

    var mcpClient = McpClient.sync(stdioTransport).build();

    mcpClient.initialize();

    ListToolsResult toolsList = mcpClient.listTools();

    调用工具 weather = mcpClient.callTool(
      new CallToolRequest("getWeatherForecastByLocation",
          Map.of("latitude", "47.6062", "longitude", "-122.3321")));

    CallToolResult alert = mcpClient.callTool(
      new CallToolRequest("getAlerts", Map.of("state", "NY")));

    mcpClient.closeGracefully();
    ```

    ### 使用 MCP Client Boot Starter

    使用 `spring-ai-starter-mcp-client` 依赖创建一个新的 boot starter 应用：

    ```xml theme={null}
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-client</artifactId>
    </dependency>
    ```

    并将 `spring.ai.mcp.client.stdio.servers-configuration` 属性指向你的 `claude_desktop_config.json`。
    你可以复用现有的 Anthropic Desktop 配置：

    ```properties theme={null}
    spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
    ```

    当你启动客户端应用时，自动配置会从 claude\_desktop\_config.json 自动创建 MCP 客户端。

    更多信息，请查看 [MCP Client Boot Starters](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-client-docs.html) 参考文档。

    ## 更多 Java MCP Server 示例

    [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) 展示了如何使用 SSE 传输创建一个 MCP 服务器。
    它演示了如何使用 Spring Boot 的自动配置功能来定义并注册 MCP Tools、Resources 和 Prompts。
  </Tab>

  <Tab title="Kotlin">
    让我们开始构建我们的天气服务器吧！
    [你可以在这里找到我们将要构建的完整代码。](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/weather-stdio-server)

    ### 先决知识

    本快速入门假设你已经熟悉：

    * Kotlin
    * 像 Claude 这样的 LLM

    ### 在 MCP 服务器中记录日志

    在实现 MCP 服务器时，请小心处理日志输出方式：

    **基于 STDIO 的服务器：** 永远不要使用 `println()`，因为它默认会写入标准输出（stdout）。
    向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。

    **基于 HTTP 的服务器：** 标准输出日志是没问题的，因为它不会干扰 HTTP 响应。

    ### 最佳实践

    * 使用写入 stderr 或文件的日志库。

    ### 系统要求

    * 已安装 JDK 11 或更高版本。

    ### 配置你的环境

    首先，如果你还没有安装，请安装 `java` 和 `gradle`。
    你可以从 [官方 Oracle JDK 网站](https://www.oracle.com/java/technologies/downloads/) 下载 `java`。
    验证你的 `java` 安装：

    ```bash theme={null}
    java --version
    ```

    现在，让我们创建并设置我们的项目：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      # 为我们的项目创建一个新目录
      mkdir weather
      cd weather

      # 初始化一个新的 kotlin 项目
      gradle init
      ```

      ```powershell Windows theme={null}
      # 为我们的项目创建一个新目录
      md weather
      cd weather

      # 初始化一个新的 kotlin 项目
      gradle init
      ```
    </CodeGroup>

    运行 `gradle init` 之后，请选择 **Application** 作为项目类型，并选择 **Kotlin** 作为编程语言。

    或者，你也可以使用 [IntelliJ IDEA 项目向导](https://kotlinlang.org/docs/jvm-get-started.html)来创建一个 Kotlin 应用。

    创建项目后，将你的 `build.gradle.kts` 文件内容替换为：

    ```kotlin build.gradle.kts theme={null}
    // 检查最新版本：https://github.com/modelcontextprotocol/kotlin-sdk/releases
    val mcpVersion = "0.9.0"
    val ktorVersion = "3.2.3"
    val slf4jVersion = "2.0.17"

    plugins {
        kotlin("jvm") version "2.3.20"
        kotlin("plugin.serialization") version "2.3.20"
        id("com.gradleup.shadow") version "8.3.9"
        application
    }

    application {
        mainClass.set("MainKt")
    }

    dependencies {
        implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
        implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
        implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
        implementation("io.ktor:ktor-client-cio:$ktorVersion")
        implementation("org.slf4j:slf4j-simple:$slf4jVersion")
    }
    ```

    确认所有配置都正确：

    ```bash theme={null}
    ./gradlew build
    ```

    现在让我们进入构建你的服务器。

    ## 构建你的服务器

    ## 构建你的服务器

    ### 设置实例

    添加一个服务器初始化函数：

    ```kotlin theme={null}
    fun runMcpServer() {
        val server = Server(
            Implementation(
                name = "weather",
                version = "1.0.0",
            ),
            ServerOptions(
                capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
            ),
        )

        // 在这里注册服务器上的工具

        val transport = StdioServerTransport(
            System.`in`.asInput(),
            System.out.asSink().buffered(),
        )

        runBlocking {
            val session = server.createSession(transport)
            val done = Job()
            session.onClose {
                done.complete()
            }
            done.join()
        }
    }
    ```

    ### 天气 API 辅助函数

    接下来，让我们添加用于查询并转换来自国家气象服务 API 响应的函数和数据类：

    ```kotlin theme={null}
    val httpClient = HttpClient(CIO) {
        defaultRequest {
            url("https://api.weather.gov")
            headers {
                append("Accept", "application/geo+json")
                append("User-Agent", "WeatherApiClient/1.0")
            }
            contentType(ContentType.Application.Json)
        }
        install(ContentNegotiation) {
            json(Json { ignoreUnknownKeys = true })
        }
    }

    // 扩展函数：为指定州获取天气警报
    suspend fun HttpClient.getAlerts(state: String): List<String> {
        val alerts = this.get("/alerts/active/area/$state").body<AlertsResponse>()
        return alerts.features.map { feature ->
            """
                Event: ${feature.properties.event}
                Area: ${feature.properties.areaDesc}
                Severity: ${feature.properties.severity}
                Status: ${feature.properties.status}
                Headline: ${feature.properties.headline}
            """.trimIndent()
        }
    }

    // 扩展函数：为指定纬度和经度获取预报信息
    suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List<String> {
        val points = this.get("/points/$latitude,$longitude").body<PointsResponse>()
        val forecastUrl = points.properties.forecast ?: error("No forecast URL available")
        val forecast = this.get(forecastUrl).body<ForecastResponse>()
        return forecast.properties.periods.map { period ->
            """
                ${period.name}:
                Temperature: ${period.temperature}°${period.temperatureUnit}
                Wind: ${period.windSpeed} ${period.windDirection}
                ${period.shortForecast}
            """.trimIndent()
        }
    }

    @Serializable
    data class PointsResponse(val properties: PointsProperties)

    @Serializable
    data class PointsProperties(val forecast: String? = null)

    @Serializable
    data class ForecastResponse(val properties: ForecastProperties)

    @Serializable
    data class ForecastProperties(val periods: List<ForecastPeriod> = emptyList())

    @Serializable
    data class ForecastPeriod(
        val name: String? = null,
        val temperature: Int? = null,
        val temperatureUnit: String? = null,
        val windSpeed: String? = null,
        val windDirection: String? = null,
        val shortForecast: String? = null,
    )

    @Serializable
    data class AlertsResponse(val features: List<AlertFeature> = emptyList())

    @Serializable
    data class AlertFeature(val properties: AlertProperties)

    @Serializable
    data class AlertProperties(
        val event: String? = null,
        val areaDesc: String? = null,
        val severity: String? = null,
        val status: String? = null,
        val headline: String? = null,
    )
    ```

    ### 实现工具执行

    工具执行处理器负责实际执行每个工具的逻辑。让我们添加它：

    ```kotlin theme={null}
    // 注册天气工具

    server.addTool(
        name = "get_alerts",
        description = "获取某个美国州的天气警报。输入为两位字母的美国州代码（例如 CA、NY）",
        inputSchema = ToolSchema(
            properties = buildJsonObject {
                putJsonObject("state") {
                    put("type", "string")
                    put("description", "Two-letter US state code (e.g. CA, NY)")
                }
            },
            required = listOf("state"),
        ),
    ) { request ->
        val state = request.arguments?.get("state")?.jsonPrimitive?.content
            ?: return@addTool CallToolResult(
                content = listOf(TextContent("The 'state' parameter is required.")),
            )

        val alerts = httpClient.getAlerts(state)
        CallToolResult(content = alerts.map { TextContent(it) })
    }

    server.addTool(
        name = "get_forecast",
        description = "获取某个位置的天气预报。注意：仅 NWS API 支持美国位置。",
        inputSchema = ToolSchema(
            properties = buildJsonObject {
                putJsonObject("latitude") {
                    put("type", "number")
                    put("description", "Latitude of the location")
                }
                putJsonObject("longitude") {
                    put("type", "number")
                    put("description", "Longitude of the location")
                }
            },
            required = listOf("latitude", "longitude"),
        ),
    ) { request ->
        val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull
        val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull
        if (latitude == null || longitude == null) {
            return@addTool CallToolResult(
                content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),
            )
        }

        val forecast = httpClient.getForecast(latitude, longitude)
        CallToolResult(content = forecast.map { TextContent(it) })
    }
    ```

    ### 运行服务器

    最后，实现用于运行服务器的 main 函数：

    ```kotlin theme={null}
    fun main() = runMcpServer()
    ```

    你可以在开发期间直接运行服务器：

    ```bash theme={null}
    ./gradlew run
    ```

    用于生产环境，请构建 shadow JAR：

    ```bash theme={null}
    ./gradlew build
    java -jar build/libs/weather-0.1.0-all.jar
    ```

    现在，让我们使用现有的 MCP 主机——Claude for Desktop 来测试你的服务器。

    ## 使用 Claude for Desktop 测试你的服务器

    <Note>
      Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 [构建客户端](/docs/develop/build-client) 教程，以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
    </Note>

    首先，确保你已安装 Claude for Desktop。
    [你可以在这里安装最新版本。](https://claude.ai/download)
    如果你已经安装了 Claude for Desktop，**请确认已更新到最新版本。**

    我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
    为此，在文本编辑器中打开 Claude for Desktop 应用配置文件：`~/Library/Application Support/Claude/claude_desktop_config.json`。
    如果文件不存在，请先创建它。

    例如，如果你安装了 [VS Code](https://code.visualstudio.com/)：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      code ~/Library/Application\ Support/Claude/claude_desktop_config.json
      ```

      ```powershell Windows theme={null}
      code $env:AppData\Claude\claude_desktop_config.json
      ```
    </CodeGroup>

    然后在 `mcpServers` 键中添加你的服务器。
    只有当至少有一个服务器被正确配置时，MCP UI 元素才会显示在 Claude for Desktop 中。

    在这种情况下，我们将按下面方式添加我们的单个天气服务器：

    <CodeGroup>
      ```json macOS/Linux theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "java",
            "args": [
              "-jar",
              "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"
            ]
          }
        }
      }
      ```

      ```json Windows theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "java",
            "args": [
              "-jar",
              "C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\libs\\weather-0.1.0-all.jar"
            ]
          }
        }
      }
      ```
    </CodeGroup>

    这会告诉 Claude for Desktop：

    1. 有一个名为“weather”的 MCP 服务器
    2. 通过运行 `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar` 来启动它

    保存文件，然后重启 **Claude for Desktop**。
  </Tab>

  <Tab title="C#">
    让我们开始构建我们的天气服务器吧！
    [你可以在这里找到我们将要构建的完整代码。](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer)

    ### 先决知识

    本快速入门假设你已经熟悉：

    * C#
    * 像 Claude 这样的 LLM
    * .NET 8 或更高版本

    ### 在 MCP 服务器中记录日志

    在实现 MCP 服务器时，请小心处理日志输出方式：

    **基于 STDIO 的服务器：** 永远不要使用 `Console.WriteLine()` 或 `Console.Write()`，因为它们会写入标准输出（stdout）。
    向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。

    **基于 HTTP 的服务器：** 标准输出日志是没问题的，因为它不会干扰 HTTP 响应。

    ### 最佳实践

    * 使用写入 stderr 或文件的日志库。

    ### 系统要求

    * 已安装 [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) 或更高版本。

    ### 配置你的环境

    首先，如果你还没有安装，请先安装 `dotnet`。
    你可以从 [官方 Microsoft .NET 网站](https://dotnet.microsoft.com/download/) 下载 `dotnet`。
    验证你的 dotnet 安装：

    ```bash theme={null}
    dotnet --version
    ```

    现在，让我们创建并设置我们的项目：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      # 为我们的项目创建一个新目录
      mkdir weather
      cd weather
      # 初始化一个新的 C# 项目
      dotnet new console
      ```

      ```powershell Windows theme={null}
      # 为我们的项目创建一个新目录
      mkdir weather
      cd weather
      # 初始化一个新的 C# 项目
      dotnet new console
      ```
    </CodeGroup>

    运行 `dotnet new console` 后，你将看到一个新的 C# 项目。
    你可以在你喜欢的 IDE 中打开它，例如 [Visual Studio](https://visualstudio.microsoft.com/) 或 [Rider](https://www.jetbrains.com/rider/)。
    或者，你也可以使用 [Visual Studio 项目向导](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022) 创建一个 C# 应用。
    创建项目后，添加 Model Context Protocol SDK 和托管所需的 NuGet 包：

    ```bash theme={null}
    # 添加 Model Context Protocol SDK 的 NuGet 包
    dotnet add package ModelContextProtocol --prerelease
    # 添加 .NET 托管的 NuGet 包
    dotnet add package Microsoft.Extensions.Hosting
    ```

    现在让我们进入构建你的服务器。

    ## 构建你的服务器

    在项目中打开 `Program.cs` 文件，并用以下代码替换其内容：

    ```csharp theme={null}
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using ModelContextProtocol;
    using System.Net.Http.Headers;

    var builder = Host.CreateEmptyApplicationBuilder(settings: null);

    builder.Services.AddMcpServer()
        .WithStdioServerTransport()
        .WithToolsFromAssembly();

    builder.Services.AddSingleton(_ =>
    {
        var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
        client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
        return client;
    });

    var app = builder.Build();

    await app.RunAsync();
    ```

    <Note>
      创建 `ApplicationHostBuilder` 时，请确保使用 `CreateEmptyApplicationBuilder`，而不是 `CreateDefaultBuilder`。
      这样可以确保服务器不会向控制台输出任何额外消息。
      这仅在使用 STDIO 传输的服务器中是必要的。
    </Note>

    这段代码会设置一个基础控制台应用，使用 Model Context Protocol SDK 创建一个带标准 I/O 传输的 MCP 服务器。

    ### 天气 API 辅助函数

    为 `HttpClient` 创建一个扩展类，用于简化 JSON 请求处理：

    ```csharp theme={null}
    using System.Text.Json;

    internal static class HttpClientExt
    {
        public static async Task<JsonDocument> ReadJsonDocumentAsync(this HttpClient client, string requestUri)
        {
            using var response = await client.GetAsync(requestUri);
            response.EnsureSuccessStatusCode();
            return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
        }
    }
    ```

    接下来，定义一个包含工具执行处理器的类，用于查询并转换来自国家气象服务 API 的响应：

    ```csharp theme={null}
    using ModelContextProtocol.Server;
    using System.ComponentModel;
    using System.Globalization;
    using System.Text.Json;

    namespace QuickstartWeatherServer.Tools;

    [McpServerToolType]
    public static class WeatherTools
    {
        [McpServerTool, Description("获取某个美国州代码的天气警报。")]
        public static async Task<string> GetAlerts(
            HttpClient client,
            [Description("要获取警报的美国州代码。")] string state)
        {
            using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
            var jsonElement = jsonDocument.RootElement;
            var alerts = jsonElement.GetProperty("features").EnumerateArray();

            if (!alerts.Any())
            {
                return "该州没有正在生效的警报。";
            }

            return string.Join("\n--\n", alerts.Select(alert =>
            {
                JsonElement properties = alert.GetProperty("properties");
                return $"""
                        Event: {properties.GetProperty("event").GetString()}
                        Area: {properties.GetProperty("areaDesc").GetString()}
                        Severity: {properties.GetProperty("severity").GetString()}
                        Description: {properties.GetProperty("description").GetString()}
                        Instruction: {properties.GetProperty("instruction").GetString()}
                        """;
            }));
        }

        [McpServerTool, Description("获取某个位置的天气预报。")]
        public static async Task<string> GetForecast(
            HttpClient client,
            [Description("位置的纬度。")] double latitude,
            [Description("位置的经度。")] double longitude)
        {
            var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
            using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
            var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
                ?? throw new Exception($"{client.BaseAddress}points/{latitude},{longitude} 未提供预报 URL");

            using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
            var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();

            return string.Join("\n---\n", periods.Select(period => $"""
                    {period.GetProperty("name").GetString()}
                    Temperature: {period.GetProperty("temperature").GetInt32()}°F
                    Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
                    Forecast: {period.GetProperty("detailedForecast").GetString()}
                    """));
        }
    }
    ```

    ### 运行服务器

    最后，使用以下命令运行服务器：

    ```bash theme={null}
    dotnet run
    ```

    这将启动服务器，并在标准输入/输出上监听传入请求。

    ## 使用 Claude for Desktop 测试你的服务器

    <Note>
      Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 [构建客户端](/docs/develop/build-client) 教程，以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
    </Note>

    首先，确保你已安装 Claude for Desktop。
    [你可以在这里安装最新版本。](https://claude.ai/download)
    如果你已经安装了 Claude for Desktop，**请确认已更新到最新版本。**
    我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
    为此，在文本编辑器中打开 Claude for Desktop 应用配置文件：`~/Library/Application Support/Claude/claude_desktop_config.json`。
    如果文件不存在，请先创建它。
    例如，如果你安装了 [VS Code](https://code.visualstudio.com/)：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      code ~/Library/Application\ Support/Claude/claude_desktop_config.json
      ```

      ```powershell Windows theme={null}
      code $env:AppData\Claude\claude_desktop_config.json
      ```
    </CodeGroup>

    然后在 `mcpServers` 键中添加你的服务器。
    只有当至少有一个服务器被正确配置时，MCP UI 元素才会显示在 Claude for Desktop 中。
    在这种情况下，我们将按下面方式添加我们的单个天气服务器：

    <CodeGroup>
      ```json macOS/Linux theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "dotnet",
            "args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
          }
        }
      }
      ```

      ```json Windows theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "dotnet",
            "args": [
              "run",
              "--project",
              "C:\\ABSOLUTE\\PATH\\TO\\PROJECT",
              "--no-build"
            ]
          }
        }
      }
      ```
    </CodeGroup>

    这会告诉 Claude for Desktop：

    1. 有一个名为“weather”的 MCP 服务器
    2. 通过运行 `dotnet run /ABSOLUTE/PATH/TO/PROJECT` 来启动它
       保存文件，然后重启 **Claude for Desktop**。
  </Tab>

  <Tab title="Ruby">
    让我们开始构建我们的天气服务器吧！
    [你可以在这里找到我们将要构建的完整代码。](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby)

    ### 先决知识

    本快速入门假设你已经熟悉：

    * Ruby
    * 像 Claude 这样的 LLM

    ### 在 MCP 服务器中记录日志

    在实现 MCP 服务器时，请小心处理日志输出方式：

    **基于 STDIO 的服务器：** 永远不要使用 `puts` 或 `print`，因为它们默认会写入标准输出（stdout）。
    向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。

    **基于 HTTP 的服务器：** 标准输出日志是没问题的，因为它不会干扰 HTTP 响应。

    ### 最佳实践

    * 使用写入 stderr 或文件的日志库。

    ### 快速示例

    ```ruby theme={null}
    # ❌ 不好（STDIO）
    puts "Processing request"

    # ✅ 好（STDIO）
    require "logger"
    logger = Logger.new($stderr)
    logger.info("Processing request")
    ```

    ### 系统要求

    * 已安装 Ruby 2.7 或更高版本。

    ### 配置你的环境

    首先，确保你已安装 Ruby。
    你可以通过运行：

    ```bash theme={null}
    ruby --version
    ```

    现在，让我们创建并设置我们的项目：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      # 为我们的项目创建一个新目录
      mkdir weather
      cd weather

      # 创建 Gemfile
      bundle init

      # 添加 MCP SDK 依赖
      bundle add mcp

      # 创建我们的服务器文件
      touch weather.rb
      ```

      ```powershell Windows theme={null}
      # 为我们的项目创建一个新目录
      mkdir weather
      cd weather

      # 创建 Gemfile
      bundle init

      # 添加 MCP SDK 依赖
      bundle add mcp

      # 创建我们的服务器文件
      new-item weather.rb
      ```
    </CodeGroup>

    现在让我们进入构建你的服务器。

    ## 构建你的服务器

    ### 导入包并设置常量

    打开 `weather.rb`，并在顶部添加这些 require 和常量：

    ```ruby theme={null}
    require "json"
    require "mcp"
    require "net/http"
    require "uri"

    NWS_API_BASE = "https://api.weather.gov"
    USER_AGENT = "weather-app/1.0"
    ```

    `mcp` gem 为 Ruby 提供 Model Context Protocol SDK，包含服务器实现类和 stdio 传输。

    ### 辅助方法

    接下来，让我们添加用于查询并格式化来自国家气象服务 API 数据的辅助方法：

    ```ruby theme={null}
    module HelperMethods
      def make_nws_request(url)
        uri = URI(url)
        request = Net::HTTP::Get.new(uri)
        request["User-Agent"] = USER_AGENT
        request["Accept"] = "application/geo+json"

        response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
          http.request(request)
        end

        raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)

        JSON.parse(response.body)
      end

      def format_alert(feature)
        properties = feature["properties"]

        <<~ALERT
          Event: #{properties["event"] || "Unknown"}
          Area: #{properties["areaDesc"] || "Unknown"}
          Severity: #{properties["severity"] || "Unknown"}
          Description: #{properties["description"] || "No description available"}
          Instructions: #{properties["instruction"] || "No specific instructions provided"}
        ALERT
      end
    end
    ```

    ### 实现工具执行

    现在让我们定义我们的工具类。
    每个工具都继承 `MCP::Tool` 并实现工具逻辑：

    ```ruby theme={null}
    class GetAlerts < MCP::Tool
      extend HelperMethods

      tool_name "get_alerts"
      description "获取某个美国州的天气警报"
      input_schema(
        properties: {
          state: {
            type: "string",
            description: "两位字母的美国州代码（例如 CA、NY）"
          }
        },
        required: ["state"]
      )

      def self.call(state:)
        url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}"
        data = make_nws_request(url)

        if data["features"].empty?
          return MCP::Tool::Response.new([{
            type: "text",
            text: "该州没有正在生效的警报。"
          }])
        end

        alerts = data["features"].map { |feature| format_alert(feature) }
        MCP::Tool::Response.new([{
          type: "text",
          text: alerts.join("\n---\n")
        }])
      end
    end

    class GetForecast < MCP::Tool
      extend HelperMethods

      tool_name "get_forecast"
      description "获取某个位置的天气预报"
      input_schema(
        properties: {
          latitude: {
            type: "number",
            description: "位置的纬度"
          },
          longitude: {
            type: "number",
            description: "位置的经度"
          }
        },
        required: ["latitude", "longitude"]
      )

      def self.call(latitude:, longitude:)
        # 首先获取预报网格端点。
        points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}"
        points_data = make_nws_request(points_url)

        # 从 points 响应中获取预报 URL。
        forecast_url = points_data["properties"]["forecast"]
        forecast_data = make_nws_request(forecast_url)

        # 将 periods 格式化为可读的预报。
        periods = forecast_data["properties"]["periods"]
        forecasts = periods.first(5).map do |period|
          <<~FORECAST
            #{period["name"]}:
            Temperature: #{period["temperature"]}°#{period["temperatureUnit"]}
            Wind: #{period["windSpeed"]} #{period["windDirection"]}
            Forecast: #{period["detailedForecast"]}
          FORECAST
        end

        MCP::Tool::Response.new([{
          type: "text",
          text: forecasts.join("\n---\n")
        }])
      end
    end
    ```

    ### 运行服务器

    最后，初始化并运行服务器：

    ```ruby theme={null}
    server = MCP::Server.new(
      name: "weather",
      version: "1.0.0",
      tools: [GetAlerts, GetForecast]
    )

    transport = MCP::Server::Transports::StdioTransport.new(server)
    transport.open
    ```

    你的服务器已经完成了！
    运行 `bundle exec ruby weather.rb` 来启动 MCP 服务器，它将监听来自 MCP 主机的消息。

    现在，让我们使用现有的 MCP 主机——Claude for Desktop 来测试你的服务器。

    ## 使用 Claude for Desktop 测试你的服务器

    <Note>
      Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 [构建客户端](/docs/develop/build-client) 教程，以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
    </Note>

    首先，确保你已安装 Claude for Desktop。
    [你可以在这里安装最新版本。](https://claude.ai/download)
    如果你已经安装了 Claude for Desktop，**请确认已更新到最新版本。**

    我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
    为此，在文本编辑器中打开 Claude for Desktop 应用配置文件：`~/Library/Application Support/Claude/claude_desktop_config.json`。
    如果文件不存在，请先创建它。

    例如，如果你安装了 [VS Code](https://code.visualstudio.com/)：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      code ~/Library/Application\ Support/Claude/claude_desktop_config.json
      ```

      ```powershell Windows theme={null}
      code $env:AppData\Claude\claude_desktop_config.json
      ```
    </CodeGroup>

    然后在 `mcpServers` 键中添加你的服务器。
    只有当至少有一个服务器被正确配置时，MCP UI 元素才会显示在 Claude for Desktop 中。

    在这种情况下，我们将按下面方式添加我们的单个天气服务器：

    <CodeGroup>
      ```json macOS/Linux theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "bundle",
            "args": ["exec", "ruby", "weather.rb"],
            "cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather"
          }
        }
      }
      ```

      ```json Windows theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "bundle",
            "args": ["exec", "ruby", "weather.rb"],
            "cwd": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather"
          }
        }
      }
      ```
    </CodeGroup>

    <Note>
      请确保在 `cwd` 字段中传入你的项目目录的绝对路径。
      你可以在 macOS/Linux 上运行 `pwd` 获取；在 Windows 命令提示符中，从你的项目目录运行 `cd` 获取。
      在 Windows 中，请记得在 JSON 路径里使用双反斜杠（`\\`）或正斜杠（`/`）。
    </Note>

    这会告诉 Claude for Desktop：

    1. 有一个名为“weather”的 MCP 服务器
    2. 在指定目录中通过运行 `bundle exec ruby weather.rb` 来启动它

    保存文件，然后重启 **Claude for Desktop**。
  </Tab>

  <Tab title="Rust">
    让我们开始构建我们的天气服务器吧！
    [你可以在这里找到我们将要构建的完整代码。](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust)

    ### 先决知识

    本快速入门假设你已经熟悉：

    * Rust 编程语言
    * Rust 中的 async/await
    * 像 Claude 这样的 LLM

    ### 在 MCP 服务器中记录日志

    在实现 MCP 服务器时，请小心处理日志输出方式：

    **基于 STDIO 的服务器：** 永远不要使用 `println!()` 或 `print!()`，因为它们会写入标准输出（stdout）。
    向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。

    **基于 HTTP 的服务器：** 标准输出日志是没问题的，因为它不会干扰 HTTP 响应。

    ### 最佳实践

    * 使用写入 stderr 或文件的日志库，例如 Rust 中的 `tracing` 或 `log`。
    * 配置你的日志框架，避免输出到 stdout。

    ### 系统要求

    * 已安装 Rust 1.70 或更高版本。
    * Cargo（随 Rust 安装自带）。

    ### 配置你的环境

    首先，如果你还没有安装 Rust，请先安装它。
    你可以从 [rust-lang.org](https://www.rust-lang.org/tools/install) 安装：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
      ```

      ```powershell Windows theme={null}
      # 从 https://rustup.rs/ 下载并运行 rustup-init.exe
      ```
    </CodeGroup>

    验证你的 Rust 安装：

    ```bash theme={null}
    rustc --version
    cargo --version
    ```

    现在，让我们创建并设置我们的项目：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      # 创建一个新的 Rust 项目
      cargo new weather
      cd weather
      ```

      ```powershell Windows theme={null}
      # 创建一个新的 Rust 项目
      cargo new weather
      cd weather
      ```
    </CodeGroup>

    将你的 `Cargo.toml` 更新为添加所需依赖项：

    ```toml Cargo.toml theme={null}
    [package]
    name = "weather"
    version = "0.1.0"
    edition = "2024"

    [dependencies]
    rmcp = { version = "0.3", features = ["server", "macros", "transport-io"] }
    tokio = { version = "1.46", features = ["full"] }
    reqwest = { version = "0.12", features = ["json"] }
    serde = { version = "1.0", features = ["derive"] }
    serde_json = "1.0"
    anyhow = "1.0"
    tracing = "0.1"
    tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] }
    ```

    现在让我们进入构建你的服务器。

    ## 构建你的服务器

    ### 导入包并设置常量

    打开 `src/main.rs`，并在顶部添加这些 imports 和常量：

    ```rust theme={null}
    use anyhow::Result;
    use rmcp::{
        ServerHandler, ServiceExt,
        handler::server::{router::tool::ToolRouter, tool::Parameters},
        model::*,
        schemars, tool, tool_handler, tool_router,
    };
    use serde::Deserialize;
    use serde::de::DeserializeOwned;

    const NWS_API_BASE: &str = "https://api.weather.gov";
    const USER_AGENT: &str = "weather-app/1.0";
    ```

    `rmcp` crate 为 Rust 提供 Model Context Protocol SDK，包含用于服务器实现的功能、过程宏（procedural macros）以及 stdio 传输。

    ### 数据结构

    接下来，我们为反序列化来自国家气象服务 API 的响应定义数据结构：

    ```rust theme={null}
    #[derive(Debug, Deserialize)]
    struct AlertsResponse {
        features: Vec<AlertFeature>,
    }

    #[derive(Debug, Deserialize)]
    struct AlertFeature {
        properties: AlertProperties,
    }

    #[derive(Debug, Deserialize)]
    struct AlertProperties {
        event: Option<String>,
        #[serde(rename = "areaDesc")]
        area_desc: Option<String>,
        severity: Option<String>,
        description: Option<String>,
        instruction: Option<String>,
    }

    #[derive(Debug, Deserialize)]
    struct PointsResponse {
        properties: PointsProperties,
    }

    #[derive(Debug, Deserialize)]
    struct PointsProperties {
        forecast: String,
    }

    #[derive(Debug, Deserialize)]
    struct ForecastResponse {
        properties: ForecastProperties,
    }

    #[derive(Debug, Deserialize)]
    struct ForecastProperties {
        periods: Vec<ForecastPeriod>,
    }

    #[derive(Debug, Deserialize)]
    struct ForecastPeriod {
        name: String,
        temperature: i32,
        #[serde(rename = "temperatureUnit")]
        temperature_unit: String,
        #[serde(rename = "windSpeed")]
        wind_speed: String,
        #[serde(rename = "windDirection")]
        wind_direction: String,
        #[serde(rename = "detailedForecast")]
        detailed_forecast: String,
    }
    ```

    现在定义 MCP 客户端将发送的请求类型：

    ```rust theme={null}
    #[derive(serde::Deserialize, schemars::JsonSchema)]
    pub struct MCPForecastRequest {
        latitude: f32,
        longitude: f32,
    }

    #[derive(serde::Deserialize, schemars::JsonSchema)]
    pub struct MCPAlertRequest {
        state: String,
    }
    ```

    ### 辅助函数

    添加用于发起 API 请求并格式化响应的辅助函数：

    ```rust theme={null}
    async fn make_nws_request<T: DeserializeOwned>(url: &str) -> Result<T> {
        let client = reqwest::Client::new();
        let rsp = client
            .get(url)
            .header(reqwest::header::USER_AGENT, USER_AGENT)
            .header(reqwest::header::ACCEPT, "application/geo+json")
            .send()
            .await?
            .error_for_status()?;
        Ok(rsp.json::<T>().await?)
    }

    fn format_alert(feature: &AlertFeature) -> String {
        let props = &feature.properties;
        format!(
            "Event: {}\nArea: {}\nSeverity: {}\nDescription: {}\nInstructions: {}",
            props.event.as_deref().unwrap_or("Unknown"),
            props.area_desc.as_deref().unwrap_or("Unknown"),
            props.severity.as_deref().unwrap_or("Unknown"),
            props
                .description
                .as_deref()
                .unwrap_or("No description available"),
            props
                .instruction
                .as_deref()
                .unwrap_or("No specific instructions provided")
        )
    }

    fn format_period(period: &ForecastPeriod) -> String {
        format!(
            "{}:\nTemperature: {}°{}\nWind: {} {}\nForecast: {}",
            period.name,
            period.temperature,
            period.temperature_unit,
            period.wind_speed,
            period.wind_direction,
            period.detailed_forecast
        )
    }
    ```

    ### 实现 Weather 服务器与工具

    现在让我们实现带工具处理器的主 Weather 服务器结构：

    ```rust theme={null}
    pub struct Weather {
        tool_router: ToolRouter<Weather>,
    }

    #[tool_router]
    impl Weather {
        fn new() -> Self {
            Self {
                tool_router: Self::tool_router(),
            }
        }

        #[tool(description = "获取某个美国州的天气警报。")]
        async fn get_alerts(
            &self,
            Parameters(MCPAlertRequest { state }): Parameters<MCPAlertRequest>,
        ) -> String {
            let url = format!(
                "{}/alerts/active/area/{}",
                NWS_API_BASE,
                state.to_uppercase()
            );

            match make_nws_request::<AlertsResponse>(&url).await {
                Ok(data) => {
                    if data.features.is_empty() {
                        "该州没有正在生效的警报。".to_string()
                    } else {
                        data.features
                            .iter()
                            .map(format_alert)
                            .collect::<Vec<_>>()
                            .join("\n---\n")
                    }
                }
                Err(_) => "无法获取警报，或未找到任何警报。".to_string(),
            }
        }

        #[tool(description = "获取某个位置的天气预报。")]
        async fn get_forecast(
            &self,
            Parameters(MCPForecastRequest {
                latitude,
                longitude,
            }): Parameters<MCPForecastRequest>,
        ) -> String {
            let points_url = format!("{NWS_API_BASE}/points/{latitude},{longitude}");
            let Ok(points_data) = make_nws_request::<PointsResponse>(&points_url).await else {
                return "无法获取该位置的预报数据。".to_string();
            };

            let forecast_url = points_data.properties.forecast;

            let Ok(forecast_data) = make_nws_request::<ForecastResponse>(&forecast_url).await else {
                return "无法获取该位置的详细预报数据。".to_string();
            };

            let periods = &forecast_data.properties.periods;
            let forecast_summary: String = periods
                .iter()
                .take(5) // 仅显示接下来的 5 个时间段
                .map(format_period)
                .collect::<Vec<String>>()
                .join("\n---\n");
            forecast_summary
        }
    }
    ```

    `#[tool_router]` 宏会自动生成路由逻辑，而 `#[tool]` 属性会把方法标记为 MCP 工具。

    ### 实现 ServerHandler

    实现 `ServerHandler` trait 来定义服务器能力：

    ```rust theme={null}
    #[tool_handler]
    impl ServerHandler for Weather {
        fn get_info(&self) -> ServerInfo {
            ServerInfo {
                capabilities: ServerCapabilities::builder().enable_tools().build(),
                ..Default::default()
            }
        }
    }
    ```

    ### 运行服务器

    最后，使用 stdio 传输来实现运行服务器的 main 函数：

    ```rust theme={null}
    #[tokio::main]
    async fn main() -> Result<()> {
        let transport = (tokio::io::stdin(), tokio::io::stdout());
        let service = Weather::new().serve(transport).await?;
        service.waiting().await?;
        Ok(())
    }
    ```

    使用以下命令构建服务器：

    ```bash theme={null}
    cargo build --release
    ```

    编译后的二进制文件会位于 `target/release/weather`。

    现在让我们使用现有的 MCP 主机——Claude for Desktop 来测试你的服务器。

    ## 使用 Claude for Desktop 测试你的服务器

    <Note>
      Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 [构建客户端](/docs/develop/build-client) 教程，以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
    </Note>

    首先，确保你已安装 Claude for Desktop。
    [你可以在这里安装最新版本。](https://claude.ai/download)
    如果你已经安装了 Claude for Desktop，**请确认已更新到最新版本。**

    我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
    为此，在文本编辑器中打开 Claude for Desktop 应用配置文件：`~/Library/Application Support/Claude/claude_desktop_config.json`。
    如果文件不存在，请先创建它。

    例如，如果你安装了 [VS Code](https://code.visualstudio.com/)：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      code ~/Library/Application\ Support/Claude/claude_desktop_config.json
      ```

      ```powershell Windows theme={null}
      code $env:AppData\Claude\claude_desktop_config.json
      ```
    </CodeGroup>

    然后在 `mcpServers` 键中添加你的服务器。
    只有当至少有一个服务器被正确配置时，MCP UI 元素才会显示在 Claude for Desktop 中。

    在这种情况下，我们将按下面方式添加我们的单个天气服务器：

    <CodeGroup>
      ```json macOS/Linux theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
          }
        }
      }
      ```

      ```json Windows theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\target\\release\\weather.exe"
          }
        }
      }
      ```
    </CodeGroup>

    <Note>
      请确保传入你编译后的二进制文件的绝对路径。
      你可以在 macOS/Linux 上运行 `pwd`，或在 Windows 的命令提示符中从你的项目目录运行 `cd`。
      在 Windows 中，请记得在 JSON 路径里使用双反斜杠（`\\`）或正斜杠（`/`），并添加 `.exe` 扩展名。
    </Note>

    这会告诉 Claude for Desktop：

    1. 有一个名为“weather”的 MCP 服务器
    2. 通过在指定路径运行编译后的二进制文件来启动它

    保存文件，然后重启 **Claude for Desktop**。
  </Tab>

  <Tab title="Go">
    让我们开始构建我们的天气服务器吧！
    [你可以在这里找到我们将要构建的完整代码。](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-go)

    ### 先决知识

    本快速入门假设你已经熟悉：

    * Go
    * 像 Claude 这样的 LLM

    ### 在 MCP 服务器中记录日志

    在实现 MCP 服务器时，请小心处理日志输出方式：

    **基于 STDIO 的服务器：** 永远不要使用 `fmt.Println()` 或 `fmt.Printf()`，因为它们会写入标准输出（stdout）。
    向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。

    **基于 HTTP 的服务器：** 标准输出日志是没问题的，因为它不会干扰 HTTP 响应。

    ### 最佳实践

    * 使用 `log.Println()`（默认写入 stderr），或者使用写入 stderr 或文件的日志库。
    * 使用 `fmt.Fprintf(os.Stderr, ...)` 来明确写入 stderr。

    ### 快速示例

    ```go theme={null}
    // ❌ 不好（STDIO）
    fmt.Println("Processing request")

    // ✅ 好（STDIO）
    log.Println("Processing request") // 默认写入 stderr

    // ✅ 好（STDIO）
    fmt.Fprintln(os.Stderr, "Processing request")
    ```

    ### 系统要求

    * 已安装 Go 1.24 或更高版本。

    ### 配置你的环境

    首先，如果你还没有安装 Go，请先安装它。
    你可以从 [go.dev](https://go.dev/dl/) 下载并安装 Go。

    验证你的 Go 安装：

    ```bash theme={null}
    go version
    ```

    现在，让我们创建并设置我们的项目：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      # 为我们的项目创建一个新目录
      mkdir weather
      cd weather

      # 初始化 Go 模块
      go mod init weather

      # 安装依赖
      go get github.com/modelcontextprotocol/go-sdk/mcp

      # 创建我们的服务器文件
      touch main.go
      ```

      ```powershell Windows theme={null}
      # 为我们的项目创建一个新目录
      md weather
      cd weather

      # 初始化 Go 模块
      go mod init weather

      # 安装依赖
      go get github.com/modelcontextprotocol/go-sdk/mcp

      # 创建我们的服务器文件
      new-item main.go
      ```
    </CodeGroup>

    现在让我们进入构建你的服务器。

    ## 构建你的服务器

    ### 导入包并设置常量

    将以下内容添加到你的 `main.go` 文件顶部：

    ```go theme={null}
    package main

    import (
    	"cmp"
    	"context"
    	"encoding/json"
    	"fmt"
    	"io"
    	"log"
    	"net/http"
    	"strings"

    	"github.com/modelcontextprotocol/go-sdk/mcp"
    )

    const (
    	NWSAPIBase = "https://api.weather.gov"
    	UserAgent  = "weather-app/1.0"
    )
    ```

    ### 数据结构

    接下来，定义我们的工具所使用的数据结构：

    ```go theme={null}
    type PointsResponse struct {
    	Properties struct {
    		Forecast string `json:"forecast"`
    	} `json:"properties"`
    }

    type ForecastResponse struct {
    	Properties struct {
    		Periods []ForecastPeriod `json:"periods"`
    	} `json:"properties"`
    }

    type ForecastPeriod struct {
    	Name             string `json:"name"`
    	Temperature      int    `json:"temperature"`
    	TemperatureUnit  string `json:"temperatureUnit"`
    	WindSpeed        string `json:"windSpeed"`
    	WindDirection    string `json:"windDirection"`
    	DetailedForecast string `json:"detailedForecast"`
    }

    type AlertsResponse struct {
    	Features []AlertFeature `json:"features"`
    }

    type AlertFeature struct {
    	Properties AlertProperties `json:"properties"`
    }

    type AlertProperties struct {
    	Event       string `json:"event"`
    	AreaDesc    string `json:"areaDesc"`
    	Severity    string `json:"severity"`
    	Description string `json:"description"`
    	Instruction string `json:"instruction"`
    }

    type ForecastInput struct {
    	Latitude  float64 `json:"latitude" jsonschema:"Latitude of the location"`
    	Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"`
    }

    type AlertsInput struct {
    	State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"`
    }
    ```

    ### 辅助函数

    接下来，让我们添加用于查询并格式化来自国家气象服务 API 数据的辅助函数：

    ```go theme={null}
    func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) {
    	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    	if err != nil {
    		return nil, fmt.Errorf("failed to create request: %w", err)
    	}

    	req.Header.Set("User-Agent", UserAgent)
    	req.Header.Set("Accept", "application/geo+json")

    	client := http.DefaultClient
    	resp, err := client.Do(req)
    	if err != nil {
    		return nil, fmt.Errorf("failed to make request to %s: %w", url, err)
    	}
    	defer resp.Body.Close()

    	if resp.StatusCode != http.StatusOK {
    		body, _ := io.ReadAll(resp.Body)
    		return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
    	}

    	var result T
    	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
    		return nil, fmt.Errorf("failed to decode response: %w", err)
    	}

    	return &result, nil
    }

    func formatAlert(alert AlertFeature) string {
    	props := alert.Properties
    	event := cmp.Or(props.Event, "Unknown")
    	areaDesc := cmp.Or(props.AreaDesc, "Unknown")
    	severity := cmp.Or(props.Severity, "Unknown")
    	description := cmp.Or(props.Description, "No description available")
    	instruction := cmp.Or(props.Instruction, "No specific instructions provided")

    	return fmt.Sprintf(`
    Event: %s
    Area: %s
    Severity: %s
    Description: %s
    Instructions: %s
    `, event, areaDesc, severity, description, instruction)
    }

    func formatPeriod(period ForecastPeriod) string {
    	return fmt.Sprintf(`
    %s:
    Temperature: %d°%s
    Wind: %s %s
    Forecast: %s
    `, period.Name, period.Temperature, period.TemperatureUnit,
    		period.WindSpeed, period.WindDirection, period.DetailedForecast)
    }
    ```

    ### 实现工具执行

    工具执行处理器负责实际执行每个工具的逻辑。让我们添加它：

    ```go theme={null}
    func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) (
    	*mcp.CallToolResult, any, error,
    ) {
    	// 获取 points 数据
    	pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude)
    	pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL)
    	if err != nil {
    		return &mcp.CallToolResult{
    			Content: []mcp.Content{
    				&mcp.TextContent{Text: "Unable to fetch forecast data for this location."},
    			},
    		}, nil, nil
    	}

    	// 获取预报数据
    	forecastURL := pointsData.Properties.Forecast
    	if forecastURL == "" {
    		return &mcp.CallToolResult{
    			Content: []mcp.Content{
    				&mcp.TextContent{Text: "Unable to fetch forecast URL."},
    			},
    		}, nil, nil
    	}

    	forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL)
    	if err != nil {
    		return &mcp.CallToolResult{
    			Content: []mcp.Content{
    				&mcp.TextContent{Text: "Unable to fetch detailed forecast."},
    			},
    		}, nil, nil
    	}

    	// 格式化时间段
    	periods := forecastData.Properties.Periods
    	if len(periods) == 0 {
    		return &mcp.CallToolResult{
    			Content: []mcp.Content{
    				&mcp.TextContent{Text: "No forecast periods available."},
    			},
    		}, nil, nil
    	}

    	// 显示接下来的 5 个时间段
    	var forecasts []string
    	for i := range min(5, len(periods)) {
    		forecasts = append(forecasts, formatPeriod(periods[i]))
    	}

    	result := strings.Join(forecasts, "\n---\n")

    	return &mcp.CallToolResult{
    		Content: []mcp.Content{
    			&mcp.TextContent{Text: result},
    		},
    	}, nil, nil
    }

    func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) (
    	*mcp.CallToolResult, any, error,
    ) {
    	// 构建警报 URL
    	stateCode := strings.ToUpper(input.State)
    	alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode)

    	alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL)
    	if err != nil {
    		return &mcp.CallToolResult{
    			Content: []mcp.Content{
    				&mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."},
    			},
    		}, nil, nil
    	}

    	// 检查是否有任何警报
    	if len(alertsData.Features) == 0 {
    		return &mcp.CallToolResult{
    			Content: []mcp.Content{
    				&mcp.TextContent{Text: "No active alerts for this state."},
    			},
    		}, nil, nil
    	}

    	// 格式化警报
    	var alerts []string
    	for _, feature := range alertsData.Features {
    		alerts = append(alerts, formatAlert(feature))
    	}

    	result := strings.Join(alerts, "\n---\n")

    	return &mcp.CallToolResult{
    		Content: []mcp.Content{
    			&mcp.TextContent{Text: result},
    		},
    	}, nil, nil
    }
    ```

    ### 运行服务器

    最后，实现 main 函数来运行服务器：

    ```go theme={null}
    func main() {
    	// 创建 MCP 服务器
    	server := mcp.NewServer(&mcp.Implementation{
    		Name:    "weather",
    		Version: "1.0.0",
    	}, nil)

    	// 添加 get_forecast 工具
    	mcp.AddTool(server, &mcp.Tool{
    		Name:        "get_forecast",
    		Description: "获取某个位置的天气预报",
    	}, getForecast)

    	// 添加 get_alerts 工具
    	mcp.AddTool(server, &mcp.Tool{
    		Name:        "get_alerts",
    		Description: "获取某个美国州的天气警报",
    	}, getAlerts)

    	// 在 stdio 传输上运行服务器
    	if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
    		log.Fatal(err)
    	}
    }
    ```

    使用以下命令构建你的服务器：

    ```bash theme={null}
    go build -o weather .
    ```

    编译后的二进制文件会位于 `./weather`。

    现在让我们从现有的 MCP 主机——Claude for Desktop 测试你的服务器。

    ## 使用 Claude for Desktop 测试你的服务器

    <Note>
      Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 [构建客户端](/docs/develop/build-client) 教程，以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
    </Note>

    首先，确保你已安装 Claude for Desktop。
    [你可以在这里安装最新版本。](https://claude.ai/download)
    如果你已经安装了 Claude for Desktop，**请确认已更新到最新版本。**

    我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
    为此，在文本编辑器中打开 Claude for Desktop 应用配置文件：`~/Library/Application Support/Claude/claude_desktop_config.json`。
    如果文件不存在，请先创建它。

    例如，如果你安装了 [VS Code](https://code.visualstudio.com/)：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      code ~/Library/Application\ Support/Claude/claude_desktop_config.json
      ```

      ```powershell Windows theme={null}
      code $env:AppData\Claude\claude_desktop_config.json
      ```
    </CodeGroup>

    然后在 `mcpServers` 键中添加你的服务器。
    只有当至少有一个服务器被正确配置时，MCP UI 元素才会显示在 Claude for Desktop 中。

    在这种情况下，我们将按下面方式添加我们的单个天气服务器：

    <CodeGroup>
      ```json macOS/Linux theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
          }
        }
      }
      ```

      ```json Windows theme={null}
      {
        "mcpServers": {
          "weather": {
            "command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.exe"
          }
        }
      }
      ```
    </CodeGroup>

    <Note>
      请确保传入你编译后的二进制文件的绝对路径。
      你可以在 macOS/Linux 上运行 `pwd`，或在 Windows 的命令提示符中从你的项目目录运行 `cd` 获取它。
      在 Windows 中，请记得在 JSON 路径里使用双反斜杠（`\\`）或正斜杠（`/`），并添加 `.exe` 扩展名。
    </Note>

    这会告诉 Claude for Desktop：

    1. 有一个名为“weather”的 MCP 服务器
    2. 通过运行指定路径下的编译二进制文件来启动它

    保存文件，然后重启 **Claude for Desktop**。
  </Tab>
</Tabs>

### 使用命令进行测试

让我们确保 Desktop 版的 Claude 能够正确识别我们在 `weather` 服务器中暴露的两个工具。你可以通过查看“添加文件、连接器和更多 /” <img src="https://mintcdn.com/mcp-zhcndoc/e93uqR4nmQj7tWn4/images/claude-add-files-connectors-and-more.png?fit=max&auto=format&n=e93uqR4nmQj7tWn4&q=85&s=61fd54edc65796d95919f62caa784556" style={{display: 'inline', margin: 0, height: '1.3em'}} width="33" height="33" data-path="images/claude-add-files-connectors-and-more.png" /> 图标来确认：

<Frame>
  <img src="https://mintcdn.com/mcp-zhcndoc/e93uqR4nmQj7tWn4/images/visual-indicator-mcp-tools.png?fit=max&auto=format&n=e93uqR4nmQj7tWn4&q=85&s=87a6554ed49cc8bebede94ba94b7b5c8" width="684" height="133" data-path="images/visual-indicator-mcp-tools.png" />
</Frame>

点击加号图标后，将鼠标悬停在“连接器（Connectors）”菜单上。你应该会看到列出的 `weather` 服务器：

<Frame>
  <img src="https://mintcdn.com/mcp-zhcndoc/e93uqR4nmQj7tWn4/images/available-mcp-tools.png?fit=max&auto=format&n=e93uqR4nmQj7tWn4&q=85&s=47ebabc3112958bf1ff8c4821b7e49ea" width="437" height="244" data-path="images/available-mcp-tools.png" />
</Frame>

如果你的服务器没有被 Desktop 版的 Claude 识别，请继续查看 [故障排查（Troubleshooting）](#troubleshooting) 部分以获取调试建议。

如果你的服务器已经出现在“连接器（Connectors）”菜单中，你现在可以通过在 Desktop 版的 Claude 中运行以下命令来测试你的服务器：

* 萨克拉门托（Sacramento）的天气怎么样？
* 德克萨斯（Texas）有哪些生效的天气警报（active weather alerts）？

<Frame>
  <img src="https://mintcdn.com/mcp-zhcndoc/e93uqR4nmQj7tWn4/images/current-weather.png?fit=max&auto=format&n=e93uqR4nmQj7tWn4&q=85&s=71cfa944e77a0303f630ab432dd4dc9b" width="2780" height="1849" data-path="images/current-weather.png" />
</Frame>

<Frame>
  <img src="https://mintcdn.com/mcp-zhcndoc/e93uqR4nmQj7tWn4/images/weather-alerts.png?fit=max&auto=format&n=e93uqR4nmQj7tWn4&q=85&s=90655758ea68652b824ad947564b0f35" width="2809" height="1850" data-path="images/weather-alerts.png" />
</Frame>

<Note>
  由于这是美国国家气象服务（National Weather service），因此这些查询只对美国境内的位置有效。
</Note>

## 后台发生了什么

当你提出问题时：

1. 客户端将你的问题发送给 Claude
2. Claude 分析可用工具，并决定使用哪一个或哪些工具
3. 客户端通过 MCP 服务器执行所选工具
4. 将结果发送回 Claude
5. Claude 生成自然语言回复
6. 回复会显示给你！

## 故障排查（Troubleshooting）

<AccordionGroup>
  <Accordion title="Claude for Desktop 集成问题">
    **从 Claude for Desktop 获取日志**

    与 MCP 相关的 Claude.app 日志会写入 `~/Library/Logs/Claude` 目录中的日志文件：

    * `mcp.log` 将包含关于 MCP 连接和连接失败的常规日志。
    * 名称为 `mcp-server-SERVERNAME.log` 的文件将包含来自指定服务器的错误（stderr）日志。

    你可以运行以下命令来列出最近的日志，并持续跟踪任何新日志：

    ```bash theme={null}
    # 检查 Claude 的日志以查看错误
    tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
    ```

    **服务器未在 Claude 中显示**

    1. 检查你的 `claude_desktop_config.json` 文件语法
    2. 确保你的项目路径是绝对路径，而不是相对路径
    3. 完全重启 Desktop 版的 Claude

    <Warning>
      要正确重启 Desktop 版的 Claude，你必须彻底退出应用程序：

      * **Windows**：在系统托盘中（可能在“隐藏图标（hidden icons）”菜单里）右键单击 Claude 图标，然后选择“退出（Quit）”或“离开（Exit）”。
      * **macOS**：使用 Cmd+Q，或在菜单栏中选择“退出 Claude（Quit Claude）”。

      仅仅关闭窗口并不会完全退出应用程序，而你的 MCP 服务器配置更改也不会生效。
    </Warning>

    **工具调用静默失败**

    如果 Claude 尝试使用这些工具但失败了：

    1. 查看 Claude 的日志以查找错误
    2. 验证你的服务器构建和运行没有错误
    3. 尝试重启 Desktop 版的 Claude

    **都没有效果。该怎么办？**

    请参考我们的 [调试指南](/docs/tools/debugging) 以获得更好的调试工具和更详细的指导。
  </Accordion>

  <Accordion title="天气 API 问题">
    **错误：无法获取网格点数据（Failed to retrieve grid point data）**

    这通常意味着以下情况之一：

    1. 坐标超出了美国范围
    2. NWS API 出现了问题
    3. 你被限流了（rate limited）

    修复方法：

    * 确认你使用的是美国坐标
    * 在请求之间加入一点小延迟
    * 查看 NWS API 状态页面

    **错误：\[STATE] 没有活动警报（No active alerts for \[STATE]）**

    这不是错误——只是表示该州目前没有天气警报。尝试换一个州，或在严重天气发生时再查看。
  </Accordion>
</AccordionGroup>

<Note>
  如需更高级的故障排查，请查看我们关于 [调试 MCP](/docs/tools/debugging) 的指南
</Note>

## 下一步

<CardGroup cols={2}>
  <Card title="构建客户端（Building a client）" icon="outlet" href="/docs/develop/build-client">
    了解如何构建你自己的 MCP 客户端，以连接到你的服务器
  </Card>

  <Card title="示例服务器（Example servers）" icon="grid" href="/examples">
    查看我们官方 MCP 服务器和实现的画廊
  </Card>

  <Card title="调试指南（Debugging Guide）" icon="bug" href="/docs/tools/debugging">
    学习如何有效调试 MCP 服务器和集成
  </Card>

  <Card title="使用 Agent Skills 构建（Build with Agent Skills）" icon="comments" href="/docs/develop/build-with-agent-skills">
    使用 agent skills 引导 AI 代码助手完成服务器设计
  </Card>
</CardGroup>
