关于 shop-mcp
shop-mcp 是面向需要查询 Shopify 商品目录和库存的 LLM Agent 的单文件 Python MCP 服务,无外部依赖。四个只读工具涵盖商品搜索与详情、按 SKU 和地点查询库存及低库存报告,并内置自测以检查协议和数据处理行为。
主要功能
- 01搜索商品并查看详情
- 02按 SKU 和地点查询库存
- 03报告低于库存阈值的变体
- 04仅使用 Python 标准库
- 05内置协议与行为自测
适用场景
- 01查找符合客户查询的商品
- 02查询各门店地点的 SKU 库存
- 03识别需要补货的商品变体
MCP 相关
该服务通过 stdio 与 MCP 客户端通信,并提供四个只读 Shopify 商品目录和库存工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
4收录的工具列表 · 更新于 3 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| limit | integer | 否 | 最多返回的产品数,1-50。 |
| query | string | 是 | Shopify 搜索语法,例如 'title:shirt'、'vendor:Acme'、'status:ACTIVE' 或普通词语。 |
详细介绍
shop-mcp
一个模型上下文协议服务,让 LLM Agent 能够通过 stdio 查询 Shopify 商店的商品目录和库存——只需一个文件,且仅使用 Python 标准库。
无需 MCP SDK。无需 requests。无需 GraphQL 客户端。安装只需运行 python3 shop_mcp.py。
$ python3 shop_mcp.py --self-test
all green: 200 assertions
此命令无需凭据,也无需网络。它体现了此仓库的核心:协议层和工具层都经过实际测试,因为 Shopify 传输层是在接缝处被替换,而非在边界处被模拟。
从 PyPI 安装后,同一命令会报告 183;少掉的 17 条断言是打包方式所致,并不代表检查较弱:
$ uvx --from shop-mcp shop-mcp --self-test
all green: 183 assertions
manifest.json(5 条断言)、llms-install.md(11 条)和 README.md(1 条)特意不随包发布到 site-packages 中——manifest 的 entry_point 指向一个在已安装副本中不存在的包路径,因此将其打包会导致正确的安装也失败。缺少这些文件时,三个测试组都会跳过而不是失败,所以计数会变化,测试结果却不会。克隆仓库即可运行全部 200 条测试。
此服务是一个实践示例,而非产品系列。我会以固定价格承接相同形式的项目——基于现有数据的 stdio MCP 服务,配备自测套件,并验证该套件能检测出注入的缺陷: hello532.github.io/services.html,或联系 [email protected]。此页面上的内容都无需付费;无论如何,都欢迎提交 issue 和 PR。
为什么手写协议
因为 stdio MCP 服务的故障模式在本地都不可见,却会在宿主中造成致命问题。下面列出的每一种都是此文件力求避免的真实缺陷,而且都有一条指明缺陷的断言:
- 在 stdout 输出诊断信息。 多一个
print()就会破坏客户端接下来的解析。在你自己运行服务时,一切看起来都正常。这里的所有诊断信息都会发送到 stderr,并且有一项测试会断言完整会话期间 stdout 始终为空。 - 响应通知。
notifications/initialized没有id,因此对它的回复会成为一条没有待处理请求的消息。严格的客户端会将其视为协议违规并断开连接。 - 将
id: 0视为通知。if msg.get("id")对零值会返回假,因此从零开始编号请求的客户端,其第一个调用会被悄悄丢弃。应检查字段是否存在,而非其真假值。 - 将工具失败作为 JSON-RPC 错误发送。 JSON-RPC 错误用于格式错误的请求。已运行但失败的工具必须返回正常结果,并设置
isError: true,同时以文本说明原因——否则模型看不到这条消息,也就无法修正自己的参数。 - 回显未知的
protocolVersion。 如果客户端请求服务器不认识的版本,服务器却表示同意,双方就会误以为正在使用一个彼此都未实现的规范版本。此处会回退到规范自身的默认版本2025-03-26,并说明这一点。 - 美化回复格式。 缩进后的 JSON 会包含换行符,而换行符就是帧分隔符。一条消息会变成多条损坏的消息。
工具
| 工具 | 回答的问题 |
|---|---|
search_products | “我们卖哪些符合 X 条件的商品?”——商品信息和库存总量 |
get_product | 获取某个商品的完整信息,包括每个变体的 SKU、价格和库存 |
check_inventory | 查询某个 SKU 在各地点的库存:可用、已承诺、现有库存 |
low_stock_report | 列出库存低于或等于指定阈值的变体,并按库存从低到高排序 |
这四种工具经过精心挑选,分别回答店主实际会提出的问题。扩展工具范围并不难,但会降低模型的选择能力。
不属于协议层的正确性
其中有 3 项断言覆盖了会导致自信地给出错误答案的失误,这比报错更糟:
- 未加引号的 SKU。
sku:SH 1与sku:"SH 1"是不同的查询。前者会悄悄匹配到错误的变体,并把它们的库存报告成你的库存。SKU 会加上引号,内部的引号会转义。 - 将 null 数量读作零。 Shopify 对不跟踪库存的变体会返回
null。如果将其强制转换为0,它就会永远出现在每份补货报告中。未跟踪库存和缺货是不同的事实,必须保持区分。 scan_exhausted。low_stock_report会扫描数量有限的变体。如果扫描达到上限,“没有库存不足的商品”就无法与“我没有查得足够多”区分开来。因此,结果会说明实际是哪种情况,模型也可以据此说明。
此外,还有任何 Shopify 客户端都需要遵守、但多数客户端会忽略的传输规则:THROTTLED GraphQL 响应的状态码是 200,必须重试,不能视为成功;401 不得重试,因为等待无法修复无效令牌;退避时间必须实际递增。
已验证与未验证
**每次运行时由自测验证:**共 200 项断言,覆盖握手、帧处理、通知处理、ID 是否存在、错误映射、架构严格性、重试与退避策略、SKU 引号处理、null 数量处理、阈值边界以及扫描耗尽情况。线路格式取自官方 mcp Python SDK 的 types.py(LATEST_PROTOCOL_VERSION、CallToolResult、ServerCapabilities),而非凭记忆编写。
**未验证:**此项目从未针对真实 Shopify 商店运行过。此仓库没有凭据,也没有记录的 API 会话。Shopify Admin GraphQL 查询依据文档中的架构编写,所有相关代码路径都通过传输层模拟对象进行了测试——但尚未证明它们能与真实商店完成往返通信;这些测试替身只是我对 Shopify 行为的模拟,并非 Shopify 本身。
如实说明这一区别才是正确做法,这也正是 gpt-ads-feed 所划定的界限。模糊这一区别的 README,就是在要求读者把信任放在不该信任的事情上。
会失败的断言
mutation_test.sh 会在源代码的副本中注入已知缺陷,并断言每个缺陷都会让 --self-test 报错,同时指出捕获该缺陷的断言。它还会在搜索模式过时后标记 NO-OP EDIT——因为未能应用的变异什么都没测试,却看起来全绿;这正是让测试套件不如没有的失败模式:值得信赖,却空无一物。
它首次运行就发现了套件中的真实弱点,而且三个问题的模式都相同:缺陷确实被检测到了,但靠的是异常,而不是有名称的断言,因此错误消息毫无说明作用,后面的每条断言也都没能运行。
其中两个问题是未处理的 KeyError: 'result':缺陷将回复变成 JSON-RPC 错误后,代码仍然尝试索引回复。修复方法是通过形状检查器访问结果,因此同一缺陷现在会报告 工具崩溃会返回结果,因此循环能够继续:reply is a JSON-RPC error {'code': -32603, ...},而后续三条断言也都会分别报告自己的判定。
第三个问题是一个裸的设置调用——S.Tools(c).search_products(...),它只为让下面的断言有意义而存在。禁用限流分支时,它会引发异常,导致测试在该断言运行前就中止。修复方法是使用 completes(),它与 raises() 正好相反:现在该缺陷会报告 收到带有 THROTTLED 的 200 响应可以继续处理,而非硬性失败:raised ShopifyError: Throttled [THROTTLED],既指出规则,也保留了原因。
还有三个缺陷,只有在服务器打包为 .mcpb 包并按主机启动方式运行时才会暴露;此前从未有测试这样做:
- 代码读取的是
SHOPIFY_SHOP;但本 README 和包清单都告诉用户导出SHOPIFY_SHOP_DOMAIN。任何按照文档操作的用户都会得到一个始终未配置的服务器。180 条断言全部通过,因为没有一条会将代码与文档进行比较。 - 在凭据存在之前,
tools/list都会返回[],因此主机看到的是一个空服务器,并报告它已损坏——而tools/call中清晰易懂的未配置商店提示无法触达,因为没有任何工具可供调用。该代码上方的文档字符串写明了相反的要求,而下方的测试还断言了这个缺陷:eq(tools, [], ...)。列出工具从来不依赖凭据;descriptors()完全不访问实例状态,现在已改为staticmethod。 - 模块文档字符串中宣传了
--self-test,但它在包内会崩溃,因为包里只包含服务器文件。现在包中也包含测试套件。
第一个修复随后以一种值得记录的方式破坏了测试框架。缺少 README.md 时,新断言会失败;而测试框架只复制两个文件,因此每个变异都会触发它。运行仍然显示 17 caught,但其中六个被计入 README.md is present,而不是各自的标签:这样一来,即使六条真实断言失效,套件仍可能全绿。缺少 README 是打包问题,不是代码缺陷。现在,这项关键比较改为针对模块文档字符串运行,因为它会随源代码一同发布;测试框架也会复制 README,因此交叉检查确实有效。
全部 23 个变异都会被明确指出问题所在的断言捕获,并且每个变异都会计入自己的标签。
使用方法
从 PyPI 安装——无需克隆:
export SHOPIFY_SHOP_DOMAIN=your-shop.myshopify.com
export SHOPIFY_ADMIN_TOKEN=shpat_... # read_products, read_inventory
uvx shop-mcp # or: pip install shop-mcp && shop-mcp
Claude Desktop / 任意 MCP 主机:
{
"mcpServers": {
"shop": {
"command": "uvx",
"args": ["shop-mcp"],
"env": {
"SHOPIFY_SHOP_DOMAIN": "your-shop.myshopify.com",
"SHOPIFY_ADMIN_TOKEN": "shpat_..."
}
}
}
}
如果想先阅读源代码再运行,也可以从克隆的仓库运行——这正是单文件、无依赖设计的意义,也是运行完整 200 条断言测试套件的唯一方式:
python3 shop_mcp.py --self-test # 200 here, 183 installed; see above
python3 shop_mcp.py
{ "command": "python3", "args": ["/absolute/path/to/shop_mcp.py"] }
即使未设置凭据,服务器仍会完成握手并提供 tools/list,随后返回 isError,并指出缺失的变量。无法读取 tools/list 的主机会报告“服务器已损坏”,让你去错误的地方查找问题。
MIT。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。