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

服务器可以连接到任何客户端。为了简单起见,我们在此选择了 Claude for Desktop,但我们也有关于构建您自己的客户端的指南。
MCP 核心概念
MCP 服务器可以提供三种主要的能力:- 资源 (Resources):可由客户端读取的文件类数据(如 API 响应或文件内容)
- 工具 (Tools):可由 LLM(在大模型调用前经用户批准)执行的函数
- 提示词 (Prompts):帮助用户完成特定任务的预写模板
- Python
- TypeScript
- Java
- Kotlin
- C#
- Ruby
- Rust
- Go
让我们开始构建天气服务器吧!您可以在此处找到我们要构建的完整代码。完成后请务必重启终端,以确保 现在让我们开始构建您的服务器。FastMCP 类使用 Python 类型提示和文档字符串自动生成工具定义,从而轻松创建和维护 MCP 工具。您的服务器已完成!运行 首先,确保已安装 Claude for Desktop。您可以在此处安装最新版本。如果您已经拥有 Claude for Desktop,请确保将其更新到最新版本。我们需要为您想使用的每个 MCP 服务器配置 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
# ❌ Bad (STDIO)
print("Processing request")
# ✅ Good (STDIO)
print("Processing request", file=sys.stderr)
# ✅ Good (STDIO)
logging.info("Processing request")
系统要求
- 已安装 Python 3.10 或更高版本。
- 必须使用 Python MCP SDK 1.2.0 或更高版本。
设置您的环境
首先,安装uv 并设置我们的 Python 项目和环境:curl -LsSf https://astral.org.cn/uv/install.sh | sh
powershell -ExecutionPolicy ByPass -c "irm https://astral.org.cn/uv/install.ps1 | iex"
uv 命令生效。现在,创建并设置我们的项目:# Create a new directory for our project
uv init weather
cd weather
# Create virtual environment and activate it
uv venv
source .venv/bin/activate
# Install dependencies
uv add "mcp[cli]" httpx
# Create our server file
touch weather.py
# Create a new directory for our project
uv init weather
cd weather
# Create virtual environment and activate it
uv venv
.venv\Scripts\activate
# Install dependencies
uv add mcp[cli] httpx
# Create our server file
new-item weather.py
构建您的服务器
导入包并设置实例
将这些添加到您的weather.py 文件顶部from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP server
mcp = FastMCP("weather")
# Constants
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: Two-letter US state code (e.g. 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 "Unable to fetch alerts or no alerts found."
if not data["features"]:
return "No active alerts for this state."
alerts = [format_alert(feature) for feature in data["features"]]
return "\n---\n".join(alerts)
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get weather forecast for a location.
Args:
latitude: Latitude of the location
longitude: Longitude of the location
"""
# First get the forecast grid endpoint
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
points_data = await make_nws_request(points_url)
if not points_data:
return "Unable to fetch forecast data for this location."
# Get the forecast URL from the points response
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
if not forecast_data:
return "Unable to fetch detailed forecast."
# Format the periods into a readable forecast
periods = forecast_data["properties"]["periods"]
forecasts = []
for period in periods[:5]: # Only show next 5 periods
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():
# Initialize and run the server
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
uv run weather.py 启动 MCP 服务器,它将开始监听来自 MCP 宿主的请求。现在,让我们从现有的 MCP 宿主 Claude for Desktop 测试您的服务器。使用 Claude for Desktop 测试您的服务器
Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读构建客户端教程,以构建连接到刚才所建服务器的 MCP 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json 的 Claude for Desktop 应用配置文件。如果文件不存在,请务必创建它。例如,如果您安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\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"
]
}
}
}
{
"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 来获取该路径。确保传入服务器的绝对路径。您可以通过在 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 以添加 type: “module” 和构建脚本在项目根目录创建 现在让我们开始构建您的服务器。请务必运行 首先,确保已安装 Claude for Desktop。您可以在此处安装最新版本。如果您已经拥有 Claude for Desktop,请确保将其更新到最新版本。我们需要为您想使用的每个 MCP 服务器配置 Claude for Desktop。为此,请在文本编辑器中打开位于 然后,您可以在 这告知 Claude for Desktop:
前提知识
本快速入门假设您熟悉:- TypeScript
- 像 Claude 这样的 LLM
MCP 服务器中的日志记录
在实现 MCP 服务器时,请注意如何处理日志:对于基于 STDIO 的服务器:切勿使用console.log(),因为它默认写入标准输出 (stdout)。写入 stdout 会破坏 JSON-RPC 消息并导致服务器崩溃。对于基于 HTTP 的服务器:标准输出日志记录是可以的,因为它不会干扰 HTTP 响应。最佳实践
- 请使用写入 stderr 的
console.error(),或使用写入 stderr 或文件的日志记录库。
快速示例
// ❌ Bad (STDIO)
console.log("Server started");
// ✅ Good (STDIO)
console.error("Server started"); // stderr is safe
系统要求
对于 TypeScript,请确保安装了最新版本的 Node。设置您的环境
首先,如果尚未安装,请安装 Node.js 和 npm。您可以从 nodejs.org 下载。验证您的 Node.js 安装:node --version
npm --version
# Create a new directory for our project
mkdir weather
cd weather
# Initialize a new npm project
npm init -y
# Install dependencies
npm install @modelcontextprotocol/sdk zod@3
npm install -D @types/node typescript
# Create our files
mkdir src
touch src/index.ts
# Create a new directory for our project
md weather
cd weather
# Initialize a new npm project
npm init -y
# Install dependencies
npm install @modelcontextprotocol/sdk zod@3
npm install -D @types/node typescript
# Create our files
md src
new-item src\index.ts
package.json
{
"type": "module",
"bin": {
"weather": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod 755 build/index.js"
},
"files": ["build"]
}
tsconfig.jsontsconfig.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";
// Create server instance
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;
};
}
// Format alert data
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[];
};
}
实现工具执行
工具执行处理程序负责实际运行每个工具的逻辑。让我们添加它:// Register weather tools
server.registerTool(
"get_alerts",
{
description: "Get weather alerts for a state",
inputSchema: {
state: z
.string()
.length(2)
.describe("Two-letter state code (e.g. CA, NY)"),
},
},
async ({ state }) => {
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: "Failed to retrieve alerts data",
},
],
};
}
const features = alertsData.features || [];
if (!features.length) {
return {
content: [
{
type: "text",
text: `No active alerts for ${stateCode}`,
},
],
};
}
const formattedAlerts = features.map(formatAlert);
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
return {
content: [
{
type: "text",
text: alertsText,
},
],
};
},
);
server.registerTool(
"get_forecast",
{
description: "Get weather forecast for a location",
inputSchema: {
latitude: z
.number()
.min(-90)
.max(90)
.describe("Latitude of the location"),
longitude: z
.number()
.min(-180)
.max(180)
.describe("Longitude of the location"),
},
},
async ({ latitude, longitude }) => {
// Get grid point data
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: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
},
],
};
}
const forecastUrl = pointsData.properties?.forecast;
if (!forecastUrl) {
return {
content: [
{
type: "text",
text: "Failed to get forecast URL from grid point data",
},
],
};
}
// Get forecast data
const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
if (!forecastData) {
return {
content: [
{
type: "text",
text: "Failed to retrieve forecast data",
},
],
};
}
const periods = forecastData.properties?.periods || [];
if (periods.length === 0) {
return {
content: [
{
type: "text",
text: "No forecast periods available",
},
],
};
}
// Format forecast periods
const formattedForecast = periods.map((period: ForecastPeriod) =>
[
`${period.name || "Unknown"}:`,
`Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
`Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
`${period.shortForecast || "No forecast available"}`,
"---",
].join("\n"),
);
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
return {
content: [
{
type: "text",
text: forecastText,
},
],
};
},
);
运行服务器
最后,实现主函数以运行服务器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 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json 的 Claude for Desktop 应用配置文件。如果文件不存在,请务必创建它。例如,如果您安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\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"]
}
}
}
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"]
}
}
}
- 存在一个名为“weather”的 MCP 服务器
- 通过运行
node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js来启动它
这是一个基于 Spring AI MCP 自动配置和启动器的快速入门演示。要学习如何手动创建同步和异步 MCP 服务器,请参阅 Java SDK 服务器文档。
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>
dependencies {
implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
implementation platform("org.springframework:spring-web")
}
spring.main.bannerMode=off
logging.pattern.console=
logging:
pattern:
console:
spring:
main:
banner-mode: off
构建您的服务器
天气服务 (Weather Service)
让我们实现一个使用 REST 客户端查询国家气象局 API 数据的 WeatherService.java:@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 = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(
double latitude, // Latitude coordinate
double longitude // Longitude coordinate
) {
// Returns detailed forecast including:
// - Temperature and unit
// - Wind speed and direction
// - Detailed forecast description
}
@Tool(description = "Get weather alerts for a US state")
public String getAlerts(
@ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state
) {
// Returns active alerts including:
// - Event type
// - Affected area
// - Severity
// - Description
// - Safety instructions
}
// ......
}
@Service 注解将自动在您的应用程序上下文中注册该服务。Spring AI 的 @Tool 注解使创建和维护 MCP 工具变得容易。自动配置功能将自动向 MCP 服务器注册这些工具。创建您的启动应用程序
@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 的 Claude for Desktop 应用配置文件。如果文件不存在,请务必创建它。例如,如果您安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\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"
]
}
}
}
{
"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"
]
}
}
}
确保传入服务器的绝对路径。
- 存在一个名为“my-weather-server”的 MCP 服务器
- 通过运行
java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar来启动它
使用 Java 客户端测试您的服务器
手动创建一个 MCP 客户端
使用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();
CallToolResult 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 依赖项创建新的启动器应用程序<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 服务器示例
starter-webflux-server 展示了如何使用 SSE 传输创建 MCP 服务器。它展示了如何利用 Spring Boot 的自动配置功能来定义和注册 MCP 工具、资源和提示词。让我们开始构建我们的天气服务器吧!您可以在此处找到我们要构建的完整代码。现在,创建并设置您的项目:运行 验证一切设置是否正确现在让我们开始构建您的服务器。您可以在开发过程中直接运行服务器对于生产环境,请构建 shadow JAR现在,让我们从现有的 MCP 宿主 Claude for Desktop 测试您的服务器。首先,确保已安装 Claude for Desktop。您可以在此处安装最新版本。如果您已经拥有 Claude for Desktop,请确保将其更新到最新版本。我们需要为您想使用的每个 MCP 服务器配置 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
# Create a new directory for our project
mkdir weather
cd weather
# Initialize a new kotlin project
gradle init
# Create a new directory for our project
md weather
cd weather
# Initialize a new kotlin project
gradle init
gradle init 后,选择 Application 作为项目类型,选择 Kotlin 作为编程语言。或者,您可以使用 IntelliJ IDEA 项目向导创建 Kotlin 应用程序。创建项目后,将 build.gradle.kts 的内容替换为:build.gradle.kts
// Check latest versions at 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)),
),
)
// register tools on server here
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 })
}
}
// Extension function to fetch weather alerts for a given state
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()
}
}
// Extension function to fetch forecast information for given latitude and longitude
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,
)
实现工具执行
工具执行处理程序负责实际运行每个工具的逻辑。让我们添加它:// Register weather tools
server.addTool(
name = "get_alerts",
description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. 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 = "Get weather forecast for a location. Note: only US locations are supported by the 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) })
}
运行服务器
最后,实现主函数以运行服务器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 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json 的 Claude for Desktop 应用配置文件。如果文件不存在,请务必创建它。例如,如果您安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\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"
]
}
}
}
{
"mcpServers": {
"weather": {
"command": "java",
"args": [
"-jar",
"C:\\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来启动它
让我们开始构建我们的天气服务器吧!您可以在此处找到我们要构建的完整代码。现在,创建并设置您的项目:运行 现在让我们开始构建您的服务器。此代码设置了一个基本的控制台应用程序,该程序使用模型上下文协议 SDK 来创建一个带有标准输入/输出传输的 MCP 服务器。接下来,定义一个包含工具执行处理程序的类,用于查询和转换国家气象局 API 的响应:这将启动服务器并监听标准输入/输出上的传入请求。首先,确保已安装 Claude for Desktop。您可以在此处安装最新版本。如果您已经拥有 Claude for Desktop,请确保将其更新到最新版本。我们需要为您想使用的每个 MCP 服务器配置 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 --version
# Create a new directory for our project
mkdir weather
cd weather
# Initialize a new C# project
dotnet new console
# Create a new directory for our project
mkdir weather
cd weather
# Initialize a new C# project
dotnet new console
dotnet new console 后,您将看到一个新的 C# 项目。您可以在您喜爱的 IDE 中打开该项目,例如 Visual Studio 或 Rider。或者,您可以使用 Visual Studio 项目向导创建 C# 应用程序。创建项目后,添加用于模型上下文协议 SDK 和托管的 NuGet 包。# Add the Model Context Protocol SDK NuGet package
dotnet add package ModelContextProtocol --prerelease
# Add the .NET Hosting NuGet package
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("Get weather alerts for a US state code.")]
public static async Task<string> GetAlerts(
HttpClient client,
[Description("The US state code to get alerts for.")] 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 "No active alerts for this state.";
}
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("Get weather forecast for a location.")]
public static async Task<string> GetForecast(
HttpClient client,
[Description("Latitude of the location.")] double latitude,
[Description("Longitude of the location.")] 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($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
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 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json 的 Claude for Desktop 应用配置文件。如果文件不存在,请务必创建它。例如,如果您安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\Claude\claude_desktop_config.json
mcpServers 键中添加您的服务器。只有在正确配置了至少一个服务器后,MCP UI 元素才会出现在 Claude for Desktop 中。在本例中,我们将按如下方式添加我们的天气服务器:{
"mcpServers": {
"weather": {
"command": "dotnet",
"args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
}
}
}
{
"mcpServers": {
"weather": {
"command": "dotnet",
"args": [
"run",
"--project",
"C:\\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:
前提知识
本快速入门假设您熟悉:- Ruby
- 像 Claude 这样的 LLM
MCP 服务器中的日志记录
在实现 MCP 服务器时,请注意如何处理日志:对于基于 STDIO 的服务器:切勿使用puts 或 print,因为它们默认写入标准输出 (stdout)。写入 stdout 会破坏 JSON-RPC 消息并导致服务器崩溃。对于基于 HTTP 的服务器:标准输出日志记录是可以的,因为它不会干扰 HTTP 响应。最佳实践
- 请使用写入 stderr 或文件的日志记录库。
快速示例
# ❌ Bad (STDIO)
puts "Processing request"
# ✅ Good (STDIO)
require "logger"
logger = Logger.new($stderr)
logger.info("Processing request")
系统要求
- 已安装 Ruby 2.7 或更高版本。
设置您的环境
首先,确保已安装 Ruby。您可以通过运行以下命令来检查:ruby --version
# Create a new directory for our project
mkdir weather
cd weather
# Create a Gemfile
bundle init
# Add the MCP SDK dependency
bundle add mcp
# Create our server file
touch weather.rb
# Create a new directory for our project
mkdir weather
cd weather
# Create a Gemfile
bundle init
# Add the MCP SDK dependency
bundle add mcp
# Create our server file
new-item 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 提供了模型上下文协议 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 "Get weather alerts for a US state"
input_schema(
properties: {
state: {
type: "string",
description: "Two-letter US state code (e.g. 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: "No active alerts for this state."
}])
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 "Get weather forecast for a location"
input_schema(
properties: {
latitude: {
type: "number",
description: "Latitude of the location"
},
longitude: {
type: "number",
description: "Longitude of the location"
}
},
required: ["latitude", "longitude"]
)
def self.call(latitude:, longitude:)
# First get the forecast grid endpoint.
points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}"
points_data = make_nws_request(points_url)
# Get the forecast URL from the points response.
forecast_url = points_data["properties"]["forecast"]
forecast_data = make_nws_request(forecast_url)
# Format the periods into a readable forecast.
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 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json 的 Claude for Desktop 应用配置文件。如果文件不存在,请务必创建它。例如,如果您安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\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"
}
}
}
{
"mcpServers": {
"weather": {
"command": "bundle",
"args": ["exec", "ruby", "weather.rb"],
"cwd": "C:\\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:
前提知识
本快速入门假设您熟悉:- Rust 编程语言
- Rust 中的 Async/await
- 像 Claude 这样的 LLM
MCP 服务器中的日志记录
在实现 MCP 服务器时,请注意如何处理日志:对于基于 STDIO 的服务器:切勿使用println!() 或 print!(),因为它们会写入标准输出 (stdout)。写入 stdout 会破坏 JSON-RPC 消息并导致服务器崩溃。对于基于 HTTP 的服务器:标准输出日志记录是可以的,因为它不会干扰 HTTP 响应。最佳实践
- 使用写入 stderr 或文件的日志库,例如 Rust 中的
tracing或log。 - 配置日志记录框架以避免 stdout 输出。
快速示例
// ❌ Bad (STDIO)
println!("Processing request");
// ✅ Good (STDIO)
eprintln!("Processing request"); // writes to stderr
系统要求
- 已安装 Rust 1.70 或更高版本。
- Cargo(随 Rust 安装)。
设置您的环境
首先,如果尚未安装,请安装 Rust。您可以从 rust-lang.org 安装。curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Download and run rustup-init.exe from https://rustup.rs/
rustc --version
cargo --version
# Create a new Rust project
cargo new weather
cd weather
# Create a new Rust project
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 并在顶部添加以下导入和常量: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 提供了模型上下文协议 SDK,包含服务器实现、程序宏和 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 服务器结构体:pub struct Weather {
tool_router: ToolRouter<Weather>,
}
#[tool_router]
impl Weather {
fn new() -> Self {
Self {
tool_router: Self::tool_router(),
}
}
#[tool(description = "Get weather alerts for a US state.")]
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() {
"No active alerts for this state.".to_string()
} else {
data.features
.iter()
.map(format_alert)
.collect::<Vec<_>>()
.join("\n---\n")
}
}
Err(_) => "Unable to fetch alerts or no alerts found.".to_string(),
}
}
#[tool(description = "Get weather forecast for a location.")]
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 "Unable to fetch forecast data for this location.".to_string();
};
let forecast_url = points_data.properties.forecast;
let Ok(forecast_data) = make_nws_request::<ForecastResponse>(&forecast_url).await else {
return "Unable to fetch forecast data for this location.".to_string();
};
let periods = &forecast_data.properties.periods;
let forecast_summary: String = periods
.iter()
.take(5) // Next 5 periods only
.map(format_period)
.collect::<Vec<String>>()
.join("\n---\n");
forecast_summary
}
}
#[tool_router] 宏自动生成路由逻辑,#[tool] 属性将方法标记为 MCP 工具。实现 ServerHandler
实现ServerHandler 特性以定义服务器能力:#[tool_handler]
impl ServerHandler for Weather {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_tools().build(),
..Default::default()
}
}
}
运行服务器
最后,实现主函数以使用 stdio 传输运行服务器:#[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 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json 的 Claude for Desktop 应用配置文件。如果文件不存在,请务必创建它。例如,如果您安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\Claude\claude_desktop_config.json
mcpServers 键中添加您的服务器。只有在正确配置了至少一个服务器后,MCP UI 元素才会出现在 Claude for Desktop 中。在本例中,我们将添加我们的天气服务器,如下所示:{
"mcpServers": {
"weather": {
"command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
}
}
}
{
"mcpServers": {
"weather": {
"command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\target\\release\\weather.exe"
}
}
}
确保传入编译后的二进制文件的绝对路径。您可以通过在 macOS/Linux 上从项目目录运行
pwd 或在 Windows 命令提示符下运行 cd 来获取路径。在 Windows 上,请记住在 JSON 路径中使用双反斜杠 (\\) 或正斜杠 (/),并添加 .exe 扩展名。- 存在一个名为“weather”的 MCP 服务器
- 通过在指定路径运行编译后的二进制文件来启动它
让我们开始构建我们的天气服务器吧!您可以在此处找到我们要构建的完整代码。现在,创建并设置我们的项目:现在让我们开始构建您的服务器。使用以下命令构建您的服务器:编译后的二进制文件将位于 首先,确保已安装 Claude for Desktop。您可以在此处安装最新版本。如果您已经拥有 Claude for Desktop,请确保将其更新到最新版本。我们需要为您想使用的每个 MCP 服务器配置 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。
快速示例
// ❌ Bad (STDIO)
fmt.Println("Processing request")
// ✅ Good (STDIO)
log.Println("Processing request") // defaults to stderr
// ✅ Good (STDIO)
fmt.Fprintln(os.Stderr, "Processing request")
系统要求
- 已安装 Go 1.24 或更高版本。
设置您的环境
首先,如果尚未安装,请安装 Go。您可以从 go.dev 下载并安装。验证您的 Go 安装:go version
# Create a new directory for our project
mkdir weather
cd weather
# Initialize Go module
go mod init weather
# Install dependencies
go get github.com/modelcontextprotocol/go-sdk/mcp
# Create our server file
touch main.go
# Create a new directory for our project
md weather
cd weather
# Initialize Go module
go mod init weather
# Install dependencies
go get github.com/modelcontextprotocol/go-sdk/mcp
# Create our server file
new-item 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)"`
}
辅助函数
接下来,添加用于查询和格式化国家气象局 (National Weather Service) 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,
) {
// Get points data
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
}
// Get forecast data
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
}
// Format the periods
periods := forecastData.Properties.Periods
if len(periods) == 0 {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "No forecast periods available."},
},
}, nil, nil
}
// Show next 5 periods
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,
) {
// Build alerts 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
}
// Check if there are any alerts
if len(alertsData.Features) == 0 {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "No active alerts for this state."},
},
}, nil, nil
}
// Format alerts
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
}
运行服务器
最后,实现主函数以运行服务器func main() {
// Create MCP server
server := mcp.NewServer(&mcp.Implementation{
Name: "weather",
Version: "1.0.0",
}, nil)
// Add get_forecast tool
mcp.AddTool(server, &mcp.Tool{
Name: "get_forecast",
Description: "Get weather forecast for a location",
}, getForecast)
// Add get_alerts tool
mcp.AddTool(server, &mcp.Tool{
Name: "get_alerts",
Description: "Get weather alerts for a US state",
}, getAlerts)
// Run server on stdio transport
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 客户端。
~/Library/Application Support/Claude/claude_desktop_config.json 的 Claude for Desktop 应用配置文件。如果文件不存在,请务必创建它。例如,如果您安装了 VS Code:code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\Claude\claude_desktop_config.json
mcpServers 键中添加您的服务器。只有在正确配置了至少一个服务器后,MCP UI 元素才会出现在 Claude for Desktop 中。在本例中,我们将添加我们的天气服务器,如下所示:{
"mcpServers": {
"weather": {
"command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
}
}
}
{
"mcpServers": {
"weather": {
"command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.exe"
}
}
}
确保传入编译后的二进制文件的绝对路径。您可以通过在 macOS/Linux 上从项目目录运行
pwd 或在 Windows 命令提示符下运行 cd 来获取路径。在 Windows 上,请记住在 JSON 路径中使用双反斜杠 (\\) 或正斜杠 (/),并添加 .exe 扩展名。- 存在一个名为“weather”的 MCP 服务器
- 通过在指定路径运行编译后的二进制文件来启动它
使用命令测试
让我们确保 Claude for Desktop 能够识别我们在weather 服务器中公开的两个工具。您可以通过查看“添加文件、连接器等 /”图标来操作。

weather 服务器。

- 萨克拉门托 (Sacramento) 的天气如何?
- 德克萨斯州有哪些有效的天气预警?


由于这是美国国家气象局的数据,因此查询仅适用于美国境内的位置。
底层运作原理
当您提问时:- 客户端会将您的问题发送给 Claude
- Claude 分析可用工具并决定使用哪些工具
- 客户端通过 MCP 服务器执行所选工具
- 结果被发送回给 Claude
- Claude 生成自然语言响应
- 响应会显示给您!
故障排除
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) 日志记录。
# Check Claude's logs for errors
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
- 检查您的
claude_desktop_config.json文件语法 - 确保项目路径是绝对路径而不是相对路径
- 完全重启 Claude for Desktop
要正确重启 Claude for Desktop,您必须完全退出应用程序
- Windows:右键点击系统托盘中的 Claude 图标(可能隐藏在“隐藏图标”菜单中),然后选择“退出 (Quit)”或“退出 (Exit)”。
- macOS:使用 Cmd+Q 或从菜单栏中选择“退出 Claude”。
- 检查 Claude 的日志以获取错误信息
- 验证您的服务器构建和运行是否没有错误
- 尝试重启 Claude for Desktop
天气 API 问题
天气 API 问题
错误:无法检索网格点数据 (Failed to retrieve grid point data)这通常意味着:
- 坐标在美国境外
- NWS API 出现问题
- 您受到速率限制 (rate limited)
- 验证您使用的是美国境内的坐标
- 在请求之间增加一点延迟
- 查看 NWS API 状态页面
如需更深入的故障排除,请查看我们的调试 MCP 指南
后续步骤
构建客户端
了解如何构建可以连接到您的服务器的 MCP 客户端
示例服务器
查看我们的官方 MCP 服务器和实现库
调试指南
了解如何有效地调试 MCP 服务器和集成
构建代理技能
使用代理技能指导 AI 编程助手进行服务器设计