☰
Agent 落地企业系统:MCP 协议与 FDE 实战指南
2026/10/1 18:21:04 网站建设 项目流程

1. 从 Demo 到生产:Agent 落地最容易被低估的深水区

做 Agent 的人大多经历过这样一个阶段:本地跑通一个 ReAct 循环,接上几个工具,看着模型自己规划、调用、反思,觉得“这东西成了”。然后老板说,接一下公司的 OA 和 ERP 吧,让 Agent 能查审批、能建单据、能拉库存。这时候你才发现,前面那些调 Prompt、换模型、加 Memory 的活儿,加起来可能只占整个工程量的三成,剩下七成全卡在“怎么让 Agent 安全、稳定、可维护地碰到企业里那套跑了十年的老系统”。

这个项目标题里的“FDE MCP Blade”,拆开看其实是三个关键词的叠加:FDE(Forward Deployed Engineer,前置交付工程师)、MCP(Model Context Protocol,模型上下文协议)、Blade(这里我理解为一把用来“切开”企业系统边界的工具层)。合在一起,它描述的是一类非常具体的工程实践——由 FDE 角色主导,用 MCP 协议作为桥梁,把 OA、ERP 这类企业核心系统封装成 Agent 可调用的能力,并且这套东西是要上生产的,不是演示用的。

为什么说难的不是模型?因为模型能力在过去一年里已经卷到“够用”了。你拿一个中等规模的模型,配上好的工具描述和 Few-shot 示例,它在单步工具调用上的准确率可以做到很高。但企业系统不一样:OA 的审批流有几十种状态,ERP 的单据有复杂的必填字段和联动逻辑,接口可能是 SOAP 的、可能是私有协议的、可能压根没有 API 只能走页面。Agent 面对的不是“选哪个工具”,而是“这个工具背后那套业务规则,我该怎么让它理解并且不闯祸”。

这篇文章适合三类人看:一是正在做 Agent 落地、被企业系统集成卡住的工程师;二是 FDE 角色或者想转 FDE 的开发者,想了解这类岗位到底在干什么;三是技术负责人,在评估 Agent 上生产的可行性和成本。我会从架构设计、MCP 封装、OA/ERP 对接实操、并发与安全、问题排查几个维度,把这件事拆开讲透。里面有不少是我自己踩过的坑,也有一些是跟同行交流时验证过的做法,你拿去就能对照着改。

2. 整体架构设计:为什么是 MCP + FDE 这个组合

2.1 FDE 角色的真实定位:不是售前,也不是纯后端

FDE 这个词最早在 Palantir 那套体系里被广泛提及,国内腾讯等公司也在推类似的课程和岗位。很多人把它理解成“懂技术的售前”,这个理解偏了。FDE 的核心价值在于:他站在客户业务现场,把通用技术能力翻译成解决具体业务问题的方案,并且亲手把它交付上线。

放到 Agent 接 OA、ERP 这个场景里,FDE 要干的事包括:搞清楚客户 OA 里“请假审批”这个流程到底有几个节点、每个节点的操作权限是什么、ERP 里“采购订单”创建时哪些字段是必填、哪些字段有联动校验、接口的鉴权方式是什么、有没有频率限制。这些信息不会写在公开文档里,得去问、去试、去翻旧代码。然后 FDE 要把这些业务规则抽象成 Agent 能理解的工具描述,用 MCP 协议封装好,再跟 Agent 的编排层对接。

为什么不让纯后端来做?因为纯后端容易陷入“接口能通就行”的思维,而 Agent 需要的是“语义层面的可用”。比如一个“查询库存”的接口,后端返回一个 JSON 数组就完事了,但 Agent 需要知道:这个库存数是实时还是缓存、单位是什么、负库存代表什么、多个仓库时怎么选。这些语义信息,必须由懂业务的人来补。

2.2 MCP 协议到底解决了什么问题

MCP 是 Anthropic 提出的一个开放协议,全称 Model Context Protocol。你可以把它理解成“AI 模型和外部工具之间的 USB-C 接口”。在 MCP 之前,每个 Agent 框架都有自己的工具定义方式:LangChain 有 Tool 类,OpenAI 有 Function Calling 的 JSON Schema,各家 IDE 又有自己的插件机制。你为一个系统写好的工具,换个框架就得重写一遍。

MCP 把这件事标准化了。它定义了一套基于 JSON-RPC 的通信规范,工具提供方(MCP Server)只需要按照协议暴露自己的能力,任何支持 MCP 的客户端(Claude Desktop、各种 IDE、自研 Agent 框架)都能直接调用。对于企业系统集成来说,这意味着:你为 OA 写一套 MCP Server,以后不管上层 Agent 用什么框架,这套 Server 都能复用。

MCP 的核心概念有三个:Tools(可调用的函数)、Resources(可读取的数据源)、Prompts(预置的提示模板)。接 OA、ERP 主要用到 Tools,偶尔用 Resources 来暴露一些静态配置。协议本身支持 stdio 和 SSE 两种传输方式,生产环境里 SSE 更常见,因为可以跨网络部署。

注意:MCP 是软件协议,不是硬件协议。经常有人问“MCP 是不是像 USB 那样的硬件标准”,不是的,它是应用层的通信协议,跑在 TCP 之上。

2.3 为什么不用直接 Function Calling 而要套一层 MCP

有人会问:我直接用 OpenAI 的 Function Calling 或者 LangChain 的 Tool 不就行了,为什么要多一层 MCP?这个问题我在项目初期也纠结过。直接 Function Calling 的优势是简单,少一层转发,延迟低。但上了生产之后,MCP 的价值就体现出来了:

第一,解耦。Agent 的编排逻辑和工具的实现逻辑分开。OA 的接口变了,只需要改 MCP Server,Agent 侧不用动。第二,复用。同一个 MCP Server 可以同时给多个 Agent 用,也可以给不同的 IDE 用。第三,可观测。MCP Server 是一个独立的进程或服务,你可以单独给它加日志、加监控、加限流,而 Function Calling 是嵌在 Agent 进程里的,不好隔离。第四,安全边界。MCP Server 可以部署在内网,只暴露必要的工具,Agent 跑在外面也碰不到核心系统。

代价是多了网络跳转和序列化开销。实测下来,一次工具调用的额外延迟在 20-50ms 左右,对于 OA、ERP 这种本身响应就在几百毫秒的系统来说,可以接受。

2.4 整体架构分层

我把这套东西分成四层,从下往上:

层级职责典型组件
系统层企业原有系统OA、ERP、CRM、数据库
适配层协议转换、鉴权、字段映射MCP Server、适配器
编排层意图理解、工具选择、多步规划Agent 框架
交互层用户入口对话界面、IDE 插件、API

FDE 主要工作在适配层,偶尔碰编排层的工具描述优化。这个分层的好处是,每一层可以独立测试和替换。适配层用 Mock 数据就能测,不用连真实 OA。

3. OA 系统对接实操:从鉴权到审批流封装

3.1 先搞清楚 OA 的鉴权体系

国内 OA 系统五花八门,泛微、通达、致远、蓝凌各有各的玩法。但鉴权方式逃不出这几类:Session/Cookie、Token、CAS 单点登录、OAuth2。热搜词里出现了“通达 OA CAS”和“泛微 OA 建模引擎”,说明这两个是高频场景。

通达 OA 的 CAS 是比较典型的一种。它的流程是:Agent 侧先访问 CAS 登录接口,带上用户名密码或者票据,拿到一个 ticket,再用 ticket 去换 session。这里有个坑:CAS 的 ticket 是一次性的,且有效期很短,通常几十秒。如果你的 MCP Server 每次调用都重新走一遍 CAS,延迟会很高,而且容易触发风控。正确做法是在 MCP Server 里维护一个 session 池,登录一次拿到 session 后缓存起来,定期续期。

泛微 OA 的鉴权相对简单一些,很多版本支持直接传 token 或者用固定的 appid/secret 换 token。但泛微的建模引擎有个特点:它的接口是按“建模”维度组织的,你得先知道目标模块的 modelId 和对应的字段定义,才能构造请求。这些信息在 CSDN 上有很多人分享,但版本差异大,最好还是从自己的环境里导出来。

# 通达 OA CAS 登录的简化示例 import requests import time class TongdaOAClient: def __init__(self, base_url, username, password): self.base_url = base_url self.username = username self.password = password self.session = None self.session_expire = 0 def _login(self): # 第一步:获取 CAS 登录页的 execution 参数 login_page = requests.get(f"{self.base_url}/cas/login") # 解析出 execution 值(实际代码需要用 BeautifulSoup 或正则) execution = self._parse_execution(login_page.text) # 第二步:提交登录 resp = requests.post( f"{self.base_url}/cas/login", data={ "username": self.username, "password": self.password, "execution": execution, "_eventId": "submit" }, allow_redirects=False ) # 第三步:从重定向中提取 ticket,再换 session ticket = self._extract_ticket(resp.headers.get("Location", "")) session_resp = requests.get( f"{self.base_url}/cas/serviceValidate", params={"ticket": ticket, "service": f"{self.base_url}/oa"} ) self.session = session_resp.cookies self.session_expire = time.time() + 1800 # 假设 30 分钟有效 def ensure_session(self): if not self.session or time.time() > self.session_expire - 60: self._login() return self.session

这段代码的关键点在于ensure_session的提前续期逻辑。我设的是过期前 60 秒就重新登录,避免边界情况。实际跑下来,这个策略比每次调用都登录稳定得多。

3.2 审批流封装:把状态机翻译成工具

OA 最核心的能力是审批流。一个请假流程可能有“发起-部门经理审批-HR审批-归档”四个节点,每个节点有不同的操作权限。Agent 要能查审批状态、能发起审批、能审批(如果用户授权的话)。

这里最大的难点是:审批流的状态是动态的,不同流程、不同组织架构下节点名称和数量都不一样。你不能硬编码“部门经理审批”这个节点,因为有的公司叫“直属领导审批”,有的公司是两级审批。

我的做法是:在 MCP Server 里封装一个get_approval_status工具,输入是流程实例 ID,输出是一个结构化的状态对象,包含当前节点、待办人、已办节点列表、可执行的操作。然后在工具描述里告诉 Agent:这个工具返回的available_actions字段列出了当前用户可以执行的操作,你只能从这里面选。

{ "name": "get_approval_status", "description": "查询指定审批流程的当前状态。返回当前节点、待办人、已办节点和可执行操作。注意:只能执行 available_actions 中列出的操作。", "inputSchema": { "type": "object", "properties": { "process_id": { "type": "string", "description": "审批流程实例 ID,通常以 FLOW_ 开头" } }, "required": ["process_id"] } }

这个描述里我特意加了一句“只能执行 available_actions 中列出的操作”,这是为了防止 Agent 幻觉出一个不存在的操作去调用。实测下来,加了这句话之后,误操作率明显下降。

3.3 泛微建模引擎的字段映射坑

泛微的建模引擎是很多企业做自定义表单的基础。它的接口返回的字段名往往是field_123这种内部 ID,而不是人类可读的leave_type。Agent 拿到这种数据会懵,因为它不知道field_123是什么。

解决办法是在 MCP Server 里做一层字段映射。启动时从泛微的元数据接口拉取字段定义,缓存到内存里,返回给 Agent 之前把field_123转成请假类型。这个映射表要定期刷新,因为业务人员可能随时改表单。

实操心得:泛微的元数据接口有时候会返回缓存数据,改了表单之后不立即生效。我一般会在 MCP Server 里加一个手动刷新映射的接口,改完表单后调一下,不用重启服务。

还有一个坑是“泛微 OA 添加外部地址作为目录报错连接被阻止”。这个错误通常是因为泛微的安全策略限制了外部 iframe 或跳转。如果你要在 OA 里嵌入 Agent 的对话界面,需要把 Agent 的域名加到泛微的白名单里,具体位置在系统管理-安全设置-可信站点。这个配置项藏得比较深,第一次找要花点时间。

4. ERP 系统对接:比 OA 更硬的骨头

4.1 ERP 接口的三种形态和应对策略

ERP 比 OA 难接,因为 ERP 的数据模型更复杂、业务规则更严格、接口更不统一。我见过的 ERP 接口大概分三种:

第一种是标准 REST/SOAP API。金蝶、用友的新版本都提供了比较完整的 API,有文档、有 SDK。这种最好办,直接封装成 MCP Tool 就行。但要注意:ERP 的 API 通常有严格的字段校验,少一个必填字段就报错,而且错误信息往往很模糊。我的做法是在 MCP Server 里做一层预校验,把必填字段检查提前,返回更清晰的错误提示给 Agent。

第二种是数据库直连。很多老 ERP 没有 API,只能读数据库。这种最危险,因为直接读生产库可能锁表、可能读到中间状态的数据。如果非要用,一定要走只读从库,并且加查询超时和行数限制。写操作绝对不要直连数据库,必须走应用层。

第三种是页面自动化。有些 ERP 只有 Web 界面,没有 API。这时候可以用 Playwright MCP 或者 Browser Use MCP 来做页面操作。热搜词里出现了“playwright mcp”和“browser use mcp 跟 playwright mcp 有什么区别”,说明这是很多人在探索的方向。

4.2 Playwright MCP 和 Browser Use MCP 的选型

这两个都是让 Agent 操作浏览器的方案,但定位不同。Playwright MCP是把 Playwright 的能力封装成 MCP 工具,Agent 可以调用click、fill、navigate等原子操作。Browser Use MCP更上层一些,它让 Agent 用自然语言描述目标,内部自己规划操作步骤。

接 ERP 页面的话,我倾向于用 Playwright MCP,原因是可控。ERP 的操作往往有严格的顺序和确认步骤,比如“先选仓库,再选物料,再填数量,最后点保存”,每一步都不能错。Playwright MCP 的原子操作让 Agent 可以精确控制每一步,而 Browser Use MCP 的自主规划在复杂页面上容易跑偏。

# Playwright MCP Server 中一个工具的定义示例 @mcp.tool() async def fill_erp_field(field_label: str, value: str) -> str: """在 ERP 页面中填写指定标签的字段。 Args: field_label: 字段的显示标签,如"物料编码"、"数量" value: 要填入的值 """ # 通过 label 文本定位输入框 locator = page.get_by_label(field_label) if await locator.count() == 0: return f"未找到标签为'{field_label}'的字段" await locator.fill(value) return f"已将'{field_label}'填写为'{value}'"

这个工具的设计要点是:用标签文本定位而不是 CSS 选择器。因为 ERP 页面的 DOM 结构经常变,但标签文本相对稳定。而且标签文本是 Agent 能理解的,它知道“物料编码”是什么,但不知道#ctl00_ContentPlaceHolder1_txtMaterialCode是什么。

4.3 ERP 单据创建的字段联动处理

ERP 最烦人的是字段联动。比如你选了“采购订单”类型,物料字段的可选范围就变了;你填了数量,单价可能自动带出来;你选了税率,总价自动计算。这些联动在页面上是 JavaScript 处理的,但通过 API 调用时不会自动触发。

我的处理方式是在 MCP Server 里模拟这些联动逻辑。具体做法是:先从 ERP 的配置表或者前端代码里把联动规则扒出来,然后在 Server 里实现一个validate_and_fill函数,在提交之前把该算的算好、该填的填上。

def prepare_purchase_order(data: dict) -> dict: """根据 ERP 的联动规则补全采购订单字段""" # 规则1:根据物料编码带出默认供应商 if data.get("material_code") and not data.get("supplier_code"): data["supplier_code"] = get_default_supplier(data["material_code"]) # 规则2:根据数量和单价计算金额 if data.get("quantity") and data.get("unit_price"): data["total_amount"] = round( float(data["quantity"]) * float(data["unit_price"]), 2 ) # 规则3:根据供应商和物料确定税率 if data.get("supplier_code") and data.get("material_code"): data["tax_rate"] = get_tax_rate( data["supplier_code"], data["material_code"] ) return data

这些规则不是拍脑袋写的,是从 ERP 的前端 JS 和数据库触发器里一点点扒出来的。扒的过程很痛苦,但扒完之后 Agent 创建单据的成功率从不到 50% 提升到了 95% 以上。

注意:不同 ERP 版本的联动规则可能不同,升级 ERP 后一定要重新验证。我遇到过升级后税率计算方式变了,导致一批单据金额错误的情况。

4.4 金蝶 ERP 操作手册里的隐藏信息

金蝶的官方操作手册写得很详细,但它是写给最终用户看的,不是写给集成开发者看的。手册里不会告诉你:某个字段在 API 里的名称是什么、某个操作的幂等性如何、并发调用会不会有问题。

我的经验是:手册看流程,代码看细节。先把手册里的业务流程搞清楚,知道“采购订单从创建到审核要经过哪些步骤”,然后去翻金蝶的 API 文档和 SDK 源码,找到每个步骤对应的接口和参数。金蝶的 SDK 里有一些注释会透露关键信息,比如某个接口是否支持批量、是否有事务保证。

还有一个技巧:金蝶的演示环境(如果有的话)可以用来做破坏性测试。比如故意传错参数看报错信息、并发调用看会不会锁、重复提交看是否幂等。这些信息在正式环境里不敢试,在演示环境里可以随便折腾。

5. 并发、安全与可观测性:上生产必须过的三关

5.1 Agent 怎么扛并发

热搜词里有“ai agent 怎么扛并发”,这是上生产绕不开的问题。Agent 的并发和普通 Web 服务的并发不一样,因为 Agent 的一次请求可能包含多轮模型调用和多次工具调用,耗时可能是几十秒甚至几分钟。

我的做法是分层限流:

层级限流对象策略
接入层用户请求按用户维度限流,防止单用户刷
编排层Agent 实例控制同时运行的 Agent 数量
工具层MCP Server按工具维度限流,保护后端系统
系统层OA/ERP遵守对方的频率限制

工具层的限流最关键。OA 和 ERP 通常有并发限制,你并发太高会被封 IP 或者拖垮系统。我在 MCP Server 里给每个工具配了独立的信号量,比如查询类工具允许 10 并发,写入类工具只允许 2 并发。

import asyncio class RateLimiter: def __init__(self, max_concurrent: int): self.semaphore = asyncio.Semaphore(max_concurrent) async def __aenter__(self): await self.semaphore.acquire() return self async def __aexit__(self, *args): self.semaphore.release() # 查询工具允许 10 并发 query_limiter = RateLimiter(10) # 写入工具只允许 2 并发 write_limiter = RateLimiter(2) @mcp.tool() async def query_inventory(material_code: str) -> dict: async with query_limiter: return await erp_client.query_inventory(material_code) @mcp.tool() async def create_purchase_order(data: dict) -> dict: async with write_limiter: return await erp_client.create_purchase_order(data)

除了限流,还要考虑超时和重试。ERP 的写入操作有时候会超时,但实际已经成功了。如果盲目重试,可能创建出重复单据。我的做法是:写入操作带一个唯一的业务流水号,重试时先查一下这个流水号是否已存在,存在就返回已有结果,不存在才真正重试。

5.2 Agent 安全:不只是 Prompt 注入

Agent 安全是个大话题,热搜词里有“agent 安全”和“a-memguard: a proactive defense framework for llm-based agent memory”,说明大家开始关注 Agent 的记忆安全和行为安全。

接 OA、ERP 的场景下,安全风险主要有三类:

第一类是越权操作。Agent 可能被诱导去执行用户没有权限的操作。比如一个普通员工通过精心构造的 Prompt,让 Agent 去审批一个本该由经理审批的单据。防御手段是在 MCP Server 里做权限校验,每次工具调用都带上用户身份,Server 侧检查这个用户是否有权限执行这个操作。不要信任 Agent 传来的任何权限声明。

第二类是数据泄露。Agent 在查询数据时可能把敏感信息带出来。比如查询员工信息时返回了薪资字段。防御手段是在 MCP Server 里做字段级过滤,根据调用者的角色决定返回哪些字段。

第三类是 Prompt 注入。OA 里的审批意见、ERP 里的备注字段,都可能被恶意用户写入 Prompt 注入内容。当 Agent 读取这些内容时,可能被操控。防御手段是在把外部数据喂给模型之前,做一层清洗和转义,把类似“忽略之前的指令”这种模式识别出来并处理掉。

实操心得:我在 MCP Server 里加了一个sanitize_external_text函数,对所有来自 OA/ERP 的文本字段做清洗。它会移除或转义一些危险模式,比如系统指令关键词、特殊分隔符等。这个函数不复杂,但能挡掉大部分低级注入。

5.3 可观测性:出问题时怎么查

Agent 上生产后,最怕的是“它为什么这么干”。用户说“我让它查库存,它给我创建了个订单”,你得能查出来是哪一步出了问题。

我的做法是在三个地方打日志:

MCP Server 侧:记录每次工具调用的入参、出参、耗时、调用者身份。这是最关键的日志,因为工具调用是 Agent 和外部系统交互的唯一通道。

Agent 编排侧:记录每轮模型调用的输入 Prompt、输出内容、选择的工具。这个日志量大,一般只保留最近 N 轮,或者采样记录。

业务系统侧:OA/ERP 自己的日志。这个你控制不了,但出问题时可以对照时间戳去查。

import logging import time logger = logging.getLogger("mcp_server") def log_tool_call(tool_name: str, user_id: str, params: dict): def decorator(func): async def wrapper(*args, **kwargs): start = time.time() try: result = await func(*args, **kwargs) logger.info( f"tool_call tool={tool_name} user={user_id} " f"params={params} duration={time.time()-start:.2f}s " f"status=success" ) return result except Exception as e: logger.error( f"tool_call tool={tool_name} user={user_id} " f"params={params} duration={time.time()-start:.2f}s " f"status=error error={str(e)}" ) raise return wrapper return decorator

有了这些日志,排查问题时可以按用户 ID 或者时间范围过滤,很快就能定位到是哪次工具调用出了问题。

6. 常见问题与排查技巧实录

6.1 工具调用失败的高频原因速查表

现象可能原因排查方法解决
Agent 说“没有权限”鉴权过期或身份未传递查 MCP Server 日志中的鉴权信息检查 session 续期逻辑
工具返回空结果字段映射错误或查询条件不对对比原始接口返回和映射后结果修正映射表
写入操作报“必填字段缺失”联动字段未预填检查 prepare 函数是否覆盖所有联动补充联动规则
调用超时后端系统慢或并发太高查耗时日志和限流日志调整限流或加缓存
Agent 选错工具工具描述不清晰看 Agent 的思考日志优化工具描述和示例
重复创建单据重试逻辑没有幂等保护查是否有重复流水号加幂等校验

6.2 泛微 OA 连接被阻止的完整解决路径

“泛微 OA 添加外部地址作为目录报错连接被阻止,因为它是由公共页面启动的,意图连接到”这个错误,我遇到过好几次。完整的解决路径是:

第一步,确认 Agent 界面的域名和端口。第二步,登录泛微管理后台,找到“系统管理-安全设置-可信站点”。第三步,把 Agent 的域名加进去,注意要带协议和端口,比如https://agent.example.com:8443。第四步,如果泛微版本较老,可能还需要改ecology/WEB-INF/prop/weaver.properties里的security.trusted.domains配置。第五步,重启泛微服务。

如果加了可信站点还是不行,检查一下是不是 HTTPS 证书问题。泛微对自签名证书比较敏感,Agent 侧最好用受信任的证书。

6.3 通达 OA MySQL 服务启动失败的排查

“通达 OA 无法启动 MySQL5-OA 服务”这个问题的原因比较多,我按概率从高到低列一下:

端口冲突是最常见的。通达 OA 自带的 MySQL 默认用 3306,如果机器上已经装了 MySQL 或者别的服务占了 3306,就起不来。解决办法是改通达 MySQL 的端口,在mysql/my.ini里改port参数。

数据目录权限问题排第二。通达 OA 的 MySQL 数据目录如果权限不对,MySQL 会拒绝启动。Windows 下检查数据目录的 NTFS 权限,Linux 下检查 owner 和 mode。

InnoDB 日志损坏排第三。如果之前非正常关机,InnoDB 的 redo log 可能损坏。可以在my.ini里临时加innodb_force_recovery=1启动,导出数据后重建。

磁盘满排第四。这个最容易被忽略,但确实会发生。检查一下数据盘剩余空间。

6.4 Agent 选错工具的优化技巧

Agent 选错工具,九成是工具描述的问题。我总结了几条优化原则:

描述里写清楚“什么时候用”和“什么时候不用”。比如“查询库存”工具的描述里,除了写“查询指定物料的库存数量”,还要写“当用户询问库存、可用量、在途量时使用此工具;当用户询问价格、供应商时不要使用此工具”。

参数描述里给示例。Agent 对示例的理解比抽象描述好。比如process_id的描述写成“审批流程实例 ID,例如 FLOW_20240115_001”。

工具数量控制在 20 个以内。工具太多,Agent 的选择准确率会下降。如果确实有很多工具,可以分组,用两级选择:先选组,再选具体工具。

定期用测试集验证。我维护了一个包含 50 个典型用户请求的测试集,每次改完工具描述就跑一遍,看准确率有没有下降。

6.5 写入操作的幂等设计

ERP 的写入操作必须做幂等,否则重试会产生重复单据。我的做法是:

在 MCP Server 侧,每个写入工具都要求传入一个request_id,这个 ID 由 Agent 生成(可以用 UUID)。Server 收到请求后,先查request_id是否已经处理过。如果处理过,直接返回上次的结果;如果没有,执行写入并记录request_id和结果。

async def create_order_idempotent(request_id: str, data: dict): # 查是否已处理 existing = await cache.get(f"order_req:{request_id}") if existing: return existing # 未处理,执行写入 result = await erp_client.create_order(data) # 记录结果,设置较长的过期时间 await cache.set(f"order_req:{request_id}", result, ex=86400) return result

这个request_id的生成和传递要在 Agent 的 Prompt 里说清楚,让模型知道每次写入都要带一个新的 ID,重试时要带同一个 ID。

7. 一些收尾的实操体会

这套东西我从零搭到上生产,前后花了大概三个月。最大的体会是:Agent 接企业系统,技术难点不在 AI 侧,在集成侧。模型的能力已经足够支撑大部分场景,真正花时间的是搞清楚 OA 的审批流怎么走、ERP 的字段怎么联动、鉴权怎么续期、并发怎么控制。

FDE 这个角色在这类项目里价值很大,因为他既懂技术又懂业务,能站在中间把两边翻译清楚。如果你在做类似的项目,我的建议是:前期花足够的时间去摸清楚目标系统的脾气,不要急着写代码。把鉴权、字段映射、联动规则、并发限制这些“脏活”先理清楚,后面的 Agent 编排反而简单。

MCP 这个协议目前还在快速演进,不同版本的实现有差异。选 MCP Server 框架的时候,优先选社区活跃、更新频繁的。我用的过程中遇到过协议版本不兼容的问题,后来统一了版本就好了。

最后分享一个小技巧:在 MCP Server 里加一个health_check工具,返回各个后端系统的连通状态和 session 有效期。Agent 在开始复杂任务之前可以先调一下这个工具,如果发现某个系统不可用,可以提前告诉用户,而不是执行到一半才报错。这个工具实现简单,但用户体验提升很明显。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询