写点什么

贝壳商机平台:MCP 原理剖析及实践

  • 2025-08-15
    北京
  • 本文字数:3475 字

    阅读完需:约 11 分钟

大小:1.41M时长:08:13
贝壳商机平台:MCP 原理剖析及实践

随着生成式 AI 技术的以惊人的速度迅猛发展,AI 已逐渐渗透到大家日常工作或生活中的各个领域。作为一名互联网程序员,除了会使用 AI 技术提效外,更是得“折腾折腾”,学习下 AI 背后的技术原理。


在去年 11 月,由 Anthropic 团队提出的 MCP(Model Context Protocol)相信大家肯定不陌生,MCP 如同 USB-C 接口般通过统一协议实现 LLM 与外部能力的高效互联。对 MCP 的概念在此就不过多介绍了,搜索引擎随便一搜就有大把 MCP 科普文章。而本文想讲点不一样的干货:


  • MCP 如何让大模型与外部系统互联的?

  • 如何实现 MCP Server?

  • MCP Gateway 实践

MCP 代码原理剖析


大语言模型(LLM)的回答通常会受限于训练数据,它们缺乏对实时数据的认知,例如让大模型查询今天天气、股票价格等是不可能的。让我们举个 OpenAI 官网的栗子,咨询一下 gpt-4o-mini:“今天北京的天气怎么样?”。此时,LLM 会因为预训练数据中没有相关答案,而无法回答这个问题。如图所示:


Tool Calls


Tool Calls 则为大模型提供了一种灵活的方式,使 LLM 能连接到外部数据和系统(本地代码、外部服务)。但 Tool calls 并不是 LLM 模型本身独立的能力,实际执行函数的环境不是在 LLM 内部,而是在外部系统中,这些外部系统可以是本地代码,也可以是独立运行的系统服务,LLM 模型可以通过自然语言输出的方式间接控制外部系统的执行,看起来就像 LLM 模型会以自己调用并执行 Function 一样。

 

以 OpenAI 官方的交互流程示例,LLM 与 Tool Calls 交互如下:



总结来说,在使用 Tool Calls 时,会产生两次大模型调用:第一次,让 LLM 决定调用哪个工具,按照协议返回工具入参。第二次,将工具执行结果放入上下文,大模型返回结果。

 

Talk is less, show code。下面将用实际代码来具象化描述执行过程:


a)首先,注册一个查天气的工具(tools)


  • 本地写好 get_weather 方法,为了方便演示效果,该方法永远返回“22 度”。

  • 将 get_weather 函数加入到的 tools 列表中(tools 是一个对象数组)。

  • 为 get_weather 函数描述它的作用(description,description 的质量好坏能直接影响 LLM 是否能正常进行工具调用),描述它的入参规范(类型、参数名、参数含义、是否必传)。



这样,一个能根据经纬度获取天气温度的工具,就注册好了。

 

b)调用 LLM 时,将 tools 作为参数传入 LLM,由 LLM 决定是否调用工具

  • 引入 openai SDK(python),将  tools 工具集作为参数传入到 completions-chat 中进行 client 初始化。此时,LLM 会根据用户问题,以及传入的 tools 工具集,来决定是否调用工具。

  • 此例中,LLM 判断需要调用 get_weather 工具来获取北京的天气结果,所以,LLM 返回的结果中,message.content 为 None,message.tool_calls 中,返回了需要被调用的工具名称入参。

  • 需要注意的是,如果 LLM 判断不需要调用工具时,则会直接在 mesaage.content 返回答案。




c) 执行 Tool 工具将工具执行结果放入上下文,并第二次调用 LLM

  • 依据 LLM 返回的工具方法名称和入参,我们即可以在本地执行函数,拿到 get_weather 执行结果。



  • 将 get_weather 的执行结果,以 tool 角色添加到 messages 中,同时发起第二次 LLM 调用。

  • 模型返回结果 message.content =>  “今天北京的天气是 22 度。”


相信大部分开发者习惯用 Langchain 等优秀框架,在 LLM 中使用工具调用,感觉就一个 SDK API 调用就完事了。诚然,Langchain 为用户做了极其友好的,开箱即用的各种 API,方便开发者们快速构建 AI 应用,但是在提供各种开发便利的同时,Langchain 屏蔽了很多底层实现细节。希望本例中实际代码,能帮助大家更进一步了解 Tool calls 背后的实际工作原理。

 

读到这里,或许有人就会有疑问了,不是讲 MCP 吗?怎么就一直在讲 Tool calls?别慌,这就来。

MCP 协议详解


首先,在你的应用第一次向一个 LLM (比如 OpenAI 的 GPT、Google 的 Gemini 或 Anthropic 的 Claude)发送请求后,一旦 LLM 决定使用外部工具来回答你的问题时,不同 LLM 厂商返回的工具调用指令 的数据结构不同的,例如:

 

  • OpenAI (GPT):会在响应的 message 对象中包含一个 tool_calls 数组,数组中每个元素格式如下:


{    "id": "call_ojmFhd5...",    "type": "funcation",    "name": "get_weather",    "arguments": "{\"lat\":39.9042, \"lon\":116.4074}"}
复制代码


  • Anthropic (Claude):会在响应的 content 数组中包含一个 tool_use 代码块


{    "role": "assistant",    "content": [      {        "type": "text",        "text": "<thinking>To answer this question, I will: 1....."      },      {        "type": "tool_use",        "id": "toolu_ojmFhd5...",        "name": "get_weather",        "input": {"lat":39.9042, "lon":116.4074}      }    ]}
复制代码


  • Gemini(Google) 格式如下:


{    "funcationCall": {        "name": "get_weather",        "args": {"lat":39.9042, "lon":116.4074}    }}
复制代码


往往开发者需要针对不同的 LLM 厂商所返回的工具调用指令做不同的数据格式适配,接入的 LLM 越多,这个适配过程越痛苦。MCP,则是一个基于 JSON-RPC 2.0 的开源协议,创建一个标准化的中间层,像一个”通用适配器“一样,旨在统一 LLM 与外部工具调用的交互方式,解决了因不同 LLM 返回的工具调用指令格式不一所带来的适配工作量问题。

 

在没有 MCP 之前,我们可能需要这么做:


def process_llm_tool_call(response: dict, provider: str):"""接收来自不同 LLM 的响应,解析其独特的工具调用格式,然后执行本地工具。这是适配工作量集中的地方。"""tool_name = Nonetool_args = {}


# ==========================================================# 这部分代码就是为适配不同模型而产生的“胶水代码”。# 每增加一个模型,就需要增加一个新的 `if/elif` 块。# ==========================================================if provider == "openai":    try:        tool_call = response["choices"][0]["message"]["tool_calls"][0]        tool_name = tool_call["function"]["name"]        # OpenAI的参数是JSON字符串,需要解析        tool_args = json.loads(tool_call["function"]["arguments"])    except (KeyError, IndexError, TypeError) as e:        print(f"Error parsing OpenAI response: {e}")        return        elif provider == "anthropic":    try:        tool_call = next(item for item in response["content"] if item["type"] == "tool_use")        tool_name = tool_call["name"]        # Anthropic的参数直接是字典
复制代码


可以看到,示例中存在大量”胶水“代码,增加开发者工作量,但有了 MCP 协议之后:


  • 所有的 MCP Server 上的工具接受如下形式的请求结构:

 

所有的 MCP Server 返回数据结构(也即 MCP client 接收到的响应):


{    "id": 2,    "jsonrpc": "2.0",    "result": {      "content": [        {           "type": "text",           "text": "22度"        }      ]         }}
复制代码


MCP Client 中将不同 LLM 返回的工具调用指令适配为 MCP 协议规定的请求体格式、发送到 MCP server 并解析结果,最终返回给用户。对开发者而言,只需要关注 MCP Server 的实现细节,MCP Client 会自动将各个大模型的输出转换为标准请求,开发者无需关心模型间的差异。

MCP 调用实践


讲完 MCP 协议,示例下如何用 langchain_mcp_adapters + langgraph 去调用 MCP Server:

 

  • 找到提供服务的 MCP Server 地址

此例中,我们集成了 Doris MCP server,该 server 背后连接了贝壳商机平台中的 Doris 数据库。

 

  • 初始化 LLM Client

Langgraph 是 Langchain 生态系统中的一部分,专门用于构建基于 LLM 的复杂工作流和 Agent 系统,示例中使用 Langgraph 中的 create_react_agent 来初始化 LLM Client。


create_react_agent 需要传入两个参数,一是大模型 model,二是外部工具列表,使用 client.get_tools 可以直接获取 MCP Server 上所有的工具列表。


该 MCP Server 背后提供 8 个 tools,功能分别如下:


  • 写模型 prompt


在 prompt 中让 LLM 执行三个数据库操作:1) 获取所有 Doris 数据库名称,2)根据库名获取库下所有表名  3)执行一段 SQL 并返回结果。


可以看到,大模型最终返回的结果中如预期的分别执行了以上的数据库操作。在传统的工作流编排中,一般是需要实现「意图识别->参数提取-> API 调用->结果解析」 的,这一定程度上会抑制模型的智慧,而使用 MCP Server 则让 LLM 充分的自主决策是否选择调用外部工具,充分发挥大模型的智慧,并且随着以后大模型能力的快速迭代和提升,准召率也会自然而然随之提升。

 

本例中使用了 OpenAI 的模型,如想更换为 Google 的 gemini 模型或者 Anthropic claude 模型,直接替换 model 变量即可,无需更多代码适配的开发量。

2025-08-15 17:248
用户头像
李冬梅 加V:busulishang4668

发布了 1129 篇内容, 共 749.5 次阅读, 收获喜欢 1275 次。

关注

评论

发布
暂无评论

管理规划篇

姜戈

团队管理 团队组织

联邦学习与推荐系统

博文视点Broadview

人工智能 大数据 学习 推荐系统

Vol.1 Java初探,新手必看!

pyfn2030

编程 新手指南

数据与广告系列三:合约广告与与衍生的第三方广告数据监控

黄崇远@数据虫巢

数据挖掘 互联网 广告 移动互联网

假如孔乙己是程序员

顿晓

学习 程序员 孔乙己

你真的会用Mac中的Finder吗

Winann

macos 效率 App Mac

突破困局

Neco.W

感悟 工作 创业心态

提升输入效率第一步——切换双拼

dongh11

效率工具 提升效率 生产力 分享 有趣

Android原生人脸识别Camera2+FaceDetector 快速实现人脸跟踪

sar

你的团队是干什么的?

姜戈

团队管理 团队职能

一致性算法 Raft 简述

架构精进之路

raft 一致性算法

点击劫持:无X-Frame-Options头信息(修复)

唯爱

Spring Security 两种资源放行策略,千万别用错了!

江南一点雨

Java spring springboot springsecurity

源码分析 | Mybatis接口没有实现类为什么可以执行增删改查

小傅哥

Java 源码分析 小傅哥 mybatis 编程思维

redis过期策略和内存淘汰机制

wjchenge

揭秘神经拟态计算:缘何成为AI界新宠?

最新动态

ARTS week 2

锈蠢刀

使用<input>标签实现六个格子验证码输入框

码字与律动

Java vue.js 大前端

宕机原因千千万,被雷劈了最无奈

田晓旭

码农远程办公指北

大伟

python实现·十大排序算法之计数排序(Counting Sort)

南风以南

Python 排序算法 计数排序

多线程与线程安全(实例讲解)

YoungZY

Java 多线程 线程安全

栀子花,我们应该像你一样静静绽放

小天同学

个人感想 感悟 日常思考

好的软件工程原则

pydata

软件开发生产率改进之我见(二)

清水

软件工程 软件开发 技术管理

你为什么“啃不动”你手中的技术书?

图灵社区

Java Python 算法 HTTP R语言

健身一周年:持续锻炼带来无法想象的改变

小鲸数据

学习 职业 专注 健身

100天从 Python 小白到大神最良心的学习资源!

JackTian

Python GitHub 学习 Python-100-Days Python-Core-50-Courses

你的团队想做出什么成果?

姜戈

团队管理

实现元素等高: Flexbox vs. Grid

寇云

CSS css3

终于,我也到了和Eclipse说再见的时候,难说再见

程序员小跃

Java eclipse IDEA

贝壳商机平台:MCP 原理剖析及实践_生成式 AI_贝壳基础业务技术团队_InfoQ精选文章