MCP 到底是什么?用一次真实工具调用讲清 Host、Client 和 Server
MCP 不是模型 API,也不只是插件协议。本文通过一个真实订单查询工具,拆解 MCP Host、Client、Server、JSON-RPC、stdio 与 Streamable HTTP 的完整调用链。

如果只记一句话:
MCP 是 AI 应用连接外部数据、工具和工作流的标准协议。
它不是一个模型,不负责让模型“更聪明”;它也不是 OpenAI 兼容 API,不负责生成答案。它解决的是另一件事:让 AI 应用用统一方式发现和调用文件系统、数据库、浏览器、GitHub 或你自己的业务服务。
但“AI 的 USB-C”这个比喻仍然太抽象。本文不继续堆名词,而是运行一个真实的 get_order 工具,把 Host、Client、Server 和一次 JSON-RPC 调用逐步拆开。
先看答案:三个角色分别做什么
假设用户在桌面 AI 助手里问:
帮我查订单
NB-1001的状态。
这时至少有三个不同角色:
| 角色 | 在例子里是谁 | 负责什么 |
|---|---|---|
| Host | 桌面 AI 助手或 Coding Agent | 管理用户界面、模型、权限和多个 MCP 连接 |
| Client | Host 内部的一条 MCP 会话 | 与一个 Server 初始化、协商能力、发送请求和接收结果 |
| Server | order-tools 本地进程 | 暴露 get_order 工具并真正查询订单数据 |
一个 Host 可以连接多个 Server,但通常会为每个 Server 创建独立 Client。这里的 Client 不是最终用户,也不是整个桌面应用,而是 Host 内负责一条 MCP 连接的协议组件。

数据流可以简化为:
用户
↓
Host(应用与模型编排)
↓
Client(MCP 会话)
↓ JSON-RPC
Server(工具实现)
↓
订单数据 / 外部 API / 数据库
MCP 的两层:协议消息和传输方式
理解 MCP 时,最好把它拆成两层。
数据层
数据层定义:
- JSON-RPC 请求、响应和通知;
- 初始化与能力协商;
tools/list、tools/call等方法;- Resources、Prompts、Tools 等协议原语;
- 错误、进度和变更通知。
传输层
传输层负责把这些消息送到另一端。当前规范定义两种标准传输:
- stdio:Host 启动本地 Server 子进程,通过标准输入和标准输出通信;
- Streamable HTTP:Client 通过 HTTP POST/GET 与远程 Server 通信,服务端可选择用 SSE 流返回多条消息。
它们传递的是同一种 MCP JSON-RPC 消息。区别在进程边界、连接方式、认证和安全风险,而不是工具语义。
一次真实调用:我们实际运行了什么
为了避免把文档示例当运行结果,我用官方 Python SDK mcp 1.28.1 建了一个最小 Server,并通过 stdio 实际完成初始化、工具发现和调用。
1. Server:暴露订单查询工具
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("order-tools")
ORDERS = {
"NB-1001": {"status": "shipped", "items": 2},
"NB-1002": {"status": "processing", "items": 1},
}
@mcp.tool()
def get_order(order_id: str) -> dict:
"""Look up an order by its public order ID."""
return ORDERS.get(order_id, {"status": "not_found", "items": 0})
if __name__ == "__main__":
mcp.run(transport="stdio")
@mcp.tool() 会把函数名、描述和参数类型转换成工具定义。这里没有调用任何 LLM;Server 只是声明并执行业务能力。
2. Client:启动 Server 并调用工具
import asyncio
import sys
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(
command=sys.executable,
args=["server.py"],
)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print([tool.name for tool in tools.tools])
result = await session.call_tool(
"get_order",
{"order_id": "NB-1001"},
)
print(result)
asyncio.run(main())
SDK 隐藏了大部分协议细节,但 Wire 上实际发生的仍是下面这组 JSON-RPC 消息。
Wire 级时序:从初始化到结果

第一步:初始化连接
Client 发送:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "article-demo",
"version": "1.0.0"
}
}
}
Server 返回它接受的协议版本、能力和实现信息:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-11-25",
"capabilities": {
"prompts": { "listChanged": false },
"resources": {
"subscribe": false,
"listChanged": false
},
"tools": { "listChanged": false }
},
"serverInfo": {
"name": "order-tools",
"version": "1.28.1"
}
}
}
然后 Client 发送不需要响应的通知:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
初始化必须是 Client 与 Server 的第一次协议交互。双方在这里确认版本和能力,之后才能进入正常操作阶段。
本次运行的 SDK 协商为
2025-11-25。规范版本会演进,生产代码应让 SDK 协商,而不是把本文版本写死。本文链接的稳定规范页面为2025-06-18,角色和核心调用方法与本次演示一致。
第二步:发现工具
Client 请求:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}
真实响应中的工具定义是:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "get_order",
"description": "Look up an order by its public order ID.",
"inputSchema": {
"type": "object",
"properties": {
"order_id": { "type": "string" }
},
"required": ["order_id"]
}
}
]
}
}
这一步很重要:模型或 Host 不必提前硬编码每个工具的参数,它可以通过 Server 返回的 Schema 理解工具接受什么输入。
第三步:调用工具
Client 发送:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_order",
"arguments": {
"order_id": "NB-1001"
}
}
}
Server 真正执行 Python 函数,返回:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"status\": \"shipped\",\n \"items\": 2\n}"
}
],
"isError": false
}
}
这不是伪造的预期输出,而是本地 Server 在本文制作过程中返回的实际结果。
模型到底在调用链的哪里
前面的 Demo 完全没用模型也能运行。这说明 MCP 本身是 Client–Server 协议,不要求每次 tools/call 都由 LLM 发起。
在真实 AI Host 中,常见流程是:
- Host 通过 MCP Client 获取工具列表;
- Host 把可用工具的名称、描述和 Schema 提供给模型;
- 模型根据用户问题建议调用
get_order; - Host 根据权限策略决定是否批准;
- MCP Client 向 Server 发送
tools/call; - Host 把工具结果交回模型;
- 模型组织成用户看到的自然语言回答。
因此要分清两段协议:
Host ↔ 模型 Provider:OpenAI / Claude / Gemini 等模型 API
Host 内 MCP Client ↔ MCP Server:MCP JSON-RPC
MCP 不替代模型 API,模型 API 也不会自动把你的数据库变成 MCP Server。使用多家模型时,统一模型入口仍可交给 OpenAI 兼容 API 或网关处理;MCP 则负责工具和上下文连接。
Tools、Resources、Prompts 有什么区别
MCP Server 不只提供工具。
| 原语 | 控制方式 | 适合什么 | 例子 |
|---|---|---|---|
| Tools | 通常由模型选择调用 | 执行动作或动态计算 | 查订单、创建 Issue、运行查询 |
| Resources | 通常由应用选择读取 | 提供可寻址的上下文数据 | 文件、数据库 Schema、日志 |
| Prompts | 通常由用户显式选择 | 提供可复用工作流模板 | 代码审查、事故分析模板 |
“通常”很重要:协议描述了交互原语,但 Host 可以设计自己的确认界面、自动化策略和权限规则。
stdio 还是 Streamable HTTP

选 stdio,当 Server 跟着 Host 本地运行
适合:
- 文件系统或本地 Git 工具;
- 单用户桌面助手;
- Host 可以安全启动的受信任进程;
- 不需要独立网络认证的场景。
注意:stdout 只能输出合法 MCP 消息。调试日志应写到 stderr,否则一行普通日志就可能破坏协议流。
选 Streamable HTTP,当 Server 是远程共享服务
适合:
- 多用户或多设备访问;
- 独立部署、弹性伸缩和集中升级;
- 需要 OAuth、审计、租户隔离的企业工具;
- Server 不能由 Host 本地启动的场景。
不要把旧教程里的独立 “HTTP+SSE transport” 当当前标准。当前标准是 Streamable HTTP:Client 把 JSON-RPC 发送到一个 MCP Endpoint,Server 可以直接返回 JSON,也可以用 SSE 流式返回消息。
安全边界:工具能做事,也能做错事
MCP 统一了调用方式,但不会自动保证工具安全。
至少需要这些控制:
- 显示工具名称、参数和影响:用户知道将要发生什么;
- 高风险动作要求确认:删除文件、发送消息、付款不能静默执行;
- 最小权限:只读查询不要拿写权限,Server 不应继承不必要的环境变量;
- 输入验证:即使有 JSON Schema,也要做路径、ID、额度和租户校验;
- 输出视为不可信内容:网页和数据库文本可能包含 Prompt Injection;
- 日志与审计:记录谁调用了哪个工具,但不要泄露 API Key 和敏感返回;
- 远程 Server 做来源和认证校验:Streamable HTTP Server 应验证
Origin,本地部署应尽可能绑定 localhost,并使用正确认证; - 超时和取消:Server 卡住时,Client 不能无限等待。
官方 Tools 规范特别建议保留 Human in the Loop,让用户有机会拒绝工具调用。后续我们会单独通过威胁模型拆解 MCP Server 安全,而不是把所有风险塞进一篇入门文。
常见误解
“装了 MCP,模型就知道我的数据”
不是。Server 要先暴露 Resource 或 Tool,Host 要连接它,并决定哪些信息可以进入模型上下文。
“一个 MCP Client 会同时连接所有 Server”
Host 可以管理多个 Server,但协议架构通常是一条 Client 会话对应一个 Server。这样能力协商、状态和权限边界更清楚。
“Tools 就是普通 Function Calling 的新名字”
两者有关,但层次不同。模型 Provider 的 Function Calling 描述模型如何请求函数;MCP 描述 Host 如何发现和调用外部 Server 的工具。Host 经常把 MCP Tool 转换成模型可理解的 Tool Schema,再把模型选择映射回 tools/call。
“MCP Server 必须调用 LLM”
不需要。本文的订单 Server 没有任何模型依赖。Server 可以只是数据库适配器、浏览器控制器或业务 API 包装层。
“远程 MCP 就是把 stdio 暴露到公网”
不是。应使用 Streamable HTTP、认证、授权、Origin 校验、会话管理和安全部署,而不是转发裸进程管道。
什么时候值得做 MCP Server
适合:
- 同一工具需要接入多个支持 MCP 的 Host;
- 希望工具 Schema、错误和权限边界统一;
- 外部系统变化频繁,不想为每个 Agent 重写适配;
- 需要把工具运行与模型 Provider 解耦。
不一定适合:
- 只有一个应用、一个简单内部函数;
- 操作风险高,却没有审批和审计能力;
- 只是想统一多家模型 API;
- 团队还没有清楚定义工具权限和租户边界。
如果问题只是把 OpenAI、Claude 和 Gemini 统一到一个模型入口,优先处理 三家 API 格式差异 和网关适配,不要把 MCP 当成万能中间层。
总结
一次 MCP 工具调用并不神秘:
- Host 为 Server 建立 Client 会话;
- Client 用
initialize协商版本和能力; - Client 用
tools/list发现工具与输入 Schema; - Host 或模型选择工具,并经过权限决策;
- Client 用
tools/call发起调用; - Server 执行业务逻辑并返回内容或结构化结果;
- Host 决定如何展示或继续交给模型处理。
真正重要的不是记住每个 JSON 字段,而是分清边界:模型负责推理,Host 负责控制,Client 负责协议会话,Server 负责接入真实世界。
如果你的 Agent 同时要调用多家模型,Nbility 可以统一模型 API、用量和渠道;MCP Server 仍然由 Host 按工具权限独立连接。两者是互补关系,而不是替代关系。


