服务器开发

在本教程中,我们将构建一个简单的MCP天气服务器并将其连接到宿主应用程序Claude桌面版。我们将从基本设置开始,然后逐步进行到更复杂的用例。

我们将构建什么

许多大语言模型(包括Claude)目前没有获取天气预报和严重天气警报的能力。让我们使用MCP来解决这个问题!

我们将构建一个服务器,提供两个工具:get-alertsget-forecast。然后我们将服务器连接到MCP宿主应用程序(在本例中是Claude桌面版):

ℹ️
服务器可以连接到任何客户端。我们在这里选择Claude桌面版是为了简单起见,但我们也有关于构建自己的客户端的指南以及其他客户端列表
为什么是Claude桌面版而不是Claude.ai?
因为服务器是本地运行的,MCP目前只支持桌面宿主应用程序。远程宿主应用程序正在积极开发中。

MCP核心概念

MCP服务器可以提供三种主要类型的功能:

  1. 资源:类似文件的数据,可以被客户端读取(如API响应或文件内容)
  2. 工具:可以被大语言模型调用的函数(需要用户批准)
  3. 提示:预先编写的模板,帮助用户完成特定任务

本教程将主要关注工具。

让我们开始构建我们的天气服务器!您可以在这里找到我们将要构建的完整代码。

前提知识

本快速入门假设您熟悉:

  • Python
  • 像Claude这样的大语言模型

系统要求

  • 安装Python 3.10或更高版本。
  • 您必须使用Python MCP SDK 1.2.0或更高版本。

设置您的环境

首先,让我们安装uv并设置我们的Python项目和环境:

安装 uv
curl -LsSf https://astral.sh/uv/install.sh | sh
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

安装后请确保重启您的终端,以确保uv命令被正确识别。

现在,让我们创建并设置我们的项目:

创建项目
# 为我们的项目创建一个新目录
uv init weather
cd weather

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

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

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

现在让我们开始构建您的服务器。

构建您的服务器

导入包并设置实例

将这些添加到您的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"

FastMCP类使用Python类型提示和文档字符串自动生成工具定义,使创建和维护MCP工具变得简单。

辅助函数

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

async def make_nws_request(url: str) -> dict[str, Any] | None:
    """向NWS API发出GET请求,处理错误并返回JSON响应"""
    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:
    """将警报特征格式化为可读字符串。"""
    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')}
"""

实现工具执行

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

@mcp.tool()
async def get_alerts(state: str) -> str:
    """获取指定州的天气警报(使用两字母州代码如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:
    """获取位置的天气预报。

    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 "无法为此位置获取预报数据。"

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

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

    # 将时段格式化为可读预报
    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)

运行服务器

最后,让我们初始化并运行服务器:

if __name__ == "__main__":
    # 初始化并运行服务器
    mcp.run(transport='stdio')

您的服务器已完成!运行uv run weather.py以确认一切正常工作。

现在让我们使用现有的MCP宿主应用程序Claude桌面版测试您的服务器。

使用Claude桌面版测试您的服务器

⚠️
Claude桌面版尚未在Linux上可用。Linux用户可以通过构建客户端教程创建自定义客户端。

首先,确保您已安装Claude桌面版。您可以在这里安装最新版本。 如果您已经安装了Claude桌面版,请确保它已更新到最新版本。

我们需要为您想要使用的任何MCP服务器配置Claude桌面版。为此,请在文本编辑器中打开您的Claude桌面版应用程序配置文件,位于~/Library/Application Support/Claude/claude_desktop_config.json。如果该文件不存在,请创建它。

例如,如果您已安装VS Code

打开配置文件
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code %APPDATA%\Claude\claude_desktop_config.json

然后您将在mcpServers键中添加您的服务器。只有在至少一个服务器正确配置的情况下,MCP UI元素才会在Claude桌面版中显示。

在这种情况下,我们将添加我们的单个天气服务器,如下所示:

配置Claude桌面版
{
    "mcpServers": {
        "weather": {
            "command": "uv",
            "args": [
                "--directory",
                "ABSOLUTE_PATH_PLACEHOLDER",
                "run",
                "weather.py"
            ]
        }
    }
}
{
    "mcpServers": {
        "weather": {
            "command": "uv",
            "args": [
                "--directory",
                "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
                "run",
                "weather.py"
            ]
        }
    }
}
ℹ️
您可能需要在command字段中放入uv可执行文件的完整路径。您可以通过在MacOS/Linux上运行which uv或在Windows上运行where uv来获取此路径。
⚠️
确保您传入服务器的绝对路径。

这告诉Claude桌面版:

  1. 有一个名为"weather"的MCP服务器
  2. 使用uv命令运行该服务器
  3. 在指定目录中运行服务器
  4. 使用weather.py作为入口点

现在,重启Claude桌面版。您应该会在工具栏中看到一个新的MCP图标。点击它,您将看到我们的天气工具!

下一步

恭喜!您已经成功构建了您的第一个MCP服务器。这只是开始 - 您可以:

让我们开始构建我们的天气服务器!您可以在这里找到我们将要构建的完整代码。

前提知识

本快速入门假设您熟悉:

  • Node.js
  • 像Claude这样的大语言模型

系统要求

  • 安装Node.js 18或更高版本
  • 您必须使用Node.js MCP SDK 1.2.0或更高版本

设置您的环境

首先,让我们创建一个新的Node.js项目:

创建项目
# 创建一个新目录
mkdir weather
cd weather

# 初始化项目
npm init -y

# 安装依赖
npm install @mcp/node node-fetch

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

现在让我们开始构建您的服务器。

构建您的服务器

导入包并设置实例

将这些添加到您的weather.js顶部:

const { MCPServer } = require('@mcp/node');
const fetch = require('node-fetch');

// 初始化MCP服务器
const mcp = new MCPServer('weather');

// 常量
const NWS_API_BASE = 'https://api.weather.gov';
const USER_AGENT = 'weather-app/1.0';

辅助函数

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

async function makeNWSRequest(url) {
    const headers = {
        'User-Agent': USER_AGENT,
        'Accept': 'application/geo+json'
    };
    try {
        const response = await fetch(url, { headers });
        if (!response.ok) throw new Error('API request failed');
        return await response.json();
    } catch (error) {
        return null;
    }
}

function formatAlert(feature) {
    const props = feature.properties;
    return `
Event: ${props.event || 'Unknown'}
Area: ${props.areaDesc || 'Unknown'}
Severity: ${props.severity || 'Unknown'}
Description: ${props.description || 'No description available'}
Instructions: ${props.instruction || 'No specific instructions provided'}
`;
}

实现工具执行

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

// 获取警报工具
mcp.tool({
    name: 'get-alerts',
    description: '获取美国州的天气警报',
    parameters: {
        state: {
            type: 'string',
            description: '两字母美国州代码(例如CA、NY)'
        }
    },
    async execute({ state }) {
        const url = `${NWS_API_BASE}/alerts/active/area/${state}`;
        const data = await makeNWSRequest(url);

        if (!data || !data.features) {
            return '无法获取警报或未找到警报。';
        }

        if (data.features.length === 0) {
            return '该州没有活动警报。';
        }

        const alerts = data.features.map(formatAlert);
        return alerts.join('\n---\n');
    }
});

// 获取预报工具
mcp.tool({
    name: 'get-forecast',
    description: '获取位置的天气预报',
    parameters: {
        latitude: {
            type: 'number',
            description: '位置的纬度'
        },
        longitude: {
            type: 'number',
            description: '位置的经度'
        }
    },
    async execute({ latitude, longitude }) {
        // 首先获取预报网格端点
        const pointsUrl = `${NWS_API_BASE}/points/${latitude},${longitude}`;
        const pointsData = await makeNWSRequest(pointsUrl);

        if (!pointsData) {
            return '无法为此位置获取预报数据。';
        }

        // 从点响应中获取预报URL
        const forecastUrl = pointsData.properties.forecast;
        const forecastData = await makeNWSRequest(forecastUrl);

        if (!forecastData) {
            return '无法获取详细预报。';
        }

        // 将时段格式化为可读预报
        const periods = forecastData.properties.periods;
        const forecasts = periods.slice(0, 5).map(period => `
${period.name}:
Temperature: ${period.temperature}°${period.temperatureUnit}
Wind: ${period.windSpeed} ${period.windDirection}
Forecast: ${period.detailedForecast}
`);

        return forecasts.join('\n---\n');
    }
});

运行服务器

最后,让我们初始化并运行服务器:

// 初始化并运行服务器
mcp.start();

您的服务器已完成!运行node weather.js以确认一切正常工作。

现在让我们使用现有的MCP宿主应用程序Claude桌面版测试您的服务器。

使用Claude桌面版测试您的服务器

⚠️
Claude桌面版尚未在Linux上可用。Linux用户可以继续学习构建客户端教程,以构建连接到我们刚刚构建的服务器的MCP客户端。

首先,确保您已安装Claude桌面版。您可以在这里安装最新版本。 如果您已经安装了Claude桌面版,请确保它已更新到最新版本。

我们需要为您想要使用的任何MCP服务器配置Claude桌面版。为此,请在文本编辑器中打开您的Claude桌面版应用程序配置文件,位于~/Library/Application Support/Claude/claude_desktop_config.json。如果该文件不存在,请创建它。

例如,如果您已安装VS Code

打开配置文件
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\Claude\claude_desktop_config.json

然后您将在mcpServers键中添加您的服务器。只有在至少一个服务器正确配置的情况下,MCP UI元素才会在Claude桌面版中显示。

在这种情况下,我们将添加我们的单个天气服务器,如下所示:

配置Claude桌面版
{
    "mcpServers": {
        "weather": {
            "command": "node",
            "args": [
                "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather.js"
            ]
        }
    }
}
{
    "mcpServers": {
        "weather": {
            "command": "node",
            "args": [
                "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.js"
            ]
        }
    }
}
⚠️
确保您传入服务器的绝对路径。

这告诉Claude桌面版:

  1. 有一个名为"weather"的MCP服务器
  2. 使用node命令运行该服务器
  3. 使用weather.js作为入口点

现在,重启Claude桌面版。您应该会在工具栏中看到一个新的MCP图标。点击它,您将看到我们的天气工具!

下一步

恭喜!您已经成功构建了您的第一个MCP服务器。这只是开始 - 您可以:

让我们开始构建我们的天气服务器!您可以在这里找到我们将要构建的完整代码。

前提知识

本快速入门假设您熟悉:

  • Java
  • 像Claude这样的大语言模型

系统要求

  • 安装Java 17或更高版本
  • 您必须使用Java MCP SDK 1.2.0或更高版本

设置您的环境

首先,让我们创建一个新的Maven项目。在您喜欢的IDE中创建一个新的Maven项目,或者使用以下命令:

创建项目
mvn archetype:generate \
    -DgroupId=com.example \
    -DartifactId=weather \
    -DarchetypeArtifactId=maven-archetype-quickstart \
    -DinteractiveMode=false

然后,将以下依赖项添加到您的pom.xml文件中:

<dependencies>
    <dependency>
        <groupId>dev.mcp</groupId>
        <artifactId>mcp-java</artifactId>
        <version>1.2.0</version>
    </dependency>
</dependencies>

现在让我们开始构建您的服务器。

构建您的服务器

创建主类

src/main/java/com/example目录中创建一个新的Weather.java文件:

package com.example;

import dev.mcp.MCPServer;
import dev.mcp.Tool;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Weather {
    private static final String NWS_API_BASE = "https://api.weather.gov";
    private static final String USER_AGENT = "weather-app/1.0";
    private static final HttpClient client = HttpClient.newHttpClient();
    private static final ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) {
        MCPServer mcp = new MCPServer("weather");
        setupTools(mcp);
        mcp.start();
    }

    private static void setupTools(MCPServer mcp) {
        // 工具设置代码将在这里
    }
}

辅助方法

Weather类中添加以下辅助方法:

private static Map<String, Object> makeNWSRequest(String url) {
    try {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("User-Agent", USER_AGENT)
            .header("Accept", "application/geo+json")
            .build();

        HttpResponse<String> response = client.send(request, 
            HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 200) {
            return null;
        }

        return mapper.readValue(response.body(), Map.class);
    } catch (Exception e) {
        return null;
    }
}

private static String formatAlert(Map<String, Object> feature) {
    Map<String, Object> props = (Map<String, Object>) feature.get("properties");
    return String.format("""
        Event: %s
        Area: %s
        Severity: %s
        Description: %s
        Instructions: %s
        """,
        props.getOrDefault("event", "Unknown"),
        props.getOrDefault("areaDesc", "Unknown"),
        props.getOrDefault("severity", "Unknown"),
        props.getOrDefault("description", "No description available"),
        props.getOrDefault("instruction", "No specific instructions provided"));
}

实现工具执行

现在让我们在setupTools方法中实现我们的工具:

private static void setupTools(MCPServer mcp) {
    // 获取警报工具
    mcp.tool(new Tool.Builder()
        .name("get-alerts")
        .description("获取美国州的天气警报")
        .parameter("state", "string", "两字母美国州代码(例如CA、NY)")
        .execute(params -> {
            String state = params.get("state").toString();
            String url = String.format("%s/alerts/active/area/%s", NWS_API_BASE, state);
            Map<String, Object> data = makeNWSRequest(url);

            if (data == null || !data.containsKey("features")) {
                return "无法获取警报或未找到警报。";
            }

            List<Map<String, Object>> features = (List<Map<String, Object>>) data.get("features");
            if (features.isEmpty()) {
                return "该州没有活动警报。";
            }

            return features.stream()
                .map(Weather::formatAlert)
                .collect(Collectors.joining("\n---\n"));
        })
        .build());

    // 获取预报工具
    mcp.tool(new Tool.Builder()
        .name("get-forecast")
        .description("获取位置的天气预报")
        .parameter("latitude", "number", "位置的纬度")
        .parameter("longitude", "number", "位置的经度")
        .execute(params -> {
            double latitude = Double.parseDouble(params.get("latitude").toString());
            double longitude = Double.parseDouble(params.get("longitude").toString());

            // 首先获取预报网格端点
            String pointsUrl = String.format("%s/points/%f,%f", NWS_API_BASE, latitude, longitude);
            Map<String, Object> pointsData = makeNWSRequest(pointsUrl);

            if (pointsData == null) {
                return "无法为此位置获取预报数据。";
            }

            // 从点响应中获取预报URL
            Map<String, Object> properties = (Map<String, Object>) pointsData.get("properties");
            String forecastUrl = properties.get("forecast").toString();
            Map<String, Object> forecastData = makeNWSRequest(forecastUrl);

            if (forecastData == null) {
                return "无法获取详细预报。";
            }

            // 将时段格式化为可读预报
            Map<String, Object> forecastProps = (Map<String, Object>) forecastData.get("properties");
            List<Map<String, Object>> periods = (List<Map<String, Object>>) forecastProps.get("periods");
            
            return periods.stream()
                .limit(5)
                .map(period -> String.format("""
                    %s:
                    Temperature: %s°%s
                    Wind: %s %s
                    Forecast: %s
                    """,
                    period.get("name"),
                    period.get("temperature"),
                    period.get("temperatureUnit"),
                    period.get("windSpeed"),
                    period.get("windDirection"),
                    period.get("detailedForecast")))
                .collect(Collectors.joining("\n---\n"));
        })
        .build());
}

您的服务器已完成!运行mvn compile exec:java -Dexec.mainClass="com.example.Weather"以确认一切正常工作。

现在让我们使用现有的MCP宿主应用程序Claude桌面版测试您的服务器。

使用Claude桌面版测试您的服务器

⚠️
Claude桌面版尚未在Linux上可用。Linux用户可以继续学习构建客户端教程,以构建连接到我们刚刚构建的服务器的MCP客户端。

首先,确保您已安装Claude桌面版。您可以在这里安装最新版本。 如果您已经安装了Claude桌面版,请确保它已更新到最新版本。

我们需要为您想要使用的任何MCP服务器配置Claude桌面版。为此,请在文本编辑器中打开您的Claude桌面版应用程序配置文件,位于~/Library/Application Support/Claude/claude_desktop_config.json。如果该文件不存在,请创建它。

例如,如果您已安装VS Code

打开配置文件
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\Claude\claude_desktop_config.json

然后您将在mcpServers键中添加您的服务器。只有在至少一个服务器正确配置的情况下,MCP UI元素才会在Claude桌面版中显示。

在这种情况下,我们将添加我们的单个天气服务器,如下所示:

配置Claude桌面版
{
    "mcpServers": {
        "weather": {
            "command": "mvn",
            "args": [
                "compile",
                "exec:java",
                "-Dexec.mainClass=com.example.Weather",
                "-f",
                "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/pom.xml"
            ]
        }
    }
}
{
    "mcpServers": {
        "weather": {
            "command": "mvn",
            "args": [
                "compile",
                "exec:java",
                "-Dexec.mainClass=com.example.Weather",
                "-f",
                "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\pom.xml"
            ]
        }
    }
}
⚠️
确保您传入服务器的绝对路径。

这告诉Claude桌面版:

  1. 有一个名为"weather"的MCP服务器
  2. 使用mvn命令运行该服务器
  3. 在指定目录中运行服务器
  4. 使用Weather类作为入口点

现在,重启Claude桌面版。您应该会在工具栏中看到一个新的MCP图标。点击它,您将看到我们的天气工具!

下一步

恭喜!您已经成功构建了您的第一个MCP服务器。这只是开始 - 您可以:

测试命令

让我们确认Claude桌面端能正确识别天气服务中的两个工具。注意查看锤子图标

MCP工具图标

点击锤子图标后应该看到两个工具:

如果工具未显示,请参考故障排除章节。

成功显示工具后,可以在Claude桌面端测试以下命令:

  • 萨克拉门托的天气如何?
  • 德克萨斯州有哪些活跃的天气警报?
ℹ️
该服务使用美国国家气象局数据,仅支持美国地区查询。

底层工作原理

问题处理流程:

  1. 客户端将问题发送给Claude
  2. Claude分析可用工具并选择使用
  3. 通过MCP服务器执行工具
  4. 返回结果给Claude
  5. Claude生成自然语言响应
  6. 向用户展示最终结果

故障排除

Claude桌面端集成问题

获取桌面端日志

日志文件位于~/Library/Logs/Claude

  • mcp.log 包含MCP连接日志
  • mcp-server-SERVERNAME.log 包含指定服务器的错误日志

实时查看日志命令:

tail -n 20 -f ~/Library/Logs/Claude/mcp*.log

服务器未显示

  1. 检查claude_desktop_config.json文件语法
  2. 确认项目使用绝对路径
  3. 完全重启Claude桌面端

工具调用静默失败

  1. 检查Claude日志
  2. 验证服务器能否正常启动
  3. 尝试重启Claude桌面端
天气API问题

错误:无法获取网格点数据 可能原因:

  1. 坐标不在美国境内
  2. NWS API服务异常
  3. 触发速率限制

解决方案:

  • 确认使用美国坐标
  • 增加请求间隔时间
  • 查看NWS API状态页
📚
完整调试指南请参考《MCP调试手册》

后续步骤