<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mcp on DK Blog</title><link>/tags/mcp/</link><description>Recent content in Mcp on DK Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Thu, 28 May 2026 22:16:00 +0800</lastBuildDate><atom:link href="/tags/mcp/index.xml" rel="self" type="application/rss+xml"/><item><title>Multica 初体验：把 Codex、Claude Code 这类 coding agent 当成队友来管理</title><link>/dkblog/022-multica-coding-agent-workspace/</link><pubDate>Thu, 28 May 2026 22:16:00 +0800</pubDate><guid>/dkblog/022-multica-coding-agent-workspace/</guid><description>&lt;img src="https://images.unsplash.com/photo-1515879218367-8466d910aaa4?auto=format&amp;fit=crop&amp;w=900&amp;q=60" alt="Featured image of post Multica 初体验：把 Codex、Claude Code 这类 coding agent 当成队友来管理" /&gt;&lt;p&gt;最近看了一下 Multica，感觉它解决的不是“让 AI 写代码”这个问题，而是另一个更实际的问题：当你同时用 Codex、Claude Code、Copilot CLI、OpenCode 这类 coding agent 时，怎么管理它们干活。&lt;/p&gt;
&lt;p&gt;以前我的用法比较粗糙：开几个终端窗口，每个窗口一个 agent，谁在跑什么、结果有没有回来、上下文有没有断，全靠自己记。任务少的时候还行，一旦同时丢两三个问题，比如一个查 bug、一个写测试、一个做文档，很快就乱了。&lt;/p&gt;
&lt;p&gt;Multica 的思路是把 agent 放进一个协作空间里。它有 workspace、issue、comment、agent 这些概念。你可以把一个 issue 分配给某个 agent，也可以在评论里 &lt;code&gt;@&lt;/code&gt; 它。这个体验更像是在给同事派活，而不是在终端里继续补 prompt。&lt;/p&gt;
&lt;p&gt;我当时顺手记了一个很小的判断：如果一个工具只是在网页里多开几个聊天窗口，那它对 coding agent 的帮助有限；如果它能回答“谁在跑、跑到哪、改了什么、失败后能不能复盘”，它才像是在管理执行单元。Multica 比较接近后者。&lt;/p&gt;
&lt;h2 id="我比较在意的点"&gt;&lt;a href="#%e6%88%91%e6%af%94%e8%be%83%e5%9c%a8%e6%84%8f%e7%9a%84%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;我比较在意的点
&lt;/h2&gt;&lt;p&gt;我比较在意的一点是：Multica 并不是简单把所有东西都丢到云端跑。官方文档里说，真正执行任务的是本地 daemon。Multica server 负责 workspace、issue、成员、任务队列这些数据；daemon 跑在你自己的机器上，去调用本地安装的 Claude Code、Codex、Copilot CLI 等工具。&lt;/p&gt;
&lt;p&gt;这个设计对我来说挺关键。coding agent 最敏感的地方不是“问答内容”，而是它会读代码、跑命令、改文件。如果一个平台只是提供任务面板，但实际执行仍然在本地，那接受成本会低很多。&lt;/p&gt;
&lt;p&gt;当然这不代表完全没有风险。agent 能做什么、工具权限怎么给、仓库怎么隔离，还是要自己控制。&lt;/p&gt;
&lt;p&gt;从 provider matrix 看，Multica 对不同 coding tool 的支持不是完全一样的。Claude Code、Codex、OpenCode 在 MCP 支持、skill 注入路径、session resumption 上都有差异。这个地方我觉得文档写得比较实在，没有假装所有工具都能做到同一个水平。&lt;/p&gt;
&lt;p&gt;我会特别看这些差异，而不是只看“支持多少 agent”：&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;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;session resumption&lt;/td&gt;
					&lt;td&gt;agent 中途断了以后能不能接着跑，不然长任务很容易废掉&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MCP support&lt;/td&gt;
					&lt;td&gt;能不能接公司内部工具，而不是只靠本地 shell&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;skill injection&lt;/td&gt;
					&lt;td&gt;能不能把团队约定、代码规范、常用命令放进 agent 上下文&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;task history&lt;/td&gt;
					&lt;td&gt;失败任务能不能复盘，而不是只剩一句“没做好”&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="如果我要试"&gt;&lt;a href="#%e5%a6%82%e6%9e%9c%e6%88%91%e8%a6%81%e8%af%95" class="header-anchor"&gt;&lt;/a&gt;如果我要试
&lt;/h2&gt;&lt;p&gt;如果要在项目里试，我大概会先按这个方式用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-2"&gt;&lt;a class="lnlinks" href="#hl-0-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-3"&gt;&lt;a class="lnlinks" href="#hl-0-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;multica daemon start
&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;multica setup self-host
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后给不同 agent 分清职责，而不是所有任务都丢给一个“全能助手”：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-1-1"&gt;&lt;a class="lnlinks" href="#hl-1-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-2"&gt;&lt;a class="lnlinks" href="#hl-1-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-3"&gt;&lt;a class="lnlinks" href="#hl-1-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;codex-fixer 小范围代码修复和测试
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude-reviewer review、解释设计风险
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;opencode-docs 文档草稿和命令整理
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;我现在对这类工具的看法是：不要指望它让 agent 变聪明。它真正有价值的地方是让 agent 的工作可排队、可观察、可追溯。&lt;/p&gt;
&lt;p&gt;终端里跑 agent 最大的问题是过程容易丢。Multica 把一次运行抽象成 task，有 queued、dispatched、running、completed、failed 这类状态，也有重试和历史记录。这样你至少知道它是没开始、跑挂了，还是已经完成只是结果不理想。&lt;/p&gt;
&lt;h2 id="我不会一上来就接正式仓库"&gt;&lt;a href="#%e6%88%91%e4%b8%8d%e4%bc%9a%e4%b8%80%e4%b8%8a%e6%9d%a5%e5%b0%b1%e6%8e%a5%e6%ad%a3%e5%bc%8f%e4%bb%93%e5%ba%93" class="header-anchor"&gt;&lt;/a&gt;我不会一上来就接正式仓库
&lt;/h2&gt;&lt;p&gt;更合理的方式是先拿一个 side project 或内部工具试：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;只给只读或低风险仓库。&lt;/li&gt;
&lt;li&gt;agent 默认不要有生产凭证。&lt;/li&gt;
&lt;li&gt;每个任务只允许小范围修改。&lt;/li&gt;
&lt;li&gt;输出必须经过人工 review。&lt;/li&gt;
&lt;li&gt;不要把“能跑起来”误认为“能放心用”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Multica 这类项目让我感觉到一个趋势：coding agent 正在从“单次对话工具”变成“可管理的执行单元”。以前我们关心的是 prompt 怎么写，现在更关心任务怎么派、权限怎么控、结果怎么验收。&lt;/p&gt;
&lt;p&gt;这篇先记到这里。它不是那种看完文档就能断言成熟的工具，但方向我觉得值得关注。尤其是团队里已经有人混用 Codex、Claude Code、Copilot CLI 的时候，与其每个人各玩各的，不如有一个地方把任务和 agent 统一管起来。&lt;/p&gt;
&lt;p&gt;我暂时不会把它写成“推荐大家马上上”。更准确的评价是：如果你的团队还没有稳定使用 coding agent，它不是第一优先级；如果已经有人同时开好几个 agent 做修复、review、文档和测试，它解决的就是混乱本身。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://multica.ai/docs" target="_blank" rel="noopener"
 &gt;Multica Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://multica.ai/docs/how-multica-works" target="_blank" rel="noopener"
 &gt;How Multica works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://multica.ai/docs/tasks" target="_blank" rel="noopener"
 &gt;Multica Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://multica.ai/docs/providers" target="_blank" rel="noopener"
 &gt;Multica AI coding tools matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/multica-ai/multica" target="_blank" rel="noopener"
 &gt;multica-ai/multica&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>MCP 工具接入后的 Agent 设计：别只会调工具，还要能兜底</title><link>/dkblog/031-mcp-agent-tool-fallback/</link><pubDate>Thu, 19 Feb 2026 23:08:00 +0800</pubDate><guid>/dkblog/031-mcp-agent-tool-fallback/</guid><description>&lt;img src="https://images.unsplash.com/photo-1639322537228-f710d846310a?auto=format&amp;fit=crop&amp;w=900&amp;q=60" alt="Featured image of post MCP 工具接入后的 Agent 设计：别只会调工具，还要能兜底" /&gt;&lt;p&gt;最近接 MCP 工具时有个感受：让 agent 能调工具其实不难，难的是工具出错以后它怎么收场。&lt;/p&gt;
&lt;p&gt;很多 demo 都停在“模型发现工具，然后调用工具，最后返回结果”。这个流程当然重要，但一到真实项目里，问题马上会变多：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工具参数不完整。&lt;/li&gt;
&lt;li&gt;MCP server 连不上。&lt;/li&gt;
&lt;li&gt;工具返回了业务错误。&lt;/li&gt;
&lt;li&gt;工具返回内容太长。&lt;/li&gt;
&lt;li&gt;工具结果里混了不该给模型看的内容。&lt;/li&gt;
&lt;li&gt;用户问的是高风险操作，但 agent 没有停下来确认。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以我现在更愿意把 MCP 看成一个工具协议，而不是完整的 agent 方案。MCP 解决的是“工具怎么暴露、怎么发现、怎么调用”。但 agent 怎么规划、怎么重试、怎么审批、怎么记录，这些还是应用自己要设计。&lt;/p&gt;
&lt;p&gt;我踩到的第一个误区就是：工具列表一多，agent 看起来就“能力很强”。但真实情况是，工具越多，出错路径也越多。尤其是工具之间有依赖的时候，模型一次错误调用可能会把后面的上下文都带偏。&lt;/p&gt;
&lt;h2 id="失败要分类"&gt;&lt;a href="#%e5%a4%b1%e8%b4%a5%e8%a6%81%e5%88%86%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;失败要分类
&lt;/h2&gt;&lt;p&gt;MCP 官方规范里，tool 是 server 暴露给模型调用的能力。每个 tool 有 &lt;code&gt;name&lt;/code&gt;、&lt;code&gt;description&lt;/code&gt;、&lt;code&gt;inputSchema&lt;/code&gt;，新版本里还可以有 &lt;code&gt;outputSchema&lt;/code&gt;。client 可以通过 &lt;code&gt;tools/list&lt;/code&gt; 发现工具，通过 &lt;code&gt;tools/call&lt;/code&gt; 调用工具。&lt;/p&gt;
&lt;p&gt;工具失败不能都当成一种失败。我比较喜欢分成四类：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-2"&gt;&lt;a class="lnlinks" href="#hl-0-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-3"&gt;&lt;a class="lnlinks" href="#hl-0-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-4"&gt;&lt;a class="lnlinks" href="#hl-0-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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. 参数错误：模型传错了，应该让模型修正参数后重试
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 临时错误：网络、限流、超时，可以做有限重试
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 权限错误：不要重试，直接提示需要授权或人工处理
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. 业务错误：工具正常执行了，但结果不满足条件，需要解释给用户
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果不分这些类型，agent 很容易出现两种问题：要么无限重试，要么一失败就胡编。&lt;/p&gt;
&lt;p&gt;一个比较朴素的工具调用兜底逻辑可以这样设计：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-1-1"&gt;&lt;a class="lnlinks" href="#hl-1-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-2"&gt;&lt;a class="lnlinks" href="#hl-1-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-3"&gt;&lt;a class="lnlinks" href="#hl-1-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-4"&gt;&lt;a class="lnlinks" href="#hl-1-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-5"&gt;&lt;a class="lnlinks" href="#hl-1-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-6"&gt;&lt;a class="lnlinks" href="#hl-1-6"&gt;6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-7"&gt;&lt;a class="lnlinks" href="#hl-1-7"&gt;7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-8"&gt;&lt;a class="lnlinks" href="#hl-1-8"&gt;8&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;用户请求
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 判断是否需要工具
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 检查工具是否允许在当前场景使用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 调用工具
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 校验结果结构
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 如果失败，按失败类型处理
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 如果是敏感操作，等待人工确认
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 汇总结果，明确哪些是工具返回，哪些是推断
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里我会强制 agent 在最终回答里暴露两类信息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-2"&gt;&lt;a class="lnlinks" href="#hl-2-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;已确认：来自工具返回、日志、数据库或 API 的事实
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;推测：根据事实做出的判断，需要继续验证
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这个写法有点啰嗦，但对排障类 agent 很有用。最怕的是模型把推测写成事实，用户再拿这个“事实”去改线上配置。&lt;/p&gt;
&lt;h2 id="敏感工具不要让模型自己决定"&gt;&lt;a href="#%e6%95%8f%e6%84%9f%e5%b7%a5%e5%85%b7%e4%b8%8d%e8%a6%81%e8%ae%a9%e6%a8%a1%e5%9e%8b%e8%87%aa%e5%b7%b1%e5%86%b3%e5%ae%9a" class="header-anchor"&gt;&lt;/a&gt;敏感工具不要让模型自己决定
&lt;/h2&gt;&lt;p&gt;这里面最容易被忽略的是“工具是否允许在当前场景使用”。MCP 规范也强调了 trust &amp;amp; safety：客户端应该让用户知道哪些工具暴露给了模型，敏感操作前应该有确认。&lt;/p&gt;
&lt;p&gt;我自己的倾向是把 MCP 工具分级：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-3-1"&gt;&lt;a class="lnlinks" href="#hl-3-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-2"&gt;&lt;a class="lnlinks" href="#hl-3-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-3"&gt;&lt;a class="lnlinks" href="#hl-3-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-4"&gt;&lt;a class="lnlinks" href="#hl-3-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;read_only 只读查询，比如查文档、查资源、查状态
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;low_risk 可写但影响小，比如创建草稿、生成 issue
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dangerous 可能改配置、删数据、执行命令
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;external 会访问外部系统或带来数据外发风险
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后给 agent 一个硬规则：&lt;code&gt;dangerous&lt;/code&gt; 和 &lt;code&gt;external&lt;/code&gt; 默认必须人工确认，不能让模型自己判断“应该没事”。&lt;/p&gt;
&lt;p&gt;我还会给工具加一个“是否可回滚”的维度。比如创建一条草稿 issue 是低风险，因为删掉就行；修改线上 ConfigMap 也许看起来只是一个字段，但一旦触发 rollout，影响就不是工具自己能兜住的。&lt;/p&gt;
&lt;h2 id="trace-比想象中重要"&gt;&lt;a href="#trace-%e6%af%94%e6%83%b3%e8%b1%a1%e4%b8%ad%e9%87%8d%e8%a6%81" class="header-anchor"&gt;&lt;/a&gt;trace 比想象中重要
&lt;/h2&gt;&lt;p&gt;如果用 OpenAI Agents SDK，它本身也提供了不少跟工程化有关的东西，比如 MCP servers、guardrails、handoffs、tracing。这里我觉得 tracing 很重要，因为 agent 一旦开始多步调用工具，事后没有 trace 基本没法排查。&lt;/p&gt;
&lt;p&gt;一次完整 trace 至少要能回答几个问题：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-4-1"&gt;&lt;a class="lnlinks" href="#hl-4-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-2"&gt;&lt;a class="lnlinks" href="#hl-4-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-3"&gt;&lt;a class="lnlinks" href="#hl-4-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-4"&gt;&lt;a class="lnlinks" href="#hl-4-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-5"&gt;&lt;a class="lnlinks" href="#hl-4-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-6"&gt;&lt;a class="lnlinks" href="#hl-4-6"&gt;6&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;用户原始输入是什么？
&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&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;中间有没有 guardrail 拦截？
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;最终答案里哪些来自工具，哪些是模型总结？
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;另外，MCP transport 也要注意。stdio 适合本地工具，client 拉起子进程，通过 stdin/stdout 通信；Streamable HTTP 适合远程或服务化工具。HTTP 场景下要考虑认证、Origin 校验和服务绑定地址。&lt;/p&gt;
&lt;p&gt;我现在更愿意把 MCP server 写得“笨”一点：输入严格校验，输出结构稳定，错误信息清楚，不在工具里偷偷做太多推理。推理留给 agent，工具只负责可靠地拿数据或执行动作。&lt;/p&gt;
&lt;p&gt;比如一个查询 Kubernetes pod 的 MCP tool，返回最好是这种结构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-5-1"&gt;&lt;a class="lnlinks" href="#hl-5-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-2"&gt;&lt;a class="lnlinks" href="#hl-5-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-3"&gt;&lt;a class="lnlinks" href="#hl-5-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-4"&gt;&lt;a class="lnlinks" href="#hl-5-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-5"&gt;&lt;a class="lnlinks" href="#hl-5-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-6"&gt;&lt;a class="lnlinks" href="#hl-5-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-7"&gt;&lt;a class="lnlinks" href="#hl-5-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-8"&gt;&lt;a class="lnlinks" href="#hl-5-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-9"&gt;&lt;a class="lnlinks" href="#hl-5-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-10"&gt;&lt;a class="lnlinks" href="#hl-5-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-11"&gt;&lt;a class="lnlinks" href="#hl-5-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-12"&gt;&lt;a class="lnlinks" href="#hl-5-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-13"&gt;&lt;a class="lnlinks" href="#hl-5-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-14"&gt;&lt;a class="lnlinks" href="#hl-5-14"&gt;14&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&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="nt"&gt;&amp;#34;namespace&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&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="nt"&gt;&amp;#34;pods&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="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;api-7f8d7c9b8d-abcde&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="nt"&gt;&amp;#34;phase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Running&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="nt"&gt;&amp;#34;restartCount&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;worker-1&amp;#34;&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="nt"&gt;&amp;#34;warnings&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;restartCount &amp;gt; 0, check container logs&amp;#34;&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;不要直接返回一大段混杂的自然语言。自然语言看起来顺，但后续不好校验，也不好让 agent 做稳定判断。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;接 MCP 以后我会重点看这些东西：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工具有没有清晰的 inputSchema 和 outputSchema。&lt;/li&gt;
&lt;li&gt;工具失败有没有分类型。&lt;/li&gt;
&lt;li&gt;是否设置超时、限流和审计日志。&lt;/li&gt;
&lt;li&gt;敏感工具有没有人工确认。&lt;/li&gt;
&lt;li&gt;工具结果进模型前有没有校验和脱敏。&lt;/li&gt;
&lt;li&gt;有没有 trace 能复盘每一步。&lt;/li&gt;
&lt;li&gt;agent 最终回答有没有区分事实和推断。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MCP 的价值是把工具接入标准化，但它不会自动让 agent 变可靠。真正影响线上可用性的，往往是这些不太显眼的兜底逻辑。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools" target="_blank" rel="noopener"
 &gt;MCP Tools 规范&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports" target="_blank" rel="noopener"
 &gt;MCP Transports 规范&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization" target="_blank" rel="noopener"
 &gt;MCP Authorization 规范&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://developers.openai.com/api/docs/guides/agents" target="_blank" rel="noopener"
 &gt;OpenAI Agents SDK Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://openai.github.io/openai-agents-python/mcp/" target="_blank" rel="noopener"
 &gt;OpenAI Agents Python SDK MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://openai.github.io/openai-agents-python/tracing/" target="_blank" rel="noopener"
 &gt;OpenAI Agents SDK Tracing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>kagent：在 Kubernetes 里跑一个完整的 AI Agent Case</title><link>/dkblog/023-kagent-kubernetes-ai-agent/</link><pubDate>Thu, 13 Nov 2025 21:34:00 +0800</pubDate><guid>/dkblog/023-kagent-kubernetes-ai-agent/</guid><description>&lt;img src="https://images.unsplash.com/photo-1544197150-b99a580bb7a8?auto=format&amp;fit=crop&amp;w=900&amp;q=60" alt="Featured image of post kagent：在 Kubernetes 里跑一个完整的 AI Agent Case" /&gt;&lt;p&gt;前面看 kagent 的时候，我只把它当成一个 Kubernetes native 的 Agent 框架来理解。后来再翻官方文档，发现更值得记录的是完整落地链路：CRD 怎么装，controller 怎么起来，模型配置怎么声明，MCPServer 怎么接进来，最后 Agent 怎么引用这些东西。&lt;/p&gt;
&lt;p&gt;这篇就按一个完整 case 记下来。目标不是让 agent 直接改生产集群，而是先做一个低风险的“集群观察 + 网页 fetch”助手：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以调用 kagent 内置 Kubernetes 工具查资源。&lt;/li&gt;
&lt;li&gt;可以通过一个 MCPServer 抓取网页内容。&lt;/li&gt;
&lt;li&gt;Agent 使用显式的 &lt;code&gt;ModelConfig&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;所有对象都用 YAML 表达，方便放进 GitOps。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="这个-case-里会出现哪些对象"&gt;&lt;a href="#%e8%bf%99%e4%b8%aa-case-%e9%87%8c%e4%bc%9a%e5%87%ba%e7%8e%b0%e5%93%aa%e4%ba%9b%e5%af%b9%e8%b1%a1" class="header-anchor"&gt;&lt;/a&gt;这个 case 里会出现哪些对象
&lt;/h2&gt;&lt;p&gt;先把关系画清楚：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-2"&gt;&lt;a class="lnlinks" href="#hl-0-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-3"&gt;&lt;a class="lnlinks" href="#hl-0-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-4"&gt;&lt;a class="lnlinks" href="#hl-0-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-5"&gt;&lt;a class="lnlinks" href="#hl-0-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-6"&gt;&lt;a class="lnlinks" href="#hl-0-6"&gt;6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-7"&gt;&lt;a class="lnlinks" href="#hl-0-7"&gt;7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-8"&gt;&lt;a class="lnlinks" href="#hl-0-8"&gt;8&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;Secret
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; ModelConfig
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; Agent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; RemoteMCPServer: kagent-tool-server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; MCPServer: mcp-website-fetcher
&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;kagent CRDs + kagent controller
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kmcp CRDs + kmcp controller
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里有两个容易混的对象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RemoteMCPServer&lt;/code&gt;：kagent 里用来描述远程 MCP 服务的资源，内置的 &lt;code&gt;kagent-tool-server&lt;/code&gt; 就属于这一类。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MCPServer&lt;/code&gt;：kmcp 管理的 MCP server 资源，可以把一个 stdio MCP server 包成 Kubernetes 里的服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;官方文档里的 First MCP Tool 示例就是用 &lt;code&gt;MCPServer&lt;/code&gt; 起了一个 fetch 工具，然后 Agent 通过 &lt;code&gt;tools&lt;/code&gt; 引用它。&lt;/p&gt;
&lt;h2 id="1-安装-crd-和-controller"&gt;&lt;a href="#1-%e5%ae%89%e8%a3%85-crd-%e5%92%8c-controller" class="header-anchor"&gt;&lt;/a&gt;1. 安装 CRD 和 controller
&lt;/h2&gt;&lt;p&gt;我更偏向用 Helm 装，因为这套东西更适合放到平台环境里，不只是本地 demo。&lt;/p&gt;
&lt;p&gt;先装 kagent CRD：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-1-1"&gt;&lt;a class="lnlinks" href="#hl-1-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-2"&gt;&lt;a class="lnlinks" href="#hl-1-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-3"&gt;&lt;a class="lnlinks" href="#hl-1-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm install kagent-crds oci://ghcr.io/kagent-dev/kagent/helm/kagent-crds &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace kagent &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --create-namespace
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;再装 kagent controller 和 UI。这里用 OpenAI provider 举例，实际项目里可以换 Anthropic、Gemini、Azure OpenAI、Ollama 等。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-2"&gt;&lt;a class="lnlinks" href="#hl-2-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-3"&gt;&lt;a class="lnlinks" href="#hl-2-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-4"&gt;&lt;a class="lnlinks" href="#hl-2-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-5"&gt;&lt;a class="lnlinks" href="#hl-2-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-6"&gt;&lt;a class="lnlinks" href="#hl-2-6"&gt;6&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;your-api-key&amp;#34;&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;helm install kagent oci://ghcr.io/kagent-dev/kagent/helm/kagent &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace kagent &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set providers.default&lt;span class="o"&gt;=&lt;/span&gt;openAI &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set providers.openAI.apiKey&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;官方安装文档里提到，kagent 0.7 之后默认包含 kmcp 子项目。如果你已经单独装了 kmcp，可以通过 chart 配置关掉内置 kmcp。对第一次试用来说，先用默认就行。&lt;/p&gt;
&lt;p&gt;确认 CRD 和 controller：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-3-1"&gt;&lt;a class="lnlinks" href="#hl-3-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-2"&gt;&lt;a class="lnlinks" href="#hl-3-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-3"&gt;&lt;a class="lnlinks" href="#hl-3-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-4"&gt;&lt;a class="lnlinks" href="#hl-3-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get crd &lt;span class="p"&gt;|&lt;/span&gt; grep kagent.dev
&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;kubectl get pods -n kagent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get svc -n kagent
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果要单独安装 kmcp controller，官方文档给的是这条线：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-4-1"&gt;&lt;a class="lnlinks" href="#hl-4-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-2"&gt;&lt;a class="lnlinks" href="#hl-4-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-3"&gt;&lt;a class="lnlinks" href="#hl-4-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-4"&gt;&lt;a class="lnlinks" href="#hl-4-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-5"&gt;&lt;a class="lnlinks" href="#hl-4-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-6"&gt;&lt;a class="lnlinks" href="#hl-4-6"&gt;6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-7"&gt;&lt;a class="lnlinks" href="#hl-4-7"&gt;7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-8"&gt;&lt;a class="lnlinks" href="#hl-4-8"&gt;8&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm install kmcp-crds oci://ghcr.io/kagent-dev/kmcp/helm/kmcp-crds &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace kmcp-system &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --create-namespace
&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;kmcp install
&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;kubectl get pods -n kmcp-system
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl logs -l app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;kmcp -n kmcp-system
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里要注意：不要在同一个集群里重复装两套 kmcp controller。要么用 kagent 默认带的，要么明确自己单独安装并在 kagent chart 里关掉。&lt;/p&gt;
&lt;h2 id="2-准备模型-secret-和-modelconfig"&gt;&lt;a href="#2-%e5%87%86%e5%a4%87%e6%a8%a1%e5%9e%8b-secret-%e5%92%8c-modelconfig" class="header-anchor"&gt;&lt;/a&gt;2. 准备模型 Secret 和 ModelConfig
&lt;/h2&gt;&lt;p&gt;Helm 安装时如果设置了 provider，通常会带默认配置。为了把流程讲完整，我这里单独写一个 &lt;code&gt;ModelConfig&lt;/code&gt;，后续 Agent 明确引用它。&lt;/p&gt;
&lt;p&gt;先创建 API key secret：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-5-1"&gt;&lt;a class="lnlinks" href="#hl-5-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-2"&gt;&lt;a class="lnlinks" href="#hl-5-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl -n kagent create secret generic openai-api-key &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --from-literal&lt;span class="o"&gt;=&lt;/span&gt;api-key&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后创建 &lt;code&gt;ModelConfig&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-6-1"&gt;&lt;a class="lnlinks" href="#hl-6-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-2"&gt;&lt;a class="lnlinks" href="#hl-6-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-3"&gt;&lt;a class="lnlinks" href="#hl-6-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-4"&gt;&lt;a class="lnlinks" href="#hl-6-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-5"&gt;&lt;a class="lnlinks" href="#hl-6-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-6"&gt;&lt;a class="lnlinks" href="#hl-6-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-7"&gt;&lt;a class="lnlinks" href="#hl-6-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-8"&gt;&lt;a class="lnlinks" href="#hl-6-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-9"&gt;&lt;a class="lnlinks" href="#hl-6-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-10"&gt;&lt;a class="lnlinks" href="#hl-6-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-11"&gt;&lt;a class="lnlinks" href="#hl-6-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-12"&gt;&lt;a class="lnlinks" href="#hl-6-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-13"&gt;&lt;a class="lnlinks" href="#hl-6-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-14"&gt;&lt;a class="lnlinks" href="#hl-6-14"&gt;14&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kagent.dev/v1alpha2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ModelConfig&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ops-openai-model&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kagent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;OpenAI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;lt;your-openai-model&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;apiKeySecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;openai-api-key&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;apiKeySecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;api-key&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;openAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.2&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;我这里把 &lt;code&gt;model&lt;/code&gt; 写成占位符，是因为不同账号、网关或代理支持的模型可能不一样。实际落地时要改成你环境里可用的模型名。&lt;/p&gt;
&lt;p&gt;应用并检查：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-7-1"&gt;&lt;a class="lnlinks" href="#hl-7-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-7-2"&gt;&lt;a class="lnlinks" href="#hl-7-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-7-3"&gt;&lt;a class="lnlinks" href="#hl-7-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-7-4"&gt;&lt;a class="lnlinks" href="#hl-7-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f modelconfig.yaml
&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;kubectl get modelconfig -n kagent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe modelconfig ops-openai-model -n kagent
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;ModelConfig&lt;/code&gt; 里有几个字段比较关键：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;provider&lt;/code&gt;：模型提供方，API 里支持 OpenAI、Anthropic、AzureOpenAI、Ollama、Gemini、Bedrock 等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;：具体模型名。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apiKeySecret&lt;/code&gt; / &lt;code&gt;apiKeySecretKey&lt;/code&gt;：引用同 namespace 里的 Secret。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openAI&lt;/code&gt;：OpenAI provider 的额外参数，比如 temperature、maxTokens、timeout。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个对象的价值是把模型配置从 Agent 里拆出来。多个 Agent 可以引用同一个 &lt;code&gt;ModelConfig&lt;/code&gt;，模型切换也更清楚。&lt;/p&gt;
&lt;h2 id="3-创建一个-mcpserver网页-fetch-工具"&gt;&lt;a href="#3-%e5%88%9b%e5%bb%ba%e4%b8%80%e4%b8%aa-mcpserver%e7%bd%91%e9%a1%b5-fetch-%e5%b7%a5%e5%85%b7" class="header-anchor"&gt;&lt;/a&gt;3. 创建一个 MCPServer：网页 fetch 工具
&lt;/h2&gt;&lt;p&gt;接下来创建一个最小 MCPServer。官方 First MCP Tool 文档里用的是 &lt;code&gt;mcp-server-fetch&lt;/code&gt;，通过 &lt;code&gt;uvx&lt;/code&gt; 启动 stdio MCP server，再由 kmcp controller 管理它的生命周期。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-8-1"&gt;&lt;a class="lnlinks" href="#hl-8-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-2"&gt;&lt;a class="lnlinks" href="#hl-8-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-3"&gt;&lt;a class="lnlinks" href="#hl-8-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-4"&gt;&lt;a class="lnlinks" href="#hl-8-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-5"&gt;&lt;a class="lnlinks" href="#hl-8-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-6"&gt;&lt;a class="lnlinks" href="#hl-8-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-7"&gt;&lt;a class="lnlinks" href="#hl-8-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-8"&gt;&lt;a class="lnlinks" href="#hl-8-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-9"&gt;&lt;a class="lnlinks" href="#hl-8-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-10"&gt;&lt;a class="lnlinks" href="#hl-8-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-11"&gt;&lt;a class="lnlinks" href="#hl-8-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-12"&gt;&lt;a class="lnlinks" href="#hl-8-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-13"&gt;&lt;a class="lnlinks" href="#hl-8-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kagent.dev/v1alpha1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MCPServer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mcp-website-fetcher&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kagent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deployment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;uvx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;mcp-server-fetch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stdioTransport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;transportType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;stdio&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;应用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-9-1"&gt;&lt;a class="lnlinks" href="#hl-9-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f mcp-website-fetcher.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;检查状态：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-10-1"&gt;&lt;a class="lnlinks" href="#hl-10-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-10-2"&gt;&lt;a class="lnlinks" href="#hl-10-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-10-3"&gt;&lt;a class="lnlinks" href="#hl-10-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-10-4"&gt;&lt;a class="lnlinks" href="#hl-10-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get mcpserver -n kagent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe mcpserver mcp-website-fetcher -n kagent
&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;kubectl get pods -n kagent &lt;span class="p"&gt;|&lt;/span&gt; grep mcp-website-fetcher
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果 MCPServer 没起来，我会先看 kmcp controller 日志，而不是直接怀疑 Agent：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-11-1"&gt;&lt;a class="lnlinks" href="#hl-11-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl logs -l app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;kmcp -n kmcp-system
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果你用的是 kagent chart 内置 kmcp，controller 的 namespace 和 label 可能跟独立安装不同，直接用下面的方式先找：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-12-1"&gt;&lt;a class="lnlinks" href="#hl-12-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-12-2"&gt;&lt;a class="lnlinks" href="#hl-12-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pods -A &lt;span class="p"&gt;|&lt;/span&gt; grep -i kmcp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get deploy -A &lt;span class="p"&gt;|&lt;/span&gt; grep -i kmcp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="4-创建-agent同时引用-kubernetes-工具和-fetch-工具"&gt;&lt;a href="#4-%e5%88%9b%e5%bb%ba-agent%e5%90%8c%e6%97%b6%e5%bc%95%e7%94%a8-kubernetes-%e5%b7%a5%e5%85%b7%e5%92%8c-fetch-%e5%b7%a5%e5%85%b7" class="header-anchor"&gt;&lt;/a&gt;4. 创建 Agent：同时引用 Kubernetes 工具和 fetch 工具
&lt;/h2&gt;&lt;p&gt;现在创建 Agent。这个 Agent 做两件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用内置 &lt;code&gt;kagent-tool-server&lt;/code&gt; 查 Kubernetes 资源。&lt;/li&gt;
&lt;li&gt;用刚才的 &lt;code&gt;mcp-website-fetcher&lt;/code&gt; 抓网页内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-13-1"&gt;&lt;a class="lnlinks" href="#hl-13-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-2"&gt;&lt;a class="lnlinks" href="#hl-13-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-3"&gt;&lt;a class="lnlinks" href="#hl-13-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-4"&gt;&lt;a class="lnlinks" href="#hl-13-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-5"&gt;&lt;a class="lnlinks" href="#hl-13-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-6"&gt;&lt;a class="lnlinks" href="#hl-13-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-7"&gt;&lt;a class="lnlinks" href="#hl-13-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-8"&gt;&lt;a class="lnlinks" href="#hl-13-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-9"&gt;&lt;a class="lnlinks" href="#hl-13-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-10"&gt;&lt;a class="lnlinks" href="#hl-13-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-11"&gt;&lt;a class="lnlinks" href="#hl-13-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-12"&gt;&lt;a class="lnlinks" href="#hl-13-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-13"&gt;&lt;a class="lnlinks" href="#hl-13-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-14"&gt;&lt;a class="lnlinks" href="#hl-13-14"&gt;14&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-15"&gt;&lt;a class="lnlinks" href="#hl-13-15"&gt;15&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-16"&gt;&lt;a class="lnlinks" href="#hl-13-16"&gt;16&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-17"&gt;&lt;a class="lnlinks" href="#hl-13-17"&gt;17&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-18"&gt;&lt;a class="lnlinks" href="#hl-13-18"&gt;18&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-19"&gt;&lt;a class="lnlinks" href="#hl-13-19"&gt;19&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-20"&gt;&lt;a class="lnlinks" href="#hl-13-20"&gt;20&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-21"&gt;&lt;a class="lnlinks" href="#hl-13-21"&gt;21&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-22"&gt;&lt;a class="lnlinks" href="#hl-13-22"&gt;22&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-23"&gt;&lt;a class="lnlinks" href="#hl-13-23"&gt;23&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-24"&gt;&lt;a class="lnlinks" href="#hl-13-24"&gt;24&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-25"&gt;&lt;a class="lnlinks" href="#hl-13-25"&gt;25&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-26"&gt;&lt;a class="lnlinks" href="#hl-13-26"&gt;26&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-27"&gt;&lt;a class="lnlinks" href="#hl-13-27"&gt;27&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-28"&gt;&lt;a class="lnlinks" href="#hl-13-28"&gt;28&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-29"&gt;&lt;a class="lnlinks" href="#hl-13-29"&gt;29&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-30"&gt;&lt;a class="lnlinks" href="#hl-13-30"&gt;30&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-31"&gt;&lt;a class="lnlinks" href="#hl-13-31"&gt;31&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-32"&gt;&lt;a class="lnlinks" href="#hl-13-32"&gt;32&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-33"&gt;&lt;a class="lnlinks" href="#hl-13-33"&gt;33&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-34"&gt;&lt;a class="lnlinks" href="#hl-13-34"&gt;34&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-35"&gt;&lt;a class="lnlinks" href="#hl-13-35"&gt;35&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-36"&gt;&lt;a class="lnlinks" href="#hl-13-36"&gt;36&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-37"&gt;&lt;a class="lnlinks" href="#hl-13-37"&gt;37&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-38"&gt;&lt;a class="lnlinks" href="#hl-13-38"&gt;38&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-39"&gt;&lt;a class="lnlinks" href="#hl-13-39"&gt;39&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-40"&gt;&lt;a class="lnlinks" href="#hl-13-40"&gt;40&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-41"&gt;&lt;a class="lnlinks" href="#hl-13-41"&gt;41&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-42"&gt;&lt;a class="lnlinks" href="#hl-13-42"&gt;42&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-43"&gt;&lt;a class="lnlinks" href="#hl-13-43"&gt;43&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-13-44"&gt;&lt;a class="lnlinks" href="#hl-13-44"&gt;44&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kagent.dev/v1alpha2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Agent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ops-readonly-agent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kagent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; A read-only Kubernetes operations assistant. It can inspect Kubernetes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; resources and fetch documentation pages, but it must not perform write
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; operations.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Declarative&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;declarative&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;modelConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ops-openai-model&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;systemMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|-&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; You are a Kubernetes operations assistant.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Rules:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - Only use read-only tools.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - Do not make up cluster state.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - If a tool result is missing or ambiguous, say so.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - Separate confirmed facts from guesses.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - Never suggest deleting or modifying resources without human review.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Response format:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - Summary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - Evidence from tools
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - Possible causes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - Next checks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;McpServer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mcpServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;apiGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kagent.dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RemoteMCPServer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kagent-tool-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;toolNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;k8s_get_available_api_resources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;k8s_get_resources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;McpServer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mcpServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MCPServer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mcp-website-fetcher&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;toolNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;fetch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;应用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-14-1"&gt;&lt;a class="lnlinks" href="#hl-14-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f ops-readonly-agent.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;检查 Agent：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-15-1"&gt;&lt;a class="lnlinks" href="#hl-15-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-15-2"&gt;&lt;a class="lnlinks" href="#hl-15-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-15-3"&gt;&lt;a class="lnlinks" href="#hl-15-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get agent -n kagent
&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;kubectl get agent ops-readonly-agent -n kagent -o yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;官方示例里 Agent 的 status 会出现类似 &lt;code&gt;Accepted=True&lt;/code&gt;、&lt;code&gt;Ready=True&lt;/code&gt; 的 condition。如果这里不 Ready，优先看三类对象：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-16-1"&gt;&lt;a class="lnlinks" href="#hl-16-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-16-2"&gt;&lt;a class="lnlinks" href="#hl-16-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-16-3"&gt;&lt;a class="lnlinks" href="#hl-16-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get modelconfig ops-openai-model -n kagent -o yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get mcpserver mcp-website-fetcher -n kagent -o yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get agent ops-readonly-agent -n kagent -o yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;我会按这个顺序查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ModelConfig&lt;/code&gt; 是否能读到 secret。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MCPServer&lt;/code&gt; 是否已经被 kmcp controller 拉起来。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Agent&lt;/code&gt; 引用的 tool 名称是否存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Agent&lt;/code&gt; 的 namespace 是否能引用对应 MCP server。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="5-打开-ui-验证"&gt;&lt;a href="#5-%e6%89%93%e5%bc%80-ui-%e9%aa%8c%e8%af%81" class="header-anchor"&gt;&lt;/a&gt;5. 打开 UI 验证
&lt;/h2&gt;&lt;p&gt;本地打开 kagent UI：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-17-1"&gt;&lt;a class="lnlinks" href="#hl-17-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl port-forward -n kagent svc/kagent-ui 8080:8080
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后访问：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-18-1"&gt;&lt;a class="lnlinks" href="#hl-18-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;http://localhost:8080
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;可以先问几个低风险问题：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-19-1"&gt;&lt;a class="lnlinks" href="#hl-19-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;列出当前集群里可以查询的 Kubernetes API resources。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;再问一个会用到 fetch 的问题：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-20-1"&gt;&lt;a class="lnlinks" href="#hl-20-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;帮我读取 https://kagent.dev/docs/kagent/getting-started/first-mcp-tool ，总结创建 MCPServer 和 Agent 的关键步骤。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果 Agent 正常，它应该会调用对应工具，而不是直接编造结果。这个时候我会重点看 UI 里有没有展示 tool call、参数和返回内容。kagent 这类工具的价值不只是回答，还在于能不能复盘它怎么回答的。&lt;/p&gt;
&lt;h2 id="6-权限边界我不会一开始就给写权限"&gt;&lt;a href="#6-%e6%9d%83%e9%99%90%e8%be%b9%e7%95%8c%e6%88%91%e4%b8%8d%e4%bc%9a%e4%b8%80%e5%bc%80%e5%a7%8b%e5%b0%b1%e7%bb%99%e5%86%99%e6%9d%83%e9%99%90" class="header-anchor"&gt;&lt;/a&gt;6. 权限边界：我不会一开始就给写权限
&lt;/h2&gt;&lt;p&gt;这个 case 里我故意只选了只读工具。原因很简单：Agent 最容易出事故的不是回答错，而是拿着写权限执行错。&lt;/p&gt;
&lt;p&gt;如果未来要开放写操作，我会分三层：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-21-1"&gt;&lt;a class="lnlinks" href="#hl-21-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-21-2"&gt;&lt;a class="lnlinks" href="#hl-21-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-21-3"&gt;&lt;a class="lnlinks" href="#hl-21-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;read_only get/list/watch/logs/events
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;reviewed 生成 YAML、生成 patch，但不 apply
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;approved 人工确认后才允许执行写操作
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;kagent 的 Tool 配置里支持 &lt;code&gt;requireApproval&lt;/code&gt;，可以把敏感工具列进去，让工具调用前停下来等人工确认。这个设计比“相信系统提示词不会乱来”靠谱。&lt;/p&gt;
&lt;p&gt;比如后续某个 MCP server 有一个 &lt;code&gt;restart_deployment&lt;/code&gt; 工具，我不会直接放开，而是会写成：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-22-1"&gt;&lt;a class="lnlinks" href="#hl-22-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-2"&gt;&lt;a class="lnlinks" href="#hl-22-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-3"&gt;&lt;a class="lnlinks" href="#hl-22-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-4"&gt;&lt;a class="lnlinks" href="#hl-22-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-5"&gt;&lt;a class="lnlinks" href="#hl-22-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-6"&gt;&lt;a class="lnlinks" href="#hl-22-6"&gt;6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-7"&gt;&lt;a class="lnlinks" href="#hl-22-7"&gt;7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-8"&gt;&lt;a class="lnlinks" href="#hl-22-8"&gt;8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-9"&gt;&lt;a class="lnlinks" href="#hl-22-9"&gt;9&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;McpServer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mcpServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MCPServer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ops-actions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;toolNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;restart_deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requireApproval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;restart_deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="这套-case-的落地判断"&gt;&lt;a href="#%e8%bf%99%e5%a5%97-case-%e7%9a%84%e8%90%bd%e5%9c%b0%e5%88%a4%e6%96%ad" class="header-anchor"&gt;&lt;/a&gt;这套 case 的落地判断
&lt;/h2&gt;&lt;p&gt;如果只是本地试用，&lt;code&gt;kagent install --profile demo&lt;/code&gt; 更快。官方 quickstart 里这条命令会预装一些 demo agents 和 MCP tools：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-23-1"&gt;&lt;a class="lnlinks" href="#hl-23-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-23-2"&gt;&lt;a class="lnlinks" href="#hl-23-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kagent install --profile demo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kagent dashboard
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;但如果要往团队环境里放，我更建议走 Helm + YAML：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CRD 和 controller 由平台侧统一安装。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ModelConfig&lt;/code&gt;、&lt;code&gt;MCPServer&lt;/code&gt;、&lt;code&gt;Agent&lt;/code&gt; 放进 Git 仓库。&lt;/li&gt;
&lt;li&gt;每个 Agent 的工具列表明确写出来。&lt;/li&gt;
&lt;li&gt;写操作默认要求人工确认。&lt;/li&gt;
&lt;li&gt;先把目标限定为“排查摘要”和“信息收集”，不要一上来自动修复。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;kagent 这类项目让我感觉 Kubernetes 后面会多出一种新对象：不是 workload，也不是 controller，而是“会调用工具的运维助手”。但真正能不能用，不取决于它能不能聊天，而取决于工具边界、权限边界和运行记录是不是足够清楚。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kagent.dev/docs/kagent/introduction/installation" target="_blank" rel="noopener"
 &gt;kagent Installing kagent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kagent.dev/docs/kagent/getting-started/first-mcp-tool" target="_blank" rel="noopener"
 &gt;kagent First MCP Tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kagent.dev/docs/kagent/resources/api-ref" target="_blank" rel="noopener"
 &gt;kagent API reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kagent.dev/docs/kagent/examples/agents-mcp" target="_blank" rel="noopener"
 &gt;kagent Agents via MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.kagent.dev/docs/kmcp/deploy/install-controller" target="_blank" rel="noopener"
 &gt;kmcp Install controller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cncf.io/projects/kagent/" target="_blank" rel="noopener"
 &gt;CNCF kagent 项目页&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cncf.io/blog/2025/04/15/kagent-bringing-agentic-ai-to-cloud-native/" target="_blank" rel="noopener"
 &gt;CNCF Blog: kagent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Model Context Protocol 与 OpenAI 工具调用实践</title><link>/dkblog/016-mcp/</link><pubDate>Sun, 27 Jul 2025 10:16:57 +0800</pubDate><guid>/dkblog/016-mcp/</guid><description>&lt;img src="https://images.unsplash.com/photo-1639322537228-f710d846310a?auto=format&amp;fit=crop&amp;w=900&amp;q=60" alt="Featured image of post Model Context Protocol 与 OpenAI 工具调用实践" /&gt;&lt;h2 id="引言从问答机器人到行动型ai助手"&gt;&lt;a href="#%e5%bc%95%e8%a8%80%e4%bb%8e%e9%97%ae%e7%ad%94%e6%9c%ba%e5%99%a8%e4%ba%ba%e5%88%b0%e8%a1%8c%e5%8a%a8%e5%9e%8bai%e5%8a%a9%e6%89%8b" class="header-anchor"&gt;&lt;/a&gt;引言：从“问答机器人”到“行动型AI助手”
&lt;/h2&gt;&lt;p&gt;传统的聊天机器人大多停留在“理解-回答”的单向交互模式。而随着大模型能力的演进，我们不再满足于“知道答案”，更希望 AI 能“采取行动”——比如查询天气、发送邮件、执行数据库操作等。&lt;/p&gt;
&lt;p&gt;这正是 &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; 和 &lt;strong&gt;OpenAI 函数调用（Function Calling）&lt;/strong&gt; 技术的用武之地。本文将带你从零开始，用 Go 语言实现一个基于 MCP 协议与 OpenAI SDK 深度整合的 Demo，构建一个能动态发现并调用远程工具的智能对话系统。&lt;/p&gt;
&lt;p&gt;我们将深入剖析核心架构、关键设计模式，并最终实现一个可扩展、高可用的 AI 助手原型。&lt;/p&gt;
&lt;h3 id="核心目标"&gt;&lt;a href="#%e6%a0%b8%e5%bf%83%e7%9b%ae%e6%a0%87" class="header-anchor"&gt;&lt;/a&gt;核心目标：
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多轮递归调用&lt;/strong&gt;：支持 LLM 决策后多次调用工具。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下文累积&lt;/strong&gt;：确保每一轮工具调用的结果都能被 LLM 看到。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高效并发处理&lt;/strong&gt;：利用 Go 的并发特性提升响应速度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;精准流程控制&lt;/strong&gt;：正确解析 &lt;code&gt;FinishReason&lt;/code&gt;，避免冗余请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="一技术选型与核心概念"&gt;&lt;a href="#%e4%b8%80%e6%8a%80%e6%9c%af%e9%80%89%e5%9e%8b%e4%b8%8e%e6%a0%b8%e5%bf%83%e6%a6%82%e5%bf%b5" class="header-anchor"&gt;&lt;/a&gt;一、技术选型与核心概念
&lt;/h2&gt;&lt;h3 id="11-openai-function-calling函数调用"&gt;&lt;a href="#11-openai-function-calling%e5%87%bd%e6%95%b0%e8%b0%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;1.1 OpenAI Function Calling（函数调用）
&lt;/h3&gt;&lt;p&gt;OpenAI 的 &lt;code&gt;gpt-3.5-turbo&lt;/code&gt; 及以上模型支持“函数调用”功能。你可以向模型描述一组函数（工具）的能力，模型会根据用户输入决定是否调用、调用哪个函数，并生成符合函数签名的参数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心优势&lt;/strong&gt;：将自然语言指令转化为结构化 API 调用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参考实现&lt;/strong&gt;：&lt;a class="link" href="https://github.com/sashabaranov/go-openai/blob/master/examples/chatbot/main.go" target="_blank" rel="noopener"
 &gt;sashabaranov/go-openai&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="12-model-context-protocol-mcp"&gt;&lt;a href="#12-model-context-protocol-mcp" class="header-anchor"&gt;&lt;/a&gt;1.2 Model Context Protocol (MCP)
&lt;/h3&gt;&lt;p&gt;MCP 是一种新兴的标准化协议，用于描述和暴露 LLM 可调用的“工具”（Tools）。它允许 LLM 客户端动态发现远程服务提供的功能，并安全地执行调用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心价值&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动态发现&lt;/strong&gt;：无需硬编码工具列表，运行时自动获取。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解耦架构&lt;/strong&gt;：LLM 核心与工具提供方完全分离，便于微服务部署。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标准化接口&lt;/strong&gt;：统一的 &lt;code&gt;ListTools&lt;/code&gt;、&lt;code&gt;CallTool&lt;/code&gt; 接口，降低集成成本。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参考实现&lt;/strong&gt;：&lt;a class="link" href="https://github.com/modelcontextprotocol/go-sdk/tree/main/examples/client" target="_blank" rel="noopener"
 &gt;modelcontextprotocol/go-sdk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="13-为什么选择-go"&gt;&lt;a href="#13-%e4%b8%ba%e4%bb%80%e4%b9%88%e9%80%89%e6%8b%a9-go" class="header-anchor"&gt;&lt;/a&gt;1.3 为什么选择 Go？
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高性能&lt;/strong&gt;：适合高并发的 API 网关场景。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强类型与并发支持&lt;/strong&gt;：&lt;code&gt;sync&lt;/code&gt;, &lt;code&gt;errgroup&lt;/code&gt; 等包让并发控制更安全。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;丰富的生态&lt;/strong&gt;：&lt;code&gt;go-openai&lt;/code&gt;, &lt;code&gt;gin&lt;/code&gt;, &lt;code&gt;grpc&lt;/code&gt; 等库成熟稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="二系统架构设计"&gt;&lt;a href="#%e4%ba%8c%e7%b3%bb%e7%bb%9f%e6%9e%b6%e6%9e%84%e8%ae%be%e8%ae%a1" class="header-anchor"&gt;&lt;/a&gt;二、系统架构设计
&lt;/h2&gt;&lt;p&gt;我们的系统由三部分组成：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-2"&gt;&lt;a class="lnlinks" href="#hl-0-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-3"&gt;&lt;a class="lnlinks" href="#hl-0-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-4"&gt;&lt;a class="lnlinks" href="#hl-0-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-5"&gt;&lt;a class="lnlinks" href="#hl-0-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-6"&gt;&lt;a class="lnlinks" href="#hl-0-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-7"&gt;&lt;a class="lnlinks" href="#hl-0-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-8"&gt;&lt;a class="lnlinks" href="#hl-0-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-9"&gt;&lt;a class="lnlinks" href="#hl-0-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-10"&gt;&lt;a class="lnlinks" href="#hl-0-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-11"&gt;&lt;a class="lnlinks" href="#hl-0-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-12"&gt;&lt;a class="lnlinks" href="#hl-0-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 用户请求 | --&amp;gt; | OpenAI LLM | --&amp;gt; | MCP 工具服务 |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| (DingTalk等) | | (决策与生成) | | (天气、数据库等) |
&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&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; v
&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; | MCP Manager |
&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;LLM 核心&lt;/strong&gt;：负责理解用户意图，决定是否调用工具。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP Manager&lt;/strong&gt;：管理多个 MCP 服务连接，实现工具发现与负载均衡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP 服务&lt;/strong&gt;：实际提供工具能力的微服务（如天气查询、数据库操作）。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="三核心代码解析"&gt;&lt;a href="#%e4%b8%89%e6%a0%b8%e5%bf%83%e4%bb%a3%e7%a0%81%e8%a7%a3%e6%9e%90" class="header-anchor"&gt;&lt;/a&gt;三、核心代码解析
&lt;/h2&gt;&lt;h3 id="31-mcpmanager工具服务的指挥官"&gt;&lt;a href="#31-mcpmanager%e5%b7%a5%e5%85%b7%e6%9c%8d%e5%8a%a1%e7%9a%84%e6%8c%87%e6%8c%a5%e5%ae%98" class="header-anchor"&gt;&lt;/a&gt;3.1 MCPManager：工具服务的“指挥官”
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;MCPManager&lt;/code&gt; 是整个系统的核心调度器，负责管理所有 MCP 服务的生命周期和工具映射。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-1-1"&gt;&lt;a class="lnlinks" href="#hl-1-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-2"&gt;&lt;a class="lnlinks" href="#hl-1-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-3"&gt;&lt;a class="lnlinks" href="#hl-1-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-4"&gt;&lt;a class="lnlinks" href="#hl-1-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MCPManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;clients&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;MCPClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 所有 MCP 客户端&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;toolMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 工具-服务器映射: toolName -&amp;gt; []*MCPClient&lt;/span&gt;&lt;span class="w"&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 class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="关键方法"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e6%96%b9%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;关键方法：
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;NewMCPManager()&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
从配置文件加载所有 MCP 服务地址，初始化客户端列表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ConnectAll(ctx)&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
使用 &lt;code&gt;errgroup&lt;/code&gt; 并发连接所有 MCP 服务，提升启动效率。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;buildToolMap(ctx)&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
核心逻辑！遍历所有已连接的服务，调用 &lt;code&gt;ListTools()&lt;/code&gt; 获取其提供的工具，并构建全局映射表 &lt;code&gt;toolMap&lt;/code&gt;。&lt;br&gt;
✅ &lt;strong&gt;优势&lt;/strong&gt;：支持多服务提供同一工具（负载均衡），自动去重。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ExecuteTool(ctx, toolName, args)&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
根据 &lt;code&gt;toolMap&lt;/code&gt; 找到提供该工具的服务，尝试调用。支持失败重试，增强系统鲁棒性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="32-mcpclientmcp-服务的通信桥梁"&gt;&lt;a href="#32-mcpclientmcp-%e6%9c%8d%e5%8a%a1%e7%9a%84%e9%80%9a%e4%bf%a1%e6%a1%a5%e6%a2%81" class="header-anchor"&gt;&lt;/a&gt;3.2 MCPClient：MCP 服务的“通信桥梁”
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;MCPClient&lt;/code&gt; 封装了与单个 MCP 服务的通信细节。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-2"&gt;&lt;a class="lnlinks" href="#hl-2-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-3"&gt;&lt;a class="lnlinks" href="#hl-2-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-4"&gt;&lt;a class="lnlinks" href="#hl-2-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-5"&gt;&lt;a class="lnlinks" href="#hl-2-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-6"&gt;&lt;a class="lnlinks" href="#hl-2-6"&gt;6&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;MCPClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Implementation&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="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StreamableClientTransport&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&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 class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="关键方法-1"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e6%96%b9%e6%b3%95-1" class="header-anchor"&gt;&lt;/a&gt;关键方法：
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ListTools(ctx)&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
调用 MCP 的 &lt;code&gt;ListTools&lt;/code&gt; 接口，并将返回的工具描述&lt;strong&gt;转换为 OpenAI 兼容的 &lt;code&gt;openai.Tool&lt;/code&gt; 格式&lt;/strong&gt;。这是实现协议桥接的关键一步。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ExecuteTool(ctx, toolName, args)&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
使用 &lt;code&gt;CallTool&lt;/code&gt; 接口执行工具调用，并内置了&lt;strong&gt;3次重试机制&lt;/strong&gt;，避免因网络抖动导致失败。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="33-llmclientopenai-的代言人"&gt;&lt;a href="#33-llmclientopenai-%e7%9a%84%e4%bb%a3%e8%a8%80%e4%ba%ba" class="header-anchor"&gt;&lt;/a&gt;3.3 LLMClient：OpenAI 的“代言人”
&lt;/h3&gt;&lt;p&gt;封装了 &lt;code&gt;sashabaranov/go-openai&lt;/code&gt; 客户端，提供更简洁的调用接口。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-3-1"&gt;&lt;a class="lnlinks" href="#hl-3-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-2"&gt;&lt;a class="lnlinks" href="#hl-3-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-3"&gt;&lt;a class="lnlinks" href="#hl-3-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-4"&gt;&lt;a class="lnlinks" href="#hl-3-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-5"&gt;&lt;a class="lnlinks" href="#hl-3-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-6"&gt;&lt;a class="lnlinks" href="#hl-3-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-7"&gt;&lt;a class="lnlinks" href="#hl-3-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-8"&gt;&lt;a class="lnlinks" href="#hl-3-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-9"&gt;&lt;a class="lnlinks" href="#hl-3-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-10"&gt;&lt;a class="lnlinks" href="#hl-3-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LLMClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;CreateCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChatCompletionMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChatCompletionRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ToolChoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;auto&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&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 class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线程安全初始化&lt;/strong&gt;：使用 &lt;code&gt;sync.OnceFunc&lt;/code&gt; 确保 &lt;code&gt;LLMClientO&lt;/code&gt; 全局单例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;灵活配置&lt;/strong&gt;：支持自定义 &lt;code&gt;baseURL&lt;/code&gt;（用于私有化部署的 LLM）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="34-chatsession对话状态的管理者"&gt;&lt;a href="#34-chatsession%e5%af%b9%e8%af%9d%e7%8a%b6%e6%80%81%e7%9a%84%e7%ae%a1%e7%90%86%e8%80%85" class="header-anchor"&gt;&lt;/a&gt;3.4 ChatSession：对话状态的“管理者”
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;ChatSession&lt;/code&gt; 维护了完整的对话上下文（&lt;code&gt;messages&lt;/code&gt;），并驱动整个交互流程。&lt;/p&gt;
&lt;h4 id="核心流程-handleuserinput"&gt;&lt;a href="#%e6%a0%b8%e5%bf%83%e6%b5%81%e7%a8%8b-handleuserinput" class="header-anchor"&gt;&lt;/a&gt;核心流程 &lt;code&gt;HandleUserInput&lt;/code&gt;：
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;添加用户消息&lt;/strong&gt; → 2. &lt;strong&gt;调用 LLM&lt;/strong&gt; → 3. &lt;strong&gt;处理响应&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;若 LLM 返回文本，则直接回复用户。&lt;/li&gt;
&lt;li&gt;若 LLM 返回工具调用，则执行 &lt;code&gt;handleToolCalls&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="buildsystempromptllm-的操作手册"&gt;&lt;a href="#buildsystempromptllm-%e7%9a%84%e6%93%8d%e4%bd%9c%e6%89%8b%e5%86%8c" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;buildSystemPrompt()&lt;/code&gt;：LLM 的“操作手册”
&lt;/h4&gt;&lt;p&gt;这是最精妙的设计之一！我们动态生成系统提示，将所有可用工具的描述注入给 LLM：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-4-1"&gt;&lt;a class="lnlinks" href="#hl-4-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-2"&gt;&lt;a class="lnlinks" href="#hl-4-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-3"&gt;&lt;a class="lnlinks" href="#hl-4-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-4"&gt;&lt;a class="lnlinks" href="#hl-4-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-5"&gt;&lt;a class="lnlinks" href="#hl-4-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-6"&gt;&lt;a class="lnlinks" href="#hl-4-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-7"&gt;&lt;a class="lnlinks" href="#hl-4-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-8"&gt;&lt;a class="lnlinks" href="#hl-4-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-9"&gt;&lt;a class="lnlinks" href="#hl-4-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-10"&gt;&lt;a class="lnlinks" href="#hl-4-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-11"&gt;&lt;a class="lnlinks" href="#hl-4-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-12"&gt;&lt;a class="lnlinks" href="#hl-4-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-13"&gt;&lt;a class="lnlinks" href="#hl-4-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;你是一个有帮助的助手&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="nx"&gt;可以使用以下工具&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&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 class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;getWeather&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;获取天气情况&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;parameters&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&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 class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;重要提示&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;当你需要使用工具时&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="nx"&gt;必须只返回以下格式的JSON对象&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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 class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tool&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tool-name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;arguments&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&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 class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;✅ &lt;strong&gt;优势&lt;/strong&gt;：LLM 始终知道“我能做什么”，无需重新训练。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四深入理解-finishreasonllm-的决策信号灯"&gt;&lt;a href="#%e5%9b%9b%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3-finishreasonllm-%e7%9a%84%e5%86%b3%e7%ad%96%e4%bf%a1%e5%8f%b7%e7%81%af" class="header-anchor"&gt;&lt;/a&gt;四、深入理解 &lt;code&gt;FinishReason&lt;/code&gt;：LLM 的“决策信号灯”
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;FinishReason&lt;/code&gt; 是 LLM 返回响应时附带的状态码，它告诉客户端“我为什么停止生成”。正确解析它，是实现智能交互的前提。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;code&gt;FinishReason&lt;/code&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;&lt;code&gt;stop&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;模型自然结束，通常已生成最终答案&lt;/td&gt;
					&lt;td&gt;✅ &lt;strong&gt;直接回复用户&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;length&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;因达到最大 token 限制而截断&lt;/td&gt;
					&lt;td&gt;⚠️ &lt;strong&gt;可选择扩展上下文或警告用户&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;tool_calls&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;模型决定调用一个或多个工具&lt;/td&gt;
					&lt;td&gt;🔧 &lt;strong&gt;执行工具调用，并将结果返回给模型继续思考&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;function_call&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;旧版函数调用（与 &lt;code&gt;tool_calls&lt;/code&gt; 类似）&lt;/td&gt;
					&lt;td&gt;🔧 &lt;strong&gt;兼容处理，同 &lt;code&gt;tool_calls&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;content_filter&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;内容被安全策略拦截&lt;/td&gt;
					&lt;td&gt;🛡️ &lt;strong&gt;向用户返回安全提示&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;生成未完成（流式响应中常见）&lt;/td&gt;
					&lt;td&gt;🔄 &lt;strong&gt;继续接收流数据&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;✅ &lt;strong&gt;重点&lt;/strong&gt;：&lt;code&gt;tool_calls&lt;/code&gt; 和 &lt;code&gt;function_call&lt;/code&gt; 是我们进行&lt;strong&gt;工具调用&lt;/strong&gt;的触发信号。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="五核心交互流程的增强设计"&gt;&lt;a href="#%e4%ba%94%e6%a0%b8%e5%bf%83%e4%ba%a4%e4%ba%92%e6%b5%81%e7%a8%8b%e7%9a%84%e5%a2%9e%e5%bc%ba%e8%ae%be%e8%ae%a1" class="header-anchor"&gt;&lt;/a&gt;五、核心交互流程的增强设计
&lt;/h2&gt;&lt;p&gt;我们对 &lt;code&gt;ChatSession.HandleUserInput&lt;/code&gt; 方法进行了全面升级，使其支持&lt;strong&gt;递归式多轮工具调用&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="增强版交互流程图"&gt;&lt;a href="#%e5%a2%9e%e5%bc%ba%e7%89%88%e4%ba%a4%e4%ba%92%e6%b5%81%e7%a8%8b%e5%9b%be" class="header-anchor"&gt;&lt;/a&gt;增强版交互流程图
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-5-1"&gt;&lt;a class="lnlinks" href="#hl-5-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-2"&gt;&lt;a class="lnlinks" href="#hl-5-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-3"&gt;&lt;a class="lnlinks" href="#hl-5-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-4"&gt;&lt;a class="lnlinks" href="#hl-5-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-5"&gt;&lt;a class="lnlinks" href="#hl-5-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-6"&gt;&lt;a class="lnlinks" href="#hl-5-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-7"&gt;&lt;a class="lnlinks" href="#hl-5-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-8"&gt;&lt;a class="lnlinks" href="#hl-5-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-9"&gt;&lt;a class="lnlinks" href="#hl-5-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-10"&gt;&lt;a class="lnlinks" href="#hl-5-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-11"&gt;&lt;a class="lnlinks" href="#hl-5-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-12"&gt;&lt;a class="lnlinks" href="#hl-5-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-13"&gt;&lt;a class="lnlinks" href="#hl-5-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-14"&gt;&lt;a class="lnlinks" href="#hl-5-14"&gt;14&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-15"&gt;&lt;a class="lnlinks" href="#hl-5-15"&gt;15&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-16"&gt;&lt;a class="lnlinks" href="#hl-5-16"&gt;16&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-17"&gt;&lt;a class="lnlinks" href="#hl-5-17"&gt;17&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-18"&gt;&lt;a class="lnlinks" href="#hl-5-18"&gt;18&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-19"&gt;&lt;a class="lnlinks" href="#hl-5-19"&gt;19&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-20"&gt;&lt;a class="lnlinks" href="#hl-5-20"&gt;20&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-21"&gt;&lt;a class="lnlinks" href="#hl-5-21"&gt;21&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-22"&gt;&lt;a class="lnlinks" href="#hl-5-22"&gt;22&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-23"&gt;&lt;a class="lnlinks" href="#hl-5-23"&gt;23&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-24"&gt;&lt;a class="lnlinks" href="#hl-5-24"&gt;24&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-5-25"&gt;&lt;a class="lnlinks" href="#hl-5-25"&gt;25&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&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="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;用户输入问题&lt;/span&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="o"&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;v&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="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;调用&lt;/span&gt; &lt;span class="n"&gt;LLM&lt;/span&gt; &lt;span class="err"&gt;生成响应&lt;/span&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="o"&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="o"&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 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;v&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;length&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 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="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;执行所有工具调用&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;直接回复用户&lt;/span&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="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="err"&gt;并发执行（可选）&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&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="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="err"&gt;累积结果到&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&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="o"&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;v&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="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;将结果喂回&lt;/span&gt; &lt;span class="n"&gt;LLM&lt;/span&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="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;再次调用&lt;/span&gt; &lt;span class="n"&gt;HandleUserInput&lt;/span&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="o"&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="o"&gt;+--------&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;循环，直到&lt;/span&gt; &lt;span class="n"&gt;LLM&lt;/span&gt; &lt;span class="err"&gt;返回&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="六实战演示多轮工具调用"&gt;&lt;a href="#%e5%85%ad%e5%ae%9e%e6%88%98%e6%bc%94%e7%a4%ba%e5%a4%9a%e8%bd%ae%e5%b7%a5%e5%85%b7%e8%b0%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;六、实战演示：多轮工具调用
&lt;/h2&gt;&lt;p&gt;假设用户提问：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“请查询北京和杭州的天气，并告诉我哪个更热？”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="系统执行流程"&gt;&lt;a href="#%e7%b3%bb%e7%bb%9f%e6%89%a7%e8%a1%8c%e6%b5%81%e7%a8%8b" class="header-anchor"&gt;&lt;/a&gt;系统执行流程：
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;第一轮 LLM 调用&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;LLM 分析问题，决定需要调用 &lt;code&gt;getWeather&lt;/code&gt; 工具两次。&lt;/li&gt;
&lt;li&gt;返回 &lt;code&gt;FinishReason: tool_calls&lt;/code&gt;，并包含两个 &lt;code&gt;ToolCall&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="2"&gt;
&lt;li&gt;&lt;strong&gt;执行工具调用&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MCPManager&lt;/code&gt; 并发调用两次 &lt;code&gt;getWeather&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;假设返回：
&lt;ul&gt;
&lt;li&gt;北京：30°C&lt;/li&gt;
&lt;li&gt;杭州：25°C&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;&lt;strong&gt;将结果注入上下文&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;两条 &lt;code&gt;Role: tool&lt;/code&gt; 的消息被加入 &lt;code&gt;messages&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="4"&gt;
&lt;li&gt;&lt;strong&gt;第二轮 LLM 调用&lt;/strong&gt;（递归调用 &lt;code&gt;HandleUserInput(&amp;quot;&amp;quot;, ctx)&lt;/code&gt;）：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;LLM “看到”了两个城市的天气数据。&lt;/li&gt;
&lt;li&gt;经过推理，生成最终回复：

 &lt;blockquote&gt;
 &lt;p&gt;“北京的气温是 30°C，杭州是 25°C，因此北京更热。”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;返回 &lt;code&gt;FinishReason: stop&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="5"&gt;
&lt;li&gt;&lt;strong&gt;回复用户&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;系统调用 &lt;code&gt;replyToUser()&lt;/code&gt;，将 &lt;code&gt;Content&lt;/code&gt; 中的内容直接发送给用户。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ &lt;strong&gt;成功实现多轮推理与决策，且在 &lt;code&gt;stop&lt;/code&gt; 时正确终止流程！&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七设计亮点与最佳实践"&gt;&lt;a href="#%e4%b8%83%e8%ae%be%e8%ae%a1%e4%ba%ae%e7%82%b9%e4%b8%8e%e6%9c%80%e4%bd%b3%e5%ae%9e%e8%b7%b5" class="header-anchor"&gt;&lt;/a&gt;七、设计亮点与最佳实践
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;动态工具发现&lt;/strong&gt;：无需重启即可接入新服务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高可用设计&lt;/strong&gt;：&lt;code&gt;errgroup&lt;/code&gt; 并发连接、工具调用重试、服务健康检查。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协议桥接&lt;/strong&gt;：无缝整合 MCP 与 OpenAI 两种协议。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可扩展架构&lt;/strong&gt;：轻松支持数据库查询、邮件发送、内部 API 调用等更多工具。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;清晰的职责分离&lt;/strong&gt;：&lt;code&gt;MCPManager&lt;/code&gt;, &lt;code&gt;ChatSession&lt;/code&gt;, &lt;code&gt;LLMClient&lt;/code&gt; 各司其职。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="结语"&gt;&lt;a href="#%e7%bb%93%e8%af%ad" class="header-anchor"&gt;&lt;/a&gt;结语
&lt;/h2&gt;&lt;p&gt;通过将 MCP 的动态工具发现能力与 OpenAI 强大的语言理解能力相结合，我们构建了一个真正“能做事”的 AI 助手。这不仅是一个 Demo，更是一种可落地的架构范式。&lt;/p&gt;
&lt;p&gt;在微服务与 AI 融合的浪潮中，掌握这种“LLM + 工具调用”的模式，将为你打开通往下一代智能应用的大门。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;代码已开源&lt;/strong&gt;：&lt;a class="link" href="http://www.baidu.com" target="_blank" rel="noopener"
 &gt;GitHub 仓库链接&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;欢迎 Star 与贡献&lt;/strong&gt;！&lt;/p&gt;</description></item></channel></rss>