pdd-selection-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pdd_selection_mcp-0.1.0/PKG-INFO +6 -0
- pdd_selection_mcp-0.1.0/pdd_selection_mcp/__init__.py +310 -0
- pdd_selection_mcp-0.1.0/pdd_selection_mcp.egg-info/PKG-INFO +6 -0
- pdd_selection_mcp-0.1.0/pdd_selection_mcp.egg-info/SOURCES.txt +8 -0
- pdd_selection_mcp-0.1.0/pdd_selection_mcp.egg-info/dependency_links.txt +1 -0
- pdd_selection_mcp-0.1.0/pdd_selection_mcp.egg-info/entry_points.txt +2 -0
- pdd_selection_mcp-0.1.0/pdd_selection_mcp.egg-info/requires.txt +1 -0
- pdd_selection_mcp-0.1.0/pdd_selection_mcp.egg-info/top_level.txt +1 -0
- pdd_selection_mcp-0.1.0/pyproject.toml +13 -0
- pdd_selection_mcp-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
拼多多精选 MCP 服务 v0.1.0 - 魔搭部署版
|
|
3
|
+
通过腾讯云SCF代理调用拼多多联盟API
|
|
4
|
+
统一 type+params 请求格式 + X-Proxy-Token 鉴权
|
|
5
|
+
5个接口:search / detail / generate_url / recommend / pid_query
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
import urllib.request
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from mcp.server.fastmcp import FastMCP
|
|
13
|
+
|
|
14
|
+
# 腾讯云SCF代理地址(拼多多精选专用)
|
|
15
|
+
PROXY_URL = os.environ.get("PDD_PROXY_URL", "https://1439498936-1iog1h3lb1.ap-guangzhou.tencentscf.com")
|
|
16
|
+
PROXY_TOKEN = os.environ.get("PROXY_TOKEN", "tp_8k2mX9vQ4z")
|
|
17
|
+
|
|
18
|
+
mcp = FastMCP("pdd-selection")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def call_proxy(rtype, params):
|
|
22
|
+
"""调用SCF代理 - type+params格式"""
|
|
23
|
+
data = json.dumps({"type": rtype, "params": params}).encode("utf-8")
|
|
24
|
+
req = urllib.request.Request(
|
|
25
|
+
PROXY_URL,
|
|
26
|
+
data=data,
|
|
27
|
+
method="POST",
|
|
28
|
+
headers={
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
"X-Proxy-Token": PROXY_TOKEN,
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
try:
|
|
34
|
+
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
35
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
36
|
+
except Exception as e:
|
|
37
|
+
return {"ok": False, "error": str(e)}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def format_search_results(data):
|
|
41
|
+
"""格式化搜索结果"""
|
|
42
|
+
if not data.get("ok"):
|
|
43
|
+
return "搜索失败:" + data.get("error", "未知错误")
|
|
44
|
+
|
|
45
|
+
results = data.get("data", [])
|
|
46
|
+
total = data.get("total", 0)
|
|
47
|
+
|
|
48
|
+
if not results:
|
|
49
|
+
return "没有找到符合条件的商品,建议换一个关键词试试。"
|
|
50
|
+
|
|
51
|
+
lines = ["共找到 {} 件商品:\n".format(total)]
|
|
52
|
+
|
|
53
|
+
for i, item in enumerate(results, 1):
|
|
54
|
+
name = item.get("goods_name", "")
|
|
55
|
+
final_price = item.get("final_price", 0)
|
|
56
|
+
min_group_price = item.get("min_group_price", 0)
|
|
57
|
+
coupon_discount = item.get("coupon_discount", 0)
|
|
58
|
+
has_coupon = item.get("has_coupon", False)
|
|
59
|
+
sales = item.get("sales_tip", "")
|
|
60
|
+
mall = item.get("mall_name", "")
|
|
61
|
+
category = item.get("category_name", "")
|
|
62
|
+
goods_sign = item.get("goods_sign", "")
|
|
63
|
+
image = item.get("goods_image_url", "")
|
|
64
|
+
|
|
65
|
+
# 价格
|
|
66
|
+
if coupon_discount > 0 and has_coupon:
|
|
67
|
+
price_str = "到手价¥{}(拼团价¥{} - 券¥{})".format(final_price, min_group_price, coupon_discount)
|
|
68
|
+
else:
|
|
69
|
+
price_str = "拼团价¥{}".format(min_group_price)
|
|
70
|
+
|
|
71
|
+
# 优惠券标记
|
|
72
|
+
coupon_tag = " | 有券" if has_coupon else ""
|
|
73
|
+
|
|
74
|
+
# 销量
|
|
75
|
+
sales_str = " | 销量{}".format(sales) if sales else ""
|
|
76
|
+
|
|
77
|
+
# 店铺
|
|
78
|
+
mall_str = " | {}".format(mall) if mall else ""
|
|
79
|
+
|
|
80
|
+
# 分类
|
|
81
|
+
cat_str = " | {}".format(category) if category else ""
|
|
82
|
+
|
|
83
|
+
line = "{}. {}{}{}\n {}{}{}{}\n goods_sign: {}".format(
|
|
84
|
+
i, name, coupon_tag, sales_str,
|
|
85
|
+
price_str, mall_str, cat_str,
|
|
86
|
+
" | 图:{}".format(image[:50]) + "..." if image else "",
|
|
87
|
+
goods_sign
|
|
88
|
+
)
|
|
89
|
+
lines.append(line)
|
|
90
|
+
|
|
91
|
+
return "\n\n".join(lines)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def format_detail(data):
|
|
95
|
+
"""格式化商品详情"""
|
|
96
|
+
if not data.get("ok"):
|
|
97
|
+
return "查询失败:" + data.get("error", "未知错误")
|
|
98
|
+
|
|
99
|
+
item = data.get("data", {})
|
|
100
|
+
if not item:
|
|
101
|
+
return "未找到该商品信息"
|
|
102
|
+
|
|
103
|
+
name = item.get("goods_name", "")
|
|
104
|
+
desc = item.get("goods_desc", "")
|
|
105
|
+
brand = item.get("brand_name", "")
|
|
106
|
+
mall = item.get("mall_name", "")
|
|
107
|
+
category = item.get("category_name", "")
|
|
108
|
+
final_price = item.get("final_price", 0)
|
|
109
|
+
min_group_price = item.get("min_group_price", 0)
|
|
110
|
+
min_normal_price = item.get("min_normal_price", 0)
|
|
111
|
+
coupon_discount = item.get("coupon_discount", 0)
|
|
112
|
+
coupon_min_order = item.get("coupon_min_order", 0)
|
|
113
|
+
has_coupon = item.get("has_coupon", False)
|
|
114
|
+
sales = item.get("sales_tip", "")
|
|
115
|
+
goods_sign = item.get("goods_sign", "")
|
|
116
|
+
goods_id = item.get("goods_id", "")
|
|
117
|
+
image = item.get("goods_image_url", "")
|
|
118
|
+
gallery = item.get("goods_gallery_urls", [])
|
|
119
|
+
sku_list = item.get("sku_list", [])
|
|
120
|
+
|
|
121
|
+
lines = [
|
|
122
|
+
name,
|
|
123
|
+
"品牌:{} | 店铺:{} | 分类:{}".format(brand or "未知", mall or "未知", category or "未知"),
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
# 价格信息
|
|
127
|
+
if coupon_discount > 0 and has_coupon:
|
|
128
|
+
lines.append("到手价:¥{}(拼团价¥{} - 优惠券¥{},满¥{}可用)".format(
|
|
129
|
+
final_price, min_group_price, coupon_discount, coupon_min_order))
|
|
130
|
+
else:
|
|
131
|
+
lines.append("拼团价:¥{} | 单买价:¥{}".format(min_group_price, min_normal_price))
|
|
132
|
+
|
|
133
|
+
if sales:
|
|
134
|
+
lines.append("销量:{}".format(sales))
|
|
135
|
+
|
|
136
|
+
if image:
|
|
137
|
+
lines.append("主图:{}".format(image))
|
|
138
|
+
|
|
139
|
+
if sku_list:
|
|
140
|
+
lines.append("规格:")
|
|
141
|
+
for sku in sku_list[:10]:
|
|
142
|
+
lines.append(" {} - ¥{}".format(sku.get("spec", ""), sku.get("price", 0)))
|
|
143
|
+
|
|
144
|
+
lines.append("goods_sign: {}".format(goods_sign))
|
|
145
|
+
lines.append("goods_id: {}".format(goods_id))
|
|
146
|
+
|
|
147
|
+
return "\n".join(lines)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def format_generate_url(data):
|
|
151
|
+
"""格式化推广链接"""
|
|
152
|
+
if not data.get("ok"):
|
|
153
|
+
return "生成失败:" + data.get("error", "未知错误")
|
|
154
|
+
|
|
155
|
+
item = data.get("data", {})
|
|
156
|
+
if not item:
|
|
157
|
+
return "生成失败:返回为空"
|
|
158
|
+
|
|
159
|
+
lines = []
|
|
160
|
+
short_url = item.get("short_url", "")
|
|
161
|
+
mobile_url = item.get("mobile_url", "")
|
|
162
|
+
|
|
163
|
+
if short_url:
|
|
164
|
+
lines.append("短链接:{}".format(short_url))
|
|
165
|
+
if mobile_url:
|
|
166
|
+
lines.append("移动端链接:{}".format(mobile_url))
|
|
167
|
+
|
|
168
|
+
we_app_id = item.get("we_app_app_id", "")
|
|
169
|
+
we_app_path = item.get("we_app_page_path", "")
|
|
170
|
+
if we_app_id:
|
|
171
|
+
lines.append("小程序:{} {}".format(we_app_id, we_app_path))
|
|
172
|
+
|
|
173
|
+
return "\n".join(lines) if lines else "生成失败:无链接返回"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def format_recommend(data):
|
|
177
|
+
"""格式化推荐结果"""
|
|
178
|
+
if not data.get("ok"):
|
|
179
|
+
return "推荐失败:" + data.get("error", "未知错误")
|
|
180
|
+
|
|
181
|
+
results = data.get("data", [])
|
|
182
|
+
if not results:
|
|
183
|
+
return "暂无推荐商品"
|
|
184
|
+
|
|
185
|
+
lines = ["为您推荐 {} 件商品:\n".format(len(results))]
|
|
186
|
+
|
|
187
|
+
for i, item in enumerate(results, 1):
|
|
188
|
+
name = item.get("goods_name", "")
|
|
189
|
+
final_price = item.get("final_price", 0)
|
|
190
|
+
min_group_price = item.get("min_group_price", 0)
|
|
191
|
+
coupon_discount = item.get("coupon_discount", 0)
|
|
192
|
+
has_coupon = item.get("has_coupon", False)
|
|
193
|
+
sales = item.get("sales_tip", "")
|
|
194
|
+
goods_sign = item.get("goods_sign", "")
|
|
195
|
+
|
|
196
|
+
if coupon_discount > 0 and has_coupon:
|
|
197
|
+
price_str = "到手价¥{}".format(final_price)
|
|
198
|
+
else:
|
|
199
|
+
price_str = "¥{}".format(min_group_price)
|
|
200
|
+
|
|
201
|
+
sales_str = " | 销量{}".format(sales) if sales else ""
|
|
202
|
+
line = "{}. {} | {}{}\n goods_sign: {}".format(i, name, price_str, sales_str, goods_sign)
|
|
203
|
+
lines.append(line)
|
|
204
|
+
|
|
205
|
+
return "\n\n".join(lines)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@mcp.tool()
|
|
209
|
+
def search_goods(keyword: str, sort_type: Optional[int] = None,
|
|
210
|
+
cat_id: Optional[int] = None, has_coupon: Optional[bool] = None,
|
|
211
|
+
price_min: Optional[float] = None, price_max: Optional[float] = None,
|
|
212
|
+
page: int = 1, page_size: int = 20) -> str:
|
|
213
|
+
"""
|
|
214
|
+
拼多多商品搜索 - 关键词搜索拼多多商品
|
|
215
|
+
|
|
216
|
+
适用场景:搜索任意商品,支持价格筛选、优惠券筛选、分类筛选、排序等
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
keyword: 搜索关键词,如"iPhone 17"、"夏季连衣裙"、"跑步鞋"
|
|
220
|
+
sort_type: 排序方式(可选):0-综合排序 1-价格升序 2-价格降序 3-销量排序 6-佣金比例 9-优惠券面额
|
|
221
|
+
cat_id: 分类ID(可选)
|
|
222
|
+
has_coupon: 仅显示有优惠券的商品(可选)
|
|
223
|
+
price_min: 最低价格(可选,单位元)
|
|
224
|
+
price_max: 最高价格(可选,单位元)
|
|
225
|
+
page: 页码,默认1
|
|
226
|
+
page_size: 每页数量,默认20,最小10最大100
|
|
227
|
+
"""
|
|
228
|
+
params = {"keyword": keyword, "page": page, "page_size": max(10, min(page_size, 100))}
|
|
229
|
+
if sort_type is not None:
|
|
230
|
+
params["sort_type"] = sort_type
|
|
231
|
+
if cat_id is not None:
|
|
232
|
+
params["cat_id"] = cat_id
|
|
233
|
+
if has_coupon is not None:
|
|
234
|
+
params["with_coupon"] = "true" if has_coupon else "false"
|
|
235
|
+
if price_min is not None:
|
|
236
|
+
params["range_from"] = int(price_min * 100)
|
|
237
|
+
if price_max is not None:
|
|
238
|
+
params["range_to"] = int(price_max * 100)
|
|
239
|
+
|
|
240
|
+
data = call_proxy("search", params)
|
|
241
|
+
return format_search_results(data)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@mcp.tool()
|
|
245
|
+
def get_goods_detail(goods_sign: str) -> str:
|
|
246
|
+
"""
|
|
247
|
+
拼多多商品详情 - 通过goods_sign获取商品完整信息
|
|
248
|
+
|
|
249
|
+
适用场景:已知商品goods_sign,获取详情(价格、优惠券、SKU规格、图片等)
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
goods_sign: 商品签名ID(从搜索或推荐结果获取),如"E932cKJQRhht7tuxwfDAxrupzXBiZpWMgg_JQPw6VPiN3"
|
|
253
|
+
"""
|
|
254
|
+
data = call_proxy("detail", {"goods_sign": goods_sign})
|
|
255
|
+
return format_detail(data)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@mcp.tool()
|
|
259
|
+
def generate_promotion_url(goods_sign: str, generate_short_url: bool = True,
|
|
260
|
+
generate_we_app: bool = True, generate_h5: bool = True) -> str:
|
|
261
|
+
"""
|
|
262
|
+
拼多多推广链接生成 - 生成商品推广短链接和小程序路径
|
|
263
|
+
|
|
264
|
+
适用场景:为商品生成推广链接,包含短链接、移动端链接和小程序路径
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
goods_sign: 商品签名ID(从搜索或推荐结果获取)
|
|
268
|
+
generate_short_url: 生成短链接,默认true
|
|
269
|
+
generate_we_app: 生成小程序路径,默认true
|
|
270
|
+
generate_h5: 生成H5链接,默认true
|
|
271
|
+
"""
|
|
272
|
+
data = call_proxy("generate_url", {
|
|
273
|
+
"goods_sign_list": [goods_sign],
|
|
274
|
+
"generate_short_url": generate_short_url,
|
|
275
|
+
"generate_we_app": generate_we_app,
|
|
276
|
+
"generate_h5": generate_h5,
|
|
277
|
+
})
|
|
278
|
+
return format_generate_url(data)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@mcp.tool()
|
|
282
|
+
def get_recommend_goods(channel_type: int = 1, activity_tags: Optional[str] = None,
|
|
283
|
+
limit: int = 20) -> str:
|
|
284
|
+
"""
|
|
285
|
+
拼多多推荐商品 - 获取热门推荐商品列表
|
|
286
|
+
|
|
287
|
+
适用场景:获取热销榜、猜你喜欢、相似商品推荐等
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
channel_type: 频道类型:1-今日销量榜 3-相似商品推荐 4-猜你喜欢 5-实时热销榜 6-实时收益榜
|
|
291
|
+
activity_tags: 活动标签(可选):4-秒杀 7-百亿补贴 10851-千万补贴 31-品牌黑标 10564-精选爆品官方直推
|
|
292
|
+
limit: 返回数量,默认20,最小10最大100
|
|
293
|
+
"""
|
|
294
|
+
params = {
|
|
295
|
+
"channel_type": channel_type,
|
|
296
|
+
"limit": max(10, min(limit, 100)),
|
|
297
|
+
}
|
|
298
|
+
if activity_tags:
|
|
299
|
+
params["activity_tags"] = activity_tags
|
|
300
|
+
|
|
301
|
+
data = call_proxy("recommend", params)
|
|
302
|
+
return format_recommend(data)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def main():
|
|
306
|
+
mcp.run()
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
if __name__ == "__main__":
|
|
310
|
+
main()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
pdd_selection_mcp/__init__.py
|
|
3
|
+
pdd_selection_mcp.egg-info/PKG-INFO
|
|
4
|
+
pdd_selection_mcp.egg-info/SOURCES.txt
|
|
5
|
+
pdd_selection_mcp.egg-info/dependency_links.txt
|
|
6
|
+
pdd_selection_mcp.egg-info/entry_points.txt
|
|
7
|
+
pdd_selection_mcp.egg-info/requires.txt
|
|
8
|
+
pdd_selection_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mcp
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pdd_selection_mcp
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pdd-selection-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "拼多多精选MCP服务 - 全网比价好帮手,通过SCF代理调用拼多多联盟API"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = ["mcp"]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
pdd-selection-mcp = "pdd_selection_mcp:main"
|