@diviops/mcp-server 1.5.18 → 1.5.19

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 +2 -0
  2. package/dist/index.js +231 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -84,6 +84,8 @@ Additional **conditionally-registered Pro tools** appear only on sites that have
84
84
  |----------|------------------|------------|
85
85
  | FluentCart reads (V1) | Pro plugin + FluentCart installed + module enabled | `diviops_fc_product_list`, `diviops_fc_product_get` |
86
86
  | FluentCart simple product writes (V2) | Pro plugin + FluentCart installed + module enabled | `diviops_fc_product_create`, `diviops_fc_product_update`, `diviops_fc_product_delete` |
87
+ | FluentCart variation read/write (V3) | Pro plugin + FluentCart installed + module enabled | `diviops_fc_variation_list`, `diviops_fc_variation_update` |
88
+ | FluentCart license-settings read/write (V3) | Pro plugin + FluentCart Pro installed + module enabled | `diviops_fc_license_settings_get`, `diviops_fc_license_settings_update` |
87
89
 
88
90
  When the gates are not satisfied, the tools simply don't appear on the MCP surface — no error envelope, no missing-capability hint. See the `diviops-fluentcart` skill bundle for the operator-side guide.
89
91
 
package/dist/index.js CHANGED
@@ -3328,6 +3328,237 @@ function registerProTools() {
3328
3328
  ],
3329
3329
  };
3330
3330
  }, { target: "fluentcart", capabilityKey: "fluentcart_product_delete" });
3331
+ // ── V3 — variation read/write + license-settings read/write ────────
3332
+ //
3333
+ // V3 expands the FCP authoring surface so a draft simple-product catalog
3334
+ // can represent ADR-005 commercial shape: annual rows become subscription
3335
+ // products, lifetime rows stay onetime, and per-tier activation_limit
3336
+ // is writable. Activation limits live in `ProductMeta.license_settings`
3337
+ // per FluentCart Pro source (NOT variation `other_info`), so license
3338
+ // settings have their own dedicated read/write tools.
3339
+ //
3340
+ // V3 stays inside the simple-product default-variation contract:
3341
+ // no multi-variation create/delete, no signup-fee writes,
3342
+ // no license activation flow, no update-ZIP / readme / banner config.
3343
+ // diviops_fc_variation_list — POST /diviops/v1/pro/fluentcart/products/{id}/variations
3344
+ registerProTool("diviops_fc_variation_list", {
3345
+ description: "List FluentCart Pro variations for a product (Pro tier; V3; requires FluentCart Pro installed + activated). Read-only. Returns every variation row attached to the product with its subscription shape (payment_type, other_info.repeat_interval/times/trial_days/manage_setup_fee) and a license-settings projection when ProductMeta.license_settings is configured. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is { product_id, variation_type, default_variation_id, variations: VariationRow[], variations_count }. Each VariationRow carries { id, post_id, variation_title, sku, payment_type, item_price, compare_price, fulfillment_type, stock_status, manage_stock, available, other_info: { ...all stored keys... }, license: { activation_limit, validity: { unit, value } } | null }. Unit convention: item_price and compare_price are stored cents (e.g. 1900 = $19.00). compare_price is null when FCP stores the no-compare sentinel 0; sku is null when the column is SQL NULL OR an empty string. license is null when the product has no license_settings; otherwise activation_limit is null/'' (unset), 0 (unlimited per FluentCart Pro License::getActivationLimit), or a positive integer (max activations). validity.unit is one of: lifetime/day/week/month/year. Error codes: invalid_input (400) when id is not a positive integer; not_found (404) when the product does not exist; fluentcart.module_inactive (412); fluentcart.query_failed (500). Idempotency: read-only.",
3346
+ inputSchema: {
3347
+ product_id: z
3348
+ .number()
3349
+ .int()
3350
+ .positive()
3351
+ .describe("FluentCart product ID (the post ID of the fluent_products CPT entry)."),
3352
+ },
3353
+ annotations: { idempotentHint: true },
3354
+ _meta: { idempotent: "true" },
3355
+ }, async ({ product_id }) => {
3356
+ const result = await wp.requestEnveloped(`/pro/fluentcart/products/${product_id}/variations`, { method: "POST" });
3357
+ return {
3358
+ content: [
3359
+ {
3360
+ type: "text",
3361
+ text: serializeEnvelope(result, "diviops_fc_variation_list"),
3362
+ },
3363
+ ],
3364
+ };
3365
+ }, { target: "fluentcart", capabilityKey: "fluentcart_variation_list" });
3366
+ // diviops_fc_variation_update — POST /diviops/v1/pro/fluentcart/products/{product_id}/variations/{variation_id}/update
3367
+ registerProTool("diviops_fc_variation_update", {
3368
+ description: "Update the default variation of a simple FluentCart Pro product (Pro tier; V3; requires FluentCart Pro installed + activated). V3 scope: writes the product's default variation only; refuses non-simple products and non-default variations with `fluentcart.unsupported_product_shape` (HTTP 422). Multi-variation create/delete remains out of scope. Accepts partial updates on price, compare_price, sku, payment_type, and the subscription shape (repeat_interval, times, trial_days, manage_setup_fee). Switching `payment_type: \"subscription\"` requires `repeat_interval` (yearly/half_yearly/quarterly/monthly/weekly/daily) — either supplied in the same call or already stored. Switching to `onetime` strips the subscription-only keys from other_info, matching ProductVariationRequest::beforeValidation. `manage_setup_fee: \"yes\"` requires signup_fee + signup_fee_name which are out of scope for V3 (use FluentCart admin UI for setup fees); only `manage_setup_fee: \"no\"` is accepted. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; apply-mode success payload is { product_id, variation_id, changed_fields[], variation: VariationRow, product_price_range: { min_price, max_price } | null } (or { noop: true, product_id, variation_id, variation } when nothing changes). VariationRow mirrors the diviops_fc_variation_list shape — sku, item_price, compare_price, payment_type, other_info, license round-trip without a follow-up read. Unit asymmetry: write inputs (price, compare_price) are currency units (e.g. 19.00); VariationRow returns item_price + compare_price in stored cents. Error codes: invalid_input (400); not_found (404); fluentcart.unsupported_product_shape (422); fluentcart.sku_conflict (409); fluentcart.module_inactive (412); fluentcart.command_failed (500). Idempotency: conditional — identical repeat is a no-op." +
3369
+ DRY_RUN_DESC_SUFFIX,
3370
+ inputSchema: {
3371
+ product_id: z
3372
+ .number()
3373
+ .int()
3374
+ .positive()
3375
+ .describe("FluentCart product ID (the post ID of the fluent_products CPT entry)."),
3376
+ variation_id: z
3377
+ .number()
3378
+ .int()
3379
+ .positive()
3380
+ .describe("FluentCart variation ID. Must be the product's default variation (V3 constraint)."),
3381
+ price: z
3382
+ .number()
3383
+ .min(0)
3384
+ .optional()
3385
+ .describe("Variation item_price in currency units (e.g. 19.00). Non-negative."),
3386
+ compare_price: z
3387
+ .number()
3388
+ .min(0)
3389
+ .optional()
3390
+ .describe("Variation compare-at price in currency units. Must be ≥ price when both provided."),
3391
+ sku: z
3392
+ .string()
3393
+ .optional()
3394
+ .describe("Variation SKU. Must be unique across all FluentCart variations and at most 30 characters. Empty string clears the SKU (reads back as null)."),
3395
+ payment_type: z
3396
+ .enum(["onetime", "subscription"])
3397
+ .optional()
3398
+ .describe("Variation payment_type. Switching to 'subscription' requires repeat_interval. Switching to 'onetime' strips subscription-only fields from other_info."),
3399
+ repeat_interval: z
3400
+ .enum([
3401
+ "yearly",
3402
+ "half_yearly",
3403
+ "quarterly",
3404
+ "monthly",
3405
+ "weekly",
3406
+ "daily",
3407
+ ])
3408
+ .optional()
3409
+ .describe("Subscription billing interval. Required when switching to payment_type='subscription' unless already stored. For ADR-005 annual rows use 'yearly'."),
3410
+ times: z
3411
+ .number()
3412
+ .int()
3413
+ .min(0)
3414
+ .optional()
3415
+ .describe("Number of subscription billing cycles. 0 = indefinite (default for V3 subscription rows)."),
3416
+ trial_days: z
3417
+ .number()
3418
+ .int()
3419
+ .min(0)
3420
+ .max(365)
3421
+ .optional()
3422
+ .describe("Trial-period length in days (0-365)."),
3423
+ manage_setup_fee: z
3424
+ .enum(["no"])
3425
+ .optional()
3426
+ .describe("Setup-fee mode. V3 only accepts 'no' — manage_setup_fee='yes' requires signup_fee + signup_fee_name which are out of scope for V3."),
3427
+ dry_run: DRY_RUN_FIELD,
3428
+ },
3429
+ annotations: { idempotentHint: false },
3430
+ _meta: { idempotent: "conditional" },
3431
+ }, async ({ product_id, variation_id, price, compare_price, sku, payment_type, repeat_interval, times, trial_days, manage_setup_fee, dry_run, }) => {
3432
+ const body = {};
3433
+ if (price !== undefined)
3434
+ body.price = price;
3435
+ if (compare_price !== undefined)
3436
+ body.compare_price = compare_price;
3437
+ if (sku !== undefined)
3438
+ body.sku = sku;
3439
+ if (payment_type !== undefined)
3440
+ body.payment_type = payment_type;
3441
+ if (repeat_interval !== undefined)
3442
+ body.repeat_interval = repeat_interval;
3443
+ if (times !== undefined)
3444
+ body.times = times;
3445
+ if (trial_days !== undefined)
3446
+ body.trial_days = trial_days;
3447
+ if (manage_setup_fee !== undefined)
3448
+ body.manage_setup_fee = manage_setup_fee;
3449
+ if (dry_run !== undefined)
3450
+ body.dry_run = dry_run;
3451
+ const result = await wp.requestEnveloped(`/pro/fluentcart/products/${product_id}/variations/${variation_id}/update`, { method: "POST", body });
3452
+ return {
3453
+ content: [
3454
+ {
3455
+ type: "text",
3456
+ text: serializeEnvelope(result, "diviops_fc_variation_update"),
3457
+ },
3458
+ ],
3459
+ };
3460
+ }, { target: "fluentcart", capabilityKey: "fluentcart_variation_update" });
3461
+ // diviops_fc_license_settings_get — POST /diviops/v1/pro/fluentcart/products/{id}/license-settings
3462
+ registerProTool("diviops_fc_license_settings_get", {
3463
+ description: "Read the per-product FluentCart Pro license-settings projection (Pro tier; V3; requires FluentCart Pro installed + activated). FluentCart Pro stores license settings in `ProductMeta` under meta_key='license_settings'; this tool reads that meta row and joins it against the product's variations so each variation surfaces with its current activation_limit + validity. Read-only. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is { product_id, enabled: boolean, version, prefix, variations: [ { variation_id, title, activation_limit, validity: { unit, value } | null } ] }. Storage semantics: `enabled` is stored as 'yes'/'no' in FCP and projected to boolean here. `activation_limit` is null/'' (unconfigured), 0 (unlimited per FluentCart Pro License::getActivationLimit), or a positive integer. `validity.unit` is one of lifetime/day/week/month/year. Variations the product has but license_settings doesn't mention surface with `activation_limit: null` and `validity: null`. Variations license_settings mentions that no longer exist on the product are filtered out — only the live variation set is returned. Error codes: invalid_input (400) when id is not a positive integer; not_found (404) when the product does not exist; fluentcart.module_inactive (412); fluentcart.query_failed (500). Idempotency: read-only.",
3464
+ inputSchema: {
3465
+ product_id: z
3466
+ .number()
3467
+ .int()
3468
+ .positive()
3469
+ .describe("FluentCart product ID (the post ID of the fluent_products CPT entry)."),
3470
+ },
3471
+ annotations: { idempotentHint: true },
3472
+ _meta: { idempotent: "true" },
3473
+ }, async ({ product_id }) => {
3474
+ const result = await wp.requestEnveloped(`/pro/fluentcart/products/${product_id}/license-settings`, { method: "POST" });
3475
+ return {
3476
+ content: [
3477
+ {
3478
+ type: "text",
3479
+ text: serializeEnvelope(result, "diviops_fc_license_settings_get"),
3480
+ },
3481
+ ],
3482
+ };
3483
+ }, { target: "fluentcart", capabilityKey: "fluentcart_license_settings_get" });
3484
+ // diviops_fc_license_settings_update — POST /diviops/v1/pro/fluentcart/products/{id}/license-settings/update
3485
+ registerProTool("diviops_fc_license_settings_update", {
3486
+ description: "Write the per-product FluentCart Pro license-settings ProductMeta row (Pro tier; V3; requires FluentCart Pro installed + activated). Authors `enabled`, `version`, `prefix`, and per-variation `activation_limit` + `validity` — the storage shape FluentCart Pro reads via LicenseGenerationHandler when an order is placed. V3 explicitly skips configuring update-ZIP `global_update_file`, `wp` readme/banner/icon, downloadables, and the license-activation API; those fields are preserved when present but not authored. Refuses bundle products with `fluentcart.unsupported_product_shape` (HTTP 422). Inputs (all optional except product_id; partial updates supported): `enabled` (boolean — projected to FCP's 'yes'/'no' on write), `version` (required when enabling; max 50 chars), `prefix` (max 20 chars), `variations` (array of { variation_id (required), activation_limit (integer ≥ 0 or null; 0 = unlimited per FluentCart Pro License::getActivationLimit), validity (optional { unit: lifetime/day/week/month/year, value: positive integer } or null to auto-derive) }). When `validity` is omitted on a variation, the validity is derived from the variation's payment_type: subscription+yearly → { unit: 'year', value: 1 }; onetime → { unit: 'lifetime', value: 1 }. When `enabled: true` and the product carries variations, every variation must end up with a non-empty validity.unit. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; apply-mode success payload is { product_id, changed_fields[], license_settings: <same shape as diviops_fc_license_settings_get> } (or { noop: true, product_id, license_settings } on no-op). Error codes: invalid_input (400) when any field violates the constraints (unknown variation_id, negative activation_limit, missing version when enabling, missing validity.unit when enabling, bad enum value); not_found (404) when the product does not exist; fluentcart.unsupported_product_shape (422) on bundle products; fluentcart.module_inactive (412); fluentcart.command_failed (500). Idempotency: conditional — identical repeat is a no-op." +
3487
+ DRY_RUN_DESC_SUFFIX,
3488
+ inputSchema: {
3489
+ product_id: z
3490
+ .number()
3491
+ .int()
3492
+ .positive()
3493
+ .describe("FluentCart product ID (the post ID of the fluent_products CPT entry)."),
3494
+ enabled: z
3495
+ .boolean()
3496
+ .optional()
3497
+ .describe("Toggle FCP license-settings enablement. Stored as 'yes'/'no' in FCP. When true, version is required (use '1.0.0-beta' for the dogfood catalog)."),
3498
+ version: z
3499
+ .string()
3500
+ .max(50)
3501
+ .optional()
3502
+ .describe("License-settings version string (max 50 chars). Required when enabling. Recommended: '1.0.0-beta' for a beta-cohort catalog."),
3503
+ prefix: z
3504
+ .string()
3505
+ .max(20)
3506
+ .optional()
3507
+ .describe("License-key prefix (max 20 chars). Recommended: 'DOP' for DiviOps products."),
3508
+ variations: z
3509
+ .array(z.object({
3510
+ variation_id: z
3511
+ .number()
3512
+ .int()
3513
+ .positive()
3514
+ .describe("Target variation ID; must belong to this product."),
3515
+ activation_limit: z
3516
+ .number()
3517
+ .int()
3518
+ .min(0)
3519
+ .nullable()
3520
+ .optional()
3521
+ .describe("Activation limit. 0 = unlimited (per FluentCart Pro License::getActivationLimit). null clears the configured limit."),
3522
+ validity: z
3523
+ .object({
3524
+ unit: z.enum(["lifetime", "day", "week", "month", "year"]),
3525
+ value: z.number().int().positive(),
3526
+ })
3527
+ .nullable()
3528
+ .optional()
3529
+ .describe("Validity period. When omitted, derived from the variation's payment_type (subscription+yearly → year/1; onetime → lifetime/1). When null, force-rederived from current variation state."),
3530
+ }))
3531
+ .optional()
3532
+ .describe("Per-variation license configuration. Each entry's variation_id must belong to the product. Omitted variations preserve their existing license_settings."),
3533
+ dry_run: DRY_RUN_FIELD,
3534
+ },
3535
+ annotations: { idempotentHint: false },
3536
+ _meta: { idempotent: "conditional" },
3537
+ }, async ({ product_id, enabled, version, prefix, variations, dry_run, }) => {
3538
+ const body = {};
3539
+ if (enabled !== undefined)
3540
+ body.enabled = enabled;
3541
+ if (version !== undefined)
3542
+ body.version = version;
3543
+ if (prefix !== undefined)
3544
+ body.prefix = prefix;
3545
+ if (variations !== undefined)
3546
+ body.variations = variations;
3547
+ if (dry_run !== undefined)
3548
+ body.dry_run = dry_run;
3549
+ const result = await wp.requestEnveloped(`/pro/fluentcart/products/${product_id}/license-settings/update`, { method: "POST", body });
3550
+ return {
3551
+ content: [
3552
+ {
3553
+ type: "text",
3554
+ text: serializeEnvelope(result, "diviops_fc_license_settings_update"),
3555
+ },
3556
+ ],
3557
+ };
3558
+ }, {
3559
+ target: "fluentcart",
3560
+ capabilityKey: "fluentcart_license_settings_update",
3561
+ });
3331
3562
  }
3332
3563
  // ── Start ────────────────────────────────────────────────────────────
3333
3564
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diviops/mcp-server",
3
- "version": "1.5.18",
3
+ "version": "1.5.19",
4
4
  "description": "MCP server exposing Divi 5 Visual Builder as tools for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",