MCP圣经:从入门到精通,从精通到放弃    

一  为什么需要 MCP?

我认为 MCP 的出现是 prompt engineering(提示工程)发展的产物。更结构化的上下文信息对模型的性能提升是显著的。我们在构造 prompt (提示词)时,希望能提供一些更具体的信息(比如本地文件,数据库,一些网络实时信息等)给模型,这样模型更容易理解真实场景中的问题。

想象一下没有 MCP 之前我们会怎么做?

我们可能会人工从数据库中筛选或者使用工具检索可能需要的信息,手动的粘贴到 prompt 中。随着我们要解决的问题越来越复杂,手工把信息引入到 prompt 中会变得越来越困难。

为了克服手工 prompt 的局限性,许多 LLM 平台(如 OpenAI、Google)引入了 function call (函数调用)功能。

这一机制允许模型在需要时调用预定义的函数来获取数据或执行操作,显著提升了自动化水平。

Agent  开发的过程和痛点:

AI 的发展链路大致是这样的:  从最初只能对话的 Chatbot,辅助人类决策的 Copilot,再到能自主感知和行动的 Agent,AI 在任务中的参与度不断提升。

这要求 AI 拥有更丰富的任务上下文 (Context),并拥有执行 行动所需的工具  (Tool )

Agent   让LLM 调用工具

一个  Agent   让LLM 调用工具,步骤如下:

(1)写好函数工具

开发者需要在本地写好函数工具,例如,如果想让LLM学会查询天气,我们需要在本地写好一个查询天气的函数

(2)写好函数的介绍(这个很关键)

LLM将会函数的介绍,理解函数的作用。函数介绍包括:函数的作用、参数的类型、参数的作用等。例如,DeepSeek的函数介绍格式如下:

tools = [{

    "type": "function",

    "function": {

        "name": "get_weather",

        "description": "Get weather of an location, the user shoud supply a location first",

        "parameters": {

            "type": "object",

            "properties": {

                "location": {

                    "type": "string",

                    "description": "The city and state, e.g. San Francisco, CA",

                }

            },

            "required": ["location"]

        },

    }

}, ]

这是一个天气查询的函数,参数为location,LLM将会通过这些介绍,学会如何调用函数。

(3)解析响应,并在本地执行函数

若DeepSeek认为当前应该调用函数,则会输出参数的填写方式

我们可以通过解析message中是的tool_calls字段,将DeepSeek给出的参数填写在函数中,并在本地执行函数。


(4)LLM根据运行结果进行总结并回复

最后把函数执行的结果反馈给DeepSeek,DeepSeek再整理执行结果,给出回复。

Agent  开发 三大痛点


缺少标准化的上下文和工具集导致 Agent  开发有三大痛点:

1 开发耦合度高

工具开发者需要深入了解 Agent 的内部实现细节,并在 Agent 层编写工具代码。这导致在工具的开发与调试困难。

2 工具复用性差

因每个工具实现都耦合在 Agent 应用代码内,即使是通过 API 实现适配层在给到 LLM 的出入参上也有区别。

从编程语言角度来讲,没办法做到跨编程语言进行复用。

3 生态碎片化

工具提供方能提供的只有 OpenAPI,由于缺乏标准使得不同 Agent 生态中的工具 Tool 互不兼容。

image.png

但是 function call 也有其局限性, function call 平台依赖性强,不同 LLM 平台的 function call API 实现差异较大。


例如,OpenAI 的函数调用方式与 Google 的不兼容,开发者在切换模型时需要重写代码,增加了适配成本。除此之外,还有安全性,交互性等问题。

二、什么是 MCP?


数据与工具本身是客观存在的,只不过我们希望将数据连接到模型的这个环节可以更智能更统一。

MCP(Model Context Protocol)是Anthropic(Claude的母公司)在2024年提出的一种协议标准,中文翻译过来的意思是"模型上下文协议"。

MCP的核心作用是让AI模型能够主动调用外部工具和服务,从而 大大 扩展AI的  能力边界

MCP 起源于 2024 年 11 月 25 日 Anthropic 发布的文章:[Introducing the Model Context Protocol1]。

MCP (Model Context Protocol,模型上下文协议)定义了应用程序和 AI 模型之间交换上下文信息的方式。

MCP  使得开发者能够以一致的方式将各种数据源、工具和功能连接到 AI 模型(一个中间协议层),就像 USB-C 让不同设备能够通过相同的接口连接一样。

MCP 的目标是创建一个通用标准,使 AI 应用程序的开发和集成变得更加简单和统一。

所谓一图胜千言,我这里引用一些制作的非常精良的图片来帮助理解:

image.png

可以看出,MCP 就是以更标准的方式让 LLM Chat 使用不同工具,更简单的可视化如下图所示,这样你应该更容易理解“中间协议层”的概念了。


Anthropic 旨在实现 LLM Tool Call(LLM 工具调用)的标准。

image.png

Anthropic 基于这样的痛点设计了 MCP,充当 AI 模型的"万能转接头",让 LLM 能轻松得获取数据或者调用工具。


一句话解释就是 MCP 提供给 LLM 所需的上下文:Resources 资源、Prompts 提示词、Tools 工具。

更具体的说 MCP 的优势在于:

(1)生态:MCP 提供很多现成的插件,你的 AI 可以直接使用。

(2)统一性:不限制于特定的 AI 模型,任何支持 MCP 的模型都可以灵活切换。

(3)数据安全:你的敏感数据留在自己的电脑上,不必全部上传(因为我们可以自行设计接口确定传输哪些数据)。

image.png

MCP 和 Function Call 区别?


MCP

Function Call

定义

模型和其它设备集成的标准接口,包含:工具 Tools、资源 Resources、提示词 Prompts

将模型连接到外部数据和系统,平铺式的罗列 Tools 工具。和 MCP Tool 不同的在于:MCP Tool 的函数约定了输入输出的协议规范。

协议

JSON-RPC,支持双向通信(但目前使用不多)、可发现性、更新通知能力。

JSON-Schema,静态函数调用。

调用方式

Stdio / SSE / 同进程调用(见下文)

同进程调用 / 编程语言对应的函数

适用场景

更适合动态、复杂的交互场景

单一特定工具、静态函数执行调用

系统集成难度

简单

工程化程度

从前后端分离看 MCP


早期 Web 开发在 JSP、PHP 盛行时,前端交互页面都是耦合在后端逻辑里的,造成开发复杂度高、代码维护困难、前后端协作不便,难以适应现代 Web 应用对用户体验和性能的更高要求。

AJAX、Node.js、RESTful API 推动前后端分离,对应 MCP 也正在实现 AI 开发的“工具分层”:

  • 前后端分离


前端专注界面,后端专注 API 接口;

  • MCP 分层


让工具开发者和 Agent 开发者各司其职,工具质量和功能的迭代不需要 Agent 开发者感知。

这种分层让 AI Agent 开发者能像搭积木一样组合工具,快速构建复杂 AI 应用。

image.png

三、 用户如何使用 MCP?


对于用户来说,我们并不关心 MCP 是如何实现的,通常我们只考虑如何更简单地用上这一特性。

具体的使用方式参考官方文档:For Claude Desktop Users。

这里不再赘述,配置成功后可以在 Claude 中测试:Can you write a poem and save it to my desktop? Claude 会请求你的权限后在本地新建一个文件。

并且官方也提供了非常多现成的 MCP Servers,你只需要选择你希望接入的工具,然后接入即可。

(1)Awesome MCP Servers

(2)MCP Servers Website

(3)Official MCP Servers

比如官方介绍的 filesystem 工具,它允许 Claude 读取和写入文件,就像在本地文件系统中一样。

4. MCP 架构解构


这里首先引用官方给出的架构图。

image.png

MCP Architecture 原图网址


MCP 由三个核心组件构成:Host(主机)、Client(客户端) 和 Server(服务器)。

让我们通过一个实际场景来理解这些组件如何协同工作:  假设你正在使用 Claude Desktop (Host) 询问:"我桌面上有哪些文档?"

(1)Host:Claude Desktop 作为 Host,负责接收你的提问并与 Claude 模型交互。

(2)Client:当 Claude 模型决定需要访问你的文件系统时,Host 中内置的 MCP Client 会被激活。这个 Client 负责与适当的 MCP Server 建立连接。

(3)Server:在这个例子中,文件系统 MCP Server 会被调用。它负责执行实际的文件扫描操作,访问你的桌面目录,并返回找到的文档列表。

整个流程是这样的:你的问题 → Claude Desktop(Host) → Claude 模型 → 需要文件信息 → MCP Client 连接 → 文件系统 MCP Server → 执行操作 → 返回结果 → Claude 生成回答 → 显示在 Claude Desktop 上。

这种架构设计使得 Claude 可以在不同场景下灵活调用各种工具和数据源,而开发者只需专注于开发对应的 MCP Server,无需关心 Host 和 Client 的实现细节。

image.png

更加细致的 整体架构图,如下:

image.png

直观地说,MCP 就像 AI 应用的 USB-C 接口。

正如 USB-C 为设备连接各种配件提供了标准化方案,MCP 也将 AI 应用连接到不同数据源和工具的方式标准化了。

image.png

MCP 的核心遵循客户端-服务器(client-server)架构,Host 应用程序可以连接到多个 Server。


它包含三个主要组件:

  • Host

  • Client

  • Server


Host   代表任何提供 AI 交互环境、访问外部工具和数据源,Host  是一个  还负责 运行 MCP Client 的 AI 应用(如  Claude 桌面版 、 Cursor )。

MCP Client 在 Host 内运行,实现与 MCP Servers 的通信。

image.png

MCP Server 对外开放特定能力,并提供对数据源的访问权限,包括:

image.png

MCP Server  包括:

  • Tools:使大语言模型能够通过你的 Server 执行操作。

  • Resources:将 Server 上的数据和内容开放给大语言模型。

  • Prompts:创建可复用的提示词模板和工作流程。


要构建属于你自己的 MCP 系统,理解客户端-服务器通信机制是必不可少的。

动态服务发现与适配机制


MCP Server    和 MCP Client    之间,是 一种 动态服务发现与适配机制,也叫做 能力交换   Capability Exchange 机制

image.png

首先进行 Capability Exchange(译者注:Capability Exchange(能力交换)是一种动态服务发现与适配机制,是MCP连接建立的必经步骤,类似于“握手协议”。)


Capability Exchange 流程如下:

(1) 客户端发送初始请求,获取服务器能力信息(2) 服务器返回其能力信息详情(3) 例如当天气 API 服务器被调用时,它可以返回可用的“tools”、“prompts templates”及其他资源供客户端使用

交换完成后,客户端确认连接成功,然后继续交换消息。

Capability Exchange 流程 具体如何实现呢?  MCP协议官方提供了两种主要通信方式:stdio(标准输入输出)和 SSE (Server-Sent Events,服务器发送事件)。

客户端与服务器的通信流程


MCP协议官方提供了两种主要通信方式:stdio(标准输入输出)和 SSE (Server-Sent Events,服务器发送事件)。

这两种方式均采用全双工通信模式,通过独立的读写通道实现服务器消息的实时接收和发送。

什么是SSE Transport?


MCP(Model Context Protocol) 是一个开放协议,旨在标准化应用程序与大型语言模型(LLM)之间的上下文交互。它定义了客户端与服务器如何通过传输层交换消息。

MCP 支持两种标准传输机制:

  • stdio:通过标准输入输出流进行本地通信。

  • SSE(Server-Sent Events):通过 HTTP 协议实现服务器到客户端的实时单向数据推送,结合 HTTP POST 用于客户端到服务器的消息发送。


SSE Transport 是 MCP 中基于 HTTP 的传输方式,利用 SSE 技术实现服务器到客户端的流式消息推送,同时通过 HTTP POST 请求处理客户端到服务器的双向通信。

这种机制特别适合需要实时更新或远程通信的场景。

SSE Transport 的工作原理


SSE(Server-Sent Events)是一种基于 HTTP 协议的服务器推送技术,允许服务器向客户端发送实时更新。

MCP 的 SSE Transport 结合了 SSE 和 HTTP POST,形成了以下工作流程:

image.png

SSE Transport  交互流程


1.建立连接:

  • 客户端通过 HTTP GET 请求访问服务器的 SSE 端点(例如 /sse)。

  • 服务器响应一个 text/event-stream 类型的内容,保持连接打开。

  • 服务器发送一个初始的 endpoint 事件,包含一个唯一的 URI(例如 /messages?session_id=xxx),客户端后续通过这个 URI 发送消息。


2.服务器到客户端的消息推送:

  • 服务器通过 SSE 连接,将 JSON-RPC 格式的消息,以事件流的形式发送给客户端。

  • 客户端通过 EventSource 或类似机制监听这些事件。


3.客户端到服务器的消息发送:

  • 客户端通过 HTTP POST 请求将消息,发送到服务器提供的 URI(例如 /messages)。

  • 服务器接收并处理这些请求,返回响应或通过 SSE 推送结果。


4.连接管理:

  • SSE 连接是单向的(服务器到客户端),通常通过定期发送心跳消息(keep-alive)保持活跃。

  • 如果连接断开,客户端可以重新发起 SSE 请求重建连接。

数据格式


MCP 使用 JSON-RPC 2.0 协议封装消息,确保请求和响应的结构化处理

SSE 消息遵循 event:\ndata:\n\n 的格式。

示例


客户端 POST 请求:

POST /messages HTTP/1.1

Content-Type: application/json

{"jsonrpc": "2.0", "method": "example", "params": {"text": "Hi"}, "id": 1}

服务器推送:

五、SSE 通信流程详解

1 、建立 SSE 连接

客户端首先需要与服务端建立一个SSE长连接,该连接用于接收服务端推送的所有消息:

http://localhost:8000/sse

提示:

可以直接在浏览器中打开此链接实时查看接收到的消息流

同步使用curl、Postman等工具进行消息发送测试,便于双向操作验证

建立连接后,服务端会首先返回一个专用的消息发送端点(Endpoint),这是后续通信的关键:

event: endpoint

data: /messages/?session_id=2b3c8777119444c1a1b26bc0d0f05a0a

说明:

客户端之后发送的所有消息请求,都必须指向这个专用Endpoint  (其实就是专用的 url)。

当请求格式正确时,该Endpoint会返回一个”Accepted”状态码,但是,真正的服务器响应内容都是通过SSE端点异步推送的。

2、 通信初始化流程

客户端需要完成两个关键的初始化步骤,确保通信渠道正常建立:

a) 发送初始化请求

客户端需要先向服务端发送初始化请求,表明自身身份和期望的通信参数:

curl -X POST 'http://localhost:8000/messages/?session_id=2b3c8777119444c1a1b26bc0d0f05a0a' -H 'Content-Type: application/json' -d '{

    "jsonrpc": "2.0",

    "id": 0,

    "method": "initialize",

    "params": {

      "protocolVersion": "2024-11-05",

      "capabilities": {},

      "clientInfo": {

        "name": "mcp",

        "version": "0.1.0"

      }

    }

}'

服务端会返回其支持的功能列表,便于客户端了解可用的交互能力:

event: message

data: {

    "jsonrpc": "2.0",

    "id": 0,

    "result": {

        "protocolVersion": "2024-11-05",

        "capabilities": {

            "experimental": {},

            "prompts": {

                "listChanged": false

            },

            "resources": {

                "subscribe": false,

                "listChanged": false

            },

            "tools": {

                "listChanged": false

            }

        },

        "serverInfo": {

            "name": "weather",

            "version": "1.3.0"

        }

    }

}

b) 发送初始化完成通知

初始化协商完成后,客户端需要明确通知服务端,初始化已完成:

curl -X POST 'http://localhost:8000/messages/?session_id=2b3c8777119444c1a1b26bc0d0f05a0a' -H 'Content-Type: application/json' -d '{

    "jsonrpc": "2.0",

    "method": "notifications/initialized",

    "params": {}

}'

3、 工具调用示例

MCP协议的核心价值在于其工具调用能力,以下是一个完整的调用流程:

a) 获取可用工具列表

客户端首先应该获取服务端提供的工具列表,如果已经知道其工具参数也可跳过,直接去调用工具:

curl -X POST 'http://localhost:8000/messages/?session_id=2b3c8777119444c1a1b26bc0d0f05a0a' -H 'Content-Type: application/json' -d '{

    "jsonrpc": "2.0",

    "id": 1,

    "method": "tools/list",

    "params": {}

}'

服务端会返回所有可用的工具及其参数规范:

event: message

data: {

    "jsonrpc": "2.0",

    "id": 1,

    "result": {

        "tools": [{

            "name": "get_alerts",

            "description": "Get weather alerts for a US state.\n\n    Args:\n        state: Two-letter US state code (e.g. CA, NY)\n    ",

            "inputSchema": {

                "properties": {

                    "state": {

                        "title": "State",

                        "type": "string"

                    }

                },

                "required": ["state"],

                "title": "get_alertsArguments",

                "type": "object"

            }

        }, {

            "name": "get_forecast",

            "description": "Get weather forecast for a location.\n\n    Args:\n        latitude: Latitude of the location\n        longitude: Longitude of the location\n    ",

            "inputSchema": {

                "properties": {

                    "latitude": {

                        "title": "Latitude",

                        "type": "number"

                    },

                    "longitude": {

                        "title": "Longitude",

                        "type": "number"

                    }

                },

                "required": ["latitude", "longitude"],

                "title": "get_forecastArguments",

                "type": "object"

            }

        }]

    }

}

b) 调用特定工具

客户端根据需求,选择并调用特定工具:

curl -X POST 'http://localhost:8000/messages/?session_id=2b3c8777119444c1a1b26bc0d0f05a0a' -H 'Content-Type: application/json' -d '{

    "jsonrpc": "2.0",

    "id": 2,

    "method": "tools/call",

    "params": {"name": "get_alerts", "arguments": {"state": "CA"}}

}'

服务端执行工具调用并返回结果:

event: message

data: {

    "jsonrpc": "2.0",

    "id": 2,

    "result": {

        "content": [{

            "type": "text",

            "text": "\nEvent: Wind Advisory\nArea: San Gorgonio Pass Near Banning\nSeverity: Moderate\nDescription: * WHAT... \n"

        }],

        "isError": false

    }

}

4、通信特点

1 双向异步通信 : 接收通道通过 SSE 长连接接收服务端消息,发送通道通过 HTTP POST 向专用端点发送客户端消息

2 会话状态管理 :使用 session_id 维护客户端与服务端的会话状态,支持多客户端并发连接

3 标准通信协议 :基于 JSON-RPC 2.0 规范,确保通信格式统一,便于跨平台

4 可扩展性 :支持动态发现和调用服务端工具

MCP协议通过其灵活而高效的通信机制,为客户端与服务端之间的交互提供了可靠保障。基于SSE的实现方案不仅便于开发和调试,还具备良好的性能表现。

对于开发者而言,深入理解MCP通信模式不仅有助于实现高质量的客户端应用,也为设计其他分布式通信系统提供了宝贵经验。希望本文的详细解析能为您的技术实践提供有益参考。

5、 注意事项

(1) 所有客户端请求必须使用服务端分配的Endpoint

(2) 初始化流程必须严格按照顺序完成

六、MCP官方给出的代码示例

我们可以分析一段MCP官方给出的代码示例

https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/clients/simple-chatbot/mcp_simple_chatbot/main.py#L196

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])

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.")

方便起见,我们翻译一些系统提示词:

您是一个有用的助手, 可以访问这些工具:{ tools_description} 根据用户的问题选择合适的工具。 如果不需要工具, 请直接回复。

重要提示: 当您需要使用工具时, 您必须只回复下面确切的JSON对象格式, 不回复别的内容: {

    "tool": "tool-name",

    "arguments": {

        "argument-name": "value"

    }

}

收到执行结果后:

(1) 将原始数据转换为自然的对话式响应

2.保持回答简洁但信息丰富

3.关注最相关的信息

4.使用用户问题中的适当上下文

5.避免简单地重复原始数据

你只能使用上面明确定义的工具。

看到这里,你是不是恍然大悟。

MCP,实际上就是把函数的介绍写在了系统提示词里。

对于Function Calling来说,我需要把函数的参数填到请求参数tools里面,这些函数最终以什么样的方式输入到模型里,这个过程是不透明的,解析tools的工作是在云端进行的,返回的格式也是在云端定义的。我们只能通过官方文档来确定返回参数的格式。

而MCP很巧妙的绕过了云端解析tools的过程。

MCP把函数的介绍、参数的返回格式都写在了系统提示里面,这样一来,只要LLM能够理解系统提示词,就能够按照提示词定义的格式输出。

这就实现了格式的统一。

在理解了MCP的原理后,我们就能很清晰地解释MCP的服务端和 客户端 了:

?MCP服务端 ,  是执行函数工具的场所。

?MCP客户端,  是与LLM交互的场所,包括介绍函数和获取函数的参数

MCP服务端中常用的函数工具、数据查询工具等已经由社区的开发者们开发好了,可以直接进行调用。

对于大多数的开发者来说,需要开发的是MCP客户端。

而在MCP客户端中,开发者根据业务流程,让LLM选择合适的函数工具进行调用,最终实现业务逻辑。

image.png

七、LLM大 模型是如何确定工具的选用的?


在学习的过程中,我一直好奇一个问题:Claude(模型)是在什么时候确定使用哪些工具的呢

Anthropic 在官网为我们提供了一个 简单的解释 :

当用户提出一个问题时:

(1)客户端(Claude Desktop / Cursor)将你的问题发送给 Claude。

(2)Claude 分析可用的工具,并决定使用哪一个(或多个)。

(3)MCP  Client 客户端   通过 MCP Server 执行所选的工具。

(4)工具的执行结果被送回给 Claude。

(5)Claude 结合执行结果构造最终的 prompt 并生成自然语言的回应。

(6)回应最终展示给用户!

MCP Server 是由 Claude 主动选择并调用的。

有意思的是 Claude 具体是如何确定该使用哪些工具呢?以

及是否会使用一些不存在的工具呢(幻觉)?


为了探索这个问题让我们深入源码。

显然这个调用过程可以分为两个步骤:

(1)由 LLM(Claude)确定使用哪些 MCP Server。

(2)执行对应的 MCP Server 并对执行结果进行重新处理。

先给出一个简单可视化帮助理解:

image.png

第一步模型如何确定该使用哪些工具

先理解第一步模型如何确定该使用哪些工具?

这里以 MCP 官方提供的  client  example[8] 为讲解示例,并简化了对应的代码(删除了一些不影响阅读逻辑的异常控制代码)。

通过阅读代码,可以发现模型是通过 prompt 来确定当前有哪些工具。我们通过将工具的具体使用描述以文本的形式传递给模型,供模型了解有哪些工具以及结合实时情况进行选择。

参考代码中的注释:

... // 省略了无关的代码

 async def start(self):

     // 初始化所有的 mcp server

     for server in self.servers:

         await server.initialize()

     // 获取所有的 tools 命名为 all_tools

     all_tools = []

     for server in self.servers:

         tools = await server.list_tools()

         all_tools.extend(tools)

     // 将所有的 tools 的功能描述格式化成字符串供 LLM 使用

     // tool.format_for_llm() 我放到了这段代码最后,方便阅读。

     tools_description = "\n".join(

         [tool.format_for_llm() for tool in all_tools]

     )

     // 这里就不简化了,以供参考,实际上就是基于 prompt 和当前所有工具的信息

     // 询问 LLM(Claude) 应该使用哪些工具。

     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:

         // Final... 假设这里已经处理了用户消息输入.

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

         // 将 system_message 和用户消息输入一起发送给 LLM

         llm_response = self.llm_client.get_response(messages)

     ... // 后面和确定使用哪些工具无关

 class Tool:

     """Represents a tool with its properties and formatting."""

     def init(

         self, name: str, description: str, input_schema: dict[str, Any]

     ) -> None:

         self.name: str = name

         self.description: str = description

         self.input_schema: dict[str, Any] = input_schema

     // 把工具的名字 / 工具的用途(description)和工具所需要的参数(args_desc)转化为文本

     def format_for_llm(self) -> str:

         """Format tool information for LLM.

         Returns:

             A formatted string describing the tool.

         """

         args_desc = []

         if "properties" in self.input_schema:

             for param_name, param_info in self.input_schema["properties"].items():

                 arg_desc = (

                     f"- {param_name}: {param_info.get('description', 'No description')}"

                 )

                 if param_name in self.input_schema.get("required", []):

                     arg_desc += " (required)"

                 args_desc.append(arg_desc)

         return f"""

 Tool: {self.name}

 Description: {self.description}

 Arguments:

 {chr(10).join(args_desc)}

那 tool 的描述和代码中的 input_schema 是从哪里来的呢?

通过进一步分析 MCP 的 Python SDK 源代码可以发现:大部分情况下,当使用装饰器 @mcp.tool() 来装饰函数时,对应的 name 和 description 等其实直接源自用户定义函数的函数名以及函数的 docstring 等。

这里仅截取一小部分片段,想了解更多请参考原始代码 。

@classmethod

 def from_function(

     cls,

     fn: Callable,

     name: str | None = None,

     description: str | None = None,

     context_kwarg: str | None = None,

 ) -> "Tool":

     """Create a Tool from a function."""

     func_name = name or fn.name # 获取函数名

     if func_name == "<lambda>":

         raise ValueError("You must provide a name for lambda functions")

     func_doc = description or fn.doc or "" # 获取函数 docstring

     is_async = inspect.iscoroutinefunction(fn)

     ...   更多请参考原始代码...

总结:模型是通过 prompt engineering,即提供所有工具的结构化描述和 few-shot 的 example 来确定该使用哪些工具。

另一方面,Anthropic 肯定对 Claude 做了专门的训练(毕竟是自家协议,Claude 更能理解工具的 prompt 以及输出结构化的 tool call json 代码)

第二步:工具执行与结果反馈机制

其实工具的执行就比较简单和直接了。承接上一步,我们把 system prompt(指令与工具调用描述)和用户消息一起发送给模型,然后接收模型的回复。当模型分析用户请求后,它会决定是否需要调用工具:

(1)无需工具时:模型直接生成自然语言回复。

(2)需要工具时:模型输出结构化 JSON 格式的工具调用请求。

如果回复中包含结构化 JSON 格式的工具调用请求,则客户端会根据这个 json 代码执行对应的工具。

具体的实现逻辑都在 process_llm_response 中,[代码10],逻辑非常简单。

如果模型执行了 tool call,则工具执行的结果 result 会和 system prompt 和用户消息一起重新发送给模型,请求模型生成最终回复。

如果 tool call 的 json 代码存在问题或者模型产生了幻觉怎么办呢?通过阅读[代码11] 发现,我们会 skip 掉无效的调用请求。

执行相关的代码与注释如下:

 ... // 省略无关的代码

 async def start(self):

     ... // 上面已经介绍过了,模型如何选择工具

     while True:

         // 假设这里已经处理了用户消息输入.

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

         // 获取 LLM 的输出

         llm_response = self.llm_client.get_response(messages)

         // 处理 LLM 的输出(如果有 tool call 则执行对应的工具)

         result = await self.process_llm_response(llm_response)

         // 如果 result 与 llm_response 不同,说明执行了 tool call (有额外信息了)

         // 则将 tool call 的结果重新发送给 LLM 进行处理。

         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}

             )

         // 否则代表没有执行 tool call,则直接将 LLM 的输出返回给用户。

         else:

             messages.append({"role": "assistant", "content": llm_response})    

结合这部分原理分析:

(1)工具文档至关重要 - 模型通过工具描述文本来理解和选择工具,因此精心编写工具的名称、docstring 和参数说明至关重要。

(2)由于 MCP 的选择是基于 prompt 的,所以任何模型其实都适配 MCP,只要你能提供对应的工具描述。但是当你使用非 Claude 模型时,MCP 使用的效果和体验难以保证(没有做专门的训练)。

MCP (Model Context Protocol)  总结

MCP (Model Context Protocol) 代表了 AI 与外部工具和数据交互的标准建立。通过本文,我们可以了解到:

1 MCP 的本质:它是一个统一的协议标准,使 AI 模型能够以一致的方式连接各种数据源和工具,类似于 AI 世界的"USB-C"接口。

2 MCP 的价值:它解决了传统 function call 的平台依赖问题,提供了更统一、开放、安全、灵活的工具调用机制,让用户和开发者都能从中受益。

3 使用与开发:对于普通用户,MCP 提供了丰富的现成工具,用户可以在不了解任何技术细节的情况下使用;对于开发者,MCP 提供了清晰的架构和 SDK,使工具开发变得相对简单。

MCP 还处于发展初期,但其潜力巨大。

更重要的是生态吧,基于统一标准下构筑的生态也会正向的促进整个领域的发展。

八、MCP (Model Context Protocol)  实战

我们按照官方 github modelcontextprotocol/python-sdk 实际操作一下,这里我们仅开发 MCP Server, MCP Client 直接使用 Claude Desktop App.

我们会为 Claude Desktop App 增加个 tools: calculate_bmi 和 fetch_weather, 默认 Claude Desktop App 是不具备计算 BMI 和查询天气的功能。

实现 MCP Server (Python SDK)

代码实现

// server.py

import httpx

from mcp.server.fastmcp import FastMCP

// Create an MCP server

mcp = FastMCP("Demo")

@mcp.tool()

def calculate_bmi(weight_kg: float, height_m: float) -> float:

    """Calculate BMI given weight in kg and height in meters"""

    return weight_kg / (height_m ** 2)

@mcp.tool()

async def fetch_weather(city: str) -> str:

    """Fetch current weather for a city"""

    async with httpx.AsyncClient() as client:

        response = await client.get(f"https://api.weather.com/{city}")

        return response.text

if name == "main":

    mcp.run()

实现 MCP Client

MCP 客户端充当 LLM 和 MCP 服务器之间的桥梁,MCP 客户端的工作流程如下:

MCP 客户端首先从 MCP 服务器获取可用的工具列表。

将用户的查询连同工具描述通过 function calling 一起发送给 LLM。

LLM 决定是否需要使用工具以及使用哪些工具。

如果需要使用工具,MCP 客户端会通过 MCP 服务器执行相应的工具调用。

工具调用的结果会被发送回 LLM。

LLM 基于所有信息生成自然语言响应。

最后将响应展示给用户。

MCP  Client调用工具

在 MCP 客户端中,我们使用了以下两个函数来与 MCP 服务器进行交互。

list_tools():获取 MCP 服务器提供的所有可用工具。

call_tool(name, args):调用指定的工具并获取结果,这里调用 list_indices 来获取 Elasticsearch 集群中的索引信息。

from mcp import ClientSession, StdioServerParameters

from mcp.client.stdio import stdio_client

async def run():

    // 建立连接

    async with stdio_client(server_params) as (read, write):

        async with ClientSession(read, write) as session:

            // 初始化连接

            await session.initialize()

            // 列出可用工具

            tools = await session.list_tools()

            print("Tools:", tools)

            // 调用工具

            indices = await session.call_tool("list_indices")

            print("Indices:", indices)

if name == "main":

    import asyncio

    asyncio.run(run())

集成 LLM调用 MCP  Client

在实际应用中,我们通常希望让 LLM(如 OpenAI、Claude、 通义千问 等)自主决定调用哪些工具。

下面的代码将以通义千问为示例进行演示,并使用 OpenAI SDK 与其交互。

为了简化这一过程,我们将借助 OpenRouter (一个统一的 LLM 网关,它提供了 OpenAI 兼容的接口),使我们能够通过相同的 OpenAI API 访问包括通义千问在内的多种 LLM。

OpenRouter 的使用方式非常简单。

我们只需在创建 OpenAI 客户端时指定 OpenRouter 的 base_url 和 api_key,并在调用模型时以 <provider>/<model> 的格式(例如 qwen/qwen-plus)指定目标模型,OpenRouter 就会根据模型名称自动将请求路由到对应的 LLM 上。

除此之外,其他代码与标准的 OpenAI SDK 保持一致。

接下来介绍一下 MCP 客户端的主要代码。

初始化 MCP  Client 客户端类

MCPClient 类的初始化包含以下 3 个组件:

1 self.session:用于存储与 MCP 服务器的会话对象,初始设为 None,将在连接服务器时被赋值。

2 self.exit_stack:使用 AsyncExitStack 来管理异步资源,确保所有资源(如服务器连接、会话等)在程序结束时能够正确关闭。

3 self.client:创建 OpenAI 异步客户端,通过 OpenRouter 来访问 LLM。

这里我们:

设置 base_url 为 OpenRouter 的 API 端点。

从环境变量获取 API Key(请确保设置了 OPENROUTER_API_KEY 环境变量)。

class MCPClient:

    def init(self):

        self.session: Optional[ClientSession] = None

        self.exit_stack = AsyncExitStack()

        self.client = AsyncOpenAI(

            base_url="https://openrouter.ai/api/v1",

            api_key=os.getenv("OPENROUTER_API_KEY"),

        )

MCP  Client 和 MCP  服务器连接

connect_to_server 方法负责建立与 MCP 服务器的连接。它首先配置服务器进程的启动参数,然后通过 stdio_client 建立双向通信通道,最后创建并初始化会话。

所有的资源管理都通过 AsyncExitStack 来处理,确保资源能够正确释放。

连接成功后,它会打印出 MCP 服务器提供的所有可用工具。

async def connect_to_server(self, server_script_path: str):

    server_params = StdioServerParameters(

        command="python",

        args=[server_script_path],

        env=None

    )

    stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))

    self.stdio, self.write = stdio_transport

    self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))

    await self.session.initialize()

    # 列出可用工具

    response = await self.session.list_tools()

    tools = response.tools

    print("\nConnected to server with tools:", [tool.name for tool in tools])

处理请求

定义一个 process_query 方法,完成 处理请求的流程:

首先,将用户的查询作为初始消息发送给 LLM,同时提供 MCP 服务器上所有可用工具的描述信息。

LLM 分析用户查询,决定是直接回答还是需要调用工具。如果需要工具,它会指定要调用的工具名称和参数。

对于每个工具调用,MCP 客户端执行调用并收集结果。

将工具调用的结果返回给 LLM,让它基于这些新信息生成或更新回答。

如果 LLM 认为还需要更多信息,它会继续请求调用其他工具。

这个过程会一直重复,直到 LLM 收集了足够的信息来完整回答用户的查询。

async def process_query(self, query: str) -> str:

    """使用 LLM 和 MCP 服务器提供的工具处理查询"""

    messages = [

        {

            "role": "user",

            "content": query

        }

    ]

    response = await self.session.list_tools()

    available_tools = [{

        "type": "function",

        "function": {

            "name": tool.name,

            "description": tool.description,

            "parameters": tool.inputSchema

        }

    } for tool in response.tools]

    // 初始化 LLM API 调用

    response = await self.client.chat.completions.create(

        model="qwen/qwen-plus",

        messages=messages,

        tools=available_tools

    )

    final_text = []

    message = response.choices[0].message

    final_text.append(message.content or "")

    // 处理响应并处理工具调用

    while message.tool_calls:

        // 处理每个工具调用

        for tool_call in message.tool_calls:

            tool_name = tool_call.function.name

            tool_args = json.loads(tool_call.function.arguments)

            // 执行工具调用

            result = await self.session.call_tool(tool_name, tool_args)

            final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")

            // 将工具调用和结果添加到消息历史

            messages.append({

                "role": "assistant",

                "tool_calls": [

                    {

                        "id": tool_call.id,

                        "type": "function",

                        "function": {

                            "name": tool_name,

                            "arguments": json.dumps(tool_args)

                        }

                    }

                ]

            })

            messages.append({

                "role": "tool",

                "tool_call_id": tool_call.id,

                "content": str(result.content)

            })

        // 将工具调用的结果交给 LLM

        response = await self.client.chat.completions.create(

            model="qwen/qwen-plus",

            messages=messages,

            tools=available_tools

        )

        message = response.choices[0].message

        if message.content:

            final_text.append(message.content)

    return "\n".join(final_text)

九、MCP (Model Context Protocol) 生态

MCP 生态不断发展壮大,越来越多的应用支持 MCP,同时开放平台也提供 MCP Server。

同时也有像  Cloudflare 、 Composio 、 Zapier 使用 SSE 方式将 MCP 进行托管(即接入一个 MCP Endpoint 即接入一批 MCP Servers),通过 Stdio 方式最理想场景是 MCP Servers 和 Agent 系统跑在同一 Docker 容器中(类似 Sidecar 模式)。

image.png

举个例子:接入地图厂商的 MCP Server 后,Agent 具备生活服务工具能力,远远优于单纯依赖搜索的方式。

MCP (Model Context Protocol)  未来


目前的 MCP 开发非常初级,在工程化上缺少一套完善的框架来约束和规范。

根据 MCP Roadmap ,未来主要三件事:

  • Remote MCP Support:鉴权、服务发现、无状态服务,很明显奔着K8S架构去的,这样才能构建一个生产级、可扩展的 MCP 服务。根据最近的 RFC Replace HTTP+SSE with new "Streamable HTTP" transport,支持 Streamable HTTP,可以低延迟、双向传输。

  • Agent Support:提升不同领域复杂的 Agent 工作流,并可以处理更好的人机交互。

  • Developer Ecosystem:更多的开发者和大厂商参与进来,才能扩展 AI Agent 的能力边界。


实践下来,MCP Server SSE 并不是理想的方案,因为需要保持连接和 session 状态,而云服务(如 FaaS)更倾向于无状态架构 ,所以最近提出了更适配云场景的 Streamable HTTP Transport。

MCP 模型调用与 RL 强化学习

如果 MCP 成为未来的规范,那么 Agent 应用能否准确调用各个 MCP,将成为模型 RL 未来需要支持的关键功能。与 Function Call 模型不同,MCP 是一个动态的工具库,模型需要具备对新增 MCP 的泛化理解能力。

Agent K8S

虽然目前 LLM 和上下文之间建立了标准化的通信协议,但 Agent 之间的交互协议尚未形成统一标准,Agent 服务发现、恢复、监控等一系列生产级问题等解决。目前  ANP  (Agent Network Protocol)在做这方面的探索与尝试。

MCP  的应用场景分析


1  相对于开发者目前本身就在用 MCP 支持的 Client ( Claude Desktop App, Zed, Cline 等)

-  提供了巨大的便利, 可以很低成本用 MCP 扩展 LLM 的本身的能力,构建一个复杂 agent, 比如 Claude Desktop App 本身不具备任何扩展能力

  • 目前官方支持的 Client 都是一些 toC 的应用,比如 Claude Desktop App、 Zed(IDE 编辑器) 等,适合个人开发;


2  相对于开发者调用 LLM API ,自己从零开始写代码构建 agent

  • 便于快速构建比较复杂的 agent;


3  相对于开发者调用 LLM API 并使用开源的 Agent 构建项目,如:modelscope-agent, FastGPT, dify

  • MCP 相较于这些项目,更轻量。MCP 仅仅负责连接作用;

  • 这类开源项目相对较重,类似全托管的方式,但是功能较为丰富,上手简单,比如提供知识库,以及 web 管理页面等等;


4  相对于商业的 Agent 平台,比如: 字节 coze,清华 chatglm,百度 AppBuilder 等,

  • 云厂商 agent 平台更成熟,构建 AI Agnet 或者 AI App 更方便,提供一站式托管,可视化的管理台,拖拽构建工作流,配置知识库等低代码操作;

  • MCP 实现更为复杂,且可用的 MCP Client 有限, 同时需要实现 MPC Server 代码, 并且 MCP 目前支持的 sdk 有限;

  • 云厂商 agent 平台的 LLM 提供相对闭塞,只能使用云厂商提供的模型列表,mcp 相对自由;

  • 数据安全来说, mcp 这种支持全部本地部署,会更高一点,云厂商的 agent 平台需要访问自己的数据,目前除了内部集成的知识库,其他数据源访问的方式,只能通过调用 tools 插件,走 API 的方式访问;


5  在企业应用场景,目前使用场景有限。

企业应用 自己提供接口,自己调用。

自己 弄个api, 通过Langchain 写个 LLM 实现 Tool调用的智能体,直接调用就OK了, 省掉了 额外还要写个 MCP 服务,隔靴搔痒,非常麻烦。


6 在 个人应用场景, 使用场景将会 越来越广阔。

可以 通过 LLM+  MCP  调用 大量的互联网  MCP  应用, 使用场景将会 越来越广阔。

总体来说, MCP 这套方案或者架构建的 Agent 或者工作流,在 互联网个人应用场景,将会 越来越广阔。

关联文档