上周云栖大会我泡了两天技术专场大部分分享都在讲模型能力怎么更强、RAG怎么更准唯独Kymo那场讲的东西不太一样——主题里同时出现了Harness引擎和MCP审计两个词。老实说我起初是冲着审计去的因为最近团队做的Agent项目已经接了七八个MCP工具模型能调用的东西越来越多我越来越觉得心里没底它在什么情况下会调用哪个工具、参数是谁生成的、调用前有没有人校验这些几乎全凭运气。听完分享的当天晚上我就把Kymo提到的Harness引擎和MCP审计方案翻来覆去研究了一遍翻源码、搭Demo、跑本地环境前前后后花了一周。这篇就把我研究的过程、核心结论和踩过的坑完整写出来给正在做Agent工程化的朋友一个参考。1. Kymo抛出的问题Agent越能干越需要一套运行轨道1.1 分享现场最扎心的一句话Kymo演讲到一半放了一张架构图底下坐着的人基本都拍照了。图里是一个典型的Agent挂着代码库MCP、数据库MCP、浏览器自动化MCP模型正在生成一个delete操作的工具调用参数。他问了一句如果这个delete参数来自一个被Prompt Injection污染的上下文你的框架能拦住吗现场安静了两秒。老实说绝大多数团队真的拦不住。我们平时关注的是模型选没选对工具、生成的参数合不合理很少有人关注到工具调用从意图到执行中间其实应该有一层独立的运行轨道而这一层在多数项目里是缺失的。1.2 Harness和Agent到底差在哪这是我研究之前最先想搞清楚的问题也是网上讨论最混乱的地方。我的理解是Agent指的是感知、规划、行动、反思这个循环本身它本质上是模型侧的策略逻辑而Harness是承载这个循环的可执行环境负责上下文怎么组织、工具怎么注册、权限怎么校验、调用轨迹怎么留痕。打个比方Agent像驾驶员负责看路、打方向盘、踩油门Harness则是整台车的底盘、刹车系统和行车记录仪。司机技术再好没有刹车和记录仪的车也没人敢开。现在的Agent项目普遍是司机很聪明、车没有刹车Harness就是补上这一层的。2. Harness引擎的核心设计模型只负责决策执行必须被约束2.1 一个能落地的Harness由哪几块组成我自己研究后认为一个可用的Harness引擎至少要包含四块模型适配层、上下文管理、工具注册表、审计钩子。四者缺一不可。模型适配层把不同模型本地部署的开源模型、云端API统一成同样的聊天接口模型产出的tool_call格式也统一化。上下文管理决定哪些历史消息进窗口、哪些压缩、哪些放到外部记忆控制上下文的体积和纯度。工具注册表MCP server暴露的每一个工具进注册表时都要经过登记、分级、参数Schema校验不能无条件全部暴露给模型。审计钩子在工具调用前、调用后、异常时分别埋点把决策依据、参数快照、执行结果全部记录下来。如果只有模型适配和上下文管理那它顶多算一个封装SDK工具注册表和审计钩子才是Harness区别于普通Agent框架的关键。2.2 工具注册表不是能调就行而是分级管控MCP协议做得很好的一点是工具定义天然带JSON Schema。但Schema只能说明参数长得什么样子说明不了这个工具有多危险。同一个执行SQL工具连只读副本和连生产主库风险完全不一样。我在研究时模仿了一套三级分级策略实践下来比较实用级别典型工具校验策略审计策略safe搜索、白名单路径内读文件、获取当前时间参数Schema校验旁路记录risky写文件、执行只读SQL、安装依赖包参数白名单频控同步记录阈值告警dangerousshell执行、删除操作、生产库写操作、外发网络请求人工确认/二次确认全量快照请求与响应注册表里存的不是简单的tool_name - function映射而是一个包含风险等级、允许参数范围、调用频控、审计策略的元数据对象。模型看到的是工具的使用说明书Harness执行的是工具的准入门禁。2.3 最小Harness执行流的伪代码视角我搭最小验证时写了一个很薄的执行骨架核心逻辑就一句话模型可以提建议但决定工具能不能执行的是Harness。class HarnessRuntime: def __init__(self, model_adapter, registry, auditor): self.model_adapter model_adapter self.registry registry # 分级工具注册表 self.auditor auditor # 审计钩子 self.session SessionState() async def execute(self, user_request): context self.session.build_context(user_request) response await self.model_adapter.chat(context.messages) for call in response.tool_calls: decision self.registry.check(call.name, call.arguments) self.auditor.record( subjectself.session.user_id, actioncall.name, argumentscall.arguments, decisiondecision, ) if decision.allowed: result await self.invoke(call.name, call.arguments) else: result decision.refuse_message context.append_tool_result(call, result) return await self.model_adapter.chat(context.messages)注意registry.check放在模型产出参数之后、真实工具执行之前。这一层就是Harness存在的意义模型只负责生成意图安全边界由Harness来守。这个骨架虽然简陋但把决策和执行分离的核心思想表达得很清楚。3. MCP审计方案当模型开始操作真实系统该审什么、怎么审3.1 常规日志为什么不够用很多团队现在已经有日志了记录的是请求参数和响应文本。但Agent场景下审计对象不是一次HTTP请求而是一段意图链路用户说了一句话模型理解出意图选择了某个工具生成了具体参数执行后拿到了结果结果又影响下一步决策。常规日志把这个链路切断了你不知道这个SQL查询是哪个用户会话触发的不知道为什么模型选择查这张表更不知道参数生成时的上下文里有没有被塞进恶意指令。MCP审计要解决的就是这个完整性问题。3.2 审计五元组每次都该记什么我把审计字段归纳成五个维度每次工具调用都按这个结构记录维度关键字段解决什么问题SubjectuserId、agentId、sessionId、projectId谁在什么场景下发起了这次调用ActiontoolName、operation、arguments模型到底想做什么Resource目标仓库、库表、文件路径、URL作用于哪个对象Contextprompt摘要、上下文窗口占用率、意图Tag这次调用是基于什么上下文产生的Decisionallowed/denied、ruleId、耗时、异常信息系统允许了吗依据是什么尤其Context这个维度很多审计方案会漏掉。它看起来不直接影响执行但事后复盘Prompt Injection时没有上下文快照几乎无法定位问题源头。3.3 旁路采集与同步裁决两种审计模式的取舍研究过程中我发现审计这个动作本身有两种截然不同的介入深度。旁路采集是异步的Harness在工具调用前后发事件审计模块监听后落库、跑规则、出报表。它的好处是对主链路延迟基本无感坏处是只能事后追溯拦不住正在发生的危险操作。同步裁决是在工具调用前同步执行审计规则返回allow或deny。它能真正阻断危险操作但审计链路一旦超时或出错会影响正常业务。Kymo的方案给我的启发是不要二选一而是按风险等级混用——safe工具旁路采集risky工具同步告警dangerous工具同步拦截。4. 从研究到落地我搭的一套MCP审计闭环4.1 整体架构与目录安排研究不能停在概念层我用自己的项目做了个最小落地架构从上到下是Agent应用 - HarnessRuntime - MCP Client - MCP Server集群审计链路从HarnessRuntime的审计钩子出发走事件总线 - 结构化 - PostgreSQL - 规则引擎 - 告警。目录结构我按模块拆得很清楚方便后面加规则和换存储agent-project/ harness/ runtime.py # HarnessRuntime 最小实现 registry.py # 工具注册表 分级策略 auditor.py # 审计钩子旁路/同步双模式 mcp_host/ registry.yaml # MCP Server 注册配置 health_check.py # 连接健康检查 audit/ models.py # 审计五元组模型 rules.yaml # 审计规则 storage.py # PostgreSQL 落库4.2 落地步骤五步走完第一步统一MCP Server注册。把所有MCP Server的transport类型stdio或streamable HTTP、启动命令、环境变量、工作目录全部收口到一个registry.yaml里由MCP Host统一拉起禁止业务代码里各自new Client。第二步在Harness里挂审计钩子。审计钩子设计成事件发布/订阅模式HarnessRuntime不用关心审计逻辑怎么实现只管发事件。这样审计模块可以独立升级规则不影响主链路代码。第三步审计数据结构化落库。我把五元组直接映射成一张表核心字段加了索引查询和告警都很顺手CREATE TABLE mcp_audit_log ( id BIGSERIAL PRIMARY KEY, ts TIMESTAMPTZ NOT NULL DEFAULT now(), subject_id TEXT NOT NULL, agent_id TEXT NOT NULL, session_id TEXT, tool_name TEXT NOT NULL, arguments JSONB, risk_level TEXT NOT NULL, decision TEXT NOT NULL, rule_id TEXT, latency_ms INTEGER, result_summary TEXT, trace_id TEXT ); CREATE INDEX idx_audit_ts_tool ON mcp_audit_log(ts DESC, tool_name); CREATE INDEX idx_audit_subject_ts ON mcp_audit_log(subject_id, ts DESC);第四步配置规则引擎。我先配了几条最基础但最有价值的规则用YAML管理改规则不用改代码rules: - name: dangerous_sql_ddl match: tool: db_execute arguments_contains: [drop, truncate, alter] policy: deny message: 检测到DDL语句默认拒绝执行 notify: true - name: file_read_frequency match: tool: fs_read window: 60s threshold: 100 action: throttle message: 60秒内读文件超过100次疑似工具循环 - name: production_db_write match: tool: db_execute env: prod operation: insert|update|delete policy: require_approval message: 生产库写操作需要人工审批window和threshold组合起来对付模型死循环特别有效。我实际遇到过Agent因为上下文里一个误导性结果连续读了上百次文件如果没有频控当天就能把外部API配额打爆。第五步接告警。规则命中后通过Webhook推到团队协作群payload里带上trace_id和decision开发可以直接点进审计系统看上下文快照。从发现问题到定位问题不用再翻原始日志。4.3 审计系统和业务代码的关系落地时我最担心的是审计模块反向污染业务代码。我的处理原则是审计模块不让业务团队直接调只允许通过HarnessRuntime的execute入口间接触发。这样审计的完整性就交给了Harness这一层业务代码保持干净。5. 实测中的三个坑插件加载、MCP超时、误报5.1 harness failed to load plugins入口激活失败的完整排查链路搭环境的第一天我就踩了坑。启动Harness时日志报harness failed to load plugins, web boot: 1 entry did not activate插件系统只加载了一半看起来是web boot阶段某个插件入口没有激活。我的排查路径是这样走的先看启动日志最前面几行确认是哪个插件报的错日志里插件名后面直接跟了entry did not activate。打开插件清单文件manifest检查entry字段发现它指向一个Python模块路径。用python -c import xxx逐一验证入口模块能不能导入结果发现清单里写的是AuditWriter实际文件叫audit_writer.py大小写不匹配在Linux环境直接找不到。改成实际模块名后重启插件正常激活。这个坑看起来很小但很典型Harness插件系统加载的是动态入口环境里大小写敏感、PYTHONPATH配置稍有不对就会出现进程起来了、插件没起来的诡异状态。排查一定要顺着日志从哪个插件失败而非整个系统失败的角度去拆。5.2 MCP Server连接超时根因往往不在网络另一个高频问题是MCP Server拉起后握手超时。我一开始以为是网络慢反复调大超时时间都没用。后来发现stdio型的MCP Server本质是Host拉起一个子进程子进程里如果找不到解释器或依赖模块Server根本起不来。我在macOS本地跑得好好的部署到Linux容器就超时最后定位到是容器里PATH没有包含我安装Python解释器的目录。解决方案是MCP Server的command配置里不写python3改成解释器绝对路径并且显式传入env里的PATH。顺手把启动探活做成三次重试每次间隔两秒超过十秒直接标记该Server不可用并告警。5.3 审计误报比漏报更折磨人审计方案上线后第一个星期告警群里全是误报。最有意思的一条本地开发时Agent为了确认单元测试结果正常读取测试报告文件被危险路径规则拦了另一个是运维同学手动触发了一次数据订正脚本被当成危险SQL报警。误报过多会让团队对审计系统脱敏狼来了喊多了真报警也没人看。我的调整方法是给审计规则加上env字段区分dev、staging、prod同一工具在不同环境的阈值和策略完全不同同时在规则引擎里增加评估模式新规则先只记录不改决策跑一段时间看误报率再切换成拦截模式。这个先旁路、后拦截的灰度思路比一上来就全量拦截稳妥得多。6. 研究之后的几个判断把Kymo分享的Harness引擎和MCP审计方案研究完又在自己项目上滚了一周之后我形成了几个比较明确的判断。第一Harness引擎解决的不是模型能力问题而是Agent能不能进生产环境的问题。只要Agent还要调外部工具、还要多人共用、还要处理敏感数据工具注册表和审计钩子就不是可选项而是必选项。第二MCP审计的价值不在记录而在决策可解释。五元组里Context和Decision两个字段才是将来排查事故、追溯责任最值钱的部分。第三落地节奏上不要追求一步到位。先做旁路记录把工具清单、调用频次、风险分布摸清楚再把规则从只记录逐步切到同步告警同步拦截。我这个项目从第一天就开了拦截模式结果头一个礼拜全在处理误报这是我在实际操作中踩出来的教训。如果让我再扩展下一步我会把审计规则从本地YAML迁移到动态下发配合MCP的资源读取审计让整个agent行为链路的可见性再往上走一层。