前言
随着大语言模型(LLM)的快速发展,如何让 AI 能够有效地与外部世界交互,已成为 AI 应用开发的核心课题。Anthropic 推出的 MCP(Model Context Protocol)、智能代理(Agent)和大模型应用三者的结合,形成了一套完整的 AI 系统架构。
接下来,我们深入解读这三个核心概念及其相互关系。
一、3个核心概念的定义
1.1 大模型应用(AI Application)
大模型应用是整个系统的最外层容器。它包括:
-
应用程序框架和生命周期管理
-
用户交互界面(CLI、Web、API等)
-
系统配置和资源管理
-
外部集成(数据库、监控等)
大模型应用
├─ 启动应用
├─ 管理配置
├─ 处理用户输入
├─ 返回处理结果
└─ 关闭应用
1.2 Agent(智能代理)
Agent 是大模型应用的大脑和执行引擎。它的职责是:
- 理解用户意图(通过大模型)
- 规划执行步骤
- 决定调用什么工具
- 处理工具执行结果
- 持续优化和迭代
Agent 的核心价值在于将大模型的推理能力与外部工具执行能力结合。
1.3 MCP(Model Context Protocol)
MCP 是一个开放的通信协议规范。它定义了:
- 工具的统一调用接口
- 消息的标准格式(JSON-RPC 2.0)
- 服务的发现和注册机制
- 错误处理规范
MCP 的核心价值在于解耦工具调用的复杂性,实现工具即插即用。
二、三者的包含关系
┌──────────────────────────────────────────────────┐
│ 大模型应用 │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ Agent │ │
│ │ │ │
│ │ ├─ 初始化 MCP (建立连接、获取工具) │ │
│ │ ├─ 与大模型交互 (发送提示词、接收响应) │ │
│ │ ├─ 解析大模型输出 (识别工具调用) │ │
│ │ ├─ 通过 MCP 调用工具 (执行具体任务) │ │
│ │ ├─ 处理工具结果 (反馈给大模型) │ │
│ │ └─ 循环迭代 (直到任务完成) │ │
│ │ │ │
│ │ ◄──────────────────────► │ │
│ │ MCP (工具协议) │ │
│ │ ◄──────────────────────► │ │
│ │ │ │
│ └────────────────────────────────────────────┘ │
│ │
│ 用户输入 ──► 应用处理 ──► 用户输出 │
│ │
└──────────────────────────────────────────────────┘
三、工作流程详解
3.1 初始化阶段
第一步:读取配置文件(mcp.json)
├─ 检查有哪些 MCP Server
├─ 验证配置的合法性
└─ 记录工具来源信息
第二步:连接所有 MCP Server
├─ 为每个 Server 创建 MCP Client
├─ 建立传输连接(stdio/HTTP/WebSocket)
├─ 发送 initialize 信息握手
└─ 获取 Server 能力信息
第三步:获取所有工具列表
├─ 从每个 Server 调用 listTools()
├─ 收集返回的工具定义
├─ 合并工具列表并检查冲突
└─ 标记每个工具来自哪个 Server
第四步:准备就绪
└─ Agent 获得完整的工具清单,可以开始工作
代码示例:
class AIApplication {
private agent: Agent
async initialize() {
// Agent 初始化
this.agent = new Agent("mcp.json")
await this.agent.initialize()
console.log("✓ 应用初始化完成")
console.log(`✓ 可用工具数: ${this.agent.toolCount}`)
}
}
3.2 处理请求阶段
当用户输入一个请求时,完整的处理流程如下:
用户输入: "帮我计算 (10 + 5) * 2 的结果"
│
▼
┌─────────────────────────────────────────┐
│ Agent 第一步:准备提示词 │
│ ├─ 获取当前的工具列表 │
│ ├─ 组织成 Claude 能理解的格式 │
│ └─ 加入用户的原始请求 │
└──────────────┬──────────────────────────┘
│
┌────────────▼─────────────┐
│ Claude API │
│ (处理用户请求) │
│ ├─ 理解用户意图 │
│ ├─ 规划执行步骤 │
│ └─ 决定调用哪些工具 │
│ │
│ Claude 响应: │
│ "我需要先调用 add(10,5)"│
└────────────┬─────────────┘
│
┌────────────▼──────────────────────┐
│ Agent 第二步:处理工具调用请求 │
│ ├─ 解析 Claude 的响应 │
│ ├─ 识别出要调用 "add" 工具 │
│ ├─ 找到 "add" 来自哪个 Server │
│ └─ 获取该 Server 的 MCP Client │
└────────────┬──────────────────────┘
│
┌────────────▼──────────────────────┐
│ Agent 第三步:通过 MCP 调用工具 │
│ ├─ 构建标准化的 RPC 请求 │
│ ├─ 调用: client.callTool("add", │
│ │ {a: 10, b: 5}) │
│ └─ 等待工具执行完毕 │
└────────────┬──────────────────────┘
│
┌────────────▼──────────────────────┐
│ MCP Server (实际执行工具) │
│ ├─ 接收 RPC 请求 │
│ ├─ 执行: 10 + 5 = 15 │
│ └─ 返回结果: {result: 15} │
└────────────┬──────────────────────┘
│
┌────────────▼──────────────────────┐
│ Agent 第四步:反馈给 Claude │
│ ├─ 把结果添加到对话历史 │
│ ├─ "add(10, 5) 的结果是 15" │
│ └─ 重新调用 Claude │
└────────────┬──────────────────────┘
│
┌────────────▼──────────────────────┐
│ Claude 继续推理 │
│ ├─ 看到了第一步的结果 │
│ ├─ 继续规划下一步 │
│ └─ "现在我需要调用 multiply(15,2)"│
└────────────┬──────────────────────┘
│
(重复步骤 2-4 直到 Claude 说完成)
│
┌────────────▼──────────────────────┐
│ Claude 最终响应 │
│ ├─ stop_reason = "end_turn" │
│ ├─ content = "答案是 30" │
│ └─ Agent 停止循环 │
└────────────┬──────────────────────┘
│
▼
返回用户: "答案是 30"
3.3 循环机制的关键
Agent 的循环处理是理解整个架构的关键:
async process(userInput: string): Promise<string> {
let messages = [{ role: "user", content: userInput }]
for (let iteration = 0; iteration < maxIterations; iteration++) {
// 1. 调用 Claude
const response = await claude.messages.create({
messages,
tools: this.tools // 传递所有可用工具
})
// 2. 添加 Claude 的响应到历史
messages.push({ role: "assistant", content: response.content })
// 3. 检查 Claude 是否完成
if (response.stop_reason === "end_turn") {
// Claude 完成了,返回最终答案
const textBlock = response.content.find(b => b.type === "text")
return textBlock.text
}
// 4. Claude 要求调用工具
if (response.stop_reason === "tool_use") {
const toolResults = [ ]
for (const block of response.content) {
if (block.type === "tool_use") {
// 通过 MCP 调用工具
const result = await this.callToolViaMCP(
block.name,
block.input
)
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(result)
})
}
}
// 5. 把工具结果添加到历史(关键!Claude 需要看到结果)
messages.push({
role: "user",
content: toolResults
})
// 循环回第 1 步,Claude 基于工具结果继续推理
}
}
}
关键点:
messages数组是"记忆",不断积累- 每次调用 Claude 时,都传递完整的历史
- Claude 基于之前的工具执行结果进行下一步决策
四、MCP 的泛化调用设计
4.1 为什么需要泛化?
不泛化的方式(混乱):
// 需要为每个工具写特定代码
if (toolName === "add") {
result = calculator.add(args.a, args.b)
} else if (toolName === "query") {
result = database.query(args.sql)
} else if (toolName === "analyzeCode") {
result = codeAnalyzer.analyze(args.code)
}
// ... 100+ 个 else if ...
// 问题:新增工具时要改应用代码
泛化的方式(MCP):
// 一个函数搞定所有工具
const result = await this.callToolViaMCP(toolName, args)
// 问题解决:新增工具时只需改配置
4.2 泛化的实现原理
┌──────────────────────────────────────────┐
│ 统一的工具调用接口 │
│ callTool(name: string, args: any) │
└──────────────┬───────────────────────────┘
│
┌────────┴────────┐
│ │
▼ ▼
┌────────┐ ┌──────────┐
│ Server │ │ Server │
│ A │ │ B │
│ │ │ │
│ add │ │ query │
│ sub │ │ insert │
└────────┘ └──────────┘
所有 Server 遵守相同的 MCP 规范:
├─ 都支持 listTools() 方法
├─ 都支持 callTool(name, args) 调用
├─ 都返回标准格式的结果
└─ 应用无需关心 Server 差异
4.3 MCP 规范的约束
MCP 定义了统一的消息格式:
// 工具列表请求
{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/list"
}
// 工具列表响应
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"tools": [
{
"name": "add",
"description": "Add two numbers",
"inputSchema": {
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["a", "b"]
}
}
]
}
}
// 工具调用请求
{
"jsonrpc": "2.0",
"id": "2",
"method": "tools/call",
"params": {
"name": "add",
"arguments": {"a": 5, "b": 3}
}
}
// 工具调用响应
{
"jsonrpc": "2.0",
"id": "2",
"result": {
"content": [
{
"type": "text",
"text": "5 + 3 = 8"
}
]
}
}
四、结尾
因此有了对这三者的核心概念的了解,其实对大模型应用开发也有了比较深入的认识了。
评论区欢迎讨论。