MCP核心概念解析
什么是MCP:让大模型拥有“手和脚”
大模型本身只有对话和决策能力,没有执行调用工具,获取资源的能力。因此定义一套让大模型调用外部能力的通用协议很重要。
MCP(Model Context Protocol)是一套让大模型“安全、可控、可扩展”地调用外部能力的通用协议。
它统一了一件事:大模型只负责“决策”,真正的动作由你实现的工具去执行。
- 你可以把 MCP 理解为“给大模型接插件”的标准化方式:HTTP 接口、数据库查询、浏览器调试、内部文档检索,都可以被包装成一个个工具。
- 好处:规范的参数校验(JSON Schema)、一致的权限边界、可观测的调用链、更容易在 IDE/Agent/服务端之间迁移与复用。
MCP 与 tool_calls 的深层差异
在MCP早期,大模型实现外部能力调用,需要通过function calling和tool_calls两种方式。
function calling是早期的调用方式,现在已经不推荐使用。
tool_calls则是他的替代品,允许在上下文中发送一个Tools列表,告诉大模型,我有这些工具,这些工具应该在什么时候被调用,我需要哪些入参,返回的结果应该是怎么样的。
示意:
const tools = [{type: 'function',function: {name: 'get_weather',description:'获取指定城市的实时天气信息,包括温度、天气状况、湿度、风速等。通过调用高德天气 API 获取真实数据。',parameters: {type: 'object',properties: {location: {type: 'string',description: '地理配置信息,例如 "北京"、"上海"',},unit: {type: 'string',enum: ['celsius', 'fahrenheit'],description: '温度单位,默认为 "celsius"',},},required: ['location'],},},},
];
模型返回:
{"choices": [{"finish_reason": "tool_calls","index": 0,"message": {"content": null,"role": "assistant","tool_calls": [{"id": "call_abc123","function": {"arguments": "{\"location\":\"北京\",\"unit\":\"celsius\"}","name": "get_weather"},"type": "function"}]}}],"created": 1694268198,"id": "chatcmpl-...","model": "gpt-4-...","object": "chat.completion","system_fingerprint": "fp_..."
}
两者差异
为了彻底理解MCP和tool_calls的区别,我们用两张图来对比处理同一个用户请求(“北京今天天气怎么样?”)时的根本差异。
模式一:直接 tool_calls 调用

图解:您可以看到,为了完成这个简单任务,客户端和LLM之间进行了两次完整的来回交互。第一次是为了“决策”,第二次是为了“总结”。
模式二:MCP 调用

图解:从客户端的视角看,整个过程只有一次网络往返。它发出请求,然后等待最终结果。所有与 LLM 的多步交互、工具调用、结果回传都在服务端内部由 MCP Agent 完成。这才是 MCP 模式带来效率提升的关键。MCP通过自身的代码逻辑,“砍掉”了第一次用于决策的LLM调用,极大地优化了流程。
|
对比维度 |
模式一:直接 |
模式二:MCP调用 |
|
决策主导者 |
LLM (凡事问LLM) |
代码/MCP (小事自己定) |
|
LLM交互次数 |
两次 (决策 + 总结) |
一次 (仅总结) |
|
核心优势 |
灵活性 (能处理模糊任务) |
高效率 (快速处理明确任务) |
一个设计精良的MCP,其真正的优势在于它是一个能够智能选择模式的调度中心:
- 遇到它能理解的简单任务,就采用模式二,追求极致效率。
- 遇到它看不懂的复杂任务,就自动切换到模式一,发挥LLM的灵活性。
通过这种方式,MCP在效率和灵活性之间取得了完美的平衡,为用户提供最佳的交互体验。
MCP的三种类型
|
MCP |
特点 |
适用 |
局限 |
|
Stdio |
通过标准输入/输出与子进程通信;零网络、延迟低、部署简单。 |
本地提供并执行所有能力。 |
如果往 stdout 打日志(会干扰协议),因此需要把日志打到 stderr 或文件。 |
|
SSE |
HTTP 单向流(Server→Client),易部署,浏览器友好。 |
简单的云端推送、只需要“结果下行”的场景。 |
天然单向,复杂双向 RPC/并发多路复用需要额外回路与状态管理。 |
|
Streamable |
支持“参数与结果的增量分片”“多通道并发”“可中断/可取消/背压控制”,把“文本流”和“工具结果流”统一起来。 |
需要边生成边调用工具、复杂 UI/长任务、多人协作与可观测的生产环境。 |
为什么用 Streamable 替代 SSE?
Streamable HTTP 相比于 SSE (Server-Sent Events) 提供了更简洁、高效且灵活的实现方式。以下是几个关键优势:
1. 实现更简洁,协议开销更低
- SSE 的复杂性:SSE 协议要求服务器严格遵循
data: ...\n\n的格式来封装消息。这意味着前端和后端都需要实现特定的解析逻辑来处理这种格式,增加了实现的复杂度。 - Streamable 的简洁性:Streamable 本质上是一个标准的 HTTP 响应。服务器通过
Transfer-Encoding: chunked持续向响应体中写入数据,客户端则像读取一个大文件一样持续接收。几乎所有的网络库都原生支持这种方式,无需额外的协议解析,代码逻辑更统一、简单。
2. 更好地利用 HTTP/2 的原生优势:多路复用 (Multiplexing)
- SSE 的限制:SSE诞生于 HTTP/1.1 时代,其设计模型是“一个 TCP 连接专门服务于一个单向数据流”。即使在 HTTP/2 环境下运行,SSE 协议本身也限制了这个连接的用途。
- Streamable 的高效:在 HTTP/2 环境下,Streamable 只是一个普通的 HTTP 请求。客户端可以在同一个 TCP 连接上发起这个流式请求,同时并行处理其他常规请求(如发送心跳、取消指令、获取元数据等)。所有通信都在一个连接上高效并发,极大地提升了网络资源利用率,这是 HTTP/2 的核心优势。
3. 更强的连接控制与可靠性
- 断线重连:对于长时间运行的流式连接,断线重连至关重要。标准的 HTTP 请求可以更方便地利用现代网络库提供的重试和恢复机制,从而实现更稳健的断线重连逻辑。而 SSE 的内置重连机制则相对固定,自定义和控制能力较弱。
如何开发一个简单的mcp
目标:做一个“内部文档检索”工具,支持 stdio,本地在 Cursor/Claude Code 中直接调用。
1)定义工具 Schema(告诉 LLM 你提供什么、需要什么)
{"name": "get_internal_doc","description": "根据关键词检索内部文档并返回摘要与链接","parameters": {"type": "object","properties": {"query": { "type": "string", "description": "检索关键词" },"limit": { "type": "number", "description": "返回条数", "default": 3 }},"required": ["query"]}
}
2)实现 handler(真正访问你的系统)
- 约定:收到
tool_calls→ 解析arguments→ 执行以下逻辑。 - 示例流程:关键词 → 命中你的索引或 webhook 缓存 → 返回标准化结果
// 伪代码
async function getInternalDoc({ query, limit = 3 }) {const items = await searchIntranetDocs(query, limit);return items.map((i) => ({title: i.title,url: i.url,summary: i.snippet,}));
}
3)选择传输方式
- 本地试用:stdio(最轻),注意 stdout 只输出协议数据,日志用 stderr。
- 上云/协作:优先 Streamable;SSE 仅用于过渡或简单单向通知。