@shoppexio/mcp-commerce-server 0.7.0 → 0.8.0

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.
Files changed (3) hide show
  1. package/README.md +109 -39
  2. package/package.json +2 -2
  3. package/src/server.mjs +122 -66
package/README.md CHANGED
@@ -2,18 +2,20 @@
2
2
 
3
3
  Model Context Protocol server for Shoppex commerce operations.
4
4
 
5
- Connect Claude Desktop, Claude Code, Cursor, Windsurf, or Codex to your Shoppex store. Read products, orders, customers, and analytics, create payment links, manage coupons, and create or update products directly from chat.
5
+ Connect Claude Desktop, Claude Code, Cursor, Windsurf, or Codex to your Shoppex store manage products, orders, customers, coupons, payment links, subscriptions, tickets, licenses, blacklist, analytics, and more. 51 tools, 5 resources, 6 workflow prompts.
6
6
 
7
7
  ## Install
8
8
 
9
+ The fastest way is the one-shot installer:
10
+
9
11
  ```bash
10
- npm install -g @shoppexio/mcp-commerce-server
12
+ npx @shoppexio/mcp-shoppex install --api-key shx_your_dev_api_key
11
13
  ```
12
14
 
13
- Or run without installing:
15
+ That installs both commerce and theme MCPs. Or install commerce standalone:
14
16
 
15
17
  ```bash
16
- npx -y @shoppexio/mcp-commerce-server
18
+ npm install -g @shoppexio/mcp-commerce-server
17
19
  ```
18
20
 
19
21
  ## Claude Desktop
@@ -34,63 +36,131 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
34
36
  }
35
37
  ```
36
38
 
37
- Restart Claude Desktop. Tools accept a `shop_api_key` argument on each call; the env var is available for clients that forward env to tool arguments.
39
+ Restart Claude Desktop.
38
40
 
39
- ## Cursor / Windsurf / Other
41
+ ## Cursor / Windsurf / Claude Code
40
42
 
41
- Any MCP client with stdio transport works. Set the same env var and point to `shoppex-mcp-commerce-server` as the command.
43
+ Any MCP client with stdio transport works. Set the same env var and point to `shoppex-mcp-commerce-server` as the command — or use the `@shoppexio/cli` helper: `shoppex mcp install --client cursor|windsurf|claude-code`.
42
44
 
43
- ## Tools (12)
45
+ ## Tools (51)
44
46
 
45
- ### Read
47
+ ### Products
48
+ - `products_list` — search or list products
49
+ - `products_get` — fetch one by id/uniqid
50
+ - `products_create` — create (service, serials, dynamic, file)
51
+ - `products_update` — price, title, gateways, stock, visibility
52
+ - `products_delete` — soft-delete
53
+ - `products_duplicate` — clone
54
+ - `products_upload_serials` — bulk-add license keys
46
55
 
47
- | Tool | Purpose |
48
- |------|---------|
49
- | `products.list` | Search or list products |
50
- | `products.get` | Fetch one product by id or uniqid |
51
- | `orders.list` | List recent orders, optionally filtered |
52
- | `orders.get` | Fetch one order with line items |
53
- | `customers.get` | Fetch a customer by id or email |
54
- | `coupons.list` | List active coupons |
55
- | `analytics.revenue` | Revenue totals for a date range |
56
+ ### Orders
57
+ - `orders_list` — list, filter by status/email
58
+ - `orders_get` fetch one with line items
59
+ - `orders_update` notes, internal notes
60
+ - `orders_fulfill` mark delivered
56
61
 
57
- ### Write
62
+ ### Customers
63
+ - `customers_list` — list with filter expressions
64
+ - `customers_get` — fetch by id or email
65
+ - `customers_wallet_credit` — credit wallet balance
58
66
 
59
- | Tool | Purpose |
60
- |------|---------|
61
- | `products.create` | Create a product (service, serials, dynamic, file) |
62
- | `products.update` | Update a product's price, title, gateways, stock, visibility |
63
- | `coupons.create` | Create a coupon (percentage or fixed discount) |
64
- | `coupons.update` | Update an existing coupon |
65
- | `payment_links.create` | Create a payment link |
67
+ ### Invoices
68
+ - `invoices_list` — filter by status, customer
69
+ - `invoices_get` fetch one
66
70
 
67
- ## Required Scopes
71
+ ### Coupons
72
+ - `coupons_list` — list active
73
+ - `coupons_get` — fetch one
74
+ - `coupons_create` — percentage or fixed
75
+ - `coupons_update` — update any field
76
+ - `coupons_delete` — remove
68
77
 
69
- Your Shoppex Dev API key needs the scopes matching the tools you use (e.g. `products.read`, `products.write`, `orders.read`, `coupons.write`, `analytics.read`, `payment_links.write`).
78
+ ### Payment Links
79
+ - `payment_links_list` / `_get` / `_update` / `_delete` / `_toggle`
80
+ - `payment_links_create` — one-click checkout URLs
70
81
 
71
- Create one at [dashboard.shoppex.io/developer/api](https://dashboard.shoppex.io/developer/api).
82
+ ### Categories
83
+ - `categories_list` / `_create` / `_update`
72
84
 
73
- ## Environment Variables
85
+ ### Webhooks
86
+ - `webhooks_list` / `_create`
74
87
 
75
- | Variable | Required | Default |
76
- |----------|----------|---------|
77
- | `SHOPPEX_SHOP_API_KEY` | passed per call, or set here | — |
78
- | `SHOPPEX_API_BASE_URL` | no | `https://api.shoppex.io` |
79
- | `SHOPPEX_DEV_API_TIMEOUT_MS` | no | `10000` |
88
+ ### Tickets (support inbox)
89
+ - `tickets_list` / `_get` / `_create` / `_reply` / `_close`
90
+
91
+ ### Disputes
92
+ - `disputes_list` / `_get`
93
+
94
+ ### Licenses
95
+ - `licenses_list` / `_get` / `_update` (revoke/extend/reset HWID locks)
96
+
97
+ ### Subscriptions
98
+ - `subscriptions_list` / `_cancel`
99
+
100
+ ### Affiliates
101
+ - `affiliates_list`
102
+
103
+ ### Blacklist
104
+ - `blacklist_list` / `_add` / `_remove`
105
+
106
+ ### Analytics
107
+ - `analytics_revenue` — revenue totals
108
+ - `analytics_reports_list` — custom reports
109
+ - `analytics_report_generate` — re-generate a report
110
+
111
+ ## Resources (5)
80
112
 
81
- ## Example Prompts
113
+ Claude Desktop surfaces these as attachable context documents. Drag them into any chat to ground the conversation in live shop data:
82
114
 
83
- Once connected in Claude Desktop:
115
+ | URI | What |
116
+ |------|------|
117
+ | `shoppex://products` | Full product catalog with variants |
118
+ | `shoppex://orders/recent` | Last 50 orders |
119
+ | `shoppex://analytics/summary` | MTD revenue snapshot |
120
+ | `shoppex://categories` | Category tree |
121
+ | `shoppex://tickets/open` | Open support tickets |
122
+
123
+ ## Prompts (6)
124
+
125
+ Pre-built workflows available as slash commands:
126
+
127
+ | Prompt | What it does |
128
+ |--------|--------------|
129
+ | `/shoppex_daily_brief` | Morning summary: revenue, orders, attention items |
130
+ | `/shoppex_launch_product` | End-to-end product launch (args: product_name, price) |
131
+ | `/shoppex_fraud_check` | Suspicious-orders scan + blacklist suggestions |
132
+ | `/shoppex_weekly_recap` | 7-day performance recap |
133
+ | `/shoppex_customer_care` | Full customer timeline + draft reply (args: email) |
134
+ | `/shoppex_theme_refresh` | Active-theme structural review |
135
+
136
+ ## Example prompts
84
137
 
85
138
  - "Create a Discord Nitro 3-month product for $14.99 paid in crypto."
86
139
  - "Show me this month's top 10 customers by revenue."
87
140
  - "Generate a 20% off coupon for my Discord members, valid until end of month."
88
141
  - "Update the price of product `prd_abc123` to $12.99."
89
- - "Make a payment link for a $50 USDT one-time purchase."
142
+ - "Refund customer alice@example.com for invoice `inv_xyz`."
143
+ - "Are there any fraud patterns in the last 48h?"
144
+ - `/shoppex_daily_brief`
145
+
146
+ ## Environment Variables
147
+
148
+ | Variable | Required | Default |
149
+ |----------|----------|---------|
150
+ | `SHOPPEX_SHOP_API_KEY` | yes (or passed per call) | — |
151
+ | `SHOPPEX_API_BASE_URL` | no | `https://api.shoppex.io` |
152
+ | `SHOPPEX_DEV_API_TIMEOUT_MS` | no | `10000` |
153
+
154
+ Create an API key at [dashboard.shoppex.io/developer/api](https://dashboard.shoppex.io/developer/api).
90
155
 
91
156
  ## Companion
92
157
 
93
- Pair with [`@shoppexio/mcp-theme-server`](https://www.npmjs.com/package/@shoppexio/mcp-theme-server) for storefront theme operations.
158
+ Pair with [`@shoppexio/mcp-theme-server`](https://www.npmjs.com/package/@shoppexio/mcp-theme-server) for storefront theme operations including `theme_ai_apply` (natural-language theme editing).
159
+
160
+ ## Agent Templates
161
+
162
+ Paste-ready agent personalities (customer care, product launch, fraud investigator, analyst, theme editor):
163
+ [`docs/agents/`](https://github.com/ShoppexIO/shoppex/tree/main/docs/agents)
94
164
 
95
165
  ## Docs
96
166
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shoppexio/mcp-commerce-server",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Shoppex MCP server for commerce operations over the Dev API",
5
5
  "type": "module",
6
6
  "repository": {
@@ -47,6 +47,6 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@modelcontextprotocol/sdk": "^1.28.0",
50
- "zod": "^4.1.12"
50
+ "zod": "^4.3.6"
51
51
  }
52
52
  }
package/src/server.mjs CHANGED
@@ -435,9 +435,15 @@ export class ShoppexCommerceDevApiClient {
435
435
  }
436
436
  }
437
437
 
438
+ // SECURITY: `api_base_url` is intentionally NOT a tool argument.
439
+ // The Shoppex shop API bearer token is attached to every request, so the
440
+ // destination origin must never be chosen by an (untrusted) tool caller —
441
+ // that would allow exfiltrating the credential to an attacker-controlled host
442
+ // (SSRF / credential disclosure, CWE-918/CWE-200). The API base URL is resolved
443
+ // solely from server-side env (`SHOPPEX_API_BASE_URL`/`API_URL`) via
444
+ // `resolveBaseUrl`, which the operator controls, not the caller.
438
445
  const BaseToolSchema = {
439
446
  shop_api_key: z.string().trim().min(1).optional(),
440
- api_base_url: z.string().trim().url().optional(),
441
447
  };
442
448
 
443
449
  function resolveShopApiKey(explicitValue) {
@@ -457,9 +463,10 @@ function resolveShopApiKey(explicitValue) {
457
463
  }
458
464
 
459
465
  function createClient(args) {
466
+ // Do not pass any caller-supplied base URL here. The destination origin is
467
+ // resolved from server env only (see BaseToolSchema security note).
460
468
  return new ShoppexCommerceDevApiClient({
461
469
  shopApiKey: resolveShopApiKey(args.shop_api_key),
462
- apiBaseUrl: args.api_base_url,
463
470
  });
464
471
  }
465
472
 
@@ -761,39 +768,43 @@ export function createCommerceToolCatalog() {
761
768
  },
762
769
  {
763
770
  name: 'categories_create',
764
- description: 'Create a product category.',
771
+ description: 'Create a product category. Fields match POST /dev/v1/categories.',
765
772
  inputSchema: {
766
773
  ...BaseToolSchema,
767
- name: z.string().trim().min(1).max(128),
768
- description: z.string().optional(),
769
- parent_id: z.string().trim().min(1).optional().nullable(),
770
- image_id: z.string().trim().min(1).optional().nullable(),
774
+ title: z.string().trim().min(1).max(64),
775
+ unlisted: z.boolean().optional(),
776
+ products_bound: z.array(z.string().trim().min(1)).optional(),
777
+ groups_bound: z.array(z.string().trim().min(1)).optional(),
778
+ sort_priority: z.number().int().optional(),
771
779
  },
772
780
  execute: async (args) => {
773
- const body = { name: args.name };
774
- if (args.description !== undefined) body.description = args.description;
775
- if (args.parent_id !== undefined) body.parent_id = args.parent_id;
776
- if (args.image_id !== undefined) body.image_id = args.image_id;
781
+ const body = { title: args.title };
782
+ if (args.unlisted !== undefined) body.unlisted = args.unlisted;
783
+ if (args.products_bound !== undefined) body.products_bound = args.products_bound;
784
+ if (args.groups_bound !== undefined) body.groups_bound = args.groups_bound;
785
+ if (args.sort_priority !== undefined) body.sort_priority = args.sort_priority;
777
786
  return createClient(args).categoriesCreate(body);
778
787
  },
779
788
  },
780
789
  {
781
790
  name: 'categories_update',
782
- description: 'Update a category by id. Only provided fields change.',
791
+ description: 'Update a category by uniqid. Only provided fields change. Uses the same field shape as categories_create.',
783
792
  inputSchema: {
784
793
  ...BaseToolSchema,
785
794
  category_id: z.string().trim().min(1),
786
- name: z.string().trim().min(1).max(128).optional(),
787
- description: z.string().optional(),
788
- parent_id: z.string().trim().min(1).optional().nullable(),
789
- image_id: z.string().trim().min(1).optional().nullable(),
795
+ title: z.string().trim().min(1).max(64).optional(),
796
+ unlisted: z.boolean().optional(),
797
+ products_bound: z.array(z.string().trim().min(1)).optional(),
798
+ groups_bound: z.array(z.string().trim().min(1)).optional(),
799
+ sort_priority: z.number().int().optional(),
790
800
  },
791
801
  execute: async (args) => {
792
802
  const body = {};
793
- if (args.name !== undefined) body.name = args.name;
794
- if (args.description !== undefined) body.description = args.description;
795
- if (args.parent_id !== undefined) body.parent_id = args.parent_id;
796
- if (args.image_id !== undefined) body.image_id = args.image_id;
803
+ if (args.title !== undefined) body.title = args.title;
804
+ if (args.unlisted !== undefined) body.unlisted = args.unlisted;
805
+ if (args.products_bound !== undefined) body.products_bound = args.products_bound;
806
+ if (args.groups_bound !== undefined) body.groups_bound = args.groups_bound;
807
+ if (args.sort_priority !== undefined) body.sort_priority = args.sort_priority;
797
808
  return createClient(args).categoriesUpdate(args.category_id, body);
798
809
  },
799
810
  },
@@ -899,18 +910,20 @@ export function createCommerceToolCatalog() {
899
910
  },
900
911
  },
901
912
  {
902
- name: 'orders_update',
903
- description: 'Update an order (notes, internal state). Only provided fields change.',
913
+ name: 'orders_set_affiliate',
914
+ description: 'Attribute an order to an affiliate after the fact. Backed by PATCH /dev/v1/orders/:id which is the affiliate-attribution endpoint. Provide one of affiliate_code, referral_code, or affiliate_id (pass null to clear an existing attribution).',
904
915
  inputSchema: {
905
916
  ...BaseToolSchema,
906
917
  order_id: z.string().trim().min(1),
907
- notes: z.string().max(2000).optional().nullable(),
908
- internal_note: z.string().max(2000).optional().nullable(),
918
+ affiliate_code: z.string().trim().optional().nullable(),
919
+ referral_code: z.string().trim().optional().nullable(),
920
+ affiliate_id: z.string().trim().optional().nullable(),
909
921
  },
910
922
  execute: async (args) => {
911
923
  const body = {};
912
- if (args.notes !== undefined) body.notes = args.notes;
913
- if (args.internal_note !== undefined) body.internal_note = args.internal_note;
924
+ if (args.affiliate_code !== undefined) body.affiliate_code = args.affiliate_code;
925
+ if (args.referral_code !== undefined) body.referral_code = args.referral_code;
926
+ if (args.affiliate_id !== undefined) body.affiliate_id = args.affiliate_id;
914
927
  return createClient(args).ordersUpdate(args.order_id, body);
915
928
  },
916
929
  },
@@ -930,19 +943,22 @@ export function createCommerceToolCatalog() {
930
943
  },
931
944
  {
932
945
  name: 'customers_wallet_credit',
933
- description: 'Credit a customer wallet balance. Useful for refunds, goodwill credits, or promo grants.',
946
+ description: 'Credit a customer wallet balance. Useful for refunds, goodwill credits, or promo grants. Amount is a decimal string with up to 2 decimal places (e.g. "5.00"). Wallet currency is fixed to the shop default.',
934
947
  inputSchema: {
935
948
  ...BaseToolSchema,
936
949
  customer_id: z.string().trim().min(1),
937
- amount: z.number().positive(),
938
- currency: z.string().trim().min(3).max(8).default('USD'),
939
- reason: z.string().max(255).optional(),
950
+ amount: z.string().regex(/^\d+(\.\d{1,2})?$/, 'Amount must be a positive decimal string (e.g. "5.00")'),
951
+ type: z.enum(['CREDIT', 'PROMO', 'ADJUSTMENT']).optional(),
952
+ description: z.string().max(500).optional(),
953
+ expires_at: z.string().datetime().optional(),
954
+ },
955
+ execute: async (args) => {
956
+ const body = { amount: args.amount };
957
+ if (args.type !== undefined) body.type = args.type;
958
+ if (args.description !== undefined) body.description = args.description;
959
+ if (args.expires_at !== undefined) body.expires_at = args.expires_at;
960
+ return createClient(args).customersWalletCredit(args.customer_id, body);
940
961
  },
941
- execute: async (args) => createClient(args).customersWalletCredit(args.customer_id, {
942
- amount: args.amount,
943
- currency: args.currency,
944
- reason: args.reason,
945
- }),
946
962
  },
947
963
  {
948
964
  name: 'coupons_get',
@@ -970,26 +986,65 @@ export function createCommerceToolCatalog() {
970
986
  },
971
987
  {
972
988
  name: 'payment_links_update',
973
- description: 'Update a payment link by uniqid. Only provided fields change.',
989
+ description: 'Update a payment link by uniqid. The backend expects a full create-style payload, so this tool reads the existing link, merges your overrides, and sends the merged result. Pass only the fields you want to change — the rest are preserved from the current link.',
974
990
  inputSchema: {
975
991
  ...BaseToolSchema,
976
992
  link_id: z.string().trim().min(1),
977
993
  name: z.string().trim().min(1).max(255).optional(),
994
+ type: z.enum(['PRODUCT', 'SUBSCRIPTION', 'SUBSCRIPTION_V2', 'LICENSE', 'PAY_WHAT_YOU_WANT', 'FIXED_PRICE']).optional(),
978
995
  description: z.string().max(255).optional().nullable(),
996
+ active: z.boolean().optional(),
979
997
  price: z.number().positive().optional(),
980
998
  currency: z.string().trim().min(3).max(8).optional(),
981
999
  gateways: z.array(z.string().trim().min(1)).optional(),
982
- active: z.boolean().optional(),
1000
+ product_ids: z.array(z.string().trim().min(1)).optional(),
1001
+ cta: z.enum(['PAY', 'BOOK', 'DONATE', 'SUBSCRIBE']).optional(),
1002
+ allow_discount_code: z.boolean().optional(),
1003
+ require_customer_address: z.boolean().optional(),
1004
+ require_customer_phone: z.boolean().optional(),
1005
+ show_confirmation_page: z.boolean().optional(),
1006
+ return_url: z.string().trim().url().optional().nullable(),
983
1007
  },
984
1008
  execute: async (args) => {
985
- const body = {};
986
- if (args.name !== undefined) body.name = args.name;
987
- if (args.description !== undefined) body.description = args.description;
988
- if (args.price !== undefined) body.price = args.price;
989
- if (args.currency !== undefined) body.currency = args.currency;
990
- if (args.gateways !== undefined) body.gateways = args.gateways.join(',');
991
- if (args.active !== undefined) body.status = args.active;
992
- return createClient(args).paymentLinksUpdate(args.link_id, body);
1009
+ const client = createClient(args);
1010
+ const existing = await client.paymentLinksGet(args.link_id);
1011
+ if (!existing || typeof existing !== 'object') {
1012
+ throw new ShoppexCommerceDevApiError(
1013
+ `payment_links_update: payment link ${args.link_id} not found.`,
1014
+ { code: 'not_found', retryable: false },
1015
+ );
1016
+ }
1017
+ const cur = existing;
1018
+ const body = {
1019
+ uniqid: cur.uniqid ?? args.link_id,
1020
+ name: args.name ?? cur.name,
1021
+ type: args.type ?? cur.type,
1022
+ description: args.description !== undefined ? args.description : cur.description ?? null,
1023
+ status: args.active !== undefined ? args.active : cur.status,
1024
+ price: args.price !== undefined ? args.price : cur.price,
1025
+ currency: args.currency ?? cur.currency ?? 'USD',
1026
+ gateways: args.gateways !== undefined
1027
+ ? args.gateways.join(',')
1028
+ : Array.isArray(cur.gateways) ? cur.gateways.join(',') : (cur.gateways ?? ''),
1029
+ products_ids: args.product_ids !== undefined
1030
+ ? args.product_ids.join(',')
1031
+ : Array.isArray(cur.products_ids) ? cur.products_ids.join(',') : (cur.products_ids ?? ''),
1032
+ cta: args.cta ?? cur.cta ?? 'PAY',
1033
+ discount_code_allowed: args.allow_discount_code !== undefined
1034
+ ? args.allow_discount_code
1035
+ : (cur.discount_code_allowed ?? false),
1036
+ customer_address_required: args.require_customer_address !== undefined
1037
+ ? args.require_customer_address
1038
+ : (cur.customer_address_required ?? false),
1039
+ customer_phone_required: args.require_customer_phone !== undefined
1040
+ ? args.require_customer_phone
1041
+ : (cur.customer_phone_required ?? false),
1042
+ show_confirmation_page: args.show_confirmation_page !== undefined
1043
+ ? args.show_confirmation_page
1044
+ : (cur.show_confirmation_page ?? true),
1045
+ return_url: args.return_url !== undefined ? args.return_url : cur.return_url ?? null,
1046
+ };
1047
+ return client.paymentLinksUpdate(args.link_id, body);
993
1048
  },
994
1049
  },
995
1050
  {
@@ -1029,22 +1084,21 @@ export function createCommerceToolCatalog() {
1029
1084
  },
1030
1085
  {
1031
1086
  name: 'tickets_create',
1032
- description: 'Create a new support ticket. Used when the merchant wants to proactively open a support conversation with a customer.',
1087
+ description: 'Create a new support ticket. Email is the customer email; title is short (max 30 chars); message is the opening body. Optionally link an invoice.',
1033
1088
  inputSchema: {
1034
1089
  ...BaseToolSchema,
1035
- subject: z.string().trim().min(1).max(255),
1036
- message: z.string().trim().min(1),
1037
- customer_id: z.string().trim().min(1).optional(),
1038
- customer_email: z.string().trim().email().optional(),
1090
+ email: z.string().trim().min(1),
1091
+ title: z.string().trim().min(2).max(30),
1092
+ message: z.string().trim().min(2).max(2000),
1039
1093
  invoice_id: z.string().trim().min(1).optional(),
1040
- order_id: z.string().trim().min(1).optional(),
1041
1094
  },
1042
1095
  execute: async (args) => {
1043
- const body = { subject: args.subject, message: args.message };
1044
- if (args.customer_id !== undefined) body.customer_id = args.customer_id;
1045
- if (args.customer_email !== undefined) body.customer_email = args.customer_email;
1096
+ const body = {
1097
+ email: args.email,
1098
+ title: args.title,
1099
+ message: args.message,
1100
+ };
1046
1101
  if (args.invoice_id !== undefined) body.invoice_id = args.invoice_id;
1047
- if (args.order_id !== undefined) body.order_id = args.order_id;
1048
1102
  return createClient(args).ticketsCreate(body);
1049
1103
  },
1050
1104
  },
@@ -1114,21 +1168,23 @@ export function createCommerceToolCatalog() {
1114
1168
  },
1115
1169
  {
1116
1170
  name: 'licenses_update',
1117
- description: 'Update a license (revoke, extend validity, reset HWID/IP locks). Set active=false to revoke access.',
1171
+ description: 'Update a license: set status (ACTIVE/SUSPENDED/REVOKED/EXPIRED), change the hardware-id binding, allowlist IPs, change activation limit, or update the expiry. Set status="REVOKED" to revoke access.',
1118
1172
  inputSchema: {
1119
1173
  ...BaseToolSchema,
1120
1174
  license_id: z.string().trim().min(1),
1121
- active: z.boolean().optional(),
1122
- valid_until: z.string().trim().optional().nullable(),
1123
- hwid_lock: z.string().trim().optional().nullable(),
1124
- ip_lock: z.string().trim().optional().nullable(),
1175
+ status: z.enum(['ACTIVE', 'SUSPENDED', 'REVOKED', 'EXPIRED']).optional(),
1176
+ hardware_id: z.string().trim().optional().nullable(),
1177
+ allowed_ips: z.array(z.string().trim().min(1)).optional().nullable(),
1178
+ max_uses: z.number().int().min(1).optional().nullable(),
1179
+ expires_at: z.string().trim().optional().nullable(),
1125
1180
  },
1126
1181
  execute: async (args) => {
1127
- const body = {};
1128
- if (args.active !== undefined) body.active = args.active;
1129
- if (args.valid_until !== undefined) body.valid_until = args.valid_until;
1130
- if (args.hwid_lock !== undefined) body.hwid_lock = args.hwid_lock;
1131
- if (args.ip_lock !== undefined) body.ip_lock = args.ip_lock;
1182
+ const body = { id: args.license_id };
1183
+ if (args.status !== undefined) body.status = args.status;
1184
+ if (args.hardware_id !== undefined) body.hardware_id = args.hardware_id;
1185
+ if (args.allowed_ips !== undefined) body.allowed_ips = args.allowed_ips;
1186
+ if (args.max_uses !== undefined) body.max_uses = args.max_uses;
1187
+ if (args.expires_at !== undefined) body.expires_at = args.expires_at;
1132
1188
  return createClient(args).licensesUpdate(args.license_id, body);
1133
1189
  },
1134
1190
  },
@@ -1164,7 +1220,7 @@ export async function executeCommerceTool(toolName, args) {
1164
1220
  export function createCommerceMcpServer() {
1165
1221
  const server = new McpServer({
1166
1222
  name: 'shoppex-commerce',
1167
- version: '0.7.0',
1223
+ version: '0.8.0',
1168
1224
  });
1169
1225
 
1170
1226
  for (const tool of createCommerceToolCatalog()) {