<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>JSON-RPC on Renxin's Blog</title><link>https://zh.renxinblog.cn/tags/json-rpc/</link><description>Recent content in JSON-RPC on Renxin's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Tue, 09 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://zh.renxinblog.cn/tags/json-rpc/index.xml" rel="self" type="application/rss+xml"/><item><title>第八话｜MCP 的边界：工具发现与调用是协议，选哪个工具仍是模型的事</title><link>https://zh.renxinblog.cn/post/c08-mcp/</link><pubDate>Tue, 09 Jun 2026 00:00:00 +0000</pubDate><guid>https://zh.renxinblog.cn/post/c08-mcp/</guid><description>&lt;img src="https://zh.renxinblog.cn/images/c08-mcp-cover.png" alt="Featured image of post 第八话｜MCP 的边界：工具发现与调用是协议，选哪个工具仍是模型的事" /&gt;&lt;p&gt;第七话结尾，留下了一个没解决的问题。&lt;/p&gt;
&lt;p&gt;那一篇我们让 Agent 记住了「你是谁、你要什么」——跨会话、跨进程地。但记忆要真正派上用场，还差一环：&lt;strong&gt;Agent 要去做事，就得调工具。而调工具这件事，前七话一直是「写死」的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;查天气，是代码里写死的一个函数；查库存，是代码里写死的另一个函数。每接一个新工具，就得写一套适配——把它的参数拼成 LLM 认识的 schema，把它的返回值包成 LLM 认识的 Observation。一个工具一套，N 个模型 × M 个工具，就是 N×M 套。&lt;/p&gt;
&lt;p&gt;于是问题冒出来了：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;工具能不能像 USB 外设一样，&lt;strong&gt;插上就能被 Agent 发现、调用&lt;/strong&gt;，而不是每接一个都要手写一套适配？&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;「怎么把工具接进来」，就是 &lt;strong&gt;MCP（Model Context Protocol）&lt;/strong&gt; 要解决的问题。&lt;/p&gt;
&lt;p&gt;但讲 MCP 之前，得先立住一条边界——它是 MCP 最容易讲错、也最容易被误解的地方：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;MCP 定义了「工具怎么被发现、怎么被调用」的格式，但「选哪个工具、给什么参数」这一步，MCP 一个字都不管。&lt;/strong&gt; 前者是协议，后者是 LLM 在 function-calling 层做的决策。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这条边界落到代码上，就是一件事：&lt;strong&gt;把前七话那个 ReAct 循环里「写死的工具源」，换成「可发现、可调用」的 MCP Server——以及在这两者之间，你自己要写的那几行桥接。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="一内存注册表的集成成本每接一个工具都是一份私有适配"&gt;一、内存注册表的集成成本：每接一个工具都是一份私有适配
&lt;/h2&gt;&lt;p&gt;先回到第五话。那一次我们手写了一个 ReAct 循环，模型「想」一步、「做」一步、「看」一步。其中「做」这一步，工具是从哪来的？看代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# C05 tools.py —— 工具注册表，写死在进程里&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ToolRegistry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;# 工具名 → {fn, schema}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;fn&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;schema&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_schemas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;schema&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="c1"&gt;# 给 LLM 拼 tools 参数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;fn&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 本地函数调用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而 ReAct 循环里，工具是这样被用到的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# C05 react_loop.py —— 循环里对工具的两次触碰&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_schemas&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;# ① 工具列表来自注册表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ② 调用发给本地函数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;两个动作，一个来源：&lt;strong&gt;工具定义和工具实现，都住在同一个进程的内存里。&lt;/strong&gt; &lt;code&gt;list_schemas()&lt;/code&gt; 把工具描述拼给 LLM，&lt;code&gt;execute()&lt;/code&gt; 直接调本地函数——工具「从哪来」和「调用发给谁」，是一件事，而且都写死。&lt;/p&gt;
&lt;p&gt;这在只有一个 &lt;code&gt;get_weather&lt;/code&gt; 的时候很顺手。但一旦要接别人家的工具，问题就现形了：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;场景&lt;/th&gt;
					&lt;th&gt;写死的注册表&lt;/th&gt;
					&lt;th&gt;你实际要做的&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;接一个天气 API&lt;/td&gt;
					&lt;td&gt;手写 &lt;code&gt;get_weather&lt;/code&gt; 函数&lt;/td&gt;
					&lt;td&gt;读它家的文档，自己写适配&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;接一个股票数据源&lt;/td&gt;
					&lt;td&gt;手写 &lt;code&gt;get_quote&lt;/code&gt; 函数&lt;/td&gt;
					&lt;td&gt;再读一套文档，再写一套适配&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;换一个 Agent 框架&lt;/td&gt;
					&lt;td&gt;注册表 API 全变&lt;/td&gt;
					&lt;td&gt;全重写&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;工具升级了&lt;/td&gt;
					&lt;td&gt;改函数体&lt;/td&gt;
					&lt;td&gt;改完还要同步改 schema&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;问题不在「写函数」这件事本身，而在于：&lt;strong&gt;「工具怎么被描述」和「工具怎么被调用」，每个工具、每个框架、每个厂商都各有一套私有的约定。&lt;/strong&gt; 你写的适配，绑死了工具、绑死了框架、绑死了厂商。&lt;/p&gt;
&lt;p&gt;这就是 N×M 集成问题——N 个模型/框架 × M 个工具，组合爆炸。&lt;strong&gt;你要的是一套「工具如何被发现和调用」的公共约定，而不是第 M+1 套私有适配。&lt;/strong&gt; 而这，正是 MCP 要解决的。&lt;/p&gt;
&lt;h2 id="二mcp-只解决一件事把工具接口与实现解耦"&gt;二、MCP 只解决一件事：把工具接口与实现解耦
&lt;/h2&gt;&lt;p&gt;MCP 干的事，一句话：&lt;strong&gt;把「工具怎么被发现、怎么被调用」做成一套公开标准，让「提供工具的一方」和「使用工具的一方」彻底解耦。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它像什么？像 USB-C。你插一个键盘、一个显示器、一个硬盘，主机不用为每个外设写驱动——因为「外设怎么自我介绍、数据怎么传」有一套公开协议。外设厂商照协议做设备，主机照协议读设备，谁也不用认识谁。&lt;/p&gt;
&lt;p&gt;MCP 也是这么个东西，只是对象从「外设」换成了「工具」：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 subgraph S[工具提供方 · MCP Server]
 T1[get_weather]
 T2[get_quote]
 T3[refund_order]
 end
 subgraph C[工具消费方 · MCP Client]
 H[你的 Agent 循环]
 end
 S -- "标准协议：发现 tools/list · 调用 tools/call" --&gt; C&lt;/pre&gt;&lt;p&gt;Server 端负责「我有这些工具，能这么调」；Client 端负责「我发现、我调用」。中间只有一套标准协议，两边各写各的，谁都不需要知道对方的实现。&lt;/p&gt;
&lt;p&gt;这套协议有几个关键事实，先立住：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;它借了 LSP 的消息流设计&lt;/strong&gt;——LSP（Language Server Protocol）早就证明了「编辑器 ↔ 语言服务」可以用 JSON-RPC 解耦，MCP 把同样的思路搬到了「Agent ↔ 工具」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它由 Anthropic 于 2024 年 11 月开源&lt;/strong&gt;，之后 OpenAI（2025-03）、Google（2025-04）、微软（2025-05）陆续采纳，2025 年 12 月捐给 Linux Foundation 旗下的 Agentic AI Foundation（AAIF）——已经从一个厂商的项目，变成了跨厂商的事实标准。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;底层是 JSON-RPC 2.0&lt;/strong&gt;，消息走 &lt;code&gt;stdio&lt;/code&gt;（子进程标准输入输出）或 Streamable HTTP 两种传输。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三个核心原语：Tools、Resources、Prompts&lt;/strong&gt;（后来加了 Sampling）。这里只展开 Tools——因为它正是我们前七话一直在调、也最关心的一种。Resources（让模型读数据）和 Prompts（让模型拿模板）是另外两个维度，这里不展开。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但比这些事实更关键的，是下面这条边界：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;MCP 只到「工具发现与调用」为止。它不替 LLM 决策。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;MCP 的 &lt;code&gt;tools/list&lt;/code&gt; 返回的，是「这些工具叫什么、能收什么参数」；MCP 的 &lt;code&gt;tools/call&lt;/code&gt; 执行的，是「你（Agent）指名要调的那个工具」。&lt;strong&gt;至于「该调哪个工具、给什么参数」，MCP 从头到尾不参与&lt;/strong&gt;——那是 LLM 在 function-calling 层干的活。&lt;/p&gt;
&lt;p&gt;这条边界落到代码里，就是「发现」和「调用」两段——接下来动手把它实现出来。&lt;/p&gt;
&lt;h2 id="三真正要动的只有一处把循环里的工具源换成-mcp-server"&gt;三、真正要动的只有一处：把循环里的工具源换成 MCP Server
&lt;/h2&gt;&lt;p&gt;不背协议术语，就做一件事：&lt;strong&gt;把第五话那个 ReAct 循环里的「写死的工具源」，换成「MCP Server」，然后看中间多出来的那几行桥接。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="31-server-端暴露工具就一个装饰器"&gt;3.1 Server 端：暴露工具，就一个装饰器
&lt;/h3&gt;&lt;p&gt;Server 端用官方 Python SDK v2，暴露三个工具——两个语义化工具（&lt;code&gt;get_weather&lt;/code&gt;、&lt;code&gt;get_time&lt;/code&gt;）给 LLM 去「选择」，一个最小确定性工具（&lt;code&gt;add&lt;/code&gt;）用来验收协议链路本身：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# server.py —— 工具提供方，跑在独立进程里&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mcp.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MCPServer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MCPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;c08-mcp-demo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Add two integers and return the result.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Get the current weather for a city.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;北京&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;晴，24°C&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;上海&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;多云，27°C&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;深圳&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;阵雨，30°C&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 暂无数据&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Get the current server time.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 默认走 stdio：从 stdin 读 JSON-RPC，往 stdout 写 JSON-RPC&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;有个细节现在就得记住，它是 MCP 最容易踩的坑之一：&lt;strong&gt;&lt;code&gt;mcp.run()&lt;/code&gt; 走 stdio，意味着 stdout 就是协议通道。&lt;/strong&gt; 任何业务日志 &lt;code&gt;print&lt;/code&gt; 到 stdout，都会污染 JSON-RPC 消息流，导致客户端解析失败。所以日志只能写 stderr（代码里那条 &lt;code&gt;sys.stderr.write(...)&lt;/code&gt; 就是为此）。&lt;/p&gt;
&lt;h3 id="32-client-端发现和调用两行就够"&gt;3.2 Client 端：发现和调用，两行就够
&lt;/h3&gt;&lt;p&gt;Client 端启动这个 Server 子进程，连上去，发现工具、调用工具：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# main.py —— 工具消费方&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StdioServerParameters&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StdioServerParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;server.py&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;listed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 发现：tools/list&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;add&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;# 调用：tools/call&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;structured_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;跑起来，真实输出：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[1] 纯 MCP 层：发现工具 → 调用工具 → 错误路径
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 发现工具: [&amp;#39;add&amp;#39;, &amp;#39;get_weather&amp;#39;, &amp;#39;get_time&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - add: Add two integers and return the result.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - get_weather: Get the current weather for a city.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - get_time: Get the current server time.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 调用 add(2, 3): 5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 错误场景（未知工具）: is_error=True; Unknown tool: missing_tool
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意最后一行。调用一个不存在的工具，&lt;strong&gt;MCP 没有抛异常&lt;/strong&gt;，而是返回了一个 &lt;code&gt;is_error=True&lt;/code&gt; 的结果，错误信息放在结果里。这是 MCP 的一个设计取向：&lt;strong&gt;错误也是协议的一部分&lt;/strong&gt;，走的是返回值而不是异常栈——调用方拿到的是结构化的错误结果，而不是一个需要 try/catch 去猜的异常。这跟第四话 ToolRegistry 里「错误要结构化、可读」是同一个思路，只是这次被提到了协议层。&lt;/p&gt;
&lt;p&gt;到这里，「发现」和「调用」这两条协议链路都通了。但——&lt;strong&gt;这还只是 MCP 自己。LLM 在哪？&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="33-桥接是唯一的脏活也是唯一不能省的一步"&gt;3.3 桥接是唯一的脏活，也是唯一不能省的一步
&lt;/h3&gt;&lt;p&gt;回到第五话的 ReAct 循环，工具源是内存注册表，两处触碰：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_schemas&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;# ① 工具列表 → LLM&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ② LLM 的选择 → 本地函数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;换成 MCP 之后，循环本身&lt;strong&gt;一行都不用改&lt;/strong&gt;——模型照样「想」、照样「做」、照样「看」。变的只有这两处触碰的&lt;strong&gt;来源&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 换成 MCP 之后&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdio_server&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# ① 工具列表来自 MCP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools_to_llm_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ② 调用发给 MCP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但注意中间那一步——&lt;strong&gt;&lt;code&gt;tools_to_llm_schema(tools)&lt;/code&gt;。&lt;/strong&gt; 这就是桥接，而且是 MCP 自己&lt;strong&gt;不会&lt;/strong&gt;替你做的事。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;client.list_tools()&lt;/code&gt; 返回的，是 MCP 的 &lt;code&gt;Tool&lt;/code&gt; 对象（&lt;code&gt;name&lt;/code&gt; / &lt;code&gt;description&lt;/code&gt; / &lt;code&gt;input_schema&lt;/code&gt;）；而 LLM 的 function-calling 认的，是 OpenAI 风格的 &lt;code&gt;tools&lt;/code&gt; 参数（&lt;code&gt;type: &amp;quot;function&amp;quot;&lt;/code&gt; / &lt;code&gt;function: {name, description, parameters}&lt;/code&gt;）。这两个格式&lt;strong&gt;长得像，但不是一个东西&lt;/strong&gt;。把它们对齐，是你（Agent 代码）要写的字段搬运：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tools_to_llm_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcp_tools&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;把 MCP 的 tools/list 结果，转成 LLM function-calling 的 tools 参数。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;parameters&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_schema&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;object&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mcp_tools&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;就是这十几行，没用什么高深技巧。但它恰恰是&lt;strong&gt;最容易被忽略、也最容易误解的一环&lt;/strong&gt;：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;MCP 到 LLM 之间，没有一步是 MCP 自己完成的。&lt;/strong&gt; &lt;code&gt;tools/list&lt;/code&gt; 返回 MCP 的工具描述，要变成 LLM 认识的 &lt;code&gt;tools&lt;/code&gt; 参数，是你做的字段搬运；LLM 返回的 &lt;code&gt;tool_call&lt;/code&gt;，要变成 MCP 的 &lt;code&gt;tools/call&lt;/code&gt;，也是你做的。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;很多人对 MCP 的误解，恰恰卡在这里：以为「装上 MCP 工具，就自动能被 LLM 用了」。不是的。&lt;strong&gt;MCP 把「工具怎么描述、怎么调用」标准化了，但「把 MCP 工具翻译给 LLM」和「把 LLM 的选择翻译回 MCP 调用」，这两步是 Agent 循环里你自己写的桥接代码。&lt;/strong&gt; 框架（LangChain、SDK 的 agent 封装）会把这一步藏进 &lt;code&gt;bind_tools()&lt;/code&gt;、&lt;code&gt;create_mcp_client()&lt;/code&gt; 之类的黑盒里，让你看不见；手写循环反而把这一步摊开在眼前。&lt;/p&gt;
&lt;p&gt;这条链，用一张图看更清楚：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;sequenceDiagram
 participant L as LLM
 participant A as Agent 循环&lt;br&gt;（你的代码）
 participant C as MCP Client
 participant S as MCP Server

 A-&gt;&gt;C: list_tools()
 C-&gt;&gt;S: tools/list (JSON-RPC)
 S--&gt;&gt;C: [add, get_weather, get_time]
 C--&gt;&gt;A: MCP Tool 对象
 A-&gt;&gt;A: tools_to_llm_schema()&lt;br&gt;（桥接：MCP → LLM 格式）
 A-&gt;&gt;L: chat(messages, tools=...)
 L--&gt;&gt;A: tool_calls = [get_weather(city=北京), add(a=12,b=30)]
 A-&gt;&gt;C: call_tool(get_weather, {city:北京})
 C-&gt;&gt;S: tools/call (JSON-RPC)
 S--&gt;&gt;C: "晴，24°C"
 C--&gt;&gt;A: Observation
 A-&gt;&gt;A: 把 Observation 回填 messages
 A-&gt;&gt;L: chat(messages) ← 带上工具结果
 L--&gt;&gt;A: "北京晴 24°C，12+30=42"&lt;/pre&gt;&lt;p&gt;看清这条链上，MCP 到底在哪、LLM 到底在哪：&lt;strong&gt;MCP 只出现在「工具发现」和「工具执行」两段；「选哪个工具、给什么参数」，是 LLM 从 &lt;code&gt;tools&lt;/code&gt; 参数里自己决定的；「把 MCP 结果翻译给 LLM、把 LLM 的选择翻译成 MCP 调用」，是 Agent 循环做的。&lt;/strong&gt; 三者各司其职，边界清晰。&lt;/p&gt;
&lt;h3 id="34-串起来让模型自己挑工具"&gt;3.4 串起来，让模型自己挑工具
&lt;/h3&gt;&lt;p&gt;把上面全部串起来，带真实 LLM 跑一遍。问题是「北京今天天气怎么样？顺便帮我算一下 12 加 30」——这句话故意&lt;strong&gt;一次要两个工具&lt;/strong&gt;，好让「模型选择」这个关键点现形：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2] LLM 完整闭环：MCP 工具 → LLM 决策 → 执行 → 回答
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 问：北京今天天气怎么样？顺便帮我算一下 12 加 30。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 从 MCP 拿到 3 个工具，转成 function-calling schema
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 模型选择调用 get_weather({&amp;#39;city&amp;#39;: &amp;#39;北京&amp;#39;})
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Observation: &amp;#34;晴，24°C&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 模型选择调用 add({&amp;#39;a&amp;#39;: 12, &amp;#39;b&amp;#39;: 30})
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Observation: 42
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 最终回答: 北京今天晴，24°C；12 加 30 等于 42。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;模型自己挑了 &lt;code&gt;get_weather&lt;/code&gt;（不是 &lt;code&gt;get_time&lt;/code&gt;，不是 &lt;code&gt;add&lt;/code&gt;），给对了参数 &lt;code&gt;city=北京&lt;/code&gt;；又挑了 &lt;code&gt;add&lt;/code&gt; 去算 12+30。&lt;strong&gt;这两个「选择」全是 LLM 在 function-calling 层做的，MCP 全程没参与决策&lt;/strong&gt;——它只负责把 &lt;code&gt;get_weather&lt;/code&gt; 和 &lt;code&gt;add&lt;/code&gt; 这两个工具「被发现」和「被调用」。&lt;/p&gt;
&lt;p&gt;这正是那句话的活体证据：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;MCP 标准化了「工具怎么被发现、怎么被调用」，但它不替 LLM 决策——选哪个工具、给什么参数，仍是 LLM 在 function-calling 层做的。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="四从-demo-到生产还有四块要补"&gt;四、从 demo 到生产，还有四块要补
&lt;/h2&gt;&lt;p&gt;跑通了最小闭环，但你不会就这么上线。从「demo 能跑」到「生产能用」，至少要补四块：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;传输换成 Streamable HTTP。&lt;/strong&gt; stdio 只能本地子进程；生产里的 MCP Server 往往跑在远端、要鉴权、要并发。Streamable HTTP 是 stdio 的远程版，也支持无状态服务器的水平扩展。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具白名单 / 覆写。&lt;/strong&gt; MCP Server 暴露的工具，你不一定全要、也不一定全信。生产里 Client 端通常会过滤、重命名、甚至覆写某个工具的 schema，而不是「Server 给什么就接什么」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可观测。&lt;/strong&gt; 一次 &lt;code&gt;tools/call&lt;/code&gt; 从发起到返回，走了哪台 Server、耗时多少、失败在哪，需要 Trace 和日志。demo 里那些打到 stderr 的日志，就是这条链的雏形。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全边界。&lt;/strong&gt; MCP 的 &lt;code&gt;tools/call&lt;/code&gt; 本质是「让 Agent 替你执行一个远端函数」。这个函数能不能写文件、能不能发请求、能不能碰敏感数据，是 Server 端要想清楚并显式约束的，不能把「协议通了」当成「安全没问题」。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这四块，把 MCP 从「能跑」推进到「敢用」。它们属于「工程化」的延伸，不是「MCP 是什么」本身，所以这里只列出来，不展开。&lt;/p&gt;
&lt;h2 id="五工具能标准化接入了下一个没解决的问题是什么"&gt;五、工具能标准化接入了，下一个没解决的问题是什么
&lt;/h2&gt;&lt;p&gt;把这条数据流从头到尾捋一遍，答案就完整了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;前七话：工具写死在内存注册表里，list_schemas() 拼给 LLM，execute() 调本地函数
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 痛点：每接一个工具/框架/厂商，都要手写一套适配，N×M 组合爆炸
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → MCP：把「工具怎么被发现、怎么被调用」做成标准协议，Server/Client 解耦
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 桥接：tools/list 结果 → tools_to_llm_schema() → LLM 的 function-calling 参数
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 决策：LLM 从 tools 里自己选工具、给参数（MCP 不参与）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 执行：Agent 把 LLM 的选择，翻译成 MCP 的 tools/call，拿回 Observation
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 回答：Observation 回填 messages，LLM 生成最终答案
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MCP 是「工具注册表」和「工具执行器」的替代品，不是「Agent 循环」的替代品。它把原来「本地字典 + 本地函数」这两件事，换成了「跨进程问一个 Server」；而循环、状态、记忆、决策，全都还在你手里。那几行 &lt;code&gt;tools_to_llm_schema()&lt;/code&gt; 桥接，就是 MCP 和 LLM 之间「你」的存在——框架会把它藏起来，但理解了它，你才真正理解了 MCP 是怎么进 Agent 的。&lt;/p&gt;
&lt;p&gt;工具能标准化地接入了，但还有一类东西没法靠 MCP 解决：&lt;strong&gt;「怎么做一件事的方法」。&lt;/strong&gt; 查天气是工具，但「处理订单要先查库存、再扣库存、扣完发通知、全程落盘」是一套&lt;strong&gt;方法&lt;/strong&gt;——它不是「一个工具」，而是「一串工具的编排 + 判断」。工具能即插即用了，方法怎么复用？这就是第九话要讲的：&lt;strong&gt;Skill&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;演示代码&lt;/strong&gt;：Python 版在 &lt;a class="link" href="https://github.com/renxin2024/GYA/tree/main/c08-mcp" target="_blank" rel="noopener"
 &gt;GYA 仓库 &lt;code&gt;c08-mcp&lt;/code&gt;&lt;/a&gt;，Java 21 等价实现在 &lt;a class="link" href="https://github.com/renxin2024/GYA-Java/tree/main/c08-mcp" target="_blank" rel="noopener"
 &gt;GYA-Java 仓库 &lt;code&gt;c08-mcp&lt;/code&gt;&lt;/a&gt;。按各自 README 的命令跑，不要复制这篇文章里零散的代码块。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;运行环境&lt;/strong&gt;：Python 版用官方 MCP Python SDK v2（&lt;code&gt;mcp[cli]&lt;/code&gt;，实测 2.2.0）；Java 版用官方 MCP Java SDK 2.0.1。&lt;strong&gt;注意：本文代码基于 SDK v2 的 API（&lt;code&gt;from mcp import Client&lt;/code&gt; / &lt;code&gt;from mcp.server import MCPServer&lt;/code&gt;）。&lt;/strong&gt; 如果你手上还留着 v1 风格的旧代码（&lt;code&gt;ClientSession&lt;/code&gt;、&lt;code&gt;stdio_client&lt;/code&gt; 上下文、&lt;code&gt;client.initialize()&lt;/code&gt;、&lt;code&gt;result.content[0].text&lt;/code&gt;），那在 v2 下是跑不通的——v2 把客户端统一成了 &lt;code&gt;Client&lt;/code&gt;、结果结构也从 &lt;code&gt;content[0].text&lt;/code&gt; 改成了 &lt;code&gt;structured_content&lt;/code&gt; / &lt;code&gt;is_error&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;：MCP 由 Anthropic 于 2024 年 11 月 25 日开源（Wikipedia「Model Context Protocol」条目），2025 年 12 月捐给 Linux Foundation 旗下的 Agentic AI Foundation（AAIF）；底层为 JSON-RPC 2.0，传输支持 stdio 与 Streamable HTTP；核心原语为 Tools / Resources / Prompts（另加 Sampling）。借鉴 LSP 消息流设计一事，见 Anthropic 官方介绍与 MCP 规范文档。闭环 Trace 为 2026-09 本机实测（Python 与 Java 双跑）。&lt;/p&gt;

 &lt;/blockquote&gt;</description></item></channel></rss>