@sea-ecommerce/admin-mcp-server 0.1.2 → 0.1.4
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 +1 -1
- package/dist/derlux-admin-mcp.js +91 -23
- package/package.json +1 -1
- package/skill/SKILL.md +5 -3
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| `update_product_prices` | 替换变体价格(多币种) | 写(dry_run 默认) |
|
|
18
18
|
| `upload_product_image` / `add_product_to_sales_channel` | 追加图片 / 挂渠道 | 写(dry_run 默认) |
|
|
19
19
|
| `create_product_content` / `get_product_content` | 详情页富内容块(hero/richtext/specs/图集/faq/video) | 写 / 读 |
|
|
20
|
-
| `set_product_stock` |
|
|
20
|
+
| `set_product_stock` | 给商品建仓/改库存(自动建库存条目 + 关联变体,幂等) | 写(dry_run 默认) |
|
|
21
21
|
| `list_stock_locations` / `create_stock_location` | 列 / 增仓库 | 写(dry_run 默认) |
|
|
22
22
|
| `get_inventory_levels` / `update_inventory_level` | 查 / 改库存水平 | 写(dry_run 默认) |
|
|
23
23
|
| `list_orders` / `get_order` | 查订单 | 读 |
|
package/dist/derlux-admin-mcp.js
CHANGED
|
@@ -21567,6 +21567,23 @@ function loadConfig() {
|
|
|
21567
21567
|
requiredLocales
|
|
21568
21568
|
};
|
|
21569
21569
|
}
|
|
21570
|
+
var LOCALE_CANON = {
|
|
21571
|
+
en: "en-US",
|
|
21572
|
+
"en-us": "en-US",
|
|
21573
|
+
"en-US": "en-US",
|
|
21574
|
+
zh: "zh-CN",
|
|
21575
|
+
"zh-cn": "zh-CN",
|
|
21576
|
+
"zh-CN": "zh-CN",
|
|
21577
|
+
ms: "ms-MY",
|
|
21578
|
+
"ms-my": "ms-MY",
|
|
21579
|
+
"ms-MY": "ms-MY",
|
|
21580
|
+
id: "id-ID",
|
|
21581
|
+
"id-id": "id-ID",
|
|
21582
|
+
"id-ID": "id-ID"
|
|
21583
|
+
};
|
|
21584
|
+
function normalizeLocale(input) {
|
|
21585
|
+
return LOCALE_CANON[input] ?? LOCALE_CANON[input.trim().toLowerCase()] ?? null;
|
|
21586
|
+
}
|
|
21570
21587
|
|
|
21571
21588
|
// src/auth.ts
|
|
21572
21589
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
@@ -21858,6 +21875,19 @@ var MedusaAdminClient = class {
|
|
|
21858
21875
|
body
|
|
21859
21876
|
);
|
|
21860
21877
|
}
|
|
21878
|
+
/**
|
|
21879
|
+
* 把库存条目关联到变体(POST /admin/products/{pid}/variants/{vid}/inventory-items)。
|
|
21880
|
+
* ⚠️ 关联是「变体能查到库存」的关键:只有关联后,变体的 manage_inventory 才指向该条目,
|
|
21881
|
+
* 且设置 manage_inventory=true 时 Medusa 不会因「无关联条目」而另建同 SKU 条目(撞唯一约束)。
|
|
21882
|
+
* 重复关联会报已存在,调用方应 try/catch 吞掉「已关联」类错误。
|
|
21883
|
+
*/
|
|
21884
|
+
linkVariantInventoryItem(productId, variantId, inventoryItemId) {
|
|
21885
|
+
return this.request(
|
|
21886
|
+
"POST",
|
|
21887
|
+
`/admin/products/${encodeURIComponent(productId)}/variants/${encodeURIComponent(variantId)}/inventory-items`,
|
|
21888
|
+
{ inventory_item_id: inventoryItemId, required_quantity: 1 }
|
|
21889
|
+
);
|
|
21890
|
+
}
|
|
21861
21891
|
/**
|
|
21862
21892
|
* 幂等地设置某库存条目在指定仓库的库存数量:
|
|
21863
21893
|
* 已有 (item, location) 水平 → 更新;没有 → 创建。
|
|
@@ -21912,35 +21942,65 @@ async function applyProductStock(ctx2, opts) {
|
|
|
21912
21942
|
location = list.find((l) => /main warehouse/i.test(l.name)) || list[0];
|
|
21913
21943
|
if (!location) throw new Error("\u6CA1\u6709\u4EFB\u4F55\u53EF\u7528\u4ED3\u5E93\uFF0C\u8BF7\u5148 create_stock_location");
|
|
21914
21944
|
}
|
|
21915
|
-
|
|
21916
|
-
|
|
21945
|
+
const findItem = async () => {
|
|
21946
|
+
if (!variant.sku) return null;
|
|
21917
21947
|
const items = await client2.listInventoryItems({ sku: variant.sku, limit: 10 });
|
|
21918
|
-
|
|
21919
|
-
}
|
|
21920
|
-
if (
|
|
21921
|
-
if (!variant.sku)
|
|
21922
|
-
|
|
21948
|
+
return (items?.inventory_items || []).find((i) => i.sku === variant.sku) || null;
|
|
21949
|
+
};
|
|
21950
|
+
if (opts.manageInventory) {
|
|
21951
|
+
if (!variant.sku) {
|
|
21952
|
+
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`);
|
|
21953
|
+
}
|
|
21954
|
+
let iitem2 = await findItem();
|
|
21955
|
+
if (!iitem2) {
|
|
21956
|
+
const created = await client2.createInventoryItem({
|
|
21957
|
+
sku: variant.sku,
|
|
21958
|
+
title: variant.title || void 0
|
|
21959
|
+
});
|
|
21960
|
+
iitem2 = created?.inventory_item ?? created;
|
|
21961
|
+
}
|
|
21962
|
+
try {
|
|
21963
|
+
await client2.linkVariantInventoryItem(product.id, variant.id, iitem2.id);
|
|
21964
|
+
} catch {
|
|
21965
|
+
}
|
|
21966
|
+
if (Boolean(variant.manage_inventory) !== opts.manageInventory) {
|
|
21967
|
+
await client2.updateProduct(product.id, {
|
|
21968
|
+
variants: [{ id: variant.id, manage_inventory: opts.manageInventory }]
|
|
21969
|
+
});
|
|
21970
|
+
}
|
|
21971
|
+
iitem2 = await findItem() || iitem2;
|
|
21972
|
+
const level = await client2.upsertInventoryLevel(iitem2.id, location.id, opts.quantity);
|
|
21973
|
+
return {
|
|
21974
|
+
product_id: product.id,
|
|
21975
|
+
variant_id: variant.id,
|
|
21923
21976
|
sku: variant.sku,
|
|
21924
|
-
|
|
21925
|
-
|
|
21926
|
-
|
|
21977
|
+
location_id: location.id,
|
|
21978
|
+
location_name: location.name,
|
|
21979
|
+
inventory_item_id: iitem2.id,
|
|
21980
|
+
manage_inventory: opts.manageInventory,
|
|
21981
|
+
stocked_quantity: level.stocked_quantity,
|
|
21982
|
+
level_created: level.created
|
|
21983
|
+
};
|
|
21927
21984
|
}
|
|
21928
|
-
const level = await client2.upsertInventoryLevel(iitem.id, location.id, opts.quantity);
|
|
21929
21985
|
if (Boolean(variant.manage_inventory) !== opts.manageInventory) {
|
|
21930
21986
|
await client2.updateProduct(product.id, {
|
|
21931
21987
|
variants: [{ id: variant.id, manage_inventory: opts.manageInventory }]
|
|
21932
21988
|
});
|
|
21933
21989
|
}
|
|
21990
|
+
const iitem = await findItem();
|
|
21991
|
+
if (iitem) {
|
|
21992
|
+
await client2.upsertInventoryLevel(iitem.id, location.id, 0);
|
|
21993
|
+
}
|
|
21934
21994
|
return {
|
|
21935
21995
|
product_id: product.id,
|
|
21936
21996
|
variant_id: variant.id,
|
|
21937
21997
|
sku: variant.sku,
|
|
21938
21998
|
location_id: location.id,
|
|
21939
21999
|
location_name: location.name,
|
|
21940
|
-
inventory_item_id: iitem
|
|
21941
|
-
manage_inventory:
|
|
21942
|
-
stocked_quantity:
|
|
21943
|
-
|
|
22000
|
+
inventory_item_id: iitem?.id ?? null,
|
|
22001
|
+
manage_inventory: false,
|
|
22002
|
+
stocked_quantity: 0,
|
|
22003
|
+
note: "\u5DF2\u5173\u95ED\u5E93\u5B58\u7BA1\u7406\u5E76\u6E05\u96F6\u5E93\u5B58"
|
|
21944
22004
|
};
|
|
21945
22005
|
}
|
|
21946
22006
|
var stockTools = [
|
|
@@ -22530,15 +22590,19 @@ metadata \u4E3A**\u5408\u5E76**\u8BED\u4E49\uFF1A\u65B0\u952E\u8986\u76D6\u540C\
|
|
|
22530
22590
|
{
|
|
22531
22591
|
name: "get_product_content",
|
|
22532
22592
|
description: `\u8BFB\u53D6\u5546\u54C1\u8BE6\u60C5\u9875\u5BCC\u5185\u5BB9\u5757\uFF08GET /admin/product-contents\uFF09\u3002
|
|
22533
|
-
locale \
|
|
22593
|
+
locale \u63A5\u53D7\u533A\u57DF\u7801 en-US / zh-CN / ms-MY / id-ID\uFF08\u63A8\u8350\uFF0C\u4E0E\u524D\u7AEF\u4E00\u81F4\uFF09\u6216\u77ED\u7801 en / zh / ms / id\uFF08\u81EA\u52A8\u5F52\u4E00\u5316\u4E3A\u533A\u57DF\u7801\uFF09\u3002
|
|
22534
22594
|
\u8FD4\u56DE { product_content: { id, product_id, locale, status, blocks(\u5DF2\u89E3\u6790\u6570\u7EC4) } | null }\u3002`,
|
|
22535
22595
|
schema: {
|
|
22536
22596
|
product_id: external_exports.string(),
|
|
22537
|
-
locale: external_exports.
|
|
22597
|
+
locale: external_exports.string().default("en-US").describe("\u533A\u57DF\u7801 en-US/zh-CN/ms-MY/id-ID\uFF0C\u6216\u77ED\u7801 en/zh/ms/id"),
|
|
22538
22598
|
status: external_exports.enum(["draft", "published"]).default("published")
|
|
22539
22599
|
},
|
|
22540
22600
|
async run(input, { client: client2 }) {
|
|
22541
|
-
const
|
|
22601
|
+
const locale = normalizeLocale(input.locale);
|
|
22602
|
+
if (!locale) {
|
|
22603
|
+
return `\u9519\u8BEF\uFF1Alocale "${input.locale}" \u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u7528 en-US / zh-CN / ms-MY / id-ID\uFF08\u6216\u77ED\u7801 en/zh/ms/id\uFF09\u3002`;
|
|
22604
|
+
}
|
|
22605
|
+
const data = await client2.getProductContent(input.product_id, locale, input.status);
|
|
22542
22606
|
const pc = data?.product_content;
|
|
22543
22607
|
if (!pc) return { product_content: null };
|
|
22544
22608
|
let blocks = [];
|
|
@@ -22561,11 +22625,11 @@ storefront \u8BE6\u60C5\u9875\u300C\u5BCC\u5185\u5BB9\u300D\u533A\u7531\u8FD9\u4
|
|
|
22561
22625
|
- video: data.{ url, poster? }\uFF08\u652F\u6301 YouTube\uFF09
|
|
22562
22626
|
- faq: data.{ items: {question,answer}[] }
|
|
22563
22627
|
- specs: data.{ rows: {label,value}[] }
|
|
22564
|
-
\u26A0\uFE0F locale \
|
|
22628
|
+
\u26A0\uFE0F locale \u7528\u533A\u57DF\u7801 en-US / zh-CN / ms-MY / id-ID\uFF08\u4E0E\u524D\u7AEF\u4E00\u81F4\uFF0C\u63A8\u8350\uFF09\uFF1B\u4E5F\u63A5\u53D7\u77ED\u7801 en/zh/ms/id\uFF08\u81EA\u52A8\u5F52\u4E00\u5316\u4E3A\u533A\u57DF\u7801\uFF09\u3002\u5546\u54C1\u6807\u9898/\u63CF\u8FF0\u7684\u7FFB\u8BD1\u7528\u77ED\u7801\uFF0C\u4E8C\u8005\u4E0D\u540C\u3002
|
|
22565
22629
|
\u9ED8\u8BA4 dry_run\uFF08confirm=false\uFF09\u4EC5\u9884\u89C8\u3002`,
|
|
22566
22630
|
schema: {
|
|
22567
22631
|
product_id: external_exports.string(),
|
|
22568
|
-
locale: external_exports.
|
|
22632
|
+
locale: external_exports.string().default("en-US").describe("\u533A\u57DF\u7801 en-US/zh-CN/ms-MY/id-ID\uFF0C\u6216\u77ED\u7801 en/zh/ms/id"),
|
|
22569
22633
|
status: external_exports.enum(["draft", "published"]).default("published"),
|
|
22570
22634
|
blocks: external_exports.array(
|
|
22571
22635
|
external_exports.object({
|
|
@@ -22577,12 +22641,16 @@ storefront \u8BE6\u60C5\u9875\u300C\u5BCC\u5185\u5BB9\u300D\u533A\u7531\u8FD9\u4
|
|
|
22577
22641
|
confirm: external_exports.boolean().default(false)
|
|
22578
22642
|
},
|
|
22579
22643
|
async run(input, { client: client2 }) {
|
|
22644
|
+
const locale = normalizeLocale(input.locale);
|
|
22645
|
+
if (!locale) {
|
|
22646
|
+
return `\u9519\u8BEF\uFF1Alocale "${input.locale}" \u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u7528 en-US / zh-CN / ms-MY / id-ID\uFF08\u6216\u77ED\u7801 en/zh/ms/id\uFF09\u3002`;
|
|
22647
|
+
}
|
|
22580
22648
|
if (!input.confirm) {
|
|
22581
22649
|
return {
|
|
22582
22650
|
dry_run: true,
|
|
22583
22651
|
will_create: {
|
|
22584
22652
|
product_id: input.product_id,
|
|
22585
|
-
locale
|
|
22653
|
+
locale,
|
|
22586
22654
|
status: input.status,
|
|
22587
22655
|
block_count: input.blocks.length,
|
|
22588
22656
|
types: input.blocks.map((b) => b.type)
|
|
@@ -22591,7 +22659,7 @@ storefront \u8BE6\u60C5\u9875\u300C\u5BCC\u5185\u5BB9\u300D\u533A\u7531\u8FD9\u4
|
|
|
22591
22659
|
}
|
|
22592
22660
|
const data = await client2.createProductContent({
|
|
22593
22661
|
product_id: input.product_id,
|
|
22594
|
-
locale
|
|
22662
|
+
locale,
|
|
22595
22663
|
status: input.status,
|
|
22596
22664
|
blocks: input.blocks
|
|
22597
22665
|
});
|
|
@@ -22779,7 +22847,7 @@ var config2 = loadConfig();
|
|
|
22779
22847
|
var auth = new AuthManager(config2);
|
|
22780
22848
|
var client = new MedusaAdminClient(config2, auth);
|
|
22781
22849
|
var ctx = { client, config: config2 };
|
|
22782
|
-
var server = new McpServer({ name: "derlux-admin", version: "0.1.
|
|
22850
|
+
var server = new McpServer({ name: "derlux-admin", version: "0.1.4" });
|
|
22783
22851
|
for (const def of toolDefs) {
|
|
22784
22852
|
server.tool(def.name, def.description, def.schema, async (args) => {
|
|
22785
22853
|
try {
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -29,7 +29,7 @@ description: 运营使用本地 AI agent(derlux-admin MCP server)操作 DERL
|
|
|
29
29
|
6. **商品必须挂销售渠道才在前端可见**:新建商品默认**不在任何销售渠道**,storefront(按 publishable key → 渠道过滤)拉不到它,详情页表现成「空/404」。录入后用 `add_product_to_sales_channel`(`confirm=true`)把它加入渠道(默认第一个启用渠道;马来西亚站用 `sc_01M4K4F0D6HBHZ0NH6WB7H5KPT`「MY Storefront」)。
|
|
30
30
|
7. **两套 locale 码,别混**:
|
|
31
31
|
- 商品标题/描述的**翻译**(`create_product` 的 `translations`、后端 `/admin/translations/batch`):用**简码** `en` / `zh` / `ms` / `id`。store `/store/products?locale=zh` 即回退到 zh 翻译。
|
|
32
|
-
- 商品详情页**富内容块**(`create_product_content` 的 `locale
|
|
32
|
+
- 商品详情页**富内容块**(`create_product_content` / `get_product_content` 的 `locale`):推荐用**区域码** `en-US` / `zh-CN` / `ms-MY` / `id-ID`(与前端 `useProductContent` 的 localeMap 一致);**简码 `en`/`zh`/`ms`/`id` 也会被工具自动归一化为区域码**,两种写法都能命中,但请统一用区域码避免歧义。
|
|
33
33
|
8. **详情页「富内容」区靠 `create_product_content`,不是 `description`**:storefront 详情页下方大块内容由 `product_content` 模块渲染(`richtext`/`specs`/`image_gallery` 等)。只填 `product.description` 不会显示在那里——录入完整商品时必须再建一份 `product_content`(建议每语言一份)。`get_product` 默认不含该字段,核对请用 `get_product_content`。
|
|
34
34
|
9. **不建库存 = 前端缺货**:变体 `manage_inventory` 默认 `true`,但新商品默认 0 库存——`create_product` 时必须给 `initial_stock`(如 `{quantity:100}`,默认写 Main Warehouse),或对已有商品调 `set_product_stock`,否则前台显示缺货、无法下单。
|
|
35
35
|
10. **多语言核对用 `get_translations`**:`get_product` 默认字段**不含** translations,不要凭它为空就判断没翻译。用 `get_translations`(reference_id=商品id或handle) 读取实际写入的各语言 title/description。
|
|
@@ -130,7 +130,9 @@ description: 运营使用本地 AI agent(derlux-admin MCP server)操作 DERL
|
|
|
130
130
|
- `name`(string,**必填**);`address`(可选:`{address_1?,address_2?,city?,country_code?,province?,postal_code?,phone?}`);`metadata`(`record<string,string>`,可选);`confirm`(默认 false)。
|
|
131
131
|
- **`set_product_stock`**:给商品建仓/改库存(**运营最常用的库存工具**,幂等)。
|
|
132
132
|
- `product_id`(string,**必填**,商品 id 或 handle);`sku`(string,可选,目标变体;省略取第一个变体);`location_id`(string,可选,省略=Main Warehouse/第一个仓库);`quantity`(int ≥0,**必填**,绝对值非增量);`manage_inventory`(bool,默认 true);`confirm`(默认 false)。
|
|
133
|
-
-
|
|
133
|
+
- 行为(`manage_inventory=true`):按变体 SKU 查库存条目(缺失则自动创建)→ **关联条目到变体**(`POST .../variants/{id}/inventory-items`,已关联则忽略)→ 置变体 `manage_inventory=true` → 幂等写 (条目, 仓库) 库存水平。重复调用安全。
|
|
134
|
+
- ⚠️ 「关联」是变体能查到库存的关键:只建条目不关联,前台仍显示缺货。本工具已自动处理关联,运营无需关心。
|
|
135
|
+
- (`manage_inventory=false`)关闭库存管理并把库存清零。
|
|
134
136
|
- **`update_inventory_level`**:直接改某**库存条目**(`inventory_item_id`)在某仓库的数量(upsert,幂等)。
|
|
135
137
|
- `inventory_item_id`(string,**必填**);`location_id`(string,**必填**);`stocked_quantity`(int ≥0,**必填**);`confirm`(默认 false)。已知 `inventory_item_id` 时用;否则优先 `set_product_stock`。
|
|
136
138
|
|
|
@@ -209,7 +211,7 @@ agent 客户端(Codex / Claude Desktop / Cursor / Workbuddy / CodeBuddy 等)
|
|
|
209
211
|
- 有变体就必须有 product options,工具已自动补一个 `Default` 选项,你无需关心;**更新**商品时 `options` 字段被移除(2.16+),工具不会误传。
|
|
210
212
|
- 商品创建/更新体**不接受 `translations` 字段**;翻译由工具自动走 `/admin/translations/batch`。`get_product` 默认字段不含 `translations` 关系,**核对翻译请用 `get_translations`**,不要凭 `get_product` 返回为空就误判没写入。
|
|
211
213
|
- 图片走 `/admin/uploads` 上传后再挂到商品(无独立 `/admin/products/{id}/images` 端点)。
|
|
212
|
-
- 库存查询/写入端点已按 2.21 契约校准(`/admin/inventory-items` + `location-levels`,sku 唯一约束);`set_product_stock`
|
|
214
|
+
- 库存查询/写入端点已按 2.21 契约校准(`/admin/inventory-items` + `location-levels`,sku 唯一约束);`set_product_stock` 会**关联条目↔变体**并幂等可重放(变体只有关联到库存条目,前台才显示有货;只建条目不关联会撞唯一约束或前台缺货,工具已自动处理)。
|
|
213
215
|
- 自定义端点 `warehouse-tasks` / `return-requests` / `product-contents` 为本项目扩展。
|
|
214
216
|
- 文档问答(`search_docs` / `ask_usage`)依赖知识源目录;npx 形态下默认 `./docs`,若问不到相关内容,提示运营换关键词或查后台手册。
|
|
215
217
|
- 用受限的 `csr` 账号即可完成日常运营;敏感动作由后端 RBAC 拦截。
|