mcpField
S
shop-mcp

shop-mcp

本地服务
github.com/hello532商业与营销07 周前更新

关于 shop-mcp

shop-mcp 是面向需要查询 Shopify 商品目录和库存的 LLM Agent 的单文件 Python MCP 服务,无外部依赖。四个只读工具涵盖商品搜索与详情、按 SKU 和地点查询库存及低库存报告,并内置自测以检查协议和数据处理行为。

主要功能

  1. 01搜索商品并查看详情
  2. 02按 SKU 和地点查询库存
  3. 03报告低于库存阈值的变体
  4. 04仅使用 Python 标准库
  5. 05内置协议与行为自测

适用场景

  1. 01查找符合客户查询的商品
  2. 02查询各门店地点的 SKU 库存
  3. 03识别需要补货的商品变体

MCP 相关

该服务通过 stdio 与 MCP 客户端通信,并提供四个只读 Shopify 商品目录和库存工具。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"hello532-shop": {
"command": "uvx",
"args": [
"shop-mcp"
],
"env": {
"SHOPIFY_SHOP_DOMAIN": "<SHOPIFY_SHOP_DOMAIN>",
"SHOPIFY_ADMIN_TOKEN": "<SHOPIFY_ADMIN_TOKEN>"
}
}
}
}
启动命令uvx shop-mcp
需要配置的环境变量
SHOPIFY_SHOP_DOMAINYour store domain, e.g. your-shop.myshopify.com
SHOPIFY_ADMIN_TOKEN密钥Shopify Admin API access token (shpat_...) with read_products and read_inventory scopes

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypishop-mcp1.0.1

工具

4

收录的工具列表 · 更新于 3 周前

参数类型必填说明
limitinteger否最多返回的产品数,1-50。
querystring是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 包并按主机启动方式运行时才会暴露;此前从未有测试这样做:

  1. 代码读取的是 SHOPIFY_SHOP;但本 README 和包清单都告诉用户导出 SHOPIFY_SHOP_DOMAIN。任何按照文档操作的用户都会得到一个始终未配置的服务器。180 条断言全部通过,因为没有一条会将代码与文档进行比较。
  2. 在凭据存在之前,tools/list 都会返回 [],因此主机看到的是一个空服务器,并报告它已损坏——而 tools/call 中清晰易懂的未配置商店提示无法触达,因为没有任何工具可供调用。该代码上方的文档字符串写明了相反的要求,而下方的测试还断言了这个缺陷:eq(tools, [], ...)。列出工具从来不依赖凭据;descriptors() 完全不访问实例状态,现在已改为 staticmethod。
  3. 模块文档字符串中宣传了 --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。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。