@cmssy/mcp-server 0.9.0 → 0.11.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.
- package/README.md +68 -2
- package/dist/graphql-client.d.ts +1 -1
- package/dist/graphql-client.d.ts.map +1 -1
- package/dist/graphql-client.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/queries.d.ts +35 -4
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +414 -0
- package/dist/queries.js.map +1 -1
- package/dist/responses.d.ts +40 -0
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +34 -0
- package/dist/responses.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +713 -5
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/server.js
CHANGED
|
@@ -34,8 +34,8 @@ const jsonPreprocess = (val) => {
|
|
|
34
34
|
return val;
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
-
import { PAGES_QUERY, PAGE_BY_ID_QUERY, SITE_CONFIG_QUERY, CURRENT_WORKSPACE_QUERY, MEDIA_ASSETS_QUERY, SAVE_PAGE_MUTATION, PATCH_BLOCK_CONTENT_MUTATION, UPDATE_PAGE_SETTINGS_MUTATION, TOGGLE_PUBLISH_MUTATION, PUBLISH_PAGE_CONTENT_MUTATION, PUBLISH_PAGE_LAYOUT_MUTATION, REVERT_CONTENT_TO_PUBLISHED_MUTATION, REVERT_LAYOUT_TO_PUBLISHED_MUTATION, REMOVE_PAGE_MUTATION, UPDATE_PAGE_LAYOUT_MUTATION, FORMS_QUERY, FORM_BY_ID_QUERY, FORM_SUBMISSIONS_QUERY, FORM_SUBMISSION_BY_ID_QUERY, CREATE_FORM_MUTATION, UPDATE_FORM_MUTATION, DELETE_FORM_MUTATION, UPDATE_FORM_SUBMISSION_STATUS_MUTATION, DELETE_FORM_SUBMISSION_MUTATION, MODEL_DEFINITIONS_QUERY, MODEL_DEFINITIONS_BY_SLUG_INDEX_QUERY, MODEL_DEFINITION_BY_ID_QUERY, MODEL_RECORDS_QUERY, MODEL_RECORD_BY_ID_QUERY, CREATE_MODEL_DEFINITION_MUTATION, UPDATE_MODEL_DEFINITION_MUTATION, DELETE_MODEL_DEFINITION_MUTATION, CREATE_MODEL_RECORD_MUTATION, UPDATE_MODEL_RECORD_MUTATION, UPDATE_MODEL_RECORD_STATUS_MUTATION, DELETE_MODEL_RECORD_MUTATION, IMPORT_MODEL_RECORDS_MUTATION, } from "./queries.js";
|
|
38
|
-
import { responseModeSchema, pageMinimal, pageBlockMinimal, formMinimal, modelMinimal, recordMinimal, jsonText, } from "./responses.js";
|
|
37
|
+
import { PAGES_QUERY, PAGE_BY_ID_QUERY, SITE_CONFIG_QUERY, CURRENT_WORKSPACE_QUERY, MEDIA_ASSETS_QUERY, SAVE_PAGE_MUTATION, PATCH_BLOCK_CONTENT_MUTATION, UPDATE_PAGE_SETTINGS_MUTATION, PAGE_TYPES_QUERY, CREATE_PAGE_TYPE_MUTATION, TOGGLE_PUBLISH_MUTATION, PUBLISH_PAGE_CONTENT_MUTATION, PUBLISH_PAGE_LAYOUT_MUTATION, REVERT_CONTENT_TO_PUBLISHED_MUTATION, REVERT_LAYOUT_TO_PUBLISHED_MUTATION, REMOVE_PAGE_MUTATION, UPDATE_PAGE_LAYOUT_MUTATION, FORMS_QUERY, FORM_BY_ID_QUERY, FORM_SUBMISSIONS_QUERY, FORM_SUBMISSION_BY_ID_QUERY, CREATE_FORM_MUTATION, UPDATE_FORM_MUTATION, DELETE_FORM_MUTATION, UPDATE_FORM_SUBMISSION_STATUS_MUTATION, DELETE_FORM_SUBMISSION_MUTATION, MODEL_DEFINITIONS_QUERY, MODEL_DEFINITIONS_BY_SLUG_INDEX_QUERY, MODEL_DEFINITION_BY_ID_QUERY, MODEL_RECORDS_QUERY, MODEL_RECORD_BY_ID_QUERY, CREATE_MODEL_DEFINITION_MUTATION, UPDATE_MODEL_DEFINITION_MUTATION, DELETE_MODEL_DEFINITION_MUTATION, CREATE_MODEL_RECORD_MUTATION, UPDATE_MODEL_RECORD_MUTATION, UPDATE_MODEL_RECORD_STATUS_MUTATION, DELETE_MODEL_RECORD_MUTATION, IMPORT_MODEL_RECORDS_MUTATION, ORDERS_QUERY, 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, ADMIN_CARTS_QUERY, DISCOUNTS_QUERY, DISCOUNT_BY_ID_QUERY, CREATE_DISCOUNT_MUTATION, 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, PRODUCT_CATALOG_QUERY, BULK_UPDATE_PRODUCT_RECORDS_MUTATION, BULK_DELETE_PRODUCT_RECORDS_MUTATION, } from "./queries.js";
|
|
38
|
+
import { responseModeSchema, pageMinimal, pageBlockMinimal, formMinimal, modelMinimal, recordMinimal, orderMinimal, discountMinimal, jsonText, } from "./responses.js";
|
|
39
39
|
export function createServer(client) {
|
|
40
40
|
const server = new McpServer({
|
|
41
41
|
name: "cmssy",
|
|
@@ -189,8 +189,12 @@ export function createServer(client) {
|
|
|
189
189
|
.preprocess(jsonPreprocess, z.record(z.string(), z.string()))
|
|
190
190
|
.optional()
|
|
191
191
|
.describe("Multilingual SEO description"),
|
|
192
|
+
customFields: z
|
|
193
|
+
.preprocess(jsonPreprocess, z.array(z.object({ fieldKey: z.string(), value: z.unknown() })))
|
|
194
|
+
.optional()
|
|
195
|
+
.describe("Custom field values for the page type's schema: [{ fieldKey, value }]. value is JSON."),
|
|
192
196
|
response: responseModeSchema,
|
|
193
|
-
}, async ({ name, slug, parentId, pageType, displayName, seoTitle, seoDescription, response, }) => {
|
|
197
|
+
}, async ({ name, slug, parentId, pageType, displayName, seoTitle, seoDescription, customFields, response, }) => {
|
|
194
198
|
const input = { name, slug };
|
|
195
199
|
if (parentId)
|
|
196
200
|
input.parentId = parentId;
|
|
@@ -202,6 +206,8 @@ export function createServer(client) {
|
|
|
202
206
|
input.seoTitle = seoTitle;
|
|
203
207
|
if (seoDescription)
|
|
204
208
|
input.seoDescription = seoDescription;
|
|
209
|
+
if (customFields !== undefined)
|
|
210
|
+
input.customFields = customFields;
|
|
205
211
|
const data = await client.query(SAVE_PAGE_MUTATION, {
|
|
206
212
|
input,
|
|
207
213
|
});
|
|
@@ -268,7 +274,7 @@ export function createServer(client) {
|
|
|
268
274
|
});
|
|
269
275
|
return jsonText(response, data.savePage, pageMinimal);
|
|
270
276
|
});
|
|
271
|
-
server.tool("update_page_settings", "Update page metadata: name, slug, display name, SEO fields. Returns a minimal ack by default; pass response='full' for the full mutation response.", {
|
|
277
|
+
server.tool("update_page_settings", "Update page metadata: name, slug, display name, SEO fields, and custom fields. Returns a minimal ack by default; pass response='full' for the full mutation response.", {
|
|
272
278
|
id: z.string().describe("Page ID"),
|
|
273
279
|
name: z.string().optional().describe("Internal page name"),
|
|
274
280
|
slug: z.string().optional().describe("URL slug"),
|
|
@@ -285,8 +291,12 @@ export function createServer(client) {
|
|
|
285
291
|
.optional()
|
|
286
292
|
.describe("Multilingual SEO description"),
|
|
287
293
|
seoKeywords: z.array(z.string()).optional().describe("SEO keywords"),
|
|
294
|
+
customFields: z
|
|
295
|
+
.preprocess(jsonPreprocess, z.array(z.object({ fieldKey: z.string(), value: z.unknown() })))
|
|
296
|
+
.optional()
|
|
297
|
+
.describe("Custom field values: [{ fieldKey, value }]. value is JSON."),
|
|
288
298
|
response: responseModeSchema,
|
|
289
|
-
}, async ({ id, name, slug, displayName, seoTitle, seoDescription, seoKeywords, response, }) => {
|
|
299
|
+
}, async ({ id, name, slug, displayName, seoTitle, seoDescription, seoKeywords, customFields, response, }) => {
|
|
290
300
|
const input = { id };
|
|
291
301
|
if (name !== undefined)
|
|
292
302
|
input.name = name;
|
|
@@ -300,9 +310,84 @@ export function createServer(client) {
|
|
|
300
310
|
input.seoDescription = seoDescription;
|
|
301
311
|
if (seoKeywords !== undefined)
|
|
302
312
|
input.seoKeywords = seoKeywords;
|
|
313
|
+
if (customFields !== undefined)
|
|
314
|
+
input.customFields = customFields;
|
|
303
315
|
const data = await client.query(UPDATE_PAGE_SETTINGS_MUTATION, { input });
|
|
304
316
|
return jsonText(response, data.updatePageSettings, pageMinimal);
|
|
305
317
|
});
|
|
318
|
+
server.tool("list_page_types", "List page types in the workspace with their custom-field schema. Page types define which custom fields a page of that type carries.", {}, async () => {
|
|
319
|
+
const data = await client.query(PAGE_TYPES_QUERY, {});
|
|
320
|
+
return {
|
|
321
|
+
content: [
|
|
322
|
+
{
|
|
323
|
+
type: "text",
|
|
324
|
+
text: JSON.stringify(data.pageTypes, null, 2),
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
};
|
|
328
|
+
});
|
|
329
|
+
server.tool("create_page_type", "Create a page type with a custom-field schema. Pages created with this pageType carry these custom fields (settable via create_page/update_page_settings customFields).", {
|
|
330
|
+
name: z.string().describe("Display name"),
|
|
331
|
+
slug: z
|
|
332
|
+
.string()
|
|
333
|
+
.regex(/^[a-z][a-z0-9-]*$/, "lowercase, URL-safe: start with a letter, then letters/digits/hyphens")
|
|
334
|
+
.describe("URL-safe slug, lowercase (e.g. 'post', 'case-study')"),
|
|
335
|
+
description: z.string().optional().describe("Description"),
|
|
336
|
+
icon: z.string().optional().describe("Lucide icon name"),
|
|
337
|
+
urlPrefix: z
|
|
338
|
+
.string()
|
|
339
|
+
.optional()
|
|
340
|
+
.describe("URL prefix for pages of this type (e.g. 'blog')"),
|
|
341
|
+
allowChildren: z.boolean().optional(),
|
|
342
|
+
fields: z
|
|
343
|
+
.preprocess(jsonPreprocess, z.array(z.object({
|
|
344
|
+
key: z.string().describe("Field key (identifier in customFields)"),
|
|
345
|
+
label: z.string().describe("Human-readable label"),
|
|
346
|
+
type: z
|
|
347
|
+
.enum([
|
|
348
|
+
"string",
|
|
349
|
+
"richtext",
|
|
350
|
+
"number",
|
|
351
|
+
"boolean",
|
|
352
|
+
"date",
|
|
353
|
+
"datetime",
|
|
354
|
+
"email",
|
|
355
|
+
"url",
|
|
356
|
+
"media",
|
|
357
|
+
"relation",
|
|
358
|
+
"select",
|
|
359
|
+
"multiselect",
|
|
360
|
+
])
|
|
361
|
+
.describe("Field type"),
|
|
362
|
+
required: z.boolean().optional(),
|
|
363
|
+
description: z.string().optional(),
|
|
364
|
+
options: z
|
|
365
|
+
.array(z.string())
|
|
366
|
+
.optional()
|
|
367
|
+
.describe("For select/multiselect"),
|
|
368
|
+
defaultValue: z.string().optional(),
|
|
369
|
+
})))
|
|
370
|
+
.optional()
|
|
371
|
+
.describe("Custom-field schema for this page type"),
|
|
372
|
+
response: responseModeSchema,
|
|
373
|
+
}, async ({ name, slug, description, icon, urlPrefix, allowChildren, fields, response, }) => {
|
|
374
|
+
const input = { name, slug };
|
|
375
|
+
if (description !== undefined)
|
|
376
|
+
input.description = description;
|
|
377
|
+
if (icon !== undefined)
|
|
378
|
+
input.icon = icon;
|
|
379
|
+
if (urlPrefix !== undefined)
|
|
380
|
+
input.urlPrefix = urlPrefix;
|
|
381
|
+
if (allowChildren !== undefined)
|
|
382
|
+
input.allowChildren = allowChildren;
|
|
383
|
+
if (fields !== undefined)
|
|
384
|
+
input.fields = fields;
|
|
385
|
+
const data = await client.query(CREATE_PAGE_TYPE_MUTATION, { input });
|
|
386
|
+
return jsonText(response, data.createPageType, (pt) => {
|
|
387
|
+
const p = pt;
|
|
388
|
+
return { id: p.id, name: p.name, slug: p.slug };
|
|
389
|
+
});
|
|
390
|
+
});
|
|
306
391
|
server.tool("publish_page", "Publish a page or re-publish with latest draft changes. Publishes both content and layout axes. Returns a minimal ack by default; pass response='full' for the full mutation response.", {
|
|
307
392
|
pageId: z.string().describe("Page ID to publish"),
|
|
308
393
|
response: responseModeSchema,
|
|
@@ -1690,6 +1775,629 @@ export function createServer(client) {
|
|
|
1690
1775
|
],
|
|
1691
1776
|
};
|
|
1692
1777
|
});
|
|
1778
|
+
// ─── Commerce: Orders ────────────────────────────────────────
|
|
1779
|
+
//
|
|
1780
|
+
// workspaceId is required by every commerce resolver and cross-checked
|
|
1781
|
+
// against the token's workspace context, so it always comes from the
|
|
1782
|
+
// client (the same value sent in the x-workspace-id header) - it is not
|
|
1783
|
+
// a caller-supplied arg. All money fields are integer minor units (cents).
|
|
1784
|
+
const PAYMENT_STATUS = [
|
|
1785
|
+
"unpaid",
|
|
1786
|
+
"partially_paid",
|
|
1787
|
+
"authorized",
|
|
1788
|
+
"paid",
|
|
1789
|
+
"partially_refunded",
|
|
1790
|
+
"refunded",
|
|
1791
|
+
];
|
|
1792
|
+
const FULFILLMENT_STATUS = [
|
|
1793
|
+
"unfulfilled",
|
|
1794
|
+
"partially_fulfilled",
|
|
1795
|
+
"fulfilled",
|
|
1796
|
+
"returned",
|
|
1797
|
+
];
|
|
1798
|
+
const CART_STATUS = [
|
|
1799
|
+
"active",
|
|
1800
|
+
"saved",
|
|
1801
|
+
"quote_requested",
|
|
1802
|
+
"merged",
|
|
1803
|
+
"expired",
|
|
1804
|
+
"abandoned",
|
|
1805
|
+
"ordered",
|
|
1806
|
+
];
|
|
1807
|
+
const DISCOUNT_TYPE = ["percentage", "fixed", "free_shipping"];
|
|
1808
|
+
const orderItemSchema = z
|
|
1809
|
+
.object({
|
|
1810
|
+
recordId: z
|
|
1811
|
+
.string()
|
|
1812
|
+
.optional()
|
|
1813
|
+
.describe("Product record id (ObjectId) the line refers to"),
|
|
1814
|
+
quantity: z.number().int().positive(),
|
|
1815
|
+
variantSelections: z
|
|
1816
|
+
.preprocess(jsonPreprocess, z.record(z.string(), z.unknown()))
|
|
1817
|
+
.optional()
|
|
1818
|
+
.describe("Selected variant options, e.g. {size: 'M', color: 'red'}"),
|
|
1819
|
+
name: z.string().optional().describe("Custom line name (ad-hoc item)"),
|
|
1820
|
+
price: z
|
|
1821
|
+
.number()
|
|
1822
|
+
.int()
|
|
1823
|
+
.optional()
|
|
1824
|
+
.describe("Custom unit price in minor units (ad-hoc item)"),
|
|
1825
|
+
})
|
|
1826
|
+
.refine((item) => item.recordId != null || (item.name != null && item.price != null), {
|
|
1827
|
+
message: "Each item needs a recordId, or both name and price for an ad-hoc line",
|
|
1828
|
+
});
|
|
1829
|
+
server.tool("list_orders", "List orders with optional filters and pagination. Money fields are integer minor units (cents). Pass pipelineStageId='__unassigned__' to list orders with no pipeline stage.", {
|
|
1830
|
+
paymentStatus: z.enum(PAYMENT_STATUS).optional(),
|
|
1831
|
+
fulfillmentStatus: z.enum(FULFILLMENT_STATUS).optional(),
|
|
1832
|
+
customerId: z.string().optional(),
|
|
1833
|
+
search: z.string().optional(),
|
|
1834
|
+
pipelineStageId: z.string().optional(),
|
|
1835
|
+
dateFrom: z
|
|
1836
|
+
.string()
|
|
1837
|
+
.optional()
|
|
1838
|
+
.describe("ISO date-time lower bound (inclusive)"),
|
|
1839
|
+
dateTo: z
|
|
1840
|
+
.string()
|
|
1841
|
+
.optional()
|
|
1842
|
+
.describe("ISO date-time upper bound (inclusive)"),
|
|
1843
|
+
skip: z.number().int().optional().default(0),
|
|
1844
|
+
limit: z.number().int().optional().default(20),
|
|
1845
|
+
}, async (args) => {
|
|
1846
|
+
const data = await client.query(ORDERS_QUERY, { workspaceId: client.workspaceId, ...args });
|
|
1847
|
+
return {
|
|
1848
|
+
content: [
|
|
1849
|
+
{ type: "text", text: JSON.stringify(data.orders, null, 2) },
|
|
1850
|
+
],
|
|
1851
|
+
};
|
|
1852
|
+
});
|
|
1853
|
+
server.tool("get_order", "Get a single order by id, including items, payments, and tax summary.", {
|
|
1854
|
+
id: z.string().describe("Order id"),
|
|
1855
|
+
}, async ({ id }) => {
|
|
1856
|
+
const data = await client.query(ORDER_BY_ID_QUERY, { workspaceId: client.workspaceId, id });
|
|
1857
|
+
if (!data.order) {
|
|
1858
|
+
return {
|
|
1859
|
+
content: [{ type: "text", text: "Order not found" }],
|
|
1860
|
+
isError: true,
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
return {
|
|
1864
|
+
content: [
|
|
1865
|
+
{ type: "text", text: JSON.stringify(data.order, null, 2) },
|
|
1866
|
+
],
|
|
1867
|
+
};
|
|
1868
|
+
});
|
|
1869
|
+
server.tool("get_order_pipeline", "Get the workspace order pipeline (the configurable stages orders move through).", {}, async () => {
|
|
1870
|
+
const data = await client.query(ORDER_PIPELINE_QUERY, { workspaceId: client.workspaceId });
|
|
1871
|
+
return {
|
|
1872
|
+
content: [
|
|
1873
|
+
{
|
|
1874
|
+
type: "text",
|
|
1875
|
+
text: JSON.stringify(data.orderPipeline, null, 2),
|
|
1876
|
+
},
|
|
1877
|
+
],
|
|
1878
|
+
};
|
|
1879
|
+
});
|
|
1880
|
+
server.tool("create_manual_order", "Create a manual (admin-entered) order. Each item references a product recordId or is an ad-hoc line with name+price (minor units). Returns a minimal ack by default; pass response='full' for the full order.", {
|
|
1881
|
+
customerEmail: z.string().describe("Customer email for the order"),
|
|
1882
|
+
customerId: z.string().optional(),
|
|
1883
|
+
items: z.array(orderItemSchema).min(1, "Provide at least 1 item"),
|
|
1884
|
+
response: responseModeSchema,
|
|
1885
|
+
}, async ({ customerEmail, customerId, items, response }) => {
|
|
1886
|
+
const result = await client.query(CREATE_MANUAL_ORDER_MUTATION, {
|
|
1887
|
+
input: {
|
|
1888
|
+
workspaceId: client.workspaceId,
|
|
1889
|
+
customerEmail,
|
|
1890
|
+
customerId,
|
|
1891
|
+
items,
|
|
1892
|
+
},
|
|
1893
|
+
});
|
|
1894
|
+
return jsonText(response, result.createManualOrder, orderMinimal);
|
|
1895
|
+
});
|
|
1896
|
+
server.tool("edit_order", "Replace an order's line items (full replace of the items array). Recomputes totals. Returns a minimal ack by default; pass response='full' for the full order.", {
|
|
1897
|
+
orderId: z.string(),
|
|
1898
|
+
items: z.array(orderItemSchema).min(1, "Provide at least 1 item"),
|
|
1899
|
+
response: responseModeSchema,
|
|
1900
|
+
}, async ({ orderId, items, response }) => {
|
|
1901
|
+
const result = await client.query(EDIT_ORDER_MUTATION, { input: { workspaceId: client.workspaceId, orderId, items } });
|
|
1902
|
+
return jsonText(response, result.editOrder, orderMinimal);
|
|
1903
|
+
});
|
|
1904
|
+
server.tool("update_order_details", "Update order metadata: customer email, internal notes, and shipment tracking. Only provided fields change.", {
|
|
1905
|
+
orderId: z.string(),
|
|
1906
|
+
customerEmail: z.string().optional(),
|
|
1907
|
+
notes: z.string().optional(),
|
|
1908
|
+
trackingNumber: z.string().optional(),
|
|
1909
|
+
trackingCarrier: z.string().optional(),
|
|
1910
|
+
response: responseModeSchema,
|
|
1911
|
+
}, async ({ orderId, response, ...fields }) => {
|
|
1912
|
+
if (Object.keys(fields).length === 0) {
|
|
1913
|
+
return {
|
|
1914
|
+
content: [
|
|
1915
|
+
{
|
|
1916
|
+
type: "text",
|
|
1917
|
+
text: "Provide at least one field to update",
|
|
1918
|
+
},
|
|
1919
|
+
],
|
|
1920
|
+
isError: true,
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
const result = await client.query(UPDATE_ORDER_DETAILS_MUTATION, { input: { workspaceId: client.workspaceId, orderId, ...fields } });
|
|
1924
|
+
return jsonText(response, result.updateOrderDetails, orderMinimal);
|
|
1925
|
+
});
|
|
1926
|
+
server.tool("mark_order_paid", "Record a full payment for an order (does NOT verify with a payment provider - admin/manual reconciliation). amount is in minor units.", {
|
|
1927
|
+
orderId: z.string(),
|
|
1928
|
+
amount: z.number().int().describe("Amount in minor units (cents)"),
|
|
1929
|
+
reference: z.string().describe("Payment reference / receipt id"),
|
|
1930
|
+
provider: z.string().describe("Payment provider label, e.g. 'manual'"),
|
|
1931
|
+
response: responseModeSchema,
|
|
1932
|
+
}, async ({ orderId, amount, reference, provider, response }) => {
|
|
1933
|
+
const result = await client.query(MARK_ORDER_PAID_MUTATION, {
|
|
1934
|
+
input: {
|
|
1935
|
+
workspaceId: client.workspaceId,
|
|
1936
|
+
orderId,
|
|
1937
|
+
amount,
|
|
1938
|
+
reference,
|
|
1939
|
+
provider,
|
|
1940
|
+
},
|
|
1941
|
+
});
|
|
1942
|
+
return jsonText(response, result.markOrderPaid, orderMinimal);
|
|
1943
|
+
});
|
|
1944
|
+
server.tool("record_order_payment", "Record a (possibly partial) payment against an order's outstanding balance. amount is in minor units and capped at the balance due.", {
|
|
1945
|
+
orderId: z.string(),
|
|
1946
|
+
amount: z.number().int().describe("Amount in minor units (cents)"),
|
|
1947
|
+
reference: z.string().describe("Payment reference / receipt id"),
|
|
1948
|
+
provider: z.string().optional(),
|
|
1949
|
+
response: responseModeSchema,
|
|
1950
|
+
}, async ({ orderId, amount, reference, provider, response }) => {
|
|
1951
|
+
const result = await client.query(RECORD_ORDER_PAYMENT_MUTATION, {
|
|
1952
|
+
input: {
|
|
1953
|
+
workspaceId: client.workspaceId,
|
|
1954
|
+
orderId,
|
|
1955
|
+
amount,
|
|
1956
|
+
reference,
|
|
1957
|
+
provider,
|
|
1958
|
+
},
|
|
1959
|
+
});
|
|
1960
|
+
return jsonText(response, result.recordOrderPayment, orderMinimal);
|
|
1961
|
+
});
|
|
1962
|
+
server.tool("refund_order", "Refund an order. Omit amount for a full refund; pass amount (minor units) for a partial refund.", {
|
|
1963
|
+
orderId: z.string(),
|
|
1964
|
+
reference: z.string().describe("Refund reference / receipt id"),
|
|
1965
|
+
amount: z
|
|
1966
|
+
.number()
|
|
1967
|
+
.int()
|
|
1968
|
+
.optional()
|
|
1969
|
+
.describe("Partial refund amount in minor units; omit for full refund"),
|
|
1970
|
+
response: responseModeSchema,
|
|
1971
|
+
}, async ({ orderId, reference, amount, response }) => {
|
|
1972
|
+
const result = await client.query(REFUND_ORDER_MUTATION, {
|
|
1973
|
+
input: {
|
|
1974
|
+
workspaceId: client.workspaceId,
|
|
1975
|
+
orderId,
|
|
1976
|
+
reference,
|
|
1977
|
+
amount,
|
|
1978
|
+
},
|
|
1979
|
+
});
|
|
1980
|
+
return jsonText(response, result.refundOrder, orderMinimal);
|
|
1981
|
+
});
|
|
1982
|
+
server.tool("cancel_order", "Cancel an order.", {
|
|
1983
|
+
orderId: z.string(),
|
|
1984
|
+
response: responseModeSchema,
|
|
1985
|
+
}, async ({ orderId, response }) => {
|
|
1986
|
+
const result = await client.query(CANCEL_ORDER_MUTATION, { input: { workspaceId: client.workspaceId, orderId } });
|
|
1987
|
+
return jsonText(response, result.cancelOrder, orderMinimal);
|
|
1988
|
+
});
|
|
1989
|
+
server.tool("transition_order_fulfillment", "Move an order to a new fulfillment status. Optionally attach tracking when marking fulfilled.", {
|
|
1990
|
+
orderId: z.string(),
|
|
1991
|
+
status: z.enum(FULFILLMENT_STATUS),
|
|
1992
|
+
trackingNumber: z.string().optional(),
|
|
1993
|
+
trackingCarrier: z.string().optional(),
|
|
1994
|
+
response: responseModeSchema,
|
|
1995
|
+
}, async ({ orderId, status, trackingNumber, trackingCarrier, response }) => {
|
|
1996
|
+
const result = await client.query(TRANSITION_ORDER_FULFILLMENT_MUTATION, {
|
|
1997
|
+
input: {
|
|
1998
|
+
workspaceId: client.workspaceId,
|
|
1999
|
+
orderId,
|
|
2000
|
+
status,
|
|
2001
|
+
trackingNumber,
|
|
2002
|
+
trackingCarrier,
|
|
2003
|
+
},
|
|
2004
|
+
});
|
|
2005
|
+
return jsonText(response, result.transitionOrderFulfillment, orderMinimal);
|
|
2006
|
+
});
|
|
2007
|
+
server.tool("set_order_pipeline_stage", "Move an order to a pipeline stage (use get_order_pipeline for valid stage ids).", {
|
|
2008
|
+
orderId: z.string(),
|
|
2009
|
+
stageId: z.string(),
|
|
2010
|
+
response: responseModeSchema,
|
|
2011
|
+
}, async ({ orderId, stageId, response }) => {
|
|
2012
|
+
const result = await client.query(SET_ORDER_PIPELINE_STAGE_MUTATION, { input: { workspaceId: client.workspaceId, orderId, stageId } });
|
|
2013
|
+
return jsonText(response, result.setOrderPipelineStage, orderMinimal);
|
|
2014
|
+
});
|
|
2015
|
+
server.tool("record_order_invoice", "Attach an invoice (number, optional URL and provider) to an order.", {
|
|
2016
|
+
orderId: z.string(),
|
|
2017
|
+
number: z.string().describe("Invoice number"),
|
|
2018
|
+
url: z.string().optional(),
|
|
2019
|
+
provider: z.string().optional(),
|
|
2020
|
+
response: responseModeSchema,
|
|
2021
|
+
}, async ({ orderId, number, url, provider, response }) => {
|
|
2022
|
+
const result = await client.query(RECORD_ORDER_INVOICE_MUTATION, {
|
|
2023
|
+
input: {
|
|
2024
|
+
workspaceId: client.workspaceId,
|
|
2025
|
+
orderId,
|
|
2026
|
+
number,
|
|
2027
|
+
url,
|
|
2028
|
+
provider,
|
|
2029
|
+
},
|
|
2030
|
+
});
|
|
2031
|
+
return jsonText(response, result.recordOrderInvoice, orderMinimal);
|
|
2032
|
+
});
|
|
2033
|
+
// ─── Commerce: Carts ─────────────────────────────────────────
|
|
2034
|
+
server.tool("list_carts", "List shopping carts (admin view) with optional status filter and pagination. totalValue is in minor units.", {
|
|
2035
|
+
status: z.enum(CART_STATUS).optional(),
|
|
2036
|
+
skip: z.number().int().optional().default(0),
|
|
2037
|
+
limit: z.number().int().optional().default(20),
|
|
2038
|
+
}, async (args) => {
|
|
2039
|
+
const data = await client.query(ADMIN_CARTS_QUERY, { workspaceId: client.workspaceId, ...args });
|
|
2040
|
+
return {
|
|
2041
|
+
content: [
|
|
2042
|
+
{
|
|
2043
|
+
type: "text",
|
|
2044
|
+
text: JSON.stringify(data.adminCarts, null, 2),
|
|
2045
|
+
},
|
|
2046
|
+
],
|
|
2047
|
+
};
|
|
2048
|
+
});
|
|
2049
|
+
// ─── Commerce: Discounts ─────────────────────────────────────
|
|
2050
|
+
server.tool("list_discounts", "List discount codes with optional filters and pagination.", {
|
|
2051
|
+
enabled: z.boolean().optional(),
|
|
2052
|
+
type: z.enum(DISCOUNT_TYPE).optional(),
|
|
2053
|
+
search: z.string().optional().describe("Substring match on code"),
|
|
2054
|
+
limit: z.number().int().optional().default(25),
|
|
2055
|
+
offset: z.number().int().optional().default(0),
|
|
2056
|
+
}, async (args) => {
|
|
2057
|
+
const data = await client.query(DISCOUNTS_QUERY, { workspaceId: client.workspaceId, ...args });
|
|
2058
|
+
return {
|
|
2059
|
+
content: [
|
|
2060
|
+
{
|
|
2061
|
+
type: "text",
|
|
2062
|
+
text: JSON.stringify(data.discounts, null, 2),
|
|
2063
|
+
},
|
|
2064
|
+
],
|
|
2065
|
+
};
|
|
2066
|
+
});
|
|
2067
|
+
server.tool("get_discount", "Get a single discount by id.", {
|
|
2068
|
+
id: z.string(),
|
|
2069
|
+
}, async ({ id }) => {
|
|
2070
|
+
const data = await client.query(DISCOUNT_BY_ID_QUERY, { workspaceId: client.workspaceId, id });
|
|
2071
|
+
if (!data.discount) {
|
|
2072
|
+
return {
|
|
2073
|
+
content: [{ type: "text", text: "Discount not found" }],
|
|
2074
|
+
isError: true,
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
return {
|
|
2078
|
+
content: [
|
|
2079
|
+
{
|
|
2080
|
+
type: "text",
|
|
2081
|
+
text: JSON.stringify(data.discount, null, 2),
|
|
2082
|
+
},
|
|
2083
|
+
],
|
|
2084
|
+
};
|
|
2085
|
+
});
|
|
2086
|
+
server.tool("create_discount", "Create a discount code. For type='fixed', value and currency are required (value in minor units). For 'percentage', value is the percent and currency must be omitted. minSubtotal is in minor units. Dates are ISO date-time strings.", {
|
|
2087
|
+
code: z.string(),
|
|
2088
|
+
type: z.enum(DISCOUNT_TYPE),
|
|
2089
|
+
value: z
|
|
2090
|
+
.number()
|
|
2091
|
+
.describe("Percent for 'percentage'; minor units for 'fixed'"),
|
|
2092
|
+
currency: z
|
|
2093
|
+
.string()
|
|
2094
|
+
.optional()
|
|
2095
|
+
.describe("Required for 'fixed'; omit otherwise"),
|
|
2096
|
+
minSubtotal: z
|
|
2097
|
+
.number()
|
|
2098
|
+
.optional()
|
|
2099
|
+
.describe("Minimum order subtotal in minor units"),
|
|
2100
|
+
maxUses: z.number().int().optional(),
|
|
2101
|
+
maxUsesPerUser: z.number().int().optional(),
|
|
2102
|
+
startsAt: z.string().optional().describe("ISO date-time"),
|
|
2103
|
+
endsAt: z.string().optional().describe("ISO date-time"),
|
|
2104
|
+
enabled: z.boolean(),
|
|
2105
|
+
response: responseModeSchema,
|
|
2106
|
+
}, async ({ response, ...input }) => {
|
|
2107
|
+
const currencyError = input.type === "fixed" && !input.currency
|
|
2108
|
+
? "currency is required for type 'fixed'"
|
|
2109
|
+
: input.type !== "fixed" && input.currency
|
|
2110
|
+
? `currency must be omitted for type '${input.type}'`
|
|
2111
|
+
: null;
|
|
2112
|
+
if (currencyError) {
|
|
2113
|
+
return {
|
|
2114
|
+
content: [{ type: "text", text: currencyError }],
|
|
2115
|
+
isError: true,
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
const result = await client.query(CREATE_DISCOUNT_MUTATION, { workspaceId: client.workspaceId, input });
|
|
2119
|
+
return jsonText(response, result.createDiscount, discountMinimal);
|
|
2120
|
+
});
|
|
2121
|
+
server.tool("update_discount", "Update a discount (partial - only provided fields change). code/type/currency become immutable once the discount has been used at least once.", {
|
|
2122
|
+
id: z.string(),
|
|
2123
|
+
code: z.string().optional(),
|
|
2124
|
+
type: z.enum(DISCOUNT_TYPE).optional(),
|
|
2125
|
+
value: z.number().optional(),
|
|
2126
|
+
currency: z.string().optional(),
|
|
2127
|
+
minSubtotal: z.number().optional(),
|
|
2128
|
+
maxUses: z.number().int().optional(),
|
|
2129
|
+
maxUsesPerUser: z.number().int().optional(),
|
|
2130
|
+
startsAt: z.string().optional().describe("ISO date-time"),
|
|
2131
|
+
endsAt: z.string().optional().describe("ISO date-time"),
|
|
2132
|
+
enabled: z.boolean().optional(),
|
|
2133
|
+
response: responseModeSchema,
|
|
2134
|
+
}, async ({ id, response, ...input }) => {
|
|
2135
|
+
if (Object.keys(input).length === 0) {
|
|
2136
|
+
return {
|
|
2137
|
+
content: [
|
|
2138
|
+
{
|
|
2139
|
+
type: "text",
|
|
2140
|
+
text: "Provide at least one field to update",
|
|
2141
|
+
},
|
|
2142
|
+
],
|
|
2143
|
+
isError: true,
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
const result = await client.query(UPDATE_DISCOUNT_MUTATION, { workspaceId: client.workspaceId, id, input });
|
|
2147
|
+
return jsonText(response, result.updateDiscount, discountMinimal);
|
|
2148
|
+
});
|
|
2149
|
+
server.tool("set_discount_enabled", "Enable or disable a discount code.", {
|
|
2150
|
+
id: z.string(),
|
|
2151
|
+
enabled: z.boolean(),
|
|
2152
|
+
response: responseModeSchema,
|
|
2153
|
+
}, async ({ id, enabled, response }) => {
|
|
2154
|
+
const result = await client.query(SET_DISCOUNT_ENABLED_MUTATION, { workspaceId: client.workspaceId, id, enabled });
|
|
2155
|
+
return jsonText(response, result.setDiscountEnabled, discountMinimal);
|
|
2156
|
+
});
|
|
2157
|
+
// ─── Commerce: Products (catalog over model records) ─────────
|
|
2158
|
+
//
|
|
2159
|
+
// Products are stored as records of a Custom Data Model. These tools add
|
|
2160
|
+
// product-aware reads/writes (stock + variants) on top of the generic
|
|
2161
|
+
// record tools. Money fields are integer minor units (cents).
|
|
2162
|
+
const productFilterSchema = z.object({
|
|
2163
|
+
search: z.string().optional(),
|
|
2164
|
+
status: z.string().optional(),
|
|
2165
|
+
stockState: z
|
|
2166
|
+
.enum(["in", "low", "out"])
|
|
2167
|
+
.optional()
|
|
2168
|
+
.describe("in stock / low stock / out of stock"),
|
|
2169
|
+
priceMin: z.number().optional(),
|
|
2170
|
+
priceMax: z.number().optional(),
|
|
2171
|
+
hasVariants: z.boolean().optional(),
|
|
2172
|
+
});
|
|
2173
|
+
server.tool("list_products", "List a product model's catalog with stock and variant info. modelId is the Custom Data Model holding the products. Returns CatalogItem rows (onHand/reserved/available + per-variant stock) plus the workspace lowStockThreshold.", {
|
|
2174
|
+
modelId: z.string().describe("Product model id (ObjectId)"),
|
|
2175
|
+
filter: productFilterSchema.optional(),
|
|
2176
|
+
limit: z.number().int().optional().default(50),
|
|
2177
|
+
offset: z.number().int().optional().default(0),
|
|
2178
|
+
sort: z
|
|
2179
|
+
.string()
|
|
2180
|
+
.optional()
|
|
2181
|
+
.describe("Sort expression, e.g. 'createdAt' or '-updatedAt'"),
|
|
2182
|
+
}, async ({ modelId, filter, limit, offset, sort }) => {
|
|
2183
|
+
const data = await client.query(PRODUCT_CATALOG_QUERY, { modelId, filter, limit, offset, sort });
|
|
2184
|
+
return {
|
|
2185
|
+
content: [
|
|
2186
|
+
{
|
|
2187
|
+
type: "text",
|
|
2188
|
+
text: JSON.stringify(data.productCatalog, null, 2),
|
|
2189
|
+
},
|
|
2190
|
+
],
|
|
2191
|
+
};
|
|
2192
|
+
});
|
|
2193
|
+
// Selection model shared by the bulk product mutations: target an explicit
|
|
2194
|
+
// list of ids, OR everything matching a filter (allMatching=true). One of
|
|
2195
|
+
// the two is required by the backend.
|
|
2196
|
+
const productSelectionSchema = z
|
|
2197
|
+
.object({
|
|
2198
|
+
ids: z
|
|
2199
|
+
.array(z.string())
|
|
2200
|
+
.optional()
|
|
2201
|
+
.describe("Explicit record ids to target"),
|
|
2202
|
+
allMatching: z
|
|
2203
|
+
.boolean()
|
|
2204
|
+
.optional()
|
|
2205
|
+
.describe("Target every record matching filter (ignores ids)"),
|
|
2206
|
+
filter: productFilterSchema.optional(),
|
|
2207
|
+
})
|
|
2208
|
+
.refine((s) => (s.ids?.length ?? 0) > 0 || s.allMatching === true, {
|
|
2209
|
+
message: "Provide a non-empty 'ids' array or set 'allMatching: true'",
|
|
2210
|
+
})
|
|
2211
|
+
.describe("Provide non-empty 'ids' OR 'allMatching: true'");
|
|
2212
|
+
server.tool("bulk_update_products", "Bulk-update selected product records with a single patch applied uniformly (status, set/adjust stock, set/adjust price). Stock fields are units; price fields are minor units. Returns the number of records updated.", {
|
|
2213
|
+
modelId: z.string().describe("Product model id (ObjectId)"),
|
|
2214
|
+
selection: productSelectionSchema,
|
|
2215
|
+
patch: z
|
|
2216
|
+
.object({
|
|
2217
|
+
status: z.string().optional(),
|
|
2218
|
+
setStock: z.number().int().optional().describe("Set absolute stock"),
|
|
2219
|
+
adjustStock: z
|
|
2220
|
+
.number()
|
|
2221
|
+
.int()
|
|
2222
|
+
.optional()
|
|
2223
|
+
.describe("Delta to stock (+/-)"),
|
|
2224
|
+
setPrice: z
|
|
2225
|
+
.number()
|
|
2226
|
+
.int()
|
|
2227
|
+
.optional()
|
|
2228
|
+
.describe("Set absolute price (minor units)"),
|
|
2229
|
+
adjustPrice: z
|
|
2230
|
+
.number()
|
|
2231
|
+
.int()
|
|
2232
|
+
.optional()
|
|
2233
|
+
.describe("Delta to price (minor units, +/-)"),
|
|
2234
|
+
})
|
|
2235
|
+
.describe("Single patch applied to every selected record"),
|
|
2236
|
+
}, async ({ modelId, selection, patch }) => {
|
|
2237
|
+
if (Object.keys(patch).length === 0) {
|
|
2238
|
+
return {
|
|
2239
|
+
content: [
|
|
2240
|
+
{
|
|
2241
|
+
type: "text",
|
|
2242
|
+
text: "Provide at least one patch field (status, set/adjust stock or price)",
|
|
2243
|
+
},
|
|
2244
|
+
],
|
|
2245
|
+
isError: true,
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
const data = await client.query(BULK_UPDATE_PRODUCT_RECORDS_MUTATION, { modelId, selection, patch });
|
|
2249
|
+
return {
|
|
2250
|
+
content: [
|
|
2251
|
+
{
|
|
2252
|
+
type: "text",
|
|
2253
|
+
text: JSON.stringify({ updated: data.bulkUpdateProductRecords }),
|
|
2254
|
+
},
|
|
2255
|
+
],
|
|
2256
|
+
};
|
|
2257
|
+
});
|
|
2258
|
+
server.tool("bulk_delete_products", "Bulk-delete selected product records permanently. Returns the number of records deleted.", {
|
|
2259
|
+
modelId: z.string().describe("Product model id (ObjectId)"),
|
|
2260
|
+
selection: productSelectionSchema,
|
|
2261
|
+
}, async ({ modelId, selection }) => {
|
|
2262
|
+
const data = await client.query(BULK_DELETE_PRODUCT_RECORDS_MUTATION, { modelId, selection });
|
|
2263
|
+
return {
|
|
2264
|
+
content: [
|
|
2265
|
+
{
|
|
2266
|
+
type: "text",
|
|
2267
|
+
text: JSON.stringify({ deleted: data.bulkDeleteProductRecords }),
|
|
2268
|
+
},
|
|
2269
|
+
],
|
|
2270
|
+
};
|
|
2271
|
+
});
|
|
2272
|
+
// ─── Webhooks ────────────────────────────────────────────────
|
|
2273
|
+
//
|
|
2274
|
+
// Outbound event webhooks. Like commerce, workspaceId is required by every
|
|
2275
|
+
// resolver and sourced from the client. createWebhookEndpoint and
|
|
2276
|
+
// rotateWebhookSecret return the signing secret ONCE - it is surfaced in
|
|
2277
|
+
// full and cannot be retrieved again.
|
|
2278
|
+
server.tool("list_webhooks", "List the workspace's webhook endpoints (signing secrets are never returned here).", {}, async () => {
|
|
2279
|
+
const data = await client.query(WEBHOOK_ENDPOINTS_QUERY, { workspaceId: client.workspaceId });
|
|
2280
|
+
return {
|
|
2281
|
+
content: [
|
|
2282
|
+
{
|
|
2283
|
+
type: "text",
|
|
2284
|
+
text: JSON.stringify(data.webhookEndpoints, null, 2),
|
|
2285
|
+
},
|
|
2286
|
+
],
|
|
2287
|
+
};
|
|
2288
|
+
});
|
|
2289
|
+
server.tool("list_webhook_deliveries", "List recent webhook delivery attempts (status: pending/success/failed) for debugging.", {
|
|
2290
|
+
limit: z.number().int().optional().default(50),
|
|
2291
|
+
}, async ({ limit }) => {
|
|
2292
|
+
const data = await client.query(WEBHOOK_DELIVERIES_QUERY, { workspaceId: client.workspaceId, limit });
|
|
2293
|
+
return {
|
|
2294
|
+
content: [
|
|
2295
|
+
{
|
|
2296
|
+
type: "text",
|
|
2297
|
+
text: JSON.stringify(data.webhookDeliveries, null, 2),
|
|
2298
|
+
},
|
|
2299
|
+
],
|
|
2300
|
+
};
|
|
2301
|
+
});
|
|
2302
|
+
server.tool("list_webhook_event_types", "List the event types a webhook can subscribe to (the authoritative allowlist).", {}, async () => {
|
|
2303
|
+
const data = await client.query(WEBHOOK_EVENT_TYPES_QUERY, { workspaceId: client.workspaceId });
|
|
2304
|
+
return {
|
|
2305
|
+
content: [
|
|
2306
|
+
{
|
|
2307
|
+
type: "text",
|
|
2308
|
+
text: JSON.stringify(data.webhookEventTypes, null, 2),
|
|
2309
|
+
},
|
|
2310
|
+
],
|
|
2311
|
+
};
|
|
2312
|
+
});
|
|
2313
|
+
// The backend validates events against its allowlist; list_webhook_event_types
|
|
2314
|
+
// is authoritative. These are the known values at time of writing.
|
|
2315
|
+
const WEBHOOK_EVENTS_HINT = "Event names, e.g. order.created, order.paid, order.refunded, order.canceled, order.fulfilled, order.returned, order.edited, order.pipeline_changed. Use list_webhook_event_types for the authoritative list.";
|
|
2316
|
+
server.tool("create_webhook", "Create a webhook endpoint subscribed to one or more events. The URL must be a public https endpoint. Returns the endpoint AND its signing secret - the secret is shown only once.", {
|
|
2317
|
+
url: z.string().describe("Public https endpoint URL"),
|
|
2318
|
+
events: z
|
|
2319
|
+
.array(z.string())
|
|
2320
|
+
.min(1, "Subscribe to at least 1 event")
|
|
2321
|
+
.describe(WEBHOOK_EVENTS_HINT),
|
|
2322
|
+
description: z.string().optional(),
|
|
2323
|
+
}, async ({ url, events, description }) => {
|
|
2324
|
+
const data = await client.query(CREATE_WEBHOOK_ENDPOINT_MUTATION, {
|
|
2325
|
+
input: { workspaceId: client.workspaceId, url, events, description },
|
|
2326
|
+
});
|
|
2327
|
+
return {
|
|
2328
|
+
content: [
|
|
2329
|
+
{
|
|
2330
|
+
type: "text",
|
|
2331
|
+
text: JSON.stringify(data.createWebhookEndpoint, null, 2),
|
|
2332
|
+
},
|
|
2333
|
+
],
|
|
2334
|
+
};
|
|
2335
|
+
});
|
|
2336
|
+
server.tool("update_webhook", "Update a webhook endpoint (partial - only provided fields change). Pass enabled to enable/disable. Pass description=null to clear it. The signing secret is not returned.", {
|
|
2337
|
+
id: z.string(),
|
|
2338
|
+
url: z.string().optional(),
|
|
2339
|
+
events: z
|
|
2340
|
+
.array(z.string())
|
|
2341
|
+
.min(1)
|
|
2342
|
+
.optional()
|
|
2343
|
+
.describe(WEBHOOK_EVENTS_HINT),
|
|
2344
|
+
enabled: z.boolean().optional(),
|
|
2345
|
+
description: z
|
|
2346
|
+
.string()
|
|
2347
|
+
.nullable()
|
|
2348
|
+
.optional()
|
|
2349
|
+
.describe("New description; null clears it"),
|
|
2350
|
+
}, async ({ id, ...fields }) => {
|
|
2351
|
+
if (Object.keys(fields).length === 0) {
|
|
2352
|
+
return {
|
|
2353
|
+
content: [
|
|
2354
|
+
{
|
|
2355
|
+
type: "text",
|
|
2356
|
+
text: "Provide at least one field to update",
|
|
2357
|
+
},
|
|
2358
|
+
],
|
|
2359
|
+
isError: true,
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
const data = await client.query(UPDATE_WEBHOOK_ENDPOINT_MUTATION, { input: { workspaceId: client.workspaceId, id, ...fields } });
|
|
2363
|
+
return {
|
|
2364
|
+
content: [
|
|
2365
|
+
{
|
|
2366
|
+
type: "text",
|
|
2367
|
+
text: JSON.stringify(data.updateWebhookEndpoint, null, 2),
|
|
2368
|
+
},
|
|
2369
|
+
],
|
|
2370
|
+
};
|
|
2371
|
+
});
|
|
2372
|
+
server.tool("rotate_webhook_secret", "Rotate a webhook endpoint's signing secret. Returns the endpoint and the NEW secret - shown only once.", {
|
|
2373
|
+
id: z.string(),
|
|
2374
|
+
}, async ({ id }) => {
|
|
2375
|
+
const data = await client.query(ROTATE_WEBHOOK_SECRET_MUTATION, { workspaceId: client.workspaceId, id });
|
|
2376
|
+
return {
|
|
2377
|
+
content: [
|
|
2378
|
+
{
|
|
2379
|
+
type: "text",
|
|
2380
|
+
text: JSON.stringify(data.rotateWebhookSecret, null, 2),
|
|
2381
|
+
},
|
|
2382
|
+
],
|
|
2383
|
+
};
|
|
2384
|
+
});
|
|
2385
|
+
server.tool("delete_webhook", "Delete a webhook endpoint permanently.", {
|
|
2386
|
+
id: z.string(),
|
|
2387
|
+
}, async ({ id }) => {
|
|
2388
|
+
const data = await client.query(DELETE_WEBHOOK_ENDPOINT_MUTATION, { workspaceId: client.workspaceId, id });
|
|
2389
|
+
return {
|
|
2390
|
+
content: [
|
|
2391
|
+
{
|
|
2392
|
+
type: "text",
|
|
2393
|
+
text: data.deleteWebhookEndpoint
|
|
2394
|
+
? "Webhook deleted"
|
|
2395
|
+
: "Failed to delete webhook",
|
|
2396
|
+
},
|
|
2397
|
+
],
|
|
2398
|
+
isError: !data.deleteWebhookEndpoint,
|
|
2399
|
+
};
|
|
2400
|
+
});
|
|
1693
2401
|
// ─── Resources ───────────────────────────────────────────────
|
|
1694
2402
|
server.resource("sitemap", "cmssy://sitemap", {
|
|
1695
2403
|
description: "Full page tree as JSON — all pages with hierarchy",
|