Skip to main content
Glama
EastMG

shopping-mcp

by EastMG
README.md
# shopping-mcp

一个 MCP server,把**淘宝 / 京东 / 拼多多**的商品搜索、详情、转链(含优惠券)收成同一套工具,直接接进 DSH。

**只调官方开放平台接口。** 没有第三方代理、没有远程配置下发、没有凭证转发。

```
淘宝   淘宝联盟 / 淘宝客  →  https://eco.taobao.com/router/rest
京东   京东联盟            →  https://api.jd.com/routerjson
拼多多 多多进宝            →  https://gw-api.pinduoduo.com/api/router
```

## 为什么不用现成的 taoke-mcp

调研过 `liuliang520530/taoke-mcp`(125 星,功能对口),放弃的原因写在 [`docs/why-not-taoke-mcp.md`](docs/why-not-taoke-mcp.md)。一句话:它的授权走作者的 OAuth 应用,启动时还会从 `config.sinataoke.cn` 拉配置。本项目把这两条都换成了「你自己的 appkey + 本地 .env」。

## 快速开始

```powershell
# 1. 装依赖
uv sync

# 2. 先看状态(此时三家都是 mock 模式,没有任何凭证也能跑)
uv run shopping-mcp
```

`shopping-mcp` 是 **stdio** 传输的 MCP server,正常用法是被 DSH 拉起来,而不是手动运行。

### 挂进 DSH

**推荐做法:用 DSH 自带的 MCP 管理界面。**

如果你装了 [`dsh-mcp-manager-plus`](https://github.com/appthin/dsh-mcp-manager-plus)(设置里的「MCP 管理」页),这是最干净的一条路:写入由 DSH **主进程**完成,本来就有 profile 目录的写权限;profile 是 `patchReload: live`,所以**约 1 秒热生效,不需要重启**,而且是持久化的。

1. **设置 → MCP 管理 → 添加**
2. 粘贴下面这段(格式自动识别):

```json
{
  "mcpServers": {
    "shopping": {
      "command": "F:/DeepSeek/DeepSeek-Workplace/DSH-Workplace-01/shopping-mcp/.venv/Scripts/python.exe",
      "args": ["-m", "shopping_mcp.server"]
    }
  }
}
```

3. 保存后展开 `shopping` 那一行,应能看到 5 个工具:`shopping_status` / `search_products` / `get_product` / `convert_link`

凭证**不要写在这里**——server 自己读项目根目录的 `.env`。管理器的掩码机制(`KEY|SECRET|TOKEN` 类值会打码)也说明这类配置本就是给它看的,密钥不该放进去。

---

以下三条是**没有管理器时的备选**,需要改文件或换启动参数。注意在 DSH 会话内(沙箱里)写 `~/.dsh` 会被拒绝,所以要在这之外执行。

叠加层文件已经写好并做过结构校验:[`dsh/mcp-shopping.patch.yml`](dsh/mcp-shopping.patch.yml)。

**最省事:双击 [`scripts/register-mcp.cmd`](scripts/register-mcp.cmd)** —— 在普通 PowerShell 窗口执行(不要在 DSH 的终端里),会先备份再追加,写完校验 YAML。

或者在 PowerShell 里手动跑(等价):

```powershell
cd F:\DeepSeek\DeepSeek-Workplace\DSH-Workplace-01\shopping-mcp
$env:UV_CACHE_DIR="$PWD\..\.uv-cache-probe"
uv run --with pyyaml python scripts\register_mcp.py
```

其它两种姿势:

**A. 手动粘贴** — 把 `dsh/mcp-shopping.patch.yml` 里 `- insert:` 那一段追加到
`C:\Users\<你>\.dsh\profiles\desktop\cordis.patch.yml` 末尾,重启 DSH。

**B. 临时挂载** — 不写任何配置,每次启动带参数:

```powershell
dsh --profile desktop --patch "F:\DeepSeek\DeepSeek-Workplace\DSH-Workplace-01\shopping-mcp\dsh\mcp-shopping.patch.yml" web
```

凭证**不写在这些配置里**。server 启动时自己读项目根目录的 `.env`,所以配置文件里看不到密钥,也不会进 config dump。

启动后调用 `shopping_status` 应能看到 `serverName: shopping` 下的四个工具。

## 配置凭证

```powershell
Copy-Item .env.example .env
# 然后编辑 .env,只填你拿到的那几家
```

未填的平台会自动降级成 **mock 模式**:工具照样能用,返回的是 `mock.local` 域名下的假数据,字段和真实数据完全一致。这样在等审核期间就能把链路和 prompt 调好。

随时调用 `shopping_status` 看当前哪家是 live、哪家是 mock、缺哪个变量。

### 填完先自检

拿到 appkey 后别急着让 agent 搜商品,先跑一次凭证自检——它真的发一次请求,很便宜:

```powershell
uv run python scripts\probe_creds.py              # 验证所有已配置的平台
uv run python scripts\probe_creds.py --platform pdd
```

它会区分这几类失败(它们的报错长得不一样,脚本会把 code 和原始返回一起打出来):

| 现象 | 含义 |
|---|---|
| 签名错误 | secret 填错,或首尾有多余空格/引号 |
| 应用凭证无效 / 应用未上线 | key 复制错,或应用还在审核、没发布 |
| 权限不足 / 不支持的接口 | **最常见**:多多进宝没绑定、淘宝客权限没批下来 |
| PID / 推广位无效 | PID 取错 |
| 授权 session 失效 | OAuth token 过期,要重新授权 |

成功时会打印**平台真实返回的字段名**和**我们的归一化结果**并排对照,同时把完整原始返回写到 `_probe_raw_<platform>.json`。把这个输出贴给 agent,就能据此校准字段映射——现在的映射是照文档推的,只有对过真实返回才算数。

拼多多的完整申请流程见 [docs/apply-pdd.md](docs/apply-pdd.md)(含最容易漏的「多多进宝绑定」那一步)。

## 三家平台的申请门槛

### 拼多多多多进宝(门槛最低,建议先做这家)

1. 注册 [拼多多开放平台](https://open.pinduoduo.com/) 应用 → 拿 `client_id` / `client_secret`
2. 登录 [多多进宝](https://jinbao.pinduoduo.com/) → 推广管理 → 创建推广位 → 拿 `PID`
3. 给应用申请 `pdd.ddk.*`(多多进宝)接口权限

个人可注册,需实名。

### 京东联盟

1. [京东联盟](https://union.jd.com/) 注册推广者 → 拿网站 ID(`SITE_ID`)和推广位 ID(`POSITION_ID`)
2. 在京东开放平台创建应用 → 拿 `app_key` / `app_secret` → 申请 `jd.union.open.*` 权限

### 淘宝联盟(门槛最高)

1. [阿里妈妈](https://www.alimama.com/) 注册淘宝客 → 实名认证 + 推广媒体备案
2. [淘宝开放平台](https://open.taobao.com/) 创建应用 → 申请淘宝客 API 权限(审核通常 1-3 工作日)
3. 通过 `oauth.taobao.com` 授权拿 `session`
4. PID 形如 `mm_<memberId>_<siteId>_<adzoneId>`,`site_id` 与 `adzone_id` 由本程序自动从 PID 解析

三家具体权限目录以平台当期审核为准,本 README 不替代官方文档。

## 工具

| 工具 | 作用 |
|---|---|
| `shopping_status` | 三家凭证状态、当前模式、申请指引 |
| `search_products(platform, keyword, ...)` | 关键词搜索;支持 `has_coupon`、排序 |
| `get_product(platform, item_id)` | 按 id 查详情 |
| `convert_link(platform, item, sub_pid)` | 商品 id/链接 → 自己的推广链接 |

`platform` 取 `taobao` / `jd` / `pdd`。

统一后的商品字段:

```json
{
  "platform": "pdd",
  "item_id": "1234567890",
  "title": "...",
  "price": 179.0,              // 券后价 / 到手价
  "original_price": 259.0,
  "coupon_amount": 80.0,
  "commission_rate": 5.5,      // 百分比
  "commission_amount": 9.85,   // 预估佣金
  "sales": 12000,
  "shop_name": "...",
  "item_url": "...",
  "promotion_url": "...",
  "image_url": "..."
}
```

## 单位归一化(踩过的坑)

| 平台 | 坑 | 本项目怎么处理 |
|---|---|---|
| 三大平台通用 | `commission_rate` 历史上有过小数/百分比/万分比几种口径 | `_normalize_rate()` 按量级判断,原始值保留在 `raw` |
| 拼多多 | 所有金额单位是**分** | `_cents()` 统一除以 100 |
| 拼多多 | `promotion_rate` 是**千分比**(100 = 10%) | `_per_mille()` 除以 10 |
| 京东 | 业务结果藏在 `<method>_response.result` 里,而且是 **JSON 字符串** | 二次解析 |
| 淘宝 | `has_coupon` 要传字符串 `"true"` 不是布尔 | adapter 内转换 |

任何平台返回的原始 payload 都在 `raw` 字段(`to_dict()` 默认不输出,调试时可用)。**归一化一旦有偏差,用 `raw` 对照官方文档核对**——不要直接相信换算结果。

## 安全边界

- 凭证只从本地 `.env` 或进程环境读取,**不写进 DSH 配置**,所以不会出现在配置 dump 或共享 patch 里
- 不抓网页、不模拟登录、不碰支付环节
- 只读性质:搜索、查详情、生成推广链接。没有下单、没有改价、没有动账户设置
- `.env` 必须留在本机,不要提交到 git

## 当前状态

### 已验证

| 项 | 方式 | 结果 |
|---|---|---|
| 签名算法(三平台共用) | 与手算 MD5 对齐 | 通过 |
| 单位归一化(分→元、千分比→百分比、汇率三口径) | 单元断言 | 通过 |
| 淘宝 PID 解析 / 拼多多 `sales_tip` 解析 / `goods_sign` 判别 | 单元断言 | 通过 |
| 五个工具的 MCP 协议往返 | 内存传输握手 + `tools/list` + `tools/call` | 43/43 通过 |
| 商品字段一致性(价格 = 原价 − 券) | 断言 | 通过 |
| `has_coupon` 确实只返回有券商品 | 断言 | 通过 |
| 非法 platform 不崩、返回 `ok:false` | 断言 | 通过 |
| DSH 叠加层结构与 YAML 合法性 | 结构校验 | 通过 |
| smoke 的机器无关性 | 灌入假凭证后重跑 | 仍 43/43,不依赖本机 `.env` |
| **拼多多真实接口全链路** | **真实 appkey + 已完成授权备案** | **✅ 通过** |

**拼多多 live 实核(2026-10-18)**:

| 能力 | 结果 |
|---|---|
| 签名 | ✅ 真实网关接受(走到业务校验层,不再报签名错) |
| `pdd.ddk.goods.search` | ✅ 返回真实商品:`【原道】弦雪入耳DSP有线耳机` ¥49.99/¥60.99、券 ¥10、佣金率 3.0%、销量 4076 |
| `pdd.ddk.goods.detail` | ✅ 用 `goods_sign` 查到详情 |
| `pdd.ddk.goods.promotion.url.generate` | ✅ 生成真实推广链接 + 短链 `https://p.pinduoduo.com/lL4kjhCQ?sc=EFAC` |
| `pdd.ddk.member.authority.query` | ✅ `bind=1`(授权备案已完成) |
| 传数字 goods_id | ✅ 按预期拒绝,并给出「改用 extra.goods_sign」的指引 |

跑法:`uv run python scripts\smoke.py`(43 项,全 mock,无需凭证)

### 未验证

- **淘宝 / 京东从未调通** —— 还没申请这两家
- **DSH 里的 stdio 启动** 未验证。会话内沙箱禁止创建子进程双向管道(WinError 5),所以协议层是用内存传输测的;真实 stdio 拉起要等你注册并重启 DSH 后确认
- 拼多多 `ddk.goods.search` 对新应用有节流,同样关键词有时返回 0 条、有时 3 条。这不是 bug,重试即可

### 踩过的坑(已修,都是真实接口才暴露的)

| 坑 | 表现 | 修法 |
|---|---|---|
| `commission_rate` 走 `to_float()` | 该函数为金额舍入到 2 位,把 `0.155` 变成 `0.15` → 报 15.0% 而非 15.5% | 改用原始 `float()` |
| Windows 管道 stdout 默认 cp936 | 中文标题乱码 / `UnicodeEncodeError` | `main()` 里 `reconfigure(encoding="utf-8")` |
| 拼多多响应 key 不总镜像方法名 | `member.authority.query` 实际返回 `authority_query_response`,被误报 "unexpected response shape" | 回退到唯一的 `*_response` key |
| `pageSize` 必须 10-100 | 传 2 报 `50001 / 3000000` | adapter 内 clamp |
| `goods_id_list` 已下线 | 转链报 `40005`,详情报 `40004` | 改用 `goods_sign`(单数)/ `goods_sign_list`(转链) |
| `pdd.ddk.rp.prom.url.generate` 要 `p_id_list` | 报「请求参数不能为空:p_id_list」 | 传数组 |
| `promotion_url` 被误赋成图片地址 | 归一化结果里推广链接变成了商品图 | 置 `None`,只有转链后才有 |
| `sales` 是字符串 `"7万+"` | 直接 `to_int` 得到 None | 新增 `_parse_sales_tip()` |
| 拼多多搜索不返回店铺名 | `mall_name` 恒为空字符串 | 如实返回 `null`,不用品牌名冒充 |

## 开发

```powershell
uv sync                                              # 装依赖
uv run python scripts\smoke.py                       # 36 项检查,全部走 mock,不需要任何凭证
uv run --with pyyaml python scripts\check_patch.py    # 校验 DSH 叠加层结构
uv run python scripts\probe_creds.py                 # 用真实凭证验证字段映射
```

`scripts/register_mcp.py` 负责把服务注册进 DSH profile,幂等、自动备份、写后校验。
`scripts/register-mcp.cmd` 是它的双击入口。

`smoke.py` 是 **hermetic** 的:它会忽略真实 `.env` 并清掉环境里所有凭证变量,所以无论你机器上配没配 appkey,结果都一样。填了真凭证之后它也不会误报。

## License

[MIT](LICENSE) © 2026 EastMG

本项目只调用各平台官方开放平台 API,不包含任何平台的数据或代码。使用前请自行确认符合各平台的开放平台协议与推广规则。

TDQS

A4.1/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: status/config check, keyword search, single-item detail lookup, and affiliate-link conversion. The descriptions explicitly differentiate item_id semantics per platform (num_iid vs skuId vs goods_sign), leaving no room for misselection.

Naming Consistency4/5

Three tools follow a clean verb_noun pattern (search_products, get_product, convert_link), but shopping_status uses a domain prefix plus noun rather than a verb, a minor deviation. The set is still highly readable and predictable.

Tool Count4/5

Four tools is a tight but sensible scope for an affiliate-shopping workflow (search, detail, link conversion, plus a setup/status helper). It leans slightly thin — a category or batch-conversion tool could earn its place — but nothing is redundant.

Completeness4/5

The core affiliate lifecycle (discover via search, inspect via get_product, monetize via convert_link, verify setup via status) is fully covered. Minor gaps like batch conversion or order/reporting operations exist but are workable around.

Maintenance

ActivityMaintained
ResponsivenessNo issues