最近一段时间,我一直在折腾一件事:把MCP协议接到CRMEB电商系统上,让AI Agent能直接读写商品、订单、库存这些核心业务数据。说实话,最初的想法很简单——CRMEB这玩意儿功能确实全,但每次想扩展点新能力,都得动PHP代码或者等官方插件,太不灵活。MCP的出现给了另一个思路:与其在系统内部改代码,不如在系统外面架一层“AI翻译官”,让大模型通过标准协议直接操作电商系统的能力。
这篇文章就来聊聊这套方案的完整实现过程,包括MCP协议的核心概念、如何用Python给CRMEB快速搭一个MCP Server、实际业务场景怎么用,以及我在实操中踩过的坑。如果你正在用CRMEB做电商项目,或者对MCP怎么落地到真实业务感兴趣,这篇应该能给你一个可以直接抄作业的参考。
1. 为什么要把MCP接到CRMEB上
1.1 CRMEB的扩展困境
CRMEB是一套基于ThinkPHP框架的开源电商系统,商城、分销、营销、会员、优惠券这些基础能力开箱即用,后台管理界面也做得比较完善,国内不少中小商家都在用它。但真用到生产环境里,你会发现它有一个绕不开的问题——扩展能力被框架锁死了。
官方提供的插件机制和钩子(Hook)确实能覆盖一部分需求,但一旦遇到定制化业务,比如对接第三方物流、接入新的支付渠道、做复杂的营销规则引擎,你还是得改源码。改源码的问题在于:每次官方升级版本,你都得重新合并代码,搞不好就是一场灾难。所以用CRMEB做项目的团队,几乎都有一套自己的“二次开发守则”,核心思想就是:能不改核心文件就不改,能用API就用API。
CRMEB本身提供了完整的RESTful API接口,商品、订单、用户、支付这些模块都有对应的接口文档。这一点很重要,因为这意味着我们不需要侵入系统内部,就能通过API拿到业务数据、执行业务操作。这为MCP的接入打下了很好的基础。
1.2 MCP到底解决什么问题
MCP的全称是Model Context Protocol,也就是模型上下文协议。你可以把它理解成AI世界的USB-C接口——在MCP出现之前,每个AI应用要对接外部数据,都得单独写一套集成代码,A应用对接数据库写一套,B应用对接CRM又写一套,重复劳动且难以复用。MCP把这件事标准化了:AI应用(Host)通过统一的协议连接MCP Server,Server背后再对接具体的数据源或工具,一次开发,处处复用。
接入MCP之后,大模型不再是“只会聊天”的对话机器人,而是能真正动手干活的执行者。比如用户问“帮我查一下订单20250101001现在到什么状态了”,AI能直接调用CRMEB的订单查询接口,拿到结果再组织语言回复。这就是MCP的核心价值——把大模型的理解能力跟业务系统的执行能力打通。
我最初尝试过直接用Function Calling的方式接CRMEB API,也能跑通,但问题在于:函数定义散落在代码里,每次新增接口都要改Prompt或者重新注册函数,维护成本高;而且不同的AI客户端(Claude Desktop、Cursor、自研应用)对接方式还不一样,换一个客户端就要重写一遍。MCP把这块统一了,Server注册好工具,任何支持MCP的客户端都能直接发现并调用,这才是它最吸引我的地方。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. MCP协议核心概念与工作原理
2.1 一次请求的完整链路
我们先看一条完整的数据链路,这比看一堆概念文档管用得多。假设用户通过支持MCP的AI助手(Host)问了一句“最近7天卖得最好的5个商品是什么”,背后发生的事情大概是这样的:
- Host把用户问题发给大模型,大模型发现需要查询销售数据。
- 大模型根据已注册的工具列表,决定调用某个工具(比如
get_top_products)。 - Host通过MCP协议向MCP Server发送工具调用请求,参数是
{days: 7, limit: 5}。 - MCP Server收到请求后,调用CRMEB的后端API或直接查数据库。
- Server把结构化数据返回给Host,大模型把结果组织成自然语言回复用户。
整个过程中,MCP Server就是那个“翻译官”,它把大模型发来的意图转成CRMEB能理解的API请求,再把CRMEB返回的数据转成大模型能看懂的结构化JSON。对CRMEB来说,它只是在接受正常的API请求,完全感知不到背后是个AI在操作。
2.2 三种核心原语:Tools、Resources、Prompts
MCP协议定义了三种核心原语,理解这三个概念,你基本就掌握了MCP的用法。
**Tools(工具)**是最常用的原语,代表一个可执行的操作,比如“查询订单状态”“创建商品”“修改库存”。工具需要定义名称、描述、输入参数(JSON Schema格式),大模型根据描述来决定什么时候调用、传什么参数。工具是会执行动作的,所以有副作用,要特别注意权限控制。
**Resources(资源)**用于暴露数据,比如“用户列表”“商品分类树”“订单导出文件”。资源更像是一个可读取的数据源,客户端可以主动拉取,也可以让模型读取作为上下文。举个例子,你让AI解释“这个项目的订单量为什么波动”,它可能需要先读取订单数据的资源。
**Prompts(提示模板)**是预置的提示词模板,让用户可以一键触发特定任务。比如定义一个analyze_sales提示模板,用户只要输入“分析销售”,AI就会按照预设的步骤去查询数据、生成报告。这个适合把复杂的分析流程固化成标准操作。
实操中,90%的场景用Tools就够了,Resources在需要提供大量上下文时才有明显价值,Prompts更像锦上添花。我建议第一次上手先专注把Tools做好,其他的后面再补。
2.3 为什么选FastMCP而不是自己写协议
MCP的协议层其实不复杂,本质就是基于JSON-RPC 2.0的消息交换,理论上你可以自己封装HTTP或SSE通道来通信。但我强烈不建议这么做——官方SDK和社区框架已经帮你处理了协议协商、会话管理、工具发现这些琐碎事,自己造轮子纯属浪费时间。
Python生态里,我最常用的是FastMCP(前身是mcp-python-sdk的快速封装),它用装饰器就能定义工具,几行代码就能跑起一个完整的MCP Server。如果你更熟悉Node.js,@modelcontextprotocol/sdk也很好用。我的习惯是:能用FastMCP就用FastMCP,代码量少一半,而且对SSE(Server-Sent Events)和Stdio两种传输方式都支持得很好。
3. 动手搭建:给CRMEB包一层MCP服务
3.1 方案选型与整体架构
在动手写代码之前,我先确定了几条设计原则:
- 不侵入CRMEB源码,所有操作走官方API,这样CRMEB升级不影响我们。
- MCP Server独立部署,作为中间层单独跑一个服务,方便单独升级和扩展。
- 连接方式选SSE,因为要支持远程访问(比如从Claude Desktop连接部署在服务器上的Server),而Stdio模式只适合本地进程通信。
- 鉴权走Token,CRMEB API需要管理员Token或用户Token,我们把Token配置在服务端环境变量里,不暴露给客户端。
整体架构就是一个典型的星型结构:AI客户端(Host)连到MCP Server,MCP Server通过HTTP调用CRMEB的API。中间没有复杂的消息队列或事件总线,简单直接。
3.2 项目初始化和基础配置
我的技术栈选的是Python + FastMCP,因为FastMCP的代码量最小,而且处理并发请求的性能足够应对中小电商场景。先安装依赖:
bash复制pip install fastmcp httpx python-dotenv
项目的目录结构很简单:
code复制crmeb-mcp-server/
├── server.py # MCP Server主入口
├── crmeb_client.py # CRMEB API客户端封装
├── tools/ # 按业务域划分的工具集
│ ├── order_tools.py
│ ├── product_tools.py
│ ├── user_tools.py
│ └── data_tools.py
├── .env # 环境变量:API地址、Token等
└── requirements.txt
.env文件里至少需要配置这几项:
bash复制CRMEB_API_BASE=https://your-domain.com/api
CRMEB_TOKEN=your_admin_token
CRMEB_APP_ID=your_app_id
MCP_SERVER_HOST=0.0.0.0
MCP_SERVER_PORT=8000
注意:Token一定要通过环境变量或密钥管理服务注入,千万别硬编码在代码或提交到Git仓库里。我在项目里用
python-dotenv加载环境变量,并在.gitignore里把.env文件排除掉。
3.3 封装CRMEB API客户端
CRMEB的API遵循RESTful风格,返回结构一般是{status: 200, message: "ok", data: {...}}。我们需要统一封装请求逻辑,处理认证头、错误码和超时。这里用httpx.AsyncClient,因为FastMCP支持异步工具,用异步HTTP能提高并发吞吐。
python复制# crmeb_client.py
import os
import httpx
from typing import Any
class CRMEBClient:
def __init__(self):
self.base_url = os.getenv("CRMEB_API_BASE")
self.token = os.getenv("CRMEB_TOKEN")
self.client = httpx.AsyncClient(
base_url=self.base_url,
headers={"Authorization": f"Bearer {self.token}"},
timeout=30.0
)
async def get(self, path: str, params: dict | None = None) -> dict:
resp = await self.client.get(path, params=params)
resp.raise_for_status()
return self._parse(resp.json())
async def post(self, path: str, json: dict | None = None) -> dict:
resp = await self.client.post(path, json=json)
resp.raise_for_status()
return self._parse(resp.json())
@staticmethod
def _parse(body: dict[str, Any]) -> dict[str, Any]:
if body.get("status") != 200:
raise RuntimeError(f"CRMEB API error: {body.get('message')}")
return body.get("data", body)
为什么要做这一层封装?因为MCP工具函数的职责是“把参数转成业务动作”,不应该关心HTTP细节。有了CRMEBClient,工具层代码就能写得非常简洁,后面加新工具的时候,复用现有Client就行,不用每个工具都写一遍请求逻辑。
3.4 定义核心MCP工具
接下来我们定义几个最常用的工具。先看订单查询工具,这是所有电商场景里频率最高的:
python复制# tools/order_tools.py
from fastmcp import FastMCP
def register_order_tools(mcp: FastMCP, crmeb: CRMEBClient):
@mcp.tool()
async def get_order_by_no(order_no: str) -> dict:
"""根据订单编号查询订单详情,包括商品列表、支付状态、物流信息。"""
return await crmeb.get(f"/order/detail/{order_no}")
@mcp.tool()
async def list_recent_orders(days: int = 7, status: str = "") -> list[dict]:
"""查询最近N天的订单列表,可按状态筛选,status可选值:pending, paid, shipped, completed, cancelled。"""
params = {"days": days}
if status:
params["status"] = status
data = await crmeb.get("/order/list", params=params)
return data if isinstance(data, list) else data.get("list", [])
@mcp.tool()
async def update_order_status(order_no: str, action: str) -> dict:
"""对订单执行状态变更操作,action可选值:ship(发货)、complete(完成)、cancel(取消)。"""
return await crmeb.post(f"/order/{order_no}/{action}")
再看商品和库存类工具:
python复制# tools/product_tools.py
def register_product_tools(mcp: FastMCP, crmeb: CRMEBClient):
@mcp.tool()
async def get_product_info(product_id: int) -> dict:
"""根据商品ID查询商品详情,包括价格、库存、规格、上下架状态。"""
return await crmeb.get(f"/product/detail/{product_id}")
@mcp.tool()
async def update_product_stock(product_id: int, sku_id: int, delta: int) -> dict:
"""调整商品SKU的库存数量,delta为正数表示增加,负数表示减少。"""
return await crmeb.post(f"/product/stock", json={
"product_id": product_id,
"sku_id": sku_id,
"delta": delta
})
@mcp.tool()
async def search_products(keyword: str, page: int = 1, limit: int = 20) -> list[dict]:
"""根据关键词搜索商品,返回商品ID、名称、价格、库存等基础信息。"""
return await crmeb.get("/product/search", params={
"keyword": keyword, "page": page, "limit": limit
})
定义工具的要点是描述要写得足够清楚。大模型是靠工具的描述来决定何时调用、如何调用的,描述写得太模糊,模型就可能猜错用途。比如get_order_by_no的描述里我特意标注了“包括商品列表、支付状态、物流信息”,这样模型查到订单后就能直接回答用户关于这些信息的追问。
3.5 服务启动与工具注册
最后在主入口把各个业务域的工具注册进去,启动SSE服务:
python复制# server.py
import os
from fastmcp import FastMCP
from dotenv import load_dotenv
from crmeb_client import CRMEBClient
from tools.order_tools import register_order_tools
from tools.product_tools import register_product_tools
from tools.user_tools import register_user_tools
from tools.data_tools import register_data_tools
load_dotenv()
mcp = FastMCP("crmeb-mcp-server")
crmeb = CRMEBClient()
register_order_tools(mcp, crmeb)
register_product_tools(mcp, crmeb)
register_user_tools(mcp, crmeb)
register_data_tools(mcp, crmeb)
if __name__ == "__main__":
mcp.run(transport="sse", host=os.getenv("MCP_SERVER_HOST", "0.0.0.0"), port=int(os.getenv("MCP_SERVER_PORT", "8000")))
启动命令很简单:
bash复制uvicorn server:app --host 0.0.0.0 --port 8000
FastMCP在底层会把mcp.run包装成一个ASGI应用,所以直接用uvicorn启动即可。启动之后,服务会在/sse端点暴露SSE接口,AI客户端(比如Claude Desktop)在配置里填上这个地址,就能自动发现并调用我们定义的所有工具。
3.6 客户端配置实操
这里以Claude Desktop为例,在配置文件claude_desktop_config.json里添加MCP Server配置:
json复制{
"mcpServers": {
"crmeb": {
"url": "http://your-server-ip:8000/sse"
}
}
}
提示:配置完成后重启Claude Desktop,在对话界面里应该能看到MCP工具已经加载。如果没有出现,先确认Server进程是否正常启动、防火墙是否放行了8000端口。
Cursor的配置方式类似,在.cursor/mcp.json里写上同样的配置即可。支持MCP的客户端越来越多了,比如JetBrains的AI Assistant、开源的OpenCode等,配置模式都差不多。
4. 实战场景:AI如何操作电商系统
4.1 智能客服:语义理解加订单查询
MCP接入后最直接的效果,就是把普通客服变成了“AI + 人工”的双层体系。用户问“我的手机到了吗”,大模型先理解这背后需要查询订单,然后向MCP Server发起list_recent_orders或get_order_by_no调用,拿到物流状态后自然回复。
这里有个细节值得注意:如果用户没有提供订单号,模型需要引导用户提供,或者通过手机号、用户名去查询关联订单。我们可以在工具侧补一个get_order_by_phone接口,让模型多一个选择。实操中,多准备几个“从不同入口查询同一业务”的工具,能显著提升AI回答的准确率。
4.2 商品运营:批量操作的正确姿势
商品上下架、库存调整、价格修改这些操作,通过MCP工具也能完成。比如运营说“把上一批临期商品库存减50件”,AI会调用search_products找出目标商品,再逐个调用update_product_stock调整库存。
但这里要特别小心:批量操作一定要做好参数校验和确认机制。我在开发时给库存调整工具加了参数范围限制,delta的绝对值不能超过库存上限,同时工具描述里明确标注“这是不可逆操作”。另外,我建议对写操作类工具设计一个“二次确认”模式——第一次调用先返回待执行的动作预览(比如“将商品A库存从100调整为50”),由用户确认后再真正执行。这种方式虽然多一步交互,但能大幅降低误操作风险。
4.3 数据看板:自然语言查销售报表
这是我认为最有想象力的一块。以前想看销售数据,要么登录后台点一堆筛选条件,要么让开发写SQL。有了MCP,直接在AI对话里说“帮我对比上周和这周的GMV,按天拆开”,AI就会调用销售统计工具,拿到每天的GMV数据,然后自己画表格、写结论。
我实现了一个get_sales_summary工具,接收起止日期、统计维度(按天/按周/按商品)、订单状态等参数,返回聚合好的销售数据。大模型拿到这些数据后,不仅能展示数字,还能做简单的对比分析——比如指出“周三出现明显下降,可能跟活动结束有关”。这种“自然语言报表”的能力,对小团队来说非常实用,不用花几万块去买BI工具。
4.4 一个完整的联动案例
为了让你更直观地感受效果,我把一个典型的联动场景串起来:
运营输入:“把销量前10的商品库存全部加100,然后生成一份补货建议。”
AI执行过程:
- 调用
get_sales_summary查询近30天销量排行,取前10名。- 对每个商品调用
update_product_stock,delta设为100。- 调用
get_product_info获取这些商品的当前库存和销量,生成补货建议。- 返回处理结果和补货建议表。
整个过程只花了几十秒,而以前人工操作至少需要小半天。当然,这个场景里的“批量库存调整”动作风险较高,所以我加了确认环节——AI会先把商品列表和调整方案列出来,用户确认后才执行第2步。这套“先规划、后执行、再汇报”的模式,是我认为AI操作业务系统最安全的架构。
5. 常见问题与排查实录
5.1 工具注册失败或客户端找不到工具
我踩过不少这方面的坑。最典型的是:MCP Server明明启动了,但客户端就是发现不了工具,或者提示“tool registration failed”。
排查思路按顺序来:
- 先确认Server的健康状态:直接访问
http://server:8000/sse,看是否正常建立SSE连接。 - 再确认工具注册是否真的成功:FastMCP启动时会在控制台打印已注册的工具列表,检查有没有你要的工具。
- 确认工具名是否重复:如果两个工具注册了相同的名字,后者会覆盖前者,导致某些工具“消失”。
- 检查Schema是否能被正确序列化:工具参数如果用了复杂的自定义类型,某些客户端可能解析失败。保持参数为基本类型(str、int、float、bool、list、dict)是最稳妥的。
有一个隐藏坑是版本兼容:不同版本的MCP协议(比如2024-11-05和2025-03-26两版)之间,客户端和Server的握手逻辑有差异。如果客户端提示协议版本不支持,优先升级Server端的SDK到最新版,同时确认客户端用的是较新的版本。
5.2 接口超时与上下文截断
MCP工具调用默认有超时限制,而CRMEB的一些报表接口响应很慢(特别是数据量大时),很容易触发超时。我的解决方案是:
- 把
httpx.AsyncClient的timeout调大到30秒甚至60秒。 - 在工具描述里明确标注“该接口可能较慢”,让模型在调用时告知用户耐心等待。
- 对长时间运行的查询,考虑异步任务模式:工具先返回“任务已提交,任务ID为xxx”,用户稍后通过进度查询工具获取结果。
上下文截断是另一个常见问题。当工具返回的数据量很大(比如查询了1000条订单),会瞬间撑爆模型的上下文窗口,导致对话卡顿或丢失历史信息。解决办法是:给所有列表查询工具做好分页和字段筛选。我一般在工具描述里建议模型只请求必要的字段,同时在Server端对返回数据做截断——比如最多返回50条记录,多余的部分提示“共X条记录,仅显示前50条,可按页查询”。
5.3 幻觉调用与参数校验
大模型有时会“自作聪明”地乱传参数。比如用户问“帮我查一下今天的订单”,模型可能调get_order_by_no并传了一个根本不存在的订单号。避免幻觉的关键是:工具描述要准确,参数要做严格校验。
我在Server端给每个工具增加了参数校验逻辑,比如订单号必须匹配^[A-Za-z0-9]{10,32}$的格式,商品ID必须是正整数。校验失败时返回明确的错误信息,让模型知道错在哪、如何修正。
还有一个很实用的做法:对写操作工具增加一个dry_run参数。当dry_run=true时,工具只校验参数并返回将要执行的结果预览,不真正修改数据。模型在不确定参数是否正确时,可以先跑一次dry_run看结果,再决定是否真实执行。这个机制极大地减少了误操作。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 客户端连不上Server | SSE端点路径不对或防火墙拦截 | 确认使用/sse端点;放行端口;检查Server日志 |
| 工具列表为空 | 工具注册出错或协议版本不兼容 | 检查控制台日志;升级SDK;简化参数类型 |
| 调用工具超时 | CRMEB接口响应慢或网络问题 | 调大timeout;用异步任务模式;检查网络延迟 |
| 返回数据太大撑爆上下文 | 查询结果集过大 | 强制分页和字段裁剪;限制返回最大条数 |
| 模型把参数传错 | 工具描述不清晰或幻觉 | 完善描述;严格参数校验;提供dry_run模式 |
| 写操作误执行 | 没有确认机制 | 增加二次确认流程;敏感操作单独授权 |
6. 扩展思路与经验沉淀
6.1 从“单点工具”到“多系统Agent”
这一步做完,CRMEB就只是MCP连接的一个业务系统了。你完全可以用同样的思路,把进销存系统、ERP、物流查询、电子发票平台都包装成各自的MCP Server,然后让AI统一调配。比如一个用户问“这个订单能今天发货吗”,AI会先查CRMEB的订单状态,再调用物流平台的运费模板算时效,最后给出结论。
MCP协议支持一个Host连接多个Server,这两个Server互不干扰。这种“一个AI大脑,多个业务插件”的架构,正好符合电商系统不断接入新服务的演进路径。而且每个MCP Server是独立部署的,升级其中一个不影响其他系统,比在CRMEB内部堆模块要清爽得多。
6.2 安全底线的三条原则
做业务系统接入,安全永远是第一位的。我给自己定了几条硬规矩:
- 最小权限原则:MCP Server使用的CRMEB账号权限,只给“查询”和“必要操作”的最小集合。绝对不能用管理员账号直接跑写操作工具。
- 操作审计:所有通过MCP Server执行的写操作,都记录日志(操作人、时间、参数、结果)。出了问题能追溯。
- 敏感操作独立授权:涉及退款、删除、批量改价这类高风险操作,不在MCP工具里开放,或者要求必须经过人工在后台二次审批。
6.3 踩过几次坑后的个人建议
经过这段时间的折腾,我的体会是:MCP的价值不在于“技术多炫”,而在于它把AI能力和业务系统的对接成本降到了极低。以前做AI客服要写专门的中间服务、维护函数清单,现在用MCP Server一套协议全解决了。
最后再分享一个小技巧:刚开始搭建时,不要想着把所有业务都做成MCP工具,先从“只读查询”开始,跑通链路后,再逐步加写操作。这样风险可控,也更容易让团队接受这套新架构。等大家用习惯了,再慢慢把库存调整、订单处理、营销配置这些高频操作纳入进来,你会发现自己手里的这套电商系统,确实有了“无限扩展”的可能。
