MCP 与 Agent 框架集成:工具调用编排实战

MCP 只是定义了工具的统一接口,真正让模型学会「什么时候调、调完怎么继续」的是 Agent 框架。本文详解 MCP 与 ReAct / Function Calling 的结合方式,LangChain 与 LlamaIndex 适配层实现,工具选择置信度、多步调用状态管理以及 Claude / OpenAI 的集成实践。

1. Agent 与工具调用的结合

MCP 解决了「工具如何暴露」的问题,但 Agent 还面临另一层问题:模型如何决定调用哪个工具、按什么顺序调用、调用结果如何反馈到下一步思考。这两层合在一起,才是完整的「行动式 AI」。

一句话:MCP 是给工具装上的统一插座,Agent 框架则是那个会思考「该插哪个插座、拔下后再插哪个」的机械臂。

1.1 一个 Agent 工具调用循环

模型思考(ReAct 中的 Thought)
   ↓ 输出 tool_call
执行工具(可能经过 MCP 转发)
   ↓ 返回 tool_result
模型再思考(Observation)
   ↓ 直到给出最终答案

这个循环在实现上分为两个流派:ReAct(把思考/行动写进提示词)与 Function Calling(模型原生输出结构化调用意图)。

2. MCP 与 ReAct / Function Calling 的结合

维度ReActFunction Calling
机制提示词引导输出 Thought/Action/Action Input模型 API 原生返回 tool_calls 字段
可移植性任何模型可用依赖供应商支持
结构化弱,靠解析文本强,JSON 原生
与 MCP 关系通过提示词把 MCP 工具名注入模板把 MCP 工具列表转成 API 的 tools 参数

2.1 ReAct 提示词模板(注入 MCP 工具)

你是一个能调用外部工具执行任务的智能体。可用工具如下:

{工具清单,例如:
- search_docs(查询语句): 检索技术文档
- query_sql(查询语句): 查询业务数据库
- send_email(收件人, 内容): 发送邮件}

请严格按以下格式思考与行动:
Thought: 你当前的思考
Action: 工具名(必须来自上面清单)
Action Input: {"参数名": "参数值"}
Observation: 工具返回结果
……(可多轮)
Thought: 我已得到答案
Final Answer: 最终回复

模型按模板产出 Action 后,编排层解析文本、解析 JSON 参数、调用 MCP 工具,再把结果作为 Observation 拼回下一轮。

2.2 Function Calling 将 MCP 工具转为 API schema

from mcp import ClientSession, StdioServerParameters
import asyncio, json

async def tools_to_openai_schema(session: ClientSession):
    """把 MCP 工具列表转换为 OpenAI 的 tools 参数"""
    result = await session.list_tools()
    return [
        {
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description or "",
                "parameters": tool.inputSchema,   # JSON Schema 直接可用
            },
        }
        for tool in result.tools
    ]

一句话:MCP 的工具描述 + JSON Schema 恰好是 Function Calling 需要的全部输入——协议层顺手就把桥搭好了。

3. LangChain 适配层

LangChain 通过 MCPAdapter 把 MCP 服务器暴露成 LangChain Tool,从而复用其 Agent 编排能力。

from langchain_mcp_adapters.tools import load_mcp_tools
from langchain.agents import create_react_agent
from langchain_openai import ChatOpenAI

async def build_agent():
    # 1. 用 MCP 客户端连接服务器(stdio 传输)
    server_params = StdioServerParameters(
        command="npx",
        args=["-y", "tsx", "/path/to/mcp-server/src/index.ts"],
    )

    # 2. 加载 MCP 工具为 LangChain Tool
    tools = await load_mcp_tools(server_params)

    # 3. 组装 ReAct Agent
    model = ChatOpenAI(model="gpt-4o", temperature=0)
    prompt = hub.pull("hwchase17/react")
    agent = create_react_agent(model, tools, prompt)

    # 4. 调用
    result = await agent.ainvoke({"input": "查询北京当前天气"})
    print(result["output"])

3.1 适配层做了什么

load_mcp_tools 内部完成了三件事:

  1. 发起 initialize 握手并协商能力;
  2. 调用 tools/list 拉取工具清单,逐条转换成 LangChain 的 Tool 对象;
  3. 把每次 invoke 转成 tools/call 请求,并把 isError 映射为 LangChain 可识别的错误。
# 手动转换:理解底层映射
async def mcp_tool_to_langchain(name, mcp_tool):
    def invoke(args_str: str):
        args = json.loads(args_str)
        result = asyncio.run(session.call_tool(name, args))
        if result.isError:
            raise ToolException(result.content[0].text)
        return result.content[0].text
    return Tool(name=name, func=invoke, description=mcp_tool.description)

4. LlamaIndex 适配层

LlamaIndex 的 MCPAgent / MCPQueryEngine 把 MCP 工具包装为 FunctionTool,并能把工具结果写入它的索引体系:

from llama_index.tools.mcp import BasicMCPClient, McpToolSpec
from llama_index.core.agent import FunctionCallingAgentWorker

def build_llama_index_agent():
    # 连接 MCP 服务器
    mcp_client = BasicMCPClient(
        "npx -y tsx /path/to/mcp-server/src/index.ts"
    )
    tool_spec = McpToolSpec.from_client(mcp_client)
    tools = tool_spec.to_tool_list()

    # 构建 Function Calling Agent
    agent = FunctionCallingAgentWorker.from_tools(
        tools,
        llm=OpenAI(model="gpt-4o"),
        verbose=True,
    ).as_agent()

    return agent

一句话:LangChain 与 LlamaIndex 的适配层思路同构——把 MCP 工具「翻译」成各自框架的 Tool 对象,其余编排逻辑全部复用框架能力。

5. 工具选择的置信度

工具越多,模型选错的概率越高。工程上可以从三个维度压制误选率:

手段做法效果
描述工程在 description 写明「何时用 / 何时不用」减少语义歧义
显式拒绝描述末尾加「不要用于 X 场景」引导排除
低置信重试模型犹豫(低 logprob)时退回用户确认避免错误执行

5.1 低置信度处理

def should_confirm(tool_call, threshold=0.7) -> bool:
    """基于 logprob 判断工具选择是否可信"""
    conf = sum(arg.finish_reason == "tool_calls"
               for arg in [tool_call])  # 简化示意
    # 实际可读取 API 返回的 token logprob
    avg_prob = tool_call.get("avg_logprob", 0.0)
    return avg_prob < threshold

当置信度不足时,不直接执行,而是把「将要调用 X 工具、参数为 Y」抛给用户确认,把决策权交还给人类。

6. 多步调用的状态管理

现实任务几乎都是多步的:「先查用户订单,再根据金额算折扣,最后发邮件」。每一步都依赖上一步的输出,这就对状态管理提出要求。

6.1 状态建模

interface AgentStep {
  stepId: string;
  tool: string;
  args: Record<string, unknown>;
  result: string;         // 精简后的结果
  createdAt: number;
  tokensUsed: number;
}

class AgentMemory {
  private steps: AgentStep[] = [];

  append(step: AgentStep): void {
    this.steps.push(step);
    // 裁剪最旧步骤,防止上下文无限膨胀
    while (this.totalTokens() > MAX_STEP_TOKENS) {
      this.steps.shift();
    }
  }

  toContext(): string {
    // 只保留「结论性摘要」而非全部原始结果
    return this.steps
      .map((s) => `Step${s.stepId} [${s.tool}]: ${summarize(s.result)}`)
      .join("\n");
  }
}

6.2 把中间结果传给下一步

# 第二步的参数引用第一步的结果
step1 = await agent.call_tool("query_orders", {"user_id": 42})
order_total = extract_amount(step1.result)

step2 = await agent.call_tool(
    "compute_discount", {"amount": order_total, "tier": "gold"}
)

step3 = await agent.call_tool(
    "send_email",
    {"to": "user@example.com", "body": f"您的折扣后金额为 {step2.result}"},
)

一句话:多步调用的状态管理核心只有两条——只保留摘要、把上一步结论喂给下一步,否则历史会像雪球一样把上下文窗口滚爆。

7. Claude / OpenAI 集成实践

7.1 Claude 侧:原生工具 + MCP 配置

Claude 桌面端通过配置文件直接挂载 MCP 服务器,工具自动进入模型视野,无需手写适配层:

{
  "mcpServers": {
    "weather": {
      "command": "npx",
      "args": ["-y", "tsx", "/path/to/mcp-server/src/index.ts"]
    },
    "db": {
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

7.2 OpenAI 侧:手写 Function Calling 循环

OpenAI 的 API 需要自己管理循环:发消息 → 收到 tool_calls → 执行 → 把结果拼回 messages → 再发。

from openai import OpenAI

client = OpenAI()

def run_with_mcp_tools(messages, tools):
    while True:
        resp = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=tools,
        )
        choice = resp.choices[0]
        if not choice.message.tool_calls:
            return choice.message.content

        messages.append(choice.message)          # 带上 assistant 的 tool_calls
        for call in choice.message.tool_calls:
            # 经 MCP 客户端执行工具
            mcp_result = asyncio.run(
                session.call_tool(call.function.name,
                                  json.loads(call.function.arguments))
            )
            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": mcp_result.content[0].text,
            })
集成对象集成方式适配成本
Claude配置文件挂载 MCP,原生支持最低
OpenAI手写 Function Calling 循环中
LangChainMCPAdapter → LangChain Tool低
LlamaIndexMcpToolSpec → FunctionTool低

8. 总结

MCP 与 Agent 框架的集成,本质是「协议层的能力」与「决策层的智能」的拼装:

层面职责落点
协议层(MCP)工具统一暴露与调用工具列表、JSON Schema、结果格式
决策层(Agent)何时调、调什么、下一步做什么ReAct / Function Calling 循环
适配层两种能力的翻译LangChain / LlamaIndex Adapter
状态层多步结果传递与记忆摘要化记忆 + 参数引用

把工具接进来只是第一步,如何让模型在有限上下文里高效使用这些工具,则属于「上下文工程」的范畴。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「AI工程」更多文章

  1. MCP 资源模板与订阅:URI 模板、ListChanged 通知与上下文注入
  2. MCP 的 OAuth 鉴权与会话:动态注册、PKCE 与令牌轮换
  3. MCP 服务器测试框架:in-memory 传输、协议断言与端到端测试