@centrali-io/centrali-mcp 4.2.1 → 4.2.3

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.
@@ -129,4 +129,327 @@ function registerComputeTools(server, sdk) {
129
129
  };
130
130
  }
131
131
  }));
132
+ // ── Function CRUD tools ──────────────────────────────────────────
133
+ server.tool("get_function", "Get a compute function by ID. Returns the full function definition including code.", {
134
+ functionId: zod_1.z.string().describe("The compute function ID (UUID)"),
135
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ functionId }) {
136
+ try {
137
+ const result = yield sdk.functions.get(functionId);
138
+ return {
139
+ content: [
140
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
141
+ ],
142
+ };
143
+ }
144
+ catch (error) {
145
+ return {
146
+ content: [
147
+ {
148
+ type: "text",
149
+ text: formatError(error, `getting function '${functionId}'`),
150
+ },
151
+ ],
152
+ isError: true,
153
+ };
154
+ }
155
+ }));
156
+ server.tool("create_function", "Create a new compute function. Compute functions are JavaScript code blocks that run server-side.", {
157
+ name: zod_1.z.string().describe("Display name for the function"),
158
+ slug: zod_1.z.string().describe("URL-safe unique identifier (e.g., 'process-order')"),
159
+ code: zod_1.z.string().describe("JavaScript source code. Must export an async function: module.exports = async (ctx) => { ... }"),
160
+ description: zod_1.z.string().optional().describe("Optional description of what the function does"),
161
+ timeout: zod_1.z.number().optional().describe("Execution timeout in milliseconds (default: 30000)"),
162
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ name, slug, code, description, timeout }) {
163
+ try {
164
+ const input = { name, slug, code };
165
+ if (description !== undefined)
166
+ input.description = description;
167
+ if (timeout !== undefined)
168
+ input.timeout = timeout;
169
+ const result = yield sdk.functions.create(input);
170
+ return {
171
+ content: [
172
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
173
+ ],
174
+ };
175
+ }
176
+ catch (error) {
177
+ return {
178
+ content: [
179
+ {
180
+ type: "text",
181
+ text: formatError(error, `creating function '${slug}'`),
182
+ },
183
+ ],
184
+ isError: true,
185
+ };
186
+ }
187
+ }));
188
+ server.tool("update_function", "Update an existing compute function by ID. Only include the fields you want to change.", {
189
+ functionId: zod_1.z.string().describe("The compute function ID (UUID) to update"),
190
+ name: zod_1.z.string().optional().describe("Updated display name"),
191
+ description: zod_1.z.string().optional().describe("Updated description"),
192
+ code: zod_1.z.string().optional().describe("Updated JavaScript source code"),
193
+ timeout: zod_1.z.number().optional().describe("Updated execution timeout in milliseconds"),
194
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ functionId, name, description, code, timeout }) {
195
+ try {
196
+ const input = {};
197
+ if (name !== undefined)
198
+ input.name = name;
199
+ if (description !== undefined)
200
+ input.description = description;
201
+ if (code !== undefined)
202
+ input.code = code;
203
+ if (timeout !== undefined)
204
+ input.timeout = timeout;
205
+ const result = yield sdk.functions.update(functionId, input);
206
+ return {
207
+ content: [
208
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
209
+ ],
210
+ };
211
+ }
212
+ catch (error) {
213
+ return {
214
+ content: [
215
+ {
216
+ type: "text",
217
+ text: formatError(error, `updating function '${functionId}'`),
218
+ },
219
+ ],
220
+ isError: true,
221
+ };
222
+ }
223
+ }));
224
+ server.tool("delete_function", "Delete a compute function by ID.", {
225
+ functionId: zod_1.z.string().describe("The compute function ID (UUID) to delete"),
226
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ functionId }) {
227
+ try {
228
+ yield sdk.functions.delete(functionId);
229
+ return {
230
+ content: [
231
+ {
232
+ type: "text",
233
+ text: `Function '${functionId}' deleted successfully.`,
234
+ },
235
+ ],
236
+ };
237
+ }
238
+ catch (error) {
239
+ return {
240
+ content: [
241
+ {
242
+ type: "text",
243
+ text: formatError(error, `deleting function '${functionId}'`),
244
+ },
245
+ ],
246
+ isError: true,
247
+ };
248
+ }
249
+ }));
250
+ server.tool("test_function", "Test execute code without saving it as a function. Useful for validating function code before creating or updating.", {
251
+ code: zod_1.z.string().describe("JavaScript code to test. Must export an async function: module.exports = async (ctx) => { ... }"),
252
+ input: zod_1.z
253
+ .record(zod_1.z.string(), zod_1.z.any())
254
+ .optional()
255
+ .describe("Optional input data passed to the function as ctx.input"),
256
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ code, input }) {
257
+ try {
258
+ const testInput = { code };
259
+ if (input !== undefined)
260
+ testInput.input = input;
261
+ const result = yield sdk.functions.testExecute(testInput);
262
+ return {
263
+ content: [
264
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
265
+ ],
266
+ };
267
+ }
268
+ catch (error) {
269
+ return {
270
+ content: [
271
+ {
272
+ type: "text",
273
+ text: formatError(error, "test-executing function"),
274
+ },
275
+ ],
276
+ isError: true,
277
+ };
278
+ }
279
+ }));
280
+ // ── Trigger CRUD tools ───────────────────────────────────────────
281
+ server.tool("get_trigger", "Get a function trigger by ID. Returns full trigger details including execution type and metadata.", {
282
+ triggerId: zod_1.z.string().describe("The trigger ID (UUID)"),
283
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ triggerId }) {
284
+ try {
285
+ const result = yield sdk.triggers.getDetails(triggerId);
286
+ return {
287
+ content: [
288
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
289
+ ],
290
+ };
291
+ }
292
+ catch (error) {
293
+ return {
294
+ content: [
295
+ {
296
+ type: "text",
297
+ text: formatError(error, `getting trigger '${triggerId}'`),
298
+ },
299
+ ],
300
+ isError: true,
301
+ };
302
+ }
303
+ }));
304
+ server.tool("create_trigger", "Create a new function trigger. Triggers define how and when a compute function is executed.", {
305
+ name: zod_1.z.string().describe("Display name for the trigger"),
306
+ functionId: zod_1.z.string().describe("The compute function ID (UUID) to execute"),
307
+ executionType: zod_1.z
308
+ .enum(["on-demand", "event-driven", "scheduled", "webhook"])
309
+ .describe("How the trigger fires: on-demand (manual), event-driven (data events), scheduled (cron), or webhook (HTTP)"),
310
+ description: zod_1.z.string().optional().describe("Optional description"),
311
+ triggerMetadata: zod_1.z
312
+ .record(zod_1.z.string(), zod_1.z.any())
313
+ .optional()
314
+ .describe("Type-specific configuration. For event-driven: { event, recordSlug }. For scheduled: { scheduleType, cronExpression, timezone }. For webhook: auto-generated URL."),
315
+ enabled: zod_1.z.boolean().optional().describe("Whether the trigger is enabled (default: true)"),
316
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ name, functionId, executionType, description, triggerMetadata, enabled }) {
317
+ try {
318
+ const input = { name, functionId, executionType };
319
+ if (description !== undefined)
320
+ input.description = description;
321
+ if (triggerMetadata !== undefined)
322
+ input.triggerMetadata = triggerMetadata;
323
+ if (enabled !== undefined)
324
+ input.enabled = enabled;
325
+ const result = yield sdk.triggers.create(input);
326
+ return {
327
+ content: [
328
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
329
+ ],
330
+ };
331
+ }
332
+ catch (error) {
333
+ return {
334
+ content: [
335
+ {
336
+ type: "text",
337
+ text: formatError(error, `creating trigger '${name}'`),
338
+ },
339
+ ],
340
+ isError: true,
341
+ };
342
+ }
343
+ }));
344
+ server.tool("update_trigger", "Update an existing function trigger by ID. Only include the fields you want to change.", {
345
+ triggerId: zod_1.z.string().describe("The trigger ID (UUID) to update"),
346
+ name: zod_1.z.string().optional().describe("Updated display name"),
347
+ description: zod_1.z.string().optional().describe("Updated description"),
348
+ enabled: zod_1.z.boolean().optional().describe("Enable or disable the trigger"),
349
+ triggerMetadata: zod_1.z
350
+ .record(zod_1.z.string(), zod_1.z.any())
351
+ .optional()
352
+ .describe("Updated type-specific configuration"),
353
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ triggerId, name, description, enabled, triggerMetadata }) {
354
+ try {
355
+ const input = {};
356
+ if (name !== undefined)
357
+ input.name = name;
358
+ if (description !== undefined)
359
+ input.description = description;
360
+ if (enabled !== undefined)
361
+ input.enabled = enabled;
362
+ if (triggerMetadata !== undefined)
363
+ input.triggerMetadata = triggerMetadata;
364
+ const result = yield sdk.triggers.update(triggerId, input);
365
+ return {
366
+ content: [
367
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
368
+ ],
369
+ };
370
+ }
371
+ catch (error) {
372
+ return {
373
+ content: [
374
+ {
375
+ type: "text",
376
+ text: formatError(error, `updating trigger '${triggerId}'`),
377
+ },
378
+ ],
379
+ isError: true,
380
+ };
381
+ }
382
+ }));
383
+ server.tool("delete_trigger", "Delete a function trigger by ID.", {
384
+ triggerId: zod_1.z.string().describe("The trigger ID (UUID) to delete"),
385
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ triggerId }) {
386
+ try {
387
+ yield sdk.triggers.delete(triggerId);
388
+ return {
389
+ content: [
390
+ {
391
+ type: "text",
392
+ text: `Trigger '${triggerId}' deleted successfully.`,
393
+ },
394
+ ],
395
+ };
396
+ }
397
+ catch (error) {
398
+ return {
399
+ content: [
400
+ {
401
+ type: "text",
402
+ text: formatError(error, `deleting trigger '${triggerId}'`),
403
+ },
404
+ ],
405
+ isError: true,
406
+ };
407
+ }
408
+ }));
409
+ server.tool("pause_trigger", "Pause a function trigger. Paused triggers will not fire until resumed.", {
410
+ triggerId: zod_1.z.string().describe("The trigger ID (UUID) to pause"),
411
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ triggerId }) {
412
+ try {
413
+ const result = yield sdk.triggers.pauseTrigger(triggerId);
414
+ return {
415
+ content: [
416
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
417
+ ],
418
+ };
419
+ }
420
+ catch (error) {
421
+ return {
422
+ content: [
423
+ {
424
+ type: "text",
425
+ text: formatError(error, `pausing trigger '${triggerId}'`),
426
+ },
427
+ ],
428
+ isError: true,
429
+ };
430
+ }
431
+ }));
432
+ server.tool("resume_trigger", "Resume a paused function trigger. The trigger will start firing again on matching events or schedules.", {
433
+ triggerId: zod_1.z.string().describe("The trigger ID (UUID) to resume"),
434
+ }, (_a) => __awaiter(this, [_a], void 0, function* ({ triggerId }) {
435
+ try {
436
+ const result = yield sdk.triggers.resumeTrigger(triggerId);
437
+ return {
438
+ content: [
439
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
440
+ ],
441
+ };
442
+ }
443
+ catch (error) {
444
+ return {
445
+ content: [
446
+ {
447
+ type: "text",
448
+ text: formatError(error, `resuming trigger '${triggerId}'`),
449
+ },
450
+ ],
451
+ isError: true,
452
+ };
453
+ }
454
+ }));
132
455
  }
@@ -35,6 +35,9 @@ function registerDescribeTools(server) {
35
35
  tools: [
36
36
  "list_collections",
37
37
  "get_collection",
38
+ "create_collection",
39
+ "update_collection",
40
+ "delete_collection",
38
41
  "list_structures (deprecated)",
39
42
  "get_structure (deprecated)",
40
43
  ],
@@ -61,12 +64,35 @@ function registerDescribeTools(server) {
61
64
  compute: {
62
65
  summary: "Server-side JavaScript functions with triggers (on-demand, event-driven, scheduled, webhook).",
63
66
  describeWith: "describe_compute",
64
- tools: ["list_functions", "list_triggers", "invoke_trigger"],
67
+ tools: [
68
+ "list_functions",
69
+ "get_function",
70
+ "create_function",
71
+ "update_function",
72
+ "delete_function",
73
+ "test_function",
74
+ "list_triggers",
75
+ "get_trigger",
76
+ "create_trigger",
77
+ "update_trigger",
78
+ "delete_trigger",
79
+ "invoke_trigger",
80
+ "pause_trigger",
81
+ "resume_trigger",
82
+ ],
65
83
  },
66
84
  smart_queries: {
67
85
  summary: "Reusable, parameterized queries defined in the console. Support {{variable}} substitution.",
68
86
  describeWith: "describe_smart_queries",
69
- tools: ["list_smart_queries", "execute_smart_query"],
87
+ tools: [
88
+ "list_smart_queries",
89
+ "get_smart_query",
90
+ "create_smart_query",
91
+ "update_smart_query",
92
+ "delete_smart_query",
93
+ "execute_smart_query",
94
+ "test_smart_query",
95
+ ],
70
96
  },
71
97
  orchestrations: {
72
98
  summary: "Multi-step workflows that chain compute functions. Support sequential execution with input/output passing between steps.",
@@ -74,6 +100,11 @@ function registerDescribeTools(server) {
74
100
  tools: [
75
101
  "list_orchestrations",
76
102
  "get_orchestration",
103
+ "create_orchestration",
104
+ "update_orchestration",
105
+ "delete_orchestration",
106
+ "activate_orchestration",
107
+ "pause_orchestration",
77
108
  "trigger_orchestration",
78
109
  "list_orchestration_runs",
79
110
  "get_orchestration_run",
@@ -153,34 +184,59 @@ function registerDescribeTools(server) {
153
184
  },
154
185
  property_shape: {
155
186
  name: "string — field name (e.g., 'email', 'totalAmount')",
156
- type: "string — the field data type (see field_types below)",
187
+ type: "string — one of: string, number, boolean, datetime, array, object, reference",
188
+ description: "string | null — optional human-readable description",
157
189
  required: "boolean — whether the field is mandatory",
158
- unique: "boolean — whether values must be unique across records",
159
- defaultValue: "any | null default value for new records",
160
- description: "string | null — field description",
161
- config: "object | null — type-specific configuration (e.g., min/max for numbers, options for select)",
190
+ nullable: "boolean — whether the field accepts null values",
191
+ isUnique: "boolean whether values must be unique across records",
192
+ immutable: "boolean — field cannot be changed after creation",
193
+ default: "any | null — default value for new records",
194
+ enum: "any[] | null — restrict to specific allowed values",
162
195
  },
163
196
  field_types: {
164
- text: "Short text string",
165
- long_text: "Multi-line text / rich text",
166
- number: "Numeric value (integer or decimal). Config: { min, max, precision }",
167
- boolean: "True/false toggle",
168
- date: "Date value (ISO 8601)",
169
- datetime: "Date and time value (ISO 8601 with time)",
170
- email: "Email address (validated format)",
171
- url: "URL (validated format)",
172
- select: "Single choice from predefined options. Config: { options: [{ label, value }] }",
173
- multi_select: "Multiple choices from predefined options. Config: { options: [{ label, value }] }",
174
- reference: "Foreign key to another collection. Config: { referenceSlug: 'other-collection-slug' }",
175
- file: "File attachment (stored in Azure Blob Storage)",
176
- json: "Arbitrary JSON object",
177
- auto_increment: "Auto-incrementing integer (read-only)",
197
+ string: "Text field. Options: minLength, maxLength, pattern (regex), renderAs ('textarea'|'secret'|'color'|'code'|'html'|'markdown'), enum (string[]), not (disallowed values), isSecret (boolean). Use enum for select-like behavior.",
198
+ number: "Numeric field (integer or decimal). Options: minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, enum (number[]), autoIncrement ({ startAt, incrementBy? }).",
199
+ boolean: "True/false toggle. Options: default (boolean), enum ([true]|[false]).",
200
+ datetime: "ISO 8601 date/time field. Options: earliestDate, latestDate, exclusiveEarliest, exclusiveLatest, default (ISO string), enum (ISO string[]).",
201
+ array: "Array of typed items. Required: items: { type: 'string'|'number'|'boolean'|'datetime'|'object' }. Options: minItems, maxItems, uniqueItems, itemSchema (PropertyDefinition[] when items.type='object').",
202
+ object: "Nested object. Options: properties (PropertyDefinition[]), requiredProperties (string[]), strictProperties (boolean — reject unknown fields).",
203
+ reference: "Foreign key to another collection. Required: target (collection recordSlug), relationship ('many-to-one'|'one-to-one'|'many-to-many'), onDelete ('restrict'|'cascade'|'set_null'). Options: targetField (default: 'id'), displayField, nullable (required if onDelete='set_null').",
204
+ },
205
+ examples: {
206
+ string_with_enum: '{ "name": "status", "type": "string", "required": true, "enum": ["active", "inactive", "archived"] }',
207
+ number_with_range: '{ "name": "price", "type": "number", "minimum": 0, "maximum": 10000 }',
208
+ reference: '{ "name": "categoryId", "type": "reference", "target": "categories", "relationship": "many-to-one", "onDelete": "restrict" }',
209
+ array_of_strings: '{ "name": "tags", "type": "array", "items": { "type": "string" } }',
210
+ nested_object: '{ "name": "address", "type": "object", "properties": [{ "name": "street", "type": "string" }, { "name": "city", "type": "string" }] }',
211
+ },
212
+ crud_tools: {
213
+ create_collection: {
214
+ description: "Create a new collection with name, slug, properties, and optional settings",
215
+ required_params: ["name", "slug"],
216
+ optional_params: ["description", "properties", "enableVersioning", "schemaDiscoveryMode", "tags"],
217
+ },
218
+ update_collection: {
219
+ description: "Update an existing collection by ID. Partial updates supported.",
220
+ required_params: ["collectionId"],
221
+ optional_params: ["name", "description", "properties", "enableVersioning", "tags", "defaultTtlSeconds"],
222
+ },
223
+ delete_collection: {
224
+ description: "Delete a collection by ID. Permanently removes the schema and all records.",
225
+ required_params: ["collectionId"],
226
+ },
178
227
  },
179
228
  tips: [
180
229
  "Use list_collections to see all collections in the workspace",
181
230
  "Use get_collection with a recordSlug to see the full property definitions",
182
231
  "The recordSlug is what you use in query_records, create_record, etc.",
183
232
  "Reference fields create relationships between collections — use 'expand' parameter in queries to join them",
233
+ "Use create_collection to define new data schemas with typed properties",
234
+ "Properties array defines the fields — each needs at minimum a name and type",
235
+ "Use schemaDiscoveryMode: 'flexible' to auto-add new fields when records are created",
236
+ "For enum/select behavior, use type 'string' with an 'enum' array — there is no 'select' type",
237
+ "For multi-line text, use type 'string' with renderAs: 'textarea'",
238
+ "For JSON blobs, use type 'object' with strictProperties: false",
239
+ "Property options go at the top level of the property object, NOT nested inside a 'config' key",
184
240
  ],
185
241
  }, null, 2),
186
242
  },
@@ -355,12 +411,72 @@ function registerDescribeTools(server) {
355
411
  status: "'active' | 'paused'",
356
412
  config: "object — type-specific configuration (cron, event filters, etc.)",
357
413
  },
414
+ function_crud: {
415
+ get_function: {
416
+ description: "Get a compute function by ID, including its code",
417
+ required_params: ["functionId"],
418
+ },
419
+ create_function: {
420
+ description: "Create a new compute function",
421
+ required_params: ["name", "slug", "code"],
422
+ optional_params: ["description", "timeout"],
423
+ code_format: "module.exports = async (ctx) => { /* your code */ return result; }",
424
+ },
425
+ update_function: {
426
+ description: "Update an existing function. Partial updates supported.",
427
+ required_params: ["functionId"],
428
+ optional_params: ["name", "description", "code", "timeout"],
429
+ },
430
+ delete_function: {
431
+ description: "Delete a compute function by ID",
432
+ required_params: ["functionId"],
433
+ },
434
+ test_function: {
435
+ description: "Test execute code without saving. Returns output, duration, and logs.",
436
+ required_params: ["code"],
437
+ optional_params: ["input"],
438
+ },
439
+ },
440
+ trigger_crud: {
441
+ get_trigger: {
442
+ description: "Get any trigger by ID (all execution types)",
443
+ required_params: ["triggerId"],
444
+ },
445
+ create_trigger: {
446
+ description: "Create a new function trigger",
447
+ required_params: ["name", "functionId", "executionType"],
448
+ optional_params: ["description", "triggerMetadata", "enabled"],
449
+ triggerMetadata_examples: {
450
+ "event-driven": { event: "record.created", recordSlug: "orders" },
451
+ scheduled: { scheduleType: "cron", cronExpression: "0 9 * * *", timezone: "America/New_York" },
452
+ },
453
+ },
454
+ update_trigger: {
455
+ description: "Update an existing trigger. Partial updates supported.",
456
+ required_params: ["triggerId"],
457
+ optional_params: ["name", "description", "enabled", "triggerMetadata"],
458
+ },
459
+ delete_trigger: {
460
+ description: "Delete a trigger by ID",
461
+ required_params: ["triggerId"],
462
+ },
463
+ pause_trigger: {
464
+ description: "Pause a trigger to temporarily disable it",
465
+ required_params: ["triggerId"],
466
+ },
467
+ resume_trigger: {
468
+ description: "Resume a paused trigger",
469
+ required_params: ["triggerId"],
470
+ },
471
+ },
358
472
  tips: [
359
473
  "Use list_functions to see all available functions",
360
474
  "Use list_triggers to see how functions are wired to execution events",
361
475
  "Only on-demand triggers can be invoked via invoke_trigger — other types fire automatically",
362
476
  "Functions receive the trigger payload in their execution context",
363
- "Triggers can be paused (status: 'paused') without deleting them",
477
+ "Use pause_trigger/resume_trigger to temporarily disable triggers without deleting them",
478
+ "Use test_function to validate code before creating or updating a function",
479
+ "Use create_function + create_trigger together to set up a complete compute pipeline",
364
480
  ],
365
481
  }, null, 2),
366
482
  },
@@ -398,11 +514,43 @@ function registerDescribeTools(server) {
398
514
  },
399
515
  note: "Variable values are always passed as strings — the query engine handles type coercion",
400
516
  },
517
+ crud_tools: {
518
+ get_smart_query: {
519
+ description: "Get a smart query by ID, including its full definition",
520
+ required_params: ["recordSlug", "queryId"],
521
+ },
522
+ create_smart_query: {
523
+ description: "Create a new smart query for a collection",
524
+ required_params: ["recordSlug", "name", "queryDefinition"],
525
+ optional_params: ["description"],
526
+ queryDefinition_example: {
527
+ where: { status: { "$eq": "active" } },
528
+ sort: [{ field: "createdAt", direction: "desc" }],
529
+ limit: 100,
530
+ },
531
+ },
532
+ update_smart_query: {
533
+ description: "Update an existing smart query. Partial updates supported.",
534
+ required_params: ["recordSlug", "queryId"],
535
+ optional_params: ["name", "description", "queryDefinition"],
536
+ },
537
+ delete_smart_query: {
538
+ description: "Delete a smart query by ID",
539
+ required_params: ["recordSlug", "queryId"],
540
+ },
541
+ test_smart_query: {
542
+ description: "Test execute a query definition without saving it. Preview results before creating.",
543
+ required_params: ["recordSlug", "queryDefinition"],
544
+ optional_params: ["variables"],
545
+ },
546
+ },
401
547
  tips: [
402
548
  "Use list_smart_queries to discover available queries (optionally filter by collection slug)",
403
549
  "Smart queries return the same result shape as query_records",
404
550
  "Variables are defined when creating the query in the console — check the query's variables array to see what's expected",
405
551
  "Prefer smart queries over ad-hoc query_records filters for complex/reusable logic",
552
+ "Use test_smart_query to validate query syntax and preview results before saving",
553
+ "Use create_smart_query to save reusable queries that can be executed with execute_smart_query",
406
554
  ],
407
555
  }, null, 2),
408
556
  },
@@ -453,8 +601,42 @@ function registerDescribeTools(server) {
453
601
  startedAt: "ISO 8601 datetime | null",
454
602
  completedAt: "ISO 8601 datetime | null",
455
603
  },
604
+ crud_tools: {
605
+ create_orchestration: {
606
+ description: "Create a new orchestration workflow",
607
+ required_params: ["slug", "name", "trigger", "steps"],
608
+ optional_params: ["description"],
609
+ trigger_example: { type: "on-demand" },
610
+ step_example: {
611
+ id: "validate",
612
+ type: "compute",
613
+ functionId: "func-uuid",
614
+ onSuccess: { nextStepId: "process" },
615
+ },
616
+ },
617
+ update_orchestration: {
618
+ description: "Update an existing orchestration. Partial updates supported.",
619
+ required_params: ["orchestrationId"],
620
+ optional_params: ["name", "description", "status", "trigger", "steps"],
621
+ },
622
+ delete_orchestration: {
623
+ description: "Delete an orchestration and all its runs",
624
+ required_params: ["orchestrationId"],
625
+ },
626
+ activate_orchestration: {
627
+ description: "Set orchestration status to 'active' so it can be triggered",
628
+ required_params: ["orchestrationId"],
629
+ },
630
+ pause_orchestration: {
631
+ description: "Set orchestration status to 'paused' to prevent triggering",
632
+ required_params: ["orchestrationId"],
633
+ },
634
+ },
456
635
  tips: [
457
636
  "Use list_orchestrations to see available workflows",
637
+ "Use create_orchestration to define new multi-step workflows",
638
+ "Use activate_orchestration to enable a draft orchestration for triggering",
639
+ "Use pause_orchestration to temporarily disable an orchestration without deleting it",
458
640
  "Use trigger_orchestration with an input object to start a run",
459
641
  "Use get_orchestration_run with includeSteps=true to see step-by-step execution details",
460
642
  "Orchestrations must be 'active' to be triggered — draft orchestrations cannot run",