@cmssy/mcp-server 0.16.2 → 0.17.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.
@@ -1,4 +1,4 @@
1
- import { MODEL_DEFINITION_BY_ID_QUERY, MODEL_DEFINITIONS_BY_SLUG_INDEX_QUERY, CREATE_MODEL_RECORD_MUTATION, CREATE_DISCOUNT_MUTATION, SAVE_PAGE_MUTATION, CREATE_FORM_MUTATION, CREATE_MODEL_DEFINITION_MUTATION, UPDATE_MODEL_DEFINITION_MUTATION, MODEL_RECORD_BY_ID_QUERY, UPDATE_MODEL_RECORD_MUTATION, UPDATE_MODEL_RECORD_STATUS_MUTATION, } from "./queries.js";
1
+ import { MODEL_DEFINITION_BY_ID_QUERY, MODEL_DEFINITIONS_BY_SLUG_INDEX_QUERY, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, MEDIA_ASSETS_QUERY, FORMS_QUERY, ORDERS_QUERY, DISCOUNTS_QUERY, CREATE_MODEL_RECORD_MUTATION, CREATE_DISCOUNT_MUTATION, SAVE_PAGE_MUTATION, CREATE_FORM_MUTATION, CREATE_MODEL_DEFINITION_MUTATION, UPDATE_MODEL_DEFINITION_MUTATION, MODEL_RECORD_BY_ID_QUERY, UPDATE_MODEL_RECORD_MUTATION, UPDATE_MODEL_RECORD_STATUS_MUTATION, DELETE_MODEL_DEFINITION_MUTATION, DELETE_MODEL_RECORD_MUTATION, IMPORT_MODEL_RECORDS_MUTATION, PAGES_QUERY, PAGE_BY_ID_QUERY, PAGE_TYPES_QUERY, CREATE_PAGE_TYPE_MUTATION, UPDATE_PAGE_SETTINGS_MUTATION, UPDATE_PAGE_LAYOUT_MUTATION, PUBLISH_PAGE_CONTENT_MUTATION, PUBLISH_PAGE_LAYOUT_MUTATION, TOGGLE_PUBLISH_MUTATION, REVERT_CONTENT_TO_PUBLISHED_MUTATION, REVERT_LAYOUT_TO_PUBLISHED_MUTATION, REMOVE_PAGE_MUTATION, PATCH_BLOCK_CONTENT_MUTATION, SITE_CONFIG_QUERY, CURRENT_WORKSPACE_QUERY, FORM_BY_ID_QUERY, UPDATE_FORM_MUTATION, DELETE_FORM_MUTATION, FORM_SUBMISSIONS_QUERY, FORM_SUBMISSION_BY_ID_QUERY, UPDATE_FORM_SUBMISSION_STATUS_MUTATION, DELETE_FORM_SUBMISSION_MUTATION, ORDER_BY_ID_QUERY, ORDER_PIPELINE_QUERY, CREATE_MANUAL_ORDER_MUTATION, EDIT_ORDER_MUTATION, UPDATE_ORDER_DETAILS_MUTATION, MARK_ORDER_PAID_MUTATION, RECORD_ORDER_PAYMENT_MUTATION, REFUND_ORDER_MUTATION, CANCEL_ORDER_MUTATION, TRANSITION_ORDER_FULFILLMENT_MUTATION, SET_ORDER_PIPELINE_STAGE_MUTATION, RECORD_ORDER_INVOICE_MUTATION, DISCOUNT_BY_ID_QUERY, UPDATE_DISCOUNT_MUTATION, SET_DISCOUNT_ENABLED_MUTATION, WEBHOOK_ENDPOINTS_QUERY, WEBHOOK_DELIVERIES_QUERY, WEBHOOK_EVENT_TYPES_QUERY, CREATE_WEBHOOK_ENDPOINT_MUTATION, UPDATE_WEBHOOK_ENDPOINT_MUTATION, ROTATE_WEBHOOK_SECRET_MUTATION, DELETE_WEBHOOK_ENDPOINT_MUTATION, ADMIN_CARTS_QUERY, PRODUCT_CATALOG_QUERY, BULK_UPDATE_PRODUCT_RECORDS_MUTATION, BULK_DELETE_PRODUCT_RECORDS_MUTATION, } from "./queries.js";
2
2
  const OBJECT_ID_RE = /^[a-f0-9]{24}$/i;
3
3
  function slugify(value) {
4
4
  const slug = value
@@ -9,8 +9,21 @@ function slugify(value) {
9
9
  .replace(/^[0-9]/, "m$&");
10
10
  return slug || "model";
11
11
  }
12
- function notBound(name) {
13
- throw new Error(`@cmssy/ai-tools op "${name}" is not bound on the MCP server yet`);
12
+ const isEmpty = (obj) => obj == null ||
13
+ (typeof obj === "object" && Object.keys(obj).length === 0);
14
+ function toRelativeSlug(slug) {
15
+ if (slug === "/")
16
+ return "/";
17
+ return "/" + slug.split("/").filter(Boolean).pop();
18
+ }
19
+ function expectedVersionOf(page) {
20
+ return page?.version != null ? Number(page.version) : undefined;
21
+ }
22
+ async function loadPage(client, pageId) {
23
+ const data = await client.query(PAGE_BY_ID_QUERY, {
24
+ pageId,
25
+ });
26
+ return data.page;
14
27
  }
15
28
  async function fetchModelById(client, id) {
16
29
  const data = await client.query(MODEL_DEFINITION_BY_ID_QUERY, { id });
@@ -34,12 +47,85 @@ async function resolveModel(client, idOrSlug) {
34
47
  return null;
35
48
  return fetchModelById(client, match.id);
36
49
  }
50
+ function toModelSummary(m) {
51
+ return {
52
+ id: m.id,
53
+ name: m.name,
54
+ slug: m.slug,
55
+ description: m.description ?? null,
56
+ fieldCount: m.fields?.length ?? 0,
57
+ };
58
+ }
59
+ function toModelDetail(m) {
60
+ return {
61
+ id: m.id,
62
+ name: m.name,
63
+ slug: m.slug,
64
+ description: m.description ?? null,
65
+ displayField: m.displayField ?? null,
66
+ icon: m.icon ?? null,
67
+ color: m.color ?? null,
68
+ recordCount: m.recordCount ?? null,
69
+ defaultSort: m.defaultSort ?? null,
70
+ statusField: m.statusField
71
+ ? {
72
+ enabled: m.statusField.enabled,
73
+ values: m.statusField.values,
74
+ defaultValue: m.statusField.defaultValue ?? undefined,
75
+ transitions: m.statusField.transitions,
76
+ }
77
+ : null,
78
+ fields: (m.fields ?? []).map((f) => ({
79
+ key: f.key,
80
+ label: f.label,
81
+ type: f.type,
82
+ required: f.required ?? false,
83
+ })),
84
+ };
85
+ }
37
86
  export function createMcpWorkspaceOps(client) {
87
+ const ws = client.workspaceId;
38
88
  return {
39
89
  models: {
40
- list: () => notBound("models.list"),
41
- get: () => notBound("models.get"),
42
- listRecords: () => notBound("models.listRecords"),
90
+ list: async () => {
91
+ const res = await client.query(MODEL_DEFINITIONS_QUERY);
92
+ return res.modelDefinitions.map(toModelSummary);
93
+ },
94
+ get: async (idOrSlug) => {
95
+ const resolved = await resolveModel(client, idOrSlug);
96
+ if (!resolved)
97
+ return null;
98
+ const res = await client.query(MODEL_DEFINITION_BY_ID_QUERY, { id: resolved.id });
99
+ if (!res.modelDefinition)
100
+ return null;
101
+ return toModelDetail(res.modelDefinition);
102
+ },
103
+ listRecords: async (modelIdOrSlug, options) => {
104
+ const model = await resolveModel(client, modelIdOrSlug);
105
+ if (!model)
106
+ return null;
107
+ const res = await client.query(MODEL_RECORDS_QUERY, {
108
+ modelId: model.id,
109
+ filter: options?.filter,
110
+ sort: options?.sort,
111
+ limit: options?.limit,
112
+ offset: options?.offset,
113
+ populate: options?.populate,
114
+ });
115
+ return {
116
+ modelId: model.id,
117
+ items: res.modelRecords.items.map((r) => ({
118
+ id: r.id,
119
+ modelId: r.modelId,
120
+ status: r.status ?? null,
121
+ data: r.data ?? {},
122
+ createdAt: r.createdAt ?? null,
123
+ updatedAt: r.updatedAt ?? null,
124
+ })),
125
+ total: res.modelRecords.total,
126
+ hasMore: res.modelRecords.hasMore,
127
+ };
128
+ },
43
129
  create: async (input) => {
44
130
  const mutationInput = {
45
131
  name: input.name,
@@ -148,9 +234,51 @@ export function createMcpWorkspaceOps(client) {
148
234
  modelId: model.id,
149
235
  };
150
236
  },
237
+ getRecord: async (recordId) => {
238
+ const res = await client.query(MODEL_RECORD_BY_ID_QUERY, { id: recordId });
239
+ const r = res.modelRecord;
240
+ if (!r)
241
+ return null;
242
+ return {
243
+ id: r.id,
244
+ modelId: r.modelId,
245
+ status: r.status ?? null,
246
+ data: r.data ?? {},
247
+ createdAt: r.createdAt ?? null,
248
+ updatedAt: r.updatedAt ?? null,
249
+ };
250
+ },
251
+ delete: async (idOrSlug) => {
252
+ const model = await resolveModel(client, idOrSlug);
253
+ if (!model)
254
+ return { deleted: false };
255
+ const res = await client.query(DELETE_MODEL_DEFINITION_MUTATION, { id: model.id });
256
+ return { deleted: Boolean(res.deleteModelDefinition) };
257
+ },
258
+ deleteRecord: async (recordId) => {
259
+ const res = await client.query(DELETE_MODEL_RECORD_MUTATION, { id: recordId });
260
+ return { deleted: Boolean(res.deleteModelRecord) };
261
+ },
262
+ importRecords: async (modelIdOrSlug, rows) => {
263
+ const model = await resolveModel(client, modelIdOrSlug);
264
+ if (!model)
265
+ throw new Error(`Model "${modelIdOrSlug}" not found`);
266
+ const res = await client.query(IMPORT_MODEL_RECORDS_MUTATION, {
267
+ input: { modelId: model.id, rows },
268
+ });
269
+ return res.importModelRecords;
270
+ },
151
271
  },
152
272
  pages: {
153
- search: () => notBound("pages.search"),
273
+ search: async (query) => {
274
+ const res = await client.query(PAGES_QUERY, { search: query });
275
+ return res.pages.map((p) => ({
276
+ id: p.id,
277
+ name: p.name,
278
+ slug: p.slug,
279
+ published: p.published,
280
+ }));
281
+ },
154
282
  create: async (input) => {
155
283
  const mutationInput = {
156
284
  name: input.name,
@@ -173,10 +301,420 @@ export function createMcpWorkspaceOps(client) {
173
301
  const res = await client.query(SAVE_PAGE_MUTATION, { input: mutationInput });
174
302
  return { id: res.savePage.id, name: res.savePage.name };
175
303
  },
304
+ get: async (idOrSlug) => {
305
+ const page = await loadPage(client, idOrSlug);
306
+ if (!page)
307
+ return null;
308
+ return {
309
+ id: page.id,
310
+ name: page.name,
311
+ slug: page.slug,
312
+ published: Boolean(page.published),
313
+ pageType: page.pageType ?? null,
314
+ parentId: page.parentId ?? null,
315
+ blocks: page.blocks ?? null,
316
+ layoutBlocks: page.layoutBlocks ?? null,
317
+ customFields: page.customFields ?? null,
318
+ createdAt: page.createdAt ?? null,
319
+ updatedAt: page.updatedAt ?? null,
320
+ };
321
+ },
322
+ list: async (search) => {
323
+ const res = await client.query(PAGES_QUERY, search ? { search } : {});
324
+ return res.pages.map((p) => ({
325
+ id: p.id,
326
+ name: p.name,
327
+ slug: p.slug,
328
+ published: Boolean(p.published),
329
+ }));
330
+ },
331
+ listTypes: async () => {
332
+ const res = await client.query(PAGE_TYPES_QUERY);
333
+ return res.pageTypes.map((t) => ({
334
+ id: t.id,
335
+ name: t.name,
336
+ slug: t.slug,
337
+ urlPrefix: t.urlPrefix ?? null,
338
+ allowChildren: Boolean(t.allowChildren),
339
+ }));
340
+ },
341
+ updateBlocks: async (pageId, blocks) => {
342
+ const page = await loadPage(client, pageId);
343
+ if (!page)
344
+ throw new Error("Page not found");
345
+ const existing = page.blocks ?? [];
346
+ const merged = blocks.map((block) => {
347
+ const prev = existing.find((b) => b.id === block.id);
348
+ if (!prev)
349
+ return block;
350
+ return {
351
+ ...block,
352
+ content: isEmpty(block.content) ? prev.content : block.content,
353
+ settings: isEmpty(block.settings) ? prev.settings : block.settings,
354
+ style: block.style ?? prev.style,
355
+ advanced: block.advanced ?? prev.advanced,
356
+ translations: isEmpty(block.translations)
357
+ ? prev.translations
358
+ : block.translations,
359
+ defaultLanguage: block.defaultLanguage ?? prev.defaultLanguage,
360
+ metadata: block.metadata ?? prev.metadata,
361
+ blockVersion: block.blockVersion ?? prev.blockVersion,
362
+ };
363
+ });
364
+ const res = await client.query(SAVE_PAGE_MUTATION, {
365
+ input: {
366
+ id: pageId,
367
+ name: page.name,
368
+ slug: toRelativeSlug(page.slug),
369
+ parentId: page.parentId ?? undefined,
370
+ blocks: merged,
371
+ expectedVersion: expectedVersionOf(page),
372
+ },
373
+ });
374
+ return { id: res.savePage.id };
375
+ },
376
+ updateSettings: async (pageId, settings) => {
377
+ const input = { id: pageId };
378
+ for (const key of [
379
+ "name",
380
+ "slug",
381
+ "displayName",
382
+ "seoTitle",
383
+ "seoDescription",
384
+ "seoKeywords",
385
+ "customFields",
386
+ ]) {
387
+ if (settings[key] !== undefined)
388
+ input[key] = settings[key];
389
+ }
390
+ const res = await client.query(UPDATE_PAGE_SETTINGS_MUTATION, { input });
391
+ if (!res.updatePageSettings)
392
+ throw new Error("Page not found");
393
+ return { id: res.updatePageSettings.id };
394
+ },
395
+ createType: async (input) => {
396
+ const mutationInput = {
397
+ name: input.name,
398
+ slug: input.slug,
399
+ };
400
+ if (input.description !== undefined)
401
+ mutationInput.description = input.description;
402
+ if (input.icon !== undefined)
403
+ mutationInput.icon = input.icon;
404
+ if (input.urlPrefix !== undefined)
405
+ mutationInput.urlPrefix = input.urlPrefix;
406
+ if (input.allowChildren !== undefined)
407
+ mutationInput.allowChildren = input.allowChildren;
408
+ if (input.fields !== undefined)
409
+ mutationInput.fields = input.fields;
410
+ const res = await client.query(CREATE_PAGE_TYPE_MUTATION, { input: mutationInput });
411
+ return {
412
+ id: res.createPageType.id,
413
+ name: res.createPageType.name,
414
+ slug: res.createPageType.slug,
415
+ };
416
+ },
417
+ publish: async (pageId) => {
418
+ const page = await client.query(PAGE_BY_ID_QUERY, { pageId });
419
+ if (!page.page)
420
+ throw new Error("Page not found");
421
+ if (page.page.published &&
422
+ !page.page.hasUnpublishedContentChanges &&
423
+ !page.page.hasUnpublishedLayoutChanges) {
424
+ return { id: pageId };
425
+ }
426
+ await client.query(PUBLISH_PAGE_CONTENT_MUTATION, { id: pageId });
427
+ let res;
428
+ try {
429
+ res = await client.query(PUBLISH_PAGE_LAYOUT_MUTATION, { id: pageId });
430
+ }
431
+ catch (err) {
432
+ const message = err instanceof Error ? err.message : String(err);
433
+ throw new Error(`Content published, but the layout axis failed and is still unpublished: ${message}. Note: re-running publish also re-publishes the current content draft, so publish any pending content edits first.`);
434
+ }
435
+ if (!res.publishPageLayout) {
436
+ throw new Error("Content published, but the layout axis returned no result and is still unpublished. Note: re-running publish also re-publishes the current content draft, so publish any pending content edits first.");
437
+ }
438
+ return { id: res.publishPageLayout.id };
439
+ },
440
+ unpublish: async (pageId) => {
441
+ const res = await client.query(TOGGLE_PUBLISH_MUTATION, { id: pageId });
442
+ if (!res.togglePublish)
443
+ throw new Error("Page not found");
444
+ return { id: res.togglePublish.id };
445
+ },
446
+ revert: async (pageId) => {
447
+ await client.query(REVERT_CONTENT_TO_PUBLISHED_MUTATION, {
448
+ id: pageId,
449
+ });
450
+ await client.query(REVERT_LAYOUT_TO_PUBLISHED_MUTATION, { id: pageId });
451
+ return { id: pageId };
452
+ },
453
+ deletePage: async (pageId) => {
454
+ const res = await client.query(REMOVE_PAGE_MUTATION, { id: pageId });
455
+ return { deleted: Boolean(res.removePage) };
456
+ },
457
+ updateLayout: async (pageId, layout) => {
458
+ let mergedLayoutBlocks = layout.layoutBlocks;
459
+ if (layout.layoutBlocks) {
460
+ const page = await loadPage(client, pageId);
461
+ const existing = page?.layoutBlocks ?? [];
462
+ mergedLayoutBlocks = layout.layoutBlocks.map((block) => {
463
+ const prev = existing.find((b) => b.id === block.id);
464
+ if (!prev)
465
+ return block;
466
+ return {
467
+ ...block,
468
+ content: isEmpty(block.content) ? prev.content : block.content,
469
+ settings: isEmpty(block.settings)
470
+ ? prev.settings
471
+ : block.settings,
472
+ translations: isEmpty(block.translations)
473
+ ? prev.translations
474
+ : block.translations,
475
+ };
476
+ });
477
+ }
478
+ const input = { pageId };
479
+ if (mergedLayoutBlocks !== undefined)
480
+ input.layoutBlocks = mergedLayoutBlocks;
481
+ if (layout.layoutOverrides !== undefined)
482
+ input.layoutOverrides = layout.layoutOverrides;
483
+ if (layout.inheritsLayout !== undefined)
484
+ input.inheritsLayout = layout.inheritsLayout;
485
+ const res = await client.query(UPDATE_PAGE_LAYOUT_MUTATION, { input });
486
+ if (!res.updatePageLayout)
487
+ throw new Error("Page not found");
488
+ return { id: res.updatePageLayout.id };
489
+ },
490
+ addBlock: async (pageId, block, layoutPosition, position) => {
491
+ const page = await loadPage(client, pageId);
492
+ if (!page)
493
+ throw new Error("Page not found");
494
+ const configData = await client.query(SITE_CONFIG_QUERY);
495
+ const defaultLanguage = configData.siteConfig?.defaultLanguage ?? "en";
496
+ const enabledLanguages = configData.siteConfig?.enabledLanguages ?? [
497
+ defaultLanguage,
498
+ ];
499
+ const content = block.content;
500
+ const translations = {};
501
+ for (const lang of enabledLanguages) {
502
+ translations[lang] = {
503
+ status: content[lang] ? "completed" : "pending",
504
+ };
505
+ }
506
+ const newBlockId = crypto.randomUUID();
507
+ if (layoutPosition) {
508
+ const existingLayout = page.layoutBlocks ?? [];
509
+ const maxOrder = existingLayout
510
+ .filter((b) => b.position === layoutPosition)
511
+ .reduce((max, b) => Math.max(max, b.order ?? -1), -1);
512
+ const newLayoutBlock = {
513
+ id: newBlockId,
514
+ type: block.type,
515
+ position: layoutPosition,
516
+ order: maxOrder + 1,
517
+ isActive: true,
518
+ content: block.content,
519
+ settings: block.settings,
520
+ style: block.style,
521
+ translations,
522
+ defaultLanguage,
523
+ };
524
+ const res = await client.query(UPDATE_PAGE_LAYOUT_MUTATION, {
525
+ input: {
526
+ pageId,
527
+ layoutBlocks: [...existingLayout, newLayoutBlock],
528
+ },
529
+ });
530
+ if (!res.updatePageLayout)
531
+ throw new Error("Failed to add block");
532
+ return { pageId, blockId: newBlockId };
533
+ }
534
+ const newBlock = {
535
+ id: newBlockId,
536
+ type: block.type,
537
+ content: block.content,
538
+ settings: block.settings,
539
+ style: block.style,
540
+ translations,
541
+ defaultLanguage,
542
+ };
543
+ const blocks = [...(page.blocks ?? [])];
544
+ if (position !== undefined &&
545
+ position >= 0 &&
546
+ position < blocks.length) {
547
+ blocks.splice(position, 0, newBlock);
548
+ }
549
+ else {
550
+ blocks.push(newBlock);
551
+ }
552
+ await client.query(SAVE_PAGE_MUTATION, {
553
+ input: {
554
+ id: pageId,
555
+ name: page.name,
556
+ slug: toRelativeSlug(page.slug),
557
+ parentId: page.parentId ?? undefined,
558
+ blocks,
559
+ expectedVersion: expectedVersionOf(page),
560
+ },
561
+ });
562
+ return { pageId, blockId: newBlockId };
563
+ },
564
+ updateBlock: async (pageId, blockId, content, settings) => {
565
+ const page = await loadPage(client, pageId);
566
+ if (!page)
567
+ throw new Error("Page not found");
568
+ const contentIdx = (page.blocks ?? []).findIndex((b) => b.id === blockId);
569
+ const layoutIdx = contentIdx === -1
570
+ ? (page.layoutBlocks ?? []).findIndex((b) => b.id === blockId)
571
+ : -1;
572
+ if (contentIdx === -1 && layoutIdx === -1) {
573
+ throw new Error("Block not found on page");
574
+ }
575
+ const isLayout = layoutIdx !== -1;
576
+ const targetArray = isLayout
577
+ ? [...(page.layoutBlocks ?? [])]
578
+ : [...(page.blocks ?? [])];
579
+ const targetIndex = isLayout ? layoutIdx : contentIdx;
580
+ const existingBlock = {
581
+ ...targetArray[targetIndex],
582
+ };
583
+ const mergedContent = {
584
+ ...(existingBlock.content ?? {}),
585
+ };
586
+ for (const [lang, langContent] of Object.entries(content)) {
587
+ if (typeof langContent === "object" &&
588
+ langContent !== null &&
589
+ typeof mergedContent[lang] === "object" &&
590
+ mergedContent[lang] !== null) {
591
+ mergedContent[lang] = {
592
+ ...mergedContent[lang],
593
+ ...langContent,
594
+ };
595
+ }
596
+ else {
597
+ mergedContent[lang] = langContent;
598
+ }
599
+ }
600
+ existingBlock.content = mergedContent;
601
+ if (settings) {
602
+ existingBlock.settings = {
603
+ ...(existingBlock.settings ?? {}),
604
+ ...settings,
605
+ };
606
+ }
607
+ const trans = existingBlock.translations;
608
+ if (trans) {
609
+ for (const lang of Object.keys(content)) {
610
+ if (trans[lang])
611
+ trans[lang] = { status: "completed" };
612
+ }
613
+ }
614
+ targetArray[targetIndex] = existingBlock;
615
+ if (isLayout) {
616
+ const res = await client.query(UPDATE_PAGE_LAYOUT_MUTATION, {
617
+ input: { pageId, layoutBlocks: targetArray },
618
+ });
619
+ if (!res.updatePageLayout)
620
+ throw new Error("Failed to update block");
621
+ }
622
+ else {
623
+ await client.query(SAVE_PAGE_MUTATION, {
624
+ input: {
625
+ id: pageId,
626
+ name: page.name,
627
+ slug: toRelativeSlug(page.slug),
628
+ parentId: page.parentId ?? undefined,
629
+ blocks: targetArray,
630
+ expectedVersion: expectedVersionOf(page),
631
+ },
632
+ });
633
+ }
634
+ return { pageId, blockId };
635
+ },
636
+ patchBlock: async (pageId, blockId, locale, operations, fieldPath, expectedVersion) => {
637
+ const res = await client.query(PATCH_BLOCK_CONTENT_MUTATION, {
638
+ input: {
639
+ pageId,
640
+ blockId,
641
+ locale,
642
+ ...(fieldPath ? { fieldPath } : {}),
643
+ ...(expectedVersion !== undefined ? { expectedVersion } : {}),
644
+ operations,
645
+ },
646
+ });
647
+ if (!res.patchBlockContent) {
648
+ throw new Error("Page not found or patch failed");
649
+ }
650
+ return { pageId, blockId };
651
+ },
652
+ removeBlock: async (pageId, blockId) => {
653
+ const page = await loadPage(client, pageId);
654
+ if (!page)
655
+ throw new Error("Page not found");
656
+ const contentBlocks = (page.blocks ?? []).filter((b) => b.id !== blockId);
657
+ if (contentBlocks.length < (page.blocks ?? []).length) {
658
+ await client.query(SAVE_PAGE_MUTATION, {
659
+ input: {
660
+ id: pageId,
661
+ name: page.name,
662
+ slug: toRelativeSlug(page.slug),
663
+ parentId: page.parentId ?? undefined,
664
+ blocks: contentBlocks,
665
+ expectedVersion: expectedVersionOf(page),
666
+ },
667
+ });
668
+ return { pageId, blockId };
669
+ }
670
+ const layoutBlocks = (page.layoutBlocks ?? []).filter((b) => b.id !== blockId);
671
+ if (layoutBlocks.length < (page.layoutBlocks ?? []).length) {
672
+ const res = await client.query(UPDATE_PAGE_LAYOUT_MUTATION, {
673
+ input: { pageId, layoutBlocks },
674
+ });
675
+ if (!res.updatePageLayout)
676
+ throw new Error("Failed to remove block");
677
+ return { pageId, blockId };
678
+ }
679
+ throw new Error("Block not found on page");
680
+ },
681
+ },
682
+ media: {
683
+ list: async (limit, offset) => {
684
+ const res = await client.query(MEDIA_ASSETS_QUERY, { limit, offset });
685
+ return {
686
+ items: res.mediaAssets.items.map((m) => ({
687
+ id: m.id,
688
+ filename: m.filename,
689
+ type: m.type,
690
+ url: m.url ?? null,
691
+ mimeType: m.mimeType ?? null,
692
+ size: m.size ?? null,
693
+ })),
694
+ total: res.mediaAssets.total,
695
+ hasMore: res.mediaAssets.hasMore,
696
+ };
697
+ },
176
698
  },
177
- media: { list: () => notBound("media.list") },
178
699
  forms: {
179
- list: () => notBound("forms.list"),
700
+ list: async (options) => {
701
+ const res = await client.query(FORMS_QUERY, {
702
+ status: options?.status,
703
+ skip: options?.skip,
704
+ limit: options?.limit,
705
+ });
706
+ return {
707
+ items: res.forms.forms.map((f) => ({
708
+ id: f.id,
709
+ name: f.name,
710
+ slug: f.slug ?? null,
711
+ status: f.status,
712
+ submissionCount: f.submissionCount ?? null,
713
+ })),
714
+ total: res.forms.total,
715
+ hasMore: res.forms.hasMore,
716
+ };
717
+ },
180
718
  create: async (input) => {
181
719
  const mutationInput = {
182
720
  name: input.name,
@@ -191,17 +729,389 @@ export function createMcpWorkspaceOps(client) {
191
729
  const res = await client.query(CREATE_FORM_MUTATION, { input: mutationInput });
192
730
  return { id: res.createForm.id, name: res.createForm.name };
193
731
  },
732
+ get: async (idOrSlug) => {
733
+ const res = await client.query(FORM_BY_ID_QUERY, { formId: idOrSlug });
734
+ const f = res.form;
735
+ if (!f)
736
+ return null;
737
+ return {
738
+ id: f.id,
739
+ name: f.name,
740
+ slug: f.slug,
741
+ status: f.status ?? null,
742
+ fields: f.fields ?? null,
743
+ settings: f.settings ?? null,
744
+ submissionCount: f.submissionCount ?? null,
745
+ };
746
+ },
747
+ update: async (idOrSlug, patch) => {
748
+ const input = {};
749
+ for (const key of [
750
+ "name",
751
+ "slug",
752
+ "description",
753
+ "status",
754
+ "fields",
755
+ "settings",
756
+ ]) {
757
+ if (patch[key] !== undefined)
758
+ input[key] = patch[key];
759
+ }
760
+ const res = await client.query(UPDATE_FORM_MUTATION, { formId: idOrSlug, input });
761
+ return res.updateForm ? { id: res.updateForm.id } : null;
762
+ },
763
+ delete: async (idOrSlug) => {
764
+ const res = await client.query(DELETE_FORM_MUTATION, { formId: idOrSlug });
765
+ return { deleted: Boolean(res.deleteForm) };
766
+ },
767
+ listSubmissions: async (options) => {
768
+ const res = await client.query(FORM_SUBMISSIONS_QUERY, {
769
+ ...(options?.formIdOrSlug ? { formId: options.formIdOrSlug } : {}),
770
+ ...(options?.status ? { status: options.status } : {}),
771
+ skip: options?.skip ?? 0,
772
+ limit: options?.limit ?? 50,
773
+ });
774
+ return res.formSubmissions;
775
+ },
776
+ getSubmission: async (submissionId) => {
777
+ const res = await client.query(FORM_SUBMISSION_BY_ID_QUERY, { submissionId });
778
+ return res.formSubmission;
779
+ },
780
+ updateSubmissionStatus: async (submissionId, status) => {
781
+ const res = await client.query(UPDATE_FORM_SUBMISSION_STATUS_MUTATION, { submissionId, status });
782
+ return { ok: Boolean(res.updateFormSubmissionStatus) };
783
+ },
784
+ deleteSubmission: async (submissionId) => {
785
+ const res = await client.query(DELETE_FORM_SUBMISSION_MUTATION, { submissionId });
786
+ return { deleted: Boolean(res.deleteFormSubmission) };
787
+ },
788
+ },
789
+ orders: {
790
+ list: async (options) => {
791
+ const res = await client.query(ORDERS_QUERY, {
792
+ workspaceId: ws,
793
+ paymentStatus: options?.paymentStatus,
794
+ fulfillmentStatus: options?.fulfillmentStatus,
795
+ customerId: options?.customerId,
796
+ search: options?.search,
797
+ pipelineStageId: options?.pipelineStageId,
798
+ dateFrom: options?.dateFrom,
799
+ dateTo: options?.dateTo,
800
+ skip: options?.skip,
801
+ limit: options?.limit,
802
+ });
803
+ return {
804
+ items: res.orders.items.map((o) => ({
805
+ id: o.id,
806
+ orderNumber: o.orderNumber ?? null,
807
+ customerEmail: o.customerEmail ?? null,
808
+ status: o.status ?? null,
809
+ paymentStatus: o.paymentStatus ?? null,
810
+ fulfillmentStatus: o.fulfillmentStatus ?? null,
811
+ total: o.total ?? null,
812
+ currency: o.currency ?? null,
813
+ createdAt: o.createdAt ?? null,
814
+ })),
815
+ total: res.orders.total,
816
+ hasMore: res.orders.hasMore,
817
+ };
818
+ },
819
+ get: async (id) => {
820
+ const res = await client.query(ORDER_BY_ID_QUERY, { workspaceId: ws, id });
821
+ const o = res.order;
822
+ if (!o)
823
+ return null;
824
+ return {
825
+ id: o.id,
826
+ orderNumber: o.orderNumber ?? null,
827
+ paymentStatus: o.paymentStatus ?? null,
828
+ fulfillmentStatus: o.fulfillmentStatus ?? null,
829
+ customerEmail: o.customerEmail ?? null,
830
+ total: o.total ?? null,
831
+ currency: o.currency ?? null,
832
+ items: o.items ?? null,
833
+ payments: o.payments ?? null,
834
+ createdAt: o.createdAt ?? null,
835
+ };
836
+ },
837
+ getPipeline: async () => {
838
+ const res = await client.query(ORDER_PIPELINE_QUERY, { workspaceId: ws });
839
+ return res.orderPipeline;
840
+ },
841
+ createManual: async (customerEmail, items, customerId) => {
842
+ const res = await client.query(CREATE_MANUAL_ORDER_MUTATION, {
843
+ input: { workspaceId: ws, customerEmail, customerId, items },
844
+ });
845
+ const o = res.createManualOrder;
846
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
847
+ },
848
+ edit: async (orderId, items) => {
849
+ const res = await client.query(EDIT_ORDER_MUTATION, {
850
+ input: { workspaceId: ws, orderId, items },
851
+ });
852
+ const o = res.editOrder;
853
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
854
+ },
855
+ updateDetails: async (orderId, details) => {
856
+ const res = await client.query(UPDATE_ORDER_DETAILS_MUTATION, {
857
+ input: { workspaceId: ws, orderId, ...details },
858
+ });
859
+ const o = res.updateOrderDetails;
860
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
861
+ },
862
+ markPaid: async (orderId, payment) => {
863
+ const res = await client.query(MARK_ORDER_PAID_MUTATION, {
864
+ input: {
865
+ workspaceId: ws,
866
+ orderId,
867
+ amount: payment.amount,
868
+ reference: payment.reference,
869
+ provider: payment.provider ?? "manual",
870
+ },
871
+ });
872
+ const o = res.markOrderPaid;
873
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
874
+ },
875
+ recordPayment: async (orderId, payment) => {
876
+ const res = await client.query(RECORD_ORDER_PAYMENT_MUTATION, {
877
+ input: {
878
+ workspaceId: ws,
879
+ orderId,
880
+ amount: payment.amount,
881
+ reference: payment.reference,
882
+ provider: payment.provider,
883
+ },
884
+ });
885
+ const o = res.recordOrderPayment;
886
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
887
+ },
888
+ refund: async (orderId, reference, amount) => {
889
+ const res = await client.query(REFUND_ORDER_MUTATION, {
890
+ input: { workspaceId: ws, orderId, reference, amount },
891
+ });
892
+ const o = res.refundOrder;
893
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
894
+ },
895
+ cancel: async (orderId) => {
896
+ const res = await client.query(CANCEL_ORDER_MUTATION, {
897
+ input: { workspaceId: ws, orderId },
898
+ });
899
+ const o = res.cancelOrder;
900
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
901
+ },
902
+ transitionFulfillment: async (orderId, status, trackingNumber, trackingCarrier) => {
903
+ const res = await client.query(TRANSITION_ORDER_FULFILLMENT_MUTATION, {
904
+ input: {
905
+ workspaceId: ws,
906
+ orderId,
907
+ status,
908
+ trackingNumber,
909
+ trackingCarrier,
910
+ },
911
+ });
912
+ const o = res.transitionOrderFulfillment;
913
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
914
+ },
915
+ setPipelineStage: async (orderId, stageId) => {
916
+ const res = await client.query(SET_ORDER_PIPELINE_STAGE_MUTATION, {
917
+ input: { workspaceId: ws, orderId, stageId },
918
+ });
919
+ const o = res.setOrderPipelineStage;
920
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
921
+ },
922
+ recordInvoice: async (orderId, invoice) => {
923
+ const res = await client.query(RECORD_ORDER_INVOICE_MUTATION, {
924
+ input: {
925
+ workspaceId: ws,
926
+ orderId,
927
+ number: invoice.number,
928
+ url: invoice.url,
929
+ provider: invoice.provider,
930
+ },
931
+ });
932
+ const o = res.recordOrderInvoice;
933
+ return { id: o.id, orderNumber: o.orderNumber ?? null };
934
+ },
194
935
  },
195
- orders: { list: () => notBound("orders.list") },
196
936
  discounts: {
197
- list: () => notBound("discounts.list"),
198
- create: async (input) => {
199
- const res = await client.query(CREATE_DISCOUNT_MUTATION, {
200
- workspaceId: client.workspaceId,
201
- input,
937
+ list: async (options) => {
938
+ const res = await client.query(DISCOUNTS_QUERY, {
939
+ workspaceId: ws,
940
+ enabled: options?.enabled,
941
+ type: options?.type,
942
+ search: options?.search,
943
+ limit: options?.limit,
944
+ offset: options?.offset,
202
945
  });
946
+ return {
947
+ items: res.discounts.items.map((d) => ({
948
+ id: d.id,
949
+ code: d.code,
950
+ type: d.type,
951
+ value: d.value ?? null,
952
+ enabled: d.enabled,
953
+ currentUses: d.currentUses ?? null,
954
+ })),
955
+ total: res.discounts.total,
956
+ hasMore: res.discounts.hasMore,
957
+ };
958
+ },
959
+ create: async (input) => {
960
+ const res = await client.query(CREATE_DISCOUNT_MUTATION, { workspaceId: ws, input });
203
961
  return { id: res.createDiscount.id, code: res.createDiscount.code };
204
962
  },
963
+ get: async (idOrSlug) => {
964
+ const res = await client.query(DISCOUNT_BY_ID_QUERY, { workspaceId: ws, id: idOrSlug });
965
+ const d = res.discount;
966
+ if (!d)
967
+ return null;
968
+ return {
969
+ id: d.id,
970
+ code: d.code,
971
+ type: d.type,
972
+ value: d.value,
973
+ enabled: d.enabled,
974
+ currency: d.currency ?? null,
975
+ minSubtotal: d.minSubtotal ?? null,
976
+ maxUses: d.maxUses ?? null,
977
+ maxUsesPerUser: d.maxUsesPerUser ?? null,
978
+ currentUses: d.currentUses ?? null,
979
+ startsAt: d.startsAt ?? null,
980
+ endsAt: d.endsAt ?? null,
981
+ };
982
+ },
983
+ update: async (idOrSlug, patch) => {
984
+ const input = {};
985
+ for (const key of [
986
+ "code",
987
+ "type",
988
+ "value",
989
+ "currency",
990
+ "minSubtotal",
991
+ "maxUses",
992
+ "maxUsesPerUser",
993
+ "startsAt",
994
+ "endsAt",
995
+ "enabled",
996
+ ]) {
997
+ if (patch[key] !== undefined)
998
+ input[key] = patch[key];
999
+ }
1000
+ const res = await client.query(UPDATE_DISCOUNT_MUTATION, { workspaceId: ws, id: idOrSlug, input });
1001
+ if (!res.updateDiscount)
1002
+ return null;
1003
+ return {
1004
+ id: res.updateDiscount.id,
1005
+ code: res.updateDiscount.code,
1006
+ enabled: res.updateDiscount.enabled,
1007
+ };
1008
+ },
1009
+ setEnabled: async (idOrSlug, enabled) => {
1010
+ const res = await client.query(SET_DISCOUNT_ENABLED_MUTATION, {
1011
+ workspaceId: ws,
1012
+ id: idOrSlug,
1013
+ enabled,
1014
+ });
1015
+ if (!res.setDiscountEnabled)
1016
+ return null;
1017
+ return {
1018
+ id: res.setDiscountEnabled.id,
1019
+ code: res.setDiscountEnabled.code,
1020
+ enabled: res.setDiscountEnabled.enabled,
1021
+ };
1022
+ },
1023
+ },
1024
+ workspace: {
1025
+ info: async () => {
1026
+ const res = await client.query(CURRENT_WORKSPACE_QUERY);
1027
+ const w = res.currentWorkspace;
1028
+ if (!w)
1029
+ throw new Error("Workspace not found");
1030
+ return {
1031
+ id: w.id,
1032
+ name: w.name,
1033
+ slug: w.slug,
1034
+ plan: w.plan ?? null,
1035
+ limits: w.limits ?? null,
1036
+ };
1037
+ },
1038
+ siteConfig: async () => {
1039
+ const res = await client.query(SITE_CONFIG_QUERY);
1040
+ const c = res.siteConfig;
1041
+ if (!c)
1042
+ return null;
1043
+ return {
1044
+ id: c.id ?? null,
1045
+ defaultLanguage: c.defaultLanguage ?? null,
1046
+ enabledLanguages: c.enabledLanguages ?? [],
1047
+ siteName: c.siteName ?? null,
1048
+ enabledFeatures: c.enabledFeatures ?? [],
1049
+ header: c.header ?? null,
1050
+ footer: c.footer ?? null,
1051
+ };
1052
+ },
1053
+ },
1054
+ webhooks: {
1055
+ list: async () => {
1056
+ const res = await client.query(WEBHOOK_ENDPOINTS_QUERY, { workspaceId: ws });
1057
+ return res.webhookEndpoints;
1058
+ },
1059
+ listDeliveries: async (limit) => {
1060
+ const res = await client.query(WEBHOOK_DELIVERIES_QUERY, { workspaceId: ws, limit: limit ?? 50 });
1061
+ return res.webhookDeliveries;
1062
+ },
1063
+ listEventTypes: async () => {
1064
+ const res = await client.query(WEBHOOK_EVENT_TYPES_QUERY, { workspaceId: ws });
1065
+ return res.webhookEventTypes;
1066
+ },
1067
+ create: async (input) => {
1068
+ const res = await client.query(CREATE_WEBHOOK_ENDPOINT_MUTATION, {
1069
+ input: {
1070
+ workspaceId: ws,
1071
+ url: input.url,
1072
+ events: input.events,
1073
+ description: input.description,
1074
+ },
1075
+ });
1076
+ return res.createWebhookEndpoint;
1077
+ },
1078
+ update: async (id, patch) => {
1079
+ const res = await client.query(UPDATE_WEBHOOK_ENDPOINT_MUTATION, { input: { workspaceId: ws, id, ...patch } });
1080
+ return res.updateWebhookEndpoint;
1081
+ },
1082
+ rotateSecret: async (id) => {
1083
+ const res = await client.query(ROTATE_WEBHOOK_SECRET_MUTATION, { workspaceId: ws, id });
1084
+ return res.rotateWebhookSecret;
1085
+ },
1086
+ delete: async (id) => {
1087
+ const res = await client.query(DELETE_WEBHOOK_ENDPOINT_MUTATION, { workspaceId: ws, id });
1088
+ return { deleted: Boolean(res.deleteWebhookEndpoint) };
1089
+ },
1090
+ },
1091
+ carts: {
1092
+ list: async (status, skip, limit) => {
1093
+ const res = await client.query(ADMIN_CARTS_QUERY, {
1094
+ workspaceId: ws,
1095
+ ...(status ? { status } : {}),
1096
+ skip: skip ?? 0,
1097
+ limit: limit ?? 20,
1098
+ });
1099
+ return res.adminCarts;
1100
+ },
1101
+ },
1102
+ products: {
1103
+ list: async (modelId, filter, limit, offset, sort) => {
1104
+ const res = await client.query(PRODUCT_CATALOG_QUERY, { modelId, filter, limit, offset, sort });
1105
+ return res.productCatalog;
1106
+ },
1107
+ bulkUpdate: async (modelId, selection, patch) => {
1108
+ const res = await client.query(BULK_UPDATE_PRODUCT_RECORDS_MUTATION, { modelId, selection, patch });
1109
+ return { count: res.bulkUpdateProductRecords };
1110
+ },
1111
+ bulkDelete: async (modelId, selection) => {
1112
+ const res = await client.query(BULK_DELETE_PRODUCT_RECORDS_MUTATION, { modelId, selection });
1113
+ return { count: res.bulkDeleteProductRecords };
1114
+ },
205
1115
  },
206
1116
  };
207
1117
  }