@sea-ecommerce/admin-mcp-server 0.1.0 → 0.1.2
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.
- package/README.md +8 -2
- package/dist/derlux-admin-mcp.js +716 -76
- package/package.json +3 -2
- package/skill/SKILL.md +215 -0
package/README.md
CHANGED
|
@@ -11,9 +11,15 @@
|
|
|
11
11
|
| 工具 | 说明 | 写操作 |
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| `list_products` / `get_product` | 查商品 | 读 |
|
|
14
|
-
| `
|
|
14
|
+
| `list_sales_channels` / `list_collections` | 列渠道 / 集合 | 读 |
|
|
15
|
+
| `get_translations` | 查多语言翻译(核对翻译用) | 读 |
|
|
16
|
+
| `create_product` / `update_product` / `deactivate_product` | 增 / 改 / 下线商品(含图片、SKU、渠道、物理/海关字段、metadata 合并) | 写(dry_run 默认) |
|
|
17
|
+
| `update_product_prices` | 替换变体价格(多币种) | 写(dry_run 默认) |
|
|
18
|
+
| `upload_product_image` / `add_product_to_sales_channel` | 追加图片 / 挂渠道 | 写(dry_run 默认) |
|
|
19
|
+
| `create_product_content` / `get_product_content` | 详情页富内容块(hero/richtext/specs/图集/faq/video) | 写 / 读 |
|
|
20
|
+
| `set_product_stock` | 给商品建仓/改库存(自动建库存条目,幂等) | 写(dry_run 默认) |
|
|
15
21
|
| `list_stock_locations` / `create_stock_location` | 列 / 增仓库 | 写(dry_run 默认) |
|
|
16
|
-
| `get_inventory_levels` / `update_inventory_level` | 查 /
|
|
22
|
+
| `get_inventory_levels` / `update_inventory_level` | 查 / 改库存水平 | 写(dry_run 默认) |
|
|
17
23
|
| `list_orders` / `get_order` | 查订单 | 读 |
|
|
18
24
|
| `list_warehouse_tasks` / `list_return_requests` | 查履约 / 退货 | 读 |
|
|
19
25
|
| `search_docs` / `ask_usage` | 基于 `docs/` 回答后台使用问题 | 读 |
|
package/dist/derlux-admin-mcp.js
CHANGED
|
@@ -21638,6 +21638,15 @@ var AuthManager = class {
|
|
|
21638
21638
|
};
|
|
21639
21639
|
|
|
21640
21640
|
// src/client.ts
|
|
21641
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
21642
|
+
function mimeOf(path) {
|
|
21643
|
+
const ext = path.split(".").pop()?.toLowerCase() || "";
|
|
21644
|
+
if (ext === "webp") return "image/webp";
|
|
21645
|
+
if (ext === "png") return "image/png";
|
|
21646
|
+
if (ext === "jpg" || ext === "jpeg") return "image/jpeg";
|
|
21647
|
+
if (ext === "gif") return "image/gif";
|
|
21648
|
+
return "application/octet-stream";
|
|
21649
|
+
}
|
|
21641
21650
|
var MedusaAdminError = class extends Error {
|
|
21642
21651
|
constructor(status, message) {
|
|
21643
21652
|
super(message);
|
|
@@ -21685,12 +21694,122 @@ var MedusaAdminClient = class {
|
|
|
21685
21694
|
getProduct(id) {
|
|
21686
21695
|
return this.request("GET", `/admin/products/${encodeURIComponent(id)}`);
|
|
21687
21696
|
}
|
|
21697
|
+
/** 列出销售渠道(用于默认渠道解析) */
|
|
21698
|
+
listSalesChannels(params = {}) {
|
|
21699
|
+
return this.request("GET", `/admin/sales-channels${buildQuery(params)}`);
|
|
21700
|
+
}
|
|
21688
21701
|
createProduct(body) {
|
|
21689
21702
|
return this.request("POST", "/admin/products", body);
|
|
21690
21703
|
}
|
|
21691
21704
|
updateProduct(id, body) {
|
|
21692
21705
|
return this.request("POST", `/admin/products/${encodeURIComponent(id)}`, body);
|
|
21693
21706
|
}
|
|
21707
|
+
/**
|
|
21708
|
+
* 整体替换某变体的价格(多币种)。Medusa v2 价格挂在「变体」上,
|
|
21709
|
+
* POST /admin/products/{id} 携带 variants:[{id, prices}] 即整体替换该变体价格(不会累加)。
|
|
21710
|
+
* prices 数组形如 [{ currency_code, amount }, ...],amount 为主货币单位(元),切勿 ×100。
|
|
21711
|
+
*/
|
|
21712
|
+
updateProductPrices(productId, variantId, prices) {
|
|
21713
|
+
return this.request("POST", `/admin/products/${encodeURIComponent(productId)}`, {
|
|
21714
|
+
variants: [{ id: variantId, prices }]
|
|
21715
|
+
});
|
|
21716
|
+
}
|
|
21717
|
+
/** 批量写入翻译(POST /admin/translations/batch)。reference 一般为 "product"。 */
|
|
21718
|
+
async batchTranslations(create, update = []) {
|
|
21719
|
+
return this.request("POST", "/admin/translations/batch", { create, update });
|
|
21720
|
+
}
|
|
21721
|
+
/**
|
|
21722
|
+
* 查询翻译(GET /admin/translations)——用于核对多语言是否写入成功。
|
|
21723
|
+
* ?reference=product&reference_id=<productId>[&locale_code=zh]
|
|
21724
|
+
*/
|
|
21725
|
+
listTranslations(params = {}) {
|
|
21726
|
+
return this.request("GET", `/admin/translations${buildQuery(params)}`);
|
|
21727
|
+
}
|
|
21728
|
+
/** 列出集合(GET /admin/collections),用于按名称找 collection_id。 */
|
|
21729
|
+
listCollections(params = {}) {
|
|
21730
|
+
return this.request("GET", `/admin/collections${buildQuery(params)}`);
|
|
21731
|
+
}
|
|
21732
|
+
/**
|
|
21733
|
+
* 把一组商品加入销售渠道(POST /admin/sales-channels/{id}/products)。
|
|
21734
|
+
* ⚠️ 注意:Medusa v2 该端点挂在「渠道」下,不是商品下;body 为 { add:[productIds], remove:[] }。
|
|
21735
|
+
* 商品只有挂到渠道,storefront(按 publishable key → 渠道)才能查到,否则详情页拉不到。
|
|
21736
|
+
*/
|
|
21737
|
+
addProductsToSalesChannel(channelId, productIds) {
|
|
21738
|
+
return this.request("POST", `/admin/sales-channels/${encodeURIComponent(channelId)}/products`, {
|
|
21739
|
+
add: productIds,
|
|
21740
|
+
remove: []
|
|
21741
|
+
});
|
|
21742
|
+
}
|
|
21743
|
+
// ─── 商品详情页富内容(product_content 模块)───
|
|
21744
|
+
/**
|
|
21745
|
+
* 读取某商品某语言的富内容块(GET /admin/product-contents)。
|
|
21746
|
+
* locale 用区域码:en-US / zh-CN / ms-MY / id-ID(与 storefront useProductContent 的 localeMap 一致)。
|
|
21747
|
+
* 返回 { product_content: { id, product_id, locale, status, blocks(JSON 字符串) } | null }。
|
|
21748
|
+
*/
|
|
21749
|
+
getProductContent(productId, locale = "en-US", status = "published") {
|
|
21750
|
+
return this.request(
|
|
21751
|
+
"GET",
|
|
21752
|
+
`/admin/product-contents${buildQuery({ product_id: productId, locale, status })}`
|
|
21753
|
+
);
|
|
21754
|
+
}
|
|
21755
|
+
/**
|
|
21756
|
+
* 创建/更新某商品某语言的富内容块(POST /admin/product-contents)。
|
|
21757
|
+
* body: { product_id, locale, status?, blocks };blocks 为 ContentBlock[] 数组
|
|
21758
|
+
* (type ∈ hero/richtext/image_gallery/video/faq/specs)。按 (product_id, locale) upsert。
|
|
21759
|
+
*/
|
|
21760
|
+
createProductContent(body) {
|
|
21761
|
+
return this.request("POST", "/admin/product-contents", body);
|
|
21762
|
+
}
|
|
21763
|
+
/**
|
|
21764
|
+
* 上传一组文件到 Medusa 文件服务(POST /admin/uploads,multipart 字段名 files),
|
|
21765
|
+
* 返回上传后的公开 URL 列表。
|
|
21766
|
+
*/
|
|
21767
|
+
async uploadFiles(files) {
|
|
21768
|
+
if (!files.length) return [];
|
|
21769
|
+
const form = new FormData();
|
|
21770
|
+
for (const f of files) {
|
|
21771
|
+
form.append("files", new Blob([new Uint8Array(f.buffer)], { type: f.mimetype }), f.filename);
|
|
21772
|
+
}
|
|
21773
|
+
const doPost = async () => {
|
|
21774
|
+
const res = await fetch(`${this.config.backendUrl}/admin/uploads`, {
|
|
21775
|
+
method: "POST",
|
|
21776
|
+
headers: this.auth.headers(),
|
|
21777
|
+
body: form
|
|
21778
|
+
});
|
|
21779
|
+
if (res.status === 401) {
|
|
21780
|
+
this.auth.clear();
|
|
21781
|
+
await this.auth.login();
|
|
21782
|
+
return doPost();
|
|
21783
|
+
}
|
|
21784
|
+
if (!res.ok) {
|
|
21785
|
+
const text = await res.text();
|
|
21786
|
+
throw new MedusaAdminError(res.status, text || res.statusText);
|
|
21787
|
+
}
|
|
21788
|
+
return await res.json();
|
|
21789
|
+
};
|
|
21790
|
+
const data = await doPost();
|
|
21791
|
+
return (data.files || []).map((f) => f.url);
|
|
21792
|
+
}
|
|
21793
|
+
/**
|
|
21794
|
+
* 把一组「本地文件路径 或 图片 URL」解析为可挂到商品的 URL:
|
|
21795
|
+
* - http(s) URL 原样透传;
|
|
21796
|
+
* - 本地路径读取后走 /admin/uploads 上传,返回 URL。
|
|
21797
|
+
*/
|
|
21798
|
+
async uploadProductAssets(pathsOrUrls) {
|
|
21799
|
+
const urls = [];
|
|
21800
|
+
const toUpload = [];
|
|
21801
|
+
for (const p of pathsOrUrls) {
|
|
21802
|
+
if (/^https?:\/\//i.test(p)) {
|
|
21803
|
+
urls.push(p);
|
|
21804
|
+
continue;
|
|
21805
|
+
}
|
|
21806
|
+
if (!existsSync2(p)) throw new MedusaAdminError(0, `\u56FE\u7247\u6587\u4EF6\u4E0D\u5B58\u5728: ${p}`);
|
|
21807
|
+
const buf = readFileSync2(p);
|
|
21808
|
+
toUpload.push({ filename: p.split("/").pop() || "image", mimetype: mimeOf(p), buffer: buf });
|
|
21809
|
+
}
|
|
21810
|
+
if (toUpload.length) urls.push(...await this.uploadFiles(toUpload));
|
|
21811
|
+
return urls;
|
|
21812
|
+
}
|
|
21694
21813
|
// ─── 仓库(StockLocation)/ 库存 ───
|
|
21695
21814
|
listStockLocations(params = {}) {
|
|
21696
21815
|
return this.request("GET", `/admin/stock-locations${buildQuery(params)}`);
|
|
@@ -21698,25 +21817,66 @@ var MedusaAdminClient = class {
|
|
|
21698
21817
|
createStockLocation(body) {
|
|
21699
21818
|
return this.request("POST", "/admin/stock-locations", body);
|
|
21700
21819
|
}
|
|
21701
|
-
/**
|
|
21702
|
-
|
|
21820
|
+
/**
|
|
21821
|
+
* 库存条目列表(GET /admin/inventory-items,Medusa 2.21 契约)。
|
|
21822
|
+
* ⚠️ 2.21 没有 GET /admin/stock-locations/{id}/inventory-items 路由;
|
|
21823
|
+
* 按仓库过滤用查询参数:`location_levels.location_id=<sloc_xxx>`(点号写法)。
|
|
21824
|
+
* 常用参数:q(匹配 sku/title)、sku(精确)、location_levels.location_id。
|
|
21825
|
+
*/
|
|
21826
|
+
listInventoryItems(params = {}) {
|
|
21827
|
+
return this.request("GET", `/admin/inventory-items${buildQuery(params)}`);
|
|
21828
|
+
}
|
|
21829
|
+
/**
|
|
21830
|
+
* 创建库存条目(POST /admin/inventory-items)。
|
|
21831
|
+
* body: { sku, title?, weight?, length?, height?, width?, requires_shipping?, ... },
|
|
21832
|
+
* 可附带 location_levels: [{ location_id, stocked_quantity }] 一步建仓。
|
|
21833
|
+
* sku 有唯一约束(未删除项),同 sku 重复创建会 409。
|
|
21834
|
+
*/
|
|
21835
|
+
createInventoryItem(body) {
|
|
21836
|
+
return this.request("POST", "/admin/inventory-items", body);
|
|
21837
|
+
}
|
|
21838
|
+
/** 某库存条目已有的库存水平(GET /admin/inventory-items/{id}/location-levels[?location_id=]) */
|
|
21839
|
+
getInventoryItemLevels(inventoryItemId, params = {}) {
|
|
21703
21840
|
return this.request(
|
|
21704
21841
|
"GET",
|
|
21705
|
-
`/admin/
|
|
21842
|
+
`/admin/inventory-items/${encodeURIComponent(inventoryItemId)}/location-levels${buildQuery(params)}`
|
|
21706
21843
|
);
|
|
21707
21844
|
}
|
|
21708
|
-
/**
|
|
21709
|
-
|
|
21710
|
-
* ⚠️ 实施时按 Medusa 2.21 官方契约核对端点与字段
|
|
21711
|
-
* (v2 常见为 POST /admin/inventory-items/{id}/location-levels,body 含 location_id + stocked_quantity)。
|
|
21712
|
-
*/
|
|
21713
|
-
updateInventoryLevel(inventoryItemId, body) {
|
|
21845
|
+
/** 新建某库存条目在某仓库的库存水平(POST .../location-levels,body: { location_id, stocked_quantity? }) */
|
|
21846
|
+
createInventoryItemLevel(inventoryItemId, body) {
|
|
21714
21847
|
return this.request(
|
|
21715
21848
|
"POST",
|
|
21716
21849
|
`/admin/inventory-items/${encodeURIComponent(inventoryItemId)}/location-levels`,
|
|
21717
21850
|
body
|
|
21718
21851
|
);
|
|
21719
21852
|
}
|
|
21853
|
+
/** 更新某库存条目在指定仓库的库存水平(POST .../location-levels/{location_id},body: { stocked_quantity? }) */
|
|
21854
|
+
updateInventoryItemLevel(inventoryItemId, locationId, body) {
|
|
21855
|
+
return this.request(
|
|
21856
|
+
"POST",
|
|
21857
|
+
`/admin/inventory-items/${encodeURIComponent(inventoryItemId)}/location-levels/${encodeURIComponent(locationId)}`,
|
|
21858
|
+
body
|
|
21859
|
+
);
|
|
21860
|
+
}
|
|
21861
|
+
/**
|
|
21862
|
+
* 幂等地设置某库存条目在指定仓库的库存数量:
|
|
21863
|
+
* 已有 (item, location) 水平 → 更新;没有 → 创建。
|
|
21864
|
+
*/
|
|
21865
|
+
async upsertInventoryLevel(inventoryItemId, locationId, stockedQuantity) {
|
|
21866
|
+
const existing = await this.getInventoryItemLevels(inventoryItemId, {
|
|
21867
|
+
location_id: locationId
|
|
21868
|
+
});
|
|
21869
|
+
const found = (existing?.inventory_levels || []).find((l) => l.location_id === locationId);
|
|
21870
|
+
if (found) {
|
|
21871
|
+
await this.updateInventoryItemLevel(inventoryItemId, locationId, { stocked_quantity: stockedQuantity });
|
|
21872
|
+
return { created: false, stocked_quantity: stockedQuantity };
|
|
21873
|
+
}
|
|
21874
|
+
await this.createInventoryItemLevel(inventoryItemId, {
|
|
21875
|
+
location_id: locationId,
|
|
21876
|
+
stocked_quantity: stockedQuantity
|
|
21877
|
+
});
|
|
21878
|
+
return { created: true, stocked_quantity: stockedQuantity };
|
|
21879
|
+
}
|
|
21720
21880
|
// ─── 订单 ───
|
|
21721
21881
|
listOrders(params = {}) {
|
|
21722
21882
|
return this.request("GET", `/admin/orders${buildQuery(params)}`);
|
|
@@ -21733,6 +21893,180 @@ var MedusaAdminClient = class {
|
|
|
21733
21893
|
}
|
|
21734
21894
|
};
|
|
21735
21895
|
|
|
21896
|
+
// src/tools/stock.ts
|
|
21897
|
+
async function applyProductStock(ctx2, opts) {
|
|
21898
|
+
const { client: client2 } = ctx2;
|
|
21899
|
+
const prod = await client2.getProduct(opts.productId);
|
|
21900
|
+
const product = prod?.product ?? prod;
|
|
21901
|
+
if (!product?.variants?.length) throw new Error(`\u5546\u54C1 ${opts.productId} \u6CA1\u6709\u53D8\u4F53\uFF0C\u65E0\u6CD5\u5EFA\u4ED3`);
|
|
21902
|
+
const variant = opts.sku ? product.variants.find((v) => v.sku === opts.sku) : product.variants[0];
|
|
21903
|
+
if (!variant) throw new Error(`\u627E\u4E0D\u5230\u76EE\u6807\u53D8\u4F53\uFF08sku=${opts.sku || "\u7B2C\u4E00\u4E2A"}\uFF09`);
|
|
21904
|
+
let location = null;
|
|
21905
|
+
if (opts.locationId) {
|
|
21906
|
+
const locs = await client2.listStockLocations({ limit: 100 });
|
|
21907
|
+
location = (locs?.stock_locations || []).find((l) => l.id === opts.locationId);
|
|
21908
|
+
if (!location) throw new Error(`\u627E\u4E0D\u5230\u4ED3\u5E93 ${opts.locationId}`);
|
|
21909
|
+
} else {
|
|
21910
|
+
const locs = await client2.listStockLocations({ limit: 100 });
|
|
21911
|
+
const list = locs?.stock_locations || [];
|
|
21912
|
+
location = list.find((l) => /main warehouse/i.test(l.name)) || list[0];
|
|
21913
|
+
if (!location) throw new Error("\u6CA1\u6709\u4EFB\u4F55\u53EF\u7528\u4ED3\u5E93\uFF0C\u8BF7\u5148 create_stock_location");
|
|
21914
|
+
}
|
|
21915
|
+
let iitem = null;
|
|
21916
|
+
if (variant.sku) {
|
|
21917
|
+
const items = await client2.listInventoryItems({ sku: variant.sku, limit: 10 });
|
|
21918
|
+
iitem = (items?.inventory_items || []).find((i) => i.sku === variant.sku) || null;
|
|
21919
|
+
}
|
|
21920
|
+
if (!iitem) {
|
|
21921
|
+
if (!variant.sku) throw new Error(`\u53D8\u4F53\u6CA1\u6709 SKU\uFF0C\u65E0\u6CD5\u5173\u8054\u5E93\u5B58\u6761\u76EE\uFF08\u8BF7\u5148 update_product \u8865 sku\uFF09`);
|
|
21922
|
+
const created = await client2.createInventoryItem({
|
|
21923
|
+
sku: variant.sku,
|
|
21924
|
+
title: variant.title || void 0
|
|
21925
|
+
});
|
|
21926
|
+
iitem = created?.inventory_item ?? created;
|
|
21927
|
+
}
|
|
21928
|
+
const level = await client2.upsertInventoryLevel(iitem.id, location.id, opts.quantity);
|
|
21929
|
+
if (Boolean(variant.manage_inventory) !== opts.manageInventory) {
|
|
21930
|
+
await client2.updateProduct(product.id, {
|
|
21931
|
+
variants: [{ id: variant.id, manage_inventory: opts.manageInventory }]
|
|
21932
|
+
});
|
|
21933
|
+
}
|
|
21934
|
+
return {
|
|
21935
|
+
product_id: product.id,
|
|
21936
|
+
variant_id: variant.id,
|
|
21937
|
+
sku: variant.sku,
|
|
21938
|
+
location_id: location.id,
|
|
21939
|
+
location_name: location.name,
|
|
21940
|
+
inventory_item_id: iitem.id,
|
|
21941
|
+
manage_inventory: opts.manageInventory,
|
|
21942
|
+
stocked_quantity: level.stocked_quantity,
|
|
21943
|
+
level_created: level.created
|
|
21944
|
+
};
|
|
21945
|
+
}
|
|
21946
|
+
var stockTools = [
|
|
21947
|
+
{
|
|
21948
|
+
name: "list_stock_locations",
|
|
21949
|
+
description: "\u5217\u51FA\u4ED3\u5E93\uFF08Medusa StockLocation\uFF09\u3002\u53EF\u9009\u5173\u952E\u8BCD q \u8FC7\u6EE4\u540D\u79F0\u3002",
|
|
21950
|
+
schema: {
|
|
21951
|
+
q: external_exports.string().optional(),
|
|
21952
|
+
limit: external_exports.number().int().positive().max(100).default(50)
|
|
21953
|
+
},
|
|
21954
|
+
async run(input, { client: client2 }) {
|
|
21955
|
+
return client2.listStockLocations({ q: input.q, limit: input.limit });
|
|
21956
|
+
}
|
|
21957
|
+
},
|
|
21958
|
+
{
|
|
21959
|
+
name: "create_stock_location",
|
|
21960
|
+
description: "\u589E\u52A0\u4E00\u4E2A\u4ED3\u5E93\uFF08StockLocation\uFF09\u3002\u9ED8\u8BA4 dry_run\u3002",
|
|
21961
|
+
schema: {
|
|
21962
|
+
name: external_exports.string(),
|
|
21963
|
+
address: external_exports.object({
|
|
21964
|
+
address_1: external_exports.string().optional(),
|
|
21965
|
+
address_2: external_exports.string().optional(),
|
|
21966
|
+
city: external_exports.string().optional(),
|
|
21967
|
+
country_code: external_exports.string().optional(),
|
|
21968
|
+
province: external_exports.string().optional(),
|
|
21969
|
+
postal_code: external_exports.string().optional(),
|
|
21970
|
+
phone: external_exports.string().optional()
|
|
21971
|
+
}).optional(),
|
|
21972
|
+
metadata: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
21973
|
+
confirm: external_exports.boolean().default(false)
|
|
21974
|
+
},
|
|
21975
|
+
async run(input, { client: client2 }) {
|
|
21976
|
+
const payload = { name: input.name };
|
|
21977
|
+
if (input.address) payload.address = input.address;
|
|
21978
|
+
if (input.metadata) payload.metadata = input.metadata;
|
|
21979
|
+
if (!input.confirm) return { dry_run: true, will_create: payload };
|
|
21980
|
+
return client2.createStockLocation(payload);
|
|
21981
|
+
}
|
|
21982
|
+
},
|
|
21983
|
+
{
|
|
21984
|
+
name: "get_inventory_levels",
|
|
21985
|
+
description: `\u67E5\u8BE2\u5E93\u5B58\u6761\u76EE\u53CA\u5E93\u5B58\u6C34\u5E73\uFF08GET /admin/inventory-items\uFF0CMedusa 2.21 \u5951\u7EA6\uFF09\u3002
|
|
21986
|
+
location_id \u53EF\u9009\uFF1A\u4F20\u4E86\u5219\u53EA\u56DE\u8BE5\u4ED3\u5E93\u4E0B\u6709\u6C34\u5E73\u7684\u6761\u76EE\uFF1B\u4E0D\u4F20\u5219\u5217\u5168\u90E8\u3002q \u6309 sku/title \u6A21\u7CCA\u8FC7\u6EE4\u3002`,
|
|
21987
|
+
schema: {
|
|
21988
|
+
location_id: external_exports.string().optional().describe("\u4ED3\u5E93 id\uFF1B\u7701\u7565\u5219\u4E0D\u9650\u4ED3\u5E93"),
|
|
21989
|
+
q: external_exports.string().optional(),
|
|
21990
|
+
limit: external_exports.number().int().positive().max(100).default(50)
|
|
21991
|
+
},
|
|
21992
|
+
async run(input, { client: client2 }) {
|
|
21993
|
+
const params = { limit: input.limit };
|
|
21994
|
+
if (input.q) params.q = input.q;
|
|
21995
|
+
if (input.location_id) params["location_levels.location_id"] = input.location_id;
|
|
21996
|
+
return client2.listInventoryItems(params);
|
|
21997
|
+
}
|
|
21998
|
+
},
|
|
21999
|
+
{
|
|
22000
|
+
name: "set_product_stock",
|
|
22001
|
+
description: `\u7ED9\u5546\u54C1\u5EFA\u4ED3/\u6539\u5E93\u5B58\uFF08\u5E42\u7B49\uFF09\uFF1A\u786E\u4FDD\u53D8\u4F53\u5BF9\u5E94\u7684\u5E93\u5B58\u6761\u76EE\u5B58\u5728\uFF08\u7F3A\u5931\u5219\u6309\u53D8\u4F53 SKU \u81EA\u52A8\u521B\u5EFA\uFF09\uFF0C
|
|
22002
|
+
\u518D\u628A\u6307\u5B9A\u4ED3\u5E93\u7684\u5E93\u5B58\u6570\u91CF\u8BBE\u4E3A quantity\uFF0C\u5E76\u628A\u53D8\u4F53 manage_inventory \u7F6E\u4E3A true\u3002
|
|
22003
|
+
\u26A0\uFE0F \u4E0D\u6307\u5B9A location_id \u65F6\u9ED8\u8BA4\u5199\u5165\u540D\u79F0\u542B "Main Warehouse" \u7684\u4ED3\u5E93\uFF08\u5426\u5219\u53D6\u7B2C\u4E00\u4E2A\u4ED3\u5E93\uFF09\u3002
|
|
22004
|
+
\u26A0\uFE0F \u672A\u5EFA\u4ED3\uFF08manage_inventory=false \u6216 0 \u5E93\u5B58\uFF09\u7684\u5546\u54C1\u5728\u524D\u7AEF\u8868\u73B0\u4E3A\u300C\u7F3A\u8D27\u300D\uFF0C\u6B63\u5F0F\u4E0A\u67B6\u524D\u5FC5\u987B\u5EFA\u4ED3\u3002
|
|
22005
|
+
\u9ED8\u8BA4 dry_run\uFF08confirm=false\uFF09\u4EC5\u9884\u89C8\uFF1Bconfirm=true \u624D\u5199\u5165\u3002`,
|
|
22006
|
+
schema: {
|
|
22007
|
+
product_id: external_exports.string().describe("\u5546\u54C1 id \u6216 handle"),
|
|
22008
|
+
sku: external_exports.string().optional().describe("\u76EE\u6807\u53D8\u4F53 SKU\uFF1B\u7701\u7565\u5219\u53D6\u7B2C\u4E00\u4E2A\u53D8\u4F53"),
|
|
22009
|
+
location_id: external_exports.string().optional().describe("\u4ED3\u5E93 id\uFF1B\u7701\u7565\u5219 Main Warehouse/\u7B2C\u4E00\u4E2A"),
|
|
22010
|
+
quantity: external_exports.number().int().nonnegative().describe("\u8BE5\u4ED3\u5E93\u5E93\u5B58\u6570\u91CF\uFF08\u7EDD\u5BF9\u503C\uFF0C\u975E\u589E\u91CF\uFF09"),
|
|
22011
|
+
manage_inventory: external_exports.boolean().default(true).describe("\u53D8\u4F53\u662F\u5426\u542F\u7528\u5E93\u5B58\u7BA1\u7406\uFF0C\u9ED8\u8BA4 true"),
|
|
22012
|
+
confirm: external_exports.boolean().default(false)
|
|
22013
|
+
},
|
|
22014
|
+
async run(input, ctx2) {
|
|
22015
|
+
const plan = {
|
|
22016
|
+
product_id: input.product_id,
|
|
22017
|
+
sku: input.sku || "(\u7B2C\u4E00\u4E2A\u53D8\u4F53)",
|
|
22018
|
+
location_id: input.location_id || "(Main Warehouse/\u7B2C\u4E00\u4E2A\u4ED3\u5E93)",
|
|
22019
|
+
quantity: input.quantity,
|
|
22020
|
+
manage_inventory: input.manage_inventory
|
|
22021
|
+
};
|
|
22022
|
+
if (!input.confirm) {
|
|
22023
|
+
return { dry_run: true, will_set_stock: plan };
|
|
22024
|
+
}
|
|
22025
|
+
try {
|
|
22026
|
+
const result = await applyProductStock(ctx2, {
|
|
22027
|
+
productId: input.product_id,
|
|
22028
|
+
sku: input.sku,
|
|
22029
|
+
locationId: input.location_id,
|
|
22030
|
+
quantity: input.quantity,
|
|
22031
|
+
manageInventory: input.manage_inventory
|
|
22032
|
+
});
|
|
22033
|
+
return { set_stock: result };
|
|
22034
|
+
} catch (e) {
|
|
22035
|
+
return `\u9519\u8BEF\uFF1A\u5EFA\u4ED3\u5931\u8D25 \u2014\u2014 ${e?.message || e}`;
|
|
22036
|
+
}
|
|
22037
|
+
}
|
|
22038
|
+
},
|
|
22039
|
+
{
|
|
22040
|
+
name: "update_inventory_level",
|
|
22041
|
+
description: `\u4FEE\u6539\u67D0\u5E93\u5B58\u6761\u76EE\u5728\u6307\u5B9A\u4ED3\u5E93\u7684\u5E93\u5B58\u6570\u91CF\uFF08\u7EDD\u5BF9\u503C\uFF0C\u5E42\u7B49 upsert\uFF09\u3002
|
|
22042
|
+
\u76F4\u63A5\u7ED9\u5546\u54C1\u5EFA\u4ED3\u8BF7\u4F18\u5148\u7528 set_product_stock\uFF08\u4F1A\u81EA\u52A8\u627E/\u5EFA\u5E93\u5B58\u6761\u76EE\uFF09\u3002\u9ED8\u8BA4 dry_run\u3002`,
|
|
22043
|
+
schema: {
|
|
22044
|
+
inventory_item_id: external_exports.string(),
|
|
22045
|
+
location_id: external_exports.string(),
|
|
22046
|
+
stocked_quantity: external_exports.number().int().nonnegative(),
|
|
22047
|
+
confirm: external_exports.boolean().default(false)
|
|
22048
|
+
},
|
|
22049
|
+
async run(input, { client: client2 }) {
|
|
22050
|
+
if (!input.confirm) {
|
|
22051
|
+
return {
|
|
22052
|
+
dry_run: true,
|
|
22053
|
+
will_update: {
|
|
22054
|
+
inventory_item_id: input.inventory_item_id,
|
|
22055
|
+
location_id: input.location_id,
|
|
22056
|
+
stocked_quantity: input.stocked_quantity
|
|
22057
|
+
}
|
|
22058
|
+
};
|
|
22059
|
+
}
|
|
22060
|
+
const level = await client2.upsertInventoryLevel(
|
|
22061
|
+
input.inventory_item_id,
|
|
22062
|
+
input.location_id,
|
|
22063
|
+
input.stocked_quantity
|
|
22064
|
+
);
|
|
22065
|
+
return { updated: { ...level, inventory_item_id: input.inventory_item_id, location_id: input.location_id } };
|
|
22066
|
+
}
|
|
22067
|
+
}
|
|
22068
|
+
];
|
|
22069
|
+
|
|
21736
22070
|
// src/tools/products.ts
|
|
21737
22071
|
var PRODUCT_STATUS = [
|
|
21738
22072
|
"draft",
|
|
@@ -21776,12 +22110,77 @@ var productTools = [
|
|
|
21776
22110
|
return client2.getProduct(input.id_or_handle);
|
|
21777
22111
|
}
|
|
21778
22112
|
},
|
|
22113
|
+
{
|
|
22114
|
+
name: "list_sales_channels",
|
|
22115
|
+
description: "\u5217\u51FA\u9500\u552E\u6E20\u9053\uFF08GET /admin/sales-channels\uFF09\u3002\u65B0\u589E\u5546\u54C1\u524D\u7528\u5B83\u786E\u8BA4\u6E20\u9053 id/\u540D\u79F0\u3002",
|
|
22116
|
+
schema: {
|
|
22117
|
+
q: external_exports.string().optional(),
|
|
22118
|
+
limit: external_exports.number().int().positive().max(100).default(50)
|
|
22119
|
+
},
|
|
22120
|
+
async run(input, { client: client2 }) {
|
|
22121
|
+
return client2.listSalesChannels({ q: input.q, limit: input.limit });
|
|
22122
|
+
}
|
|
22123
|
+
},
|
|
22124
|
+
{
|
|
22125
|
+
name: "list_collections",
|
|
22126
|
+
description: "\u5217\u51FA\u96C6\u5408\uFF08GET /admin/collections\uFF09\uFF0C\u7528\u4E8E\u628A\u5546\u54C1\u6302\u8FDB\u96C6\u5408\uFF08\u5982\u6D3B\u52A8\u96C6\u5408\uFF09\u3002",
|
|
22127
|
+
schema: {
|
|
22128
|
+
q: external_exports.string().optional(),
|
|
22129
|
+
limit: external_exports.number().int().positive().max(100).default(50)
|
|
22130
|
+
},
|
|
22131
|
+
async run(input, { client: client2 }) {
|
|
22132
|
+
return client2.listCollections({ q: input.q, limit: input.limit });
|
|
22133
|
+
}
|
|
22134
|
+
},
|
|
22135
|
+
{
|
|
22136
|
+
name: "get_translations",
|
|
22137
|
+
description: `\u8BFB\u53D6\u67D0\u5B9E\u4F53\u7684\u591A\u8BED\u8A00\u7FFB\u8BD1\uFF08GET /admin/translations\uFF09\u2014\u2014\u6838\u5BF9\u5546\u54C1\u56DB\u8BED\u8A00\u662F\u5426\u5199\u5165\u6210\u529F\u7684\u552F\u4E00\u53EF\u9760\u9014\u5F84
|
|
22138
|
+
\uFF08get_product \u9ED8\u8BA4\u5B57\u6BB5\u4E0D\u542B translations\uFF09\u3002reference \u4E00\u822C\u4E3A product\uFF0Creference_id \u4F20\u5546\u54C1 id \u6216 handle\u3002
|
|
22139
|
+
\u8FD4\u56DE\u6BCF\u6761 { locale_code, title, description }\u3002`,
|
|
22140
|
+
schema: {
|
|
22141
|
+
reference: external_exports.string().default("product"),
|
|
22142
|
+
reference_id: external_exports.string().describe("\u5546\u54C1 id \u6216 handle"),
|
|
22143
|
+
locale_code: external_exports.string().optional().describe("\u53EA\u53D6\u67D0\u4E00\u8BED\u8A00\uFF0C\u5982 zh"),
|
|
22144
|
+
limit: external_exports.number().int().positive().max(100).default(50)
|
|
22145
|
+
},
|
|
22146
|
+
async run(input, { client: client2 }) {
|
|
22147
|
+
let refId = input.reference_id;
|
|
22148
|
+
if (!/^[a-z]+_[a-z0-9]+$/i.test(refId)) {
|
|
22149
|
+
const prod = await client2.getProduct(refId).catch(() => null);
|
|
22150
|
+
const id = prod?.product?.id || prod?.id;
|
|
22151
|
+
if (!id) return `\u9519\u8BEF\uFF1A\u627E\u4E0D\u5230 ${input.reference} "${refId}"\uFF08\u8BF7\u4F20 id \u6216\u6709\u6548 handle\uFF09`;
|
|
22152
|
+
refId = id;
|
|
22153
|
+
}
|
|
22154
|
+
const params = {
|
|
22155
|
+
reference: input.reference,
|
|
22156
|
+
reference_id: refId,
|
|
22157
|
+
limit: input.limit
|
|
22158
|
+
};
|
|
22159
|
+
if (input.locale_code) params.locale_code = input.locale_code;
|
|
22160
|
+
const data = await client2.listTranslations(params);
|
|
22161
|
+
const rows = (data?.translations || []).map((t) => {
|
|
22162
|
+
const tr = t.translations || {};
|
|
22163
|
+
return {
|
|
22164
|
+
locale_code: t.locale_code,
|
|
22165
|
+
title: tr.title ?? null,
|
|
22166
|
+
description: tr.description ? tr.description.slice(0, 120) + "\u2026" : null,
|
|
22167
|
+
has_full_description: Boolean(tr.description)
|
|
22168
|
+
};
|
|
22169
|
+
});
|
|
22170
|
+
return { reference: input.reference, reference_id: input.reference_id, translations: rows, count: rows.length };
|
|
22171
|
+
}
|
|
22172
|
+
},
|
|
21779
22173
|
{
|
|
21780
22174
|
name: "create_product",
|
|
21781
22175
|
description: `\u5728\u540E\u53F0\u589E\u52A0\u4E00\u4E2A\u5546\u54C1\u3002
|
|
21782
22176
|
\u89C4\u5219\uFF08\xA716.1\uFF09\uFF1AHandle \u53EA\u80FD\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26(-)\uFF0C\u5DE5\u5177\u4F1A\u81EA\u52A8\u5F52\u4E00\u5316\uFF0C\u975E\u6CD5\u5219\u62A5\u9519\u3002
|
|
21783
22177
|
\u591A\u8BED\u8A00\uFF08\xA716.2\uFF09\uFF1Atranslations \u5FC5\u987B\u5305\u542B en/zh/ms/id \u56DB\u79CD\u8BED\u8A00\u7684 title\uFF1B\u7F3A\u5931\u65F6\u9700 confirm=true \u4E14 acknowledge_missing_languages=true\u3002
|
|
21784
|
-
\u9ED8\u8BA4 dry_run\uFF08confirm=false\uFF09\u53EA\u8FD4\u56DE\u9884\u89C8\uFF0C\u4E0D\u771F\u6B63\u521B\u5EFA\u3002
|
|
22178
|
+
\u9ED8\u8BA4 dry_run\uFF08confirm=false\uFF09\u53EA\u8FD4\u56DE\u9884\u89C8\uFF0C\u4E0D\u771F\u6B63\u521B\u5EFA\u3002
|
|
22179
|
+
\u56FE\u7247\uFF1Aimages \u53EF\u4F20\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u81EA\u52A8\u4E0A\u4F20\u5230\u6587\u4EF6\u670D\u52A1\uFF09\u6216\u56FE\u7247 URL\uFF0C\u4EC5\u5728 confirm=true \u65F6\u624D\u771F\u6B63\u4E0A\u4F20/\u6302\u63A5\u3002
|
|
22180
|
+
SKU / \u89C4\u683C\uFF1Asku \u5199\u5165\u9ED8\u8BA4\u53D8\u4F53\uFF1Bmetadata \u53EF\u5B58\u4EFB\u610F\u952E\u503C\uFF08\u5982\u89C4\u683C\u53C2\u6570\u3001\u6765\u6E90\u94FE\u63A5\uFF09\u3002
|
|
22181
|
+
\u6E20\u9053\uFF1Asales_channels \u4F20\u6E20\u9053 id \u6216\u540D\u79F0\uFF08\u5982 "MY Storefront"\uFF09\uFF0C\u521B\u5EFA\u5373\u6302\u8F7D\uFF0C\u5426\u5219\u524D\u7AEF\u4E0D\u53EF\u89C1\u3002
|
|
22182
|
+
\u5E93\u5B58\uFF1Amanage_inventory \u9ED8\u8BA4 true\uFF1B\u6B63\u5F0F\u4E0A\u67B6\u5FC5\u987B\u7ED9 initial_stock\uFF08\u5982 {quantity:100}\uFF0C\u9ED8\u8BA4\u4E3B\u4ED3\uFF09\uFF0C\u5426\u5219 0 \u5E93\u5B58=\u524D\u7AEF\u7F3A\u8D27\u3002
|
|
22183
|
+
\u7269\u7406/\u6D77\u5173\u5B57\u6BB5\uFF1Aweight(\u514B)/length/height/width(\u5398\u7C73)/origin_country/hs_code/mid_code/material \u5747\u53EF\u9009\u3002`,
|
|
21785
22184
|
schema: {
|
|
21786
22185
|
title: external_exports.string(),
|
|
21787
22186
|
handle: external_exports.string().optional().describe("\u4E0D\u586B\u5219\u7528 title \u5F52\u4E00\u5316\u751F\u6210"),
|
|
@@ -21792,9 +22191,28 @@ var productTools = [
|
|
|
21792
22191
|
// 并非最小单位(分)。官方 ESLint 规则 prices-in-major-units 明确:
|
|
21793
22192
|
// "Medusa stores prices in major units",期望 19.99 而非 1999。
|
|
21794
22193
|
// 本项目 seed 与 storefront 均遵循此官方约定,故直接透传,不要 ×100。
|
|
21795
|
-
price: external_exports.number().nonnegative().optional().describe("\u4E3B\u8D27\u5E01\u5355\u4F4D\u91D1\u989D\uFF0C\u5982 200.51 \u5373 RM200.51\uFF1BMedusa \u4EE5\u4E3B\u5355\u4F4D\u5B58\u50A8\uFF0C\u5207\u52FF\u6362\u7B97"),
|
|
22194
|
+
price: external_exports.number().nonnegative().optional().describe("\u4E3B\u8D27\u5E01\u5355\u4F4D\u91D1\u989D\uFF0C\u5982 200.51 \u5373 RM200.51\uFF1BMedusa \u4EE5\u4E3B\u5355\u4F4D\u5B58\u50A8\uFF0C\u5207\u52FF\u6362\u7B97\u3002\u4E0E prices \u4E8C\u9009\u4E00\uFF0Cprices \u4F18\u5148"),
|
|
21796
22195
|
currency_code: external_exports.string().default("MYR"),
|
|
22196
|
+
prices: external_exports.array(external_exports.object({ currency_code: external_exports.string(), amount: external_exports.number().nonnegative() })).optional().describe('\u591A\u5E01\u79CD\u4EF7\u683C\uFF08\u4E3B\u8D27\u5E01\u5355\u4F4D\uFF09\uFF0C\u5982 [{currency_code:"myr",amount:420},{currency_code:"sgd",amount:127}]\u3002\u4E1C\u5357\u4E9A\u5E97\u901A\u5E38\u9700\u8986\u76D6 myr/sgd/idr/thb/php/cny\u3002\u4F18\u5148\u4E8E\u5355\u4E00 price'),
|
|
21797
22197
|
translations: external_exports.record(external_exports.string(), translationShape).optional(),
|
|
22198
|
+
images: external_exports.array(external_exports.string()).optional().describe("\u672C\u5730\u56FE\u7247\u8DEF\u5F84\u6216\u56FE\u7247 URL\uFF0Cconfirm=true \u65F6\u4E0A\u4F20\u5E76\u6302\u5230\u5546\u54C1"),
|
|
22199
|
+
sku: external_exports.string().optional().describe("\u5199\u5165\u9ED8\u8BA4\u53D8\u4F53\u7684 SKU"),
|
|
22200
|
+
metadata: external_exports.record(external_exports.string(), external_exports.string()).optional().describe("\u4EFB\u610F\u952E\u503C\uFF0C\u5982\u89C4\u683C\u53C2\u6570 / source_url"),
|
|
22201
|
+
sales_channels: external_exports.array(external_exports.string()).optional().describe('\u9500\u552E\u6E20\u9053 id \u6216\u540D\u79F0\uFF08\u5982 "MY Storefront"\uFF09\uFF0C\u521B\u5EFA\u5373\u6302\u8F7D\uFF0C\u5426\u5219\u524D\u7AEF\u4E0D\u53EF\u89C1'),
|
|
22202
|
+
collection_id: external_exports.string().optional().describe('\u96C6\u5408 id \u6216\u540D\u79F0\uFF08\u5982 "\u53CC11\u6D3B\u52A8"\uFF09'),
|
|
22203
|
+
weight: external_exports.number().nonnegative().optional().describe("\u91CD\u91CF\uFF0C\u5355\u4F4D\u514B"),
|
|
22204
|
+
length: external_exports.number().nonnegative().optional().describe("\u957F\uFF0C\u5355\u4F4D\u5398\u7C73"),
|
|
22205
|
+
height: external_exports.number().nonnegative().optional().describe("\u9AD8\uFF0C\u5355\u4F4D\u5398\u7C73"),
|
|
22206
|
+
width: external_exports.number().nonnegative().optional().describe("\u5BBD\uFF0C\u5355\u4F4D\u5398\u7C73"),
|
|
22207
|
+
origin_country: external_exports.string().optional().describe("\u539F\u4EA7\u56FD ISO \u7801\uFF0C\u5982 KR/MY"),
|
|
22208
|
+
hs_code: external_exports.string().optional(),
|
|
22209
|
+
mid_code: external_exports.string().optional(),
|
|
22210
|
+
material: external_exports.string().optional(),
|
|
22211
|
+
manage_inventory: external_exports.boolean().default(true).describe("\u53D8\u4F53\u662F\u5426\u542F\u7528\u5E93\u5B58\u7BA1\u7406\uFF0C\u9ED8\u8BA4 true"),
|
|
22212
|
+
initial_stock: external_exports.object({
|
|
22213
|
+
location_id: external_exports.string().optional().describe("\u4ED3\u5E93 id\uFF1B\u7701\u7565\u5219 Main Warehouse/\u7B2C\u4E00\u4E2A\u4ED3\u5E93"),
|
|
22214
|
+
quantity: external_exports.number().int().nonnegative()
|
|
22215
|
+
}).optional().describe("\u5EFA\u5E93\u5B58\uFF1A\u6309\u53D8\u4F53 SKU \u81EA\u52A8\u521B\u5EFA\u5E93\u5B58\u6761\u76EE\u5E76\u5199\u5165\u8BE5\u4ED3\u5E93\u6570\u91CF"),
|
|
21798
22216
|
confirm: external_exports.boolean().default(false),
|
|
21799
22217
|
acknowledge_missing_languages: external_exports.boolean().default(false)
|
|
21800
22218
|
},
|
|
@@ -21808,6 +22226,29 @@ var productTools = [
|
|
|
21808
22226
|
const missing = config3.requiredLocales.filter(
|
|
21809
22227
|
(l) => !provided.includes(l) || !input.translations?.[l]?.title
|
|
21810
22228
|
);
|
|
22229
|
+
let channelIds = [];
|
|
22230
|
+
if (input.sales_channels?.length) {
|
|
22231
|
+
const ch = await client2.listSalesChannels({ limit: 100 });
|
|
22232
|
+
const list = ch?.sales_channels || [];
|
|
22233
|
+
channelIds = input.sales_channels.map((name) => list.find((c) => c.id === name || c.name === name)?.id).filter(Boolean);
|
|
22234
|
+
const unknown2 = input.sales_channels.filter(
|
|
22235
|
+
(name) => !list.some((c) => c.id === name || c.name === name)
|
|
22236
|
+
);
|
|
22237
|
+
if (unknown2.length) return `\u9519\u8BEF\uFF1A\u627E\u4E0D\u5230\u9500\u552E\u6E20\u9053\uFF1A${unknown2.join(", ")}\uFF08\u7528 list_sales_channels \u67E5\u770B\u53EF\u7528\u6E20\u9053\uFF09`;
|
|
22238
|
+
}
|
|
22239
|
+
let collectionId = input.collection_id;
|
|
22240
|
+
if (collectionId && !collectionId.startsWith("pcol_")) {
|
|
22241
|
+
const cols = await client2.listCollections({ limit: 100 });
|
|
22242
|
+
const hit = (cols?.collections || []).find((c) => c.id === collectionId || c.title === collectionId);
|
|
22243
|
+
if (!hit) return `\u9519\u8BEF\uFF1A\u627E\u4E0D\u5230\u96C6\u5408 "${collectionId}"\uFF08\u7528 list_collections \u67E5\u770B\u53EF\u7528\u96C6\u5408\uFF09`;
|
|
22244
|
+
collectionId = hit.id;
|
|
22245
|
+
}
|
|
22246
|
+
const warnings = [];
|
|
22247
|
+
if (input.manage_inventory !== false && !input.initial_stock) {
|
|
22248
|
+
warnings.push(
|
|
22249
|
+
"manage_inventory=true \u4F46\u672A\u7ED9 initial_stock\uFF1A\u521B\u5EFA\u540E\u4E3A 0 \u5E93\u5B58\uFF0C\u524D\u7AEF\u5C06\u663E\u793A\u7F3A\u8D27\u3002\u6B63\u5F0F\u4E0A\u67B6\u524D\u5FC5\u987B\u8C03\u7528 set_product_stock \u5EFA\u4ED3\u3002"
|
|
22250
|
+
);
|
|
22251
|
+
}
|
|
21811
22252
|
if (!input.confirm) {
|
|
21812
22253
|
return {
|
|
21813
22254
|
dry_run: true,
|
|
@@ -21817,38 +22258,102 @@ var productTools = [
|
|
|
21817
22258
|
status: input.status,
|
|
21818
22259
|
price: input.price,
|
|
21819
22260
|
currency_code: input.currency_code,
|
|
22261
|
+
prices: input.prices,
|
|
22262
|
+
sku: input.sku,
|
|
22263
|
+
images: input.images,
|
|
22264
|
+
metadata: input.metadata,
|
|
22265
|
+
sales_channels: channelIds,
|
|
22266
|
+
collection_id: collectionId || null,
|
|
22267
|
+
weight: input.weight,
|
|
22268
|
+
manage_inventory: input.manage_inventory,
|
|
22269
|
+
initial_stock: input.initial_stock,
|
|
21820
22270
|
missing_translations: missing
|
|
21821
22271
|
},
|
|
22272
|
+
warnings,
|
|
21822
22273
|
note: "\u672A confirm\uFF0C\u4EC5\u9884\u89C8\u3002\u786E\u8BA4\u8BF7\u8BBE confirm=true\u3002"
|
|
21823
22274
|
};
|
|
21824
22275
|
}
|
|
21825
22276
|
if (missing.length > 0 && !input.acknowledge_missing_languages) {
|
|
21826
22277
|
return `\u9519\u8BEF\uFF1A\u7F3A\u5C11\u4EE5\u4E0B\u8BED\u8A00\u7684 title\uFF1A${missing.join(", ")}\u3002\u5982\u786E\u8BA4\u4ECD\u8981\u521B\u5EFA\uFF0C\u8BF7\u8BBE acknowledge_missing_languages=true\u3002`;
|
|
21827
22278
|
}
|
|
22279
|
+
let imageUrls = [];
|
|
22280
|
+
if (input.images?.length) {
|
|
22281
|
+
try {
|
|
22282
|
+
imageUrls = await client2.uploadProductAssets(input.images);
|
|
22283
|
+
} catch (e) {
|
|
22284
|
+
return `\u9519\u8BEF\uFF1A\u56FE\u7247\u4E0A\u4F20\u5931\u8D25 \u2014\u2014 ${e?.message || e}`;
|
|
22285
|
+
}
|
|
22286
|
+
}
|
|
21828
22287
|
const payload = {
|
|
21829
22288
|
title: input.title,
|
|
21830
22289
|
handle,
|
|
21831
22290
|
status: input.status,
|
|
21832
22291
|
subtitle: input.subtitle,
|
|
21833
|
-
description: input.description
|
|
22292
|
+
description: input.description,
|
|
22293
|
+
options: [{ title: "Default", values: ["Default"] }]
|
|
21834
22294
|
};
|
|
21835
|
-
if (input.
|
|
21836
|
-
if (
|
|
21837
|
-
|
|
21838
|
-
|
|
21839
|
-
|
|
21840
|
-
|
|
21841
|
-
|
|
21842
|
-
|
|
21843
|
-
];
|
|
22295
|
+
if (input.metadata) payload.metadata = input.metadata;
|
|
22296
|
+
if (imageUrls.length) payload.images = imageUrls.map((url) => ({ url }));
|
|
22297
|
+
if (channelIds.length) payload.sales_channels = channelIds.map((id) => ({ id }));
|
|
22298
|
+
if (collectionId) payload.collection_id = collectionId;
|
|
22299
|
+
for (const k of ["weight", "length", "height", "width"]) {
|
|
22300
|
+
if (input[k] != null) payload[k] = input[k];
|
|
22301
|
+
}
|
|
22302
|
+
for (const k of ["origin_country", "hs_code", "mid_code", "material"]) {
|
|
22303
|
+
if (input[k]) payload[k] = input[k];
|
|
22304
|
+
}
|
|
22305
|
+
const variantPrices = input.prices && input.prices.length ? input.prices.map((p) => ({ amount: p.amount, currency_code: p.currency_code })) : input.price != null ? [{ amount: input.price, currency_code: input.currency_code }] : void 0;
|
|
22306
|
+
if (variantPrices) {
|
|
22307
|
+
const variant = {
|
|
22308
|
+
title: "Default",
|
|
22309
|
+
options: { Default: "Default" },
|
|
22310
|
+
prices: variantPrices,
|
|
22311
|
+
manage_inventory: input.manage_inventory
|
|
22312
|
+
};
|
|
22313
|
+
if (input.sku) variant.sku = input.sku;
|
|
22314
|
+
payload.variants = [variant];
|
|
21844
22315
|
}
|
|
21845
22316
|
const data = await client2.createProduct(payload);
|
|
21846
|
-
|
|
22317
|
+
const productId = data?.product?.id || data?.id;
|
|
22318
|
+
let translationResult = null;
|
|
22319
|
+
if (input.translations && Object.keys(input.translations).length) {
|
|
22320
|
+
const create = Object.entries(input.translations).map(([locale, t]) => ({
|
|
22321
|
+
reference: "product",
|
|
22322
|
+
reference_id: productId,
|
|
22323
|
+
locale_code: locale,
|
|
22324
|
+
translations: { title: t?.title, description: t?.description }
|
|
22325
|
+
}));
|
|
22326
|
+
try {
|
|
22327
|
+
translationResult = await client2.batchTranslations(create);
|
|
22328
|
+
} catch (e) {
|
|
22329
|
+
return { created: data, warning_translations: `\u7FFB\u8BD1\u5199\u5165\u5931\u8D25 \u2014\u2014 ${e?.message || e}` };
|
|
22330
|
+
}
|
|
22331
|
+
}
|
|
22332
|
+
let stock = null;
|
|
22333
|
+
if (input.initial_stock && productId) {
|
|
22334
|
+
try {
|
|
22335
|
+
stock = await applyProductStock(
|
|
22336
|
+
{ client: client2, config: config3 },
|
|
22337
|
+
{
|
|
22338
|
+
productId,
|
|
22339
|
+
sku: input.sku,
|
|
22340
|
+
locationId: input.initial_stock.location_id,
|
|
22341
|
+
quantity: input.initial_stock.quantity,
|
|
22342
|
+
manageInventory: input.manage_inventory
|
|
22343
|
+
}
|
|
22344
|
+
);
|
|
22345
|
+
} catch (e) {
|
|
22346
|
+
warnings.push(`\u5EFA\u5E93\u5B58\u5931\u8D25\uFF08\u5546\u54C1\u5DF2\u521B\u5EFA\uFF0C\u8BF7\u8865\u8C03 set_product_stock\uFF09\u2014\u2014 ${e?.message || e}`);
|
|
22347
|
+
}
|
|
22348
|
+
}
|
|
22349
|
+
return { created: data, translations: translationResult, stock, warnings, warning_missing_languages: missing };
|
|
21847
22350
|
}
|
|
21848
22351
|
},
|
|
21849
22352
|
{
|
|
21850
22353
|
name: "update_product",
|
|
21851
|
-
description:
|
|
22354
|
+
description: `\u4FEE\u6539\u5546\u54C1\u5B57\u6BB5\uFF08\u6807\u9898/handle/\u72B6\u6001/\u63CF\u8FF0/\u591A\u8BED\u8A00/\u7269\u7406\u5C3A\u5BF8/\u6D77\u5173/\u96C6\u5408/metadata\uFF09\u3002Handle \u81EA\u52A8\u5F52\u4E00\u5316\u3002
|
|
22355
|
+
weight \u5355\u4F4D\u514B\uFF1Blength/height/width \u5355\u4F4D\u5398\u7C73\uFF1Borigin_country \u7528 ISO \u7801\uFF08\u5982 KR\uFF09\u3002
|
|
22356
|
+
metadata \u4E3A**\u5408\u5E76**\u8BED\u4E49\uFF1A\u65B0\u952E\u8986\u76D6\u540C\u540D\u952E\u3001\u672A\u63D0\u53CA\u7684\u65E7\u952E\u4FDD\u7559\uFF08\u4E0D\u652F\u6301\u5220\u9664\u952E\uFF09\u3002\u9ED8\u8BA4 dry_run\u3002`,
|
|
21852
22357
|
schema: {
|
|
21853
22358
|
id: external_exports.string(),
|
|
21854
22359
|
title: external_exports.string().optional(),
|
|
@@ -21857,6 +22362,16 @@ var productTools = [
|
|
|
21857
22362
|
subtitle: external_exports.string().optional(),
|
|
21858
22363
|
description: external_exports.string().optional(),
|
|
21859
22364
|
translations: external_exports.record(external_exports.string(), translationShape).optional(),
|
|
22365
|
+
collection_id: external_exports.string().optional().describe("\u96C6\u5408 id \u6216\u540D\u79F0"),
|
|
22366
|
+
weight: external_exports.number().nonnegative().optional().describe("\u91CD\u91CF\uFF0C\u5355\u4F4D\u514B"),
|
|
22367
|
+
length: external_exports.number().nonnegative().optional().describe("\u957F\uFF0C\u5355\u4F4D\u5398\u7C73"),
|
|
22368
|
+
height: external_exports.number().nonnegative().optional().describe("\u9AD8\uFF0C\u5355\u4F4D\u5398\u7C73"),
|
|
22369
|
+
width: external_exports.number().nonnegative().optional().describe("\u5BBD\uFF0C\u5355\u4F4D\u5398\u7C73"),
|
|
22370
|
+
origin_country: external_exports.string().optional(),
|
|
22371
|
+
hs_code: external_exports.string().optional(),
|
|
22372
|
+
mid_code: external_exports.string().optional(),
|
|
22373
|
+
material: external_exports.string().optional(),
|
|
22374
|
+
metadata: external_exports.record(external_exports.string(), external_exports.string()).optional().describe("\u5408\u5E76\u8FDB\u73B0\u6709 metadata\uFF08\u4E0D\u6574\u4F53\u8986\u76D6\uFF09"),
|
|
21860
22375
|
confirm: external_exports.boolean().default(false)
|
|
21861
22376
|
},
|
|
21862
22377
|
async run(input, { client: client2 }) {
|
|
@@ -21870,9 +22385,79 @@ var productTools = [
|
|
|
21870
22385
|
if (input.status) patch.status = input.status;
|
|
21871
22386
|
if (input.subtitle) patch.subtitle = input.subtitle;
|
|
21872
22387
|
if (input.description) patch.description = input.description;
|
|
21873
|
-
if (input.
|
|
22388
|
+
if (input.collection_id) {
|
|
22389
|
+
let cid = input.collection_id;
|
|
22390
|
+
if (!cid.startsWith("pcol_")) {
|
|
22391
|
+
const cols = await client2.listCollections({ limit: 100 });
|
|
22392
|
+
const hit = (cols?.collections || []).find((c) => c.id === cid || c.title === cid);
|
|
22393
|
+
if (!hit) return `\u9519\u8BEF\uFF1A\u627E\u4E0D\u5230\u96C6\u5408 "${cid}"\uFF08\u7528 list_collections \u67E5\u770B\u53EF\u7528\u96C6\u5408\uFF09`;
|
|
22394
|
+
cid = hit.id;
|
|
22395
|
+
}
|
|
22396
|
+
patch.collection_id = cid;
|
|
22397
|
+
}
|
|
22398
|
+
for (const k of ["weight", "length", "height", "width"]) {
|
|
22399
|
+
if (input[k] != null) patch[k] = input[k];
|
|
22400
|
+
}
|
|
22401
|
+
for (const k of ["origin_country", "hs_code", "mid_code", "material"]) {
|
|
22402
|
+
if (input[k]) patch[k] = input[k];
|
|
22403
|
+
}
|
|
22404
|
+
if (input.metadata && Object.keys(input.metadata).length) {
|
|
22405
|
+
const cur = await client2.getProduct(input.id).catch(() => null);
|
|
22406
|
+
const existingMeta = (cur?.product ?? cur)?.metadata || {};
|
|
22407
|
+
patch.metadata = { ...existingMeta, ...input.metadata };
|
|
22408
|
+
}
|
|
21874
22409
|
if (!input.confirm) return { dry_run: true, will_update: patch };
|
|
21875
|
-
|
|
22410
|
+
const data = await client2.updateProduct(input.id, patch);
|
|
22411
|
+
let translationResult = null;
|
|
22412
|
+
if (input.translations && Object.keys(input.translations).length) {
|
|
22413
|
+
const create = Object.entries(input.translations).map(([locale, t]) => ({
|
|
22414
|
+
reference: "product",
|
|
22415
|
+
reference_id: input.id,
|
|
22416
|
+
locale_code: locale,
|
|
22417
|
+
translations: { title: t?.title, description: t?.description }
|
|
22418
|
+
}));
|
|
22419
|
+
try {
|
|
22420
|
+
translationResult = await client2.batchTranslations(create);
|
|
22421
|
+
} catch (e) {
|
|
22422
|
+
return { updated: data, warning_translations: `\u7FFB\u8BD1\u5199\u5165\u5931\u8D25 \u2014\u2014 ${e?.message || e}` };
|
|
22423
|
+
}
|
|
22424
|
+
}
|
|
22425
|
+
return { updated: data, translations: translationResult };
|
|
22426
|
+
}
|
|
22427
|
+
},
|
|
22428
|
+
{
|
|
22429
|
+
name: "update_product_prices",
|
|
22430
|
+
description: `\u8BBE\u7F6E/\u66FF\u6362\u67D0\u5546\u54C1\u7684\uFF08\u53D8\u4F53\uFF09\u4EF7\u683C\uFF0C\u652F\u6301\u591A\u5E01\u79CD\u3002Medusa \u4EF7\u683C\u6302\u5728\u300C\u53D8\u4F53\u300D\u4E0A\uFF0C\u5199\u5165\u5373\u6574\u4F53\u66FF\u6362\u8BE5\u53D8\u4F53\u7684\u5168\u90E8\u4EF7\u683C\uFF08\u4E0D\u4F1A\u7D2F\u52A0\u65E7\u4EF7\uFF09\u3002
|
|
22431
|
+
\u4EF7\u683C\u4EE5\u4E3B\u8D27\u5E01\u5355\u4F4D\uFF08\u5143\uFF09\u5B58\u50A8\uFF0C\u5207\u52FF \xD7100\u3002\u4E1C\u5357\u4E9A\u8DE8\u5883\u5E97\u901A\u5E38\u9700\u8986\u76D6 myr/sgd/idr/thb/php/cny\u3002
|
|
22432
|
+
\u4E0D\u6307\u5B9A variant_id \u65F6\u4F5C\u7528\u4E8E\u7B2C\u4E00\u4E2A\u53D8\u4F53\uFF08\u5355 SKU \u5546\u54C1\u5373\u552F\u4E00\u53D8\u4F53\uFF09\u3002\u9ED8\u8BA4 dry_run\uFF08confirm=false\uFF09\u4EC5\u9884\u89C8\u3002`,
|
|
22433
|
+
schema: {
|
|
22434
|
+
product_id: external_exports.string(),
|
|
22435
|
+
prices: external_exports.array(external_exports.object({ currency_code: external_exports.string(), amount: external_exports.number().nonnegative() })).describe('\u591A\u5E01\u79CD\u4EF7\u683C\u6570\u7EC4\uFF08\u4E3B\u8D27\u5E01\u5355\u4F4D\uFF09\uFF0C\u5982 [{currency_code:"myr",amount:420},{currency_code:"sgd",amount:127}]'),
|
|
22436
|
+
variant_id: external_exports.string().optional().describe("\u76EE\u6807\u53D8\u4F53 id\uFF1B\u7701\u7565\u5219\u53D6\u7B2C\u4E00\u4E2A\u53D8\u4F53"),
|
|
22437
|
+
confirm: external_exports.boolean().default(false)
|
|
22438
|
+
},
|
|
22439
|
+
async run(input, { client: client2 }) {
|
|
22440
|
+
if (!input.confirm) {
|
|
22441
|
+
return {
|
|
22442
|
+
dry_run: true,
|
|
22443
|
+
product_id: input.product_id,
|
|
22444
|
+
will_set_prices: input.prices,
|
|
22445
|
+
variant_id: input.variant_id || "(\u7B2C\u4E00\u4E2A\u53D8\u4F53)"
|
|
22446
|
+
};
|
|
22447
|
+
}
|
|
22448
|
+
const prod = await client2.getProduct(input.product_id).catch(() => null);
|
|
22449
|
+
const product = prod?.product ?? prod;
|
|
22450
|
+
if (!product) return `\u9519\u8BEF\uFF1A\u627E\u4E0D\u5230\u5546\u54C1 ${input.product_id}`;
|
|
22451
|
+
const variants = product.variants || [];
|
|
22452
|
+
if (!variants.length) return `\u9519\u8BEF\uFF1A\u5546\u54C1\u6CA1\u6709\u53D8\u4F53\uFF0C\u65E0\u6CD5\u8BBE\u7F6E\u4EF7\u683C\u3002`;
|
|
22453
|
+
const target = input.variant_id ? variants.find((v) => v.id === input.variant_id) : variants[0];
|
|
22454
|
+
if (!target) return `\u9519\u8BEF\uFF1A\u627E\u4E0D\u5230\u76EE\u6807\u53D8\u4F53\uFF08\u6307\u5B9A ${input.variant_id} \u6216\u7B2C\u4E00\u4E2A\u53D8\u4F53\uFF09\u3002`;
|
|
22455
|
+
const data = await client2.updateProductPrices(
|
|
22456
|
+
input.product_id,
|
|
22457
|
+
target.id,
|
|
22458
|
+
input.prices.map((p) => ({ amount: p.amount, currency_code: p.currency_code }))
|
|
22459
|
+
);
|
|
22460
|
+
return { updated: data };
|
|
21876
22461
|
}
|
|
21877
22462
|
},
|
|
21878
22463
|
{
|
|
@@ -21886,76 +22471,131 @@ var productTools = [
|
|
|
21886
22471
|
if (!input.confirm) return { dry_run: true, will_set_status: "archived" };
|
|
21887
22472
|
return client2.updateProduct(input.id, { status: "archived" });
|
|
21888
22473
|
}
|
|
21889
|
-
}
|
|
21890
|
-
];
|
|
21891
|
-
|
|
21892
|
-
// src/tools/stock.ts
|
|
21893
|
-
var stockTools = [
|
|
22474
|
+
},
|
|
21894
22475
|
{
|
|
21895
|
-
name: "
|
|
21896
|
-
description:
|
|
22476
|
+
name: "upload_product_image",
|
|
22477
|
+
description: `\u7ED9\u5DF2\u5B58\u5728\u7684\u5546\u54C1\u8FFD\u52A0\u56FE\u7247\u3002\u652F\u6301\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\uFF08\u81EA\u52A8\u4E0A\u4F20\u5230\u6587\u4EF6\u670D\u52A1\uFF09\u6216\u56FE\u7247 URL\u3002
|
|
22478
|
+
\u9ED8\u8BA4 dry_run\uFF08confirm=false\uFF09\u4EC5\u9884\u89C8\uFF1Bconfirm=true \u624D\u4E0A\u4F20\u5E76\u6302\u5230\u5546\u54C1\uFF08\u4E0E\u73B0\u6709\u56FE\u7247\u5408\u5E76\uFF0C\u4E0D\u4F1A\u8986\u76D6\u5DF2\u6709\u56FE\u7247\uFF09\u3002`,
|
|
21897
22479
|
schema: {
|
|
21898
|
-
|
|
21899
|
-
|
|
22480
|
+
product_id: external_exports.string(),
|
|
22481
|
+
images: external_exports.array(external_exports.string()).describe("\u672C\u5730\u56FE\u7247\u8DEF\u5F84\u6216\u56FE\u7247 URL"),
|
|
22482
|
+
confirm: external_exports.boolean().default(false)
|
|
21900
22483
|
},
|
|
21901
22484
|
async run(input, { client: client2 }) {
|
|
21902
|
-
|
|
22485
|
+
if (!input.confirm) {
|
|
22486
|
+
return { dry_run: true, product_id: input.product_id, will_upload: input.images };
|
|
22487
|
+
}
|
|
22488
|
+
let urls = [];
|
|
22489
|
+
try {
|
|
22490
|
+
urls = await client2.uploadProductAssets(input.images);
|
|
22491
|
+
} catch (e) {
|
|
22492
|
+
return `\u9519\u8BEF\uFF1A\u56FE\u7247\u4E0A\u4F20\u5931\u8D25 \u2014\u2014 ${e?.message || e}`;
|
|
22493
|
+
}
|
|
22494
|
+
const existing = await client2.getProduct(input.product_id).catch(() => null);
|
|
22495
|
+
const prod = existing?.product ?? existing ?? {};
|
|
22496
|
+
const existingUrls = (prod.images || []).map((i) => i.url).filter(Boolean);
|
|
22497
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...existingUrls, ...urls]));
|
|
22498
|
+
const data = await client2.updateProduct(input.product_id, {
|
|
22499
|
+
images: merged.map((u) => ({ url: u }))
|
|
22500
|
+
});
|
|
22501
|
+
return { updated: data };
|
|
21903
22502
|
}
|
|
21904
22503
|
},
|
|
21905
22504
|
{
|
|
21906
|
-
name: "
|
|
21907
|
-
description:
|
|
22505
|
+
name: "add_product_to_sales_channel",
|
|
22506
|
+
description: `\u628A\u5546\u54C1\u52A0\u5165\u9500\u552E\u6E20\u9053\uFF0C\u4F7F\u5176\u5728\u524D\u7AEF\u5546\u57CE\uFF08\u6309 publishable key \u2192 \u6E20\u9053\u8FC7\u6EE4\uFF09\u53EF\u89C1\u3002
|
|
22507
|
+
\u26A0\uFE0F Medusa v2 \u7AEF\u70B9\u6302\u5728\u6E20\u9053\u4E0B\uFF1APOST /admin/sales-channels/{id}/products\uFF0C\u800C\u975E\u5546\u54C1\u4E0B\u3002
|
|
22508
|
+
\u4E0D\u6307\u5B9A channel_id \u65F6\u9ED8\u8BA4\u52A0\u5165\u7B2C\u4E00\u4E2A\u542F\u7528\u7684\u6E20\u9053\u3002\u9ED8\u8BA4 dry_run\uFF08confirm=false\uFF09\u4EC5\u9884\u89C8\u3002`,
|
|
21908
22509
|
schema: {
|
|
21909
|
-
|
|
21910
|
-
|
|
21911
|
-
address_1: external_exports.string().optional(),
|
|
21912
|
-
address_2: external_exports.string().optional(),
|
|
21913
|
-
city: external_exports.string().optional(),
|
|
21914
|
-
country_code: external_exports.string().optional(),
|
|
21915
|
-
province: external_exports.string().optional(),
|
|
21916
|
-
postal_code: external_exports.string().optional(),
|
|
21917
|
-
phone: external_exports.string().optional()
|
|
21918
|
-
}).optional(),
|
|
21919
|
-
metadata: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
22510
|
+
product_ids: external_exports.array(external_exports.string()).describe("\u8981\u52A0\u5165\u6E20\u9053\u7684\u5546\u54C1 id \u5217\u8868"),
|
|
22511
|
+
channel_id: external_exports.string().optional().describe("\u9500\u552E\u6E20\u9053 id\uFF1B\u7701\u7565\u5219\u53D6\u7B2C\u4E00\u4E2A\u542F\u7528\u7684\u6E20\u9053"),
|
|
21920
22512
|
confirm: external_exports.boolean().default(false)
|
|
21921
22513
|
},
|
|
21922
|
-
async run(input, { client: client2 }) {
|
|
21923
|
-
|
|
21924
|
-
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
|
|
22514
|
+
async run(input, { client: client2, config: config3 }) {
|
|
22515
|
+
if (!input.confirm) {
|
|
22516
|
+
return { dry_run: true, will_add: input.product_ids, channel_id: input.channel_id || "(\u9ED8\u8BA4\u7B2C\u4E00\u4E2A\u542F\u7528\u6E20\u9053)" };
|
|
22517
|
+
}
|
|
22518
|
+
let channelId = input.channel_id;
|
|
22519
|
+
if (!channelId) {
|
|
22520
|
+
const ch = await client2.listSalesChannels?.();
|
|
22521
|
+
const list = ch?.sales_channels || [];
|
|
22522
|
+
const firstEnabled = list.find((c) => !c.is_disabled) || list[0];
|
|
22523
|
+
if (!firstEnabled) return "\u9519\u8BEF\uFF1A\u627E\u4E0D\u5230\u53EF\u7528\u7684\u9500\u552E\u6E20\u9053\u3002";
|
|
22524
|
+
channelId = firstEnabled.id;
|
|
22525
|
+
}
|
|
22526
|
+
const data = await client2.addProductsToSalesChannel(channelId, input.product_ids);
|
|
22527
|
+
return { added_to_channel: channelId, result: data };
|
|
21928
22528
|
}
|
|
21929
22529
|
},
|
|
21930
22530
|
{
|
|
21931
|
-
name: "
|
|
21932
|
-
description:
|
|
22531
|
+
name: "get_product_content",
|
|
22532
|
+
description: `\u8BFB\u53D6\u5546\u54C1\u8BE6\u60C5\u9875\u5BCC\u5185\u5BB9\u5757\uFF08GET /admin/product-contents\uFF09\u3002
|
|
22533
|
+
locale \u7528\u533A\u57DF\u7801\uFF1Aen-US / zh-CN / ms-MY / id-ID\uFF08\u4E0E\u524D\u7AEF useProductContent \u7684 localeMap \u4E00\u81F4\uFF09\u3002
|
|
22534
|
+
\u8FD4\u56DE { product_content: { id, product_id, locale, status, blocks(\u5DF2\u89E3\u6790\u6570\u7EC4) } | null }\u3002`,
|
|
21933
22535
|
schema: {
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
22536
|
+
product_id: external_exports.string(),
|
|
22537
|
+
locale: external_exports.enum(["en-US", "zh-CN", "ms-MY", "id-ID"]).default("en-US"),
|
|
22538
|
+
status: external_exports.enum(["draft", "published"]).default("published")
|
|
21937
22539
|
},
|
|
21938
22540
|
async run(input, { client: client2 }) {
|
|
21939
|
-
|
|
22541
|
+
const data = await client2.getProductContent(input.product_id, input.locale, input.status);
|
|
22542
|
+
const pc = data?.product_content;
|
|
22543
|
+
if (!pc) return { product_content: null };
|
|
22544
|
+
let blocks = [];
|
|
22545
|
+
try {
|
|
22546
|
+
blocks = pc.blocks ? JSON.parse(pc.blocks) : [];
|
|
22547
|
+
} catch {
|
|
22548
|
+
blocks = [];
|
|
22549
|
+
}
|
|
22550
|
+
return { product_content: { ...pc, blocks } };
|
|
21940
22551
|
}
|
|
21941
22552
|
},
|
|
21942
22553
|
{
|
|
21943
|
-
name: "
|
|
21944
|
-
description: `\
|
|
21945
|
-
\
|
|
22554
|
+
name: "create_product_content",
|
|
22555
|
+
description: `\u521B\u5EFA/\u66F4\u65B0\u5546\u54C1\u8BE6\u60C5\u9875\u5BCC\u5185\u5BB9\u5757\uFF08POST /admin/product-contents\uFF0C\u6309 product_id+locale upsert\uFF09\u3002
|
|
22556
|
+
storefront \u8BE6\u60C5\u9875\u300C\u5BCC\u5185\u5BB9\u300D\u533A\u7531\u8FD9\u4E9B\u5757\u6E32\u67D3\u2014\u2014\u4EC5\u586B product.description \u4E0D\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\uFF0C\u5FC5\u987B\u5EFA\u5185\u5BB9\u5757\uFF0C\u5426\u5219\u8BE6\u60C5\u9875\u4E3B\u4F53\u7A7A\u767D\u3002
|
|
22557
|
+
\u533A\u5757\u7C7B\u578B type \u2208 hero / richtext / image_gallery / video / faq / specs\uFF1A
|
|
22558
|
+
- richtext: data.{ html }\uFF08\u76F4\u63A5\u6E32\u67D3 HTML\uFF0C\u53EF\u542B <p>/<h2>/<ul> \u7B49\uFF09
|
|
22559
|
+
- image_gallery: data.{ images:string[], caption? }
|
|
22560
|
+
- hero: data.{ image?, title?, subtitle? }
|
|
22561
|
+
- video: data.{ url, poster? }\uFF08\u652F\u6301 YouTube\uFF09
|
|
22562
|
+
- faq: data.{ items: {question,answer}[] }
|
|
22563
|
+
- specs: data.{ rows: {label,value}[] }
|
|
22564
|
+
\u26A0\uFE0F locale \u5FC5\u987B\u7528\u533A\u57DF\u7801 en-US / zh-CN / ms-MY / id-ID\uFF08\u4E0E\u524D\u7AEF\u4E00\u81F4\uFF09\uFF1B\u5546\u54C1\u6807\u9898/\u63CF\u8FF0\u7684\u7FFB\u8BD1\u7528\u77ED\u7801 en/zh/ms/id\uFF0C\u4E8C\u8005\u4E0D\u540C\u3002
|
|
22565
|
+
\u9ED8\u8BA4 dry_run\uFF08confirm=false\uFF09\u4EC5\u9884\u89C8\u3002`,
|
|
21946
22566
|
schema: {
|
|
21947
|
-
|
|
21948
|
-
|
|
21949
|
-
|
|
22567
|
+
product_id: external_exports.string(),
|
|
22568
|
+
locale: external_exports.enum(["en-US", "zh-CN", "ms-MY", "id-ID"]).default("en-US"),
|
|
22569
|
+
status: external_exports.enum(["draft", "published"]).default("published"),
|
|
22570
|
+
blocks: external_exports.array(
|
|
22571
|
+
external_exports.object({
|
|
22572
|
+
id: external_exports.string().optional(),
|
|
22573
|
+
type: external_exports.enum(["hero", "richtext", "image_gallery", "video", "faq", "specs"]),
|
|
22574
|
+
data: external_exports.record(external_exports.string(), external_exports.any())
|
|
22575
|
+
})
|
|
22576
|
+
).describe("ContentBlock[] \u6570\u7EC4"),
|
|
21950
22577
|
confirm: external_exports.boolean().default(false)
|
|
21951
22578
|
},
|
|
21952
22579
|
async run(input, { client: client2 }) {
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
22580
|
+
if (!input.confirm) {
|
|
22581
|
+
return {
|
|
22582
|
+
dry_run: true,
|
|
22583
|
+
will_create: {
|
|
22584
|
+
product_id: input.product_id,
|
|
22585
|
+
locale: input.locale,
|
|
22586
|
+
status: input.status,
|
|
22587
|
+
block_count: input.blocks.length,
|
|
22588
|
+
types: input.blocks.map((b) => b.type)
|
|
22589
|
+
}
|
|
22590
|
+
};
|
|
22591
|
+
}
|
|
22592
|
+
const data = await client2.createProductContent({
|
|
22593
|
+
product_id: input.product_id,
|
|
22594
|
+
locale: input.locale,
|
|
22595
|
+
status: input.status,
|
|
22596
|
+
blocks: input.blocks
|
|
22597
|
+
});
|
|
22598
|
+
return { created: data };
|
|
21959
22599
|
}
|
|
21960
22600
|
}
|
|
21961
22601
|
];
|
|
@@ -22012,11 +22652,11 @@ var opsTools = [
|
|
|
22012
22652
|
];
|
|
22013
22653
|
|
|
22014
22654
|
// src/resources/docs.ts
|
|
22015
|
-
import { readdirSync, readFileSync as
|
|
22655
|
+
import { readdirSync, readFileSync as readFileSync3, existsSync as existsSync3, statSync } from "node:fs";
|
|
22016
22656
|
import { join as join2 } from "node:path";
|
|
22017
22657
|
var cache = null;
|
|
22018
22658
|
function walk(dir, out = []) {
|
|
22019
|
-
if (!
|
|
22659
|
+
if (!existsSync3(dir)) return out;
|
|
22020
22660
|
for (const entry of readdirSync(dir)) {
|
|
22021
22661
|
const full = join2(dir, entry);
|
|
22022
22662
|
try {
|
|
@@ -22028,7 +22668,7 @@ function walk(dir, out = []) {
|
|
|
22028
22668
|
return out;
|
|
22029
22669
|
}
|
|
22030
22670
|
function parseFile(path) {
|
|
22031
|
-
const raw =
|
|
22671
|
+
const raw = readFileSync3(path, "utf8");
|
|
22032
22672
|
const lines = raw.split("\n");
|
|
22033
22673
|
const title = lines.find((l) => l.startsWith("# "))?.slice(2).trim() || path;
|
|
22034
22674
|
const sections = [];
|
|
@@ -22139,7 +22779,7 @@ var config2 = loadConfig();
|
|
|
22139
22779
|
var auth = new AuthManager(config2);
|
|
22140
22780
|
var client = new MedusaAdminClient(config2, auth);
|
|
22141
22781
|
var ctx = { client, config: config2 };
|
|
22142
|
-
var server = new McpServer({ name: "derlux-admin", version: "0.1.
|
|
22782
|
+
var server = new McpServer({ name: "derlux-admin", version: "0.1.2" });
|
|
22143
22783
|
for (const def of toolDefs) {
|
|
22144
22784
|
server.tool(def.name, def.description, def.schema, async (args) => {
|
|
22145
22785
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sea-ecommerce/admin-mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "让运营同学用本地 AI agent(Codex / Workbuddy / Claude / Cursor / CodeBuddy)运营 Medusa 后台的 MCP server。",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
-
"README.md"
|
|
12
|
+
"README.md",
|
|
13
|
+
"skill"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
16
|
"dev": "tsx src/index.ts",
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: derlux-admin-mcp-ops
|
|
3
|
+
description: 运营使用本地 AI agent(derlux-admin MCP server)操作 DERLUX Store 后台的指南。覆盖商品创建/修改/下架、仓库与库存调整、订单/退货/仓库作业查询、后台使用问答。当用户要让 agent 创建、修改或下架商品、调整某仓库库存、查询订单或退货、或询问「后台怎么操作某功能」时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DERLUX 运营后台 MCP 操作指南(derlux-admin)
|
|
7
|
+
|
|
8
|
+
> 本文件随 npm 包 `@sea-ecommerce/admin-mcp-server` 一同发布,运营/agent 可经 npm 公开文件链接读取,
|
|
9
|
+
> 例如:`https://unpkg.com/@sea-ecommerce/admin-mcp-server/skill/SKILL.md`
|
|
10
|
+
> 或 `https://cdn.jsdelivr.net/npm/@sea-ecommerce/admin-mcp-server/skill/SKILL.md`
|
|
11
|
+
|
|
12
|
+
## 你是谁 / 这个 skill 是做什么的
|
|
13
|
+
|
|
14
|
+
你已接入一个名为 **`derlux-admin`** 的 MCP server。它把 DERLUX Store(面向马来西亚/印尼的东南亚电商,法律主体「香港德樂仕股份有限公司」)Medusa 后台的运营动作封装成一批语义化工具(tool),由你直接调用,代替运营同学在后台界面手动点选。
|
|
15
|
+
|
|
16
|
+
关键前提(务必记牢):
|
|
17
|
+
|
|
18
|
+
- **认证复用真实后台管理员账号**(须持有 `csr` 或 `super_admin` 角色),权限受现有 RBAC 约束。敏感动作若账号无权限,后端会直接拦截。
|
|
19
|
+
- **所有写操作默认是「预览(dry_run)」**,只有显式设 `confirm=true` 才会真正落到后台。这是给运营的兜底保护,不要跳过预览。
|
|
20
|
+
- 每次调用都会写入审计日志 `~/.derlux/mcp-audit.log`,操作可追溯。
|
|
21
|
+
|
|
22
|
+
## ⚠️ 必须遵守的硬规则(每次调用写工具前自查)
|
|
23
|
+
|
|
24
|
+
1. **预览优先,确认后执行**:任何写工具先以 `confirm=false`(默认)跑一次,把返回的预览读给运营确认;得到明确确认后,再用 `confirm=true` 真正执行。绝不在未确认时直接写库。
|
|
25
|
+
2. **价格单位 = 主货币单位(元),不要 ×100**:金额直接透传主单位,例如 `200.51` 就是 RM200.51 / MYR200.51。Medusa v2.21 以主单位存储金额,**绝不要写成 20051**。
|
|
26
|
+
3. **Handle 自动归一化**:商品 `handle` 会被自动转小写、空格/下划线/点→连字符、剔除其余非法字符。归一化后仍非法(空或全部被剔除)工具会直接报错,你无需手工处理,但应避免给无意义输入;未填时由 `title` 自动生成。
|
|
27
|
+
4. **多语言必填**:`create_product` / `update_product` 的 `translations` 应覆盖 `en` / `zh` / `ms` / `id` 四种语言的 `title`(建议也带 `description`)。缺语言时:预览会告警;真正写入需 `confirm=true` **且** `acknowledge_missing_languages=true`。创建前主动提醒运营补全翻译,不要默默放行。工具会**自动**把翻译写入 Medusa v2.21 专用的 `/admin/translations/batch` 端点(商品创建体本身不接受 `translations` 字段),你无需手动调用该端点。
|
|
28
|
+
5. **审计可追溯**:无需你处理,但要知道每次动作都在 `~/.derlux/mcp-audit.log` 留痕。
|
|
29
|
+
6. **商品必须挂销售渠道才在前端可见**:新建商品默认**不在任何销售渠道**,storefront(按 publishable key → 渠道过滤)拉不到它,详情页表现成「空/404」。录入后用 `add_product_to_sales_channel`(`confirm=true`)把它加入渠道(默认第一个启用渠道;马来西亚站用 `sc_01M4K4F0D6HBHZ0NH6WB7H5KPT`「MY Storefront」)。
|
|
30
|
+
7. **两套 locale 码,别混**:
|
|
31
|
+
- 商品标题/描述的**翻译**(`create_product` 的 `translations`、后端 `/admin/translations/batch`):用**简码** `en` / `zh` / `ms` / `id`。store `/store/products?locale=zh` 即回退到 zh 翻译。
|
|
32
|
+
- 商品详情页**富内容块**(`create_product_content` 的 `locale`):必须用**区域码** `en-US` / `zh-CN` / `ms-MY` / `id-ID`(与前端 `useProductContent` 的 localeMap 一致)。用简码会导致该语言详情页读不到内容。
|
|
33
|
+
8. **详情页「富内容」区靠 `create_product_content`,不是 `description`**:storefront 详情页下方大块内容由 `product_content` 模块渲染(`richtext`/`specs`/`image_gallery` 等)。只填 `product.description` 不会显示在那里——录入完整商品时必须再建一份 `product_content`(建议每语言一份)。`get_product` 默认不含该字段,核对请用 `get_product_content`。
|
|
34
|
+
9. **不建库存 = 前端缺货**:变体 `manage_inventory` 默认 `true`,但新商品默认 0 库存——`create_product` 时必须给 `initial_stock`(如 `{quantity:100}`,默认写 Main Warehouse),或对已有商品调 `set_product_stock`,否则前台显示缺货、无法下单。
|
|
35
|
+
10. **多语言核对用 `get_translations`**:`get_product` 默认字段**不含** translations,不要凭它为空就判断没翻译。用 `get_translations`(reference_id=商品id或handle) 读取实际写入的各语言 title/description。
|
|
36
|
+
|
|
37
|
+
## 工具速查表
|
|
38
|
+
|
|
39
|
+
| 工具 | 类型 | 写库? | 默认行为 |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| `list_products` | 读 | 否 | 直接返回 |
|
|
42
|
+
| `get_product` | 读 | 否 | 直接返回 |
|
|
43
|
+
| `list_sales_channels` | 读 | 否 | 直接返回(渠道 id/名称,建商品前确认用)|
|
|
44
|
+
| `list_collections` | 读 | 否 | 直接返回 |
|
|
45
|
+
| `get_translations` | 读 | 否 | 直接返回(某商品各语言的 title/description,多语言核对唯一可靠途径)|
|
|
46
|
+
| `list_stock_locations` | 读 | 否 | 直接返回 |
|
|
47
|
+
| `get_inventory_levels` | 读 | 否 | 直接返回(location_id 可选:按仓库列库存条目与水平;q 按 sku/title 过滤)|
|
|
48
|
+
| `set_product_stock` | 写 | 是(`confirm=true` 才写)| 幂等建仓:自动找/建库存条目 + 写仓库数量 + manage_inventory;`confirm=false` 仅预览 |
|
|
49
|
+
| `list_orders` | 读 | 否 | 直接返回 |
|
|
50
|
+
| `get_order` | 读 | 否 | 直接返回 |
|
|
51
|
+
| `list_warehouse_tasks` | 读 | 否 | 直接返回(自定义端点)|
|
|
52
|
+
| `list_return_requests` | 读 | 否 | 直接返回(自定义端点)|
|
|
53
|
+
| `search_docs` | 读 | 否 | 检索后台知识库 |
|
|
54
|
+
| `ask_usage` | 读 | 否 | 回答「后台怎么操作」类问题 |
|
|
55
|
+
| `create_product` | 写 | 是(`confirm=true` 才写)| `confirm=false` → 仅预览 |
|
|
56
|
+
| `update_product` | 写 | 是(`confirm=true` 才写)| `confirm=false` → 仅预览 |
|
|
57
|
+
| `deactivate_product` | 写 | 是(`confirm=true` 才写)| `confirm=false` → 仅预览,置 `archived` |
|
|
58
|
+
| `upload_product_image` | 写 | 是(`confirm=true` 才写)| `confirm=false` → 仅预览,给已有商品追加图片 |
|
|
59
|
+
| `add_product_to_sales_channel` | 写 | 是(`confirm=true` 才写)| `confirm=false` → 仅预览,把商品加入销售渠道使其在前端可见 |
|
|
60
|
+
| `create_product_content` | 写 | 是(`confirm=true` 才写)| `confirm=false` → 仅预览,写商品详情页富内容块(hero/richtext/image_gallery/video/faq/specs)|
|
|
61
|
+
| `get_product_content` | 读 | 否 | 读取某商品某语言的富内容块 |
|
|
62
|
+
| `create_stock_location` | 写 | 是(`confirm=true` 才写)| `confirm=false` → 仅预览 |
|
|
63
|
+
| `update_inventory_level` | 写 | 是(`confirm=true` 才写)| `confirm=false` → 仅预览 |
|
|
64
|
+
|
|
65
|
+
## 各工具参数与说明
|
|
66
|
+
|
|
67
|
+
### 读 —— 商品
|
|
68
|
+
- **`list_products`**:查询商品列表。
|
|
69
|
+
- `q`(string,可选)关键词;`status`(enum:`draft|published|proposed|rejected|archived`,可选)状态过滤;`handle`(string,可选);`limit`(int 1–100,默认 20)。
|
|
70
|
+
- **`get_product`**:按商品 id 或 handle 获取详情。
|
|
71
|
+
- `id_or_handle`(string,**必填**)。
|
|
72
|
+
- **`list_sales_channels`**:列出销售渠道(含 id / 名称 / 启用状态)。
|
|
73
|
+
- `q`(string,可选)按名称过滤;`limit`(int 1–100,默认 50)。
|
|
74
|
+
- **`list_collections`**:列出集合(如「双11活动」)。
|
|
75
|
+
- `q`(string,可选);`limit`(int 1–100,默认 50)。
|
|
76
|
+
- **`get_translations`**:读取多语言翻译(GET /admin/translations)。
|
|
77
|
+
- `reference_id`(string,**必填**,商品 id 或 handle);`reference`(默认 `product`);`locale_code`(可选,如 `zh`);`limit`(默认 50)。
|
|
78
|
+
|
|
79
|
+
### 读 —— 仓库与库存
|
|
80
|
+
- **`list_stock_locations`**:列出仓库(StockLocation)。
|
|
81
|
+
- `q`(string,可选)按名称过滤;`limit`(int 1–100,默认 50)。
|
|
82
|
+
- **`get_inventory_levels`**:列出库存条目及库存水平(Medusa 2.21 走 `GET /admin/inventory-items`)。
|
|
83
|
+
- `location_id`(string,**可选**:只回该仓库有水平的条目);`q`(string,可选,按 sku/title 过滤);`limit`(int 1–100,默认 50)。
|
|
84
|
+
|
|
85
|
+
### 读 —— 订单 / 履约 / 退货
|
|
86
|
+
- **`list_orders`**:查询订单列表。
|
|
87
|
+
- `q`(string,可选);`status`(string,可选);`limit`(int 1–100,默认 20)。
|
|
88
|
+
- **`get_order`**:按订单 id 或 `display_id` 获取详情。
|
|
89
|
+
- `id`(string,**必填**)。
|
|
90
|
+
- **`list_warehouse_tasks`**:查询仓库作业任务(自定义端点 `/admin/warehouse-tasks`)。
|
|
91
|
+
- `order_id`(string,可选);`stage`(string,可选);`limit`(int 1–200,默认 200)。
|
|
92
|
+
- **`list_return_requests`**:查询退货/售后请求(自定义端点 `/admin/return-requests`)。
|
|
93
|
+
- `status`(string,可选);`limit`(int 1–100,默认 50)。
|
|
94
|
+
|
|
95
|
+
### 读 —— 后台知识问答
|
|
96
|
+
- **`search_docs`**:在知识库(docs)中检索相关段落,返回文件、章节与摘要。
|
|
97
|
+
- `query`(string,**必填**);`limit`(int 1–20,默认 5)。
|
|
98
|
+
- **`ask_usage`**:回答「后台怎么操作」类问题,返回最相关段落与出处文件。
|
|
99
|
+
- `question`(string,**必填**);`limit`(int 1–10,默认 5)。若搜不到,提示运营换关键词或查后台手册。
|
|
100
|
+
|
|
101
|
+
### 写 —— 商品
|
|
102
|
+
- **`create_product`**:新增商品。
|
|
103
|
+
- `title`(string,**必填**);`handle`(string,可选,缺省由 title 归一化);`status`(enum,默认 `draft`);`subtitle`(可选);`description`(可选);`price`(number ≥0,主货币单位,**切勿 ×100**);`currency_code`(默认 `MYR`);`prices`(`{currency_code,amount}[]`,可选,多币种,优先于单一 price);`translations`(`record<locale,{title?,description?}>`,建议覆盖 `en/zh/ms/id`);`images`(string[],可选,本地图片路径或图片 URL,工具自动上传并挂接);`sku`(string,可选,写入默认变体 SKU);`metadata`(`record<string,string>`,可选,存规格参数/来源链接等任意键值);`sales_channels`(string[],可选,渠道 id 或名称如 `"MY Storefront"`,创建即挂载);`collection_id`(string,可选,集合 id 或名称);`weight`/`length`/`height`/`width`(number,可选,克/厘米);`origin_country`/`hs_code`/`mid_code`/`material`(string,可选);`manage_inventory`(bool,**默认 true**);`initial_stock`(`{location_id?, quantity}`,可选,建库存;**正式上架必给**,见硬规则 9);`confirm`(bool,默认 false);`acknowledge_missing_languages`(bool,默认 false)。
|
|
104
|
+
- 行为:`confirm=false` 返回预览(含缺语言清单、缺货告警);`confirm=true` 才真正创建;缺语言时须同时 `acknowledge_missing_languages=true`。创建时**自动**补上 Medusa v2 必需的 `options`(单一 Default)、图片上传并挂接、`sku` 写入默认变体、`translations` 写入 v2.21 专用的 `/admin/translations/batch` 端点、按 `initial_stock` 建库存条目并写仓库。
|
|
105
|
+
- **`update_product`**:修改商品字段(标题/handle/状态/描述/多语言/物理尺寸/海关/集合/metadata)。
|
|
106
|
+
- `id`(string,**必填**);`title`/`handle`/`status`/`subtitle`/`description`/`translations`/`collection_id`(集合 id 或名称)/`weight`/`length`/`height`/`width`/`origin_country`/`hs_code`/`mid_code`/`material`/`metadata`(均可选)。
|
|
107
|
+
- `metadata` 为**合并**语义:新键覆盖同名键、未提及的旧键保留(不支持删除键)。改库存请用 `set_product_stock`,改价格请用 `update_product_prices`。
|
|
108
|
+
- `confirm`(默认 false)。`confirm=false` 返回合并后的完整预览。
|
|
109
|
+
- **`deactivate_product`**:下架商品(status 置 `archived`)。
|
|
110
|
+
- `id`(string,**必填**);`confirm`(默认 false)。`confirm=false` 返回预览。
|
|
111
|
+
- **`upload_product_image`**:给**已存在**的商品追加图片(与已有图片合并,不覆盖)。
|
|
112
|
+
- `product_id`(string,**必填**);`images`(string[],本地图片路径或图片 URL);`confirm`(默认 false)。`confirm=false` 仅预览;`confirm=true` 才上传并挂接。
|
|
113
|
+
- **`add_product_to_sales_channel`**:把商品加入销售渠道,使其在前端商城可见。
|
|
114
|
+
- `product_ids`(string[],**必填**,商品 id 列表);`channel_id`(string,可选,省略则取第一个启用渠道);`confirm`(默认 false)。`confirm=false` 仅预览。
|
|
115
|
+
- **`get_product_content`**:读取商品详情页富内容块。
|
|
116
|
+
- `product_id`(string,**必填**);`locale`(`en-US` / `zh-CN` / `ms-MY` / `id-ID`,默认 `en-US`);`status`(`draft` / `published`,默认 `published`)。返回 `{ product_content: { id, product_id, locale, status, blocks(已解析数组) } | null }`。
|
|
117
|
+
- **`create_product_content`**:创建/更新商品详情页富内容块(按 `product_id` + `locale` upsert)。**这是填充详情页主体内容的工具**——只填 `description` 详情页大块区域仍是空白。
|
|
118
|
+
- `product_id`(string,**必填**);`locale`(区域码,默认 `en-US`,见硬规则 7);`status`(默认 `published`);`blocks`(ContentBlock[],**必填**);`confirm`(默认 false)。
|
|
119
|
+
- 区块 `type` 与 `data`:
|
|
120
|
+
- `richtext`:`data.{ html }`(直接渲染 HTML,可含 `<p>/<h2>/<ul>`)
|
|
121
|
+
- `image_gallery`:`data.{ images:string[], caption? }`
|
|
122
|
+
- `hero`:`data.{ image?, title?, subtitle? }`
|
|
123
|
+
- `video`:`data.{ url, poster? }`(支持 YouTube)
|
|
124
|
+
- `faq`:`data.{ items: {question,answer}[] }`
|
|
125
|
+
- `specs`:`data.{ rows: {label,value}[] }`
|
|
126
|
+
- `confirm=false` 返回预览(含块数量与类型);`confirm=true` 才写入。
|
|
127
|
+
|
|
128
|
+
### 写 —— 仓库与库存
|
|
129
|
+
- **`create_stock_location`**:新增仓库(StockLocation)。
|
|
130
|
+
- `name`(string,**必填**);`address`(可选:`{address_1?,address_2?,city?,country_code?,province?,postal_code?,phone?}`);`metadata`(`record<string,string>`,可选);`confirm`(默认 false)。
|
|
131
|
+
- **`set_product_stock`**:给商品建仓/改库存(**运营最常用的库存工具**,幂等)。
|
|
132
|
+
- `product_id`(string,**必填**,商品 id 或 handle);`sku`(string,可选,目标变体;省略取第一个变体);`location_id`(string,可选,省略=Main Warehouse/第一个仓库);`quantity`(int ≥0,**必填**,绝对值非增量);`manage_inventory`(bool,默认 true);`confirm`(默认 false)。
|
|
133
|
+
- 行为:按变体 SKU 查找库存条目(缺失则自动 `POST /admin/inventory-items` 创建)→ 幂等写 (条目, 仓库) 库存水平(有则更新、无则创建)→ 确保变体 `manage_inventory` 为 true。重复调用安全。
|
|
134
|
+
- **`update_inventory_level`**:直接改某**库存条目**(`inventory_item_id`)在某仓库的数量(upsert,幂等)。
|
|
135
|
+
- `inventory_item_id`(string,**必填**);`location_id`(string,**必填**);`stocked_quantity`(int ≥0,**必填**);`confirm`(默认 false)。已知 `inventory_item_id` 时用;否则优先 `set_product_stock`。
|
|
136
|
+
|
|
137
|
+
## 推荐操作流程(SOP)
|
|
138
|
+
|
|
139
|
+
**创建商品**
|
|
140
|
+
1. 先收集:标题、handle(或告知由标题生成)、价格(主单位)、四语言标题、状态、图片、规格、重量、初始库存数量、目标渠道、详情页富文本内容(按语言)。
|
|
141
|
+
2. 调用 `create_product`(`confirm=false`)→ 展示预览与「缺语言 / 缺货」告警。
|
|
142
|
+
3. 提醒运营补全缺失语言与 `initial_stock`;确认无误后调用 `create_product`(`confirm=true`,`sales_channels` 里带上目标渠道,必要时 `acknowledge_missing_languages=true`)。创建即完成:图片上传、SKU、翻译、渠道挂载、建库存。
|
|
143
|
+
4. **`create_product_content`(`confirm=true`,逐语言 `en-US/zh-CN/ms-MY/id-ID`)** → 写详情页富内容块(richtext 描述 + specs 规格 + image_gallery 图集)。否则详情页主体空白。
|
|
144
|
+
5. 用 `get_translations` + `get_product_content` 核对多语言与富内容;把新商品 id/handle 告知运营。
|
|
145
|
+
|
|
146
|
+
**修改 / 下架商品**
|
|
147
|
+
1. `get_product` 或 `list_products` 确认目标 id。
|
|
148
|
+
2. `update_product` / `deactivate_product`(`confirm=false`)→ 展示预览。
|
|
149
|
+
3. 确认后同名工具 `confirm=true` 执行。
|
|
150
|
+
|
|
151
|
+
**建仓 / 调整库存**
|
|
152
|
+
1. 直接 `set_product_stock`(`product_id` + `quantity`,`confirm=false` 预览 → 确认后 `confirm=true`)——自动处理库存条目与仓库。
|
|
153
|
+
2. 查现状:`get_inventory_levels`(`location_id` 可选、`q` 按 sku 过滤)。
|
|
154
|
+
3. 已知 `inventory_item_id` 的精细调整:`update_inventory_level`(upsert)。
|
|
155
|
+
|
|
156
|
+
**查询类**:直接调用对应读工具即可,无需 `confirm`。
|
|
157
|
+
|
|
158
|
+
## 常见任务示例(运营怎么说 / 你怎么调)
|
|
159
|
+
|
|
160
|
+
- 「下架 handle 为 `summer-serum` 的商品」→ `get_product`(id_or_handle=`summer-serum`) 取 id → `deactivate_product`(id, confirm=false 预览 → 确认后 confirm=true)。
|
|
161
|
+
- 「给 A 商品补 100 件库存」→ `set_product_stock`(product_id=A 的 id, quantity=100, confirm=false 预览 → 确认后 confirm=true;默认写 Main Warehouse,指定仓传 location_id)。
|
|
162
|
+
- 「查一下最近 10 个待发货订单」→ `list_orders`(status=`pending`, limit=10)。
|
|
163
|
+
- 「后台怎么新建优惠券」→ `ask_usage`(question=`如何新建优惠券`)。
|
|
164
|
+
- 「列出所有退货请求」→ `list_return_requests`()。
|
|
165
|
+
- 「把这份抓取的 Lazada 商品录入:标题 DR.CYJ Hair Filler、价格 420 MYR、4 张本地图、SKU `4820...`、规格存 metadata、补全中/马来/印尼语翻译」→ 先整理数据,`create_product`(`confirm=false` 看预览含 images/sku/metadata/缺语言/缺货告警 → 确认后 `confirm=true`,带 `sales_channels: ["MY Storefront"]`、`initial_stock: {quantity: 100}`、`weight: 150`,必要时 `acknowledge_missing_languages=true`)。工具会自动上传图片、写 SKU/metadata/物理字段、四语言翻译、渠道挂载并建库存。再 `create_product_content`(`locale=en-US/zh-CN/ms-MY/id-ID`, `confirm=true`) 逐语言写富内容块(richtext 描述 + specs 规格 + image_gallery 图集),最后 `get_translations` + `get_product_content` 核对。
|
|
166
|
+
- 「核对某商品多语言是否齐全」→ `get_translations`(reference_id=商品 id 或 handle),看 4 条 locale 是否都有 title/description。
|
|
167
|
+
- 「让这个商品在前端能搜到」→ `get_product` 取 id → `add_product_to_sales_channel`(`product_ids=[id]`, `confirm=true`)。
|
|
168
|
+
- 「补全某商品中文详情页内容」→ `create_product_content`(`product_id`, `locale=zh-CN`, `blocks=[{type:'richtext',data:{html:'...'}},{type:'specs',data:{rows:[...]}}]`, `confirm=true`)。可用 `get_product_content` 先核对现状。
|
|
169
|
+
|
|
170
|
+
## 给运营的一次性安装配置
|
|
171
|
+
|
|
172
|
+
agent 客户端(Codex / Claude Desktop / Cursor / Workbuddy / CodeBuddy 等)的 MCP 配置里加一条即可(机器只需 Node ≥ 18,无需 clone 仓库、无需 build):
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"mcpServers": {
|
|
177
|
+
"derlux-admin": {
|
|
178
|
+
"command": "npx",
|
|
179
|
+
"args": ["-y", "@sea-ecommerce/admin-mcp-server"],
|
|
180
|
+
"env": {
|
|
181
|
+
"MEDUSA_BACKEND_URL": "https://admin.internal.derlux.com",
|
|
182
|
+
"DERLUX_ADMIN_EMAIL": "你的后台账号",
|
|
183
|
+
"DERLUX_ADMIN_PASSWORD": "你的密码"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 环境变量说明
|
|
191
|
+
|
|
192
|
+
| 变量 | 必填 | 默认 | 说明 |
|
|
193
|
+
|---|---|---|---|
|
|
194
|
+
| `MEDUSA_BACKEND_URL` | 否 | `http://localhost:9000` | 后端地址(生产填内部域名)|
|
|
195
|
+
| `DERLUX_ADMIN_EMAIL` | **是** | — | 后台管理员账号(须持 `csr`/`super_admin`)|
|
|
196
|
+
| `DERLUX_ADMIN_PASSWORD` | **是** | — | 该账号密码 |
|
|
197
|
+
| `DERLUX_AUTH_PATH` | 否 | `/auth/user/emailpass` | 登录端点 |
|
|
198
|
+
| `DERLUX_SESSION_FILE` | 否 | `~/.derlux/mcp-session` | 会话持久化文件(权限 0600)|
|
|
199
|
+
| `DERLUX_AUDIT_FILE` | 否 | `~/.derlux/mcp-audit.log` | 审计日志 |
|
|
200
|
+
| `DERLUX_DOCS_DIR` | 否 | `./docs` | 问答知识源目录(npx 形态下通常留默认或指向本地 docs 副本)|
|
|
201
|
+
| `DERLUX_REQUIRED_LOCALES` | 否 | `en,zh,ms,id` | 商品必须覆盖的语言 |
|
|
202
|
+
|
|
203
|
+
## 已知限制 / 注意事项
|
|
204
|
+
|
|
205
|
+
- 本 MCP **不改动任何数据库 schema**,仅复用现有 `/admin/*` 接口;运营动作本身按既有逻辑写库。
|
|
206
|
+
- 登录走**用户账号**而非 API Key(现有 RBAC 依赖 `user.metadata.rbac_roles`)。
|
|
207
|
+
- 金额以**主货币单位**存储(见上「硬规则 2」),不要换算成分。
|
|
208
|
+
- **Medusa v2.21 差异(重要)**:
|
|
209
|
+
- 有变体就必须有 product options,工具已自动补一个 `Default` 选项,你无需关心;**更新**商品时 `options` 字段被移除(2.16+),工具不会误传。
|
|
210
|
+
- 商品创建/更新体**不接受 `translations` 字段**;翻译由工具自动走 `/admin/translations/batch`。`get_product` 默认字段不含 `translations` 关系,**核对翻译请用 `get_translations`**,不要凭 `get_product` 返回为空就误判没写入。
|
|
211
|
+
- 图片走 `/admin/uploads` 上传后再挂到商品(无独立 `/admin/products/{id}/images` 端点)。
|
|
212
|
+
- 库存查询/写入端点已按 2.21 契约校准(`/admin/inventory-items` + `location-levels`,sku 唯一约束);`set_product_stock` 全程幂等可重放。
|
|
213
|
+
- 自定义端点 `warehouse-tasks` / `return-requests` / `product-contents` 为本项目扩展。
|
|
214
|
+
- 文档问答(`search_docs` / `ask_usage`)依赖知识源目录;npx 形态下默认 `./docs`,若问不到相关内容,提示运营换关键词或查后台手册。
|
|
215
|
+
- 用受限的 `csr` 账号即可完成日常运营;敏感动作由后端 RBAC 拦截。
|