@centrali-io/centrali-mcp 4.2.1 → 4.2.2

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.
@@ -138,4 +138,375 @@ export function registerComputeTools(server: McpServer, sdk: CentraliSDK) {
138
138
  }
139
139
  }
140
140
  );
141
+
142
+ // ── Function CRUD tools ──────────────────────────────────────────
143
+
144
+ server.tool(
145
+ "get_function",
146
+ "Get a compute function by ID. Returns the full function definition including code.",
147
+ {
148
+ functionId: z.string().describe("The compute function ID (UUID)"),
149
+ },
150
+ async ({ functionId }) => {
151
+ try {
152
+ const result = await sdk.functions.get(functionId);
153
+ return {
154
+ content: [
155
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
156
+ ],
157
+ };
158
+ } catch (error: unknown) {
159
+ return {
160
+ content: [
161
+ {
162
+ type: "text",
163
+ text: formatError(error, `getting function '${functionId}'`),
164
+ },
165
+ ],
166
+ isError: true,
167
+ };
168
+ }
169
+ }
170
+ );
171
+
172
+ server.tool(
173
+ "create_function",
174
+ "Create a new compute function. Compute functions are JavaScript code blocks that run server-side.",
175
+ {
176
+ name: z.string().describe("Display name for the function"),
177
+ slug: z.string().describe("URL-safe unique identifier (e.g., 'process-order')"),
178
+ code: z.string().describe("JavaScript source code. Must export an async function: module.exports = async (ctx) => { ... }"),
179
+ description: z.string().optional().describe("Optional description of what the function does"),
180
+ timeout: z.number().optional().describe("Execution timeout in milliseconds (default: 30000)"),
181
+ },
182
+ async ({ name, slug, code, description, timeout }) => {
183
+ try {
184
+ const input: Record<string, any> = { name, slug, code };
185
+ if (description !== undefined) input.description = description;
186
+ if (timeout !== undefined) input.timeout = timeout;
187
+
188
+ const result = await sdk.functions.create(input as any);
189
+ return {
190
+ content: [
191
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
192
+ ],
193
+ };
194
+ } catch (error: unknown) {
195
+ return {
196
+ content: [
197
+ {
198
+ type: "text",
199
+ text: formatError(error, `creating function '${slug}'`),
200
+ },
201
+ ],
202
+ isError: true,
203
+ };
204
+ }
205
+ }
206
+ );
207
+
208
+ server.tool(
209
+ "update_function",
210
+ "Update an existing compute function by ID. Only include the fields you want to change.",
211
+ {
212
+ functionId: z.string().describe("The compute function ID (UUID) to update"),
213
+ name: z.string().optional().describe("Updated display name"),
214
+ description: z.string().optional().describe("Updated description"),
215
+ code: z.string().optional().describe("Updated JavaScript source code"),
216
+ timeout: z.number().optional().describe("Updated execution timeout in milliseconds"),
217
+ },
218
+ async ({ functionId, name, description, code, timeout }) => {
219
+ try {
220
+ const input: Record<string, any> = {};
221
+ if (name !== undefined) input.name = name;
222
+ if (description !== undefined) input.description = description;
223
+ if (code !== undefined) input.code = code;
224
+ if (timeout !== undefined) input.timeout = timeout;
225
+
226
+ const result = await sdk.functions.update(functionId, input as any);
227
+ return {
228
+ content: [
229
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
230
+ ],
231
+ };
232
+ } catch (error: unknown) {
233
+ return {
234
+ content: [
235
+ {
236
+ type: "text",
237
+ text: formatError(error, `updating function '${functionId}'`),
238
+ },
239
+ ],
240
+ isError: true,
241
+ };
242
+ }
243
+ }
244
+ );
245
+
246
+ server.tool(
247
+ "delete_function",
248
+ "Delete a compute function by ID.",
249
+ {
250
+ functionId: z.string().describe("The compute function ID (UUID) to delete"),
251
+ },
252
+ async ({ functionId }) => {
253
+ try {
254
+ await sdk.functions.delete(functionId);
255
+ return {
256
+ content: [
257
+ {
258
+ type: "text",
259
+ text: `Function '${functionId}' deleted successfully.`,
260
+ },
261
+ ],
262
+ };
263
+ } catch (error: unknown) {
264
+ return {
265
+ content: [
266
+ {
267
+ type: "text",
268
+ text: formatError(error, `deleting function '${functionId}'`),
269
+ },
270
+ ],
271
+ isError: true,
272
+ };
273
+ }
274
+ }
275
+ );
276
+
277
+ server.tool(
278
+ "test_function",
279
+ "Test execute code without saving it as a function. Useful for validating function code before creating or updating.",
280
+ {
281
+ code: z.string().describe("JavaScript code to test. Must export an async function: module.exports = async (ctx) => { ... }"),
282
+ input: z
283
+ .record(z.string(), z.any())
284
+ .optional()
285
+ .describe("Optional input data passed to the function as ctx.input"),
286
+ },
287
+ async ({ code, input }) => {
288
+ try {
289
+ const testInput: Record<string, any> = { code };
290
+ if (input !== undefined) testInput.input = input;
291
+
292
+ const result = await sdk.functions.testExecute(testInput as any);
293
+ return {
294
+ content: [
295
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
296
+ ],
297
+ };
298
+ } catch (error: unknown) {
299
+ return {
300
+ content: [
301
+ {
302
+ type: "text",
303
+ text: formatError(error, "test-executing function"),
304
+ },
305
+ ],
306
+ isError: true,
307
+ };
308
+ }
309
+ }
310
+ );
311
+
312
+ // ── Trigger CRUD tools ───────────────────────────────────────────
313
+
314
+ server.tool(
315
+ "get_trigger",
316
+ "Get a function trigger by ID. Returns full trigger details including execution type and metadata.",
317
+ {
318
+ triggerId: z.string().describe("The trigger ID (UUID)"),
319
+ },
320
+ async ({ triggerId }) => {
321
+ try {
322
+ const result = await sdk.triggers.getDetails(triggerId);
323
+ return {
324
+ content: [
325
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
326
+ ],
327
+ };
328
+ } catch (error: unknown) {
329
+ return {
330
+ content: [
331
+ {
332
+ type: "text",
333
+ text: formatError(error, `getting trigger '${triggerId}'`),
334
+ },
335
+ ],
336
+ isError: true,
337
+ };
338
+ }
339
+ }
340
+ );
341
+
342
+ server.tool(
343
+ "create_trigger",
344
+ "Create a new function trigger. Triggers define how and when a compute function is executed.",
345
+ {
346
+ name: z.string().describe("Display name for the trigger"),
347
+ functionId: z.string().describe("The compute function ID (UUID) to execute"),
348
+ executionType: z
349
+ .enum(["on-demand", "event-driven", "scheduled", "webhook"])
350
+ .describe("How the trigger fires: on-demand (manual), event-driven (data events), scheduled (cron), or webhook (HTTP)"),
351
+ description: z.string().optional().describe("Optional description"),
352
+ triggerMetadata: z
353
+ .record(z.string(), z.any())
354
+ .optional()
355
+ .describe("Type-specific configuration. For event-driven: { event, recordSlug }. For scheduled: { scheduleType, cronExpression, timezone }. For webhook: auto-generated URL."),
356
+ enabled: z.boolean().optional().describe("Whether the trigger is enabled (default: true)"),
357
+ },
358
+ async ({ name, functionId, executionType, description, triggerMetadata, enabled }) => {
359
+ try {
360
+ const input: Record<string, any> = { name, functionId, executionType };
361
+ if (description !== undefined) input.description = description;
362
+ if (triggerMetadata !== undefined) input.triggerMetadata = triggerMetadata;
363
+ if (enabled !== undefined) input.enabled = enabled;
364
+
365
+ const result = await sdk.triggers.create(input as any);
366
+ return {
367
+ content: [
368
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
369
+ ],
370
+ };
371
+ } catch (error: unknown) {
372
+ return {
373
+ content: [
374
+ {
375
+ type: "text",
376
+ text: formatError(error, `creating trigger '${name}'`),
377
+ },
378
+ ],
379
+ isError: true,
380
+ };
381
+ }
382
+ }
383
+ );
384
+
385
+ server.tool(
386
+ "update_trigger",
387
+ "Update an existing function trigger by ID. Only include the fields you want to change.",
388
+ {
389
+ triggerId: z.string().describe("The trigger ID (UUID) to update"),
390
+ name: z.string().optional().describe("Updated display name"),
391
+ description: z.string().optional().describe("Updated description"),
392
+ enabled: z.boolean().optional().describe("Enable or disable the trigger"),
393
+ triggerMetadata: z
394
+ .record(z.string(), z.any())
395
+ .optional()
396
+ .describe("Updated type-specific configuration"),
397
+ },
398
+ async ({ triggerId, name, description, enabled, triggerMetadata }) => {
399
+ try {
400
+ const input: Record<string, any> = {};
401
+ if (name !== undefined) input.name = name;
402
+ if (description !== undefined) input.description = description;
403
+ if (enabled !== undefined) input.enabled = enabled;
404
+ if (triggerMetadata !== undefined) input.triggerMetadata = triggerMetadata;
405
+
406
+ const result = await sdk.triggers.update(triggerId, input as any);
407
+ return {
408
+ content: [
409
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
410
+ ],
411
+ };
412
+ } catch (error: unknown) {
413
+ return {
414
+ content: [
415
+ {
416
+ type: "text",
417
+ text: formatError(error, `updating trigger '${triggerId}'`),
418
+ },
419
+ ],
420
+ isError: true,
421
+ };
422
+ }
423
+ }
424
+ );
425
+
426
+ server.tool(
427
+ "delete_trigger",
428
+ "Delete a function trigger by ID.",
429
+ {
430
+ triggerId: z.string().describe("The trigger ID (UUID) to delete"),
431
+ },
432
+ async ({ triggerId }) => {
433
+ try {
434
+ await sdk.triggers.delete(triggerId);
435
+ return {
436
+ content: [
437
+ {
438
+ type: "text",
439
+ text: `Trigger '${triggerId}' deleted successfully.`,
440
+ },
441
+ ],
442
+ };
443
+ } catch (error: unknown) {
444
+ return {
445
+ content: [
446
+ {
447
+ type: "text",
448
+ text: formatError(error, `deleting trigger '${triggerId}'`),
449
+ },
450
+ ],
451
+ isError: true,
452
+ };
453
+ }
454
+ }
455
+ );
456
+
457
+ server.tool(
458
+ "pause_trigger",
459
+ "Pause a function trigger. Paused triggers will not fire until resumed.",
460
+ {
461
+ triggerId: z.string().describe("The trigger ID (UUID) to pause"),
462
+ },
463
+ async ({ triggerId }) => {
464
+ try {
465
+ const result = await sdk.triggers.pauseTrigger(triggerId);
466
+ return {
467
+ content: [
468
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
469
+ ],
470
+ };
471
+ } catch (error: unknown) {
472
+ return {
473
+ content: [
474
+ {
475
+ type: "text",
476
+ text: formatError(error, `pausing trigger '${triggerId}'`),
477
+ },
478
+ ],
479
+ isError: true,
480
+ };
481
+ }
482
+ }
483
+ );
484
+
485
+ server.tool(
486
+ "resume_trigger",
487
+ "Resume a paused function trigger. The trigger will start firing again on matching events or schedules.",
488
+ {
489
+ triggerId: z.string().describe("The trigger ID (UUID) to resume"),
490
+ },
491
+ async ({ triggerId }) => {
492
+ try {
493
+ const result = await sdk.triggers.resumeTrigger(triggerId);
494
+ return {
495
+ content: [
496
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
497
+ ],
498
+ };
499
+ } catch (error: unknown) {
500
+ return {
501
+ content: [
502
+ {
503
+ type: "text",
504
+ text: formatError(error, `resuming trigger '${triggerId}'`),
505
+ },
506
+ ],
507
+ isError: true,
508
+ };
509
+ }
510
+ }
511
+ );
141
512
  }
@@ -32,6 +32,9 @@ export function registerDescribeTools(server: McpServer) {
32
32
  tools: [
33
33
  "list_collections",
34
34
  "get_collection",
35
+ "create_collection",
36
+ "update_collection",
37
+ "delete_collection",
35
38
  "list_structures (deprecated)",
36
39
  "get_structure (deprecated)",
37
40
  ],
@@ -61,13 +64,36 @@ export function registerDescribeTools(server: McpServer) {
61
64
  summary:
62
65
  "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:
68
86
  "Reusable, parameterized queries defined in the console. Support {{variable}} substitution.",
69
87
  describeWith: "describe_smart_queries",
70
- tools: ["list_smart_queries", "execute_smart_query"],
88
+ tools: [
89
+ "list_smart_queries",
90
+ "get_smart_query",
91
+ "create_smart_query",
92
+ "update_smart_query",
93
+ "delete_smart_query",
94
+ "execute_smart_query",
95
+ "test_smart_query",
96
+ ],
71
97
  },
72
98
  orchestrations: {
73
99
  summary:
@@ -76,6 +102,11 @@ export function registerDescribeTools(server: McpServer) {
76
102
  tools: [
77
103
  "list_orchestrations",
78
104
  "get_orchestration",
105
+ "create_orchestration",
106
+ "update_orchestration",
107
+ "delete_orchestration",
108
+ "activate_orchestration",
109
+ "pause_orchestration",
79
110
  "trigger_orchestration",
80
111
  "list_orchestration_runs",
81
112
  "get_orchestration_run",
@@ -197,11 +228,30 @@ export function registerDescribeTools(server: McpServer) {
197
228
  json: "Arbitrary JSON object",
198
229
  auto_increment: "Auto-incrementing integer (read-only)",
199
230
  },
231
+ crud_tools: {
232
+ create_collection: {
233
+ description: "Create a new collection with name, slug, properties, and optional settings",
234
+ required_params: ["name", "slug"],
235
+ optional_params: ["description", "properties", "enableVersioning", "schemaDiscoveryMode", "tags"],
236
+ },
237
+ update_collection: {
238
+ description: "Update an existing collection by ID. Partial updates supported.",
239
+ required_params: ["collectionId"],
240
+ optional_params: ["name", "description", "properties", "enableVersioning", "tags", "defaultTtlSeconds"],
241
+ },
242
+ delete_collection: {
243
+ description: "Delete a collection by ID. Permanently removes the schema and all records.",
244
+ required_params: ["collectionId"],
245
+ },
246
+ },
200
247
  tips: [
201
248
  "Use list_collections to see all collections in the workspace",
202
249
  "Use get_collection with a recordSlug to see the full property definitions",
203
250
  "The recordSlug is what you use in query_records, create_record, etc.",
204
251
  "Reference fields create relationships between collections — use 'expand' parameter in queries to join them",
252
+ "Use create_collection to define new data schemas with typed properties",
253
+ "Properties array defines the fields — each needs at minimum a name and type",
254
+ "Use schemaDiscoveryMode: 'flexible' to auto-add new fields when records are created",
205
255
  ],
206
256
  },
207
257
  null,
@@ -418,12 +468,72 @@ export function registerDescribeTools(server: McpServer) {
418
468
  status: "'active' | 'paused'",
419
469
  config: "object — type-specific configuration (cron, event filters, etc.)",
420
470
  },
471
+ function_crud: {
472
+ get_function: {
473
+ description: "Get a compute function by ID, including its code",
474
+ required_params: ["functionId"],
475
+ },
476
+ create_function: {
477
+ description: "Create a new compute function",
478
+ required_params: ["name", "slug", "code"],
479
+ optional_params: ["description", "timeout"],
480
+ code_format: "module.exports = async (ctx) => { /* your code */ return result; }",
481
+ },
482
+ update_function: {
483
+ description: "Update an existing function. Partial updates supported.",
484
+ required_params: ["functionId"],
485
+ optional_params: ["name", "description", "code", "timeout"],
486
+ },
487
+ delete_function: {
488
+ description: "Delete a compute function by ID",
489
+ required_params: ["functionId"],
490
+ },
491
+ test_function: {
492
+ description: "Test execute code without saving. Returns output, duration, and logs.",
493
+ required_params: ["code"],
494
+ optional_params: ["input"],
495
+ },
496
+ },
497
+ trigger_crud: {
498
+ get_trigger: {
499
+ description: "Get any trigger by ID (all execution types)",
500
+ required_params: ["triggerId"],
501
+ },
502
+ create_trigger: {
503
+ description: "Create a new function trigger",
504
+ required_params: ["name", "functionId", "executionType"],
505
+ optional_params: ["description", "triggerMetadata", "enabled"],
506
+ triggerMetadata_examples: {
507
+ "event-driven": { event: "record.created", recordSlug: "orders" },
508
+ scheduled: { scheduleType: "cron", cronExpression: "0 9 * * *", timezone: "America/New_York" },
509
+ },
510
+ },
511
+ update_trigger: {
512
+ description: "Update an existing trigger. Partial updates supported.",
513
+ required_params: ["triggerId"],
514
+ optional_params: ["name", "description", "enabled", "triggerMetadata"],
515
+ },
516
+ delete_trigger: {
517
+ description: "Delete a trigger by ID",
518
+ required_params: ["triggerId"],
519
+ },
520
+ pause_trigger: {
521
+ description: "Pause a trigger to temporarily disable it",
522
+ required_params: ["triggerId"],
523
+ },
524
+ resume_trigger: {
525
+ description: "Resume a paused trigger",
526
+ required_params: ["triggerId"],
527
+ },
528
+ },
421
529
  tips: [
422
530
  "Use list_functions to see all available functions",
423
531
  "Use list_triggers to see how functions are wired to execution events",
424
532
  "Only on-demand triggers can be invoked via invoke_trigger — other types fire automatically",
425
533
  "Functions receive the trigger payload in their execution context",
426
- "Triggers can be paused (status: 'paused') without deleting them",
534
+ "Use pause_trigger/resume_trigger to temporarily disable triggers without deleting them",
535
+ "Use test_function to validate code before creating or updating a function",
536
+ "Use create_function + create_trigger together to set up a complete compute pipeline",
427
537
  ],
428
538
  },
429
539
  null,
@@ -474,11 +584,43 @@ export function registerDescribeTools(server: McpServer) {
474
584
  },
475
585
  note: "Variable values are always passed as strings — the query engine handles type coercion",
476
586
  },
587
+ crud_tools: {
588
+ get_smart_query: {
589
+ description: "Get a smart query by ID, including its full definition",
590
+ required_params: ["recordSlug", "queryId"],
591
+ },
592
+ create_smart_query: {
593
+ description: "Create a new smart query for a collection",
594
+ required_params: ["recordSlug", "name", "queryDefinition"],
595
+ optional_params: ["description"],
596
+ queryDefinition_example: {
597
+ where: { status: { "$eq": "active" } },
598
+ sort: [{ field: "createdAt", direction: "desc" }],
599
+ limit: 100,
600
+ },
601
+ },
602
+ update_smart_query: {
603
+ description: "Update an existing smart query. Partial updates supported.",
604
+ required_params: ["recordSlug", "queryId"],
605
+ optional_params: ["name", "description", "queryDefinition"],
606
+ },
607
+ delete_smart_query: {
608
+ description: "Delete a smart query by ID",
609
+ required_params: ["recordSlug", "queryId"],
610
+ },
611
+ test_smart_query: {
612
+ description: "Test execute a query definition without saving it. Preview results before creating.",
613
+ required_params: ["recordSlug", "queryDefinition"],
614
+ optional_params: ["variables"],
615
+ },
616
+ },
477
617
  tips: [
478
618
  "Use list_smart_queries to discover available queries (optionally filter by collection slug)",
479
619
  "Smart queries return the same result shape as query_records",
480
620
  "Variables are defined when creating the query in the console — check the query's variables array to see what's expected",
481
621
  "Prefer smart queries over ad-hoc query_records filters for complex/reusable logic",
622
+ "Use test_smart_query to validate query syntax and preview results before saving",
623
+ "Use create_smart_query to save reusable queries that can be executed with execute_smart_query",
482
624
  ],
483
625
  },
484
626
  null,
@@ -541,8 +683,42 @@ export function registerDescribeTools(server: McpServer) {
541
683
  startedAt: "ISO 8601 datetime | null",
542
684
  completedAt: "ISO 8601 datetime | null",
543
685
  },
686
+ crud_tools: {
687
+ create_orchestration: {
688
+ description: "Create a new orchestration workflow",
689
+ required_params: ["slug", "name", "trigger", "steps"],
690
+ optional_params: ["description"],
691
+ trigger_example: { type: "on-demand" },
692
+ step_example: {
693
+ id: "validate",
694
+ type: "compute",
695
+ functionId: "func-uuid",
696
+ onSuccess: { nextStepId: "process" },
697
+ },
698
+ },
699
+ update_orchestration: {
700
+ description: "Update an existing orchestration. Partial updates supported.",
701
+ required_params: ["orchestrationId"],
702
+ optional_params: ["name", "description", "status", "trigger", "steps"],
703
+ },
704
+ delete_orchestration: {
705
+ description: "Delete an orchestration and all its runs",
706
+ required_params: ["orchestrationId"],
707
+ },
708
+ activate_orchestration: {
709
+ description: "Set orchestration status to 'active' so it can be triggered",
710
+ required_params: ["orchestrationId"],
711
+ },
712
+ pause_orchestration: {
713
+ description: "Set orchestration status to 'paused' to prevent triggering",
714
+ required_params: ["orchestrationId"],
715
+ },
716
+ },
544
717
  tips: [
545
718
  "Use list_orchestrations to see available workflows",
719
+ "Use create_orchestration to define new multi-step workflows",
720
+ "Use activate_orchestration to enable a draft orchestration for triggering",
721
+ "Use pause_orchestration to temporarily disable an orchestration without deleting it",
546
722
  "Use trigger_orchestration with an input object to start a run",
547
723
  "Use get_orchestration_run with includeSteps=true to see step-by-step execution details",
548
724
  "Orchestrations must be 'active' to be triggered — draft orchestrations cannot run",