MCP官方文档翻译 - 开发文档(Documentation)
本博客提供 MCP(Model Context Protocol)官方文档的中文译文,内容清晰、结构完整,帮助中文开发者快速理解 MCP 的核心概念与使用方式。
前言
- 本文内容翻译自MCP官方文档 - 开发文档(Documentation)部分:https://modelcontextprotocol.io/docs/getting-started/intro
- 由于篇幅原因,在PC端阅读体验更加。也可以访问GitHub(https://github.com/DannyHoo/modelcontextprotocol)或者在公众号“丹尼编程”回复“MCP官方文档翻译”下载源文件,源文件包含markdown格式和pdf格式。
什么是模型上下文协议(What is the Model Context Protocol)?
MCP(Model Context Protocol,模型上下文协议) 是一种开源标准,用于将 AI 应用连接到外部系统。
通过 MCP,诸如 Claude 或 ChatGPT 这样的 AI 应用可以连接到数据源(例如本地文件、数据库)、工具(例如搜索引擎、计算器)、工作流(例如特定用途的 Prompt),从而使 AI 能够访问关键信息并执行实际任务。
可以将 MCP 理解为 AI 应用的 USB-C 接口。正如 USB-C 为电子设备之间的连接提供了标准化方式,MCP 也为 AI 应用与外部系统之间的连接提供了一种标准化机制。
MCP 能实现哪些功能?(What can MCP enable?)
- Agent 可以访问你的 Google Calendar 和 Notion,充当更个性化的 AI 助手。
- Claude Code 可以使用 Figma 设计生成完整的 Web 应用。
- 企业聊天机器人可以连接组织内的多个数据库,让用户能够通过对话来分析数据。
- AI 模型可以在 Blender 中创建 3D 设计,并使用 3D 打印机将其打印出来。
MCP 为何至关重要?(Why does MCP matter?)
其价值大小取决于你在生态系统中所处的角色,MCP 能带来多方面的效益。
-
开发者:在构建AI应用或智能体,或是与之进行集成时,MCP 可有效缩短开发周期、降低开发复杂度。
-
AI 应用程序或 Agent:MCP 提供了对数据源、Tool 和应用生态系统的访问,这将增强功能并改善最终用户体验。
-
终端用户:MCP 使 AI 应用程序或 Agent 更加强大,它们可以访问你的数据,并在必要时代表你执行操作。
架构概述(Architecture overview)
本篇 Model Context Protocol (MCP) 概述讨论了其范围和核心概念,并为每个核心概念提供了示例演示。
由于 MCP SDK 抽象化了许多底层细节,大多数开发者可能会发现数据层协议部分最为实用。该部分讨论了 MCP Server 如何向 AI 应用程序提供上下文。
关于具体的实现细节,请参阅你所使用的特定语言 SDK 的文档。
适用范围(Scope)
模型上下文协议(MCP)包含以下项目:
- MCP 规范:一份 MCP 技术规范文件,明确了客户端与服务端的实现要求。
- MCP 软件开发工具包(SDK):针对不同编程语言实现 MCP 的软件开发工具包。
- MCP 开发工具:用于开发 MCP Server 和 Client 的工具,包括 MCP 调试器(MCP Inspector)。
- MCP 参考服务端实现:MCP Server 的参考实现示例。
MCP 只专注于上下文交换的协议本身——它不规定 AI 应用程序如何使用 LLM 或管理所提供的上下文。
MCP的核心概念(Concepts of MCP)
参与主体(Participants)
MCP 采用客户端 - 服务端架构(Client - Server),其中,MCP Host(即一个AI应用程序,如 Claude Code 或 Claude Desktop)负责与一个或多个 MCP Server 建立连接。MCP Host 通过为每个 MCP Server 创建一个对应的 MCP Client 来实现这一点,且每个 MCP Client 都与目标 MCP Server 维持一条专属连接。
采用 STDIO 传输协议的本地 MCP Server,通常仅为单个 MCP Client 提供服务;而采用 Streamable HTTP 的远程 MCP Server,则一般可同时为多个 MCP Client 提供服务。
MCP 架构中的核心参与者包括:
- MCP Host:协调和管理一个或多个 MCP Client 的AI应用程序。
- MCP Client:负责与 MCP Server 维持连接的组件,从MCP Server 上获取上下文信息,供 MCP Host 使用。
- MCP Server:向 MCP Client 提供上下文信息的程序。
示例说明:Visual Studio Code 作为 MCP Host。当 Visual Studio Code 建立与 MCP Server(如 Sentry MCP Server)的连接时,Visual Studio Code 运行时会实例化一个 MCP Client 对象来维护与 Sentry MCP Server 的连接。当 Visual Studio Code 随后连接到另一个 MCP Server(如本地文件系统 Server)时,Visual Studio Code 运行时会实例化另一个 MCP Client 对象来维护这个连接。

请注意,MCP Server 指的是提供上下文数据的程序,无论它在哪里运行。MCP Server 可以在本地或远程执行。例如,当 Claude Desktop 启动文件系统 Server 时,该 Server 在同一台机器上本地运行,因为它使用 STDIO 传输。这通常被称为"本地" MCP Server。官方的 Sentry MCP Server 运行在 Sentry 平台上,并使用 Streamable HTTP 传输。这通常被称为"远程" MCP Server。
层次结构(Layers)
MCP 由两层结构组成:
- 数据层(Data Layer):定义基于 JSON-RPC 的 Client-Server 通信协议,涵盖生命周期管理,以及Tool(工具)、Resource(资源)、Prompt(提示词)、Notification(通知)等核心基础组件。
- 传输层(Transport Layer):定义实现 Client 和 Server 之间数据交换的通信机制和传输通道,包括特定传输方式的连接建立、消息帧封装和授权。包括与传输方式强相关的连接建立、消息封装和鉴权。
从概念上讲,数据层是内层,而传输层是外层。
数据层(Data layer)
数据层实现了基于 JSON-RPC 2.0 的交换协议,该协议定义了消息结构和语义。此层包括:
- 生命周期管理(Lifecycle Management):处理 Client 和 Server 之间的连接初始化、能力协商和连接终止。
- Server 功能(Server Features):使 Server 能够向 Client 提供核心功能,包括用于 AI 操作的 Tool、用于上下文数据的 Resource,以及用于交互模板的 Prompt。
- Client 功能(Client Features):使 Server 能够请求 Client 从宿主 LLM 进行采样、引导用户输入,以及向 Client 记录日志消息。
- 实用功能(Utility Features):支持额外的能力,如用于实时更新的通知和用于长时间运行操作的进度跟踪。
传输层(Transport layer)
传输层管理 Client 和 Server 之间的通信信道和身份验证。它处理连接建立、消息帧封装以及 MCP 参与者之间的安全通信。
MCP 支持两种传输机制:
- STDIO 传输(Stdio Transport):使用标准输入/输出流在同一台机器上的本地进程之间进行直接进程通信,提供最佳性能且无网络开销。
- Streamable HTTP 传输(Streamable HTTP Transport):使用 HTTP POST 传输 Client 到 Server 的消息,并可选使用 Server-Sent Events (SSE) 实现流式传输能力。此传输方式支持远程 Server 通信,并支持标准 HTTP 身份验证方法,包括 Bearer Token、API 密钥和自定义请求头。MCP 建议使用 OAuth 获取身份验证令牌。
传输层将通信细节从协议层抽象出来,使得相同的 JSON-RPC 2.0 消息格式可在所有传输机制中通用。
数据层协议(Data Layer Protocol)
MCP 的核心部分是定义 MCP Client 与 MCP Server 之间的模式和语义。开发者可能会发现数据层——特别是其中的基础组件集合——是 MCP 中最有趣的部分。正是这一层定义了开发者如何将上下文从 MCP Server 共享给 MCP Client。
MCP 使用 JSON-RPC 2.0 作为其底层 RPC 协议。Client 和 Server 相互发送请求并作出相应响应。在不需要响应的情况下,可以使用通知。
生命周期管理(Lifecycle management)
MCP 是一种有状态协议,需要进行生命周期管理。生命周期管理的目的是协商 Client 和 Server 双方支持的能力。详细信息可以在规范文档中找到,示例代码展示了初始化序列。
基础组件(Primitives)
MCP Primitives(基础组件)是 MCP 中最重要的概念。它们定义了 Client 和 Server 能够为彼此提供的内容。这些基础组件规定了可以与 AI 应用程序共享的上下文信息类型,以及可以执行的操作范围。
MCP 定义了 Server 可以暴露的三个核心基础组件:
- Tools(工具):可执行的函数,AI 应用程序可以调用它们来执行操作(例如:文件操作、API 调用、数据库查询)。
- Resources(资源):为 AI 应用程序提供上下文信息的数据源(例如:文件内容、数据库记录、API 响应)。
- Prompts(提示词):可重用的模板,用于帮助构建与语言模型的交互结构(例如:系统提示词、Few-shot 示例)。
每种基础组件类型都有相关的方法用于发现(*/list)、检索(*/get),以及在某些情况下的执行(tools/call)。MCP Client 将使用 */list 方法来发现可用的基础组件。例如,Client 可以先列出所有可用工具(tools/list),然后再执行它们。这种设计使得列表可以是动态生成的。
举一个具体示例:假设有一个提供数据库上下文信息的 MCP 服务,它可以对外暴露三类内容:用于查询数据库的Tools(工具)、包含数据库架构的Resource(资源)、以及包含与工具交互的少量示例的 Prompt(提示词)。
关于Server基础组件的更多细节,可参见《服务端核心概念》章节。
MCP 同时定义了可供Client对外暴露的基础组件,这类组件能帮助 MCP Server 开发者构建更丰富的交互能力。
-
采样功能:允许Server向Client的AI应用发起请求,获取大语言模型的生成结果。当服务开发者希望调用大语言模型,但又想保持模型无关性,且不在 MCP Server中集成特定大语言模型的 SDK 时,该功能尤为实用。开发者可调用
sampling/complete方法,向Client的AI应用请求大语言模型生成结果。 -
信息调取功能:允许服务向用户请求补充信息。当Server开发者需要获取更多用户输入,或请求用户确认某项操作时,可借助此功能。开发者可调用
elicitation/request方法向用户索取补充信息。 -
日志功能:支持Server向Client发送日志消息,用于调试和监控场景。
关于Client基础组件的更多细节,可参见《客户端核心概念》章节。
除Server与Client基础组件外,该协议还提供了跨域通用工具组件,用于增强请求的执行方式:
- 任务功能(实验性):作为一种持久性的执行封装机制,支持 MCP 请求的延迟结果获取与状态追踪(例如高开销计算、工作流自动化、批量处理、多步骤操作等场景)。
通知机制(Notifications)
本协议支持实时通知功能,以实现Server与Client之间的动态更新。例如,当Server的可用工具发生变动(如新功能上线或既有工具被修改)时,Server可发送工具更新通知,将变更信息告知所有已连接的Client。通知消息采用 JSON-RPC 2.0 通知报文格式进行传输(无需客户端返回响应),助力 MCP Server向已连接Client推送实时更新内容。
示例(Example)
数据层(Data Layer)
本节将聚焦数据层协议,为你逐步讲解 MCP Client与Server的交互流程,通过 JSON-RPC 2.0 消息示例,演示完整的生命周期时序、工具操作流程以及通知机制的工作原理。
1. 初始化(生命周期管理)
MCP 以能力协商握手的方式启动生命周期管理流程。正如生命周期管理章节所述,客户端会发送一条初始化请求,以此建立连接并协商双方支持的功能特性。
初始化请求
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {
"elicitation": {}
},
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}
初始化响应
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {}
},
"serverInfo": {
"name": "example-server",
"version": "1.0.0"
}
}
}
初始化交互流程解析
初始化流程是 MCP 生命周期管理的关键环节,承担多项核心职能:
-
协议版本协商:
protocolVersion字段(例如"2025-06-18")用于确保客户端与服务端使用兼容的协议版本。这一机制可避免因版本不匹配导致的通信异常;若双方未能协商出兼容版本,则应终止连接。 -
能力发现:
capabilities对象支持通信双方声明自身具备的功能特性,包括可处理的基础组件类型(工具、资源、提示词),以及是否支持通知等扩展功能。通过该机制可规避不支持的操作,实现高效通信。 -
身份信息交互:
clientInfo与serverInfo对象会携带身份标识及版本信息,可用于问题调试与兼容性校验。
本示例通过能力协商流程,演示 MCP 基础组件的声明方式:
- 客户端能力声明:
"elicitation": {}- 客户端声明自身支持用户交互请求(可接收elicitation/create方法调用)。- 服务端能力声明:
"tools": {"listChanged": true}- 服务端支持工具类基础组件,且当工具列表发生变更时,可发送tools/list_changed通知。"resources": {}- 服务端同时支持资源类基础组件(可处理resources/list与resources/read方法调用)。
初始化成功后,客户端会发送一条通知报文,表明自身已准备就绪。
在AI应用中的实现逻辑
初始化阶段,AI应用内置的 MCP 客户端管理器会与已配置的服务端建立连接,并将这些服务端的能力信息存储起来,供后续使用。应用程序会基于这些信息,判断哪些服务可提供特定类型的功能(工具、资源、提示词),以及这些服务是否支持实时更新。
AI应用初始化的伪代码:
# Pseudo Code
async with stdio_client(server_config) as (read, write):
async with ClientSession(read, write) as session:
init_response = await session.initialize()
if init_response.capabilities.tools:
app.register_mcp_server(session, supports_tools=True)
app.set_server_ready(session)
2. 工具发现(基础组件)
连接建立完成后,Client 可通过发送 tools/list 请求来发现可用工具。该请求是 MCP 工具发现机制的核心基础 —— 它能让 Client 在尝试调用工具前,预先了解 Server 上部署了哪些可用工具。
工具列表请求
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}
工具列表响应
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "calculator_arithmetic",
"title": "Calculator",
"description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
}
},
"required": ["expression"]
}
},
{
"name": "weather_current",
"title": "Weather Information",
"description": "Get current weather information for any location worldwide",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, address, or coordinates (latitude,longitude)"
},
"units": {
"type": "string",
"enum": ["metric", "imperial", "kelvin"],
"description": "Temperature units to use in response",
"default": "metric"
}
},
"required": ["location"]
}
}
]
}
}
工具发现请求解析
tools/list 请求结构简洁,不包含任何参数。
工具发现响应解析
响应体包含一个 tools 数组,用于提供每个可用工具的完整元数据。这种基于数组的结构,可支持 Server 同时暴露多个工具,且能清晰划分不同功能之间的边界。
响应体中的每个工具对象包含以下核心字段:
name:工具在 Server 命名空间内的唯一标识符。该字段是工具执行的主键,命名需遵循清晰的规范(例如:应使用calculator_arithmetic,而非仅用calculate)title:工具的易读性展示名称,可供 Client 直接呈现给终端用户description:工具功能及适用场景的详细说明inputSchema:用于定义工具预期输入参数的 JSON 模式,可实现参数类型校验,并清晰标注必填参数与可选参数的说明文档
该机制在 AI 应用中的工作流程
AI 应用会从所有已连接的 MCP Server 中拉取可用工具,并将其整合为一个语言模型可访问的统一工具注册表。通过该注册表,大语言模型(LLM)能够理解它能够执行哪些操作,并在对话过程中自动生成对应的工具调用指令。
AI应用工具发现的伪代码:
# 采用 MCP Python SDK 格式编写的伪代码
available_tools = []
for session in app.mcp_server_sessions():
tools_response = await session.list_tools()
available_tools.extend(tools_response.tools)
conversation.register_available_tools(available_tools)
3. 工具执行(基础组件)
连接建立并完成工具发现后,Client 现在可通过 tools/call 方法执行工具。这一过程直观展现了 MCP 基础组件的实际应用流程:在发现可用工具之后,Client 能够携带合适的参数对目标工具进行调用。
工具执行请求解析
tools/call 请求采用结构化格式构建,该格式可确保参数类型安全,并实现 Client 与 Server 之间的清晰通信。需要注意的是,请求中应使用工具发现响应返回的标准工具名称(例如 weather_current),而非简化后的名称。
工具调用请求:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "weather_current",
"arguments": {
"location": "San Francisco",
"units": "imperial"
}
}
}
工具调用响应:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
}
]
}
}
工具执行的核心要素
工具执行请求的结构包含以下几个重要组成部分:
-
name(工具名称):必须与工具发现响应中返回的工具名称完全一致(例如weather_current)。这一要求确保 Server 能够准确识别需要执行的目标工具。 -
arguments(工具参数):包含由工具inputSchema定义的输入参数。本示例中的参数如下:location(地理位置):“San Francisco”(必填参数)units(单位):“英制”(可选参数,若未指定则默认使用 “公制”)
-
JSON-RPC 结构:采用标准的 JSON-RPC 2.0 格式,通过唯一标识(id)实现请求与响应的关联匹配。
工具执行响应解析
工具执行响应直观体现了 MCP 灵活的内容体系特性:
content数组:工具执行响应会返回一个内容对象数组,支持生成丰富的多格式响应(包括文本、图片、资源等类型)- 内容类型:每个内容对象都包含一个
type字段。在本示例中,"type": "text"表示响应内容为纯文本格式,而 MCP 协议支持多种内容类型,以适配不同的业务场景需求 - 结构化输出:响应返回的是可直接使用的有效信息,AI 应用可将这些信息作为上下文,用于支撑语言模型的交互过程
这种执行范式使得 AI 应用能够动态调用 Server 端的功能,并接收结构化的响应结果,进而将这些结果整合到与语言模型的对话流程中。
该机制在AI应用中的工作流程
当语言模型在对话过程中决定调用某一工具时,AI 应用会拦截该工具调用请求,将其路由至对应的 MCP Server,执行工具调用操作,然后将执行结果作为对话流程的一部分,返回给大语言模型(LLM)。这一流程使 LLM 能够获取实时数据,并对外部世界执行相应的操作。
# AI 应用执行工具的伪代码async def handle_tool_call(conversation, tool_name, arguments): session = app.find_mcp_session_for_tool(tool_name) result = await session.call_tool(tool_name, arguments) conversation.add_tool_result(result.content)
4. 实时更新(通知机制)
MCP 支持实时通知功能,该功能允许 Server 在未被 Client 显式请求的情况下,主动将变更信息推送至 Client。这一机制直观展现了 MCP 的通知机制 —— 作为核心特性之一,它能维持 MCP 连接的同步性与响应性。
工具列表变更通知解析
当 Server 上的可用工具发生变更时(例如新增功能工具、修改现有工具,或工具暂时不可用),Server 可主动向已连接的 Client 推送通知:
请求示例:
{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed"}
MCP 通知机制的核心特性
- 无需返回响应:注意该通知中不包含
id字段。这遵循了 JSON-RPC 2.0 通知的语义规范,即这类消息不需要 Client 返回响应,Server 也不会等待响应反馈。 - 基于能力声明:只有在初始化阶段(如步骤 1 所示),在工具能力配置中声明了
"listChanged": true的 Server,才会推送该类通知。 - 事件驱动型:Server 会根据自身内部状态的变更,自主决定推送通知的时机,这让 MCP 连接具备动态性与高响应性。
Client 对通知的响应处理
Client 接收到该通知后,通常会通过请求最新工具列表来做出响应。这一流程形成了一个刷新循环,确保 Client 对可用工具的认知始终保持最新:
请求示例:
{ "jsonrpc": "2.0", "id": 4, "method": "tools/list"}
通知机制的重要性
该通知机制至关重要,原因如下:
- 适配动态环境:工具的可用状态可能会随 Server 状态、外部依赖或用户权限的变化而改变。
- 提升交互效率:Client 无需通过轮询方式查询变更,仅在有更新时才会收到主动通知。
- 保证数据一致性:确保 Client 始终持有 Server 可用能力的准确信息。
- 支持实时协作:助力 AI 应用实现高响应性,能够根据变化的上下文进行动态适配。
这种通知范式不仅适用于工具模块,还可延伸至 MCP 的其他基础组件,实现 Client 与 Server 之间全面的实时同步。
该机制在AI应用中的工作流程
当 AI 应用接收到工具变更通知后,会立即刷新其工具注册表,并更新大语言模型(LLM)的可用能力列表。这确保了正在进行的对话始终能调用最新的工具集,同时 LLM 也能在新功能工具可用时,动态适配并使用该功能。
# AI应用通知处理的伪代码async def handle_tools_changed_notification(session): tools_response = await session.list_tools() app.update_available_tools(session, tools_response.tools) if app.conversation.is_active(): app.conversation.notify_llm_of_new_capabilities()
理解MCP Server(Understanding MCP servers)
MCP Server是一类程序,它们通过标准化的协议接口,向 AI 应用开放特定的功能能力。
常见的示例包括:用于文档访问的文件系统服务、用于数据查询的数据库服务、用于代码管理的 GitHub 服务、用于团队沟通的 Slack 服务,以及用于日程规划的日历服务。
Server 核心特性(Core Server Features)
MCP Server通过以下核心组件对外提供能力:
| 特性 | 说明 | 示例 | 控制方 |
|---|---|---|---|
| Tools(工具) | 可供大语言模型(LLM)主动调用的功能,模型会根据用户请求自主决定调用时机。工具可执行写入数据库、调用外部 API、修改文件或触发其他业务逻辑等操作 | 航班查询;发送消息;创建日程事件 | 模型(Model) |
| Resources(资源) | 提供只读信息访问的被动数据源,用于为模型交互提供上下文支持,例如文件内容、数据库表结构或 API 文档等 | 文档检索;访问知识库;读取日程信息 | 应用(Application) |
| Prompts(提示词) | 预构建的指令模板,用于指导模型结合特定工具与资源完成任务 | 规划假期行程;汇总我的会议内容;草拟一封邮件 | 用户(User) |
功能协作演示说明
我们将通过一个假设场景,演示上述各项功能的作用,以及它们如何协同工作。
工具(Tools)
工具赋予 AI 模型执行具体操作的能力。每个工具都定义了一项特定操作,并包含类型化的输入与输出参数。模型会根据交互上下文,发起工具执行请求。
工具的工作机制(How Tools Work)
工具是基于模式定义的接口,可供大语言模型(LLM)调用。MCP 采用 JSON Schema 实现参数校验。每个工具仅负责执行单一操作,且输入与输出的定义清晰明确。部分工具在执行前可能需要获取用户授权,以此确保用户能够管控模型发起的各类操作行为。
协议操作说明
| 方法 | 用途 | 返回值 |
|---|---|---|
tools/list |
发现可用工具 | 包含模式定义的工具描述数组 |
tools/call |
执行指定工具 | 工具执行结果 |
工具定义示例
{ name: "searchFlights", description: "Search for available flights", inputSchema: { type: "object", properties: { origin: { type: "string", description: "Departure city" }, destination: { type: "string", description: "Arrival city" }, date: { type: "string", format: "date", description: "Travel date" } }, required: ["origin", "destination", "date"] }}
示例:旅行预定(Example: Travel Booking)
工具使AI应用能够代表用户执行各类操作。在旅行规划场景中,AI 应用可借助多款工具协助完成假期预订事宜:
航班查询
searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15")
查询多家航空公司的航班信息,并返回结构化的航班选项。
日程锁定
createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22")
在用户的日历中标记旅行的日期。
邮件通知
sendEmail(to: "team@work.com", subject: "Out of Office", body: "...")
向同事发送自动生成的外出休假通知邮件。
用户交互模型(User Interaction Model)
工具由模型自主控制,也就是说,AI模型能够自动发现并调用这些工具。不过,MCP 协议通过多种机制,强调了人类对工具操作的监督作用。
为保障安全性与可信度,应用程序可通过以下多种机制实现用户对工具的管控:
- 在用户界面中展示可用工具,允许用户定义某一工具是否可在特定交互场景下启用
- 针对单次工具执行操作弹出授权确认对话框
- 配置权限设置,预先批准部分安全无风险的工具操作
- 生成操作日志,记录所有工具的执行情况及其对应的结果
资源(Resources)
资源为 AI 应用提供结构化的信息访问能力,这些信息可被 AI 应用检索并作为上下文提供给模型。
资源的工作机制( How Resources Work)
资源对外暴露来自文件、API、数据库或其他任意数据源的信息,这些信息是 AI 理解上下文所必需的。应用程序可直接访问这些信息,并自主决定使用方式 —— 无论是筛选相关内容片段、通过向量嵌入技术进行检索,还是将全部信息传递给模型。
每个资源都拥有唯一的统一资源标识符(URI)(例如 file:///path/to/document.md),并会声明自身的多用途互联网邮件扩展类型(MIME type),以确保内容被正确处理。
资源支持两种发现模式:
-
静态资源 - 指向特定数据的固定 URI。示例:
calendar://events/2024- 返回 2024 年的日程空闲情况 -
资源模板 - 带有参数的动态 URI,支持灵活查询。示例:
travel://activities/{city}/{category}- 根据城市和类别返回相关活动travel://activities/barcelona/museums- 返回巴塞罗那的所有博物馆
资源模板包含标题、描述和预期 MIME 类型等元数据,具备自发现和自文档化的特性。
协议操作说明
| 方法 | 用途 | 返回值 |
|---|---|---|
resources/list |
列出可用的静态资源 | 资源描述信息数组 |
resources/templates/list |
发现资源模板 | 资源模板定义数组 |
resources/read |
检索资源内容 | 附带元数据的资源数据 |
resources/subscribe |
监听资源变更 | 订阅确认信息 |
示例:获取旅行规划上下文(Example: Getting Travel Planning Context)
延续旅行规划的示例,资源为 AI 应用提供相关信息的访问权限:
- 日程数据(
calendar://events/2024)- 查看用户的空闲时间 - 旅行文件(
file:///Documents/Travel/passport.pdf)- 访问重要证件文档 - 历史行程(
trips://history/barcelona-2023)- 参考过往旅行记录和偏好
AI 应用检索这些资源后,自主决定处理方式:既可通过向量嵌入或关键词检索筛选数据子集,也可将原始数据直接传递给模型。
在本场景中,应用会将日程数据、天气信息和旅行偏好提供给模型,使其能够检查用户空闲时间、查询天气趋势并参考历史旅行偏好。
资源模板示例
{ "uriTemplate": "weather://forecast/{city}/{date}", "name": "weather-forecast", "title": "Weather Forecast", "description": "Get weather forecast for any city and date", "mimeType": "application/json"}{ "uriTemplate": "travel://flights/{origin}/{destination}", "name": "flight-search", "title": "Flight Search", "description": "Search available flights between cities", "mimeType": "application/json"}
这些模板支持灵活的查询操作。对于天气数据,用户可查询任意城市在任意日期的天气预报;对于航班数据,用户可搜索任意两座机场之间的航线。当用户输入出发机场为 “NYC”,并开始输入目的地为 “Bar” 时,该机制可自动推荐 “Barcelona (巴塞罗那)” 或 “Barbados (巴巴多斯)”。
参数自动补全(Parameter Completion)
动态资源支持参数自动补全功能。例如:
- 当在
weather://forecast/{city}的城市参数中输入 “Par” 时,可能推荐 “Paris(巴黎)” 或 “Park City(帕克城)” - 当在
flights://search/{airport}的机场参数中输入 “JFK” 时,可能推荐 “JFK - John F. Kennedy International(JFK - 约翰・肯尼迪国际机场)”
该机制可帮助用户自动匹配合法参数取值,无需掌握参数的精确格式规范。
用户交互模型(User Interaction Model)
资源由应用程序驱动,这使得应用在检索、处理和呈现可用上下文时具备高度灵活性。常见的交互模式包括:
- 采用树形或列表视图,以用户熟悉的类文件夹结构浏览资源
- 提供检索和筛选界面,用于查找特定资源
- 基于启发式规则或 AI 筛选结果,自动纳入上下文或提供智能推荐
- 支持手动或批量选择界面,用于添加单个或多个资源
应用程序可根据自身需求,通过任意界面模式实现资源发现功能。MCP 协议不强制规定特定的 UI 模式,因此开发者可灵活设计具备预览功能的资源选择器、基于当前对话上下文的智能推荐组件、支持批量选择多资源的功能,或是与现有文件浏览器和数据探索工具进行集成。
提示词(Prompt)
提示词提供可复用的模板。它们允许 MCP 服务端开发者为特定业务领域编写带参数的提示词,或是展示如何以最优方式使用该 MCP 服务。
提示词的工作机制(How Prompts Work)
提示词是结构化的模板,用于定义预期的输入内容和交互模式。提示词由用户自主控制,需要用户显式调用,而非由系统自动触发。提示词可具备上下文感知能力,能够引用可用的资源与工具,构建完整的业务流程。与资源类似,提示词也支持参数自动补全功能,帮助用户快速发现合法的参数取值。
协议操作说明
| 方法 | 用途 | 返回值 |
|---|---|---|
prompts/list |
发现可用的提示词 | 提示词描述信息数组 |
prompts/get |
检索提示词的详细定义 | 包含参数的完整提示词定义 |
示例:标准化业务流程(Example: Streamlined Workflows)
提示词为常见任务提供结构化的模板。在旅行规划场景中,以下是一个 Plan a vacation(规划假期)提示词示例:
{ "name": "plan-vacation", "title": "Plan a vacation", "description": "Guide through vacation planning process", "arguments": [ { "name": "destination", "type": "string", "required": true }, { "name": "duration", "type": "number", "description": "days" }, { "name": "budget", "type": "number", "required": false }, { "name": "interests", "type": "array", "items": { "type": "string" } } ]}
相比无结构化的自然语言输入,提示词机制可实现以下能力:
- 选择
Plan a vacation模板 - 输入结构化参数:巴塞罗那、7 天、3000 美元、[“海滩”, “建筑”, “美食”]
- 基于模板执行一致性的业务流程
用户交互模型( User Interaction Model)
提示词由用户自主控制,需要用户显式调用。MCP 协议赋予开发者灵活设计交互界面的自由,使界面与应用自身的风格自然融合。核心设计原则包括:
- 便于用户发现可用的提示词
- 清晰描述每个提示词的功能用途
- 支持带参数校验的自然化参数输入
- 透明展示提示词的底层模板内容
应用程序通常通过以下多种 UI 模式向用户开放提示词功能:
- 斜杠命令(输入
/即可查看可用提示词,例如/plan-vacation) - 命令面板(支持搜索的提示词访问入口)
- 专用 UI 按钮(用于放置高频使用的提示词)
- 上下文菜单(根据当前场景推荐相关提示词)
多服务协同工作(Bringing Servers Together)
当多个服务通过统一接口整合各自的专属能力时,MCP 协议的真正强大之处才得以彰显。
示例:多服务协同的旅行规划(Example: Multi-Server Travel Planning)
我们以一款个性化 AI 旅行规划应用为例,该应用同时连接了三个服务:
- 旅行服务 - 处理航班、酒店及行程规划相关事务
- 天气服务 - 提供气候数据与天气预报服务
- 日程 / 邮件服务 - 管理日程安排与信息沟通
完整流程( The Complete Flow)
-
用户调用带参提示词
{ "prompt": "plan-vacation", "arguments": { "destination": "Barcelona", "departure_date": "2024-06-15", "return_date": "2024-06-22", "budget": 3000, "travelers": 2 }} -
用户选定待调用资源
calendar://my-calendar/June-2024(来自日程服务)travel://preferences/europe(来自旅行服务)travel://past-trips/Spain-2023(来自旅行服务)
-
AI 借助工具处理请求
AI 首先读取所有选定的资源以获取上下文信息 —— 从日历中确认用户的空闲日期、从旅行偏好中提取用户青睐的航空公司与酒店类型、从西班牙过往旅行记录中挖掘用户喜欢的目的地。
基于这些上下文,AI 接着调用一系列工具:
searchFlights()- 查询从纽约飞往巴塞罗那的航班checkWeather()- 获取旅行期间的天气预报
-
AI 执行预订及后续操作(必要时请求用户授权)
bookHotel()- 筛选符合预算的酒店createCalendarEvent()- 将行程添加至用户日历sendEmail()- 发送包含行程详情的确认邮件
最终效果:借助多个 MCP 服务的协同,用户完成了一场完全贴合自身日程的巴塞罗那旅行规划与预订。“规划假期” 提示词机制引导 AI 整合跨服务的资源(日程空闲时间、旅行历史)与工具(航班搜索、酒店预订、日程更新),将原本需要数小时手动操作的任务,压缩至短短几分钟内完成。
理解MCP Client(Bringing Servers Together)
MCP Client 由宿主应用程序实例化,用于与特定的 MCP Server 进行通信。诸如 Claude.ai 或集成开发环境(IDE)这类宿主应用程序,负责管理整体用户体验并协调多个 Client。每个 Client 负责与一个 Server 建立一条直接的通信链路。
明确二者的区别十分重要:宿主是用户与之直接交互的应用程序,而 Client 是与 Server 建立连接的协议层组件。
Client 核心功能(Core Client Features)
除了利用 Server 提供的上下文信息外,Client 还可以向 Server 提供多项功能。这些 Client 功能能够帮助 Server 开发者构建更丰富的交互流程。
| 功能 | 说明 | 示例 |
|---|---|---|
| 信息引导采集(Elicitation) | 该功能支持 Server 在交互过程中向用户请求特定信息,为 Server 按需收集信息提供了一种结构化的方式。 | 一个支持机票预订的 Server 可能会询问用户的飞机座位偏好、房型要求或联系电话,以此完成预订流程。 |
| 作用域根目录(Roots) | 该功能允许 Client 指定 Server 应聚焦的目录范围,并通过协调机制传递预期的作用域。 | 为一个支持机票预订的 Server 授予某一特定目录的访问权限,使其能够读取该目录下存储的用户日程信息。 |
| 采样(Sampling) | 该功能支持 Server 通过 Client 请求大语言模型(LLM)的生成结果,从而实现智能体(agent)式的工作流。这种方式让 Client 能够完全掌控用户权限与安全策略。 | 一个支持机票预订的 Server 可将航班列表发送给大语言模型,并请求模型为用户筛选出最优航班。 |
信息引导采集(Elicitation)
信息引导采集功能支持 Server 在交互过程中向用户请求特定信息,从而构建更具动态性与响应性的工作流。
概述(Overview)
信息引导采集为 Server 提供了一种结构化的按需收集必要信息的方式。Server 无需在交互初期就要求用户提供全部信息,也不会因缺失部分数据而中断运行,而是可以暂停当前操作,向用户请求特定的输入内容。这一机制能够实现更灵活的交互过程,让 Server 可以根据用户需求动态调整流程,而非遵循固定不变的交互模式。
信息引导采集流程:

该流程支持动态信息收集。Server 可在需要时请求特定数据,用户通过对应的用户界面(UI)提供信息,随后 Server 基于新获取的上下文继续执行处理流程。
信息引导采集组件示例:
{ method: "elicitation/requestInput", params: { message: "Please confirm your Barcelona vacation booking details:", schema: { type: "object", properties: { confirmBooking: { type: "boolean", description: "Confirm the booking (Flights + Hotel = $3,000)" }, seatPreference: { type: "string", enum: ["window", "aisle", "no preference"], description: "Preferred seat type for flights" }, roomType: { type: "string", enum: ["sea view", "city view", "garden view"], description: "Preferred room type at hotel" }, travelInsurance: { type: "boolean", default: false, description: "Add travel insurance ($150)" } }, required: ["confirmBooking"] } }}
示例:假期预定确认(Example: Holiday Booking Approval)
某旅行预订 Server 借助预订确认的收尾流程,充分展现了信息引导采集功能的实用价值。当用户选定心仪的巴塞罗那度假套餐后,该 Server 需要在推进预订流程前,获取用户的最终确认以及补充所有缺失的信息。
Server 会以结构化请求的形式发起预订确认的信息采集,请求内容包含行程概要(6 月 15 日 - 22 日巴塞罗那往返航班、临海酒店、总预算 3000 美元),同时附带用于填写额外偏好的字段 —— 例如座位选择、房型需求、旅行保险方案等。
随着预订流程的推进,Server 还会采集完成预订所需的联系信息。这其中可能包括航班预订所需的出行人详细信息、酒店特殊需求说明,以及紧急联系人信息等。
用户交互模型(User Interaction Model)
信息引导采集的交互流程遵循清晰明确、贴合上下文且尊重用户自主选择权的设计原则:
请求呈现:Client 在展示信息采集请求时,会清晰标注发起请求的 Server 身份、信息采集的目的以及信息的用途。请求消息负责阐释采集意图,而对应的结构化模型则用于规范信息格式并提供校验依据。
响应方式:用户可通过对应的用户界面控件(文本输入框、下拉菜单、复选框)提供所需信息;也可选择拒绝提供信息,并附上可选的拒绝理由;还能直接取消整个操作流程。Client 会依据请求附带的结构化模型对用户响应内容进行校验,通过后再反馈给 Server。
隐私注意事项:信息引导采集功能严禁采集用户密码或 API 密钥等敏感凭证。对于存在风险的可疑请求,Client 会向用户发出预警,同时在发送数据前允许用户对拟提交的信息进行复核确认。
作用域根目录(Roots)
作用域根目录用于为 Server 的操作定义文件系统边界,允许 Client 指定 Server 应聚焦的目录范围。
概述(Overview)
作用域根目录是 Client 向 Server 传递文件系统访问边界的一种机制。它由文件统一资源标识符(URI)构成,这些 URI 指明了 Server 可进行操作的目录,帮助 Server 明确可用文件与文件夹的作用域范围。需要注意的是,作用域根目录仅用于传递预期的边界范围,不具备安全限制的强制效力。实际的安全管控必须在操作系统层面通过文件权限和 / 或沙箱机制来实现。
作用域根目录结构
{ "uri": "file:///Users/agent/travel-planning", "name": "Travel Planning Workspace"}
作用域根目录仅为文件系统路径,且必须使用 file:// URI 协议。它帮助 Server 理解项目边界、工作区组织结构以及可访问的目录。当用户切换处理不同的项目或文件夹时,作用域根目录列表可被动态更新,边界发生变化时,Server 会通过 roots/list_changed 接口接收通知。
示例-旅行规划工作区(Example: Travel Planning Workspace)
对于需要处理多个客户行程的旅行代理来说,借助多个作用域根目录(Roots)可以更规范地管理文件系统的访问范围。我们可以设想这样一个工作区场景:其中包含了多个目录,分别对应旅行规划工作的不同环节。
Client 向旅行规划 Server 提供以下文件系统作用域根目录:
file:///Users/agent/travel-planning- 包含所有旅行相关文件的主工作区file:///Users/agent/travel-templates- 可复用的行程模板与资源文件目录file:///Users/agent/client-documents- 存储客户护照及旅行证件的目录
当旅行代理创建巴塞罗那行程时,合规的 Server 会严格遵循这些访问边界 —— 仅在指定的作用域根目录内访问模板文件、保存新行程、引用客户相关证件。Server 通常会通过两种方式访问根目录内的文件:一是使用根目录的相对路径,二是借助遵循根目录边界限制的文件搜索工具。
若代理打开一个归档文件夹(例如 file:///Users/agent/archive/2023-trips),Client 就会通过 roots/list_changed 接口更新作用域根目录列表。
关于遵循作用域根目录机制的完整 Server 实现示例,可参考官方 Server 代码仓库中的文件系统 Server。
设计理念(Design Philosophy)
作用域根目录(Roots)是 Client 与 Server 之间的协调机制,而非安全边界。MCP 规范仅要求 Server “应当遵守根目录边界” ,而非 “必须强制执行” 该限制,原因在于 Server 运行的代码不受 Client 的管控。
只有满足以下条件,作用域根目录才能发挥最佳效用:Server 来源可信或经过审核验证、用户清楚其仅为指导性机制、目标是预防误操作而非阻止恶意行为。它在以下三个方面表现突出:
- 上下文作用域界定:明确告知 Server 应聚焦的操作范围
- 误操作预防:帮助合规的 Server 始终在限定边界内运行
- 工作流组织:例如自动管理不同项目的访问边界
用户交互模型(User Interaction Model)
作用域根目录通常由宿主应用根据用户操作自动管理,不过部分应用也会提供手动配置功能:
自动根目录检测:当用户打开文件夹时,Client 会自动将该文件夹设为作用域根目录。比如打开一个旅行工作区文件夹后,Client 会将此目录暴露为根目录,帮助 Server 明确当前工作所需的行程方案与相关文档的范围。
手动根目录配置:高级用户可通过配置自定义作用域根目录。例如,添加 /travel-templates 目录以访问可复用资源,同时排除存储财务记录的目录。
采样(Sampling)
采样功能允许 Server 通过 Client 请求大语言模型的生成结果,从而在保持安全性与用户控制权的前提下,实现智能体(agent)式行为。
概述(Overview)
采样机制让 Server 无需直接集成或付费调用 AI 模型,即可完成依赖 AI 的任务。相反,Server 可以让已经具备 AI 模型访问能力的 Client 代其执行这些任务。这种方式让 Client 能够完全掌控用户权限与安全策略。
由于采样请求通常发生在其他操作的上下文之中(例如某个工具正在分析数据),并且会作为独立的模型调用进行处理,因此它们能够在不同上下文之间保持清晰的边界,从而更高效地利用上下文窗口。
采样流程:

该流程通过多个 “人工介入检查点” 来确保安全性。在将内容返回给 Server 之前,用户会先查看并可以修改初始请求和生成的响应。
请求参数示例:
{ messages: [ { role: "user", content: "Analyze these flight options and recommend the best choice:\n" + "[47 flights with prices, times, airlines, and layovers]\n" + "User preferences: morning departure, max 1 layover" } ], modelPreferences: { hints: [{ name: "claude-sonnet-4-20250514" // Suggested model }], costPriority: 0.3, // Less concerned about API cost speedPriority: 0.2, // Can wait for thorough analysis intelligencePriority: 0.9 // Need complex trade-off evaluation }, systemPrompt: "You are a travel expert helping users find the best flights based on their preferences", maxTokens: 1500}
示例:航班分析工具(Example: Flight Analysis Tool)
以一个旅行预订 Server 为例,它提供了一个名为 findBestFlight 的工具,该工具使用采样功能来分析可用航班并推荐最优选项。当用户提出 “帮我预订下个月去巴塞罗那的最佳航班” 时,该工具需要借助 AI 来评估复杂的权衡因素。
工具首先查询航空公司 API,收集到 47 个航班选项。随后,它请求 AI 协助分析这些选项,例如:“请分析这些航班选项并推荐最佳选择:[包含价格、时间、航空公司和中转次数的 47 个航班]。用户偏好:早上出发,最多一次中转。”
Client 会发起采样请求,让 AI 能够评估各种权衡因素 —— 例如更便宜的红眼航班与更方便的早班机之间的取舍。工具再根据 AI 的分析结果,向用户展示排名前三的推荐方案。
用户交互模型(User Interaction Model)
虽然不是强制要求,但采样机制在设计上支持 “人工介入”(human-in-the-loop)控制。用户可以通过多种方式保持对流程的监督:
审批控制:采样请求可能需要用户的明确同意。Client 可以展示 Server 想要分析的内容以及原因,用户则可以选择批准、拒绝或修改请求。
透明化展示:Client 可以显示完整的提示内容、模型选择和 Token 限制,让用户在将响应返回给 Server 之前,能够先查看 AI 生成的结果。
配置选项:用户可以设置模型偏好,为可信操作配置自动批准,或要求所有操作都必须经过批准。Client 也可以提供选项来自动脱敏敏感信息。
安全考量:在采样过程中,Client 和 Server 都必须妥善处理敏感数据。Client 应实施速率限制并验证所有消息内容。“人工介入” 的设计确保:Server 发起的 AI 交互不会在没有用户明确同意的情况下危害安全性或访问敏感数据。
版本控制(Versioning)
模型上下文协议(MCP)采用基于字符串的版本标识符,格式为 YYYY‑MM‑DD,用于表示上一次引入向后不兼容变更的日期。
只要协议的更新保持向后兼容,协议版本号就不会递增。这样可以在保持互操作性的前提下,进行渐进式改进。
版本状态(Revisions)
版本可以标记为以下状态:
- Draft(草稿):仍在制定中的规范,尚未准备好供使用。
- Current(当前):当前可用的协议版本,可能会继续接收向后兼容的更新。
- Final(最终):已完成的历史版本,不会再发生任何变更。
当前协议版本为:2025‑11‑25。
版本协商(Negotiation)
版本协商在初始化阶段进行。Client 和 Server 可以同时支持多个协议版本,但它们必须为本次会话协商并使用同一个版本。
如果版本协商失败,协议会提供相应的错误处理机制,使 Client 在无法找到与 Server 兼容的版本时能够优雅地终止连接。
连接本地MCP服务(Connect to local MCP servers)
了解如何通过本地 MCP 服务扩展 Claude Desktop 的功能,以实现文件系统访问及其他强大的集成能力
Model Context Protocol(MCP)服务通过提供对本地资源与工具的安全、可控访问,来扩展 AI 应用的功能。目前已有多款客户端支持 MCP,可在不同平台与应用间实现丰富的集成场景。
本指南以 Claude Desktop(众多支持 MCP 的客户端之一)为例,演示如何连接本地 MCP 服务。尽管我们聚焦于 Claude Desktop 的实现方式,但这些概念也适用于其他兼容 MCP 的客户端。完成本教程后,Claude 将能够与你电脑上的文件交互、创建新文档、整理文件夹、检索文件系统 —— 而这一切操作都需要你对每个动作进行明确授权。

前置条件(Prerequisites)
开始本教程前,请确保你的系统已安装以下内容:
Claude Desktop
为你的操作系统下载并安装 Claude Desktop(支持 macOS 和 Windows)。若已安装,可点击 Claude 菜单并选择 “检查更新…”,确认使用的是最新版本。
Node.js
文件系统服务及多数 MCP 服务需要 Node.js 运行。打开终端 / 命令提示符,执行以下命令验证安装:node --version若未安装,可从nodejs.org下载,推荐选择 LTS (长期支持版)以保证稳定性。
理解MCP服务(Understanding MCP Servers)
MCP 服务是运行在你电脑上的程序,通过标准化协议为 Claude Desktop 提供特定功能。每个服务会开放一些工具,Claude 可在你授权后使用这些工具执行操作。我们将安装的 “文件系统服务” 提供以下工具能力:
- 读取文件内容与目录结构
- 创建新文件和文件夹
- 移动、重命名文件
- 按名称或内容搜索文件
所有操作执行前都需要你明确授权,确保你能完全掌控 Claude 可访问和修改的内容。
安装文件系统服务( Installing the Filesystem Server)
这个流程需要配置 Claude Desktop,让它在你启动应用时自动启动文件系统服务。配置是通过一个 JSON 文件完成的 —— 这个文件会告诉 Claude Desktop 要运行哪些服务、以及如何连接这些服务。
- 打开 Claude Desktop 设置
首先进入 Claude Desktop 的设置界面:点击系统菜单栏中的 “Claude” 菜单(注意:不是 Claude 窗口内部的设置),然后选择 “Settings…”。
在 macOS 上,这个菜单位于顶部菜单栏中(会显示 “Settings” 选项):

这会打开 Claude Desktop 的配置窗口(它和你的 Claude 账号设置是分开的)。
- 访问开发者配置
在设置窗口中,点击左侧边栏的 “Developer”(开发者) 标签页。这里包含配置 MCP 服务和其他开发者功能的选项。
点击 “Edit Config” 按钮,打开配置文件:

如果配置文件不存在,这个操作会自动创建一个新的;如果已经存在,则直接打开你现有的配置文件。文件路径如下:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
- 配置文件系统服务
将配置文件里的原有内容替换为以下 JSON 结构。这份配置会告诉 Claude Desktop:启动文件系统服务,并授予它访问指定目录的权限:
macOS
{ "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Desktop", "/Users/username/Downloads" ] } }}
WIndows
{ "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "C:\\Users\\username\\Desktop", "C:\\Users\\username\\Downloads" ] } }}
配置中的 username 替换成你电脑的实际用户名。args 数组里的路径,是文件系统服务允许访问的目录;你可以根据需要修改这些路径,或添加更多目录。
配置项说明
"filesystem":服务的 “昵称”,会显示在 Claude Desktop 里,方便识别"command": "npx":用 Node.js 自带的 npx 工具来启动这个服务"-y":自动确认安装服务相关的包(不用手动输入 “y” 确认)"@modelcontextprotocol/server-filesystem":文件系统服务的官方包名(固定写法)- 剩下的参数:服务能访问的目录路径(可以自定义,比如加个
/Users/username/Documents)
安全注意事项
只授予 Claude 访问你完全放心让它读取和修改的目录。该服务会以你的用户账号权限运行,它能够执行与你手动操作完全相同的所有文件系统操作。
- 重启 Claude Desktop
保存配置文件后,请完全退出 Claude Desktop,然后重新启动应用。只有重启才能加载新的配置并启动 MCP 服务。
成功重启后,你会在对话输入框的右下角看到一个 MCP 服务图标:

点击该图标,可以查看文件系统服务提供的所有可用工具:

如果没有出现服务图标,请参考**故障排除**部分进行排查。
使用文件系统服务(Using the Filesystem Server)
连接文件系统服务后,Claude 就可以与你的文件系统进行交互了。你可以尝试以下示例请求来体验它的功能:
文件管理示例(File Management Examples)
- “你能写一首诗并保存到我的桌面吗?”Claude 会创作一首诗,并在你的桌面上创建一个新的文本文件。
- “我的下载文件夹里有哪些与工作相关的文件?”Claude 会扫描你的下载文件夹,并识别出与工作相关的文档。
- “请把我桌面上所有的图片整理到一个名为‘Images’的新文件夹中。”Claude 会创建该文件夹,并将所有图片文件移动进去。
授权机制说明( How Approval Works)
在执行任何文件系统操作之前,Claude 都会向你请求授权。这样可以确保你始终对所有操作保持完全控制:

请在批准前仔细查看每个请求。如果你对拟执行的操作感到不安,可以随时拒绝。
故障排除( Troubleshooting)
如果你在配置或使用文件系统服务的过程中遇到问题,以下解决方案可应对各类常见问题:
-
服务未在 Claude 中显示 / 锤子图标消失
-
彻底重启 Claude Desktop
-
检查
claude_desktop_config.json文件的语法 -
确认
claude_desktop_config.json中填写的文件路径有效,且为绝对路径而非相对路径 -
查看日志文件,定位服务连接失败的原因
-
在命令行中手动运行服务(将下方
username替换为你在claude_desktop_config.json中填写的实际用户名),查看是否出现报错:macOS/Linux
npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop /Users/username/DownloadsWindows
npx -y @modelcontextprotocol/server-filesystem C:\Users\username\Desktop C:\Users\username\Downloads
-
-
从Claude Desktop中提取日志
Claude.app 中与 MCP 相关的日志会写入以下路径的日志文件:
-
macOS 系统:
~/Library/Logs/Claude -
Windows 系统:
%APPDATA%\Claude\logs -
mcp.log:包含 MCP 连接、连接失败等通用日志信息; -
名为
mcp-server-SERVERNAME.log的文件:包含对应名称服务的错误日志(标准错误输出 stderr)。
你可以执行以下命令查看近期日志,并实时跟踪新增日志(注:Windows 系统下该命令仅显示近期日志,无法实时跟踪):
macOS/Linux
tail -n 20 -f ~/Library/Logs/Claude/mcp*.logWindows
type "%APPDATA%\Claude\logs\mcp*.log" -
-
工具调用无响应
如果 Claude 尝试使用工具,但操作没有任何反应或失败:
- 检查 Claude 的日志中是否有错误信息
- 确认你的服务能够正常构建并运行,没有报错
- 尝试重启 Claude Desktop
-
完全都不行?那改怎么办?
请参考我们的 调试指南,里面提供了更强大的调试工具和更详细的排查步骤。
-
Windows 系统下的 ENOENT 错误与路径中的 ${APPDATA} 变量问题
如果你配置的服务加载失败,且在日志中看到报错信息里的路径包含
${APPDATA},则需在claude_desktop_config.json文件的env字段中,补充%APPDATA%环境变量对应的完整路径值:{ "brave-search": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-brave-search"], "env": { "APPDATA": "C:\\Users\\user\\AppData\\Roaming\\", "BRAVE_API_KEY": "..." } }}完成上述修改后,重新启动 Claude Desktop 即可。
npm 需全局安装
若未全局安装 npm,
npx命令可能会持续执行失败。若 npm 已完成全局安装,你会在系统中找到%APPDATA%\npm目录;若该目录不存在,可执行以下命令完成 npm 的全局安装:npm install -g npm
接下来可以做的事(Next Steps)
既然你已经成功将 Claude Desktop 连接到本地 MCP 服务,接下来可以通过以下方式进一步扩展你的配置:
- 探索其他服务:浏览我们提供的官方及社区创建的 MCP 服务合集,获取更多功能。
- 构建你自己的服务:创建专属于你工作流和集成需求的自定义 MCP 服务。
- 连接到远程服务:了解如何将 Claude 连接到远程 MCP 服务,以使用基于云的工具和服务。
- 理解协议原理:深入了解 MCP 的工作机制及其架构。
连接到远程 MCP 服务(Connect to local MCP servers)
了解如何将 Claude 连接到远程 MCP 服务,并利用互联网托管的工具和数据源扩展其能力。
远程 MCP 服务将 AI 应用的能力扩展到本地环境之外,提供对互联网托管的工具、服务和数据源的访问。通过连接到远程 MCP 服务,你可以将 AI 助手从一个有用的工具转变为一个信息灵通的协作伙伴,使其能够在实时访问外部资源的情况下处理复杂、多步骤的项目。
许多客户端现在都支持远程 MCP 服务,从而实现了广泛的集成可能性。本指南以 Claude 为例(Claude 只是众多支持 MCP 的客户端之一),演示如何连接到远程 MCP 服务。虽然我们重点介绍 Claude 中自定义连接器(Custom Connectors)的实现方式,但这些概念同样适用于其他兼容 MCP 的客户端。
理解远程 MCP 服务( Understanding Remote MCP Servers)
远程 MCP 服务的功能与本地 MCP 服务类似,但它们托管在互联网上,而不是你的本地机器上。它们暴露工具、提示词和资源,供 Claude 代表你执行任务。这些服务可以与各种服务集成,例如项目管理工具、文档系统、代码仓库以及任何其他支持 API 的服务。
远程 MCP 服务的主要优势在于其可访问性。与需要在每台设备上安装和配置的本地服务不同,远程服务可以从任何具有互联网连接的 MCP 客户端访问。这使它们非常适合基于 Web 的 AI 应用、强调易用性的集成,以及需要服务端处理或身份验证的服务。
什么是自定义连接器?(What are Custom Connectors?)
自定义连接器是 Claude 与远程 MCP 服务之间的桥梁。它们让你可以将 Claude 直接连接到对你的工作流最重要的工具和数据源,使 Claude 能够在你常用的软件中运行,并从你的外部工具的完整上下文中提取洞察。
借助自定义连接器,你可以:
连接到远程 MCP 服务(Connecting to a Remote MCP Server)
将 Claude 连接到远程 MCP 服务的过程,是通过 Claude 界面添加一个自定义连接器来完成的。这会在 Claude 与你选择的远程服务之间建立一条安全连接。
-
导航到连接器设置
在浏览器中打开 Claude,并进入设置页面。你可以点击个人资料图标,然后从下拉菜单中选择 “设置”。进入设置后,在侧边栏中找到并点击 “连接器” 部分。
这将显示你当前已配置的连接器,并提供添加新连接器的选项。
-
添加自定义连接器
在 “连接器” 部分中,向下滚动到页面底部,你会看到 “添加自定义连接器” 按钮。点击此按钮开始连接流程。

随后会弹出一个对话框,提示你输入远程 MCP 服务的 URL。该 URL 应由服务开发者或管理员提供。请输入完整的 URL,确保包含正确的协议(https://)以及任何必要的路径部分。

输入 URL 后,点击 “添加” 以继续连接流程。
-
完成身份验证
大多数远程 MCP 服务都需要身份验证,以确保对其资源的安全访问。身份验证流程会因服务实现方式而异,但通常包括 OAuth、API 密钥或用户名 / 密码等方式。

请按照服务提供的身份验证提示进行操作。这可能会将你重定向到第三方身份验证提供方,或在 Claude 内部显示一个表单。身份验证完成后,Claude 将与远程服务建立安全连接。
-
访问资源与提示词
连接成功后,远程服务提供的资源和提示词将在你的 Claude 对话中可用。你可以通过点击消息输入区域的回形针图标来访问它们,这会打开附件菜单。

该菜单会列出所有已连接服务提供的资源和提示词。选择你想要在对话中包含的项目。这些资源会为 Claude 提供来自外部工具的上下文和信息。

-
配置工具权限
远程 MCP 服务通常会暴露多种能力不同的工具。你可以在连接器设置中配置权限,来控制 Claude 被允许使用哪些工具。这样可以确保 Claude 只执行你已明确授权的操作。

返回 “连接器” 设置,点击你已连接的服务。在这里,你可以根据需要启用或禁用特定工具、设置使用限制,并配置其他安全参数。
使用远程 MCP 服务的最佳实践(Best Practices for Using Remote MCP Servers)
在使用远程 MCP 服务时,请考虑以下建议,以确保安全且高效的使用体验:
安全注意事项:在连接之前,务必验证远程 MCP 服务的真实性。只连接来自可信来源的服务,并仔细查看身份验证过程中请求的权限。在授予访问敏感数据或系统的权限时要保持谨慎。
管理多个连接器:你可以同时连接到多个远程 MCP 服务。建议按用途或项目对连接器进行分类,以保持清晰。定期审查并移除不再使用的连接器,以保持工作区的整洁和安全。
接下来可以做的事(Next Steps)
现在你已经成功将 Claude 连接到远程 MCP 服务,可以在对话中探索其功能。尝试使用已连接的工具来自动化任务、访问外部数据,或与你现有的工作流程集成。
-
构建你自己的远程服务:创建自定义远程 MCP 服务,以与你专有的工具和服务集成。
-
探索可用的服务:浏览我们提供的官方及社区创建的 MCP 服务集合。
-
连接本地服务:了解如何将 Claude Desktop 连接到本地 MCP 服务,以实现对系统的直接访问。
-
理解架构深入:了解 MCP 的工作原理及其整体架构。
远程 MCP 服务为扩展 Claude 的能力解锁了很大的可能性。随着你逐渐熟悉这些集成方式,你会发现更多能够简化工作流程、更高效地完成复杂任务的新方法。
构建 MCP 服务(Build an MCP server)
开始构建你自己的服务,以便在 Claude for Desktop 和其他客户端中使用。
在本教程中,我们将构建一个简单的 MCP 天气服务,并将其连接到一个宿主(在本例中为 Claude for Desktop)。
我们将要构建的内容( What we’ll be building)
我们将构建一个暴露两个工具的服务:get_alerts 和 get_forecast。然后,我们会将该服务连接到一个 MCP 宿主(这里选择 Claude for Desktop)。

服务可以连接到任何客户端。为了简单起见,我们在这里选择了 Claude for Desktop,但我们也提供了构建你自己的客户端的指南,以及其他客户端的列表。
MCP 核心概念(Core MCP Concepts)
MCP 服务可以提供三种主要类型的能力:
-
资源(Resources):可被客户端读取的类文件数据(例如 API 响应或文件内容)
-
工具(Tools):可由 LLM 调用的函数(需用户批准)
-
提示词(Prompts):帮助用户完成特定任务的预写模板
本教程将主要聚焦于工具。
Python
让我们开始构建我们的天气服务吧!你可以在此处找到我们将要构建的完整代码。
前置知识
本快速入门教程假定你已熟悉以下内容:
- Python 编程语言
- Claude 等大语言模型(LLM)
MCP 服务中的日志记录
实现 MCP 服务时,需谨慎处理日志记录方式:
对于基于 STDIO 的服务:切勿向标准输出(stdout)中写入内容。这包括:
-
Python 中的
print()语句 -
JavaScript 中的
console.log() -
Go 中的
fmt.Println() -
其他语言中类似的标准输出函数
向 stdout 写入内容会破坏 JSON-RPC 消息格式,导致服务无法正常工作。
对于基于 HTTP 的服务:标准输出日志记录是可行的,因为它不会干扰 HTTP 响应。
最佳实践
-
使用可写入标准错误流(stderr)或文件的日志库。
-
对于 Python 需特别注意:
print()默认写入标准输出(stdout)。
快速示例
❌ 错误示例(适用于 STDIO 服务)print("Processing request")✅ 正确示例(适用于 STDIO 服务)import logginglogging.info("Processing request")
系统要求
- 已安装 Python 3.10 或更高版本。
- 必须使用 1.2.0 或更高版本的 Python MCP SDK。
搭建环境
首先,安装 uv 并搭建 Python 项目及环境:
macOS/Linux 系统
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows 系统
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
请务必重启终端,确保 uv 命令能被识别。
接下来,创建并配置项目:
macOS/Linux 系统
# 为项目创建新目录uv init weathercd weather# 创建并激活虚拟环境uv venvsource .venv/bin/activate# 安装依赖包uv add "mcp[cli]" httpx# 创建服务文件touch weather.py
Windows 系统
# 为项目创建新目录uv init weathercd weather# 创建并激活虚拟环境uv venv.venv\Scripts\activate# 安装依赖包uv add mcp[cli] httpx# 创建服务文件new-item weather.py
接下来我们深入讲解如何构建你的 MCP 服务。
构建你的服务( Building your server)
导入包并初始化实例( Importing packages and setting up the instance)
将以下代码添加到你的 weather.py 文件顶部:
from typing import Anyimport httpxfrom mcp.server.fastmcp import FastMCP# 初始化 FastMCP 服务mcp = FastMCP("weather")# 常量NWS_API_BASE = "https://api.weather.gov"USER_AGENT = "weather-app/1.0"
FastMCP 类利用 Python 类型提示(type hints)和文档字符串(docstrings)来自动生成工具定义,让 MCP 工具的创建和维护变得简单。
辅助函数(Helper functions)
接下来,我们添加用于查询和格式化美国国家气象局(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 Nonedef 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")}"""
实现工具执行逻辑( Implementing tool execution)
工具执行处理器负责实际执行每个工具的业务逻辑。我们来添加这部分代码:
@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)
运行服务( Running the server)
最后,我们来初始化并启动这个 MCP 服务:
def main(): # Initialize and run the server mcp.run(transport="stdio")if __name__ == "__main__": main()
你的 MCP 服务已经开发完成!运行 uv run weather.py 启动该服务,服务启动后会监听来自 MCP 宿主的消息。
现在,我们通过一个现成的 MCP 宿主(Claude for Desktop)来测试你的服务。
通过 Claude for Desktop 测试你的服务(Testing your server with Claude for Desktop)
Claude for Desktop 目前暂未在 Linux 系统上架。Linux 用户可参考《构建客户端》教程,搭建一个能连接我们刚开发的 MCP 服务的 MCP 客户端。
首先,请确保你已安装 Claude for Desktop。你可在此处下载安装最新版本;若已安装,请确保更新至最新版本。
我们需要为所有想要使用的 MCP 服务配置 Claude for Desktop。操作方式:用文本编辑器打开 Claude for Desktop 的应用配置文件,路径为 ~/Library/Application Support/Claude/claude_desktop_config.json,若文件不存在,请手动创建。
例如,若你安装了 VS Code,可通过以下命令打开配置文件:
macOS/Linux 系统
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
Windows 系统
code $env:AppData\Claude\claude_desktop_config.json
接下来,你需要在配置文件的 mcpServers 字段中添加你的服务配置。只有至少正确配置一个服务后,Claude for Desktop 才会显示 MCP 相关的 UI 界面。
本示例中,我们添加唯一的天气服务,配置如下:
macOS/Linux 系统
{ "mcpServers": { "weather": { "command": "uv", "args": [ "--directory", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather", "run", "weather.py" ] } }}
Windows 系统
{ "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 服务;
- Claude for Desktop 可以通过执行命令
uv --directory C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather run weather.py来启动该服务。
保存配置文件后,需要重启 Claude for Desktop 使配置生效。
通过命令进行测试
接下来,我们需要确认 Claude for Desktop 是否成功识别到了我们在 weather 服务中暴露的两个工具。你可以通过点击界面中的 “Add files, connectors, and more /” 图标来查看:

点击加号图标后,将鼠标悬停在 “Connectors” 菜单上,你应该能看到列出的 weather 服务:

如果你的服务没有被 Claude for Desktop 识别到,请前往 “故障排除” 部分查看调试建议。
如果服务已经出现在 “Connectors” 菜单中,你就可以在 Claude for Desktop 中输入以下命令来测试你的服务:
- Sacramento 的天气怎么样?
- Texas 当前有哪些生效的天气预警?


由于我们使用的是美国国家气象局(US National Weather Service)的 API,因此这些查询只能用于美国境内的地点。
TypeScript
让我们开始构建我们的天气服务吧!你可以在此处找到我们将要构建的完整代码。
前置知识
本快速入门教程假定你已熟悉以下内容:
- TypeScript
- Claude 等大语言模型(LLM)
MCP 服务中的日志记录
实现 MCP 服务时,需谨慎处理日志记录方式:
对于基于 STDIO 的服务:切勿向标准输出(stdout)中写入内容。这包括:
-
Python 中的
print()语句 -
JavaScript 中的
console.log() -
Go 中的
fmt.Println() -
其他语言中类似的标准输出函数
向 stdout 写入内容会破坏 JSON-RPC 消息格式,导致服务无法正常工作。
对于基于 HTTP 的服务:标准输出日志记录是可行的,因为它不会干扰 HTTP 响应。
最佳实践
- 使用支持写入标准错误输出(stderr)或文件的日志库,例如 Python 中的 logging 库。
- 对于 JavaScript 需特别注意 ——console.log () 默认会写入 stdout。
快速示例
❌ 错误示例(适用于 STDIO 服务)console.log("Server started");✅ 正确示例(适用于 STDIO 服务)console.error("Server started"); // stderr is safe
系统要求
对于 TypeScript 开发环境,请确保安装了最新版本的 Node。
配置开发环境
首先,如果你尚未安装 Node.js 和 npm,请先完成安装。你可以从 nodejs.org 下载安装包。验证 Node.js 安装是否成功:
node --versionnpm --version
本教程要求使用 Node.js 16 或更高版本。
接下来,我们创建并配置项目:
macOS/Linux 系统
# Create a new directory for our projectmkdir weathercd weather# Initialize a new npm projectnpm init -y# Install dependenciesnpm install @modelcontextprotocol/sdk zod@3npm install -D @types/node typescript# Create our filesmkdir srctouch src/index.ts
Windows 系统
# Create a new directory for our projectmd weathercd weather# Initialize a new npm projectnpm init -y# Install dependenciesnpm install @modelcontextprotocol/sdk zod@3npm install -D @types/node typescript# Create our filesmd srcnew-item src\index.ts
更新你的 package.json 文件,添加 type: “module” 配置项和一个build脚本:
{ "type": "module", "bin": { "weather": "./build/index.js" }, "scripts": { "build": "tsc && chmod 755 build/index.js" }, "files": ["build"]}
在你的项目根目录下创建一个 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"]}
接下来我们深入讲解如何构建你的 MCP 服务。
构建你的服务( Building your server)
导入包并初始化实例( Importing packages and setting up the instance)
将以下代码添加到你的 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 instanceconst server = new McpServer({ name: "weather", version: "1.0.0",});
辅助函数(Helper functions)
接下来,我们添加用于查询和格式化美国国家气象局(National Weather Service)API 数据的辅助函数:
// Helper function for making NWS API requestsasync 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 datafunction 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[]; };}
实现工具执行逻辑( Implementing tool execution)
工具执行处理器负责实际执行每个工具的业务逻辑。我们来添加这部分代码:
// Register weather toolsserver.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 === 0) { 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, }, ], }; },);
运行 MCP 服务最后,实现用于启动服务的主函数:
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 测试你的服务(Testing your server with Claude for Desktop)
Claude for Desktop 目前暂未在 Linux 系统上架。Linux 用户可参考《构建客户端》教程,搭建一个能连接我们刚开发的 MCP 服务的 MCP 客户端。
首先,请确保你已安装 Claude for Desktop。你可在此处下载安装最新版本;若已安装,请确保更新至最新版本。
我们需要为所有想要使用的 MCP 服务配置 Claude for Desktop。操作方式:用文本编辑器打开 Claude for Desktop 的应用配置文件,路径为 ~/Library/Application Support/Claude/claude_desktop_config.json,若文件不存在,请手动创建。
例如,若你安装了 VS Code,可通过以下命令打开配置文件:
macOS/Linux 系统
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
Windows 系统
code $env:AppData\Claude\claude_desktop_config.json
接下来,你需要在配置文件的 mcpServers 字段中添加你的服务配置。只有至少正确配置一个服务后,Claude for Desktop 才会显示 MCP 相关的 UI 界面。
本示例中,我们添加唯一的天气服务,配置如下:
macOS/Linux 系统
{ "mcpServers": { "weather": { "command": "node", "args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"] } }}
Windows 系统
{ "mcpServers": { "weather": { "command": "node", "args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"] } }}
这份配置的含义是:
- 配置了一个名为 “weather” 的 MCP 服务;
- Claude for Desktop 可以通过执行命令
node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js来启动该服务。
保存配置文件后,需要重启 Claude for Desktop 使配置生效。
通过命令进行测试
接下来,我们需要确认 Claude for Desktop 是否成功识别到了我们在 weather 服务中暴露的两个工具。你可以通过点击界面中的 “Add files, connectors, and more /” 图标来查看:

点击加号图标后,将鼠标悬停在 “Connectors” 菜单上,你应该能看到列出的 weather 服务:

如果你的服务没有被 Claude for Desktop 识别到,请前往 “故障排除” 部分查看调试建议。
如果服务已经出现在 “Connectors” 菜单中,你就可以在 Claude for Desktop 中输入以下命令来测试你的服务:
- Sacramento 的天气怎么样?
- Texas 当前有哪些生效的天气预警?


由于我们使用的是美国国家气象局(US National Weather Service)的 API,因此这些查询只能用于美国境内的地点。
Java
这是一个基于 Spring AI MCP 自动配置和启动器的快速入门示例。如需了解如何手动创建同步和异步 MCP 服务,请参考 Java SDK Server 文档。
让我们开始构建我们的天气服务吧!你可以在此处找到我们将要构建的完整代码。
如需更多信息,请参阅 MCP Server Boot Starter 参考文档。对于手动实现 MCP 服务的场景,请参考 MCP Server Java SDK 文档。
MCP 服务中的日志记录
实现 MCP 服务时,需谨慎处理日志记录方式:
对于基于 STDIO 的服务:切勿向标准输出(stdout)中写入内容。这包括:
-
Python 中的
print()语句 -
JavaScript 中的
console.log() -
Go 中的
fmt.Println() -
其他语言中类似的标准输出函数
向 stdout 写入内容会破坏 JSON-RPC 消息格式,导致服务无法正常工作。
对于基于 HTTP 的服务:标准输出日志记录是可行的,因为它不会干扰 HTTP 响应。
最佳实践
- 使用可写入标准错误流(stderr)或文件的日志库。
- 确保所有已配置的日志库均不会向 STDOUT 写入内容。
系统要求
-
已安装 Java 17 或更高版本。
-
Spring Boot 3.3.x 或更高版本。
环境搭建
使用 Spring Initializer 初始化项目。你需要添加以下依赖项:
Maven:
<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>
Gradel:
dependencies { implementation platform("org.springframework.ai:spring-ai-starter-mcp-server") implementation platform("org.springframework:spring-web")}
然后通过设置应用属性来配置你的应用:
application.properties
spring.main.bannerMode=offlogging.pattern.console=
application.yml
logging: pattern: console:spring: main: banner-mode: off
Server Configuration Properties 文档中列出了所有可用的配置项。
接下来我们深入讲解如何构建你的 MCP 服务。
构建你的服务( Building your server)
天气服务(Weather Service)
接下来我们实现一个 WeatherService.java 类,该类将通过 REST 客户端从美国国家气象局(National Weather Service)API 中查询数据:
@Servicepublic 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 服务中。
创建你的 Boot 应用程序(Create your Boot Application)
@SpringBootApplicationpublic 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 工具类将 @Tool 注解标注的方法转换为 MCP 服务可调用的可执行回调函数。
运行 MCP 服务( Running the server)
最后,我们来构建该服务:
./mvnw clean install
该命令会在 target 目录下生成一个 mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar 文件。
现在,我们通过一个现成的 MCP 宿主(Claude for Desktop)来测试你的服务。
通过 Claude for Desktop 测试你的服务(Testing your server with Claude for Desktop)
Claude for Desktop 目前暂未在 Linux 系统上架。
首先,请确保你已安装 Claude for Desktop。你可在此处下载安装最新版本;若已安装,请确保更新至最新版本。
我们需要为所有想要使用的 MCP 服务配置 Claude for Desktop。操作方式:用文本编辑器打开 Claude for Desktop 的应用配置文件,路径为 ~/Library/Application Support/Claude/claude_desktop_config.json,若文件不存在,请手动创建。
例如,若你安装了 VS Code,可通过以下命令打开配置文件:
macOS/Linux 系统
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
Windows 系统
code $env:AppData\Claude\claude_desktop_config.json
接下来,你需要在配置文件的 mcpServers 字段中添加你的服务配置。只有至少正确配置一个服务后,Claude for Desktop 才会显示 MCP 相关的 UI 界面。
本示例中,我们添加唯一的天气服务,配置如下:
macOS/Linux 系统
{ "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" ] } }}
Windows 系统
{ "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" ] } }}
请确保传入你的 MCP 服务对应的绝对路径。
这份配置的含义是:
- 配置了一个名为 “my-weather-server” 的 MCP 服务;
- Claude for Desktop 可以通过执行命令
java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar来启动该服务。
保存配置文件后,需要重启 Claude for Desktop 使配置生效。
通过 Java 客户端 测试你的服务( Testing your server with Java client)
手动创建一个MCP客户端( Create an MCP Client manually)
用 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(Use 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 (Anthropic 公司推出的桌面版 Claude 客户端)配置:
spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
当你启动客户端应用程序时,自动配置会从 claude_desktop_config.json 文件中自动创建 MCP 客户端实例。
如需了解更多信息,请参阅 MCP Client Boot Starters 参考文档。
更多 Java 版 MCP 服务示例(More Java MCP Server examples)
starter-webflux-server 示例展示了如何基于 SSE 传输方式创建 MCP 服务。该示例演示了如何借助 Spring Boot 的自动配置能力,定义并注册 MCP 工具、资源(Resource)和提示词(Prompts)。
通过命令进行测试
接下来,我们需要确认 Claude for Desktop 是否成功识别到了我们在 weather 服务中暴露的两个工具。你可以通过点击界面中的 “Add files, connectors, and more /” 图标来查看:

点击加号图标后,将鼠标悬停在 “Connectors” 菜单上,你应该能看到列出的 weather 服务:

如果你的服务没有被 Claude for Desktop 识别到,请前往 “故障排除” 部分查看调试建议。
如果服务已经出现在 “Connectors” 菜单中,你就可以在 Claude for Desktop 中输入以下命令来测试你的服务:
- Sacramento 的天气怎么样?
- Texas 当前有哪些生效的天气预警?


由于我们使用的是美国国家气象局(US National Weather Service)的 API,因此这些查询只能用于美国境内的地点。
Kotlin
让我们开始构建我们的天气服务吧!你可以在此处找到我们将要构建的完整代码。
前置知识
本快速入门教程假定你已熟悉以下内容:
- Kotlin
- Claude 等大语言模型(LLM)
系统要求
已安装 Java 17 或更高版本。
搭建环境
首先,如果你尚未安装 java 和 gradle,请先完成安装。你可以从 Oracle JDK 官方网站下载 java。验证 java 安装是否成功:
java --version
接下来,创建并配置项目:
macOS/Linux 系统
# Create a new directory for our projectmkdir weathercd weather# Initialize a new kotlin projectgradle init
Windows 系统
# Create a new directory for our projectmd weathercd weather# Initialize a new kotlin projectgradle init
运行 gradle init 命令后,会出现项目创建选项。选择 “Application” 作为项目类型,“Kotlin” 作为编程语言,“Java 17” 作为 Java 版本。
此外,你也可以通过 IntelliJ IDEA 的项目向导创建 Kotlin 应用程序。
创建项目后,添加以下依赖项:
build.gradle.kts
val mcpVersion = "0.4.0"val slf4jVersion = "2.0.9"val ktorVersion = "3.1.1"dependencies { implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") implementation("org.slf4j:slf4j-nop:$slf4jVersion") implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")}
build.gradle
def mcpVersion = '0.3.0'def slf4jVersion = '2.0.9'def ktorVersion = '3.1.1'dependencies { implementation "io.modelcontextprotocol:kotlin-sdk:$mcpVersion" implementation "org.slf4j:slf4j-nop:$slf4jVersion" implementation "io.ktor:ktor-client-content-negotiation:$ktorVersion" implementation "io.ktor:ktor-serialization-kotlinx-json:$ktorVersion"}
同时,在构建脚本中添加以下插件:
build.gradle.kts
plugins { kotlin("plugin.serialization") version "your_version_of_kotlin" id("com.gradleup.shadow") version "8.3.9"}
build.gradle
plugins { id 'org.jetbrains.kotlin.plugin.serialization' version 'your_version_of_kotlin' id 'com.gradleup.shadow' version '8.3.9'}
现在,让我们开始深入构建你的 MCP 服务。
构建你的服务( Building your server)
初始化实例( Setting up the instance)
添加一个服务初始化函数:
// Main function to run the MCP serverfun `run mcp server`() { // Create the MCP Server instance with a basic implementation val server = Server( Implementation( name = "weather", // Tool name is "weather" version = "1.0.0" // Version of the implementation ), ServerOptions( capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)) ) ) // Create a transport using standard IO for server communication val transport = StdioServerTransport( System.`in`.asInput(), System.out.asSink().buffered() ) runBlocking { server.connect(transport) val done = Job() server.onClose { done.complete() } done.join() }}
天气 API 辅助函数( Weather API helper functions)
接下来,我们添加用于查询美国国家气象局 API 并转换其响应数据的函数和数据类:
// Extension function to fetch forecast information for given latitude and longitudesuspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List<String> { val points = this.get("/points/$latitude,$longitude").body<Points>() val forecast = this.get(points.properties.forecast).body<Forecast>() return forecast.properties.periods.map { period -> """ ${period.name}: Temperature: ${period.temperature} ${period.temperatureUnit} Wind: ${period.windSpeed} ${period.windDirection} Forecast: ${period.detailedForecast} """.trimIndent() }}// Extension function to fetch weather alerts for a given statesuspend fun HttpClient.getAlerts(state: String): List<String> { val alerts = this.get("/alerts/active/area/$state").body<Alert>() return alerts.features.map { feature -> """ Event: ${feature.properties.event} Area: ${feature.properties.areaDesc} Severity: ${feature.properties.severity} Description: ${feature.properties.description} Instruction: ${feature.properties.instruction} """.trimIndent() }}@Serializabledata class Points( val properties: Properties) { @Serializable data class Properties(val forecast: String)}@Serializabledata class Forecast( val properties: Properties) { @Serializable data class Properties(val periods: List<Period>) @Serializable data class Period( val number: Int, val name: String, val startTime: String, val endTime: String, val isDaytime: Boolean, val temperature: Int, val temperatureUnit: String, val temperatureTrend: String, val probabilityOfPrecipitation: JsonObject, val windSpeed: String, val windDirection: String, val shortForecast: String, val detailedForecast: String, )}@Serializabledata class Alert( val features: List<Feature>) { @Serializable data class Feature( val properties: Properties ) @Serializable data class Properties( val event: String, val areaDesc: String, val severity: String, val description: String, val instruction: String?, )}
实现工具执行逻辑( Implementing tool execution)
工具执行处理器负责实际执行每个 MCP 工具的业务逻辑。接下来我们添加该处理器:
// Create an HTTP client with a default request configuration and JSON content negotiationval httpClient = HttpClient { defaultRequest { url("https://api.weather.gov") headers { append("Accept", "application/geo+json") append("User-Agent", "WeatherApiClient/1.0") } contentType(ContentType.Application.Json) } // Install content negotiation plugin for JSON serialization/deserialization install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }}// Register a tool to fetch weather alerts by stateserver.addTool( name = "get_alerts", description = """ Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY) """.trimIndent(), inputSchema = Tool.Input( 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["state"]?.jsonPrimitive?.content if (state == null) { return@addTool CallToolResult( content = listOf(TextContent("The 'state' parameter is required.")) ) } val alerts = httpClient.getAlerts(state) CallToolResult(content = alerts.map { TextContent(it) })}// Register a tool to fetch weather forecast by latitude and longitudeserver.addTool( name = "get_forecast", description = """ Get weather forecast for a specific latitude/longitude """.trimIndent(), inputSchema = Tool.Input( properties = buildJsonObject { putJsonObject("latitude") { put("type", "number") } putJsonObject("longitude") { put("type", "number") } }, required = listOf("latitude", "longitude") )) { request -> val latitude = request.arguments["latitude"]?.jsonPrimitive?.doubleOrNull val longitude = request.arguments["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) })}
运行 MCP 服务( Running the server)
最后,实现用于启动服务的 main 函数:
fun main() = `run mcp server`()
请务必运行 ./gradlew build 命令来构建你的 MCP 服务。这是确保你的服务能够正常建立连接的关键步骤。
现在,我们通过一个现成的 MCP 宿主(Claude for Desktop)来测试你的服务。
通过 Claude for Desktop 测试你的服务(Testing your server with Claude for Desktop)
Claude for Desktop 目前暂未在 Linux 系统上架。Linux 用户可参考《构建客户端》教程,搭建一个能连接我们刚开发的 MCP 服务的 MCP 客户端。
首先,请确保你已安装 Claude for Desktop。你可在此处下载安装最新版本;若已安装,请确保更新至最新版本。
我们需要为所有想要使用的 MCP 服务配置 Claude for Desktop。操作方式:用文本编辑器打开 Claude for Desktop 的应用配置文件,路径为 ~/Library/Application Support/Claude/claude_desktop_config.json,若文件不存在,请手动创建。
例如,若你安装了 VS Code,可通过以下命令打开配置文件:
macOS/Linux 系统
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
Windows 系统
code $env:AppData\Claude\claude_desktop_config.json
接下来,你需要在配置文件的 mcpServers 字段中添加你的服务配置。只有至少正确配置一个服务后,Claude for Desktop 才会显示 MCP 相关的 UI 界面。
本示例中,我们添加唯一的天气服务,配置如下:
macOS/Linux 系统
{ "mcpServers": { "weather": { "command": "java", "args": [ "-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar" ] } }}
Windows 系统
{ "mcpServers": { "weather": { "command": "java", "args": [ "-jar", "C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\libs\\weather-0.1.0-all.jar" ] } }}
这份配置的含义是:
- 配置了一个名为 “weather” 的 MCP 服务;
- Claude for Desktop 可以通过执行命令
java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar来启动该服务。
保存配置文件后,需要重启 Claude for Desktop 使配置生效。
通过命令进行测试
接下来,我们需要确认 Claude for Desktop 是否成功识别到了我们在 weather 服务中暴露的两个工具。你可以通过点击界面中的 “Add files, connectors, and more /” 图标来查看:

点击加号图标后,将鼠标悬停在 “Connectors” 菜单上,你应该能看到列出的 weather 服务:

如果你的服务没有被 Claude for Desktop 识别到,请前往 “故障排除” 部分查看调试建议。
如果服务已经出现在 “Connectors” 菜单中,你就可以在 Claude for Desktop 中输入以下命令来测试你的服务:
- Sacramento 的天气怎么样?
- Texas 当前有哪些生效的天气预警?


由于我们使用的是美国国家气象局(US National Weather Service)的 API,因此这些查询只能用于美国境内的地点。
C#
让我们开始构建我们的天气服务吧!你可以在此处找到我们将要构建的完整代码。
前置知识
本快速入门教程假定你已熟悉以下内容:
- C#
- Claude 等大语言模型(LLM)
- .NET8 或更高版本
MCP 服务中的日志记录
实现 MCP 服务时,需谨慎处理日志记录方式:
对于基于 STDIO 的服务:切勿向标准输出(stdout)中写入内容。这包括:
-
Python 中的
print()语句 -
JavaScript 中的
console.log() -
Go 中的
fmt.Println() -
其他语言中类似的标准输出函数
向 stdout 写入内容会破坏 JSON-RPC 消息格式,导致服务无法正常工作。
对于基于 HTTP 的服务:标准输出日志记录是可行的,因为它不会干扰 HTTP 响应。
最佳实践
- 使用可写入标准错误输出(stderr)或文件的日志库
系统要求
- 已安装 .NET 8 SDK 或更高版本
搭建环境
首先,如果你尚未安装 dotnet,请先完成安装。你可以从微软 .NET 官方网站下载 dotnet。验证你的 dotnet 安装是否成功:
dotnet --version
接下来,创建并配置项目:
macOS/Linux 系统
# Create a new directory for our projectmkdir weathercd weather# Initialize a new C# projectdotnet new console
Windows 系统
# Create a new directory for our projectmkdir weathercd weather# Initialize a new C# projectdotnet new console
运行 dotnet new console 命令后,会生成一个新的 C# 控制台项目。你可以在常用的集成开发环境(IDE)中打开该项目,例如 Visual Studio 或 Rider。此外,你也可以通过 Visual Studio 的项目向导创建 C# 应用程序。创建项目后,需添加 Model Context Protocol SDK 及宿主相关的 NuGet 包:
# Add the Model Context Protocol SDK NuGet packagedotnet add package ModelContextProtocol --prerelease# Add the .NET Hosting NuGet packagedotnet add package Microsoft.Extensions.Hosting
现在,让我们开始深入构建你的 MCP 服务。
构建你的服务( Building your server)
打开项目中的 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方法。这能保证 MCP 服务不会向控制台输出任何额外信息。该要求仅针对采用 STDIO 传输方式的服务。
这段代码搭建了一个基础的控制台应用程序,该程序借助 Model Context Protocol SDK,创建了一个采用标准输入输出(STDIO)传输方式的 MCP 服务。
天气 API 辅助函数( Weather API helper functions)
为 HttpClient 创建一个扩展类,该类可帮助简化 JSON 请求的处理逻辑:
using System.Text.Json;internal static class HttpClientExt{ public static async Task<JsonDocument> ReadJsonDocumentAsync(this HttpClient client, string requestUri) {c using var response = await client.GetAsync(requestUri); response.EnsureSuccessStatusCode(); return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); }}
接下来,定义一个包含工具执行处理器的类,该类用于查询美国国家气象局 API 并转换其响应数据:
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()} """)); }}
运行 MCP 服务( Running the server)
最后,通过以下命令启动服务:
dotnet run
这会启动 MCP 服务,并开始监听来自标准输入 / 输出(STDIO)的请求。
通过 Claude for Desktop 测试你的服务(Testing your server with Claude for Desktop)
Claude for Desktop 目前暂未在 Linux 系统上架。Linux 用户可参考《构建客户端》教程,搭建一个能连接我们刚开发的 MCP 服务的 MCP 客户端。
首先,请确保你已安装 Claude for Desktop。你可在此处下载安装最新版本;若已安装,请确保更新至最新版本。
我们需要为所有想要使用的 MCP 服务配置 Claude for Desktop。操作方式:用文本编辑器打开 Claude for Desktop 的应用配置文件,路径为 ~/Library/Application Support/Claude/claude_desktop_config.json,若文件不存在,请手动创建。
例如,若你安装了 VS Code,可通过以下命令打开配置文件:
macOS/Linux 系统
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
Windows 系统
code $env:AppData\Claude\claude_desktop_config.json
接下来,你需要在配置文件的 mcpServers 字段中添加你的服务配置。只有至少正确配置一个服务后,Claude for Desktop 才会显示 MCP 相关的 UI 界面。
本示例中,我们添加唯一的天气服务,配置如下:
macOS/Linux 系统
{ "mcpServers": { "weather": { "command": "dotnet", "args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"] } }}
Windows 系统
{ "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 是否成功识别到了我们在 weather 服务中暴露的两个工具。你可以通过点击界面中的 “Add files, connectors, and more /” 图标来查看:

点击加号图标后,将鼠标悬停在 “Connectors” 菜单上,你应该能看到列出的 weather 服务:

如果你的服务没有被 Claude for Desktop 识别到,请前往 “故障排除” 部分查看调试建议。
如果服务已经出现在 “Connectors” 菜单中,你就可以在 Claude for Desktop 中输入以下命令来测试你的服务:
- Sacramento 的天气怎么样?
- Texas 当前有哪些生效的天气预警?


由于我们使用的是美国国家气象局(US National Weather Service)的 API,因此这些查询只能用于美国境内的地点。
Rust
让我们开始构建我们的天气服务吧!你可以在此处找到我们将要构建的完整代码。
前置知识
本快速入门教程假定你已熟悉以下内容:
- Rust 编程语言
- Rust 中的异步 / 等待(async/await)机制
- Claude 等大语言模型(LLM)
MCP 服务中的日志记录
实现 MCP 服务时,需谨慎处理日志记录方式:
对于基于 STDIO 的服务:切勿向标准输出(stdout)中写入内容。这包括:
-
Python 中的
print()语句 -
JavaScript 中的
console.log() -
Go 中的
fmt.Println() -
其他语言中类似的标准输出函数
向 stdout 写入内容会破坏 JSON-RPC 消息格式,导致服务无法正常工作。
对于基于 HTTP 的服务:标准输出日志记录是可行的,因为它不会干扰 HTTP 响应。
最佳实践
- 使用可写入标准错误输出(stderr)或文件的日志库,例如 Rust 中的 tracing 库或 log 库。
- 配置日志框架以避免向标准输出(stdout)输出内容。
快速示例
// ❌ Bad (STDIO)println!("Processing request");// ✅ Good (STDIO)use tracing::info;info!("Processing request"); // writes to stderr
系统要求
- 已安装 Rust 1.70 或更高版本
- 安装 Cargo(通常随 Rust 安装一起提供)。
搭建环境
首先,如果你尚未安装 Rust,请先完成安装。你可以从 rust-lang.org 安装 Rust:
macOS/Linux 系统
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Windows 系统
# Download and run rustup-init.exe from https://rustup.rs/
验证 Rust 安装结果:
rustc --versioncargo --version
接下来,创建并配置我们的项目:
macOS/Linux 系统
# Create a new Rust projectcargo new weathercd weather
Windows 系统
# Create a new Rust projectcargo new weathercd weather
更新 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"] }
现在,让我们开始深入构建你的 MCP 服务。
构建你的服务( Building your server)
导入包和常量(Importing packages and constants)
打开 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 包为 Rust 语言提供了 Model Context Protocol(模型上下文协议)SDK,包含用于 MCP 服务实现、过程宏(procedural macros)以及标准输入输出(stdio)传输的相关功能。
数据结构(Data structures)
接下来,我们定义用于反序列化美国国家气象局(National Weather Service)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,}
接下来定义 MCP 客户端(Client)将要发送的请求类型:
#[derive(serde::Deserialize, schemars::JsonSchema)]pub struct MCPForecastRequest { latitude: f32, longitude: f32,}#[derive(serde::Deserialize, schemars::JsonSchema)]pub struct MCPAlertRequest { state: String,}
辅助函数(Helper functions)
添加用于发起 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 )}
实现天气 MCP 服务及工具(Implementing the Weather server and tools)
接下来,我们基于工具处理器(tool handlers)实现核心的天气 MCP 服务结构体:
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 工具(Tool)。
实现 ServerHandler 特征( Implementing the ServerHandler)
实现 ServerHandler 特征(trait)以定义 MCP 服务的能力集:
#[tool_handler]impl ServerHandler for Weather { fn get_info(&self) -> ServerInfo { ServerInfo { capabilities: ServerCapabilities::builder().enable_tools().build(), ..Default::default() } }}
运行 MCP 服务( Running the server)
最后,实现 main 函数以通过标准输入输出(stdio)传输方式运行该 MCP 服务:
#[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)中测试你的这个 MCP 服务。
通过 Claude for Desktop 测试你的服务(Testing your server with Claude for Desktop)
Claude for Desktop 目前暂未在 Linux 系统上架。Linux 用户可参考《构建客户端》教程,搭建一个能连接我们刚开发的 MCP 服务的 MCP 客户端。
首先,请确保你已安装 Claude for Desktop。你可在此处下载安装最新版本;若已安装,请确保更新至最新版本。
我们需要为所有想要使用的 MCP 服务配置 Claude for Desktop。操作方式:用文本编辑器打开 Claude for Desktop 的应用配置文件,路径为 ~/Library/Application Support/Claude/claude_desktop_config.json,若文件不存在,请手动创建。
例如,若你安装了 VS Code,可通过以下命令打开配置文件:
macOS/Linux 系统
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
Windows 系统
code $env:AppData\Claude\claude_desktop_config.json
接下来,你需要在配置文件的 mcpServers 字段中添加你的服务配置。只有至少正确配置一个服务后,Claude for Desktop 才会显示 MCP 相关的 UI 界面。
本示例中,我们添加唯一的天气服务,配置如下:
macOS/Linux 系统
{ "mcpServers": { "weather": { "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather" } }}
Windows 系统
{ "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 是否成功识别到了我们在 weather 服务中暴露的两个工具。你可以通过点击界面中的 “Add files, connectors, and more /” 图标来查看:

点击加号图标后,将鼠标悬停在 “Connectors” 菜单上,你应该能看到列出的 weather 服务:

如果你的服务没有被 Claude for Desktop 识别到,请前往 “故障排除” 部分查看调试建议。
如果服务已经出现在 “Connectors” 菜单中,你就可以在 Claude for Desktop 中输入以下命令来测试你的服务:
- Sacramento 的天气怎么样?
- Texas 当前有哪些生效的天气预警?


由于我们使用的是美国国家气象局(US National Weather Service)的 API,因此这些查询只能用于美国境内的地点。
背后发生了什么(What’s happening under the hood)
当你提出一个问题时,会依次执行以下步骤:
- 客户端将你的问题发送给 Claude。
- Claude 分析可用的工具,并决定要调用哪一个(或多个)。
- 客户端通过 MCP 服务执行选中的工具。
- 执行结果被发送回 Claude。
- Claude 根据结果生成自然语言回答。
- 回答最终展示给你。
故障排除(Troubleshooting)
-
Claude for Desktop 相关问题
从 Claude for Desktop 获取日志
与 MCP 相关的 Claude.app 日志会写入到
~/Library/Logs/Claude目录下的日志文件中:mcp.log包含与 MCP 连接以及连接失败相关的常规日志。- 名称为
mcp-server-SERVERNAME.log的文件包含来自对应名称服务的错误(stderr)日志。
你可以运行以下命令查看最近的日志,并实时跟踪新产生的日志:
# Check Claude's logs for errorstail -n 20 -f ~/Library/Logs/Claude/mcp*.log服务未在 Claude 中显示
- 检查你的
claude_desktop_config.json文件语法是否正确。 - 确保项目路径是绝对路径,而不是相对路径。
- 完全重启 Claude for Desktop。
要正确重启 Claude for Desktop,你必须完全退出应用程序:
- Windows:右键点击系统托盘(可能隐藏在 “隐藏图标” 菜单中)中的 Claude 图标,然后选择 “退出” 或 “Exit”。
- macOS:使用 Cmd+Q,或从菜单栏中选择 “退出 Claude”。
仅关闭窗口并不会完全退出应用程序,你的 MCP 服务配置更改也不会生效。
工具调用无提示地失败
如果 Claude 尝试使用工具但调用失败:
- 检查 Claude 的日志是否有错误。
- 验证你的服务可以正常构建并运行,且没有报错。
- 尝试重启 Claude for Desktop。
这些方法都不奏效时怎么办?
请参考我们的调试指南,以获取更强大的调试工具和更详细的指导。
-
Weather API 相关问题
错误:Failed to retrieve grid point data
这通常意味着以下情况之一:
- 提供的坐标不在美国境内
- NWS API 本身出现故障
- 你的请求频率超出限制
解决方法:
- 确认你使用的是美国境内的坐标
- 在请求之间添加短暂延迟
- 查看 NWS API 状态页面
错误:No active alerts for [STATE]
这并不是错误,只是表示该州当前没有任何天气预警。你可以尝试查询其他州,或在有恶劣天气时再试。
如需更高级的故障排除,请参考我们的《调试 MCP》指南。
构建 MCP 客户端(Build an MCP client)
开始构建你自己的客户端,使其能够与所有 MCP 服务集成。
在本教程中,你将学习如何构建一个由大语言模型(LLM)驱动、可连接 MCP 服务的聊天机器人客户端。
开始之前,建议先完成我们的构建 MCP 服务教程,这样你能更好地理解客户端与服务之间的通信机制。
Python
系统要求(System Requirements)
开始之前,请确保你的系统满足以下要求:
- 搭载 macOS 或 Windows 系统的电脑
- 已安装最新版本的 Python
- 已安装最新版本的 uv
搭建环境( Setting Up Your Environment)
首先,使用 uv 创建一个新的 Python 项目:
macOS/Linux 系统
# Create project directoryuv init mcp-clientcd mcp-client# Create virtual environmentuv venv# Activate virtual environmentsource .venv/bin/activate# Install required packagesuv add mcp anthropic python-dotenv# Remove boilerplate filesrm main.py# Create our main filetouch client.py
Windows 系统
# Create project directoryuv init mcp-clientcd mcp-client# Create virtual environmentuv venv# Activate virtual environment.venv\Scripts\activate# Install required packagesuv add mcp anthropic python-dotenv# Remove boilerplate filesdel main.py# Create our main filenew-item client.py
配置 API 密钥( Setting Up Your API Key)
你需要从 Anthropic 控制台获取一个 Anthropic API 密钥。
创建一个 .env 文件来存储该密钥:
echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
将 .env 添加到你的 .gitignore 文件中:
echo ".env" >> .gitignore
请务必妥善保管你的 ANTHROPIC_API_KEY,确保其安全!
创建客户端(Creating the Client)
基础客户端结构(Basic Client Structure)
首先,我们完成所需模块的导入,并创建客户端的基础类结构:
import asynciofrom typing import Optionalfrom contextlib import AsyncExitStackfrom mcp import ClientSession, StdioServerParametersfrom mcp.client.stdio import stdio_clientfrom anthropic import Anthropicfrom dotenv import load_dotenvload_dotenv() # load environment variables from .envclass MCPClient: def __init__(self): # Initialize session and client objects self.session: Optional[ClientSession] = None self.exit_stack = AsyncExitStack() self.anthropic = Anthropic() # methods will go here
服务连接管理(Server Connection Management)
接下来,我们将实现用于连接 MCP 服务的方法:
async def connect_to_server(self, server_script_path: str): """Connect to an MCP server Args: server_script_path: Path to the server script (.py or .js) """ is_python = server_script_path.endswith('.py') is_js = server_script_path.endswith('.js') if not (is_python or is_js): raise ValueError("Server script must be a .py or .js file") command = "python" if is_python else "node" server_params = StdioServerParameters( command=command, args=[server_script_path], env=None ) stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.stdio, self.write = stdio_transport self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) await self.session.initialize() # List available tools response = await self.session.list_tools() tools = response.tools print("\nConnected to server with tools:", [tool.name for tool in tools])
查询处理逻辑( Query Processing Logic)
接下来我们添加处理查询和响应工具调用的核心功能:
async def process_query(self, query: str) -> str: """Process a query using Claude and available tools""" messages = [ { "role": "user", "content": query } ] response = await self.session.list_tools() available_tools = [{ "name": tool.name, "description": tool.description, "input_schema": tool.inputSchema } for tool in response.tools] # Initial Claude API call response = self.anthropic.messages.create( model="claude-sonnet-4-20250514", max_tokens=1000, messages=messages, tools=available_tools ) # Process response and handle tool calls final_text = [] assistant_message_content = [] for content in response.content: if content.type == 'text': final_text.append(content.text) assistant_message_content.append(content) elif content.type == 'tool_use': tool_name = content.name tool_args = content.input # Execute tool call result = await self.session.call_tool(tool_name, tool_args) final_text.append(f"[Calling tool {tool_name} with args {tool_args}]") assistant_message_content.append(content) messages.append({ "role": "assistant", "content": assistant_message_content }) messages.append({ "role": "user", "content": [ { "type": "tool_result", "tool_use_id": content.id, "content": result.content } ] }) # Get next response from Claude response = self.anthropic.messages.create( model="claude-sonnet-4-20250514", max_tokens=1000, messages=messages, tools=available_tools ) final_text.append(response.content[0].text) return "\n".join(final_text)
交互式聊天界面( Interactive Chat Interface)
接下来,我们添加聊天循环逻辑和程序退出时的资源清理功能,让客户端支持交互式的持续对话:
async def chat_loop(self): """Run an interactive chat loop""" print("\nMCP Client Started!") print("Type your queries or 'quit' to exit.") while True: try: query = input("\nQuery: ").strip() if query.lower() == 'quit': break response = await self.process_query(query) print("\n" + response) except Exception as e: print(f"\nError: {str(e)}")async def cleanup(self): """Clean up resources""" await self.exit_stack.aclose()
主程序入口( Main Entry Point)
最后,我们添加核心执行逻辑:
async def main(): if len(sys.argv) < 2: print("Usage: python client.py <path_to_server_script>") sys.exit(1) client = MCPClient() try: await client.connect_to_server(sys.argv[1]) await client.chat_loop() finally: await client.cleanup()if __name__ == "__main__": import sys asyncio.run(main())
你可在此处获取完整的 client.py 文件。
主要组件说明( Key Components Explained)
1. 客户端初始化(Client Initialization)
MCPClient类在初始化时会完成会话管理和 API 客户端的设置。- 它使用
AsyncExitStack来确保资源的正确管理。 - 同时会配置 Anthropic 客户端,用于与 Claude 进行交互。
2. 服务连接(Server Connection)
- 支持 Python 和 Node.js 编写的 MCP 服务。
- 会对服务脚本的类型进行验证。
- 建立合适的通信通道。
- 初始化会话并列出可用的工具。
3. 查询处理(Query Processing)
- 维护对话上下文。
- 处理 Claude 的响应以及工具调用请求。
- 管理 Claude 与各个工具之间的消息流转。
- 将多个工具的结果整合成一个连贯的最终响应。
4. 交互式界面(Interactive Interface)
- 提供一个简单的命令行界面。
- 负责接收用户输入并显示系统响应。
- 包含基本的错误处理逻辑。
- 支持优雅退出。
5. 资源管理(Resource Management)
- 对资源进行适当的清理。
- 处理与连接相关的错误。
- 提供优雅的关闭流程。
运行 Client( Running the Client)
将你的客户端对接任意 MCP 服务运行:
uv run client.py path/to/server.py # 对接 Python 编写的 MCP 服务uv run client.py path/to/build/index.js # 对接 Node 编写的 MCP 服务
如果你正在跟随服务端快速入门中的天气教程继续操作,你的命令可能类似于:
python client.py .../quickstart-resources/weather-server-python/weather.py
启动后,Client 会执行以下操作:
- 连接到指定的 MCP 服务
- 列出该服务提供的所有可用工具
- 启动一个交互式的聊天会话,你可以在会话中:
- 输入查询指令
- 查看工具的执行过程
- 获取来自 Claude 的响应
以下是对接快速入门教程中天气服务时,Client 运行后的典型界面效果示例:

工作原理( How It Works)
当你提交一个查询时,流程如下:
- 客户端从 MCP 服务获取可用工具列表。
- 你的查询与工具描述一起发送给 Claude。
- Claude 判断需要调用哪些工具(如果有)。
- 客户端通过 MCP 服务执行所有被请求的工具调用。
- 工具执行结果被回传给 Claude。
- Claude 生成自然语言响应。
- 响应最终展示给你。
最佳实践( Best practices)
- 错误处理
- 始终将工具调用包裹在 try‑catch 块中。
- 提供清晰、有意义的错误信息。
- 优雅处理连接问题。
- 资源管理
- 使用
AsyncExitStack进行正确的资源清理。 - 使用完毕后关闭连接。
- 处理服务端断开连接的情况。
- 使用
- 安全
- 将 API 密钥安全地存储在
.env文件中。 - 验证服务端响应的合法性。
- 谨慎管理工具权限。
- 将 API 密钥安全地存储在
- 工具名称
- 工具名称可以按照此处指定的格式进行验证。
- 如果工具名称符合指定格式,MCP 客户端不应在验证时抛出错误。
故障排查(Troubleshooting)
服务路径问题(Server Path Issues)
- 仔细检查你的 MCP 服务脚本路径是否正确。
- 如果相对路径无法正常使用,请改用绝对路径。
- 针对 Windows 用户:确保路径中使用正斜杠(/)或转义后的反斜杠(\)。
- 验证服务文件的扩展名是否正确(Python 脚本为 .py,Node.js 脚本为 .js)。
以下是路径正确使用的示例:
# Relative pathuv run client.py ./server/weather.py# Absolute pathuv run client.py /Users/username/projects/mcp-server/weather.py# Windows path (either format works)uv run client.py C:/projects/mcp-server/weather.pyuv run client.py C:\\projects\\mcp-server\\weather.py
响应时间(Response Timing)
-
首次响应可能需要长达 30 秒才能返回。
-
这是正常现象,通常发生在以下过程中:
- 服务初始化
- Claude 处理查询
- 工具正在执行
-
后续响应通常会更快。
-
在首次等待期间,请不要中断进程。
常见错误提示(Common Error Messages)
如果你看到以下错误提示,可以按对应方式排查:
FileNotFoundError:检查你的 MCP 服务脚本路径是否正确。Connection refused:确认 MCP 服务已正常运行,且路径填写无误。Tool execution failed:验证该工具所需的环境变量是否已正确配置。Timeout error:考虑在客户端配置中适当增加超时时间。
TypeScript
系统要求(System Requirements)
开始之前,请确保你的系统满足以下要求:
- 搭载 macOS 或 Windows 系统的电脑
- 已安装 Node.js 17 或更高版本
- 已安装最新版本的
npm - 拥有 Anthropic API 密钥(用于调用 Claude)
搭建环境( Setting Up Your Environment)
首先,我们来创建并完成项目的初始化配置:
macOS/Linux 系统
# Create project directorymkdir mcp-client-typescriptcd mcp-client-typescript# Initialize npm projectnpm init -y# Install dependenciesnpm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv# Install dev dependenciesnpm install -D @types/node typescript# Create source filetouch index.ts
Windows 系统
# Create project directorymd mcp-client-typescriptcd mcp-client-typescript# Initialize npm projectnpm init -y# Install dependenciesnpm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv# Install dev dependenciesnpm install -D @types/node typescript# Create source filenew-item index.ts
更新你的 package.json 文件,将 type 字段设置为 "module",并添加一个构建脚本:
{ "type": "module", "scripts": { "build": "tsc && chmod 755 build/index.js" }}
在你的项目根目录下创建 tsconfig.json 文件:
{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["index.ts"], "exclude": ["node_modules"]}
配置 API 秘钥( Setting Up Your API Key)
你需要从 Anthropic 控制台获取一个 Anthropic API 密钥。
创建一个 .env 文件来存储该密钥:
echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
将 .env 添加到你的 .gitignore 文件中:
echo ".env" >> .gitignore
请务必妥善保管你的 ANTHROPIC_API_KEY,确保其安全!
创建客户端(Creating the Client)
基础客户端结构(Basic Client Structure)
首先,我们在 index.ts 文件中完成依赖导入,并创建基础的客户端类:
import { Anthropic } from "@anthropic-ai/sdk";import { MessageParam, Tool,} from "@anthropic-ai/sdk/resources/messages/messages.mjs";import { Client } from "@modelcontextprotocol/sdk/client/index.js";import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";import readline from "readline/promises";import dotenv from "dotenv";dotenv.config();const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;if (!ANTHROPIC_API_KEY) { throw new Error("ANTHROPIC_API_KEY is not set");}class MCPClient { private mcp: Client; private anthropic: Anthropic; private transport: StdioClientTransport | null = null; private tools: Tool[] = []; constructor() { this.anthropic = new Anthropic({ apiKey: ANTHROPIC_API_KEY, }); this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" }); } // methods will go here}
服务连接管理(Server Connection Management)
接下来,我们将实现用于连接 MCP 服务的方法:
async connectToServer(serverScriptPath: string) { try { const isJs = serverScriptPath.endsWith(".js"); const isPy = serverScriptPath.endsWith(".py"); if (!isJs && !isPy) { throw new Error("Server script must be a .js or .py file"); } const command = isPy ? process.platform === "win32" ? "python" : "python3" : process.execPath; this.transport = new StdioClientTransport({ command, args: [serverScriptPath], }); await this.mcp.connect(this.transport); const toolsResult = await this.mcp.listTools(); this.tools = toolsResult.tools.map((tool) => { return { name: tool.name, description: tool.description, input_schema: tool.inputSchema, }; }); console.log( "Connected to server with tools:", this.tools.map(({ name }) => name) ); } catch (e) { console.log("Failed to connect to MCP server: ", e); throw e; }}
查询处理逻辑( Query Processing Logic)
接下来我们添加处理查询和响应工具调用的核心功能:
async processQuery(query: string) { const messages: MessageParam[] = [ { role: "user", content: query, }, ]; const response = await this.anthropic.messages.create({ model: "claude-sonnet-4-20250514", max_tokens: 1000, messages, tools: this.tools, }); const finalText = []; for (const content of response.content) { if (content.type === "text") { finalText.push(content.text); } else if (content.type === "tool_use") { const toolName = content.name; const toolArgs = content.input as { [x: string]: unknown } | undefined; const result = await this.mcp.callTool({ name: toolName, arguments: toolArgs, }); finalText.push( `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]` ); messages.push({ role: "user", content: result.content as string, }); const response = await this.anthropic.messages.create({ model: "claude-sonnet-4-20250514", max_tokens: 1000, messages, }); finalText.push( response.content[0].type === "text" ? response.content[0].text : "" ); } } return finalText.join("\n");}
交互式聊天界面( Interactive Chat Interface)
接下来,我们添加聊天循环逻辑和程序退出时的资源清理功能,让客户端支持交互式的持续对话:
async chatLoop() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); try { console.log("\nMCP Client Started!"); console.log("Type your queries or 'quit' to exit."); while (true) { const message = await rl.question("\nQuery: "); if (message.toLowerCase() === "quit") { break; } const response = await this.processQuery(message); console.log("\n" + response); } } finally { rl.close(); }}async cleanup() { await this.mcp.close();}
主程序入口( Main Entry Point)
最后,我们添加核心执行逻辑:
async function main() { if (process.argv.length < 3) { console.log("Usage: node index.ts <path_to_server_script>"); return; } const mcpClient = new MCPClient(); try { await mcpClient.connectToServer(process.argv[2]); await mcpClient.chatLoop(); } catch (e) { console.error("Error:", e); await mcpClient.cleanup(); process.exit(1); } finally { await mcpClient.cleanup(); process.exit(0); }}main();
运行 Client( Running the Client)
将你的客户端对接任意 MCP 服务运行:
# Build TypeScriptnpm run build# Run the clientnode build/index.js path/to/server.py # python servernode build/index.js path/to/build/index.js # node server
如果你正在跟随服务端快速入门中的天气教程继续操作,你的命令可能类似于:
node build/index.js .../quickstart-resources/weather-server-typescript/build/index.js
启动后,Client 会执行以下操作:
- 连接到指定的 MCP 服务
- 列出该服务提供的所有可用工具
- 启动一个交互式的聊天会话,你可以在会话中:
- 输入查询指令
- 查看工具的执行过程
- 获取来自 Claude 的响应
最佳实践( Best practices)
- 错误处理
- 利用 TypeScript 的类型系统提升错误检测能力。
- 始终将工具调用包裹在 try‑catch 块中。
- 提供清晰、有意义的错误信息。
- 优雅处理连接问题。
- 安全
- 将 API 密钥安全地存储在
.env文件中。 - 验证服务端响应的合法性。
- 谨慎管理工具权限。
- 将 API 密钥安全地存储在
故障排查(Troubleshooting)
服务路径问题(Server Path Issues)
- 仔细检查你的 MCP 服务脚本路径是否正确。
- 如果相对路径无法正常使用,请改用绝对路径。
- 针对 Windows 用户:确保路径中使用正斜杠(/)或转义后的反斜杠(\)。
- 验证服务文件的扩展名是否正确(Node.js 脚本为 .js,Python 脚本为 .py)。
以下是路径正确使用的示例:
# Relative pathnode build/index.js ./server/build/index.js# Absolute pathnode build/index.js /Users/username/projects/mcp-server/build/index.js# Windows path (either format works)node build/index.js C:/projects/mcp-server/build/index.jsnode build/index.js C:\\projects\\mcp-server\\build\\index.js
响应时间(Response Timing)
-
首次响应可能需要长达 30 秒才能返回。
-
这是正常现象,通常发生在以下过程中:
- 服务初始化
- Claude 处理查询
- 工具正在执行
-
后续响应通常会更快。
-
在首次等待期间,请不要中断进程。
常见错误提示(Common Error Messages)
如果你看到以下错误提示,可以按对应方式排查:
Error: Cannot find module:检查你的build目录是否存在,并确认 TypeScript 代码编译成功。Connection refused:确认 MCP 服务已正常运行,且传入客户端的服务路径填写无误。Tool execution failed:验证该工具运行所需的环境变量是否已正确配置。ANTHROPIC_API_KEY is not set:检查你的.env文件及系统环境变量,确认已配置 Anthropic API 密钥。TypeError:确保你为工具参数传入了正确的类型(如字符串 / 数字 / 对象匹配工具定义)。BadRequestError:确认你的 Anthropic 账号有足够的额度,可正常调用 Claude API。
Java
本示例是基于 Spring AI MCP 自动配置和启动器(boot starters)实现的快速入门演示。若需了解如何手动创建同步(sync)和异步(async)MCP Client,请参考 Java SDK Client 相关文档。
本示例展示了如何构建一个交互式聊天机器人,该机器人将 Spring AI 的模型上下文协议(Model Context Protocol,MCP)与 Brave Search MCP 服务相结合。该应用会创建一个由 Anthropic 公司的 Claude AI 模型驱动的对话界面,可通过 Brave Search 执行互联网搜索,从而实现与实时网络数据的自然语言交互。你可在此处获取本教程的完整代码。
系统要求(System Requirements)
开始之前,请确保你的系统满足以下要求:
- 已安装 Java 17 或更高版本
- 已安装 Maven 3.6 或更高版本
- npx 包管理器
- Anthropic API 密钥(用于调用 Claude)
- Brave Search API 密钥(用于调用 Brave 搜索服务)
搭建环境( Setting Up Your Environment)
- 安装 npx(Node 包执行工具)
首先确保已安装 npm,然后执行以下命令全局安装 npx:
npm install -g npx
- 克隆代码仓库
git clone https://github.com/spring-projects/spring-ai-examples.gitcd model-context-protocol/web-search/brave-chatbot
- 配置 API 密钥
export ANTHROPIC_API_KEY='你的 Anthropic API 密钥'export BRAVE_API_KEY='你的 Brave 搜索 API 密钥'
- 构建应用
./mvnw clean install
- 通过 Maven 运行应用
./mvnw spring-boot:run
⚠️ 请务必妥善保管你的 ANTHROPIC_API_KEY 和 BRAVE_API_KEY 密钥,避免泄露!
工作原理( How It Works)
该应用通过多个核心组件,实现了 Spring AI 与 Brave Search MCP 服务的集成:
MCP 客户端配置(MCP Client Configuration)
- pom.xml 中需引入的必要依赖:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId></dependency><dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-anthropic</artifactId></dependency>
- 应用配置 (application.yml):
spring: ai: mcp: client: enabled: true name: brave-search-client version: 1.0.0 type: SYNC request-timeout: 20s stdio: root-change-notification: true servers-configuration: classpath:/mcp-servers-config.json toolcallback: enabled: true anthropic: api-key: ${ANTHROPIC_API_KEY}
上述配置会激活 spring-ai-starter-mcp-client 依赖,基于提供的服务端配置创建一个或多个 McpClient 实例。其中 spring.ai.mcp.client.toolcallback.enabled=true 配置项用于启用工具回调机制 —— 该机制会自动将所有 MCP 工具注册为 Spring AI 工具(该功能默认处于禁用状态)。
- MCP 服务配置(mcp-servers-config.json)
{ "mcpServers": { "brave-search": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-brave-search"], "env": { "BRAVE_API_KEY": "<Brave Search API 密钥>" } } }}
聊天功能实现(Chat Implementation)
该聊天机器人基于 Spring AI 的 ChatClient 实现,并集成了 MCP 工具能力,核心代码如下:
var chatClient = chatClientBuilder .defaultSystem("You are useful assistant, expert in AI and Java.") // 设置系统提示词,定义助手角色 .defaultToolCallbacks((Object[]) mcpToolAdapter.toolCallbacks()) // 集成 MCP 工具回调 .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())) // 配置对话记忆 .build(); // 构建 ChatClient 实例
核心特性
- 基于 Claude AI 模型实现自然语言理解能力;
- 通过 MCP 集成 Brave Search,具备实时网页搜索能力;
- 使用
InMemoryChatMemory维护对话记忆(上下文); - 以交互式命令行应用的形式运行。
构建与运行(Build and run)
打包为 JAR 包运行
# 清理并构建项目(生成 JAR 包)./mvnw clean install# 运行打包后的 JAR 包java -jar ./target/ai-mcp-brave-chatbot-0.0.1-SNAPSHOT.jar
或者直接通过 Maven 运行
./mvnw spring-boot:run
应用启动后会开启一个交互式聊天会话,你可以在其中提出问题。当聊天机器人需要从互联网获取信息来回答你的问题时,会自动调用 Brave Search 进行搜索。
聊天机器人核心能力
- 利用内置知识回答问题;
- 按需通过 Brave Search 执行网页搜索;
- 记住对话中历史消息的上下文;
- 整合多来源信息,提供全面的回答。
高级配置(Advanced Configuration)
MCP 客户端支持以下额外配置项:
- 通过
McpSyncClientCustomizer或McpAsyncClientCustomizer进行客户端定制 - 支持多个客户端实例,并可使用多种传输类型:STDIO 和 SSE(Server-Sent Events)
- 与 Spring AI 的工具执行框架集成
- 自动初始化和生命周期管理
对于基于 WebFlux 的应用,你可以改用 WebFlux 启动器:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId></dependency>
该启动器提供与标准版一致的 MCP 客户端功能,但底层采用基于 WebFlux 的 SSE 传输实现,是生产环境部署的推荐方案。
Kotlin
系统要求(System Requirements)
开始之前,请确保你的系统满足以下要求:
- 已安装 Java 17 或更高版本
- Anthropic API 密钥(用于调用 Claude)
搭建环境( Setting Up Your Environment)
首先,如果你尚未安装 java 和 gradle,请先完成安装。你可以从 Oracle JDK 官方网站下载 java。验证 java 安装是否成功:
java --version
接下来,创建并配置项目:
macOS/Linux 系统
# Create a new directory for our projectmkdir kotlin-mcp-clientcd kotlin-mcp-client# Initialize a new kotlin projectgradle init
Windows 系统
# Create a new directory for our projectmd kotlin-mcp-clientcd kotlin-mcp-client# Initialize a new kotlin projectgradle init
运行 gradle init 命令后,会出现项目创建选项。选择 “Application” 作为项目类型,“Kotlin” 作为编程语言,“Java 17” 作为 Java 版本。
此外,你也可以通过 IntelliJ IDEA 的项目向导创建 Kotlin 应用程序。
创建项目后,添加以下依赖项:
build.gradle.kts
val mcpVersion = "0.4.0"val slf4jVersion = "2.0.9"val anthropicVersion = "0.8.0"dependencies { implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") implementation("org.slf4j:slf4j-nop:$slf4jVersion") implementation("com.anthropic:anthropic-java:$anthropicVersion")}
build.gradle
def mcpVersion = '0.3.0'def slf4jVersion = '2.0.9'def anthropicVersion = '0.8.0'dependencies { implementation "io.modelcontextprotocol:kotlin-sdk:$mcpVersion" implementation "org.slf4j:slf4j-nop:$slf4jVersion" implementation "com.anthropic:anthropic-java:$anthropicVersion"}
同时,在构建脚本中添加以下插件:
build.gradle.kts
plugins { id("com.gradleup.shadow") version "8.3.9"}
build.gradle
plugins { id 'com.gradleup.shadow' version '8.3.9'}
现在,让我们开始深入构建你的 MCP 服务。
配置 API 秘钥(Setting up your API key)
你需要从 Anthropic 控制台获取一个 Anthropic API 密钥。
设置 API 密钥:
export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
请务必妥善保管你的
ANTHROPIC_API_KEY!
创建客户端(Creating the Client)
基础客户端结构(Basic Client Structure)
首先,我们创建基础的客户端类:
class MCPClient : AutoCloseable { private val anthropic = AnthropicOkHttpClient.fromEnv() private val mcp: Client = Client(clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0")) private lateinit var tools: List<ToolUnion> // methods will go here override fun close() { runBlocking { mcp.close() anthropic.close() } }
服务连接管理(Server Connection Management)
接下来,我们将实现用于连接 MCP 服务的方法:
suspend fun connectToServer(serverScriptPath: String) { try { val command = buildList { when (serverScriptPath.substringAfterLast(".")) { "js" -> add("node") "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3") "jar" -> addAll(listOf("java", "-jar")) else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file") } add(serverScriptPath) } val process = ProcessBuilder(command).start() val transport = StdioClientTransport( input = process.inputStream.asSource().buffered(), output = process.outputStream.asSink().buffered() ) mcp.connect(transport) val toolsResult = mcp.listTools() tools = toolsResult?.tools?.map { tool -> ToolUnion.ofTool( Tool.builder() .name(tool.name) .description(tool.description ?: "") .inputSchema( Tool.InputSchema.builder() .type(JsonValue.from(tool.inputSchema.type)) .properties(tool.inputSchema.properties.toJsonValue()) .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required)) .build() ) .build() ) } ?: emptyList() println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}") } catch (e: Exception) { println("Failed to connect to MCP server: $e") throw e }}
同时创建一个辅助函数,用于将 JsonObject 转换为 Anthropic 所需的 JsonValue 类型:
private fun JsonObject.toJsonValue(): JsonValue { val mapper = ObjectMapper() val node = mapper.readTree(this.toString()) return JsonValue.fromJsonNode(node)}
查询处理逻辑( Query Processing Logic)
接下来我们添加处理查询和响应工具调用的核心功能:
private val messageParamsBuilder: MessageCreateParams.Builder = MessageCreateParams.builder() .model(Model.CLAUDE_SONNET_4_20250514) .maxTokens(1024)suspend fun processQuery(query: String): String { val messages = mutableListOf( MessageParam.builder() .role(MessageParam.Role.USER) .content(query) .build() ) val response = anthropic.messages().create( messageParamsBuilder .messages(messages) .tools(tools) .build() ) val finalText = mutableListOf<String>() response.content().forEach { content -> when { content.isText() -> finalText.add(content.text().getOrNull()?.text() ?: "") content.isToolUse() -> { val toolName = content.toolUse().get().name() val toolArgs = content.toolUse().get()._input().convert(object : TypeReference<Map<String, JsonValue>>() {}) val result = mcp.callTool( name = toolName, arguments = toolArgs ?: emptyMap() ) finalText.add("[Calling tool $toolName with args $toolArgs]") messages.add( MessageParam.builder() .role(MessageParam.Role.USER) .content( """ "type": "tool_result", "tool_name": $toolName, "result": ${result?.content?.joinToString("\n") { (it as TextContent).text ?: "" }} """.trimIndent() ) .build() ) val aiResponse = anthropic.messages().create( messageParamsBuilder .messages(messages) .build() ) finalText.add(aiResponse.content().first().text().getOrNull()?.text() ?: "") } } } return finalText.joinToString("\n", prefix = "", postfix = "")}
交互式聊天(Interactive Chat)
我们添加如下聊天循环逻辑:
suspend fun chatLoop() { println("\nMCP Client Started!") println("Type your queries or 'quit' to exit.") while (true) { print("\nQuery: ") val message = readLine() ?: break if (message.lowercase() == "quit") break val response = processQuery(message) println("\n$response") }}
主程序入口( Main Entry Point)
最后,我们添加核心执行逻辑:
fun main(args: Array<String>) = runBlocking { if (args.isEmpty()) throw IllegalArgumentException("Usage: java -jar <your_path>/build/libs/kotlin-mcp-client-0.1.0-all.jar <path_to_server_script>") val serverPath = args.first() val client = MCPClient() client.use { client.connectToServer(serverPath) client.chatLoop() }}
运行 Client( Running the Client)
将你的客户端对接任意 MCP 服务运行:
./gradlew build# Run the clientjava -jar build/libs/<your-jar-name>.jar path/to/server.jar # jvm serverjava -jar build/libs/<your-jar-name>.jar path/to/server.py # python serverjava -jar build/libs/<your-jar-name>.jar path/to/build/index.js # node server
如果你正在跟随服务端快速入门中的天气教程继续操作,你的命令可能类似于:
java -jar build/libs/kotlin-mcp-client-0.1.0-all.jar .../samples/weather-stdio-server/build/libs/weather-stdio-server-0.1.0-all.jar
启动后,Client 会执行以下操作:
- 连接到指定的 MCP 服务
- 列出该服务提供的所有可用工具
- 启动一个交互式的聊天会话,你可以在会话中:
- 输入查询指令
- 查看工具的执行过程
- 获取来自 Claude 的响应
工作原理(How it works)
下面是一个高层次的工作流程概览:

当你提交一个查询时,流程如下:
- 客户端从 MCP 服务获取可用工具列表。
- 你的查询与工具描述一起发送给 Claude。
- Claude 判断需要调用哪些工具(如果有)。
- 客户端通过 MCP 服务执行所有被请求的工具调用。
- 工具执行结果被回传给 Claude。
- Claude 生成自然语言响应。
- 响应最终展示给你。
最佳实践( Best practices)
-
错误处理
- 利用 TypeScript 的类型系统提升错误检测能力。
- 始终将工具调用包裹在 try‑catch 块中。
- 提供清晰、有意义的错误信息。
- 优雅处理连接问题。
-
安全
- 将 API 密钥安全地存储在
.env文件中。 - 验证服务端响应的合法性。
- 谨慎管理工具权限。
- 将 API 密钥安全地存储在
-
错误处理
- 利用 Kotlin 的类型系统,以显式建模的方式表达错误状态,而不是依赖隐式异常或不明确的返回值。
- 在可能抛出异常的外部工具和 API 调用上使用 try-catch 块进行包裹。
- 提供清晰且有意义的错误信息。
- 优雅地处理网络超时和连接问题。
-
安全
- 将 API 密钥和敏感信息安全地存储在 local.properties、环境变量或密钥管理服务中。
- 验证所有外部响应,避免使用不可预期或不安全的数据。
- 使用工具时,谨慎处理权限和信任边界。
故障排查(Troubleshooting)
服务路径问题(Server Path Issues)
- 仔细检查你的 MCP 服务脚本路径是否正确。
- 如果相对路径无法正常使用,请改用绝对路径。
- 针对 Windows 用户:确保路径中使用正斜杠(/)或转义后的反斜杠(\)。
- 确保已安装所需的运行时环境(Java 程序需安装 java、Node.js 程序需安装 npm、Python 程序需安装 uv)。
- 验证服务文件的扩展名是否正确(Java 为 .jar、Node.js 为 .js、Python 为 .py)。
以下是路径正确使用的示例:
# Relative pathjava -jar build/libs/client.jar ./server/build/libs/server.jar# Absolute pathjava -jar build/libs/client.jar /Users/username/projects/mcp-server/build/libs/server.jar# Windows path (either format works)java -jar build/libs/client.jar C:/projects/mcp-server/build/libs/server.jarjava -jar build/libs/client.jar C:\\projects\\mcp-server\\build\\libs\\server.jar
响应时间(Response Timing)
-
首次响应可能需要长达 30 秒才能返回。
-
这是正常现象,通常发生在以下过程中:
- 服务初始化
- Claude 处理查询
- 工具正在执行
-
后续响应通常会更快。
-
在首次等待期间,请不要中断进程。
常见错误提示(Common Error Messages)
如果你看到以下错误提示,可以按对应方式排查:
Connection refused:确认 MCP 服务已正常运行,且传入客户端的服务路径填写无误。Tool execution failed:验证该工具运行所需的环境变量是否已正确配置。ANTHROPIC_API_KEY is not set:检查你的环境变量。
C#
系统要求(System Requirements)
开始之前,请确保你的系统满足以下要求:
- 已安装 .NET 08.0 或更高版本
- Anthropic API 密钥(用于调用 Claude)
- Windows、Linux 或者 macOS 操作系统
搭建环境( Setting Up Your Environment)
首先,创建一个新的 .NET 项目:
dotnet new console -n QuickstartClientcd QuickstartClient
然后,在你的项目中添加如下依赖项:
dotnet add package ModelContextProtocol --prereleasedotnet add package Anthropic.SDKdotnet add package Microsoft.Extensions.Hostingdotnet add package Microsoft.Extensions.AI
配置 API 秘钥(Setting up your API key)
你需要从 Anthropic 控制台获取一个 Anthropic API 密钥。
dotnet user-secrets initdotnet user-secrets set "ANTHROPIC_API_KEY" "<your key here>"
创建客户端(Creating the Client)
基础客户端结构(Basic Client Structure)
首先,我们在 Program.cs 文件中创建基础的客户端类:
using Anthropic.SDK;using Microsoft.Extensions.AI;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.Hosting;using ModelContextProtocol.Client;using ModelContextProtocol.Protocol.Transport;var builder = Host.CreateApplicationBuilder(args);builder.Configuration .AddEnvironmentVariables() .AddUserSecrets<Program>();
此步骤完成了一个 .NET 控制台应用的基础搭建,该应用能够从用户密钥存储区读取 API 密钥。
接下来,我们将配置 MCP 客户端:
var (command, arguments) = GetCommandAndArguments(args);var clientTransport = new StdioClientTransport(new(){ Name = "Demo Server", Command = command, Arguments = arguments,});await using var mcpClient = await McpClient.CreateAsync(clientTransport);var tools = await mcpClient.ListToolsAsync();foreach (var tool in tools){ Console.WriteLine($"Connected to server with tools: {tool.Name}");}
在 Program.cs 文件末尾添加如下函数:
static (string command, string[] arguments) GetCommandAndArguments(string[] args){ return args switch { [var script] when script.EndsWith(".py") => ("python", args), [var script] when script.EndsWith(".js") => ("node", args), [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]), _ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj") };}
此代码创建一个 MCP 客户端,该客户端会连接到通过命令行参数指定的 MCP 服务,随后列出已连接服务中可用的工具。
查询处理逻辑( Query Processing Logic)
接下来我们添加处理查询和响应工具调用的核心功能:
using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"])) .Messages .AsBuilder() .UseFunctionInvocation() .Build();var options = new ChatOptions{ MaxOutputTokens = 1000, ModelId = "claude-sonnet-4-20250514", Tools = [.. tools]};Console.ForegroundColor = ConsoleColor.Green;Console.WriteLine("MCP Client Started!");Console.ResetColor();PromptForInput();while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase)){ if (string.IsNullOrWhiteSpace(query)) { PromptForInput(); continue; } await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options)) { Console.Write(message); } Console.WriteLine(); PromptForInput();}static void PromptForInput(){ Console.WriteLine("Enter a command (or 'exit' to quit):"); Console.ForegroundColor = ConsoleColor.Cyan; Console.Write("> "); Console.ResetColor();}
核心组件说明(Key Components Explained)
1. 客户端初始化(Client Initialization)
客户端通过 McpClient.CreateAsync() 方法完成初始化,该方法会配置传输类型,并设定运行 MCP 服务所需的命令。
2. 服务连接(Server Connection)
- 支持对接 Python、Node.js 和 .NET 开发的 MCP 服务;
- 根据参数中指定的命令启动 MCP 服务;
- 配置为使用 stdio 与 MCP 服务进行通信;
- 初始化会话及可用工具列表。
3. 查询处理(Query Processing)
- 基于 Microsoft.Extensions.AI 构建聊天客户端;
- 配置
IChatClient以支持工具(函数)的自动调用; - 客户端读取用户输入并将其发送至 MCP 服务;
- MCP 服务处理查询并返回响应结果;
- 将响应结果展示给用户。
运行 Client( Running the Client)
将你的客户端对接任意 MCP 服务运行:
dotnet run -- path/to/server.csproj # dotnet serverdotnet run -- path/to/server.py # python serverdotnet run -- path/to/server.js # node server
如果你正在跟随服务端快速入门中的天气教程继续操作,你的命令可能类似于:
dotnet run -- path/to/QuickstartWeatherServer
启动后,Client 会执行以下操作:
- 连接到指定的 MCP 服务
- 列出该服务提供的所有可用工具
- 启动一个交互式的聊天会话,你可以在会话中:
- 输入查询指令
- 查看工具的执行过程
- 获取来自 Claude 的响应
- 完成操作后退出会话。
以下是客户端连接到天气服务快速入门示例后的预期运行效果:

软件开发工具包(SDKs)
用于构建模型上下文协议(MCP)的官方软件开发工具包
你可以使用我们的官方 SDK 来构建 MCP 服务和客户端。所有 SDK 均提供相同的核心功能,并支持完整的协议规范。
可用的 SDK ( Available SDKs)
TypeScript:https://github.com/modelcontextprotocol/typescript-sdk
Python:https://github.com/modelcontextprotocol/python-sdk
Go:https://github.com/modelcontextprotocol/go-sdk
Kotlin:https://github.com/modelcontextprotocol/kotlin-sdk
Swift:https://github.com/modelcontextprotocol/swift-sdk
Java:https://github.com/modelcontextprotocol/java-sdk
C#:https://github.com/modelcontextprotocol/csharp-sdk
Ruby:https://github.com/modelcontextprotocol/ruby-sdk
Rust:https://github.com/modelcontextprotocol/rust-sdk
PHP:https://github.com/modelcontextprotocol/php-sdk
快速入门(Getting Started)
各 SDK 提供的功能一致,但均遵循对应编程语言的惯用写法与最佳实践。所有 SDK 均支持以下能力:
- 创建可对外暴露工具、资源和提示词的 MCP 服务;
- 构建可连接任意 MCP 服务的 MCP 客户端;
- 本地及远程传输协议;
- 具备类型安全性的协议合规性。
请访问对应编程语言的 SDK 页面,获取安装说明、文档及示例代码。
安全(Security)
理解 MCP 中的授权机制(Understanding Authorization in MCP)
了解如何使用 OAuth 2.1 为 MCP 服务实现安全的授权机制,以保护敏感资源和操作。
在模型上下文协议(MCP)中,授权机制用于保护由 MCP 服务暴露的敏感资源和操作。如果你的 MCP 服务处理用户数据或管理类操作,授权机制可以确保只有经过许可的用户才能访问其端点。
MCP 使用标准化的授权流程来在 MCP 客户端与 MCP 服务之间建立信任关系。其设计并不依赖于某一种特定的授权或身份系统,而是遵循 OAuth 2.1 中规定的约定。如需详细信息,请参阅授权规范。
何时应该使用授权?( When Should You Use Authorization?)
虽然 MCP 服务的授权是可选的,但在以下情况下强烈建议启用:
- 你的服务需要访问用户特定的数据(例如邮件、文档、数据库)
- 你需要审计谁执行了哪些操作
- 你的服务提供的 API 需要用户授权才能访问
- 你正在为具有严格访问控制的企业环境构建系统
- 你希望按用户实施速率限制或使用量跟踪
本地 MCP 服务的授权
对于使用 STDIO 传输方式的 MCP 服务,你可以改用基于环境变量的凭据,或直接使用嵌入在 MCP 服务中的第三方库所提供的凭据。由于基于 STDIO 的 MCP 服务运行在本地,它在获取用户凭据方面拥有更灵活的选择,这些方式可能依赖、也可能不依赖浏览器中的身份验证和授权流程。
相比之下,OAuth 流程是为基于 HTTP 的传输方式设计的。在这种场景中,MCP 服务是远程托管的,客户端通过 OAuth 来确认用户是否被授权访问该远程服务。
授权流程:分步说明(The Authorization Flow: Step by Step)
下面逐步说明当客户端希望连接到受保护的 MCP 服务时所发生的过程:
- 初始握手
当 MCP 客户端首次尝试连接时,服务会返回 401 Unauthorized,并在响应中告知客户端在哪里可以获取授权信息,这些信息包含在一个受保护资源元数据(Protected Resource Metadata,PRM)文档中。该文档由 MCP 服务托管,遵循可预测的路径格式,并通过 WWW-Authenticate 头中的 resource_metadata 参数提供给客户端。
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer realm="mcp", resource_metadata="https://your-server.com/.well-known/oauth-protected-resource"
这会告诉客户端:该 MCP 服务需要授权,以及在哪里获取启动授权流程所需的信息。
- 受保护资源元数据发现
通过 PRM 文档的 URI,客户端会获取该元数据,以了解授权服务、支持的权限范围(scope)以及其他资源信息。这些数据通常封装在一个 JSON 中,类似下面的示例:
{ "resource": "https://your-server.com/mcp", "authorization_servers": ["https://auth.your-server.com"], "scopes_supported": ["mcp:tools", "mcp:resources"]}
更完整的示例可以参考 RFC 9728 第 3.2 节。
- 授权服务发现
接下来,客户端会通过获取授权服务的元数据来了解它支持哪些功能。如果 PRM 文档中列出了多个授权服务,客户端可以自行决定使用哪一个。
选定授权服务后,客户端会构造一个标准的元数据 URI,并向 OpenID Connect(OIDC)发现端点或 OAuth 2.0 授权服务元数据端点发送请求(取决于授权服务支持哪种),以获取另一组元数据属性。这些属性会让客户端知道完成授权流程所需调用的各个端点。
{ "issuer": "https://auth.your-server.com", "authorization_endpoint": "https://auth.your-server.com/authorize", "token_endpoint": "https://auth.your-server.com/token", "registration_endpoint": "https://auth.your-server.com/register"}
- 客户端注册
在获取完所有元数据后,客户端需要确保自己已在授权服务上完成注册。这可以通过两种方式实现:
第一种方式是客户端已在某个授权服务上预先注册。在这种情况下,客户端可以直接使用内置的注册信息来完成授权流程。
第二种方式是客户端使用动态客户端注册(Dynamic Client Registration,DCR)向授权服务进行动态注册。这种方式要求授权服务支持 DCR。如果支持,客户端会向 registration_endpoint 发送包含自身信息的请求:
{ "client_name": "My MCP Client", "redirect_uris": ["http://localhost:3000/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"]}
如果注册成功,授权服务会返回一个包含客户端注册信息的 JSON。
没有 DCR 且未预先注册的情况如果 MCP 客户端连接的 MCP 服务所使用的授权服务不支持 DCR,且客户端也没有在该授权服务上预先注册,那么客户端开发者需要提供一种方式,让终端用户能够手动输入客户端信息。
- 用户授权
此时,客户端需要在浏览器中打开 /authorize 端点,让用户登录并授予所需的权限。授权完成后,授权服务会将用户重定向回客户端,并附带一个授权码。客户端随后使用该授权码换取令牌:
{ "access_token": "eyJhbGciOiJSUzI1NiIs...", "refresh_token": "def502...", "token_type": "Bearer", "expires_in": 3600}
客户端将使用 access_token 来对 MCP 服务的请求进行身份验证。这一步遵循标准的 OAuth 2.1 授权码流程,并使用 PKCE。
- 发送已认证的请求
最后,客户端可以通过嵌入在 Authorization 头中的 access_token 向 MCP 服务发送请求:
GET /mcp HTTP/1.1Host: your-server.comAuthorization: Bearer eyJhbGciOiJSUzI1NiIs...
MCP 服务需要验证该令牌。如果令牌有效且具备所需的权限,则处理该请求。
实现示例( Implementation Example)
为了开始一个实际的实现,我们将使用一个运行在 Docker 容器中的 Keycloak 授权服务。Keycloak 是一个开源授权服务,可以轻松地在本地部署,用于测试和实验。
请确保你已经下载并安装了 Docker Desktop。我们需要它在开发机器上部署 Keycloak。
Keycloak 设置( Keycloak Setup)
在终端中运行以下命令启动 Keycloak 容器:
docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
该命令会将 Keycloak 容器镜像拉取到本地并完成基本配置。Keycloak 将在 8080 端口运行,并创建一个管理员用户(用户名 admin,密码 admin)。
不适用于生产环境
上述配置仅适用于测试和实验场景;绝不要在生产环境中使用。如果需要部署适用于生产的授权服务(要求可靠性、安全性和高可用性),请参考《为生产环境配置 Keycloak》指南。
你可以通过浏览器访问 Keycloak 授权服务:http://localhost:8080。

在默认配置下运行时,Keycloak 已支持我们为 MCP 服务所需的多项能力,其中包括动态客户端注册(Dynamic Client Registration)。你可以通过访问以下地址查看 OIDC 配置来验证这一点:
http://localhost:8080/realms/master/.well-known/openid-configuration
我们还需要对 Keycloak 进行配置,使其支持我们所需的权限范围(scope),并允许我们的主机(本地机器)动态注册客户端 —— 因为默认策略会限制匿名用户执行动态客户端注册操作。
进入 Keycloak 控制台的 「Client scopes(客户端权限范围)」页面,创建一个新的 mcp:tools 权限范围。我们将使用该权限范围来访问 MCP 服务上的所有工具(Tool)。

创建完该权限范围(scope)后,请确保将其类型设置为「Default(默认)」,并开启「Include in token scope(包含在令牌权限范围中)」开关,这是令牌验证环节必需的配置。
接下来,我们还需要为 Keycloak 签发的令牌配置受众(audience)。配置受众至关重要,因为它会将令牌的目标接收方直接嵌入到所签发的访问令牌(access token)中。这能帮助你的 MCP 服务验证所收到的令牌确实是颁发给自己的,而非用于其他 API,是避免令牌被透传滥用的关键手段。
具体操作:打开你创建的 mcp:tools 客户端权限范围,点击「Mappers(映射器)」,接着点击「Configure a new mapper(配置新映射器)」,最后选择「Audience(受众)」类型。

在「Name(名称)」处填写 audience-config。为「Included Custom Audience(包含的自定义受众)」添加值,设置为 http://localhost:3000,这将作为我们测试服务的 URI。
不适用于生产环境
上述受众配置仅用于测试场景。在生产环境中,需要额外的设置和配置来确保签发令牌的受众范围被合理限定。具体来说,受众值需要基于客户端传入的
resource参数动态生成,而非使用固定值。
接下来,进入「Clients(客户端)」→「Client registration(客户端注册)」→「Trusted Hosts(可信主机)」页面。关闭「Client URIs Must Match(客户端 URI 必须匹配)」开关,并添加你用于测试的主机地址。
- 在 Linux 或 macOS 系统中,可运行
ifconfig命令获取当前主机 IP; - 在 Windows 系统中,可运行
ipconfig命令。
你也可以查看 Keycloak 日志,找到类似「Failed to verify remote host : 192.168.215.1(验证远程主机失败:192.168.215.1)」的行,其中的 IP 即为需要添加的地址。请确认该 IP 与你的主机关联 —— 根据 Docker 配置不同,这可能是桥接网络的 IP。

获取主机地址
如果你通过容器运行 Keycloak,也可以在终端的容器日志中查看主机 IP 地址。
最后,我们需要注册一个新客户端,MCP 服务本身将使用该客户端与 Keycloak 交互(例如执行令牌内省操作)。操作步骤如下:
- 进入「Clients(客户端)」页面。
- 点击「Create client(创建客户端)」。
- 为客户端设置唯一的「Client ID(客户端 ID)」,然后点击「Next(下一步)」。
- 开启「Client authentication(客户端身份验证)」开关,然后点击「Next(下一步)」。
- 点击「Save(保存)」。
值得注意的是,令牌内省(token introspection)只是验证令牌的方式之一。你也可以借助各语言 / 平台专属的独立库来完成令牌验证。
客户端创建完成后,打开该客户端的详情页,进入「Credentials(凭据)」标签页,并记录下「Client Secret(客户端密钥)」—— 后续会用到该密钥。

凭据管理
切勿将客户端凭据直接嵌入代码中。我们建议使用环境变量或专用的凭据存储方案来管理敏感信息。
完成 Keycloak 配置后,每当触发授权流程时,你的 MCP 服务都会收到如下格式的令牌:
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1TjcxMGw1WW5MWk13WGZ1VlJKWGtCS3ZZMzZzb3JnRG5scmlyZ2tlTHlzIn0.eyJleHAiOjE3NTU1NDA4MTcsImlhdCI6MTc1NTU0MDc1NywiYXV0aF90aW1lIjoxNzU1NTM4ODg4LCJqdGkiOiJvbnJ0YWM6YjM0MDgwZmYtODQwNC02ODY3LTgxYmUtMTIzMWI1MDU5M2E4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJzdWIiOiIzM2VkNmM2Yi1jNmUwLTQ5MjgtYTE2MS1mMmY2OWM3YTAzYjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI3OTc1YTViNi04YjU5LTRhODUtOWNiYS04ZmFlYmRhYjg5NzQiLCJzaWQiOiI4ZjdlYzI3Ni0zNThmLTRjY2MtYjMxMy1kYjA4MjkwZjM3NmYiLCJzY29wZSI6Im1jcDp0b29scyJ9.P5xCRtXORly0R0EXjyqRCUx-z3J4uAOWNAvYtLPXroykZuVCCJ-K1haiQSwbURqfsVOMbL7jiV-sD6miuPzI1tmKOkN_Yct0Vp-azvj7U5rEj7U6tvPfMkg2Uj_jrIX0KOskyU2pVvGZ-5BgqaSvwTEdsGu_V3_E0xDuSBq2uj_wmhqiyTFm5lJ1WkM3Hnxxx1_AAnTj7iOKMFZ4VCwMmk8hhSC7clnDauORc0sutxiJuYUZzxNiNPkmNeQtMCGqWdP1igcbWbrfnNXhJ6NswBOuRbh97_QraET3hl-CNmyS6C72Xc0aOwR_uJ7xVSBTD02OaQ1JA6kjCATz30kGYg
解码后,该令牌的结构如下:
{ "alg": "RS256", "typ": "JWT", "kid": "5N710l5YnLZMwXfuVRJXkBKvY36sorgDnlrirgkeLys"}.{ "exp": 1755540817, "iat": 1755540757, "auth_time": 1755538888, "jti": "onrtac:b34080ff-8404-6867-81be-1231b50593a8", "iss": "http://localhost:8080/realms/master", "aud": "http://localhost:3000", "sub": "33ed6c6b-c6e0-4928-a161-f2f69c7a03b9", "typ": "Bearer", "azp": "7975a5b6-8b59-4a85-9cba-8faebdab8974", "sid": "8f7ec276-358f-4ccc-b313-db08290f376f", "scope": "mcp:tools"}.[Signature]
嵌入式受众
请注意令牌中嵌入的
aud声明 —— 它当前被设置为测试用 MCP 服务的 URI,该值来源于我们之前配置的权限范围(scope)。这一字段将是我们实现令牌验证逻辑的关键。
MCP 服务搭建(MCP Server Setup)
接下来,我们将配置 MCP 服务,使其对接本地运行的 Keycloak 授权服务。你可以根据自己熟悉的编程语言,选择对应的 MCP SDK(软件开发工具包)来实现。
为满足测试需求,我们会创建一个极简的 MCP 服务:该服务对外暴露两个工具(Tool)—— 一个用于加法运算,另一个用于乘法运算,且访问这两个工具均需通过授权验证。
TypeScript
你可以在示例代码仓库中查看完整的 TypeScript 项目代码。
在运行以下代码之前,请确保你已创建一个 .env 文件,并包含以下内容:
# Server host/portHOST=localhostPORT=3000# Auth server locationAUTH_HOST=localhostAUTH_PORT=8080AUTH_REALM=master# Keycloak OAuth client credentialsOAUTH_CLIENT_ID=<YOUR_SERVER_CLIENT_ID>OAUTH_CLIENT_SECRET=<YOUR_SERVER_CLIENT_SECRET>
OAUTH_CLIENT_ID 和 OAUTH_CLIENT_SECRET 这两个环境变量对应我们此前为 MCP 服务创建的客户端(Client)凭据。
除了实现 MCP 授权规范外,下述服务代码还会通过 Keycloak 执行令牌内省(token introspection),以确保从客户端接收的令牌是有效的。同时,代码中还实现了基础的日志功能,方便你排查各类问题。
import "dotenv/config";import express from "express";import { randomUUID } from "node:crypto";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";import { z } from "zod";import cors from "cors";import { mcpAuthMetadataRouter, getOAuthProtectedResourceMetadataUrl,} from "@modelcontextprotocol/sdk/server/auth/router.js";import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js";const CONFIG = { host: process.env.HOST || "localhost", port: Number(process.env.PORT) || 3000, auth: { host: process.env.AUTH_HOST || process.env.HOST || "localhost", port: Number(process.env.AUTH_PORT) || 8080, realm: process.env.AUTH_REALM || "master", clientId: process.env.OAUTH_CLIENT_ID || "mcp-server", clientSecret: process.env.OAUTH_CLIENT_SECRET || "", },};function createOAuthUrls() { const authBaseUrl = new URL( `http://${CONFIG.auth.host}:${CONFIG.auth.port}/realms/${CONFIG.auth.realm}/`, ); return { issuer: authBaseUrl.toString(), introspection_endpoint: new URL( "protocol/openid-connect/token/introspect", authBaseUrl, ).toString(), authorization_endpoint: new URL( "protocol/openid-connect/auth", authBaseUrl, ).toString(), token_endpoint: new URL( "protocol/openid-connect/token", authBaseUrl, ).toString(), };}function createRequestLogger() { return (req: any, res: any, next: any) => { const start = Date.now(); res.on("finish", () => { const ms = Date.now() - start; console.log( `${req.method} ${req.originalUrl} -> ${res.statusCode} ${ms}ms`, ); }); next(); };}const app = express();app.use( express.json({ verify: (req: any, _res, buf) => { req.rawBody = buf?.toString() ?? ""; }, }),);app.use( cors({ origin: "*", exposedHeaders: ["Mcp-Session-Id"], }),);app.use(createRequestLogger());const mcpServerUrl = new URL(`http://${CONFIG.host}:${CONFIG.port}`);const oauthUrls = createOAuthUrls();const oauthMetadata: OAuthMetadata = { ...oauthUrls, response_types_supported: ["code"],};const tokenVerifier = { verifyAccessToken: async (token: string) => { const endpoint = oauthMetadata.introspection_endpoint; if (!endpoint) { console.error("[auth] no introspection endpoint in metadata"); throw new Error("No token verification endpoint available in metadata"); } const params = new URLSearchParams({ token: token, client_id: CONFIG.auth.clientId, }); if (CONFIG.auth.clientSecret) { params.set("client_secret", CONFIG.auth.clientSecret); } let response: Response; try { response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: params.toString(), }); } catch (e) { console.error("[auth] introspection fetch threw", e); throw e; } if (!response.ok) { const txt = await response.text(); console.error("[auth] introspection non-OK", { status: response.status }); try { const obj = JSON.parse(txt); console.log(JSON.stringify(obj, null, 2)); } catch { console.error(txt); } throw new Error(`Invalid or expired token: ${txt}`); } let data: any; try { data = await response.json(); } catch (e) { const txt = await response.text(); console.error("[auth] failed to parse introspection JSON", { error: String(e), body: txt, }); throw e; } if (data.active === false) { throw new Error("Inactive token"); } if (!data.aud) { throw new Error("Resource indicator (aud) missing"); } const audiences: string[] = Array.isArray(data.aud) ? data.aud : [data.aud]; const allowed = audiences.some((a) => checkResourceAllowed({ requestedResource: a, configuredResource: mcpServerUrl, }), ); if (!allowed) { throw new Error( `None of the provided audiences are allowed. Expected ${mcpServerUrl}, got: ${audiences.join(", ")}`, ); } return { token, clientId: data.client_id, scopes: data.scope ? data.scope.split(" ") : [], expiresAt: data.exp, }; },};app.use( mcpAuthMetadataRouter({ oauthMetadata, resourceServerUrl: mcpServerUrl, scopesSupported: ["mcp:tools"], resourceName: "MCP Demo Server", }),);const authMiddleware = requireBearerAuth({ verifier: tokenVerifier, requiredScopes: [], resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),});const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};function createMcpServer() { const server = new McpServer({ name: "example-server", version: "1.0.0", }); server.registerTool( "add", { title: "Addition Tool", description: "Add two numbers together", inputSchema: { a: z.number().describe("First number to add"), b: z.number().describe("Second number to add"), }, }, async ({ a, b }) => ({ content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }], }), ); server.registerTool( "multiply", { title: "Multiplication Tool", description: "Multiply two numbers together", inputSchema: { x: z.number().describe("First number to multiply"), y: z.number().describe("Second number to multiply"), }, }, async ({ x, y }) => ({ content: [{ type: "text", text: `${x} × ${y} = ${x * y}` }], }), ); return server;}const mcpPostHandler = async (req: express.Request, res: express.Response) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; let transport: StreamableHTTPServerTransport; if (sessionId && transports[sessionId]) { transport = transports[sessionId]; } else if (!sessionId && isInitializeRequest(req.body)) { transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (sessionId) => { transports[sessionId] = transport; }, }); transport.onclose = () => { if (transport.sessionId) { delete transports[transport.sessionId]; } }; const server = createMcpServer(); await server.connect(transport); } else { res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: No valid session ID provided", }, id: null, }); return; } await transport.handleRequest(req, res, req.body);};const handleSessionRequest = async ( req: express.Request, res: express.Response,) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; if (!sessionId || !transports[sessionId]) { res.status(400).send("Invalid or missing session ID"); return; } const transport = transports[sessionId]; await transport.handleRequest(req, res);};app.post("/", authMiddleware, mcpPostHandler);app.get("/", authMiddleware, handleSessionRequest);app.delete("/", authMiddleware, handleSessionRequest);app.listen(CONFIG.port, CONFIG.host, () => { console.log(`🚀 MCP Server running on ${mcpServerUrl.origin}`); console.log(`📡 MCP endpoint available at ${mcpServerUrl.origin}`); console.log( `🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`, );});
运行该服务后,你可以将其接入到 MCP 客户端(例如 Visual Studio Code)中,只需在客户端配置里填写该 MCP 服务的端点(endpoint)地址即可。
如需了解更多关于使用 TypeScript 实现 MCP 服务的细节,请参考 TypeScript SDK 文档。
Python
你可以在示例代码仓库中查看完整的 Python 项目代码。
为简化授权交互流程,在 Python 开发场景中,我们会基于 FastMCP 来实现。尽管不同编程语言中,授权相关的通用约定(如端点设计、令牌验证逻辑)是保持一致的,但部分语言提供了更简便的方式,可将这些授权逻辑集成到生产环境中。
在编写实际的服务代码前,我们需要先在 config.py 文件中完成配置 —— 配置内容完全取决于你的本地服务部署情况:
"""Configuration settings for the MCP auth server."""import osfrom typing import Optionalclass Config: """Configuration class that loads from environment variables with sensible defaults.""" # Server settings HOST: str = os.getenv("HOST", "localhost") PORT: int = int(os.getenv("PORT", "3000")) # Auth server settings AUTH_HOST: str = os.getenv("AUTH_HOST", "localhost") AUTH_PORT: int = int(os.getenv("AUTH_PORT", "8080")) AUTH_REALM: str = os.getenv("AUTH_REALM", "master") # OAuth client settings OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "mcp-server") OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "UO3rmozkFFkXr0QxPTkzZ0LMXDidIikB") # Server settings MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools") OAUTH_STRICT: bool = os.getenv("OAUTH_STRICT", "false").lower() in ("true", "1", "yes") TRANSPORT: str = os.getenv("TRANSPORT", "streamable-http") @property def server_url(self) -> str: """Build the server URL.""" return f"http://{self.HOST}:{self.PORT}" @property def auth_base_url(self) -> str: """Build the auth server base URL.""" return f"http://{self.AUTH_HOST}:{self.AUTH_PORT}/realms/{self.AUTH_REALM}/" def validate(self) -> None: """Validate configuration.""" if self.TRANSPORT not in ["sse", "streamable-http"]: raise ValueError(f"Invalid transport: {self.TRANSPORT}. Must be 'sse' or 'streamable-http'")# Global configuration instanceconfig = Config()
服务的实现代码如下:
import datetimeimport loggingfrom typing import Anyfrom pydantic import AnyHttpUrlfrom mcp.server.auth.settings import AuthSettingsfrom mcp.server.fastmcp.server import FastMCPfrom .config import configfrom .token_verifier import IntrospectionTokenVerifierlogger = logging.getLogger(__name__)def create_oauth_urls() -> dict[str, str]: """Create OAuth URLs based on configuration (Keycloak-style).""" from urllib.parse import urljoin auth_base_url = config.auth_base_url return { "issuer": auth_base_url, "introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"), "authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"), "token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"), }def create_server() -> FastMCP: """Create and configure the FastMCP server.""" config.validate() oauth_urls = create_oauth_urls() token_verifier = IntrospectionTokenVerifier( introspection_endpoint=oauth_urls["introspection_endpoint"], server_url=config.server_url, client_id=config.OAUTH_CLIENT_ID, client_secret=config.OAUTH_CLIENT_SECRET, ) app = FastMCP( name="MCP Resource Server", instructions="Resource Server that validates tokens via Authorization Server introspection", host=config.HOST, port=config.PORT, debug=True, streamable_http_path="/", token_verifier=token_verifier, auth=AuthSettings( issuer_url=AnyHttpUrl(oauth_urls["issuer"]), required_scopes=[config.MCP_SCOPE], resource_server_url=AnyHttpUrl(config.server_url), ), ) @app.tool() async def add_numbers(a: float, b: float) -> dict[str, Any]: """ Add two numbers together. This tool demonstrates basic arithmetic operations with OAuth authentication. Args: a: The first number to add b: The second number to add """ result = a + b return { "operation": "addition", "operand_a": a, "operand_b": b, "result": result, "timestamp": datetime.datetime.now().isoformat() } @app.tool() async def multiply_numbers(x: float, y: float) -> dict[str, Any]: """ Multiply two numbers together. This tool demonstrates basic arithmetic operations with OAuth authentication. Args: x: The first number to multiply y: The second number to multiply """ result = x * y return { "operation": "multiplication", "operand_x": x, "operand_y": y, "result": result, "timestamp": datetime.datetime.now().isoformat() } return appdef main() -> int: """ Run the MCP Resource Server. This server: - Provides RFC 9728 Protected Resource Metadata - Validates tokens via Authorization Server introspection - Serves MCP tools requiring authentication Configuration is loaded from config.py and environment variables. """ logging.basicConfig(level=logging.INFO) try: config.validate() oauth_urls = create_oauth_urls() except ValueError as e: logger.error("Configuration error: %s", e) return 1 try: mcp_server = create_server() logger.info("Starting MCP Server on %s:%s", config.HOST, config.PORT) logger.info("Authorization Server: %s", oauth_urls["issuer"]) logger.info("Transport: %s", config.TRANSPORT) mcp_server.run(transport=config.TRANSPORT) return 0 except Exception: logger.exception("Server error") return 1if __name__ == "__main__": exit(main())
最后,令牌验证逻辑被完全委托给 token_verifier.py 文件来处理,以此确保我们能够调用 Keycloak 的令牌内省端点,验证所有凭据(credential artifacts)的有效性。
"""Token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""import loggingfrom typing import Anyfrom mcp.server.auth.provider import AccessToken, TokenVerifierfrom mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_urllogger = logging.getLogger(__name__)class IntrospectionTokenVerifier(TokenVerifier): """Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662). """ def __init__( self, introspection_endpoint: str, server_url: str, client_id: str, client_secret: str, ): self.introspection_endpoint = introspection_endpoint self.server_url = server_url self.client_id = client_id self.client_secret = client_secret self.resource_url = resource_url_from_server_url(server_url) async def verify_token(self, token: str) -> AccessToken | None: """Verify token via introspection endpoint.""" import httpx if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")): return None timeout = httpx.Timeout(10.0, connect=5.0) limits = httpx.Limits(max_connections=10, max_keepalive_connections=5) async with httpx.AsyncClient( timeout=timeout, limits=limits, verify=True, ) as client: try: form_data = { "token": token, "client_id": self.client_id, "client_secret": self.client_secret, } headers = {"Content-Type": "application/x-www-form-urlencoded"} response = await client.post( self.introspection_endpoint, data=form_data, headers=headers, ) if response.status_code != 200: return None data = response.json() if not data.get("active", False): return None if not self._validate_resource(data): return None return AccessToken( token=token, client_id=data.get("client_id", "unknown"), scopes=data.get("scope", "").split() if data.get("scope") else [], expires_at=data.get("exp"), resource=data.get("aud"), # Include resource in token ) except Exception as e: return None def _validate_resource(self, token_data: dict[str, Any]) -> bool: """Validate token was issued for this resource server. Rules: - Reject if 'aud' missing. - Accept if any audience entry matches the derived resource URL. - Supports string or list forms per JWT spec. """ if not self.server_url or not self.resource_url: return False aud: list[str] | str | None = token_data.get("aud") if isinstance(aud, list): return any(self._is_valid_resource(a) for a in aud) if isinstance(aud, str): return self._is_valid_resource(aud) return False def _is_valid_resource(self, resource: str) -> bool: """Check if the given resource matches our server.""" return check_resource_allowed(self.resource_url, resource)
如需了解更多细节,请参阅 Python SDK 文档。
C#
你可以在示例代码仓库中查看完整的 C# 项目代码。
若要通过 MCP C# SDK 在你的 MCP 服务中配置授权功能,可基于 ASP.NET Core 标准的构建器模式(builder pattern)来实现。与此前调用 Keycloak 令牌内省端点的方式不同,在 C# 开发场景中,我们会使用 ASP.NET Core 内置的令牌验证能力来完成令牌校验。
using Microsoft.AspNetCore.Authentication.JwtBearer;using Microsoft.IdentityModel.Tokens;using ModelContextProtocol.AspNetCore.Authentication;using ProtectedMcpServer.Tools;using System.Security.Claims;var builder = WebApplication.CreateBuilder(args);var serverUrl = "http://localhost:3000/";var authorizationServerUrl = "http://localhost:8080/realms/master/";builder.Services.AddAuthentication(options =>{ options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;}).AddJwtBearer(options =>{ options.Authority = authorizationServerUrl; var normalizedServerAudience = serverUrl.TrimEnd('/'); options.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = authorizationServerUrl, ValidAudiences = new[] { normalizedServerAudience, serverUrl }, AudienceValidator = (audiences, securityToken, validationParameters) => { if (audiences == null) return false; foreach (var aud in audiences) { if (string.Equals(aud.TrimEnd('/'), normalizedServerAudience, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } }; options.RequireHttpsMetadata = false; // Set to true in production options.Events = new JwtBearerEvents { OnTokenValidated = context => { var name = context.Principal?.Identity?.Name ?? "unknown"; var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown"; Console.WriteLine($"Token validated for: {name} ({email})"); return Task.CompletedTask; }, OnAuthenticationFailed = context => { Console.WriteLine($"Authentication failed: {context.Exception.Message}"); return Task.CompletedTask; }, };}).AddMcp(options =>{ options.ResourceMetadata = new() { Resource = new Uri(serverUrl), ResourceDocumentation = new Uri("https://docs.example.com/api/math"), AuthorizationServers = { new Uri(authorizationServerUrl) }, ScopesSupported = ["mcp:tools"] };});builder.Services.AddAuthorization();builder.Services.AddHttpContextAccessor();builder.Services.AddMcpServer() .WithTools<MathTools>() .WithHttpTransport();var app = builder.Build();app.UseAuthentication();app.UseAuthorization();app.MapMcp().RequireAuthorization();Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}");Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");Console.WriteLine("Exposed Math tools: Add, Multiply");Console.WriteLine("Press Ctrl+C to stop the server");app.Run(serverUrl);
如需了解更多细节,请参阅 C# SDK 文档。
测试 MCP 服务( Testing the MCP Server)
为完成测试,我们将使用 Visual Studio Code 作为客户端(不过任何支持 MCP 协议及新版授权规范的客户端均适用)。
按下 Cmd + Shift + P(macOS)/ Ctrl + Shift + P(Windows/Linux)快捷键,选择「MCP: Add server…」选项,选择「HTTP」类型,输入服务地址 http://localhost:3000,并为该服务设置一个在 VS Code 中唯一的名称。此时,你的 mcp.json 文件中会新增如下条目:
"my-mcp-server-18676652": { "url": "http://localhost:3000", "type": "http"}
建立连接时,系统会自动打开浏览器,提示你授权 Visual Studio Code 访问 mcp:tools 权限范围(scope)。

完成授权同意后,你会在 mcp.json 文件中对应服务条目上方,看到已列出的(MCP 服务)工具列表。

你可以在聊天视图中通过 # 符号来调用单个工具(Tool)。

常见踩坑点以及避免方法( Common Pitfalls and How to Avoid Them)
为获取全面的安全指导(包括攻击向量、缓解策略和实施最佳实践),请务必阅读安全最佳实践。以下列出一些关键问题。
- **不要自行实现令牌验证或授权逻辑。**对于令牌验证或授权决策等功能,请使用现成、经过充分测试且安全的库。从零实现意味着你更可能出现错误,除非你是安全专家。
- **使用短期访问令牌。**根据所使用的授权服务,此设置可能可自定义。我们建议不要使用长期令牌,如果恶意攻击者窃取了它们,他们将能够在更长时间内保持访问权限。
- **始终验证令牌。**服务收到令牌并不意味着该令牌有效或目标是你的服务。请始终验证 MCP 服务从客户端收到的内容是否符合所需约束。
- 将令牌存储在安全、加密的存储中。在某些情况下,你可能需要在服务端缓存令牌。如果是这种情况,请确保存储具有适当的访问控制,并且不能被访问你服务的恶意方轻易窃取。你还应实施可靠的缓存淘汰策略,以确保 MCP 服务不会重复使用已过期或无效的令牌。
- **在生产环境中强制使用 HTTPS。**除开发期间的 localhost 外,不要通过明文 HTTP 接受令牌或重定向回调。
- **遵循最小权限范围原则。**不要使用 “一网打尽” 式的范围。尽可能按工具或功能拆分访问权限,并在资源服务上按路由 / 工具验证所需范围。
- **不要记录凭据。**切勿记录
Authorization头、令牌、代码或密钥。清理查询字符串和头信息。在结构化日志中脱敏敏感字段。 - **区分应用程序与资源服务的凭据。**不要将 MCP 服务的客户端密钥复用于终端用户流程。将所有密钥存储在适当的密钥管理器中,而不是源代码控制系统中。
- **返回正确的质询信息。**在 401 响应中包含带有
Bearer、realm和resource_metadata的WWW-Authenticate头,以便客户端能够发现如何进行身份验证。 - **DCR(动态客户端注册)控制。**如果启用 DCR,请了解你组织的特定约束,例如受信任主机、必要的审核和经过审计的注册流程。未经验证的 DCR 意味着任何人都可以向你的授权服务注册任何客户端。
- **多租户 / 领域混淆。**除非明确支持多租户,否则请固定为单一颁发者 / 租户。即使令牌由同一授权服务签名,也应拒绝来自其他领域的令牌。
- **受众 / 资源指示器滥用。**不要配置或接受通用受众(如 api)或无关资源。要求受众 / 资源与你配置的服务匹配。
- **错误细节泄露。**向客户端返回通用消息,但在内部使用关联 ID 记录详细原因,以便在不暴露内部信息的情况下进行故障排除。
- **会话标识符强化。**将
Mcp-Session-Id视为不可信输入;切勿将授权与它绑定。在身份验证发生变化时重新生成,并在服务端验证其生命周期。
关键概念注解(简短)
- 令牌验证:确认令牌是否真实、未过期、签名有效、受众正确。
- 最小权限范围:只授予完成任务所需的最小权限,降低泄露风险。
- DCR:动态客户端注册,允许客户端在运行时向授权服务注册。
- Mcp-Session-Id:用于关联请求的会话标识符,但不应作为授权依据。
相关标准与文档(Related Standards and Documentation)
MCP 的授权机制建立在以下成熟标准之上:
如需更多细节,请参考:
理解这些标准将帮助你正确实现授权,并在出现问题时进行故障排除。
MCP Inspector
使用 MCP Inspector 测试与调试 Model Context Protocol 服务的深度指南
MCP Inspector 是一款用于测试和调试 MCP 服务的交互式开发者工具。《调试指南》将 Inspector 作为整体调试工具集的一部分进行了介绍,而本文档则对 Inspector 的功能与能力进行了深入讲解。
快速上手(Getting started)
安装与基础使用(Installation and basic usage)
Inspector 可直接通过 npx 运行,无需提前安装:
npx @modelcontextprotocol/inspector <command>
npx @modelcontextprotocol/inspector <command> <arg1> <arg2>
检查来自 npm 或 PyPI 的服务(Inspecting servers from npm or PyPI)
从 npm 或 PyPI 启动服务包的常用方式:
npm package:
npx -y @modelcontextprotocol/inspector npx <package-name> <args># For examplenpx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /Users/username/Desktop
PyPI package:
npx @modelcontextprotocol/inspector uvx <package-name> <args># For examplenpx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/mcp/servers.git
检查本地开发的服务(Inspecting locally developed servers)
若要检查以代码仓库形式本地开发或下载的服务,最常用的方式如下:
TypeScript:
npx @modelcontextprotocol/inspector node path/to/server/index.js args...
Python:
npx @modelcontextprotocol/inspector \ uv \ --directory path/to/server \ run \ package-name \ args...
请仔细阅读附带的 README 文件,以获取最准确的使用说明。
功能概览(Feature Overview)

Inspector 提供了多项用于与你的 MCP 服务交互的功能:
服务连接面板( Server connection pane)
- 允许选择用于连接服务的传输方式
- 对于本地服务,支持自定义命令行参数和环境变量
资源标签页(Resources tab)
- 列出所有可用资源
- 显示资源元数据(MIME 类型、描述)
- 允许查看资源内容
- 支持订阅测试
提示词标签页(Prompts tab)
- 显示可用的提示词模板
- 显示提示词参数和描述
- 支持使用自定义参数进行提示词测试
- 预览生成的消息
工具标签页(Tools tab)
- 列出可用工具
- 显示工具 schema 和描述
- 支持使用自定义输入进行工具测试
- 显示工具执行结果
通知面板
- 展示服务的所有日志记录
- 显示来自服务的通知
最佳实践(Best practices)
开发流程(Development workflow)
-
开始开发
- 启动 Inspector 并连接你的服务
- 验证基本连通性
- 检查能力协商(capability negotiation)
-
迭代测试
- 修改服务代码
- 重新构建服务
- 在 Inspector 中重新连接
- 测试受影响的功能
- 监控消息
-
测试边界情况
- 无效输入
- 缺失提示词参数
- 并发操作
- 验证错误处理和错误响应
本文内容仅个人观点,转载请注明出处《MCP官方文档翻译 - 开发文档(Documentation)》 https://blog.csdn.net/huyuyang6688/article/details/157138588
更多推荐


所有评论(0)