@centrali-io/centrali-mcp 6.8.0 → 6.9.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/dist/index.js +2 -0
- package/dist/tools/describe.js +72 -0
- package/dist/tools/events.d.ts +3 -0
- package/dist/tools/events.js +257 -0
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/tools/describe.ts +84 -1
- package/src/tools/events.ts +293 -0
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ const describe_js_1 = require("./tools/describe.js");
|
|
|
26
26
|
const auth_providers_js_1 = require("./tools/auth-providers.js");
|
|
27
27
|
const service_accounts_js_1 = require("./tools/service-accounts.js");
|
|
28
28
|
const webhook_subscriptions_js_1 = require("./tools/webhook-subscriptions.js");
|
|
29
|
+
const events_js_1 = require("./tools/events.js");
|
|
29
30
|
const structures_js_2 = require("./resources/structures.js");
|
|
30
31
|
function getRequiredEnv(name) {
|
|
31
32
|
const value = process.env[name];
|
|
@@ -84,6 +85,7 @@ function main() {
|
|
|
84
85
|
isServiceAccount: true,
|
|
85
86
|
});
|
|
86
87
|
(0, webhook_subscriptions_js_1.registerWebhookSubscriptionTools)(server, sdk);
|
|
88
|
+
(0, events_js_1.registerEventTools)(server, sdk, baseUrl, workspaceId);
|
|
87
89
|
(0, describe_js_1.registerDescribeTools)(server);
|
|
88
90
|
// Register resources
|
|
89
91
|
(0, structures_js_2.registerCollectionResources)(server, sdk);
|
package/dist/tools/describe.js
CHANGED
|
@@ -195,6 +195,17 @@ function registerDescribeTools(server) {
|
|
|
195
195
|
"cancel_webhook_delivery",
|
|
196
196
|
],
|
|
197
197
|
},
|
|
198
|
+
event_log: {
|
|
199
|
+
summary: "Read, replay, trace, and subscribe to the Event Log — the Tier 1 projection unioning inbound HTTP-trigger arrivals, record changes, and outbound webhook deliveries into one chronological, queryable stream. Follow a single event end-to-end across the inbound → function → outbound chain via its correlation id.",
|
|
200
|
+
describeWith: "describe_events",
|
|
201
|
+
tools: [
|
|
202
|
+
"list_events",
|
|
203
|
+
"get_event",
|
|
204
|
+
"replay_event",
|
|
205
|
+
"trace_correlation",
|
|
206
|
+
"subscribe_to_events",
|
|
207
|
+
],
|
|
208
|
+
},
|
|
198
209
|
service_accounts: {
|
|
199
210
|
summary: "Machine identities for backend-to-backend API access. Create service accounts with client_credentials OAuth2 flow, manage roles and groups for fine-grained permissions, and introspect access with permission scanning.",
|
|
200
211
|
describeWith: "describe_service_accounts",
|
|
@@ -1469,6 +1480,67 @@ function registerDescribeTools(server) {
|
|
|
1469
1480
|
],
|
|
1470
1481
|
});
|
|
1471
1482
|
}));
|
|
1483
|
+
// ── Event Log ──────────────────────────────────────────────────────
|
|
1484
|
+
(0, _register_js_1.registerTool)(server, "describe_events", "Get the schema reference for the Centrali Event Log. Explains the Tier 1 projection, queryable fields, the event row shape, correlation-id lineage, and what is replayable.", {}, () => __awaiter(this, void 0, void 0, function* () {
|
|
1485
|
+
return ({
|
|
1486
|
+
content: [
|
|
1487
|
+
{
|
|
1488
|
+
type: "text",
|
|
1489
|
+
text: JSON.stringify({
|
|
1490
|
+
domain: "Event Log",
|
|
1491
|
+
description: "The Tier 1 event projection unions three immutable change logs into one chronological, queryable stream: inbound HTTP-trigger arrivals (inbound_events), record changes (record_change_log), and outbound webhook deliveries (webhook_deliveries). A correlation id stamped at chain entry ties an inbound arrival to the function runs it spawned and the outbound deliveries they produced — follow the whole chain with trace_correlation.",
|
|
1492
|
+
source_tables: {
|
|
1493
|
+
inbound_events: "Inbound HTTP-trigger arrivals. eventType 'inbound.http_trigger'. Replayable (re-dispatch).",
|
|
1494
|
+
record_change_log: "Record create/update/delete. eventType 'record.*'. Immutable history — not replayable.",
|
|
1495
|
+
webhook_deliveries: "Outbound webhook dispatch attempts. eventType 'outbound.webhook_delivery'. Replayable (re-deliver).",
|
|
1496
|
+
},
|
|
1497
|
+
event_row_shape: {
|
|
1498
|
+
eventType: "string — e.g. 'inbound.http_trigger', 'record.created', 'outbound.webhook_delivery'",
|
|
1499
|
+
sourceTable: "'inbound_events' | 'record_change_log' | 'webhook_deliveries'",
|
|
1500
|
+
sourceId: "string — the event's id within its source table; pair with sourceTable as the natural key for get_event / replay_event",
|
|
1501
|
+
correlationId: "string | null — the chain key; pass to trace_correlation for the ordered lineage",
|
|
1502
|
+
timestamp: "ISO 8601 datetime",
|
|
1503
|
+
clockTier: "0 (host-clock inbound) | 1 (DB-clock record/delivery)",
|
|
1504
|
+
note: "list_events returns metadata only (no payload). Fetch the decrypted payload, signature result, dispatch status, request headers, and nested function runs with get_event.",
|
|
1505
|
+
},
|
|
1506
|
+
queryable_fields: ["eventType", "sourceTable", "sourceId", "correlationId", "timestamp", "triggerId"],
|
|
1507
|
+
tools: {
|
|
1508
|
+
list_events: {
|
|
1509
|
+
description: "Query the projection, newest first. All filters optional; AND-combined.",
|
|
1510
|
+
optional_params: ["correlationId", "sourceTable", "eventType", "triggerId", "since", "until", "limit"],
|
|
1511
|
+
note: "triggerId scopes to one integration and only matches inbound_events rows. limit defaults to 50, caps at 200.",
|
|
1512
|
+
example: { sourceTable: "inbound_events", since: "2026-06-01T00:00:00Z", limit: 20 },
|
|
1513
|
+
},
|
|
1514
|
+
get_event: {
|
|
1515
|
+
description: "Full detail for one event by its (sourceTable, sourceId) natural key — decrypted payload plus inbound metadata and nested function runs.",
|
|
1516
|
+
required_params: ["sourceTable", "sourceId"],
|
|
1517
|
+
},
|
|
1518
|
+
replay_event: {
|
|
1519
|
+
description: "Re-dispatch an inbound arrival or re-deliver an outbound webhook. record_change_log events are not replayable.",
|
|
1520
|
+
required_params: ["sourceTable", "sourceId"],
|
|
1521
|
+
},
|
|
1522
|
+
trace_correlation: {
|
|
1523
|
+
description: "The full lineage chain for a correlation id, oldest first — inbound arrival → record changes → outbound deliveries.",
|
|
1524
|
+
required_params: ["correlationId"],
|
|
1525
|
+
},
|
|
1526
|
+
subscribe_to_events: {
|
|
1527
|
+
description: "Create an outbound webhook subscription so an external URL receives a signed payload on each matching event. Returns the whsec_ signing secret once. Manage it afterwards with the webhook tools.",
|
|
1528
|
+
required_params: ["name", "url", "eventTypes"],
|
|
1529
|
+
optional_params: ["recordSlugs", "active"],
|
|
1530
|
+
note: "Deliverable eventTypes today are record events (record_created, record_updated, record_deleted, records_bulk_created) — the outbound webhook primitive's current capability.",
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
tips: [
|
|
1534
|
+
"Start from a correlationId on any row and call trace_correlation to see the whole inbound → function → outbound story in order.",
|
|
1535
|
+
"list_events is metadata-only by design (inbound payloads are encrypted at rest); use get_event for the decrypted payload and signature result.",
|
|
1536
|
+
"Pair list_events(sourceTable='webhook_deliveries') with replay_event to re-drive failed outbound deliveries after fixing the receiver.",
|
|
1537
|
+
"Use triggerId to keep a query scoped to a single integration's inbound traffic.",
|
|
1538
|
+
],
|
|
1539
|
+
}, null, 2),
|
|
1540
|
+
},
|
|
1541
|
+
],
|
|
1542
|
+
});
|
|
1543
|
+
}));
|
|
1472
1544
|
// ── Insights ───────────────────────────────────────────────────────
|
|
1473
1545
|
(0, _register_js_1.registerTool)(server, "describe_insights", "Get the schema reference for Centrali anomaly insights. Explains anomaly detection, severity levels, and the acknowledge/dismiss workflow.", {}, () => __awaiter(this, void 0, void 0, function* () {
|
|
1474
1546
|
return ({
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.registerEventTools = registerEventTools;
|
|
16
|
+
const axios_1 = __importDefault(require("axios"));
|
|
17
|
+
const zod_1 = require("zod");
|
|
18
|
+
const _register_js_1 = require("./_register.js");
|
|
19
|
+
/**
|
|
20
|
+
* Event Log MCP tools (CEN-1302) — make an agent a first-class consumer of the
|
|
21
|
+
* Tier 1 event projection (CEN-1298). The projection unions three immutable
|
|
22
|
+
* change logs into one chronological, queryable stream:
|
|
23
|
+
*
|
|
24
|
+
* - `inbound_events` — inbound HTTP-trigger arrivals (`inbound.http_trigger`)
|
|
25
|
+
* - `record_change_log` — record create/update/delete (`record.*`)
|
|
26
|
+
* - `webhook_deliveries` — outbound webhook dispatch state (`outbound.webhook_delivery`)
|
|
27
|
+
*
|
|
28
|
+
* These tools wrap the data-service Event Log REST surface directly (no SDK
|
|
29
|
+
* manager exists for it yet), mirroring the direct-axios pattern in compute.ts.
|
|
30
|
+
*/
|
|
31
|
+
/** The three source tables the Tier 1 projection unions. */
|
|
32
|
+
const SOURCE_TABLE_VALUES = [
|
|
33
|
+
"inbound_events",
|
|
34
|
+
"record_change_log",
|
|
35
|
+
"webhook_deliveries",
|
|
36
|
+
];
|
|
37
|
+
/** Record events a webhook subscription can deliver (the only events the
|
|
38
|
+
* outbound webhook primitive supports today — inbound/outbound projection
|
|
39
|
+
* rows are not yet deliverable; that arrives with the SSE broadening). */
|
|
40
|
+
const SUBSCRIBABLE_EVENT_VALUES = [
|
|
41
|
+
"record_created",
|
|
42
|
+
"record_updated",
|
|
43
|
+
"record_deleted",
|
|
44
|
+
"records_bulk_created",
|
|
45
|
+
];
|
|
46
|
+
/** Ensure the SDK has a valid bearer token (see compute.ts). */
|
|
47
|
+
function ensureToken(sdk) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
const token = sdk.getToken();
|
|
50
|
+
if (token)
|
|
51
|
+
return token;
|
|
52
|
+
try {
|
|
53
|
+
yield sdk.functions.list({ limit: 1 });
|
|
54
|
+
}
|
|
55
|
+
catch (_a) {
|
|
56
|
+
/* token refresh side effect */
|
|
57
|
+
}
|
|
58
|
+
return sdk.getToken();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/** Build the data-service API base for a workspace, honouring the `api.`
|
|
62
|
+
* gateway-subdomain convention used across the MCP tools (see compute.ts). */
|
|
63
|
+
function dataApiBase(centraliUrl, workspaceId) {
|
|
64
|
+
const url = new URL(centraliUrl);
|
|
65
|
+
const hostname = url.hostname.startsWith("api.") ? url.hostname : `api.${url.hostname}`;
|
|
66
|
+
return `${url.protocol}//${hostname}/data/workspace/${workspaceId}/api/v1`;
|
|
67
|
+
}
|
|
68
|
+
function registerEventTools(server, sdk, centraliUrl, workspaceId) {
|
|
69
|
+
// ── list_events ────────────────────────────────────────────────────
|
|
70
|
+
(0, _register_js_1.registerTool)(server, "list_events", "Query the workspace Event Log — the Tier 1 projection that unions inbound HTTP-trigger arrivals, record changes, and outbound webhook deliveries into one chronological stream. Returns metadata-only rows (eventType, sourceTable, sourceId, correlationId, timestamp), newest first. Fetch a single event's full payload with get_event, or follow a chain with trace_correlation.", {
|
|
71
|
+
correlationId: zod_1.z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Only events sharing this correlation id (the chain key stamped at chain entry). Use trace_correlation for the ordered lineage."),
|
|
75
|
+
sourceTable: zod_1.z
|
|
76
|
+
.enum(SOURCE_TABLE_VALUES)
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Restrict to one source: inbound_events (HTTP trigger fires), record_change_log (record changes), or webhook_deliveries (outbound deliveries)."),
|
|
79
|
+
eventType: zod_1.z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Exact event type, e.g. 'inbound.http_trigger', 'record.created', 'outbound.webhook_delivery'."),
|
|
83
|
+
triggerId: zod_1.z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Scope to one integration — the HTTP trigger UUID an inbound arrival came through. Only matches inbound_events rows."),
|
|
87
|
+
since: zod_1.z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("ISO 8601 datetime — include events at or after this time."),
|
|
91
|
+
until: zod_1.z
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("ISO 8601 datetime — include events at or before this time."),
|
|
95
|
+
limit: zod_1.z
|
|
96
|
+
.number()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("Max rows to return (default 50, max 200)."),
|
|
99
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ correlationId, sourceTable, eventType, triggerId, since, until, limit }) {
|
|
100
|
+
try {
|
|
101
|
+
const token = yield ensureToken(sdk);
|
|
102
|
+
const conditions = [];
|
|
103
|
+
if (correlationId)
|
|
104
|
+
conditions.push({ correlationId: { eq: correlationId } });
|
|
105
|
+
if (sourceTable)
|
|
106
|
+
conditions.push({ sourceTable: { eq: sourceTable } });
|
|
107
|
+
if (eventType)
|
|
108
|
+
conditions.push({ eventType: { eq: eventType } });
|
|
109
|
+
if (triggerId)
|
|
110
|
+
conditions.push({ triggerId: { eq: triggerId } });
|
|
111
|
+
if (since)
|
|
112
|
+
conditions.push({ timestamp: { gte: since } });
|
|
113
|
+
if (until)
|
|
114
|
+
conditions.push({ timestamp: { lte: until } });
|
|
115
|
+
const definition = {
|
|
116
|
+
resource: "event-log",
|
|
117
|
+
sort: [{ field: "timestamp", direction: "desc" }],
|
|
118
|
+
page: { limit: limit !== null && limit !== void 0 ? limit : 50 },
|
|
119
|
+
};
|
|
120
|
+
if (conditions.length === 1)
|
|
121
|
+
definition.where = conditions[0];
|
|
122
|
+
else if (conditions.length > 1)
|
|
123
|
+
definition.where = { and: conditions };
|
|
124
|
+
const result = yield axios_1.default.post(`${dataApiBase(centraliUrl, workspaceId)}/event-log/query`, definition, {
|
|
125
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: (0, _register_js_1.formatError)(error, "querying the event log") }],
|
|
134
|
+
isError: true,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}));
|
|
138
|
+
// ── get_event ──────────────────────────────────────────────────────
|
|
139
|
+
(0, _register_js_1.registerTool)(server, "get_event", "Get a single Event Log event by its (sourceTable, sourceId) natural key, with the stored payload decrypted on read. Inbound arrivals also return signature result, dispatch status, request headers, and any replay source; the response nests the function runs sharing the event's correlationId.", {
|
|
140
|
+
sourceTable: zod_1.z
|
|
141
|
+
.enum(SOURCE_TABLE_VALUES)
|
|
142
|
+
.describe("The source table the event lives in (from a list_events row)."),
|
|
143
|
+
sourceId: zod_1.z.string().describe("The event's id within its source table."),
|
|
144
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ sourceTable, sourceId }) {
|
|
145
|
+
try {
|
|
146
|
+
const token = yield ensureToken(sdk);
|
|
147
|
+
const result = yield axios_1.default.get(`${dataApiBase(centraliUrl, workspaceId)}/event-log/${encodeURIComponent(sourceTable)}/${encodeURIComponent(sourceId)}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: "text", text: (0, _register_js_1.formatError)(error, `getting event '${sourceTable}/${sourceId}'`) }],
|
|
155
|
+
isError: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}));
|
|
159
|
+
// ── replay_event ───────────────────────────────────────────────────
|
|
160
|
+
(0, _register_js_1.registerTool)(server, "replay_event", "Re-run an event. For an inbound_events row, re-dispatches the buffered inbound arrival through its original trigger (a new event is recorded, linked via replayedFrom). For a webhook_deliveries row, re-delivers the outbound webhook, preserving the original payload and signature. record_change_log events are immutable history and cannot be replayed.", {
|
|
161
|
+
sourceTable: zod_1.z
|
|
162
|
+
.enum(SOURCE_TABLE_VALUES)
|
|
163
|
+
.describe("The source table of the event to replay (from a list_events / get_event row)."),
|
|
164
|
+
sourceId: zod_1.z.string().describe("The event's id within its source table."),
|
|
165
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ sourceTable, sourceId }) {
|
|
166
|
+
if (sourceTable === "record_change_log") {
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: "record_change_log events are immutable history and cannot be replayed. Only inbound_events (re-dispatch) and webhook_deliveries (re-deliver) are replayable.",
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
isError: true,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const token = yield ensureToken(sdk);
|
|
179
|
+
const base = dataApiBase(centraliUrl, workspaceId);
|
|
180
|
+
const path = sourceTable === "inbound_events"
|
|
181
|
+
? `${base}/inbound-events/${encodeURIComponent(sourceId)}/replay`
|
|
182
|
+
: `${base}/webhook-subscriptions/deliveries/${encodeURIComponent(sourceId)}/retry`;
|
|
183
|
+
const result = yield axios_1.default.post(path, {}, {
|
|
184
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
return {
|
|
192
|
+
content: [{ type: "text", text: (0, _register_js_1.formatError)(error, `replaying event '${sourceTable}/${sourceId}'`) }],
|
|
193
|
+
isError: true,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}));
|
|
197
|
+
// ── trace_correlation ──────────────────────────────────────────────
|
|
198
|
+
(0, _register_js_1.registerTool)(server, "trace_correlation", "Return the full lineage chain for a correlation id — every Tier 1 event (inbound arrival → record changes → outbound deliveries) that shares it, oldest first. The canonical way to follow an event end-to-end across the inbound → function → outbound flow.", {
|
|
199
|
+
correlationId: zod_1.z.string().describe("The correlation id shared across the event chain (from any list_events / get_event row)."),
|
|
200
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ correlationId }) {
|
|
201
|
+
try {
|
|
202
|
+
const token = yield ensureToken(sdk);
|
|
203
|
+
const result = yield axios_1.default.get(`${dataApiBase(centraliUrl, workspaceId)}/event-lineage`, {
|
|
204
|
+
params: { correlationId },
|
|
205
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
return {
|
|
213
|
+
content: [{ type: "text", text: (0, _register_js_1.formatError)(error, `tracing correlation '${correlationId}'`) }],
|
|
214
|
+
isError: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}));
|
|
218
|
+
// ── subscribe_to_events ────────────────────────────────────────────
|
|
219
|
+
// Event-namespace convenience over the outbound webhook primitive: lets an
|
|
220
|
+
// agent set up its own production reactive path without first discovering the
|
|
221
|
+
// separate webhook-subscriptions tool family. Backed by the same primitive,
|
|
222
|
+
// so manage the subscription afterwards with the webhook tools
|
|
223
|
+
// (update/rotate/delete_webhook_subscription, list_webhook_deliveries, ...).
|
|
224
|
+
(0, _register_js_1.registerTool)(server, "subscribe_to_events", "Subscribe an external URL to a live event stream by creating an outbound webhook subscription. Centrali POSTs a signed payload (HMAC-SHA256 under a whsec_ secret) on each matching event. The response includes the signing secret — capture it immediately, it is not returned on later reads. Today's deliverable events are record events; manage or inspect the subscription afterwards with the webhook tools (get/update/delete_webhook_subscription, list_webhook_deliveries, retry_webhook_delivery).", {
|
|
225
|
+
name: zod_1.z.string().describe("Display name for the subscription."),
|
|
226
|
+
url: zod_1.z.string().describe("HTTPS URL that will receive POSTed event payloads."),
|
|
227
|
+
eventTypes: zod_1.z
|
|
228
|
+
.array(zod_1.z.enum(SUBSCRIBABLE_EVENT_VALUES))
|
|
229
|
+
.describe("Events to deliver: record_created, record_updated, record_deleted, records_bulk_created."),
|
|
230
|
+
recordSlugs: zod_1.z
|
|
231
|
+
.array(zod_1.z.string())
|
|
232
|
+
.optional()
|
|
233
|
+
.describe("Optional collection slugs to scope the subscription to. Omit for all collections."),
|
|
234
|
+
active: zod_1.z
|
|
235
|
+
.boolean()
|
|
236
|
+
.optional()
|
|
237
|
+
.describe("Whether the subscription is active on creation. Defaults to true."),
|
|
238
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ name, url, eventTypes, recordSlugs, active }) {
|
|
239
|
+
try {
|
|
240
|
+
const input = { name, url, events: eventTypes };
|
|
241
|
+
if (recordSlugs !== undefined)
|
|
242
|
+
input.recordSlugs = recordSlugs;
|
|
243
|
+
if (active !== undefined)
|
|
244
|
+
input.active = active;
|
|
245
|
+
const result = yield sdk.webhookSubscriptions.create(input);
|
|
246
|
+
return {
|
|
247
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: "text", text: (0, _register_js_1.formatError)(error, `subscribing to events as '${name}'`) }],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}));
|
|
257
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centrali-io/centrali-mcp",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.9.0",
|
|
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": "^6.
|
|
28
|
+
"@centrali-io/centrali-sdk": "^6.9.0",
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.28.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { registerDescribeTools } from "./tools/describe.js";
|
|
|
16
16
|
import { registerAuthProviderTools } from "./tools/auth-providers.js";
|
|
17
17
|
import { registerServiceAccountTools } from "./tools/service-accounts.js";
|
|
18
18
|
import { registerWebhookSubscriptionTools } from "./tools/webhook-subscriptions.js";
|
|
19
|
+
import { registerEventTools } from "./tools/events.js";
|
|
19
20
|
import { registerStructureResources, registerCollectionResources } from "./resources/structures.js";
|
|
20
21
|
|
|
21
22
|
function getRequiredEnv(name: string): string {
|
|
@@ -81,6 +82,7 @@ async function main() {
|
|
|
81
82
|
isServiceAccount: true,
|
|
82
83
|
});
|
|
83
84
|
registerWebhookSubscriptionTools(server, sdk);
|
|
85
|
+
registerEventTools(server, sdk, baseUrl, workspaceId);
|
|
84
86
|
registerDescribeTools(server);
|
|
85
87
|
|
|
86
88
|
// Register resources
|
package/src/tools/describe.ts
CHANGED
|
@@ -202,6 +202,18 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
202
202
|
"cancel_webhook_delivery",
|
|
203
203
|
],
|
|
204
204
|
},
|
|
205
|
+
event_log: {
|
|
206
|
+
summary:
|
|
207
|
+
"Read, replay, trace, and subscribe to the Event Log — the Tier 1 projection unioning inbound HTTP-trigger arrivals, record changes, and outbound webhook deliveries into one chronological, queryable stream. Follow a single event end-to-end across the inbound → function → outbound chain via its correlation id.",
|
|
208
|
+
describeWith: "describe_events",
|
|
209
|
+
tools: [
|
|
210
|
+
"list_events",
|
|
211
|
+
"get_event",
|
|
212
|
+
"replay_event",
|
|
213
|
+
"trace_correlation",
|
|
214
|
+
"subscribe_to_events",
|
|
215
|
+
],
|
|
216
|
+
},
|
|
205
217
|
service_accounts: {
|
|
206
218
|
summary:
|
|
207
219
|
"Machine identities for backend-to-backend API access. Create service accounts with client_credentials OAuth2 flow, manage roles and groups for fine-grained permissions, and introspect access with permission scanning.",
|
|
@@ -1606,9 +1618,80 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
1606
1618
|
})
|
|
1607
1619
|
);
|
|
1608
1620
|
|
|
1621
|
+
// ── Event Log ──────────────────────────────────────────────────────
|
|
1622
|
+
|
|
1623
|
+
registerTool<any>(server,
|
|
1624
|
+
"describe_events",
|
|
1625
|
+
"Get the schema reference for the Centrali Event Log. Explains the Tier 1 projection, queryable fields, the event row shape, correlation-id lineage, and what is replayable.",
|
|
1626
|
+
{},
|
|
1627
|
+
async () => ({
|
|
1628
|
+
content: [
|
|
1629
|
+
{
|
|
1630
|
+
type: "text",
|
|
1631
|
+
text: JSON.stringify(
|
|
1632
|
+
{
|
|
1633
|
+
domain: "Event Log",
|
|
1634
|
+
description:
|
|
1635
|
+
"The Tier 1 event projection unions three immutable change logs into one chronological, queryable stream: inbound HTTP-trigger arrivals (inbound_events), record changes (record_change_log), and outbound webhook deliveries (webhook_deliveries). A correlation id stamped at chain entry ties an inbound arrival to the function runs it spawned and the outbound deliveries they produced — follow the whole chain with trace_correlation.",
|
|
1636
|
+
source_tables: {
|
|
1637
|
+
inbound_events: "Inbound HTTP-trigger arrivals. eventType 'inbound.http_trigger'. Replayable (re-dispatch).",
|
|
1638
|
+
record_change_log: "Record create/update/delete. eventType 'record.*'. Immutable history — not replayable.",
|
|
1639
|
+
webhook_deliveries: "Outbound webhook dispatch attempts. eventType 'outbound.webhook_delivery'. Replayable (re-deliver).",
|
|
1640
|
+
},
|
|
1641
|
+
event_row_shape: {
|
|
1642
|
+
eventType: "string — e.g. 'inbound.http_trigger', 'record.created', 'outbound.webhook_delivery'",
|
|
1643
|
+
sourceTable: "'inbound_events' | 'record_change_log' | 'webhook_deliveries'",
|
|
1644
|
+
sourceId: "string — the event's id within its source table; pair with sourceTable as the natural key for get_event / replay_event",
|
|
1645
|
+
correlationId: "string | null — the chain key; pass to trace_correlation for the ordered lineage",
|
|
1646
|
+
timestamp: "ISO 8601 datetime",
|
|
1647
|
+
clockTier: "0 (host-clock inbound) | 1 (DB-clock record/delivery)",
|
|
1648
|
+
note: "list_events returns metadata only (no payload). Fetch the decrypted payload, signature result, dispatch status, request headers, and nested function runs with get_event.",
|
|
1649
|
+
},
|
|
1650
|
+
queryable_fields: ["eventType", "sourceTable", "sourceId", "correlationId", "timestamp", "triggerId"],
|
|
1651
|
+
tools: {
|
|
1652
|
+
list_events: {
|
|
1653
|
+
description: "Query the projection, newest first. All filters optional; AND-combined.",
|
|
1654
|
+
optional_params: ["correlationId", "sourceTable", "eventType", "triggerId", "since", "until", "limit"],
|
|
1655
|
+
note: "triggerId scopes to one integration and only matches inbound_events rows. limit defaults to 50, caps at 200.",
|
|
1656
|
+
example: { sourceTable: "inbound_events", since: "2026-06-01T00:00:00Z", limit: 20 },
|
|
1657
|
+
},
|
|
1658
|
+
get_event: {
|
|
1659
|
+
description: "Full detail for one event by its (sourceTable, sourceId) natural key — decrypted payload plus inbound metadata and nested function runs.",
|
|
1660
|
+
required_params: ["sourceTable", "sourceId"],
|
|
1661
|
+
},
|
|
1662
|
+
replay_event: {
|
|
1663
|
+
description: "Re-dispatch an inbound arrival or re-deliver an outbound webhook. record_change_log events are not replayable.",
|
|
1664
|
+
required_params: ["sourceTable", "sourceId"],
|
|
1665
|
+
},
|
|
1666
|
+
trace_correlation: {
|
|
1667
|
+
description: "The full lineage chain for a correlation id, oldest first — inbound arrival → record changes → outbound deliveries.",
|
|
1668
|
+
required_params: ["correlationId"],
|
|
1669
|
+
},
|
|
1670
|
+
subscribe_to_events: {
|
|
1671
|
+
description: "Create an outbound webhook subscription so an external URL receives a signed payload on each matching event. Returns the whsec_ signing secret once. Manage it afterwards with the webhook tools.",
|
|
1672
|
+
required_params: ["name", "url", "eventTypes"],
|
|
1673
|
+
optional_params: ["recordSlugs", "active"],
|
|
1674
|
+
note: "Deliverable eventTypes today are record events (record_created, record_updated, record_deleted, records_bulk_created) — the outbound webhook primitive's current capability.",
|
|
1675
|
+
},
|
|
1676
|
+
},
|
|
1677
|
+
tips: [
|
|
1678
|
+
"Start from a correlationId on any row and call trace_correlation to see the whole inbound → function → outbound story in order.",
|
|
1679
|
+
"list_events is metadata-only by design (inbound payloads are encrypted at rest); use get_event for the decrypted payload and signature result.",
|
|
1680
|
+
"Pair list_events(sourceTable='webhook_deliveries') with replay_event to re-drive failed outbound deliveries after fixing the receiver.",
|
|
1681
|
+
"Use triggerId to keep a query scoped to a single integration's inbound traffic.",
|
|
1682
|
+
],
|
|
1683
|
+
},
|
|
1684
|
+
null,
|
|
1685
|
+
2
|
|
1686
|
+
),
|
|
1687
|
+
},
|
|
1688
|
+
],
|
|
1689
|
+
})
|
|
1690
|
+
);
|
|
1691
|
+
|
|
1609
1692
|
// ── Insights ───────────────────────────────────────────────────────
|
|
1610
1693
|
|
|
1611
|
-
registerTool<any>(server,
|
|
1694
|
+
registerTool<any>(server,
|
|
1612
1695
|
"describe_insights",
|
|
1613
1696
|
"Get the schema reference for Centrali anomaly insights. Explains anomaly detection, severity levels, and the acknowledge/dismiss workflow.",
|
|
1614
1697
|
{},
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CentraliSDK } from "@centrali-io/centrali-sdk";
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { registerTool, formatError } from "./_register.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Event Log MCP tools (CEN-1302) — make an agent a first-class consumer of the
|
|
9
|
+
* Tier 1 event projection (CEN-1298). The projection unions three immutable
|
|
10
|
+
* change logs into one chronological, queryable stream:
|
|
11
|
+
*
|
|
12
|
+
* - `inbound_events` — inbound HTTP-trigger arrivals (`inbound.http_trigger`)
|
|
13
|
+
* - `record_change_log` — record create/update/delete (`record.*`)
|
|
14
|
+
* - `webhook_deliveries` — outbound webhook dispatch state (`outbound.webhook_delivery`)
|
|
15
|
+
*
|
|
16
|
+
* These tools wrap the data-service Event Log REST surface directly (no SDK
|
|
17
|
+
* manager exists for it yet), mirroring the direct-axios pattern in compute.ts.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** The three source tables the Tier 1 projection unions. */
|
|
21
|
+
const SOURCE_TABLE_VALUES = [
|
|
22
|
+
"inbound_events",
|
|
23
|
+
"record_change_log",
|
|
24
|
+
"webhook_deliveries",
|
|
25
|
+
] as const;
|
|
26
|
+
type SourceTableValue = (typeof SOURCE_TABLE_VALUES)[number];
|
|
27
|
+
|
|
28
|
+
/** Record events a webhook subscription can deliver (the only events the
|
|
29
|
+
* outbound webhook primitive supports today — inbound/outbound projection
|
|
30
|
+
* rows are not yet deliverable; that arrives with the SSE broadening). */
|
|
31
|
+
const SUBSCRIBABLE_EVENT_VALUES = [
|
|
32
|
+
"record_created",
|
|
33
|
+
"record_updated",
|
|
34
|
+
"record_deleted",
|
|
35
|
+
"records_bulk_created",
|
|
36
|
+
] as const;
|
|
37
|
+
type SubscribableEventValue = (typeof SUBSCRIBABLE_EVENT_VALUES)[number];
|
|
38
|
+
|
|
39
|
+
/** Ensure the SDK has a valid bearer token (see compute.ts). */
|
|
40
|
+
async function ensureToken(sdk: CentraliSDK): Promise<string | null> {
|
|
41
|
+
const token = sdk.getToken();
|
|
42
|
+
if (token) return token;
|
|
43
|
+
try {
|
|
44
|
+
await sdk.functions.list({ limit: 1 });
|
|
45
|
+
} catch {
|
|
46
|
+
/* token refresh side effect */
|
|
47
|
+
}
|
|
48
|
+
return sdk.getToken();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Build the data-service API base for a workspace, honouring the `api.`
|
|
52
|
+
* gateway-subdomain convention used across the MCP tools (see compute.ts). */
|
|
53
|
+
function dataApiBase(centraliUrl: string, workspaceId: string): string {
|
|
54
|
+
const url = new URL(centraliUrl);
|
|
55
|
+
const hostname = url.hostname.startsWith("api.") ? url.hostname : `api.${url.hostname}`;
|
|
56
|
+
return `${url.protocol}//${hostname}/data/workspace/${workspaceId}/api/v1`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function registerEventTools(
|
|
60
|
+
server: McpServer,
|
|
61
|
+
sdk: CentraliSDK,
|
|
62
|
+
centraliUrl: string,
|
|
63
|
+
workspaceId: string
|
|
64
|
+
) {
|
|
65
|
+
// ── list_events ────────────────────────────────────────────────────
|
|
66
|
+
registerTool<{
|
|
67
|
+
correlationId?: string;
|
|
68
|
+
sourceTable?: SourceTableValue;
|
|
69
|
+
eventType?: string;
|
|
70
|
+
triggerId?: string;
|
|
71
|
+
since?: string;
|
|
72
|
+
until?: string;
|
|
73
|
+
limit?: number;
|
|
74
|
+
}>(
|
|
75
|
+
server,
|
|
76
|
+
"list_events",
|
|
77
|
+
"Query the workspace Event Log — the Tier 1 projection that unions inbound HTTP-trigger arrivals, record changes, and outbound webhook deliveries into one chronological stream. Returns metadata-only rows (eventType, sourceTable, sourceId, correlationId, timestamp), newest first. Fetch a single event's full payload with get_event, or follow a chain with trace_correlation.",
|
|
78
|
+
{
|
|
79
|
+
correlationId: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Only events sharing this correlation id (the chain key stamped at chain entry). Use trace_correlation for the ordered lineage."),
|
|
83
|
+
sourceTable: z
|
|
84
|
+
.enum(SOURCE_TABLE_VALUES)
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Restrict to one source: inbound_events (HTTP trigger fires), record_change_log (record changes), or webhook_deliveries (outbound deliveries)."),
|
|
87
|
+
eventType: z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("Exact event type, e.g. 'inbound.http_trigger', 'record.created', 'outbound.webhook_delivery'."),
|
|
91
|
+
triggerId: z
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Scope to one integration — the HTTP trigger UUID an inbound arrival came through. Only matches inbound_events rows."),
|
|
95
|
+
since: z
|
|
96
|
+
.string()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("ISO 8601 datetime — include events at or after this time."),
|
|
99
|
+
until: z
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("ISO 8601 datetime — include events at or before this time."),
|
|
103
|
+
limit: z
|
|
104
|
+
.number()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe("Max rows to return (default 50, max 200)."),
|
|
107
|
+
},
|
|
108
|
+
async ({ correlationId, sourceTable, eventType, triggerId, since, until, limit }) => {
|
|
109
|
+
try {
|
|
110
|
+
const token = await ensureToken(sdk);
|
|
111
|
+
const conditions: Record<string, unknown>[] = [];
|
|
112
|
+
if (correlationId) conditions.push({ correlationId: { eq: correlationId } });
|
|
113
|
+
if (sourceTable) conditions.push({ sourceTable: { eq: sourceTable } });
|
|
114
|
+
if (eventType) conditions.push({ eventType: { eq: eventType } });
|
|
115
|
+
if (triggerId) conditions.push({ triggerId: { eq: triggerId } });
|
|
116
|
+
if (since) conditions.push({ timestamp: { gte: since } });
|
|
117
|
+
if (until) conditions.push({ timestamp: { lte: until } });
|
|
118
|
+
|
|
119
|
+
const definition: Record<string, unknown> = {
|
|
120
|
+
resource: "event-log",
|
|
121
|
+
sort: [{ field: "timestamp", direction: "desc" }],
|
|
122
|
+
page: { limit: limit ?? 50 },
|
|
123
|
+
};
|
|
124
|
+
if (conditions.length === 1) definition.where = conditions[0];
|
|
125
|
+
else if (conditions.length > 1) definition.where = { and: conditions };
|
|
126
|
+
|
|
127
|
+
const result = await axios.post(`${dataApiBase(centraliUrl, workspaceId)}/event-log/query`, definition, {
|
|
128
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
132
|
+
};
|
|
133
|
+
} catch (error: unknown) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: formatError(error, "querying the event log") }],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// ── get_event ──────────────────────────────────────────────────────
|
|
143
|
+
registerTool<{ sourceTable: SourceTableValue; sourceId: string }>(
|
|
144
|
+
server,
|
|
145
|
+
"get_event",
|
|
146
|
+
"Get a single Event Log event by its (sourceTable, sourceId) natural key, with the stored payload decrypted on read. Inbound arrivals also return signature result, dispatch status, request headers, and any replay source; the response nests the function runs sharing the event's correlationId.",
|
|
147
|
+
{
|
|
148
|
+
sourceTable: z
|
|
149
|
+
.enum(SOURCE_TABLE_VALUES)
|
|
150
|
+
.describe("The source table the event lives in (from a list_events row)."),
|
|
151
|
+
sourceId: z.string().describe("The event's id within its source table."),
|
|
152
|
+
},
|
|
153
|
+
async ({ sourceTable, sourceId }) => {
|
|
154
|
+
try {
|
|
155
|
+
const token = await ensureToken(sdk);
|
|
156
|
+
const result = await axios.get(
|
|
157
|
+
`${dataApiBase(centraliUrl, workspaceId)}/event-log/${encodeURIComponent(sourceTable)}/${encodeURIComponent(sourceId)}`,
|
|
158
|
+
{ headers: token ? { Authorization: `Bearer ${token}` } : {} }
|
|
159
|
+
);
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
162
|
+
};
|
|
163
|
+
} catch (error: unknown) {
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: "text", text: formatError(error, `getting event '${sourceTable}/${sourceId}'`) }],
|
|
166
|
+
isError: true,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// ── replay_event ───────────────────────────────────────────────────
|
|
173
|
+
registerTool<{ sourceTable: SourceTableValue; sourceId: string }>(
|
|
174
|
+
server,
|
|
175
|
+
"replay_event",
|
|
176
|
+
"Re-run an event. For an inbound_events row, re-dispatches the buffered inbound arrival through its original trigger (a new event is recorded, linked via replayedFrom). For a webhook_deliveries row, re-delivers the outbound webhook, preserving the original payload and signature. record_change_log events are immutable history and cannot be replayed.",
|
|
177
|
+
{
|
|
178
|
+
sourceTable: z
|
|
179
|
+
.enum(SOURCE_TABLE_VALUES)
|
|
180
|
+
.describe("The source table of the event to replay (from a list_events / get_event row)."),
|
|
181
|
+
sourceId: z.string().describe("The event's id within its source table."),
|
|
182
|
+
},
|
|
183
|
+
async ({ sourceTable, sourceId }) => {
|
|
184
|
+
if (sourceTable === "record_change_log") {
|
|
185
|
+
return {
|
|
186
|
+
content: [
|
|
187
|
+
{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: "record_change_log events are immutable history and cannot be replayed. Only inbound_events (re-dispatch) and webhook_deliveries (re-deliver) are replayable.",
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
isError: true,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const token = await ensureToken(sdk);
|
|
197
|
+
const base = dataApiBase(centraliUrl, workspaceId);
|
|
198
|
+
const path =
|
|
199
|
+
sourceTable === "inbound_events"
|
|
200
|
+
? `${base}/inbound-events/${encodeURIComponent(sourceId)}/replay`
|
|
201
|
+
: `${base}/webhook-subscriptions/deliveries/${encodeURIComponent(sourceId)}/retry`;
|
|
202
|
+
const result = await axios.post(path, {}, {
|
|
203
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
207
|
+
};
|
|
208
|
+
} catch (error: unknown) {
|
|
209
|
+
return {
|
|
210
|
+
content: [{ type: "text", text: formatError(error, `replaying event '${sourceTable}/${sourceId}'`) }],
|
|
211
|
+
isError: true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// ── trace_correlation ──────────────────────────────────────────────
|
|
218
|
+
registerTool<{ correlationId: string }>(
|
|
219
|
+
server,
|
|
220
|
+
"trace_correlation",
|
|
221
|
+
"Return the full lineage chain for a correlation id — every Tier 1 event (inbound arrival → record changes → outbound deliveries) that shares it, oldest first. The canonical way to follow an event end-to-end across the inbound → function → outbound flow.",
|
|
222
|
+
{
|
|
223
|
+
correlationId: z.string().describe("The correlation id shared across the event chain (from any list_events / get_event row)."),
|
|
224
|
+
},
|
|
225
|
+
async ({ correlationId }) => {
|
|
226
|
+
try {
|
|
227
|
+
const token = await ensureToken(sdk);
|
|
228
|
+
const result = await axios.get(`${dataApiBase(centraliUrl, workspaceId)}/event-lineage`, {
|
|
229
|
+
params: { correlationId },
|
|
230
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
234
|
+
};
|
|
235
|
+
} catch (error: unknown) {
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: "text", text: formatError(error, `tracing correlation '${correlationId}'`) }],
|
|
238
|
+
isError: true,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// ── subscribe_to_events ────────────────────────────────────────────
|
|
245
|
+
// Event-namespace convenience over the outbound webhook primitive: lets an
|
|
246
|
+
// agent set up its own production reactive path without first discovering the
|
|
247
|
+
// separate webhook-subscriptions tool family. Backed by the same primitive,
|
|
248
|
+
// so manage the subscription afterwards with the webhook tools
|
|
249
|
+
// (update/rotate/delete_webhook_subscription, list_webhook_deliveries, ...).
|
|
250
|
+
registerTool<{
|
|
251
|
+
name: string;
|
|
252
|
+
url: string;
|
|
253
|
+
eventTypes: SubscribableEventValue[];
|
|
254
|
+
recordSlugs?: string[];
|
|
255
|
+
active?: boolean;
|
|
256
|
+
}>(
|
|
257
|
+
server,
|
|
258
|
+
"subscribe_to_events",
|
|
259
|
+
"Subscribe an external URL to a live event stream by creating an outbound webhook subscription. Centrali POSTs a signed payload (HMAC-SHA256 under a whsec_ secret) on each matching event. The response includes the signing secret — capture it immediately, it is not returned on later reads. Today's deliverable events are record events; manage or inspect the subscription afterwards with the webhook tools (get/update/delete_webhook_subscription, list_webhook_deliveries, retry_webhook_delivery).",
|
|
260
|
+
{
|
|
261
|
+
name: z.string().describe("Display name for the subscription."),
|
|
262
|
+
url: z.string().describe("HTTPS URL that will receive POSTed event payloads."),
|
|
263
|
+
eventTypes: z
|
|
264
|
+
.array(z.enum(SUBSCRIBABLE_EVENT_VALUES))
|
|
265
|
+
.describe("Events to deliver: record_created, record_updated, record_deleted, records_bulk_created."),
|
|
266
|
+
recordSlugs: z
|
|
267
|
+
.array(z.string())
|
|
268
|
+
.optional()
|
|
269
|
+
.describe("Optional collection slugs to scope the subscription to. Omit for all collections."),
|
|
270
|
+
active: z
|
|
271
|
+
.boolean()
|
|
272
|
+
.optional()
|
|
273
|
+
.describe("Whether the subscription is active on creation. Defaults to true."),
|
|
274
|
+
},
|
|
275
|
+
async ({ name, url, eventTypes, recordSlugs, active }) => {
|
|
276
|
+
try {
|
|
277
|
+
const input: Record<string, unknown> = { name, url, events: eventTypes };
|
|
278
|
+
if (recordSlugs !== undefined) input.recordSlugs = recordSlugs;
|
|
279
|
+
if (active !== undefined) input.active = active;
|
|
280
|
+
|
|
281
|
+
const result = await sdk.webhookSubscriptions.create(input as any);
|
|
282
|
+
return {
|
|
283
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
284
|
+
};
|
|
285
|
+
} catch (error: unknown) {
|
|
286
|
+
return {
|
|
287
|
+
content: [{ type: "text", text: formatError(error, `subscribing to events as '${name}'`) }],
|
|
288
|
+
isError: true,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
}
|