@cmssy/mcp-server 0.10.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/queries.d.ts +29 -0
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +371 -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 +625 -2
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
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, 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, } 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",
|
|
@@ -1775,6 +1775,629 @@ export function createServer(client) {
|
|
|
1775
1775
|
],
|
|
1776
1776
|
};
|
|
1777
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
|
+
});
|
|
1778
2401
|
// ─── Resources ───────────────────────────────────────────────
|
|
1779
2402
|
server.resource("sitemap", "cmssy://sitemap", {
|
|
1780
2403
|
description: "Full page tree as JSON — all pages with hierarchy",
|