惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Darknet – Hacking Tools, Hacker News & Cyber Security
NISL@THU
NISL@THU
S
Securelist
O
OpenAI News
S
Security Affairs
Cyberwarzone
Cyberwarzone
T
Threatpost
Simon Willison's Weblog
Simon Willison's Weblog
The Last Watchdog
The Last Watchdog
L
LINUX DO - 最新话题
C
Cisco Blogs
PCI Perspectives
PCI Perspectives
SecWiki News
SecWiki News
S
Secure Thoughts
GbyAI
GbyAI
I
Intezer
AWS News Blog
AWS News Blog
F
Fortinet All Blogs
I
InfoQ
阮一峰的网络日志
阮一峰的网络日志
Google Online Security Blog
Google Online Security Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
A
About on SuperTechFans
S
Schneier on Security
P
Proofpoint News Feed
雷峰网
雷峰网
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
小众软件
小众软件
H
Heimdal Security Blog
Microsoft Security Blog
Microsoft Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs
V
V2EX
L
Lohrmann on Cybersecurity
Security Latest
Security Latest
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
H
Hacker News: Front Page
Cisco Talos Blog
Cisco Talos Blog
Webroot Blog
Webroot Blog
T
Tenable Blog
MyScale Blog
MyScale Blog
博客园 - 司徒正美
S
SegmentFault 最新的问题
Y
Y Combinator Blog
腾讯CDC
Hacker News: Ask HN
Hacker News: Ask HN
M
MIT News - Artificial intelligence
G
GRAHAM CLULEY

XINDOO

关于内卷,几个值得深想的洞察 当创作被 Skill 化:我用 AI 写了一部 320 章的长篇网文 AI第一剑,先斩程序员 AI 也会偷懒?这个 PUA 工具专治“摸鱼式编程” Gstack 深度解析:YC CEO 开源的 AI 工程团队 GitHub Trending霸榜!深度解析AI Coding辅助神器 Superpowers 深度探讨:从 OpenClaw 爆火,看 AI Agent 的真相与程序员的未来 我复刻了NotebookLM的信息图功能 我用AI写了部小说,这里是整个过程 [翻译]我在谷歌14年学到的21堂课 2万字吊打40万字:为什么我的“牛马Agent”比“数字分身”更聪明? 最近AI领域爆火的 Agent Skills 是什么? 从计算机科学的视角来看拖延症 一周改6个库后我悟了:AI时代,程序员正在退化成“甲方” 使用n8n做一个自动同步更新的Github项目问答机器人 Agent设计模式——第 20 章:优先级排序 Agent设计模式——第 1 章:提示词链 Agent设计模式——附录 D - 使用 AgentSpace 构建 Agent Agent设计模式——第 19 章:评估和监控 Agent设计模式——第 10 章:模型上下文协议 (MCP) Agent设计模式——第 21 章:探索和发现 Agent设计模式——智能体设计模式 Agent设计模式——第 11 章:目标设定和监控 Agent设计模式——第 9 章:学习和适应 Agent设计模式——第 16 章:资源感知优化 Agent设计模式——附录 G - 编码 Agent Agent设计模式——第 13 章:人机协同 Agent设计模式——第 17 章:推理技术 Agent设计模式——附录 F - 深入剖析:Agent 推理引擎的内部运作机制 Agent设计模式——章节目录 Agent设计模式—— Agent设计模式——第 7 章:多 Agent 协作 Agent设计模式——附录 E - 命令行界面中的 AI Agent Agent设计模式——附录 C - Agentic 框架快速概览 Agent设计模式——第 3 章:并行化 Agent设计模式——**常见问题解答:Agentic 设计模式** Agent设计模式——第 14 章:知识检索(RAG) Agent设计模式——第 18 章:Guardrails/安全模式 Agent设计模式——第 15 章:Agent 间通信(A2A) Agent设计模式——第 8 章:内存管理 Agent设计模式——第 12 章:异常处理和恢复 Agent设计模式——第 4 章:反思 Agent设计模式——附录 B - AI Agentic 交互:从图形界面到现实世界环境 Agent设计模式——第 5 章:工具使用(函数调用) Agent设计模式——结论 Agent设计模式——第 6 章:规划 Agent设计模式——第 2 章:路由 从经验主义到贝叶斯理论:如何排查线上问题 我用AI为自己造了一把安全的开发者“瑞士军刀”” 打通Dify与AI工具生态:将Workflow转为MCP工具的实践 一文了解知识库背后的技术RAG AI应用的五个级别:从入门到专家的进阶之路 一文入门AI圈最近爆火的MCP协议 HTTP/3:性能改进(第 2 部分) deepseek-r1祛魅:从过度热捧到理性认知⁠ 为什么AI智能体需要工作流 如何用GPT-4o解读视频 json命令行处理神器jq介绍 OpenAI的结构化浅析 从大模型的原理到提示词优化 从经济学原理看团队分工合作 [翻译]关于人工智能的30个思考 从马斯洛需求层次理论谈职场激励 知识与智慧 如何使用大语言模型绘制专业图表 两个开源项目打造自己的大模型聚合平台 我让gpt4o给我推荐了一千多次书 得到了这些数据 用Langchain创建一个可以总结网页内容的Agent 推荐一个好用的命令行工具ShellGPT 关于ffmpeg height not divisible by 2的错误 使用Certbot解决https证书自动更新的问题 Spring Cache简明教程 软件开发中的抓大放小vs极致细节思维 OpenAI Assistants-API简明教程 OpenAI的多函数调用(Multiple Function Calling)简介 如何使用ffmpeg制作透明背景的视频 spring-kafka中ContainerProperties.AckMode详解 如何在地图上寻找最密集点的位置? IO密集型服务提升性能的三种方法 职场中的基本归因错误和自利归因 使用javax.validation.constraints校验参数合法性 Java Optional:让你的代码更优雅 ChatGPT函数调用初体验:让ChatGPT具备抓取网页文本的能力 如何使用ChatGPT提升自己的“码”力? 使用ffmpeg拼接两张图片 ThreadPoolExecutor——高效处理并发任务的必备良器 从CPU的视角看 多线程代码为什么那么难写! 使用ffmpeg缩小视频体积的几种方式 Linux parallel 命令使用手册 为什么说过早优化是万恶之源? Linux xargs命令介绍 深入理解Spring的事件通知机制 Java高并发之CyclicBarrier简介 聊一聊过度设计! 详解Redisson分布式限流的实现原理 Java中使用HashMap时指定初始化容量性能一定会更好吗? 如何用ffmpeg截取视频片段&截取时间不准确的坑 XINDOO的2022年年终总结 使用ffmpeg将视频转成HLS(m3u8)格式 谷歌Guava LoadingCache介绍
从LLM和MCP的协同过程看如何做优化
xindoo · 2025-07-06 · via XINDOO


  MCP 介绍性的文章相信大家都已经看到了很多了,相信大家或多或少都有所了解了。今天我们换一个视角,从大语言模型(后统称 LLM)和 MCP 协同的整个过程来了解 MCP 的的原理,进一步了解如何更好的去配置MCP,以及提升基于 MCP 实现的 Agent 效果。

Function Calling

  其实提到 MCP 的工作过程,就不得不先说下 LLM Function Calling过程。OpenAI 在 2023 年 6 月 13 日首推了一项 LLM 的扩展能力,叫做 function calling(函数调用),让 LLM 具备了与外部系统交互的能力。但这个名称容易让人对其工作原理产生误解:LLM 通常作为外部系统,它如何调用我们内部系统的函数(同工具,后文中工具和函数会混用,实际代指同一个东西)呢?实际上,函数调用需要客户端和 LLM 端协同完成,这里的客户端可能是类似 cherry-studio 之类的聊天应用,也可能是一段本地代码。下面,我们通过时序图来了解其具体工作过程。

  其实上图已经很清晰了,这里不再赘述。实际上在一些复杂场景,LLM 可能会进行多轮函数调用,也就是说会重复上图中 4-10 的过程,直到获取到所有必要信息。举个简单例子,假设我不是查询今天北京天气,而是查询 10 天前北京天气,这个时候 LLM 需要先调用时间工具获取 10 天前的日期,然后再用日期+城市去获取对应的天气数据,最后根据获取到的天气信息拼接成完整的答案回复用户。

  大家可能会好奇,LLM 是如何感知到有哪些工具可用以及如何使用它们的?其实很简单,这些信息都是在上图中第2步传递给模型的。以 OpenAI 的调用方式为例,其 SDK 中提供了一个参数 tools,我们需要用特定的 JSON 格式来描述工具及其具体参数,然后传递给模型,在上图中第2步的具体调用如下:

from openai import OpenAI
import json

client = OpenAI()

tools = [{
  "name": "get_weather",
  "description": "获取给定地点的当前天气情况",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "要获取天气信息的城市,例如:旧金山、东京、北京"
      },
      "unit": {
        "type": "string",
        "enum": ["摄氏度", "华氏度"],
        "description": "温度单位,可选值为 '摄氏度' 或 '华氏度'。",
        "default": "摄氏度"
      }
    },
    "required": ["location"]
  }
}]

input_messages = [
     {"role":"system", "content":"你是一名人工智能助手"},
     {"role": "user", "content": "今天北京天气怎么样?"}
]

response = client.responses.create(
    model="gpt-4.1",
    input=input_messages,
    tools=tools
)

从Function Calling到MCP

  说完了 Function Calling,来说说 MCP 的执行流程。直接说结论,MCP 的执行流程本质上就是Function Calling。那为什么时隔1年多,MCP 突然爆火?我总结为 MCP 的诞生,解决了 Function Calling中 Functions(函数|工具)的热插拔问题。像上图中的步骤2里的函数描述,以及第5步中执行函数调用的过程,之前必须要硬编码(Hard Code)在客户端侧的代码中。这就意味着每次变更相关函数都需要更新代码,必然会极大拖慢 LLM 通过 Function Calling 与已有系统的集成速度。这就是过去两年多,各种智能体虽然发展迅速,但仍与已有系统存在割裂的根本原因。

  MCP 是如何解决这个问题的:计算机科学中有个非常出名的梗——计算机科学中没有任何问题不能通过增加一个抽象层来解决,如果有,那就再加一层。显然,MCP 就是被加在 LLM 和现有系统 API 之间的一层。MCP 协议实现了统一的工具获取以及工具执行,这就意味着通过 MCP 协议,你可以动态地将各种工具集成到现有系统的 LLM 应用中,不再需要为每一个工具单独硬编码函数调用。假设原有 M 个应用场景和 N 个工具,原来需要 M×N 次的开发迭代,基于 MCP 就可以变成 M+N 次开发了,显著提升了 LLM + MCP 协同调用工具的效率。

  下面我们通过一张时序图来进一步了解 MCP 的工作流程,从中我们可以看到 MCP 与传统 Function Calling 的区别,以及 MCP 如何成为工具与 LLM 之间的桥梁。

在这里插入图片描述

  上图中,其实就是你创建的 Agent 在和你交互时的实际执行流程。如果你把 MCP 执行流程和 Function Calling 执行流程放在一起比较,你就会发现二者大体上没有太大差异。只是在工具的获取和执行上,从原来的硬编码变成了调用 MCP Server。我们来具体看下这两个步骤,这里我用 MCP 官方给的示例以及我自己创建的一个 MCP Server 为例,看下具体的细节。

MCP 初始化&工具获取:

  在 MCP 协议中,所有的 MCP Server 都需要实现初始化方法 initialize(),初始化后,Client 需要调用 MCP Server 的 list_tools()方法获取服务器上所有可用的工具列表。工具描述包含了工具的名称、参数规范、调用方式等信息,这些信息会在随后传递给 LLM,使其了解有哪些工具可用以及如何正确调用它们。这一步骤相当于 Function Calling 中的工具描述传递,但更加灵活,因为工具可以动态注册和更新。 这里我们看下我自己基于 n8n 实现的一个时间相关的 MCP。

这里我们通过 mcp-sdk 初始化后,直接调用 list_tools() 就可以获取到工具信息,实际工具描述如下:

[
  {
    "name": "addTimeCurrent",
    "description": "获取当前时间指定时间后的时间值",
    "inputSchema": {
      "type": "object",
      "properties": {
        "Units": {
          "type": "string",
          "description": "时间单位,可选值有second、minute、hour、day、week、month、year"
        },
        "Duration": {
          "type": "number",
          "description": "需要增加的时间"
        }
      },
      "required": [
        "Units",
        "Duration"
      ],
      "additionalProperties": true,
      "$schema": "http://json-schema.org/draft-07/schema#"
    },
    "annotations": null
  },
  {
    "name": "addTime",
    "description": "计算给定时间增加指定时间后的结果",
    "inputSchema": {
      "type": "object",
      "properties": {
        "Date_to_Add_To": {
          "type": "string"
        },
        "Units": {
          "type": "string",
          "description": "时间单位,可选值有second、minute、hour、day、week、month、year"
        },
        "Duration": {
          "type": "number",
          "description": "需要增加的时间"
        }
      },
      "required": [
        "Date_to_Add_To",
        "Units",
        "Duration"
      ],
      "additionalProperties": true,
      "$schema": "http://json-schema.org/draft-07/schema#"
    },
    "annotations": null
  },

  {
    "name": "currentTime",
    "description": "获取当前时间",
    "inputSchema": {
      "type": "object",
      "properties": {},
      "additionalProperties": true,
      "$schema": "http://json-schema.org/draft-07/schema#"
    },
    "annotations": null
  },
  #....其他工具 
]

可以看到每个工具都有三个主要字段:

  1. name:工具的唯一标识符,LLM 通过这个名称来引用和调用特定的工具。例如,"currentTime"表示获取当前时间的工具。
  2. description:工具的功能描述,帮助 LLM 理解这个工具的用途和适用场景。这个描述应当简洁明了,让 LLM 能够准确判断何时使用该工具。
  3. inputSchema:定义了工具输入参数的结构和类型,通常使用 JSON Schema 格式。这包括:
    • 参数名称和类型(如 string、number、boolean 等)
    • 参数描述,帮助 LLM 理解每个参数的含义
    • 必填参数列表(required 字段)
    • 参数约束或可选值范围(如时间单位只能是 second、minute 等)

这三个字段共同构成了工具的完整描述,使 LLM 能够理解工具的功能、适用场景以及如何正确地调用它。

LLM的分析与决策:

  如果你对比下 MCP 协议中 tools的描述和 Function Calling 中的描述,就会发现其二者在本质上是非常相似的。 其本质作用就是为了让 LLM 理解这个工具是做什么的,以及如何使用。 在实际使用中,其实只需要将上文中的 json 传递给 LLM,LLM 就可以决策是否调用指定工具了,这里我们可以看下 MCP 官方提供的示例。

  MCP 官方示例中 工具的传入非常简单粗暴直接放在系统提示词中了。具体的提示词如下,其中tools_description就是通过 list_tools()获取到的工具描述。

You are a helpful assistant with access to these tools:
                f"{tools_description}
Choose the appropriate tool based on the user's question. 
If no tool is needed, reply directly.
IMPORTANT: When you need to use a tool, you must ONLY respond with 
the exact JSON object format below, nothing else:
{
                '    "tool": "tool-name",'
                '    "arguments": {'
                '        "argument-name": "value"'
    }
}
After receiving a tool's response:
1. Transform the raw data into a natural, conversational response
2. Keep responses concise but informative
3. Focus on the most relevant information
4. Use appropriate context from the user's question
5. Avoid simply repeating the raw data
Please use only the tools that are explicitly defined above.

工具调用执行:

  当 LLM 决定调用某个工具时,他会同时把工具名(tool-name)和参数(arguments),Client 会通过 MCP 协议的 execute_tool() 方法向对应 Server 发送调用请求。MCP Server 负责将请求转换为实际的 API 调用,并将执行结果返回给 Client。 在官方示例中,可能是 MCP 为了兼容不同模型, 并没有直接使用像 OpenAI 的 Function Calling API,而是纯通过 prompt 实现,完整流程的代码如下,我其中涵盖了上图中所有的步骤:

class ChatSession:
    """Orchestrates the interaction between user, LLM, and tools."""

    def __init__(self, servers: list[Server], llm_client: LLMClient) -> None:
        self.servers: list[Server] = servers
        self.llm_client: LLMClient = llm_client

    async def cleanup_servers(self) -> None:
        """Clean up all servers properly."""
        for server in reversed(self.servers):
            try:
                await server.cleanup()
            except Exception as e:
                logging.warning(f"Warning during final cleanup: {e}")

    async def process_llm_response(self, llm_response: str) -> str:
        """Process the LLM response and execute tools if needed.

        Args:
            llm_response: The response from the LLM.

        Returns:
            The result of tool execution or the original response.
        """
        try:
            tool_call = json.loads(llm_response)
            if "tool" in tool_call and "arguments" in tool_call:
                logging.info(f"Executing tool: {tool_call['tool']}")
                logging.info(f"With arguments: {tool_call['arguments']}")

                for server in self.servers:
                    tools = await server.list_tools()
                    if any(tool.name == tool_call["tool"] for tool in tools):
                        try:
                            logging.info(server)
                            result = await server.execute_tool(
                                tool_call["tool"], tool_call["arguments"]
                            )

                            if isinstance(result, dict) and "progress" in result:
                                progress = result["progress"]
                                total = result["total"]
                                percentage = (progress / total) * 100
                                logging.info(
                                    f"Progress: {progress}/{total} ({percentage:.1f}%)"
                                )

                            return f"Tool execution result: {result}"
                        except Exception as e:
                            error_msg = f"Error executing tool: {str(e)}"
                            logging.error(error_msg)
                            return error_msg

                return f"No server found with tool: {tool_call['tool']}"
            return llm_response
        except json.JSONDecodeError:
            return llm_response

    async def start(self) -> None:
        """Main chat session handler."""
        try:
            for server in self.servers:
                try:
                    await server.initialize()
                except Exception as e:
                    logging.error(f"Failed to initialize server: {e}")
                    await self.cleanup_servers()
                    return

            all_tools = []
            for server in self.servers:
                tools = await server.list_tools()
                all_tools.extend(tools)

            tools_description = "\n".join([tool.format_for_llm() for tool in all_tools])
            print(tools_description)

            system_message = (
                "You are a helpful assistant with access to these tools:\n\n"
                f"{tools_description}\n"
                "Choose the appropriate tool based on the user's question. "
                "If no tool is needed, reply directly.\n\n"
                "IMPORTANT: When you need to use a tool, you must ONLY respond with "
                "the exact JSON object format below, nothing else:\n"
                "{\n"
                '    "tool": "tool-name",\n'
                '    "arguments": {\n'
                '        "argument-name": "value"\n'
                "    }\n"
                "}\n\n"
                "After receiving a tool's response:\n"
                "1. Transform the raw data into a natural, conversational response\n"
                "2. Keep responses concise but informative\n"
                "3. Focus on the most relevant information\n"
                "4. Use appropriate context from the user's question\n"
                "5. Avoid simply repeating the raw data\n\n"
                "Please use only the tools that are explicitly defined above."
            )

            messages = [{"role": "system", "content": system_message}]

            while True:
                try:
                    user_input = input("You: ").strip().lower()
                    if user_input in ["quit", "exit"]:
                        logging.info("\nExiting...")
                        break

                    messages.append({"role": "user", "content": user_input})

                    llm_response = self.llm_client.get_response(messages)
                    logging.info("\nAssistant: %s", llm_response)

                    result = await self.process_llm_response(llm_response)

                    if result != llm_response:
                        messages.append({"role": "assistant", "content": llm_response})
                        messages.append({"role": "system", "content": result})

                        final_response = self.llm_client.get_response(messages)
                        logging.info("\nFinal response: %s", final_response)
                        messages.append(
                            {"role": "assistant", "content": final_response}
                        )
                    else:
                        messages.append({"role": "assistant", "content": llm_response})

                except KeyboardInterrupt:
                    logging.info("\nExiting...")
                    break

        finally:
            await self.cleanup_servers()

如何优化?

  通过上文了解原理后,如何提升 MCP 的应用效果,我们可以从几个方面来优化:

工具描述优化

  工具描述是 LLM 理解工具功能的关键,优化描述可以大幅提升工具调用的准确性和效率。良好的工具描述能让 LLM 更精确地理解何时应该使用该工具,以及如何正确地传递参数。

  1. 精确的功能描述:确保每个工具的 description 字段简洁明了,准确表达工具的功能。避免模糊或过于技术化的描述,使 LLM 能够准确判断何时应该使用该工具。

  2. 参数命名与描述:参数名称应当具有自解释性,同时为每个参数提供清晰的描述。例如,对于时间相关参数,明确说明格式要求(如 "YYYY-MM-DD")。

  3. 添加示例:在工具描述中添加使用示例,帮助 LLM 理解如何正确调用工具。这点在复杂工具中尤为重要。

    合适的工具组合

      在构建基于 MCP 的Agent时,工具组合的选择至关重要。应根据具体任务需求,精心挑选互补性强的工具集:

  4. 功能互补:确保工具集能覆盖应用所需的全部功能域,避免关键能力缺失

  5. 避免冗余:减少功能重叠的工具,降低 LLM 在工具选择时的困惑

  6. 复杂度平衡:在提供足够功能的同时,避免过多工具导致的选择困难和效率降低

提示词优化

提示词是影响 LLM 工具调用行为的重要因素:

  1. 业务场景:根据你实际的业务场景,设计合适的系统提示词,比如引入更多业务背景信息、给出一些场景示例等…… 业内各种提示词技巧都可以考虑使用。
  2. 调用策略指导:指导 LLM 何时应该调用工具,何时应该直接回答,以及如何处理多步骤任务中的工具链式调用
  3. 结果处理指南:提供明确的指导,告诉 LLM 如何处理和呈现工具返回的结果,尤其是对于返回大量原始数据的工具。
  4. 示例演示:在系统提示中添加典型的工具调用示例和预期输出,帮助 LLM 更好地理解调用模式。

结语

  通过本文你可能会发现,MCP 并不是什么神秘或高科技的东西,本质上还是基于 LLM 的思考和决策能力,再加上一些工程实现来打通 LLM 与现有系统的连接。因此,我认为 MCP 的价值并不在于协议本身,而是基于该协议建立起来的工具生态。