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

核心 MCP 概念
MCP 服务器可以提供三种主要能力: 本教程将主要聚焦于工具。- Python
- TypeScript
- Java
- Kotlin
- C#
- Ruby
- Rust
- Go
让我们开始构建我们的天气服务器吧!
你可以在这里找到我们将要构建的完整代码。确保在之后重新启动你的终端,以确保能识别到 现在让我们进入构建你的服务器。FastMCP 类使用 Python 的类型标注和文档字符串来自动生成工具定义,这使得创建和维护 MCP 工具变得容易。你的服务器已经完成了!
运行 首先,确保你已安装 Claude for Desktop。
你可以在这里安装最新版本。
如果你已经安装了 Claude for Desktop,请确认已更新到最新版本。我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
为此,在文本编辑器中打开 Claude for Desktop 应用配置文件:然后在 这会告诉 Claude for Desktop:
先决知识
本快速入门假设你已经熟悉:- Python
- 像 Claude 这样的 LLM
登录 MCP 服务器
在实现 MCP 服务器时,请小心处理日志输出方式:基于 STDIO 的服务器: 永远不要向 stdout 写入内容。向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。print() 函数默认会写入 stdout,但可以通过 file=sys.stderr 安全使用。基于 HTTP 的服务器: 标准输出日志是没问题的,因为它不会干扰 HTTP 响应。最佳实践
- 使用一个写入 stderr 或写入文件的日志库。
快速示例
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 项目和环境:curl -LsSf https://astral.sh/uv/install.sh | sh
uv 命令。接下来,我们创建并设置我们的项目:# 为我们的项目创建一个新目录
uv init weather
cd weather
# 创建虚拟环境并激活
uv venv
source .venv/bin/activate
# 安装依赖
uv add "mcp[cli]" httpx
# 创建我们的服务器文件
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"
辅助函数
接下来,让我们添加用于查询并格式化来自国家气象服务(National Weather Service)API 的数据的辅助函数: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")}
"""
实现工具执行
工具执行处理器负责实际执行每个工具的逻辑。让我们添加它:@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)
运行服务器
最后,让我们初始化并运行服务器: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 测试你的服务器
Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 构建客户端 教程,以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json。
如果文件不存在,请先创建它。例如,如果你安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
mcpServers 键中添加你的服务器。
只有当至少有一个服务器被正确配置时,MCP UI 元素才会显示在 Claude for Desktop 中。在这种情况下,我们将按下面方式添加我们的单个天气服务器:{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather.py"
]
}
}
}
你可能需要在
command 字段中填写 uv 可执行文件的完整路径。
你可以在 macOS/Linux 上运行 which uv,在 Windows 上运行 where uv 来获取它。请确保在
cwd(此处为目录参数)中传入你服务器的绝对路径。
你可以在 macOS/Linux 上通过运行 pwd 获取;在 Windows 的命令提示符中通过运行 cd 获取。
在 Windows 中,请记得在 JSON 路径里使用双反斜杠(\\)或正斜杠(/)。- 有一个名为“weather”的 MCP 服务器
- 通过运行
uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py来启动它
让我们开始构建我们的天气服务器吧!
你可以在这里找到我们将要构建的完整代码。在本教程中,你需要 Node.js 版本 16 或更高。接下来,让我们创建并设置我们的项目:更新你的 package.json,加入 在项目根目录创建一个 现在让我们进入构建你的服务器。确保运行 首先,确保你已安装 Claude for Desktop。
你可以在这里安装最新版本。
如果你已经安装了 Claude for Desktop,请确认已更新到最新版本。我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
为此,在文本编辑器中打开 Claude for Desktop 应用配置文件:然后在 这会告诉 Claude for Desktop:
先决知识
本快速入门假设你已经熟悉:- TypeScript
- 像 Claude 这样的 LLM
登录 MCP 服务器
在实现 MCP 服务器时,请小心处理日志输出方式:基于 STDIO 的服务器: 永远不要使用console.log(),因为它默认会写入标准输出(stdout)。
写入 stdout 会破坏 JSON-RPC 消息并导致你的服务器失效。基于 HTTP 的服务器: 标准输出日志是没问题的,因为它不会干扰 HTTP 响应。最佳实践
- 使用
console.error()(它写入 stderr),或者使用写入 stderr 或文件的日志库。
快速示例
// ❌ 不好(STDIO)
console.log("Server started");
// ✅ 好(STDIO)
console.error("Server started"); // stderr 是安全的
系统要求
对于 TypeScript,请确保你安装的是最新版本的 Node。配置你的环境
首先,如果你还没有安装,请先安装 Node.js 和 npm。 你可以从 nodejs.org 下载它们。 验证你的 Node.js 安装:node --version
npm --version
# 为我们的项目创建一个新目录
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
type: "module" 和一个构建脚本:package.json
{
"type": "module",
"bin": {
"weather": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod 755 build/index.js"
},
"files": ["build"]
}
tsconfig.json:tsconfig.json
{
"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 文件顶部: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 数据的辅助函数:// 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[];
};
}
实现工具执行
工具执行处理器负责实际执行每个工具的逻辑。让我们添加它:// 注册天气工具
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 函数: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 测试你的服务器
Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 构建客户端 教程,以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json。
如果文件不存在,请先创建它。例如,如果你安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
mcpServers 键中添加你的服务器。
只有当至少有一个服务器被正确配置时,MCP UI 元素才会显示在 Claude for Desktop 中。在这种情况下,我们将按下面方式添加我们的单个天气服务器:{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"]
}
}
}
- 有一个名为“weather”的 MCP 服务器
- 通过运行
node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js来启动它
这是一个基于 Spring AI 的 MCP 自动配置和启动器(boot starters)的快速入门演示。
要学习如何手动创建同步和异步的 MCP 服务器,请查阅 Java SDK Server 文档。
在 MCP 服务器中记录日志
在实现 MCP 服务器时,请小心处理日志输出方式:基于 STDIO 的服务器: 永远不要使用System.out.println() 或 System.out.print(),因为它们会写入标准输出(stdout)。
向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。基于 HTTP 的服务器: 标准输出日志是没问题的,因为它不会干扰 HTTP 响应。最佳实践
- 使用写入 stderr 或文件的日志库。
- 确保任何已配置的日志框架都不会向 stdout 输出。
系统要求
- 已安装 Java 17 或更高版本。
- Spring Boot 3.3.x 或更高版本
配置你的环境
使用 Spring Initializer 来引导创建项目。你需要添加以下依赖:<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>
spring.main.bannerMode=off
logging.pattern.console=
构建你的服务器
构建你的服务器
天气服务
让我们实现一个 WeatherService.java,它使用 REST 客户端来查询来自国家气象服务 API 的数据:@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 应用
@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 服务器可执行的回调。运行服务器
最后,构建服务器:./mvnw clean install
target 文件夹中生成一个 mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar 文件。现在让我们使用现有的 MCP 主机——Claude for Desktop 来测试你的服务器。使用 Claude for Desktop 测试你的服务器
Claude for Desktop 目前在 Linux 上不可用。
~/Library/Application Support/Claude/claude_desktop_config.json。
如果文件不存在,请先创建它。例如,如果你安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
mcpServers 键中添加你的服务器。
只有当至少有一个服务器被正确配置时,MCP UI 元素才会显示在 Claude for Desktop 中。在这种情况下,我们将按下面方式添加我们的单个天气服务器:{
"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"
]
}
}
}
请确保传入你服务器的绝对路径。
- 有一个名为“my-weather-server”的 MCP 服务器
- 通过运行
java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar来启动它
使用 Java 客户端测试你的服务器
手动创建一个 MCP Client
使用McpClient 连接到服务器: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 应用:<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 配置:spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
更多 Java MCP Server 示例
starter-webflux-server 展示了如何使用 SSE 传输创建一个 MCP 服务器。 它演示了如何使用 Spring Boot 的自动配置功能来定义并注册 MCP Tools、Resources 和 Prompts。让我们开始构建我们的天气服务器吧!
你可以在这里找到我们将要构建的完整代码。现在,让我们创建并设置我们的项目:运行 确认所有配置都正确:现在让我们进入构建你的服务器。你可以在开发期间直接运行服务器:用于生产环境,请构建 shadow JAR:现在,让我们使用现有的 MCP 主机——Claude for Desktop 来测试你的服务器。首先,确保你已安装 Claude for Desktop。
你可以在这里安装最新版本。
如果你已经安装了 Claude for Desktop,请确认已更新到最新版本。我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
为此,在文本编辑器中打开 Claude for Desktop 应用配置文件:然后在 这会告诉 Claude for Desktop:
先决知识
本快速入门假设你已经熟悉:- Kotlin
- 像 Claude 这样的 LLM
在 MCP 服务器中记录日志
在实现 MCP 服务器时,请小心处理日志输出方式:基于 STDIO 的服务器: 永远不要使用println(),因为它默认会写入标准输出(stdout)。
向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。基于 HTTP 的服务器: 标准输出日志是没问题的,因为它不会干扰 HTTP 响应。最佳实践
- 使用写入 stderr 或文件的日志库。
系统要求
- 已安装 JDK 11 或更高版本。
配置你的环境
首先,如果你还没有安装,请安装java 和 gradle。
你可以从 官方 Oracle JDK 网站 下载 java。
验证你的 java 安装:java --version
# 为我们的项目创建一个新目录
mkdir weather
cd weather
# 初始化一个新的 kotlin 项目
gradle init
gradle init 之后,请选择 Application 作为项目类型,并选择 Kotlin 作为编程语言。或者,你也可以使用 IntelliJ IDEA 项目向导来创建一个 Kotlin 应用。创建项目后,将你的 build.gradle.kts 文件内容替换为:build.gradle.kts
// 检查最新版本: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")
}
./gradlew build
构建你的服务器
构建你的服务器
设置实例
添加一个服务器初始化函数: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 响应的函数和数据类: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,
)
实现工具执行
工具执行处理器负责实际执行每个工具的逻辑。让我们添加它:// 注册天气工具
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 函数:fun main() = runMcpServer()
./gradlew run
./gradlew build
java -jar build/libs/weather-0.1.0-all.jar
使用 Claude for Desktop 测试你的服务器
Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 构建客户端 教程,以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json。
如果文件不存在,请先创建它。例如,如果你安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
mcpServers 键中添加你的服务器。
只有当至少有一个服务器被正确配置时,MCP UI 元素才会显示在 Claude for Desktop 中。在这种情况下,我们将按下面方式添加我们的单个天气服务器:{
"mcpServers": {
"weather": {
"command": "java",
"args": [
"-jar",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"
]
}
}
}
- 有一个名为“weather”的 MCP 服务器
- 通过运行
java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar来启动它
让我们开始构建我们的天气服务器吧!
你可以在这里找到我们将要构建的完整代码。现在,让我们创建并设置我们的项目:运行 现在让我们进入构建你的服务器。这段代码会设置一个基础控制台应用,使用 Model Context Protocol SDK 创建一个带标准 I/O 传输的 MCP 服务器。接下来,定义一个包含工具执行处理器的类,用于查询并转换来自国家气象服务 API 的响应:这将启动服务器,并在标准输入/输出上监听传入请求。首先,确保你已安装 Claude for Desktop。
你可以在这里安装最新版本。
如果你已经安装了 Claude for Desktop,请确认已更新到最新版本。
我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
为此,在文本编辑器中打开 Claude for Desktop 应用配置文件:然后在 这会告诉 Claude for Desktop:
先决知识
本快速入门假设你已经熟悉:- C#
- 像 Claude 这样的 LLM
- .NET 8 或更高版本
在 MCP 服务器中记录日志
在实现 MCP 服务器时,请小心处理日志输出方式:基于 STDIO 的服务器: 永远不要使用Console.WriteLine() 或 Console.Write(),因为它们会写入标准输出(stdout)。
向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。基于 HTTP 的服务器: 标准输出日志是没问题的,因为它不会干扰 HTTP 响应。最佳实践
- 使用写入 stderr 或文件的日志库。
系统要求
- 已安装 .NET 8 SDK 或更高版本。
配置你的环境
首先,如果你还没有安装,请先安装dotnet。
你可以从 官方 Microsoft .NET 网站 下载 dotnet。
验证你的 dotnet 安装:dotnet --version
# 为我们的项目创建一个新目录
mkdir weather
cd weather
# 初始化一个新的 C# 项目
dotnet new console
dotnet new console 后,你将看到一个新的 C# 项目。
你可以在你喜欢的 IDE 中打开它,例如 Visual Studio 或 Rider。
或者,你也可以使用 Visual Studio 项目向导 创建一个 C# 应用。
创建项目后,添加 Model Context Protocol SDK 和托管所需的 NuGet 包:# 添加 Model Context Protocol SDK 的 NuGet 包
dotnet add package ModelContextProtocol --prerelease
# 添加 .NET 托管的 NuGet 包
dotnet add package Microsoft.Extensions.Hosting
构建你的服务器
在项目中打开Program.cs 文件,并用以下代码替换其内容: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();
创建
ApplicationHostBuilder 时,请确保使用 CreateEmptyApplicationBuilder,而不是 CreateDefaultBuilder。
这样可以确保服务器不会向控制台输出任何额外消息。
这仅在使用 STDIO 传输的服务器中是必要的。天气 API 辅助函数
为HttpClient 创建一个扩展类,用于简化 JSON 请求处理: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());
}
}
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()}
"""));
}
}
运行服务器
最后,使用以下命令运行服务器:dotnet run
使用 Claude for Desktop 测试你的服务器
Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 构建客户端 教程,以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json。
如果文件不存在,请先创建它。
例如,如果你安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
mcpServers 键中添加你的服务器。
只有当至少有一个服务器被正确配置时,MCP UI 元素才会显示在 Claude for Desktop 中。
在这种情况下,我们将按下面方式添加我们的单个天气服务器:{
"mcpServers": {
"weather": {
"command": "dotnet",
"args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
}
}
}
- 有一个名为“weather”的 MCP 服务器
- 通过运行
dotnet run /ABSOLUTE/PATH/TO/PROJECT来启动它 保存文件,然后重启 Claude for Desktop。
让我们开始构建我们的天气服务器吧!
你可以在这里找到我们将要构建的完整代码。现在,让我们创建并设置我们的项目:现在让我们进入构建你的服务器。你的服务器已经完成了!
运行 首先,确保你已安装 Claude for Desktop。
你可以在这里安装最新版本。
如果你已经安装了 Claude for Desktop,请确认已更新到最新版本。我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
为此,在文本编辑器中打开 Claude for Desktop 应用配置文件:然后在 这会告诉 Claude for Desktop:
先决知识
本快速入门假设你已经熟悉:- Ruby
- 像 Claude 这样的 LLM
在 MCP 服务器中记录日志
在实现 MCP 服务器时,请小心处理日志输出方式:基于 STDIO 的服务器: 永远不要使用puts 或 print,因为它们默认会写入标准输出(stdout)。
向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。基于 HTTP 的服务器: 标准输出日志是没问题的,因为它不会干扰 HTTP 响应。最佳实践
- 使用写入 stderr 或文件的日志库。
快速示例
# ❌ 不好(STDIO)
puts "Processing request"
# ✅ 好(STDIO)
require "logger"
logger = Logger.new($stderr)
logger.info("Processing request")
系统要求
- 已安装 Ruby 2.7 或更高版本。
配置你的环境
首先,确保你已安装 Ruby。 你可以通过运行:ruby --version
# 为我们的项目创建一个新目录
mkdir weather
cd weather
# 创建 Gemfile
bundle init
# 添加 MCP SDK 依赖
bundle add mcp
# 创建我们的服务器文件
touch weather.rb
构建你的服务器
导入包并设置常量
打开weather.rb,并在顶部添加这些 require 和常量: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 数据的辅助方法: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 并实现工具逻辑: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
运行服务器
最后,初始化并运行服务器: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 测试你的服务器
Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 构建客户端 教程,以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json。
如果文件不存在,请先创建它。例如,如果你安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
mcpServers 键中添加你的服务器。
只有当至少有一个服务器被正确配置时,MCP UI 元素才会显示在 Claude for Desktop 中。在这种情况下,我们将按下面方式添加我们的单个天气服务器:{
"mcpServers": {
"weather": {
"command": "bundle",
"args": ["exec", "ruby", "weather.rb"],
"cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather"
}
}
}
请确保在
cwd 字段中传入你的项目目录的绝对路径。
你可以在 macOS/Linux 上运行 pwd 获取;在 Windows 命令提示符中,从你的项目目录运行 cd 获取。
在 Windows 中,请记得在 JSON 路径里使用双反斜杠(\\)或正斜杠(/)。- 有一个名为“weather”的 MCP 服务器
- 在指定目录中通过运行
bundle exec ruby weather.rb来启动它
让我们开始构建我们的天气服务器吧!
你可以在这里找到我们将要构建的完整代码。验证你的 Rust 安装:现在,让我们创建并设置我们的项目:将你的 现在让我们进入构建你的服务器。现在定义 MCP 客户端将发送的请求类型:使用以下命令构建服务器:编译后的二进制文件会位于 首先,确保你已安装 Claude for Desktop。
你可以在这里安装最新版本。
如果你已经安装了 Claude for Desktop,请确认已更新到最新版本。我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
为此,在文本编辑器中打开 Claude for Desktop 应用配置文件:然后在 这会告诉 Claude for Desktop:
先决知识
本快速入门假设你已经熟悉:- 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 安装:curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustc --version
cargo --version
# 创建一个新的 Rust 项目
cargo new weather
cd weather
Cargo.toml 更新为添加所需依赖项:Cargo.toml
[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 和常量: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 的响应定义数据结构:#[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,
}
#[derive(serde::Deserialize, schemars::JsonSchema)]
pub struct MCPForecastRequest {
latitude: f32,
longitude: f32,
}
#[derive(serde::Deserialize, schemars::JsonSchema)]
pub struct MCPAlertRequest {
state: String,
}
辅助函数
添加用于发起 API 请求并格式化响应的辅助函数: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 服务器结构: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 来定义服务器能力:#[tool_handler]
impl ServerHandler for Weather {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_tools().build(),
..Default::default()
}
}
}
运行服务器
最后,使用 stdio 传输来实现运行服务器的 main 函数:#[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(())
}
cargo build --release
target/release/weather。现在让我们使用现有的 MCP 主机——Claude for Desktop 来测试你的服务器。使用 Claude for Desktop 测试你的服务器
Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 构建客户端 教程,以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json。
如果文件不存在,请先创建它。例如,如果你安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
mcpServers 键中添加你的服务器。
只有当至少有一个服务器被正确配置时,MCP UI 元素才会显示在 Claude for Desktop 中。在这种情况下,我们将按下面方式添加我们的单个天气服务器:{
"mcpServers": {
"weather": {
"command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
}
}
}
请确保传入你编译后的二进制文件的绝对路径。
你可以在 macOS/Linux 上运行
pwd,或在 Windows 的命令提示符中从你的项目目录运行 cd。
在 Windows 中,请记得在 JSON 路径里使用双反斜杠(\\)或正斜杠(/),并添加 .exe 扩展名。- 有一个名为“weather”的 MCP 服务器
- 通过在指定路径运行编译后的二进制文件来启动它
让我们开始构建我们的天气服务器吧!
你可以在这里找到我们将要构建的完整代码。现在,让我们创建并设置我们的项目:现在让我们进入构建你的服务器。使用以下命令构建你的服务器:编译后的二进制文件会位于 首先,确保你已安装 Claude for Desktop。
你可以在这里安装最新版本。
如果你已经安装了 Claude for Desktop,请确认已更新到最新版本。我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。
为此,在文本编辑器中打开 Claude for Desktop 应用配置文件:然后在 这会告诉 Claude for Desktop:
先决知识
本快速入门假设你已经熟悉:- 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。
快速示例
// ❌ 不好(STDIO)
fmt.Println("Processing request")
// ✅ 好(STDIO)
log.Println("Processing request") // 默认写入 stderr
// ✅ 好(STDIO)
fmt.Fprintln(os.Stderr, "Processing request")
系统要求
- 已安装 Go 1.24 或更高版本。
配置你的环境
首先,如果你还没有安装 Go,请先安装它。 你可以从 go.dev 下载并安装 Go。验证你的 Go 安装:go version
# 为我们的项目创建一个新目录
mkdir weather
cd weather
# 初始化 Go 模块
go mod init weather
# 安装依赖
go get github.com/modelcontextprotocol/go-sdk/mcp
# 创建我们的服务器文件
touch main.go
构建你的服务器
导入包并设置常量
将以下内容添加到你的main.go 文件顶部: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"
)
数据结构
接下来,定义我们的工具所使用的数据结构: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 数据的辅助函数: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)
}
实现工具执行
工具执行处理器负责实际执行每个工具的逻辑。让我们添加它: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 函数来运行服务器: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)
}
}
go build -o weather .
./weather。现在让我们从现有的 MCP 主机——Claude for Desktop 测试你的服务器。使用 Claude for Desktop 测试你的服务器
Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 构建客户端 教程,以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json。
如果文件不存在,请先创建它。例如,如果你安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
mcpServers 键中添加你的服务器。
只有当至少有一个服务器被正确配置时,MCP UI 元素才会显示在 Claude for Desktop 中。在这种情况下,我们将按下面方式添加我们的单个天气服务器:{
"mcpServers": {
"weather": {
"command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
}
}
}
请确保传入你编译后的二进制文件的绝对路径。
你可以在 macOS/Linux 上运行
pwd,或在 Windows 的命令提示符中从你的项目目录运行 cd 获取它。
在 Windows 中,请记得在 JSON 路径里使用双反斜杠(\\)或正斜杠(/),并添加 .exe 扩展名。- 有一个名为“weather”的 MCP 服务器
- 通过运行指定路径下的编译二进制文件来启动它
使用命令进行测试
让我们确保 Desktop 版的 Claude 能够正确识别我们在weather 服务器中暴露的两个工具。你可以通过查看“添加文件、连接器和更多 /” 
weather 服务器:

- 萨克拉门托(Sacramento)的天气怎么样?
- 德克萨斯(Texas)有哪些生效的天气警报(active weather alerts)?


由于这是美国国家气象服务(National Weather service),因此这些查询只对美国境内的位置有效。
后台发生了什么
当你提出问题时:- 客户端将你的问题发送给 Claude
- Claude 分析可用工具,并决定使用哪一个或哪些工具
- 客户端通过 MCP 服务器执行所选工具
- 将结果发送回 Claude
- Claude 生成自然语言回复
- 回复会显示给你!
故障排查(Troubleshooting)
Claude for Desktop 集成问题
Claude for Desktop 集成问题
从 Claude for Desktop 获取日志与 MCP 相关的 Claude.app 日志会写入 服务器未在 Claude 中显示工具调用静默失败如果 Claude 尝试使用这些工具但失败了:
~/Library/Logs/Claude 目录中的日志文件:mcp.log将包含关于 MCP 连接和连接失败的常规日志。- 名称为
mcp-server-SERVERNAME.log的文件将包含来自指定服务器的错误(stderr)日志。
# 检查 Claude 的日志以查看错误
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
- 检查你的
claude_desktop_config.json文件语法 - 确保你的项目路径是绝对路径,而不是相对路径
- 完全重启 Desktop 版的 Claude
要正确重启 Desktop 版的 Claude,你必须彻底退出应用程序:
- Windows:在系统托盘中(可能在“隐藏图标(hidden icons)”菜单里)右键单击 Claude 图标,然后选择“退出(Quit)”或“离开(Exit)”。
- macOS:使用 Cmd+Q,或在菜单栏中选择“退出 Claude(Quit Claude)”。
- 查看 Claude 的日志以查找错误
- 验证你的服务器构建和运行没有错误
- 尝试重启 Desktop 版的 Claude
天气 API 问题
天气 API 问题
错误:无法获取网格点数据(Failed to retrieve grid point data)这通常意味着以下情况之一:
- 坐标超出了美国范围
- NWS API 出现了问题
- 你被限流了(rate limited)
- 确认你使用的是美国坐标
- 在请求之间加入一点小延迟
- 查看 NWS API 状态页面
如需更高级的故障排查,请查看我们关于 调试 MCP 的指南
下一步
构建客户端(Building a client)
了解如何构建你自己的 MCP 客户端,以连接到你的服务器
示例服务器(Example servers)
查看我们官方 MCP 服务器和实现的画廊
调试指南(Debugging Guide)
学习如何有效调试 MCP 服务器和集成
使用 Agent Skills 构建(Build with Agent Skills)
使用 agent skills 引导 AI 代码助手完成服务器设计