mcp_tutorial
2026-03-24 00:03:39

写在前面:

本文章是关于 Anthropic 公司开源协议框架 MPC 的部分学习笔记。碍于本人学识有限,部分叙述难免存在纰漏,请读者注意甄别。

参考文档:


MCP 与 FunctionCalling 的关系

什么是 FunctionCalling

Function Calling 能让 LLM 触发外部行为,是因为:它把“行为”编码成了模型可以预测的“文本结构”。

无论多复杂,LLM 本质只有一个能力:预测下一个 token。那么为什么“预测 token”可以变成“触发行为”?

  • 没有 Function Calling 时,模型只能输出:
1
今天天气是 20 度
  • 有 Function Calling 时,我们给模型一种“特殊语言”:
1
2
3
4
5
6
{
"name": "get_weather",
"arguments": {
"city": "上海"
}
}

由此可见,函数调用(Function Calling)并不是真正调用,而是一段“特定格式(格式化)的文本”。

真正的触发发生在哪里?

  • 模型做的事:输出 JSON
  • 系统做的事:解析 JSON $\to$ 调用函数 $\to$ 返回结果

所以,触发行为的不是 LLM本身,而是“系统解释/调用了 LLM 的输出”。

为什么模型“会说这种话”?

因为我们在训练或提示中做了两件事:给它工具说明(Schema)/告诉它规则,所以当 $P(\text{function call} | 输入) > P(文本 | 输入)$时,模型就会输出“特定格式的文本”。

由此可见,针对不同的模型实现相同的 Function Calling 往往需要针对性开发适配,而 MCP 的出现很好的解决了规范性的问题(协议)。

Function Calling VS. MCP

Function Calling 解决了“模型怎么调用自定义函数”,但在实际使用中还面临一些问题:

  • 多个工具组成的调用链(先查天气、再发邮件)
  • 工具参数结构的规范与自动注册
  • 不同调用方式的适配(HTTP、本地插件等)
  • 在不同模型间复用统一的工具体系

MCP 是由 Anthropic 推出的开放标准协议,旨在为大模型和外部工具之间的通信提供通用接口。它不是 Function Calling 的替代,而是对其在执行层面的进一步规范和封装,使工具系统更易接入、更易管理、更易复用

维度 Function Calling MCP
目标 让模型调用函数 标准化工具接入
面向对象 LLM 系统 / Agent / 工具生态
关注点 调用“哪个函数 + 参数” 工具“怎么被发现 + 调用”
本质 推理能力增强 通信协议

MCP 核心角色

MCP 采用客户端-服务端架构(CS),其中 MCP 宿主(Host),如 Claude Code 等 AI 应用,会建立与一个或多个 MCP 服务端的连接。MCP 宿主通过为每个 MCP 服务端创建一个 MCP 客户端来实现这一点。每个 MCP 客户端与其对应的 MCP 服务端保持专用连接。以下是 MCP 官方展示的架构:

2430457-20250512135338388-1937866079

MCP Client

  • 向 MCP Server 请求工具列表
  • 使用 HTTP 或 stdio 协议发起工具调用请求

例如,聊天应用类,提供自然语言交互服务,让用户通过对话调用 AI 能力;编码工具类,在 IDE 里调用外部应用和系统的能力;任务自动化类,帮用户自动化执行重复性任务,如数据处理、流程调度,以提升效率。

MCP Server

  • 接收 tool_calls,根据调用内容执行对应工具
  • 返回统一格式的结构化结果

例如,数据库类(如 ClickHouse、Supabase)负责数据存储、查询与管理;设计类(如 Figma、Blender)支撑设计创作、文件协作等功能;生产力工具类(如 Notion、Obsidian)提供笔记管理、知识整理等办公协作服务;支付类(如 Stripe),处理在线支付交易,支持商业场景的资金流转。

MCP 分层

MCP 由两层组成

  • 数据层:定义基于 JSON-RPC 的客户端-服务端通信协议,包括生命周期管理以及工具、资源、提示词和通知等核心原语。
  • 传输层:定义支持客户端和服务端之间数据交换的通信机制和通道,包括特定传输方式的连接建立、消息分帧和授权。

从概念上讲,数据层是内层,而传输层是外层。

数据层

数据层实现了一个基于 JSON-RPC 2.0 的交互协议,定义了消息结构和语义。该层包括

  • 生命周期管理:处理客户端和服务端之间的连接初始化、能力协商和连接终止
  • 服务端特性:使服务端能够提供核心功能,包括用于 AI 操作的“工具”、用于上下文数据的“资源”以及用于交互模板的“提示词”
  • 客户端特性:使服务端能够请求客户端从宿主 LLM 进行采样、引导用户输入以及向客户端记录日志消息
  • 实用特性:支持额外功能,如用于实时更新的“通知”和用于长时间运行操作的“进度跟踪”

传输层

传输层管理客户端和服务端之间的通信通道和认证。它负责处理 MCP 参与者之间的连接建立、消息分帧和安全通信。MCP 支持两种传输机制:

  • Stdio 传输:使用标准输入/输出流进行同一机器上本地进程间的直接通信,提供无网络开销的最佳性能。
    • 可流式 HTTP 传输:使用 HTTP POST 发送客户端到服务端的请求,并结合可选的服务器发送事件 (SSE) 实现流式功能。此传输方式支持远程服务器通信,并支持标准 HTTP 认证方法,包括 Bearer 令牌、API 密钥和自定义标头。MCP 建议使用 OAuth 来获取身份验证令牌。

传输层将通信细节从协议层中抽象出来,使得相同的 JSON-RPC 2.0 消息格式可以在所有传输机制中使用。

MCP 原语

MCP 原语是 MCP 中最重要的概念。它们定义了客户端和服务端可以相互提供的内容。这些原语指定了可以与 AI 应用共享的上下文信息类型以及可以执行的操作范围。MCP 定义了 服务端 可以暴露的三种核心原语:

  • 工具 (Tools):AI 应用可以调用以执行操作的可执行函数(例如:文件操作、API 调用、数据库查询)
  • 资源 (Resources):为 AI 应用提供上下文信息的数据源(例如:文件内容、数据库记录、API 响应)
  • 提示词 (Prompts):帮助构建与语言模型交互的可重用模板(例如:系统提示词、少样本示例)

每种原语类型都有关联的发现 (*/list)、获取 (*/get) 以及在某些情况下的执行 (tools/call) 方法。MCP 客户端将使用 */list 方法来发现可用的原语。

例如,客户端可以先列出所有可用工具 (tools/list) 然后执行它们。这种设计允许列表是动态的。举一个具体的例子,考虑一个提供数据库上下文的 MCP 服务端。它可以暴露用于查询数据库的“工具”,一个包含数据库模式的“资源”,以及一个包含与工具交互的少样本示例的“提示词”。

MCP 还定义了 客户端 可以暴露的原语。这些原语允许 MCP 服务端作者构建更丰富的交互。

  • 采样 (Sampling):允许服务端请求客户端 AI 应用进行语言模型补全。当服务端作者想要访问语言模型,但希望保持模型独立且不愿在 MCP 服务端中包含语言模型 SDK 时,这非常有用。他们可以使用 sampling/complete 方法请求客户端 AI 应用进行语言模型补全。
  • 引导 (Elicitation):允许服务端向用户请求额外信息。当服务端作者想要从用户那里获取更多信息或请求对某项操作的确认时,这非常有用。他们可以使用 elicitation/request 方法向用户请求额外信息。
  • 日志记录 (Logging):使服务端能够向客户端发送日志消息,用于调试和监控目的。

数据层生命周期序列

1. 初始化(生命周期管理)

MCP 从通过能力协商握手(capability negotiation handshake)进行的生命周期管理开始,客户端发送 initialize 请求以建立连接并协商支持的特性。

  • 初始化请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-05-18",
"capabilities": {
"elicitation": {}
},
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}
  • 初始化响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-05-18",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {}
},
"serverInfo": {
"name": "example-server",
"version": "1.0.0"
}
}
}

初始化过程是 MCP 生命周期管理的关键部分,具有几个重要目的

  1. 协议版本协商protocolVersion 字段(例如 "2025-05-18")确保客户端和服务端使用的是兼容的协议版本。这可以防止不同版本尝试交互时可能发生的通信错误。如果没有协商出共同兼容的版本,则应终止连接。
  2. 能力发现capabilities 对象允许各方声明它们支持哪些特性,包括它们可以处理哪些原语(工具、资源、提示词)以及是否支持通知等特性。这通过避免不支持的操作来实现高效通信。
  3. 身份交换clientInfoserverInfo 对象提供标识和版本信息,用于调试和兼容性目的。

在此示例中,能力协商展示了 MCP 原语是如何声明的:

  • 客户端能力

    • "elicitation": {} - 客户端声明它可以处理用户交互请求(可以接收 elicitation/create 方法调用)
  • 服务端能力:

    • "tools": {"listChanged": true} - 服务端支持工具原语,并且可以在工具列表更改时发送 tools/list_changed 通知
    • "resources": {} - 服务端还支持资源原语(可以处理 resources/listresources/read 方法)

初始化成功后,客户端发送通知表示已就绪

1
2
3
4
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}

在初始化期间,AI 应用的 MCP 客户端管理器会建立与已配置服务端的连接,并存储它们的能力以备后用。应用使用此信息来确定哪些服务端可以提供特定类型的功能(工具、资源、提示词)以及它们是否支持实时更新。

1
2
3
4
5
6
7
# 伪代码
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. 工具发现(原语)

既然连接已建立,客户端就可以通过发送 tools/list 请求来发现可用的工具。此请求是 MCP 工具发现机制的基础——它允许客户端在尝试使用工具之前了解服务端上有哪些工具可用。

  • 工具列表请求:tools/list 请求很简单,不包含任何参数。
1
2
3
4
5
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}
  • 工具列表响应:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
"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 数组,提供有关每个可用工具的详尽元数据。这种基于数组的结构允许服务端同时暴露多个工具,同时保持不同功能之间的清晰界限。响应中的每个工具对象包含几个关键字段:

  • name:服务端命名空间内工具的唯一标识符。这是工具执行的主键,应遵循清晰的命名模式(例如:calculator_arithmetic 而不仅仅是 calculate
  • title:工具的人类可读显示名称,客户端可以向用户显示
  • description:关于工具用途及何时使用的详细说明
  • inputSchema:一个 JSON Schema,定义了预期的输入参数,支持类型验证并提供关于必填和可选参数的清晰文档。

AI 应用从所有连接的 MCP 服务端获取可用工具,并将它们合并到一个语言模型可以访问的统一工具注册表中。这使得 LLM 能够理解它可以执行哪些操作,并在对话期间自动生成适当的工具调用。

1
2
3
4
5
6
# 伪代码
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. 工具执行(原语)

客户端现在可以使用 tools/call 方法执行工具。这演示了 MCP 原语在实践中是如何使用的:在发现可用工具后,客户端可以使用适当的参数调用它们。

  • 工具调用请求:tools/call 请求遵循结构化格式,确保客户端和服务端之间的类型安全和清晰通信。
1
2
3
4
5
6
7
8
9
10
11
12
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "weather_current",
"arguments": {
"location": "San Francisco",
"units": "imperial"
}
}
}

请求结构包含几个重要部分

  1. name:必须与发现响应中的工具名称 (weather_current) 完全匹配。这确保了服务端可以正确识别要执行哪个工具。
  2. arguments:包含由工具的 inputSchema 定义的输入参数。在本例中
    • location: “San Francisco”(必填参数)
    • units: “imperial”(可选参数,如果不指定则默认为 “metric”)
  3. JSON-RPC 结构:使用标准 JSON-RPC 2.0 格式,带有用于请求-响应关联的唯一 id
  • 工具调用相应:
1
2
3
4
5
6
7
8
9
10
11
12
{
"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%"
}
]
}
}

响应展示了 MCP 灵活的内容系统

  1. content 数组:工具响应返回内容对象的数组,支持丰富的多格式响应(文本、图像、资源等)
  2. content类型:每个内容对象都有一个 type 字段。在本例中,"type": "text" 表示纯文本内容,但 MCP 支持针对不同用例的各种内容类型。
  3. 结构化输出:响应提供了可操作的信息,AI 应用可以将其用作语言模型交互的上下文。

这种执行模式允许 AI 应用动态调用服务端功能,并接收可以集成到与语言模型对话中的结构化响应。

当语言模型在对话期间决定使用工具时,AI 应用会拦截该工具调用,将其路由到适当的 MCP 服务端执行,并将结果作为对话流的一部分返回给 LLM。这使得 LLM 能够访问实时数据并在外部世界执行操作。

1
2
3
4
5
# 伪代码
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 支持实时通知,使服务端能够告知客户端更改而无需明确请求。这是一个保持 MCP 连接同步和响应灵敏的关键特性。当服务端的可用工具发生变化时,例如新功能上线、现有工具被修改或工具暂时不可用,服务端可以主动通知已连接的客户端。

MCP 通知的主要特性

  1. 无需响应:请注意通知中没有 id 字段。这遵循了 JSON-RPC 2.0 通知语义,即不期望也不发送响应。
  2. 基于能力:此通知仅由在初始化期间在其工具能力中声明了 "listChanged": true 的服务端发送(如步骤 1 所示)。
  3. 事件驱动:服务端根据内部状态更改决定何时发送通知,使 MCP 连接具有动态性和响应性。
  • 请求
1
2
3
4
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}

收到此通知后,客户端通常会通过请求更新后的工具列表来做出反应。这创建了一个刷新循环,使客户端对可用工具的理解保持最新。

  • 响应
1
2
3
4
5
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/list"
}

此通知系统至关重要,原因如下:

  1. 动态环境:工具可能会根据服务端状态、外部依赖或用户权限而出现或消失
  2. 效率:客户端不需要轮询更改;当更新发生时它们会被通知
  3. 一致性:确保客户端始终拥有关于可用服务端能力的准确信息
  4. 实时协作:支持能够适应不断变化的上下文的响应式 AI 应用

这种通知模式不仅限于工具,还扩展到了其他 MCP 原语,实现了客户端和服务端之间全面的实时同步。

当 AI 应用收到有关工具更改的通知时,它会立即刷新其工具注册表并更新 LLM 的可用能力。这确保了正在进行的对话始终可以访问最新的工具集,并且 LLM 可以动态适应新功能。

1
2
3
4
5
6
# 伪代码
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()