@centrali-io/centrali-mcp 4.4.6 → 4.4.7
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 +2 -1
- package/dist/tools/auth-providers.js +3 -3
- package/dist/tools/compute.js +53 -5
- package/dist/tools/describe.js +38 -4
- package/dist/tools/insights.js +1 -1
- package/dist/tools/service-accounts.js +3 -3
- package/package.json +2 -2
- package/src/tools/auth-providers.ts +3 -3
- package/src/tools/compute.ts +60 -5
- package/src/tools/describe.ts +39 -4
- package/src/tools/insights.ts +1 -1
- package/src/tools/service-accounts.ts +3 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ MCP (Model Context Protocol) server for the Centrali platform. Lets AI assistant
|
|
|
4
4
|
|
|
5
5
|
> **Full documentation:** [docs.centrali.io](https://docs.centrali.io) — SDK guide, API reference, compute functions, orchestrations, and more.
|
|
6
6
|
|
|
7
|
-
Built on `@centrali-io/centrali-sdk` v4.4.
|
|
7
|
+
Built on `@centrali-io/centrali-sdk` v4.4.7. Authenticates via service account client credentials.
|
|
8
8
|
|
|
9
9
|
## Setup
|
|
10
10
|
|
|
@@ -110,6 +110,7 @@ After connecting, call `describe_centrali` first — it returns the full capabil
|
|
|
110
110
|
| `get_compute_job_status` | Check async job status by job ID (poll after invoke_trigger) |
|
|
111
111
|
| `list_allowed_domains` | List allowed domains for outbound HTTP |
|
|
112
112
|
| `add_allowed_domain` | Add a domain to the allowlist |
|
|
113
|
+
| `invoke_endpoint` | Call a sync compute endpoint by path (returns response inline) |
|
|
113
114
|
| `remove_allowed_domain` | Remove a domain from the allowlist |
|
|
114
115
|
|
|
115
116
|
### Smart Queries
|
|
@@ -37,10 +37,10 @@ function ensureToken(sdk) {
|
|
|
37
37
|
*/
|
|
38
38
|
function createIamClient(sdk, centraliUrl, workspaceId) {
|
|
39
39
|
const url = new URL(centraliUrl);
|
|
40
|
-
const hostname = url.hostname.startsWith("
|
|
40
|
+
const hostname = url.hostname.startsWith("auth.")
|
|
41
41
|
? url.hostname
|
|
42
|
-
: `
|
|
43
|
-
const baseURL = `${url.protocol}//${hostname}/
|
|
42
|
+
: `auth.${url.hostname.replace(/^api\./, '')}`;
|
|
43
|
+
const baseURL = `${url.protocol}//${hostname}/workspace/${workspaceId}/api/v1/external-auth-providers`;
|
|
44
44
|
const client = axios_1.default.create({ baseURL });
|
|
45
45
|
client.interceptors.request.use((config) => __awaiter(this, void 0, void 0, function* () {
|
|
46
46
|
const token = yield ensureToken(sdk);
|
package/dist/tools/compute.js
CHANGED
|
@@ -82,9 +82,9 @@ function registerComputeTools(server, sdk, centraliUrl, workspaceId) {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
}));
|
|
85
|
-
server.tool("list_triggers", "List function triggers in the workspace. Triggers define how and when compute functions are executed (on-demand, event-driven, scheduled, http-trigger).", {
|
|
85
|
+
server.tool("list_triggers", "List function triggers in the workspace. Triggers define how and when compute functions are executed (on-demand, event-driven, scheduled, http-trigger, endpoint).", {
|
|
86
86
|
executionType: zod_1.z
|
|
87
|
-
.enum(["on-demand", "event-driven", "scheduled", "http-trigger"])
|
|
87
|
+
.enum(["on-demand", "event-driven", "scheduled", "http-trigger", "endpoint"])
|
|
88
88
|
.optional()
|
|
89
89
|
.describe("Filter by trigger execution type"),
|
|
90
90
|
page: zod_1.z.number().optional().describe("Page number"),
|
|
@@ -394,13 +394,13 @@ function registerComputeTools(server, sdk, centraliUrl, workspaceId) {
|
|
|
394
394
|
name: zod_1.z.string().describe("Display name for the trigger"),
|
|
395
395
|
functionId: zod_1.z.string().describe("The compute function ID (UUID) to execute"),
|
|
396
396
|
executionType: zod_1.z
|
|
397
|
-
.enum(["on-demand", "event-driven", "scheduled", "http-trigger"])
|
|
398
|
-
.describe("How the trigger fires: on-demand (manual), event-driven (data events), scheduled (cron),
|
|
397
|
+
.enum(["on-demand", "event-driven", "scheduled", "http-trigger", "endpoint"])
|
|
398
|
+
.describe("How the trigger fires: on-demand (manual), event-driven (data events), scheduled (cron), http-trigger (external HTTP POST), or endpoint (synchronous HTTP API — returns response inline)"),
|
|
399
399
|
description: zod_1.z.string().optional().describe("Optional description"),
|
|
400
400
|
triggerMetadata: zod_1.z
|
|
401
401
|
.record(zod_1.z.string(), zod_1.z.any())
|
|
402
402
|
.optional()
|
|
403
|
-
.describe("Type-specific configuration. For event-driven: { eventType, recordSlug }
|
|
403
|
+
.describe("Type-specific configuration. For event-driven: { eventType, recordSlug }. For scheduled: { scheduleType, cronExpression, timezone }. For http-trigger: auto-generated URL. For endpoint: { path, allowedMethods?, timeoutMs?, auth? } where path is URL-safe (e.g., 'create-order'), allowedMethods defaults to ['POST'], timeoutMs 1000-30000 (default 30000), auth is { mode: 'bearer'|'public'|'apiKey'|'hmac' }."),
|
|
404
404
|
enabled: zod_1.z.boolean().optional().describe("Whether the trigger is enabled (default: true)"),
|
|
405
405
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ name, functionId, executionType, description, triggerMetadata, enabled }) {
|
|
406
406
|
try {
|
|
@@ -616,6 +616,54 @@ function registerComputeTools(server, sdk, centraliUrl, workspaceId) {
|
|
|
616
616
|
};
|
|
617
617
|
}
|
|
618
618
|
}));
|
|
619
|
+
// ── Endpoint Trigger (Sync Execution) ─────────────────────────────
|
|
620
|
+
server.tool("invoke_endpoint", "Invoke a compute endpoint trigger by path. The function executes synchronously — Centrali waits for the function to complete and returns its output directly in the response. No polling needed. Max execution time: 30 seconds (configurable via triggerMetadata.timeoutMs, range 1–30s). If the function exceeds the timeout, returns 504. Endpoint triggers must be created first with executionType='endpoint'. Use this for real-time API responses; use invoke_trigger for long-running background work that doesn't need an immediate response.", {
|
|
621
|
+
path: zod_1.z.string().describe("The endpoint path (e.g., 'create-order', 'webhook/shipments'). This is set in the trigger's triggerMetadata.path."),
|
|
622
|
+
method: zod_1.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional().describe("HTTP method (default: POST). Must be in the trigger's allowedMethods."),
|
|
623
|
+
payload: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional().describe("Request body payload (sent as JSON)"),
|
|
624
|
+
headers: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).optional().describe("Additional headers (e.g., X-API-Key for apiKey auth)"),
|
|
625
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ path, method, payload, headers: extraHeaders }) {
|
|
626
|
+
try {
|
|
627
|
+
const token = yield ensureToken(sdk);
|
|
628
|
+
const url = new URL(centraliUrl);
|
|
629
|
+
const hostname = url.hostname.startsWith("api.")
|
|
630
|
+
? url.hostname
|
|
631
|
+
: `api.${url.hostname}`;
|
|
632
|
+
const apiUrl = `${url.protocol}//${hostname}/data/workspace/${workspaceId}/api/v1/endpoints/${path}`;
|
|
633
|
+
const reqHeaders = {};
|
|
634
|
+
if (token)
|
|
635
|
+
reqHeaders.Authorization = `Bearer ${token}`;
|
|
636
|
+
if (extraHeaders)
|
|
637
|
+
Object.assign(reqHeaders, extraHeaders);
|
|
638
|
+
const httpMethod = (method || "POST").toLowerCase();
|
|
639
|
+
const result = yield (0, axios_1.default)({
|
|
640
|
+
method: httpMethod,
|
|
641
|
+
url: apiUrl,
|
|
642
|
+
data: ["get", "delete"].includes(httpMethod) ? undefined : (payload || {}),
|
|
643
|
+
headers: reqHeaders,
|
|
644
|
+
validateStatus: () => true, // Don't throw on non-2xx — return the function's response as-is
|
|
645
|
+
});
|
|
646
|
+
return {
|
|
647
|
+
content: [{
|
|
648
|
+
type: "text",
|
|
649
|
+
text: JSON.stringify({
|
|
650
|
+
status: result.status,
|
|
651
|
+
headers: {
|
|
652
|
+
"content-type": result.headers["content-type"],
|
|
653
|
+
"x-execution-id": result.headers["x-execution-id"],
|
|
654
|
+
},
|
|
655
|
+
body: result.data,
|
|
656
|
+
}, null, 2),
|
|
657
|
+
}],
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
661
|
+
return {
|
|
662
|
+
content: [{ type: "text", text: formatError(error, `invoking endpoint '${path}'`) }],
|
|
663
|
+
isError: true,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
}));
|
|
619
667
|
// ── Allowed Domains tools ──────────────────────────────────────────
|
|
620
668
|
server.tool("list_allowed_domains", "List all allowed domains for compute function HTTP requests. Functions can only call external APIs on domains in this allowlist.", {}, () => __awaiter(this, void 0, void 0, function* () {
|
|
621
669
|
try {
|
package/dist/tools/describe.js
CHANGED
|
@@ -85,6 +85,7 @@ function registerDescribeTools(server) {
|
|
|
85
85
|
"get_function_run",
|
|
86
86
|
"list_function_runs",
|
|
87
87
|
"get_compute_job_status",
|
|
88
|
+
"invoke_endpoint",
|
|
88
89
|
],
|
|
89
90
|
},
|
|
90
91
|
smart_queries: {
|
|
@@ -220,6 +221,16 @@ function registerDescribeTools(server) {
|
|
|
220
221
|
],
|
|
221
222
|
},
|
|
222
223
|
},
|
|
224
|
+
naming_guide: {
|
|
225
|
+
description: "Different tools use different parameter names for the same concept (collection identifier). This is a historical naming drift — all of these refer to the same thing.",
|
|
226
|
+
aliases: {
|
|
227
|
+
recordSlug: "Used by record tools (query_records, create_record, etc.). This is the collection's URL-safe slug, e.g., 'orders'.",
|
|
228
|
+
structureSlug: "Used by validation and insights tools. Same value as recordSlug.",
|
|
229
|
+
collections: "Used by search_records. Same value as recordSlug. Accepts a string or array of strings.",
|
|
230
|
+
structureIds: "Used by generate_starter_pages. This is the collection UUID (not the slug). Get it from list_collections → id field.",
|
|
231
|
+
},
|
|
232
|
+
rule: "When a tool asks for recordSlug, structureSlug, or collections — use the collection's slug (e.g., 'orders'). When a tool asks for structureIds — use the collection's UUID.",
|
|
233
|
+
},
|
|
223
234
|
workflow: "Typical workflow: 1) Define collections → 2) Create records → 3) Write compute functions → 4) Wire orchestrations → 5) Build pages to surface data → 6) Publish pages for end users. When building an app, also: 7) Create a service account → 8) Grant least-privilege permissions via remediation → 9) Create publishable keys for the frontend.",
|
|
224
235
|
app_credential_setup: {
|
|
225
236
|
description: "When building an app that uses Centrali, you need credentials. The flow depends on whether the code runs server-side or client-side.",
|
|
@@ -522,13 +533,29 @@ function registerDescribeTools(server) {
|
|
|
522
533
|
description: "Triggered by an external HTTP POST to a generated webhook URL.",
|
|
523
534
|
config: "Each http-trigger gets a unique URL to share with external services",
|
|
524
535
|
},
|
|
536
|
+
"endpoint": {
|
|
537
|
+
description: "Turns a compute function into a custom API endpoint. Unlike all other trigger types (which are async and return a job ID you must poll), endpoint triggers WAIT for the function to complete and return its output directly in the HTTP response. Max execution time: 30 seconds.",
|
|
538
|
+
when_to_use: "Use endpoint triggers when the caller needs the function's output immediately (REST APIs, form handlers, webhook responders, data calculations). Use on-demand/invoke_trigger for long-running work (>30s) or fire-and-forget background jobs.",
|
|
539
|
+
config: {
|
|
540
|
+
path: "string — URL-safe path (e.g., 'create-order', 'webhook/shipments'). Must be unique per workspace.",
|
|
541
|
+
allowedMethods: "string[] — HTTP methods to accept (default: ['POST']). Options: GET, POST, PUT, DELETE, PATCH.",
|
|
542
|
+
timeoutMs: "number — execution timeout 1000-30000ms (default: 30000). Function MUST complete within this window or the request returns 504 Gateway Timeout.",
|
|
543
|
+
auth: "{ mode: 'bearer'|'public'|'apiKey'|'hmac' } — authentication mode. bearer=IAM token, public=no auth, apiKey=X-API-Key header (auto-generated), hmac=X-Signature header (auto-generated signing secret).",
|
|
544
|
+
},
|
|
545
|
+
invocation: "Call invoke_endpoint with the path. Response comes back inline — no polling needed.",
|
|
546
|
+
example_use_cases: [
|
|
547
|
+
"Build a REST API endpoint backed by a compute function",
|
|
548
|
+
"Create a webhook receiver that processes and responds synchronously",
|
|
549
|
+
"Expose a function as an HTTP service for external integrations",
|
|
550
|
+
],
|
|
551
|
+
},
|
|
525
552
|
},
|
|
526
553
|
trigger_shape: {
|
|
527
554
|
id: "UUID",
|
|
528
555
|
name: "string",
|
|
529
556
|
description: "string | null",
|
|
530
557
|
functionId: "UUID — the compute function to execute",
|
|
531
|
-
executionType: "'on-demand' | 'event-driven' | 'scheduled' | 'http-trigger'",
|
|
558
|
+
executionType: "'on-demand' | 'event-driven' | 'scheduled' | 'http-trigger' | 'endpoint'",
|
|
532
559
|
triggerMetadata: "object — type-specific configuration (event, cron, params, etc.)",
|
|
533
560
|
enabled: "boolean — whether the trigger is active (default: true)",
|
|
534
561
|
workspaceSlug: "string",
|
|
@@ -575,6 +602,7 @@ function registerDescribeTools(server) {
|
|
|
575
602
|
triggerMetadata_examples: {
|
|
576
603
|
"event-driven": { eventType: "record_created", recordSlug: "orders" },
|
|
577
604
|
scheduled: { scheduleType: "cron", cronExpression: "0 9 * * *", timezone: "America/New_York" },
|
|
605
|
+
endpoint: { path: "create-order", allowedMethods: ["POST"], timeoutMs: 10000, auth: { mode: "bearer" } },
|
|
578
606
|
},
|
|
579
607
|
},
|
|
580
608
|
update_trigger: {
|
|
@@ -969,7 +997,7 @@ function registerDescribeTools(server) {
|
|
|
969
997
|
id: "UUID",
|
|
970
998
|
structureSlug: "string — the collection where the anomaly was detected",
|
|
971
999
|
type: "string — anomaly type (e.g., 'spike', 'drop', 'outlier', 'pattern_break')",
|
|
972
|
-
severity: "'
|
|
1000
|
+
severity: "'info' | 'warning' | 'critical'",
|
|
973
1001
|
status: "'active' | 'acknowledged' | 'dismissed'",
|
|
974
1002
|
title: "string — human-readable summary",
|
|
975
1003
|
description: "string — detailed explanation of the anomaly",
|
|
@@ -986,12 +1014,12 @@ function registerDescribeTools(server) {
|
|
|
986
1014
|
totalActive: "number",
|
|
987
1015
|
totalAcknowledged: "number",
|
|
988
1016
|
totalDismissed: "number",
|
|
989
|
-
bySeverity: "{
|
|
1017
|
+
bySeverity: "{ info: n, warning: n, critical: n }",
|
|
990
1018
|
},
|
|
991
1019
|
tips: [
|
|
992
1020
|
"Use trigger_anomaly_analysis to scan a specific collection on-demand",
|
|
993
1021
|
"Use get_insights_summary for a quick overview before diving into individual insights",
|
|
994
|
-
"Filter by severity='critical' to focus on the most important issues first",
|
|
1022
|
+
"Filter by severity='critical' to focus on the most important issues first. Severity levels: info (noteworthy), warning (needs attention), critical (requires action).",
|
|
995
1023
|
"Acknowledged insights are still visible — use dismiss for false positives",
|
|
996
1024
|
],
|
|
997
1025
|
}, null, 2),
|
|
@@ -1108,6 +1136,12 @@ function registerDescribeTools(server) {
|
|
|
1108
1136
|
"4_publish": "publish_page — makes the page accessible at its runtime URL",
|
|
1109
1137
|
"5_iterate": "Repeat steps 2-4 to update. Each publish creates a new version.",
|
|
1110
1138
|
unpublish: "unpublish_page — removes the page from its runtime URL (keeps the definition)",
|
|
1139
|
+
important_behavior: {
|
|
1140
|
+
description: "Publishing does NOT consume or delete the draft. After publishing, the page has BOTH an activePublication (the live version) and a currentDraft (a new working draft for future edits). This is intentional — it allows iterating on changes without affecting the live page.",
|
|
1141
|
+
example: "After publishing version 1, get_page shows: activePublication.versionNumber=1 (live) and currentDraft.versionNumber=2 (editable). The draft is not stale — it's the starting point for the next publish cycle.",
|
|
1142
|
+
agent_tip: "Do not treat the draft as an error or leftover. The normal state of a published page is: one active publication + one working draft. Edit the draft, validate, publish again to create version 3, etc.",
|
|
1143
|
+
},
|
|
1144
|
+
eventual_consistency_note: "After save_page_draft or set_navigation, there may be a brief delay before the data is visible via get_page_draft or get_navigation. If you get a 'not found' immediately after writing, wait 1-2 seconds and retry.",
|
|
1111
1145
|
},
|
|
1112
1146
|
access_modes: {
|
|
1113
1147
|
public: "Anyone can view — no authentication required",
|
package/dist/tools/insights.js
CHANGED
|
@@ -46,7 +46,7 @@ function registerInsightTools(server, sdk) {
|
|
|
46
46
|
.optional()
|
|
47
47
|
.describe("Filter by insight status"),
|
|
48
48
|
severity: zod_1.z
|
|
49
|
-
.enum(["
|
|
49
|
+
.enum(["info", "warning", "critical"])
|
|
50
50
|
.optional()
|
|
51
51
|
.describe("Filter by severity level"),
|
|
52
52
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ structureSlug, status, severity }) {
|
|
@@ -37,10 +37,10 @@ function ensureToken(sdk) {
|
|
|
37
37
|
*/
|
|
38
38
|
function createIamClient(sdk, centraliUrl, workspaceId, baseSuffix) {
|
|
39
39
|
const url = new URL(centraliUrl);
|
|
40
|
-
const hostname = url.hostname.startsWith("
|
|
40
|
+
const hostname = url.hostname.startsWith("auth.")
|
|
41
41
|
? url.hostname
|
|
42
|
-
: `
|
|
43
|
-
const baseURL = `${url.protocol}//${hostname}/
|
|
42
|
+
: `auth.${url.hostname.replace(/^api\./, '')}`;
|
|
43
|
+
const baseURL = `${url.protocol}//${hostname}/workspace/${workspaceId}/api/v1/${baseSuffix}`;
|
|
44
44
|
const client = axios_1.default.create({ baseURL });
|
|
45
45
|
client.interceptors.request.use((config) => __awaiter(this, void 0, void 0, function* () {
|
|
46
46
|
const token = yield ensureToken(sdk);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centrali-io/centrali-mcp",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.7",
|
|
4
4
|
"description": "Centrali MCP Server - AI assistant integration for Centrali workspaces",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"author": "Blueinit",
|
|
26
26
|
"license": "ISC",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@centrali-io/centrali-sdk": "^4.4.
|
|
28
|
+
"@centrali-io/centrali-sdk": "^4.4.7",
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
@@ -22,10 +22,10 @@ async function ensureToken(sdk: CentraliSDK): Promise<string | null> {
|
|
|
22
22
|
*/
|
|
23
23
|
function createIamClient(sdk: CentraliSDK, centraliUrl: string, workspaceId: string): AxiosInstance {
|
|
24
24
|
const url = new URL(centraliUrl);
|
|
25
|
-
const hostname = url.hostname.startsWith("
|
|
25
|
+
const hostname = url.hostname.startsWith("auth.")
|
|
26
26
|
? url.hostname
|
|
27
|
-
: `
|
|
28
|
-
const baseURL = `${url.protocol}//${hostname}/
|
|
27
|
+
: `auth.${url.hostname.replace(/^api\./, '')}`;
|
|
28
|
+
const baseURL = `${url.protocol}//${hostname}/workspace/${workspaceId}/api/v1/external-auth-providers`;
|
|
29
29
|
|
|
30
30
|
const client = axios.create({ baseURL });
|
|
31
31
|
|
package/src/tools/compute.ts
CHANGED
|
@@ -74,10 +74,10 @@ export function registerComputeTools(server: McpServer, sdk: CentraliSDK, centra
|
|
|
74
74
|
|
|
75
75
|
server.tool(
|
|
76
76
|
"list_triggers",
|
|
77
|
-
"List function triggers in the workspace. Triggers define how and when compute functions are executed (on-demand, event-driven, scheduled, http-trigger).",
|
|
77
|
+
"List function triggers in the workspace. Triggers define how and when compute functions are executed (on-demand, event-driven, scheduled, http-trigger, endpoint).",
|
|
78
78
|
{
|
|
79
79
|
executionType: z
|
|
80
|
-
.enum(["on-demand", "event-driven", "scheduled", "http-trigger"])
|
|
80
|
+
.enum(["on-demand", "event-driven", "scheduled", "http-trigger", "endpoint"])
|
|
81
81
|
.optional()
|
|
82
82
|
.describe("Filter by trigger execution type"),
|
|
83
83
|
page: z.number().optional().describe("Page number"),
|
|
@@ -437,13 +437,13 @@ export function registerComputeTools(server: McpServer, sdk: CentraliSDK, centra
|
|
|
437
437
|
name: z.string().describe("Display name for the trigger"),
|
|
438
438
|
functionId: z.string().describe("The compute function ID (UUID) to execute"),
|
|
439
439
|
executionType: z
|
|
440
|
-
.enum(["on-demand", "event-driven", "scheduled", "http-trigger"])
|
|
441
|
-
.describe("How the trigger fires: on-demand (manual), event-driven (data events), scheduled (cron),
|
|
440
|
+
.enum(["on-demand", "event-driven", "scheduled", "http-trigger", "endpoint"])
|
|
441
|
+
.describe("How the trigger fires: on-demand (manual), event-driven (data events), scheduled (cron), http-trigger (external HTTP POST), or endpoint (synchronous HTTP API — returns response inline)"),
|
|
442
442
|
description: z.string().optional().describe("Optional description"),
|
|
443
443
|
triggerMetadata: z
|
|
444
444
|
.record(z.string(), z.any())
|
|
445
445
|
.optional()
|
|
446
|
-
.describe("Type-specific configuration. For event-driven: { eventType, recordSlug }
|
|
446
|
+
.describe("Type-specific configuration. For event-driven: { eventType, recordSlug }. For scheduled: { scheduleType, cronExpression, timezone }. For http-trigger: auto-generated URL. For endpoint: { path, allowedMethods?, timeoutMs?, auth? } where path is URL-safe (e.g., 'create-order'), allowedMethods defaults to ['POST'], timeoutMs 1000-30000 (default 30000), auth is { mode: 'bearer'|'public'|'apiKey'|'hmac' }."),
|
|
447
447
|
enabled: z.boolean().optional().describe("Whether the trigger is enabled (default: true)"),
|
|
448
448
|
},
|
|
449
449
|
async ({ name, functionId, executionType, description, triggerMetadata, enabled }) => {
|
|
@@ -687,6 +687,61 @@ export function registerComputeTools(server: McpServer, sdk: CentraliSDK, centra
|
|
|
687
687
|
}
|
|
688
688
|
);
|
|
689
689
|
|
|
690
|
+
// ── Endpoint Trigger (Sync Execution) ─────────────────────────────
|
|
691
|
+
|
|
692
|
+
server.tool(
|
|
693
|
+
"invoke_endpoint",
|
|
694
|
+
"Invoke a compute endpoint trigger by path. The function executes synchronously — Centrali waits for the function to complete and returns its output directly in the response. No polling needed. Max execution time: 30 seconds (configurable via triggerMetadata.timeoutMs, range 1–30s). If the function exceeds the timeout, returns 504. Endpoint triggers must be created first with executionType='endpoint'. Use this for real-time API responses; use invoke_trigger for long-running background work that doesn't need an immediate response.",
|
|
695
|
+
{
|
|
696
|
+
path: z.string().describe("The endpoint path (e.g., 'create-order', 'webhook/shipments'). This is set in the trigger's triggerMetadata.path."),
|
|
697
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional().describe("HTTP method (default: POST). Must be in the trigger's allowedMethods."),
|
|
698
|
+
payload: z.record(z.string(), z.any()).optional().describe("Request body payload (sent as JSON)"),
|
|
699
|
+
headers: z.record(z.string(), z.string()).optional().describe("Additional headers (e.g., X-API-Key for apiKey auth)"),
|
|
700
|
+
},
|
|
701
|
+
async ({ path, method, payload, headers: extraHeaders }) => {
|
|
702
|
+
try {
|
|
703
|
+
const token = await ensureToken(sdk);
|
|
704
|
+
const url = new URL(centraliUrl);
|
|
705
|
+
const hostname = url.hostname.startsWith("api.")
|
|
706
|
+
? url.hostname
|
|
707
|
+
: `api.${url.hostname}`;
|
|
708
|
+
const apiUrl = `${url.protocol}//${hostname}/data/workspace/${workspaceId}/api/v1/endpoints/${path}`;
|
|
709
|
+
|
|
710
|
+
const reqHeaders: Record<string, string> = {};
|
|
711
|
+
if (token) reqHeaders.Authorization = `Bearer ${token}`;
|
|
712
|
+
if (extraHeaders) Object.assign(reqHeaders, extraHeaders);
|
|
713
|
+
|
|
714
|
+
const httpMethod = (method || "POST").toLowerCase();
|
|
715
|
+
const result = await axios({
|
|
716
|
+
method: httpMethod as any,
|
|
717
|
+
url: apiUrl,
|
|
718
|
+
data: ["get", "delete"].includes(httpMethod) ? undefined : (payload || {}),
|
|
719
|
+
headers: reqHeaders,
|
|
720
|
+
validateStatus: () => true, // Don't throw on non-2xx — return the function's response as-is
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
return {
|
|
724
|
+
content: [{
|
|
725
|
+
type: "text",
|
|
726
|
+
text: JSON.stringify({
|
|
727
|
+
status: result.status,
|
|
728
|
+
headers: {
|
|
729
|
+
"content-type": result.headers["content-type"],
|
|
730
|
+
"x-execution-id": result.headers["x-execution-id"],
|
|
731
|
+
},
|
|
732
|
+
body: result.data,
|
|
733
|
+
}, null, 2),
|
|
734
|
+
}],
|
|
735
|
+
};
|
|
736
|
+
} catch (error: unknown) {
|
|
737
|
+
return {
|
|
738
|
+
content: [{ type: "text", text: formatError(error, `invoking endpoint '${path}'`) }],
|
|
739
|
+
isError: true,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
);
|
|
744
|
+
|
|
690
745
|
// ── Allowed Domains tools ──────────────────────────────────────────
|
|
691
746
|
|
|
692
747
|
server.tool(
|
package/src/tools/describe.ts
CHANGED
|
@@ -85,6 +85,7 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
85
85
|
"get_function_run",
|
|
86
86
|
"list_function_runs",
|
|
87
87
|
"get_compute_job_status",
|
|
88
|
+
"invoke_endpoint",
|
|
88
89
|
],
|
|
89
90
|
},
|
|
90
91
|
smart_queries: {
|
|
@@ -227,6 +228,16 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
227
228
|
],
|
|
228
229
|
},
|
|
229
230
|
},
|
|
231
|
+
naming_guide: {
|
|
232
|
+
description: "Different tools use different parameter names for the same concept (collection identifier). This is a historical naming drift — all of these refer to the same thing.",
|
|
233
|
+
aliases: {
|
|
234
|
+
recordSlug: "Used by record tools (query_records, create_record, etc.). This is the collection's URL-safe slug, e.g., 'orders'.",
|
|
235
|
+
structureSlug: "Used by validation and insights tools. Same value as recordSlug.",
|
|
236
|
+
collections: "Used by search_records. Same value as recordSlug. Accepts a string or array of strings.",
|
|
237
|
+
structureIds: "Used by generate_starter_pages. This is the collection UUID (not the slug). Get it from list_collections → id field.",
|
|
238
|
+
},
|
|
239
|
+
rule: "When a tool asks for recordSlug, structureSlug, or collections — use the collection's slug (e.g., 'orders'). When a tool asks for structureIds — use the collection's UUID.",
|
|
240
|
+
},
|
|
230
241
|
workflow:
|
|
231
242
|
"Typical workflow: 1) Define collections → 2) Create records → 3) Write compute functions → 4) Wire orchestrations → 5) Build pages to surface data → 6) Publish pages for end users. When building an app, also: 7) Create a service account → 8) Grant least-privilege permissions via remediation → 9) Create publishable keys for the frontend.",
|
|
232
243
|
app_credential_setup: {
|
|
@@ -594,13 +605,30 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
594
605
|
"Triggered by an external HTTP POST to a generated webhook URL.",
|
|
595
606
|
config: "Each http-trigger gets a unique URL to share with external services",
|
|
596
607
|
},
|
|
608
|
+
"endpoint": {
|
|
609
|
+
description:
|
|
610
|
+
"Turns a compute function into a custom API endpoint. Unlike all other trigger types (which are async and return a job ID you must poll), endpoint triggers WAIT for the function to complete and return its output directly in the HTTP response. Max execution time: 30 seconds.",
|
|
611
|
+
when_to_use: "Use endpoint triggers when the caller needs the function's output immediately (REST APIs, form handlers, webhook responders, data calculations). Use on-demand/invoke_trigger for long-running work (>30s) or fire-and-forget background jobs.",
|
|
612
|
+
config: {
|
|
613
|
+
path: "string — URL-safe path (e.g., 'create-order', 'webhook/shipments'). Must be unique per workspace.",
|
|
614
|
+
allowedMethods: "string[] — HTTP methods to accept (default: ['POST']). Options: GET, POST, PUT, DELETE, PATCH.",
|
|
615
|
+
timeoutMs: "number — execution timeout 1000-30000ms (default: 30000). Function MUST complete within this window or the request returns 504 Gateway Timeout.",
|
|
616
|
+
auth: "{ mode: 'bearer'|'public'|'apiKey'|'hmac' } — authentication mode. bearer=IAM token, public=no auth, apiKey=X-API-Key header (auto-generated), hmac=X-Signature header (auto-generated signing secret).",
|
|
617
|
+
},
|
|
618
|
+
invocation: "Call invoke_endpoint with the path. Response comes back inline — no polling needed.",
|
|
619
|
+
example_use_cases: [
|
|
620
|
+
"Build a REST API endpoint backed by a compute function",
|
|
621
|
+
"Create a webhook receiver that processes and responds synchronously",
|
|
622
|
+
"Expose a function as an HTTP service for external integrations",
|
|
623
|
+
],
|
|
624
|
+
},
|
|
597
625
|
},
|
|
598
626
|
trigger_shape: {
|
|
599
627
|
id: "UUID",
|
|
600
628
|
name: "string",
|
|
601
629
|
description: "string | null",
|
|
602
630
|
functionId: "UUID — the compute function to execute",
|
|
603
|
-
executionType: "'on-demand' | 'event-driven' | 'scheduled' | 'http-trigger'",
|
|
631
|
+
executionType: "'on-demand' | 'event-driven' | 'scheduled' | 'http-trigger' | 'endpoint'",
|
|
604
632
|
triggerMetadata: "object — type-specific configuration (event, cron, params, etc.)",
|
|
605
633
|
enabled: "boolean — whether the trigger is active (default: true)",
|
|
606
634
|
workspaceSlug: "string",
|
|
@@ -647,6 +675,7 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
647
675
|
triggerMetadata_examples: {
|
|
648
676
|
"event-driven": { eventType: "record_created", recordSlug: "orders" },
|
|
649
677
|
scheduled: { scheduleType: "cron", cronExpression: "0 9 * * *", timezone: "America/New_York" },
|
|
678
|
+
endpoint: { path: "create-order", allowedMethods: ["POST"], timeoutMs: 10000, auth: { mode: "bearer" } },
|
|
650
679
|
},
|
|
651
680
|
},
|
|
652
681
|
update_trigger: {
|
|
@@ -1074,7 +1103,7 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
1074
1103
|
id: "UUID",
|
|
1075
1104
|
structureSlug: "string — the collection where the anomaly was detected",
|
|
1076
1105
|
type: "string — anomaly type (e.g., 'spike', 'drop', 'outlier', 'pattern_break')",
|
|
1077
|
-
severity: "'
|
|
1106
|
+
severity: "'info' | 'warning' | 'critical'",
|
|
1078
1107
|
status: "'active' | 'acknowledged' | 'dismissed'",
|
|
1079
1108
|
title: "string — human-readable summary",
|
|
1080
1109
|
description: "string — detailed explanation of the anomaly",
|
|
@@ -1093,12 +1122,12 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
1093
1122
|
totalActive: "number",
|
|
1094
1123
|
totalAcknowledged: "number",
|
|
1095
1124
|
totalDismissed: "number",
|
|
1096
|
-
bySeverity: "{
|
|
1125
|
+
bySeverity: "{ info: n, warning: n, critical: n }",
|
|
1097
1126
|
},
|
|
1098
1127
|
tips: [
|
|
1099
1128
|
"Use trigger_anomaly_analysis to scan a specific collection on-demand",
|
|
1100
1129
|
"Use get_insights_summary for a quick overview before diving into individual insights",
|
|
1101
|
-
"Filter by severity='critical' to focus on the most important issues first",
|
|
1130
|
+
"Filter by severity='critical' to focus on the most important issues first. Severity levels: info (noteworthy), warning (needs attention), critical (requires action).",
|
|
1102
1131
|
"Acknowledged insights are still visible — use dismiss for false positives",
|
|
1103
1132
|
],
|
|
1104
1133
|
},
|
|
@@ -1255,6 +1284,12 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
1255
1284
|
"Repeat steps 2-4 to update. Each publish creates a new version.",
|
|
1256
1285
|
unpublish:
|
|
1257
1286
|
"unpublish_page — removes the page from its runtime URL (keeps the definition)",
|
|
1287
|
+
important_behavior: {
|
|
1288
|
+
description: "Publishing does NOT consume or delete the draft. After publishing, the page has BOTH an activePublication (the live version) and a currentDraft (a new working draft for future edits). This is intentional — it allows iterating on changes without affecting the live page.",
|
|
1289
|
+
example: "After publishing version 1, get_page shows: activePublication.versionNumber=1 (live) and currentDraft.versionNumber=2 (editable). The draft is not stale — it's the starting point for the next publish cycle.",
|
|
1290
|
+
agent_tip: "Do not treat the draft as an error or leftover. The normal state of a published page is: one active publication + one working draft. Edit the draft, validate, publish again to create version 3, etc.",
|
|
1291
|
+
},
|
|
1292
|
+
eventual_consistency_note: "After save_page_draft or set_navigation, there may be a brief delay before the data is visible via get_page_draft or get_navigation. If you get a 'not found' immediately after writing, wait 1-2 seconds and retry.",
|
|
1258
1293
|
},
|
|
1259
1294
|
access_modes: {
|
|
1260
1295
|
public: "Anyone can view — no authentication required",
|
package/src/tools/insights.ts
CHANGED
|
@@ -41,7 +41,7 @@ export function registerInsightTools(server: McpServer, sdk: CentraliSDK) {
|
|
|
41
41
|
.optional()
|
|
42
42
|
.describe("Filter by insight status"),
|
|
43
43
|
severity: z
|
|
44
|
-
.enum(["
|
|
44
|
+
.enum(["info", "warning", "critical"])
|
|
45
45
|
.optional()
|
|
46
46
|
.describe("Filter by severity level"),
|
|
47
47
|
},
|
|
@@ -22,10 +22,10 @@ async function ensureToken(sdk: CentraliSDK): Promise<string | null> {
|
|
|
22
22
|
*/
|
|
23
23
|
function createIamClient(sdk: CentraliSDK, centraliUrl: string, workspaceId: string, baseSuffix: string): AxiosInstance {
|
|
24
24
|
const url = new URL(centraliUrl);
|
|
25
|
-
const hostname = url.hostname.startsWith("
|
|
25
|
+
const hostname = url.hostname.startsWith("auth.")
|
|
26
26
|
? url.hostname
|
|
27
|
-
: `
|
|
28
|
-
const baseURL = `${url.protocol}//${hostname}/
|
|
27
|
+
: `auth.${url.hostname.replace(/^api\./, '')}`;
|
|
28
|
+
const baseURL = `${url.protocol}//${hostname}/workspace/${workspaceId}/api/v1/${baseSuffix}`;
|
|
29
29
|
|
|
30
30
|
const client = axios.create({ baseURL });
|
|
31
31
|
|